Merge pull request #1687 from mermaid-js/sidv/mobile

feat: Add mobile support
This commit is contained in:
Sidharth Vinod
2025-04-13 08:25:59 +00:00
committed by GitHub
19 changed files with 699 additions and 158 deletions
+8 -1
View File
@@ -2,12 +2,14 @@
"editor.formatOnSave": true,
"cSpell.blockCheckingWhenLineLengthGreaterThan": 150,
"cSpell.words": [
"appinstalled",
"asyncable",
"Browserslist",
"ckppp",
"corg",
"cssnano",
"esserializer",
"fsegurai",
"gantt",
"gitgraph",
"KROKI",
@@ -16,14 +18,19 @@
"mindmap",
"Pageview",
"pako",
"panmove",
"panstart",
"panzoom",
"pinchmove",
"pinchstart",
"pzoom",
"roughjs",
"Serde",
"serdes",
"Stackable",
"tailwindcss",
"uparrow"
"uparrow",
"zenuml"
],
"vitest.commandLine": "pnpm test:unit",
"vitest.enable": true,
+11
View File
@@ -30,6 +30,7 @@
"@sveltejs/adapter-static": "3.0.8",
"@sveltejs/kit": "2.20.3",
"@sveltejs/vite-plugin-svelte": "^4.0.4",
"@types/hammerjs": "^2.0.46",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.14.1",
"@types/pako": "2.0.3",
@@ -81,10 +82,20 @@
"vitest-dom": "^0.1.1"
},
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.3.2",
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.0",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.5",
"@fontsource-variable/recursive": "^5.2.5",
"@fsegurai/codemirror-theme-vscode-dark": "^6.1.2",
"@fsegurai/codemirror-theme-vscode-light": "^6.1.2",
"@mermaid-js/layout-elk": "^0.1.7",
"@mermaid-js/mermaid-zenuml": "^0.2.0",
"codemirror": "^6.0.1",
"dayjs": "^1.11.13",
"hammerjs": "^2.0.8",
"js-base64": "3.7.7",
"lodash-es": "^4.17.21",
"mermaid": "^11.6.0",
+318
View File
@@ -8,18 +8,48 @@ importers:
.:
dependencies:
'@codemirror/lang-json':
specifier: ^6.0.1
version: 6.0.1
'@codemirror/lang-markdown':
specifier: ^6.3.2
version: 6.3.2
'@codemirror/lang-yaml':
specifier: ^6.1.2
version: 6.1.2
'@codemirror/language':
specifier: ^6.11.0
version: 6.11.0
'@codemirror/state':
specifier: ^6.5.2
version: 6.5.2
'@codemirror/view':
specifier: ^6.36.5
version: 6.36.5
'@fontsource-variable/recursive':
specifier: ^5.2.5
version: 5.2.5
'@fsegurai/codemirror-theme-vscode-dark':
specifier: ^6.1.2
version: 6.1.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-vscode-light':
specifier: ^6.1.2
version: 6.1.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5)(@lezer/highlight@1.2.1)
'@mermaid-js/layout-elk':
specifier: ^0.1.7
version: 0.1.7(mermaid@11.6.0)
'@mermaid-js/mermaid-zenuml':
specifier: ^0.2.0
version: 0.2.0(mermaid@11.6.0)(typescript@5.8.2)
codemirror:
specifier: ^6.0.1
version: 6.0.1
dayjs:
specifier: ^1.11.13
version: 1.11.13
hammerjs:
specifier: ^2.0.8
version: 2.0.8
js-base64:
specifier: 3.7.7
version: 3.7.7
@@ -75,6 +105,9 @@ importers:
'@sveltejs/vite-plugin-svelte':
specifier: ^4.0.4
version: 4.0.4(svelte@5.26.1)(vite@5.4.16(@types/node@22.14.1))
'@types/hammerjs':
specifier: ^2.0.46
version: 2.0.46
'@types/lodash-es':
specifier: ^4.17.12
version: 4.17.12
@@ -284,6 +317,45 @@ packages:
'@chevrotain/utils@11.0.3':
resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
'@codemirror/autocomplete@6.18.6':
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
'@codemirror/commands@6.8.1':
resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==}
'@codemirror/lang-css@6.3.1':
resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
'@codemirror/lang-html@6.4.9':
resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==}
'@codemirror/lang-javascript@6.2.3':
resolution: {integrity: sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==}
'@codemirror/lang-json@6.0.1':
resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==}
'@codemirror/lang-markdown@6.3.2':
resolution: {integrity: sha512-c/5MYinGbFxYl4itE9q/rgN/sMTjOr8XL5OWnC+EaRMLfCbVUmmubTJfdgpfcSS2SCaT7b+Q+xi3l6CgoE+BsA==}
'@codemirror/lang-yaml@6.1.2':
resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==}
'@codemirror/language@6.11.0':
resolution: {integrity: sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==}
'@codemirror/lint@6.8.5':
resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==}
'@codemirror/search@6.5.10':
resolution: {integrity: sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==}
'@codemirror/state@6.5.2':
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
'@codemirror/view@6.36.5':
resolution: {integrity: sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==}
'@csstools/color-helpers@5.0.2':
resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==}
engines: {node: '>=18'}
@@ -487,6 +559,22 @@ packages:
resolution: {integrity: sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==}
engines: {node: '>=6'}
'@fsegurai/codemirror-theme-vscode-dark@6.1.2':
resolution: {integrity: sha512-boId19KW7GTK9Auj2wJw2Woc2G1p3MTvhZPOtTRryiwjDTG+SWxI62fw2m0bA5U0NzDndGbg/6FwTFg+3b49FA==}
peerDependencies:
'@codemirror/language': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
'@lezer/highlight': ^1.0.0
'@fsegurai/codemirror-theme-vscode-light@6.1.2':
resolution: {integrity: sha512-/RV/om9okBxOPv6SXXnpDwqd1KdZuutBZnUFGhvVHLi2TIRWFpPMagdO8d2uKnEOMyRg/98DN7x3KLE19bSquw==}
peerDependencies:
'@codemirror/language': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
'@lezer/highlight': ^1.0.0
'@headlessui-float/vue@0.14.4':
resolution: {integrity: sha512-MSyWCxUTueeex+veRCf++q4KM/fa4HOe9HDttzGrtgVDBULkGduFK6ItJh7EHJp2U/dY7qpyDUqp2KCHpCEplw==}
peerDependencies:
@@ -559,6 +647,36 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@lezer/common@1.2.3':
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
'@lezer/css@1.1.11':
resolution: {integrity: sha512-FuAnusbLBl1SEAtfN8NdShxYJiESKw9LAFysfea1T96jD3ydBn12oYjaSG1a04BQRIUd93/0D8e5CV1cUMkmQg==}
'@lezer/highlight@1.2.1':
resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==}
'@lezer/html@1.3.10':
resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==}
'@lezer/javascript@1.4.21':
resolution: {integrity: sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==}
'@lezer/json@1.0.3':
resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==}
'@lezer/lr@1.4.2':
resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==}
'@lezer/markdown@1.4.2':
resolution: {integrity: sha512-iYewCigG/517D0xJPQd7RGaCjZAFwROiH8T9h7OTtz0bRVtkxzFhGBFJ9JGKgBBs4uuo1cvxzyQ5iKhDLMcLUQ==}
'@lezer/yaml@1.0.3':
resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==}
'@marijn/find-cluster-break@1.0.2':
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
'@mermaid-js/layout-elk@0.1.7':
resolution: {integrity: sha512-G3AJ2jMaCAqky2CT3z/sf3pK5UuS3tne98GsXDl3PkKByCmPmOYmJPf+6oX5PUlV3HNWWHuSgtZ9NU/CZDSuHQ==}
peerDependencies:
@@ -850,6 +968,9 @@ packages:
'@types/geojson@7946.0.16':
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
'@types/hammerjs@2.0.46':
resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==}
'@types/istanbul-lib-coverage@2.0.6':
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
@@ -1279,6 +1400,9 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
codemirror@6.0.1:
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1340,6 +1464,9 @@ packages:
cose-base@2.2.0:
resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -2007,6 +2134,10 @@ packages:
hachure-fill@0.5.2:
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
hammerjs@2.0.8:
resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==}
engines: {node: '>=0.8.0'}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -3259,6 +3390,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
style-mod@4.1.2:
resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
style-to-object@1.0.8:
resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
@@ -3679,6 +3813,9 @@ packages:
peerDependencies:
vue: ^3.2.0
w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
@@ -3842,6 +3979,106 @@ snapshots:
'@chevrotain/utils@11.0.3': {}
'@codemirror/autocomplete@6.18.6':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
'@lezer/common': 1.2.3
'@codemirror/commands@6.8.1':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
'@lezer/common': 1.2.3
'@codemirror/lang-css@6.3.1':
dependencies:
'@codemirror/autocomplete': 6.18.6
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@lezer/common': 1.2.3
'@lezer/css': 1.1.11
'@codemirror/lang-html@6.4.9':
dependencies:
'@codemirror/autocomplete': 6.18.6
'@codemirror/lang-css': 6.3.1
'@codemirror/lang-javascript': 6.2.3
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
'@lezer/common': 1.2.3
'@lezer/css': 1.1.11
'@lezer/html': 1.3.10
'@codemirror/lang-javascript@6.2.3':
dependencies:
'@codemirror/autocomplete': 6.18.6
'@codemirror/language': 6.11.0
'@codemirror/lint': 6.8.5
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
'@lezer/common': 1.2.3
'@lezer/javascript': 1.4.21
'@codemirror/lang-json@6.0.1':
dependencies:
'@codemirror/language': 6.11.0
'@lezer/json': 1.0.3
'@codemirror/lang-markdown@6.3.2':
dependencies:
'@codemirror/autocomplete': 6.18.6
'@codemirror/lang-html': 6.4.9
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
'@lezer/common': 1.2.3
'@lezer/markdown': 1.4.2
'@codemirror/lang-yaml@6.1.2':
dependencies:
'@codemirror/autocomplete': 6.18.6
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@lezer/yaml': 1.0.3
'@codemirror/language@6.11.0':
dependencies:
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
style-mod: 4.1.2
'@codemirror/lint@6.8.5':
dependencies:
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
crelt: 1.0.6
'@codemirror/search@6.5.10':
dependencies:
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
crelt: 1.0.6
'@codemirror/state@6.5.2':
dependencies:
'@marijn/find-cluster-break': 1.0.2
'@codemirror/view@6.36.5':
dependencies:
'@codemirror/state': 6.5.2
style-mod: 4.1.2
w3c-keyname: 2.2.8
'@csstools/color-helpers@5.0.2': {}
'@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
@@ -3978,6 +4215,20 @@ snapshots:
'@fortawesome/fontawesome-free@6.7.2': {}
'@fsegurai/codemirror-theme-vscode-dark@6.1.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-vscode-light@6.1.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
'@lezer/highlight': 1.2.1
'@headlessui-float/vue@0.14.4(@headlessui/vue@1.7.23(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))':
dependencies:
'@floating-ui/core': 1.6.9
@@ -4064,6 +4315,53 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@lezer/common@1.2.3': {}
'@lezer/css@1.1.11':
dependencies:
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@lezer/highlight@1.2.1':
dependencies:
'@lezer/common': 1.2.3
'@lezer/html@1.3.10':
dependencies:
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@lezer/javascript@1.4.21':
dependencies:
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@lezer/json@1.0.3':
dependencies:
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@lezer/lr@1.4.2':
dependencies:
'@lezer/common': 1.2.3
'@lezer/markdown@1.4.2':
dependencies:
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/yaml@1.0.3':
dependencies:
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@marijn/find-cluster-break@1.0.2': {}
'@mermaid-js/layout-elk@0.1.7(mermaid@11.6.0)':
dependencies:
d3: 7.9.0
@@ -4349,6 +4647,8 @@ snapshots:
'@types/geojson@7946.0.16': {}
'@types/hammerjs@2.0.46': {}
'@types/istanbul-lib-coverage@2.0.6': {}
'@types/json-schema@7.0.15': {}
@@ -4901,6 +5201,16 @@ snapshots:
clsx@2.1.1: {}
codemirror@6.0.1:
dependencies:
'@codemirror/autocomplete': 6.18.6
'@codemirror/commands': 6.8.1
'@codemirror/language': 6.11.0
'@codemirror/lint': 6.8.5
'@codemirror/search': 6.5.10
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.5
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -4950,6 +5260,8 @@ snapshots:
dependencies:
layout-base: 2.0.1
crelt@1.0.6: {}
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -5735,6 +6047,8 @@ snapshots:
hachure-fill@0.5.2: {}
hammerjs@2.0.8: {}
has-flag@4.0.0: {}
has-symbols@1.1.0: {}
@@ -6859,6 +7173,8 @@ snapshots:
strip-json-comments@3.1.1: {}
style-mod@4.1.2: {}
style-to-object@1.0.8:
dependencies:
inline-style-parser: 0.2.4
@@ -7249,6 +7565,8 @@ snapshots:
'@vue/devtools-api': 6.6.4
vue: 3.5.13(typescript@5.8.2)
w3c-keyname@2.2.8: {}
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
+2 -4
View File
@@ -4,16 +4,14 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Online FlowChart &amp; Diagrams Editor - Mermaid Live Editor</title>
<meta
name="og:image"
content="https://github.com/mermaid-js/mermaid/raw/develop/img/header.png" />
<meta name="og:image" content="%sveltekit.assets%/favicon.svg" />
<link rel="canonical" href="https://mermaid.live" />
<meta
name="description"
content="Simplify documentation and avoid heavy tools. Open source Visio Alternative. Commonly used for explaining your code! Mermaid is a simple markdown-like script language for generating charts from text via javascript." />
<link rel="icon" type="image/png" href="%sveltekit.assets%/favicon.svg" />
<link rel="mask-icon" href="%sveltekit.assets%/favicon.svg" color="#000000" />
<meta name="theme-color" content="#6366F1" />
<meta name="theme-color" content="#ff3670" />
<link rel="manifest" href="%sveltekit.assets%/manifest.json" />
%sveltekit.head%
</head>
+101
View File
@@ -0,0 +1,101 @@
<script lang="ts">
import type { EditorProps } from '$/types';
import { stateStore } from '$/util/state';
import { initEditor } from '$lib/util/monacoExtra';
import { errorDebug } from '$lib/util/util';
import { mode } from 'mode-watcher';
import * as monaco from 'monaco-editor';
import monacoEditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import monacoJsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import { onMount } from 'svelte';
const { onUpdate }: EditorProps = $props();
let divElement: HTMLDivElement | undefined = $state();
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
let editorOptions = {
minimap: {
enabled: false
},
overviewRulerLanes: 0
} satisfies monaco.editor.IStandaloneEditorConstructionOptions;
let currentText = '';
onMount(() => {
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
return new monacoJsonWorker();
}
return new monacoEditorWorker();
}
};
if (!divElement) {
throw new Error('divEl is undefined');
}
initEditor(monaco);
errorDebug();
editor = monaco.editor.create(divElement, editorOptions);
editor.onDidChangeModelContent(({ isFlush }) => {
const newText = editor?.getValue();
if (!newText || currentText === newText || isFlush) {
return;
}
currentText = newText;
onUpdate(currentText);
});
const unsubscribeState = stateStore.subscribe(({ errorMarkers, editorMode, code, mermaid }) => {
if (!editor) {
return;
}
// Update editor text if it's different
const newText = editorMode === 'code' ? code : mermaid;
if (newText !== currentText) {
editor.setScrollTop(0);
editor.setValue(newText);
currentText = newText;
}
// Update editor mode if it's different
const language = editorMode === 'code' ? 'mermaid' : 'json';
const model = editor.getModel();
if (!model) {
console.error("editor model doesn't exist");
return;
}
if (model.getLanguageId() !== language) {
monaco.editor.setModelLanguage(model, language);
}
// Display/clear errors
monaco.editor.setModelMarkers(model, 'mermaid', errorMarkers);
});
const unsubscribeMode = mode.subscribe((mode) => {
editor && monaco.editor.setTheme(`mermaid${mode === 'dark' ? '-dark' : ''}`);
});
const resizeObserver = new ResizeObserver((entries) => {
editor?.layout({
height: entries[0].contentRect.height,
width: entries[0].contentRect.width
});
});
if (divElement.parentElement) {
resizeObserver.observe(divElement);
}
return () => {
unsubscribeState();
unsubscribeMode();
resizeObserver.disconnect();
editor?.dispose();
};
});
</script>
<div bind:this={divElement} id="editor" class="h-full flex-grow overflow-hidden"></div>
+11 -98
View File
@@ -1,117 +1,30 @@
<script lang="ts">
import DesktopEditor from '$/components/DesktopEditor.svelte';
import McWrapper from '$/components/McWrapper.svelte';
import MermaidChartIcon from '$/components/MermaidChartIcon.svelte';
import MobileEditor from '$/components/MobileEditor.svelte';
import { Button } from '$/components/ui/button';
import { TID } from '$/constants';
import { env } from '$/util/env';
import type { EditorMode } from '$lib/types';
import { initEditor } from '$lib/util/monacoExtra';
import { stateStore, updateCode, updateConfig, urlsStore } from '$lib/util/state';
import { errorDebug } from '$lib/util/util';
import { mode } from 'mode-watcher';
import * as monaco from 'monaco-editor';
import monacoEditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import monacoJsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import { onMount } from 'svelte';
import ExclamationCircleIcon from '~icons/material-symbols/error-outline-rounded';
let divElement: HTMLDivElement | undefined = $state();
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
let editorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
minimap: {
enabled: false
},
theme: 'mermaid',
overviewRulerLanes: 0
};
let currentText = '';
const unsubscribeState = stateStore.subscribe(({ errorMarkers, editorMode, code, mermaid }) => {
if (!editor) {
return;
}
// Update editor text if it's different
const newText = editorMode === 'code' ? code : mermaid;
if (newText !== currentText) {
editor.setScrollTop(0);
editor.setValue(newText);
currentText = newText;
}
// Update editor mode if it's different
const language = editorMode === 'code' ? 'mermaid' : 'json';
const model = editor.getModel();
if (!model) {
console.error("editor model doesn't exist");
return;
}
if (model.getLanguageId() !== language) {
monaco.editor.setModelLanguage(model, language);
}
// Display/clear errors
monaco.editor.setModelMarkers(model, 'mermaid', errorMarkers);
});
const handleUpdate = (text: string, mode: EditorMode) => {
if (mode === 'code') {
let { isMobile }: { isMobile: boolean } = $props();
const onUpdate = (text: string) => {
if ($stateStore.editorMode === 'code') {
updateCode(text);
} else {
updateConfig(text);
}
};
onMount(() => {
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
return new monacoJsonWorker();
}
return new monacoEditorWorker();
}
};
if (!divElement) {
throw new Error('divEl is undefined');
}
initEditor(monaco);
errorDebug();
editor = monaco.editor.create(divElement, editorOptions);
editor.onDidChangeModelContent(({ isFlush }) => {
const newText = editor?.getValue();
if (!newText || currentText === newText || isFlush) {
return;
}
currentText = newText;
handleUpdate(currentText, $stateStore.editorMode);
});
const unsubscribeMode = mode.subscribe((mode) => {
editor && monaco.editor.setTheme(`mermaid${mode === 'dark' ? '-dark' : ''}`);
});
const resizeObserver = new ResizeObserver((entries) => {
editor?.layout({
height: entries[0].contentRect.height,
width: entries[0].contentRect.width
});
});
if (divElement.parentElement) {
resizeObserver.observe(divElement);
}
return () => {
unsubscribeState();
unsubscribeMode();
resizeObserver.disconnect();
editor?.dispose();
};
});
</script>
<div class="flex h-full flex-col pt-1">
<div bind:this={divElement} id="editor" class="h-full flex-grow overflow-hidden"></div>
<div class="flex h-full flex-col">
{#if isMobile}
<MobileEditor {onUpdate} />
{:else}
<DesktopEditor {onUpdate} />
{/if}
{#if $stateStore.error instanceof Error}
<div class="flex flex-col text-sm" data-testid={TID.errorContainer}>
<div class="flex items-center justify-between gap-2 bg-slate-900 p-2 text-white">
+1 -1
View File
@@ -8,6 +8,6 @@
let { children }: Props = $props();
</script>
<div class="hidden h-12 items-center justify-between gap-2 rounded-2xl bg-muted p-3 md:flex">
<div class="flex h-12 items-center justify-between gap-2 rounded-2xl bg-muted p-3">
{@render children()}
</div>
+99
View File
@@ -0,0 +1,99 @@
<script lang="ts">
import type { EditorProps } from '$/types';
import { stateStore } from '$/util/state';
import { json, jsonLanguage } from '@codemirror/lang-json';
import { markdown } from '@codemirror/lang-markdown';
import { yamlFrontmatter } from '@codemirror/lang-yaml';
import { language } from '@codemirror/language';
import { Compartment, EditorState } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { vsCodeDark } from '@fsegurai/codemirror-theme-vscode-dark';
import { vsCodeLight } from '@fsegurai/codemirror-theme-vscode-light';
import { basicSetup } from 'codemirror';
import { mode } from 'mode-watcher';
import { onMount } from 'svelte';
let editorView: EditorView | undefined;
let editorContainer: HTMLDivElement;
let currentText = $state('');
const { onUpdate }: EditorProps = $props();
onMount(() => {
const themeCompartment = new Compartment();
const languageCompartment = new Compartment();
editorView = new EditorView({
state: EditorState.create({
doc: currentText,
extensions: [
basicSetup,
languageCompartment.of([]),
themeCompartment.of([]),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
const newText = update.state.doc.toString();
if (currentText === newText) {
return;
}
currentText = newText;
onUpdate(newText);
}
}),
EditorView.theme({
'&.cm-focused': {
outline: 'none'
},
'&.cm-editor': {
height: '100%'
},
'&.cm-scroller': {
overflow: 'auto'
}
})
]
}),
parent: editorContainer
});
const unsubscribeMode = mode.subscribe((mode) => {
editorView?.dispatch({
effects: themeCompartment.reconfigure(mode === 'dark' ? vsCodeDark : vsCodeLight)
});
});
const unsubscribeState = stateStore.subscribe(({ editorMode, code, mermaid }) => {
const text = editorMode === 'code' ? code : mermaid;
if (currentText === text || !editorView) {
return;
}
currentText = text;
editorView.dispatch({
changes: {
from: 0,
to: editorView.state.doc.length,
insert: text
}
});
const stateLanguage = editorView.state.facet(language);
const isStateJson = stateLanguage === jsonLanguage;
const isCodeJson = editorMode === 'config';
if (stateLanguage && isStateJson === isCodeJson) {
return;
}
editorView.dispatch({
effects: languageCompartment.reconfigure(
isCodeJson ? json() : yamlFrontmatter({ content: markdown() })
)
});
});
return () => {
unsubscribeMode();
unsubscribeState();
editorView?.destroy();
};
});
</script>
<div bind:this={editorContainer} class="size-full"></div>
+5 -3
View File
@@ -22,10 +22,11 @@
import DropdownNavMenu from './DropdownNavMenu.svelte';
interface Props {
mobileToggle?: Snippet;
children: Snippet;
}
let { children }: Props = $props();
let { children, mobileToggle }: Props = $props();
const isReferral = document.referrer.includes(MCBaseURL);
@@ -81,7 +82,7 @@
</div>
{/if}
<nav class="z-50 flex p-6">
<nav class="z-50 flex p-4 sm:p-6">
<div class="flex flex-1 items-center gap-4">
<MainMenu />
<div
@@ -89,7 +90,7 @@
class="flex items-center justify-center gap-4 font-medium"
class:flex-row-reverse={isReferral}>
<a href="/" class="whitespace-nowrap text-accent">
{#if !isReferral}
{#if !isReferral && !mobileToggle}
Mermaid
{/if}
Live Editor
@@ -132,4 +133,5 @@
<Separator orientation="vertical" />
{@render children()}
</div>
{@render mobileToggle?.()}
</nav>
+7 -3
View File
@@ -17,13 +17,17 @@
<ArrowsToCircleIcon />
</Button>
<Separator orientation="vertical" />
<Button variant="ghost" size="icon" onclick={() => panZoomState.zoomOut()}>
<Button
variant="ghost"
size="icon"
class="hidden sm:block"
onclick={() => panZoomState.zoomOut()}>
<MagnifyingGlassMinusIcon />
</Button>
<Button variant="ghost" size="icon" onclick={() => panZoomState.zoomIn()}>
<Button variant="ghost" size="icon" class="hidden sm:block" onclick={() => panZoomState.zoomIn()}>
<MagnifyingGlassPlusIcon />
</Button>
<Separator orientation="vertical" />
<Separator orientation="vertical" class="hidden sm:block" />
<Button variant="ghost" size="icon" title="Full Screen" href={$urlsStore.view} target="_blank">
<ExpandIcon />
</Button>
+1 -1
View File
@@ -7,7 +7,7 @@
<Dialog.Trigger>
<ShieldIcon />
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Content class="max-h-full overflow-y-auto">
<Dialog.Header>
<Dialog.Title class="flex items-center gap-2 text-xl">
<ShieldIcon class="size-8 text-green-700" />
+4
View File
@@ -101,3 +101,7 @@ export interface ErrorHash {
}
export type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
export interface EditorProps {
onUpdate: (text: string) => void;
}
+48
View File
@@ -1,4 +1,5 @@
import type { State } from '$/types';
import Hammer from 'hammerjs';
import type { Point } from 'mermaid/dist/types.js';
import panzoom from 'svg-pan-zoom';
type PanZoom = typeof panzoom;
@@ -24,9 +25,56 @@ export class PanZoomState {
}
public updateElement(diagramView: SVGElement, { pan, zoom }: Pick<State, 'pan' | 'zoom'>) {
this.pzoom?.destroy();
let hammer: HammerManager | undefined;
this.pzoom = panzoom(diagramView, {
center: true,
controlIconsEnabled: false,
customEventsHandler: {
haltEventListeners: ['touchstart', 'touchend', 'touchmove', 'touchleave', 'touchcancel'],
init: function (options) {
const instance = options.instance;
let initialScale = 1;
let pannedX = 0;
let pannedY = 0;
hammer = new Hammer(options.svgElement);
const resetPanned = () => {
pannedX = 0;
pannedY = 0;
};
const handlePan = (event: HammerInput) => {
instance.panBy({ x: event.deltaX - pannedX, y: event.deltaY - pannedY });
pannedX = event.deltaX;
pannedY = event.deltaY;
};
hammer.get('pinch').set({ enable: true });
hammer.on('panstart panmove', function (event) {
if (event.type === 'panstart') {
resetPanned();
}
handlePan(event);
});
hammer.on('pinchstart pinchmove', function (event) {
if (event.type === 'pinchstart') {
initialScale = instance.getZoom();
resetPanned();
}
instance.zoomAtPoint(initialScale * event.scale, {
x: event.center.x,
y: event.center.y
});
handlePan(event);
});
options.svgElement.addEventListener('touchmove', function (event) {
event.preventDefault();
});
},
destroy: function () {
hammer?.destroy();
}
},
fit: true,
maxZoom: 12,
minZoom: 0.2,
+1 -1
View File
@@ -70,7 +70,7 @@
</script>
<div
class="flex w-full bg-[#E0095F] p-1.5"
class="flex w-full items-center bg-[#E0095F] p-1.5"
role="banner"
onmouseenter={() => (shouldAnimate = false)}
onmouseleave={() => (shouldAnimate = true)}>
+2
View File
@@ -85,8 +85,10 @@ const delaysPerEvent = {
loadGist: defaultDelay,
loadSampleDiagram: defaultDelay,
migration: defaultDelay,
mobileViewToggle: defaultDelay,
panZoom: minutesToMilliSeconds(10),
playgroundToggle: 0,
pwaInstalled: defaultDelay,
render: minutesToMilliSeconds(5),
renderDiagram: defaultDelay,
themeChange: defaultDelay,
+1 -1
View File
@@ -42,7 +42,7 @@
<ModeWatcher />
<Toaster />
<main class="h-screen">
<main class="h-[100dvh]">
{@render children()}
</main>
+74 -40
View File
@@ -13,12 +13,14 @@
import SyncRoughToolbar from '$/components/SyncRoughToolbar.svelte';
import { Button } from '$/components/ui/button';
import * as Resizable from '$/components/ui/resizable';
import { Switch } from '$/components/ui/switch';
import { Toggle } from '$/components/ui/toggle';
import VersionSecurityToolbar from '$/components/VersionSecurityToolbar.svelte';
import View from '$/components/View.svelte';
import type { EditorMode, Tab } from '$/types';
import { PanZoomState } from '$/util/panZoom';
import { stateStore, updateCodeStore, urlsStore } from '$/util/state';
import { logEvent } from '$/util/stats';
import { initHandler } from '$/util/util';
import { onMount } from 'svelte';
import CodeIcon from '~icons/custom/code';
@@ -45,15 +47,41 @@
}
];
let width = $state(0);
let isMobile = $derived(width < 640);
let isViewMode = $state(true);
onMount(async () => {
await initHandler();
window.addEventListener('appinstalled', () => {
logEvent('pwaInstalled', { isMobile });
});
});
let isHistoryOpen = $state(false);
let editorPane: Resizable.Pane | undefined;
$effect(() => {
if (isMobile) {
editorPane?.resize(50);
}
});
</script>
<div class="flex h-full flex-col overflow-hidden">
<Navbar>
{#snippet mobileToggle()}
<div class="flex items-center gap-2">
Edit <Switch
id="editorMode"
class="data-[state=checked]:bg-accent"
bind:checked={isViewMode}
onclick={() => {
logEvent('mobileViewToggle');
}} /> View
</div>
{/snippet}
<Navbar mobileToggle={isMobile ? mobileToggle : undefined}>
<Toggle bind:pressed={isHistoryOpen} size="sm">
<HistoryIcon />
</Toggle>
@@ -70,47 +98,53 @@
</McWrapper>
</Navbar>
<div class="flex flex-1 overflow-hidden">
<Resizable.PaneGroup direction="horizontal" autoSaveId="liveEditor" class="p-6 pt-0">
<Resizable.Pane defaultSize={30} minSize={15} class="hidden md:block">
<div class="flex h-full flex-col gap-6" id="editorPane">
<Card
onselect={tabSelectHandler}
isOpen
tabs={editorTabs}
activeTabID={$stateStore.editorMode}
isClosable={false}>
{#snippet actions()}
<DiagramDocButton />
{/snippet}
<Editor />
</Card>
<div class="flex flex-1 flex-col overflow-hidden" bind:clientWidth={width}>
<div
class={[
'size-full',
isMobile && ['w-[200%] duration-300', isViewMode && '-translate-x-1/2']
]}>
<Resizable.PaneGroup
direction="horizontal"
autoSaveId="liveEditor"
class="gap-4 p-2 pt-0 sm:gap-0 sm:p-6 sm:pt-0">
<Resizable.Pane bind:this={editorPane} defaultSize={30} minSize={15}>
<div class="flex h-full flex-col gap-4 sm:gap-6">
<Card
onselect={tabSelectHandler}
isOpen
tabs={editorTabs}
activeTabID={$stateStore.editorMode}
isClosable={false}>
{#snippet actions()}
<DiagramDocButton />
{/snippet}
<Editor {isMobile} />
</Card>
<div class="group flex flex-wrap justify-between gap-6">
<Preset />
<Actions />
<div class="group flex flex-wrap justify-between gap-4 sm:gap-6">
<Preset />
<Actions />
</div>
</div>
</div>
</Resizable.Pane>
<Resizable.Handle class="mr-1 opacity-0" />
<Resizable.Pane minSize={15} class="relative flex h-full flex-1 flex-col overflow-hidden">
<View {panZoomState} shouldShowGrid={$stateStore.grid} />
<div class="absolute right-0 top-0"><PanZoomToolbar {panZoomState} /></div>
<div class="absolute bottom-0 right-0"><VersionSecurityToolbar /></div>
<div class="absolute bottom-0 left-5"><SyncRoughToolbar /></div>
<div class="rounded bg-primary p-2 text-center shadow md:hidden">
Code editing not supported on mobile. Please use a desktop browser.
</div>
</Resizable.Pane>
{#if isHistoryOpen}
<Resizable.Handle class="ml-1 hidden opacity-0 md:block" />
<Resizable.Pane
minSize={15}
defaultSize={30}
class="hidden h-full flex-grow flex-col md:flex">
<History />
</Resizable.Pane>
{/if}
</Resizable.PaneGroup>
<Resizable.Handle class="mr-1 hidden opacity-0 sm:block" />
<Resizable.Pane minSize={15} class="relative flex h-full flex-1 flex-col overflow-hidden">
<View {panZoomState} shouldShowGrid={$stateStore.grid} />
<div class="absolute right-0 top-0"><PanZoomToolbar {panZoomState} /></div>
<div class="absolute bottom-0 right-0"><VersionSecurityToolbar /></div>
<div class="absolute bottom-0 left-0 sm:left-5"><SyncRoughToolbar /></div>
</Resizable.Pane>
{#if isHistoryOpen}
<Resizable.Handle class="ml-1 hidden opacity-0 sm:block" />
<Resizable.Pane
minSize={15}
defaultSize={30}
class="hidden h-full flex-grow flex-col sm:flex">
<History />
</Resizable.Pane>
{/if}
</Resizable.PaneGroup>
</div>
</div>
</div>
+2 -2
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 491 491" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M490.16,84.61C490.16,37.912 452.248,0 405.55,0L84.61,0C37.912,0 0,37.912 0,84.61L0,405.55C0,452.248 37.912,490.16 84.61,490.16L405.55,490.16C452.248,490.16 490.16,452.248 490.16,405.55L490.16,84.61Z" style="fill:rgb(255,54,112);"/>
<svg width="100%" height="100%" viewBox="0 0 490 490" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M490.16,84.61C490.16,37.912 452.248,0 405.55,0L84.61,0C37.912,0 0,37.912 0,84.61L0,405.55C0,452.248 37.912,490.16 84.61,490.16L405.55,490.16C452.248,490.16 490.16,452.248 490.16,405.55L490.16,84.61Z" style="fill:#ff3670;"/>
<path d="M407.48,111.18C335.587,108.103 269.573,152.338 245.08,220C220.587,152.338 154.573,108.103 82.68,111.18C80.285,168.229 107.577,222.632 154.74,254.82C178.908,271.419 193.35,298.951 193.27,328.27L193.27,379.13L296.9,379.13L296.9,328.27C296.816,298.953 311.255,271.42 335.42,254.82C382.596,222.644 409.892,168.233 407.48,111.18Z" style="fill:white;fill-rule:nonzero;"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

+3 -3
View File
@@ -9,10 +9,10 @@
}
],
"start_url": "/edit",
"background_color": "#6366F1",
"background_color": "#ff3670",
"display": "standalone",
"scope": "/edit",
"theme_color": "#6366F1",
"theme_color": "#ff3670",
"description": "FlowChart & Diagrams Editor.",
"orientation": "landscape"
"orientation": "natural"
}