diff --git a/.prettierrc b/.prettierrc
index 10fae09d63..3c05b6fb9c 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,5 +1,5 @@
{
- "useTabs": true,
+ "useTabs": false,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
diff --git a/package-lock.json b/package-lock.json
index b105746c9a..677155f73b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,60 +9,106 @@
"version": "0.0.1",
"dependencies": {
"blockly": "^12.5.1",
+ "idb": "^8.0.3",
"socket.io": "^4.8.3",
"socket.io-client": "^4.8.3",
"uuid": "^14.0.0",
- "yaml": "^2.8.4"
+ "yaml": "^2.9.0"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.1",
"@sveltejs/adapter-static": "^3.0.10",
- "@sveltejs/kit": "^2.59.0",
- "@sveltejs/vite-plugin-svelte": "^7.0.0",
+ "@sveltejs/kit": "^2.59.1",
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
"@types/uuid": "^11.0.0",
- "@typescript-eslint/eslint-plugin": "^8.59.0",
- "@typescript-eslint/parser": "^8.59.2",
+ "@typescript-eslint/eslint-plugin": "^8.59.3",
+ "@typescript-eslint/parser": "^8.59.3",
"eslint": "^10.3.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.17.1",
+ "fake-indexeddb": "^6.2.5",
"globals": "^17.6.0",
+ "jsdom": "^29.1.1",
"prettier": "^3.8.3",
- "prettier-plugin-svelte": "^3.5.1",
+ "prettier-plugin-svelte": "^3.5.2",
"svelte": "^5.55.5",
- "svelte-check": "^4.4.7",
- "svelte-preprocess": "^6.0.3",
+ "svelte-check": "^4.4.8",
"tslib": "^2.8.1",
- "typescript": "^5.9.3",
- "typescript-eslint": "^8.59.2",
- "vite": "^8.0.10"
+ "typescript": "^6.0.3",
+ "typescript-eslint": "^8.59.3",
+ "vite": "^8.0.12",
+ "vitest": "^4.1.6"
}
},
- "node_modules/@aashutoshrathi/word-wrap": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
- "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "5.1.11",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
+ "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
"dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/generational-cache": "^1.0.1",
+ "@csstools/css-calc": "^3.2.0",
+ "@csstools/css-color-parser": "^4.1.0",
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
- "node_modules/@asamuzakjp/css-color": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.7.tgz",
- "integrity": "sha512-Ok5fYhtwdyJQmU1PpEv6Si7Y+A4cYb8yNM9oiIJC9TzXPMuN9fvdonKJqcnz9TbFqV6bQ8z0giRq0iaOpGZV2g==",
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
+ "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@csstools/css-calc": "^2.1.3",
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "lru-cache": "^10.4.3"
+ "@asamuzakjp/generational-cache": "^1.0.1",
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.2.1",
+ "is-potential-custom-element-name": "^1.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/generational-cache": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
+ "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@bramus/specificity": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
+ "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "css-tree": "^3.0.0"
+ },
+ "bin": {
+ "specificity": "bin/cli.js"
}
},
"node_modules/@csstools/color-helpers": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
- "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
+ "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -75,13 +121,14 @@
],
"license": "MIT-0",
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
}
},
"node_modules/@csstools/css-calc": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz",
- "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz",
+ "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -94,17 +141,18 @@
],
"license": "MIT",
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-color-parser": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz",
- "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz",
+ "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -117,21 +165,22 @@
],
"license": "MIT",
"dependencies": {
- "@csstools/color-helpers": "^5.0.2",
- "@csstools/css-calc": "^2.1.3"
+ "@csstools/color-helpers": "^6.0.2",
+ "@csstools/css-calc": "^3.2.0"
},
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-parser-algorithms": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
- "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
+ "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -143,17 +192,19 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
},
"peerDependencies": {
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
- "node_modules/@csstools/css-tokenizer": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
- "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz",
+ "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -164,32 +215,35 @@
"url": "https://opencollective.com/csstools"
}
],
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@emnapi/core": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
- "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.1",
- "tslib": "^2.4.0"
+ "license": "MIT-0",
+ "peerDependencies": {
+ "css-tree": "^3.2.1"
+ },
+ "peerDependenciesMeta": {
+ "css-tree": {
+ "optional": true
+ }
}
},
- "node_modules/@emnapi/runtime": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
- "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
+ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
"license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
+ "peer": true,
+ "engines": {
+ "node": ">=20.19.0"
}
},
"node_modules/@emnapi/wasi-threads": {
@@ -297,39 +351,60 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
+ "node_modules/@exodus/bytes": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz",
+ "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@noble/hashes": "^1.8.0 || ^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@noble/hashes": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
"dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
"dev": true,
+ "license": "Apache-2.0",
"dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.3.0"
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
+ "node": ">=18.18.0"
}
},
"node_modules/@humanwhocodes/module-importer": {
@@ -337,6 +412,7 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
"node": ">=12.22"
},
@@ -346,9 +422,9 @@
}
},
"node_modules/@humanwhocodes/retry": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
- "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -360,9 +436,9 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.12",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
- "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -371,9 +447,9 @@
}
},
"node_modules/@jridgewell/remapping": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.4.tgz",
- "integrity": "sha512-aG+WvAz17rhbzhKNkSeMLgbkPPK82ovXdONvmucbGhUqcroRFLLVhoGAk4xEI17gHpXgNX3sr0/B1ybRUsbEWw==",
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -382,10 +458,11 @@
}
},
"node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
- "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.0.0"
}
@@ -398,10 +475,11 @@
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@@ -427,9 +505,9 @@
}
},
"node_modules/@oxc-project/types": {
- "version": "0.127.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
- "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
+ "version": "0.129.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz",
+ "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==",
"dev": true,
"license": "MIT",
"funding": {
@@ -437,15 +515,16 @@
}
},
"node_modules/@polka/url": {
- "version": "1.0.0-next.28",
- "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
- "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
- "dev": true
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
- "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz",
+ "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==",
"cpu": [
"arm64"
],
@@ -460,9 +539,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
- "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz",
+ "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==",
"cpu": [
"arm64"
],
@@ -477,9 +556,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
- "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz",
+ "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==",
"cpu": [
"x64"
],
@@ -494,9 +573,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
- "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz",
+ "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==",
"cpu": [
"x64"
],
@@ -511,9 +590,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
- "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz",
+ "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==",
"cpu": [
"arm"
],
@@ -528,9 +607,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
- "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz",
+ "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==",
"cpu": [
"arm64"
],
@@ -545,9 +624,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
- "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz",
+ "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==",
"cpu": [
"arm64"
],
@@ -562,9 +641,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
- "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz",
+ "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==",
"cpu": [
"ppc64"
],
@@ -579,9 +658,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
- "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz",
+ "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==",
"cpu": [
"s390x"
],
@@ -596,9 +675,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
- "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz",
+ "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==",
"cpu": [
"x64"
],
@@ -613,9 +692,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
- "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz",
+ "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==",
"cpu": [
"x64"
],
@@ -630,9 +709,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
- "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz",
+ "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==",
"cpu": [
"arm64"
],
@@ -647,9 +726,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
- "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz",
+ "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==",
"cpu": [
"wasm32"
],
@@ -666,9 +745,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
- "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz",
+ "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==",
"cpu": [
"arm64"
],
@@ -683,9 +762,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
- "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz",
+ "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==",
"cpu": [
"x64"
],
@@ -700,28 +779,29 @@
}
},
"node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
- "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz",
+ "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@socket.io/component-emitter": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
- "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+ "license": "MIT"
},
"node_modules/@standard-schema/spec": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
- "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"dev": true,
"license": "MIT"
},
"node_modules/@sveltejs/acorn-typescript": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
- "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz",
+ "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -749,11 +829,12 @@
}
},
"node_modules/@sveltejs/kit": {
- "version": "2.59.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.59.0.tgz",
- "integrity": "sha512-WeJaGKvDf3uVQB4bnDHhM+BXCY34LC1v0HiPqnSpvNkjB54r8DAUP1rpk73s+5zprIirEKtUcVfgh6+fPODjzQ==",
+ "version": "2.59.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.59.1.tgz",
+ "integrity": "sha512-d8OON70AphLdDesuTIl//M2O6fRTIicX8aYv8vhCiYEhTTI2OboKqey0Hu1A4VFhqwgqtq0vKDmPFGkw8kKmgw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5",
@@ -791,11 +872,12 @@
}
},
"node_modules/@sveltejs/vite-plugin-svelte": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.0.0.tgz",
- "integrity": "sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.1.2.tgz",
+ "integrity": "sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"deepmerge": "^4.3.1",
"magic-string": "^0.30.21",
@@ -811,9 +893,9 @@
}
},
"node_modules/@tybys/wasm-util": {
- "version": "0.10.1",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
- "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -821,20 +903,40 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@types/cors": {
- "version": "2.8.17",
- "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
- "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
+ "version": "2.8.19",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+ "license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/esrecurse": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
@@ -843,9 +945,9 @@
"license": "MIT"
},
"node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true,
"license": "MIT"
},
@@ -857,12 +959,12 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.0.13",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.13.tgz",
- "integrity": "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==",
+ "version": "25.7.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.7.0.tgz",
+ "integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==",
"license": "MIT",
"dependencies": {
- "undici-types": "~7.8.0"
+ "undici-types": "~7.21.0"
}
},
"node_modules/@types/trusted-types": {
@@ -883,18 +985,27 @@
"uuid": "*"
}
},
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz",
- "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz",
+ "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.59.2",
- "@typescript-eslint/type-utils": "8.59.2",
- "@typescript-eslint/utils": "8.59.2",
- "@typescript-eslint/visitor-keys": "8.59.2",
+ "@typescript-eslint/scope-manager": "8.59.3",
+ "@typescript-eslint/type-utils": "8.59.3",
+ "@typescript-eslint/utils": "8.59.3",
+ "@typescript-eslint/visitor-keys": "8.59.3",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -907,32 +1018,23 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.59.2",
+ "@typescript-eslint/parser": "^8.59.3",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
"node_modules/@typescript-eslint/parser": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz",
- "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz",
+ "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "8.59.2",
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/typescript-estree": "8.59.2",
- "@typescript-eslint/visitor-keys": "8.59.2",
+ "@typescript-eslint/scope-manager": "8.59.3",
+ "@typescript-eslint/types": "8.59.3",
+ "@typescript-eslint/typescript-estree": "8.59.3",
+ "@typescript-eslint/visitor-keys": "8.59.3",
"debug": "^4.4.3"
},
"engines": {
@@ -947,33 +1049,15 @@
"typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/@typescript-eslint/parser/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
"node_modules/@typescript-eslint/project-service": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz",
- "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz",
+ "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.59.2",
- "@typescript-eslint/types": "^8.59.2",
+ "@typescript-eslint/tsconfig-utils": "^8.59.3",
+ "@typescript-eslint/types": "^8.59.3",
"debug": "^4.4.3"
},
"engines": {
@@ -987,33 +1071,15 @@
"typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/@typescript-eslint/project-service/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz",
- "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz",
+ "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/visitor-keys": "8.59.2"
+ "@typescript-eslint/types": "8.59.3",
+ "@typescript-eslint/visitor-keys": "8.59.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1024,9 +1090,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz",
- "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz",
+ "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1041,15 +1107,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz",
- "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz",
+ "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/typescript-estree": "8.59.2",
- "@typescript-eslint/utils": "8.59.2",
+ "@typescript-eslint/types": "8.59.3",
+ "@typescript-eslint/typescript-estree": "8.59.3",
+ "@typescript-eslint/utils": "8.59.3",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -1065,30 +1131,13 @@
"typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/@typescript-eslint/type-utils/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
"node_modules/@typescript-eslint/types": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
- "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz",
+ "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -1098,16 +1147,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz",
- "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz",
+ "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.59.2",
- "@typescript-eslint/tsconfig-utils": "8.59.2",
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/visitor-keys": "8.59.2",
+ "@typescript-eslint/project-service": "8.59.3",
+ "@typescript-eslint/tsconfig-utils": "8.59.3",
+ "@typescript-eslint/types": "8.59.3",
+ "@typescript-eslint/visitor-keys": "8.59.3",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -1125,35 +1174,17 @@
"typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
"node_modules/@typescript-eslint/utils": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz",
- "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz",
+ "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.59.2",
- "@typescript-eslint/types": "8.59.2",
- "@typescript-eslint/typescript-estree": "8.59.2"
+ "@typescript-eslint/scope-manager": "8.59.3",
+ "@typescript-eslint/types": "8.59.3",
+ "@typescript-eslint/typescript-estree": "8.59.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1168,13 +1199,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz",
- "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz",
+ "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.59.2",
+ "@typescript-eslint/types": "8.59.3",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -1198,53 +1229,169 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "node_modules/@vitest/expect": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
+ "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
},
- "engines": {
- "node": ">= 0.6"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/acorn": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
- "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz",
+ "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==",
"dev": true,
"license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
+ "dependencies": {
+ "@vitest/spy": "4.1.6",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
},
- "engines": {
- "node": ">=0.4.0"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
}
},
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz",
+ "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==",
"dev": true,
- "peerDependencies": {
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz",
+ "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.6",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz",
+ "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
+ "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz",
+ "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/agent-base": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
- "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ajv": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
- "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1268,23 +1415,55 @@
"node": ">= 0.4"
}
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
}
},
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+ "license": "MIT",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
"node_modules/blockly": {
"version": "12.5.1",
"resolved": "https://registry.npmjs.org/blockly/-/blockly-12.5.1.tgz",
@@ -1297,10 +1476,171 @@
"node": ">=18"
}
},
+ "node_modules/blockly/node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/blockly/node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/blockly/node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/blockly/node_modules/jsdom": {
+ "version": "26.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.2.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.5.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.16",
+ "parse5": "^7.2.1",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.1.1",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.1.1",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/blockly/node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/blockly/node_modules/tldts": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^6.1.86"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/blockly/node_modules/tldts-core": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "license": "MIT"
+ },
+ "node_modules/blockly/node_modules/tough-cookie": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^6.1.32"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/blockly/node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/blockly/node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/blockly/node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/blockly/node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/brace-expansion": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
- "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1310,21 +1650,22 @@
"node": "18 || 20 || >=22"
}
},
- "node_modules/brace-expansion/node_modules/balanced-match": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
- "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": "18 || 20 || >=22"
+ "node": ">=18"
}
},
"node_modules/chokidar": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
- "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
@@ -1340,10 +1681,18 @@
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6"
}
},
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
@@ -1354,15 +1703,20 @@
}
},
"node_modules/cors": {
- "version": "2.8.5",
- "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
- "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/cross-spawn": {
@@ -1380,6 +1734,20 @@
"node": ">= 8"
}
},
+ "node_modules/css-tree": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1394,35 +1762,168 @@
}
},
"node_modules/cssstyle": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz",
- "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==",
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cssstyle/node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/cssstyle/node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cssstyle/node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/cssstyle/node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/cssstyle/node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/cssstyle/node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
"license": "MIT",
- "dependencies": {
- "@asamuzakjp/css-color": "^3.1.2",
- "rrweb-cssom": "^0.8.0"
- },
+ "peer": true,
"engines": {
"node": ">=18"
}
},
+ "node_modules/cssstyle/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
"node_modules/data-urls": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
- "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
+ "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "whatwg-mimetype": "^4.0.0",
- "whatwg-url": "^14.0.0"
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.0"
},
"engines": {
- "node": ">=18"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
@@ -1436,22 +1937,24 @@
}
},
"node_modules/decimal.js": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
- "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"license": "MIT"
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -1467,75 +1970,81 @@
}
},
"node_modules/devalue": {
- "version": "5.6.4",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz",
- "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.0.tgz",
+ "integrity": "sha512-2zA9pFEsnp7vWBZbXF5JAgAq0fsUIt/1XPbRiAmRV3lp/2C3upzH+sADiyy66aFCihoLEsrQHxNM5w1gIDfsBg==",
"dev": true,
"license": "MIT"
},
"node_modules/engine.io": {
- "version": "6.6.2",
- "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
- "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
+ "version": "6.6.7",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.7.tgz",
+ "integrity": "sha512-DgOngfDKM2EviOH3Mr9m7ks1q8roetLy/IMmYthAYzbpInMbYc/GS+fWFA3rl1gvwKVsQrVV61fo5emD1y3OJQ==",
"license": "MIT",
"dependencies": {
- "@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
+ "@types/ws": "^8.5.12",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
- "debug": "~4.3.1",
+ "debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
- "ws": "~8.17.1"
+ "ws": "~8.18.3"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-client": {
- "version": "6.6.1",
- "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
- "integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
+ "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
+ "license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.1",
+ "debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
- "ws": "~8.17.1",
+ "ws": "~8.18.3",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
- "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+ "license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
- "node_modules/engine.io/node_modules/@types/cookie": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
- "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
- },
"node_modules/entities": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
- "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
+ "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
+ "dev": true,
"license": "BSD-2-Clause",
"engines": {
- "node": ">=0.12"
+ "node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=10"
},
@@ -1549,6 +2058,7 @@
"integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
@@ -1663,17 +2173,19 @@
}
},
"node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -1684,6 +2196,7 @@
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@@ -1691,25 +2204,6 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/eslint/node_modules/eslint-scope": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
- "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "@types/esrecurse": "^4.3.1",
- "@types/estree": "^1.0.8",
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^20.19.0 || ^22.13.0 || >=24"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
"node_modules/eslint/node_modules/eslint-visitor-keys": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
@@ -1723,22 +2217,14 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/eslint/node_modules/espree": {
- "version": "11.2.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
- "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
+ "node_modules/eslint/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.16.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^5.0.1"
- },
+ "license": "MIT",
"engines": {
- "node": "^20.19.0 || ^22.13.0 || >=24"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": ">= 4"
}
},
"node_modules/esm-env": {
@@ -1749,31 +2235,31 @@
"license": "MIT"
},
"node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "acorn": "^8.15.0",
+ "acorn": "^8.16.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
+ "eslint-visitor-keys": "^5.0.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/espree/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -1793,14 +2279,21 @@
}
},
"node_modules/esrap": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz",
- "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==",
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.7.tgz",
+ "integrity": "sha512-Dl7o7btn2YXca1VXx+PVl+lKuZdHBm8oCFuckUxqchMvNMdHMJ/qF31wtPaVyWvFYLQePkbXJrirWzbAP6Yamw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "peerDependencies": {
"@typescript-eslint/types": "^8.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/types": {
+ "optional": true
+ }
}
},
"node_modules/esrecurse": {
@@ -1808,6 +2301,7 @@
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"estraverse": "^5.2.0"
},
@@ -1820,19 +2314,51 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
+ "license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
+ "license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fake-indexeddb": {
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz",
+ "integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -1851,7 +2377,8 @@
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/fdir": {
"version": "6.5.0",
@@ -1876,6 +2403,7 @@
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"flat-cache": "^4.0.0"
},
@@ -1888,6 +2416,7 @@
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
@@ -1904,6 +2433,7 @@
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"flatted": "^3.2.9",
"keyv": "^4.5.4"
@@ -1939,6 +2469,7 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
},
@@ -1960,15 +2491,16 @@
}
},
"node_modules/html-encoding-sniffer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
- "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
+ "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "whatwg-encoding": "^3.1.1"
+ "@exodus/bytes": "^1.6.0"
},
"engines": {
- "node": ">=18"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/http-proxy-agent": {
@@ -2009,10 +2541,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/idb": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz",
+ "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==",
+ "license": "ISC"
+ },
"node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2024,6 +2562,7 @@
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.8.19"
}
@@ -2033,6 +2572,7 @@
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -2042,6 +2582,7 @@
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -2060,6 +2601,7 @@
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@types/estree": "^1.0.6"
}
@@ -2072,34 +2614,37 @@
"license": "ISC"
},
"node_modules/jsdom": {
- "version": "26.1.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
- "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "version": "29.1.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
+ "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "cssstyle": "^4.2.1",
- "data-urls": "^5.0.0",
- "decimal.js": "^10.5.0",
- "html-encoding-sniffer": "^4.0.0",
- "http-proxy-agent": "^7.0.2",
- "https-proxy-agent": "^7.0.6",
+ "@asamuzakjp/css-color": "^5.1.11",
+ "@asamuzakjp/dom-selector": "^7.1.1",
+ "@bramus/specificity": "^2.4.2",
+ "@csstools/css-syntax-patches-for-csstree": "^1.1.3",
+ "@exodus/bytes": "^1.15.0",
+ "css-tree": "^3.2.1",
+ "data-urls": "^7.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^6.0.0",
"is-potential-custom-element-name": "^1.0.1",
- "nwsapi": "^2.2.16",
- "parse5": "^7.2.1",
- "rrweb-cssom": "^0.8.0",
+ "lru-cache": "^11.3.5",
+ "parse5": "^8.0.1",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
- "tough-cookie": "^5.1.1",
+ "tough-cookie": "^6.0.1",
+ "undici": "^7.25.0",
"w3c-xmlserializer": "^5.0.0",
- "webidl-conversions": "^7.0.0",
- "whatwg-encoding": "^3.1.1",
- "whatwg-mimetype": "^4.0.0",
- "whatwg-url": "^14.1.1",
- "ws": "^8.18.0",
+ "webidl-conversions": "^8.0.1",
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.1",
"xml-name-validator": "^5.0.0"
},
"engines": {
- "node": ">=18"
+ "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
},
"peerDependencies": {
"canvas": "^3.0.0"
@@ -2110,32 +2655,12 @@
}
}
},
- "node_modules/jsdom/node_modules/ws": {
- "version": "8.18.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
- "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
@@ -2148,13 +2673,15 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"json-buffer": "3.0.1"
}
@@ -2164,6 +2691,7 @@
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6"
}
@@ -2180,6 +2708,7 @@
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
@@ -2454,6 +2983,7 @@
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=10"
}
@@ -2462,13 +2992,15 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"p-locate": "^5.0.0"
},
@@ -2480,10 +3012,14 @@
}
},
"node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "license": "ISC"
+ "version": "11.3.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz",
+ "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
},
"node_modules/magic-string": {
"version": "0.30.21",
@@ -2495,10 +3031,18 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
+ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -2507,6 +3051,7 @@
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
@@ -2515,13 +3060,13 @@
}
},
"node_modules/minimatch": {
- "version": "10.2.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
- "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^5.0.2"
+ "brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
@@ -2535,15 +3080,17 @@
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/mrmime": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
- "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=10"
}
@@ -2551,12 +3098,13 @@
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"dev": true,
"funding": [
{
@@ -2576,26 +3124,29 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/nwsapi": {
- "version": "2.2.20",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
- "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
+ "version": "2.2.23",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
+ "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==",
"license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -2612,17 +3163,18 @@
"license": "MIT"
},
"node_modules/optionator": {
- "version": "0.9.3",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
- "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
- "type-check": "^0.4.0"
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
},
"engines": {
"node": ">= 0.8.0"
@@ -2633,6 +3185,7 @@
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"yocto-queue": "^0.1.0"
},
@@ -2648,6 +3201,7 @@
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"p-limit": "^3.0.2"
},
@@ -2659,12 +3213,13 @@
}
},
"node_modules/parse5": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
- "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
+ "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "entities": "^6.0.0"
+ "entities": "^8.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
@@ -2675,6 +3230,7 @@
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
}
@@ -2689,6 +3245,13 @@
"node": ">=8"
}
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -2702,6 +3265,7 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -2710,9 +3274,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.10",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
- "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"dev": true,
"funding": [
{
@@ -2729,6 +3293,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -2743,6 +3308,7 @@
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"lilconfig": "^2.0.5",
"yaml": "^1.10.2"
@@ -2768,10 +3334,11 @@
}
},
"node_modules/postcss-load-config/node_modules/yaml": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
- "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
+ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
"dev": true,
+ "license": "ISC",
"engines": {
"node": ">= 6"
}
@@ -2831,9 +3398,9 @@
}
},
"node_modules/postcss-selector-parser": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
- "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
+ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2849,6 +3416,7 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">= 0.8.0"
}
@@ -2859,6 +3427,7 @@
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -2870,9 +3439,9 @@
}
},
"node_modules/prettier-plugin-svelte": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.1.tgz",
- "integrity": "sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg==",
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.2.tgz",
+ "integrity": "sha512-ItFouLvzSFE3ulNl4DKoWM3BGcbDCNVpIyy/Y3F2gC3aNiGLxtFUdffVqO5Z5hhYG+DFT5KULWaxmeFFpdbvaQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -2884,32 +3453,44 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/readdirp": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz",
- "integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">= 14.16.0"
+ "node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rolldown": {
- "version": "1.0.0-rc.17",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
- "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz",
+ "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.127.0",
- "@rolldown/pluginutils": "1.0.0-rc.17"
+ "@oxc-project/types": "=0.129.0",
+ "@rolldown/pluginutils": "1.0.0"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -2918,21 +3499,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.0.0-rc.17",
- "@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
- "@rolldown/binding-darwin-x64": "1.0.0-rc.17",
- "@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
- "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
- "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
- "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
- "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
- "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
- "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
- "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
- "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
- "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
- "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
- "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
+ "@rolldown/binding-android-arm64": "1.0.0",
+ "@rolldown/binding-darwin-arm64": "1.0.0",
+ "@rolldown/binding-darwin-x64": "1.0.0",
+ "@rolldown/binding-freebsd-x64": "1.0.0",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0",
+ "@rolldown/binding-linux-x64-musl": "1.0.0",
+ "@rolldown/binding-openharmony-arm64": "1.0.0",
+ "@rolldown/binding-wasm32-wasi": "1.0.0",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0"
}
},
"node_modules/rrweb-cssom": {
@@ -2946,6 +3527,7 @@
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"mri": "^1.1.0"
},
@@ -2972,9 +3554,9 @@
}
},
"node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
+ "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -2985,9 +3567,9 @@
}
},
"node_modules/set-cookie-parser": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz",
- "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz",
+ "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==",
"dev": true,
"license": "MIT"
},
@@ -3014,11 +3596,19 @@
"node": ">=8"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/sirv": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
- "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
+ "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@polka/url": "^1.0.0-next.24",
"mrmime": "^2.0.0",
@@ -3047,13 +3637,13 @@
}
},
"node_modules/socket.io-adapter": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
- "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz",
+ "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==",
"license": "MIT",
"dependencies": {
- "debug": "~4.3.4",
- "ws": "~8.17.1"
+ "debug": "~4.4.1",
+ "ws": "~8.18.3"
}
},
"node_modules/socket.io-client": {
@@ -3071,23 +3661,6 @@
"node": ">=10.0.0"
}
},
- "node_modules/socket.io-client/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
"node_modules/socket.io-parser": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
@@ -3101,40 +3674,6 @@
"node": ">=10.0.0"
}
},
- "node_modules/socket.io-parser/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -3145,12 +3684,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/svelte": {
"version": "5.55.5",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.5.tgz",
"integrity": "sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -3174,9 +3728,9 @@
}
},
"node_modules/svelte-check": {
- "version": "4.4.7",
- "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.7.tgz",
- "integrity": "sha512-JRafFTRmaPUOqmri4u1WuIKgBLiHi6wIaB57i99pmHq5BAc3ioIpzdUN/RX32ij9GhI6ALMHKvnVxu68sFZlag==",
+ "version": "4.4.8",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.8.tgz",
+ "integrity": "sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3198,9 +3752,9 @@
}
},
"node_modules/svelte-eslint-parser": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.0.tgz",
- "integrity": "sha512-fjPzOfipR5S7gQ/JvI9r2H8y9gMGXO3JtmrylHLLyahEMquXI0lrebcjT+9/hNgDej0H7abTyox5HpHmW1PSWA==",
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.6.1.tgz",
+ "integrity": "sha512-hhvSH6kRj46UzrBVO5TaotD+Iuvruj5ccKBcO4wAhVcPTLmIc/c32D8UllBTYO0on4LzYuM0rNzf1lM/gBlkSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3209,11 +3763,12 @@
"espree": "^10.0.0",
"postcss": "^8.4.49",
"postcss-scss": "^4.0.9",
- "postcss-selector-parser": "^7.0.0"
+ "postcss-selector-parser": "^7.0.0",
+ "semver": "^7.7.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0",
- "pnpm": "10.18.3"
+ "pnpm": "10.33.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
@@ -3227,6 +3782,23 @@
}
}
},
+ "node_modules/svelte-eslint-parser/node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
@@ -3240,59 +3812,22 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/svelte-preprocess": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.3.tgz",
- "integrity": "sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==",
+ "node_modules/svelte-eslint-parser/node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
- "hasInstallScript": true,
- "engines": {
- "node": ">= 18.0.0"
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
},
- "peerDependencies": {
- "@babel/core": "^7.10.2",
- "coffeescript": "^2.5.1",
- "less": "^3.11.3 || ^4.0.0",
- "postcss": "^7 || ^8",
- "postcss-load-config": ">=3",
- "pug": "^3.0.0",
- "sass": "^1.26.8",
- "stylus": ">=0.55",
- "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
- "svelte": "^4.0.0 || ^5.0.0-next.100 || ^5.0.0",
- "typescript": "^5.0.0"
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "coffeescript": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "postcss": {
- "optional": true
- },
- "postcss-load-config": {
- "optional": true
- },
- "pug": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "typescript": {
- "optional": true
- }
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/symbol-tree": {
@@ -3301,6 +3836,23 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"license": "MIT"
},
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
+ "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
@@ -3318,22 +3870,34 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/tldts": {
- "version": "6.1.86",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
- "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "version": "7.0.30",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz",
+ "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "tldts-core": "^6.1.86"
+ "tldts-core": "^7.0.30"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
- "version": "6.1.86",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
- "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "version": "7.0.30",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz",
+ "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==",
+ "dev": true,
"license": "MIT"
},
"node_modules/totalist": {
@@ -3341,32 +3905,35 @@
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/tough-cookie": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
- "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
+ "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==",
+ "dev": true,
"license": "BSD-3-Clause",
"dependencies": {
- "tldts": "^6.1.32"
+ "tldts": "^7.0.5"
},
"engines": {
"node": ">=16"
}
},
"node_modules/tr46": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
- "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
},
"node_modules/ts-api-utils": {
@@ -3386,13 +3953,15 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true
+ "dev": true,
+ "license": "0BSD"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"prelude-ls": "^1.2.1"
},
@@ -3401,11 +3970,12 @@
}
},
"node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -3415,16 +3985,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.59.2",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.2.tgz",
- "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==",
+ "version": "8.59.3",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz",
+ "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.59.2",
- "@typescript-eslint/parser": "8.59.2",
- "@typescript-eslint/typescript-estree": "8.59.2",
- "@typescript-eslint/utils": "8.59.2"
+ "@typescript-eslint/eslint-plugin": "8.59.3",
+ "@typescript-eslint/parser": "8.59.3",
+ "@typescript-eslint/typescript-estree": "8.59.3",
+ "@typescript-eslint/utils": "8.59.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3438,10 +4008,20 @@
"typescript": ">=4.8.4 <6.1.0"
}
},
+ "node_modules/undici": {
+ "version": "7.25.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
+ "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
"node_modules/undici-types": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
- "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
+ "version": "7.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz",
+ "integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==",
"license": "MIT"
},
"node_modules/uri-js": {
@@ -3478,21 +4058,23 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vite": {
- "version": "8.0.10",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
- "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
+ "version": "8.0.12",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz",
+ "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
- "postcss": "^8.5.10",
- "rolldown": "1.0.0-rc.17",
+ "postcss": "^8.5.14",
+ "rolldown": "1.0.0",
"tinyglobby": "^0.2.16"
},
"bin": {
@@ -3509,7 +4091,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.1.0",
+ "@vitejs/devtools": "^0.1.18",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
@@ -3561,9 +4143,9 @@
}
},
"node_modules/vitefu": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz",
- "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz",
+ "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==",
"dev": true,
"license": "MIT",
"workspaces": [
@@ -3572,7 +4154,7 @@
"tests/projects/workspace/packages/*"
],
"peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0"
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"vite": {
@@ -3580,6 +4162,96 @@
}
}
},
+ "node_modules/vitest": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz",
+ "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.6",
+ "@vitest/mocker": "4.1.6",
+ "@vitest/pretty-format": "4.1.6",
+ "@vitest/runner": "4.1.6",
+ "@vitest/snapshot": "4.1.6",
+ "@vitest/spy": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.6",
+ "@vitest/browser-preview": "4.1.6",
+ "@vitest/browser-webdriverio": "4.1.6",
+ "@vitest/coverage-istanbul": "4.1.6",
+ "@vitest/coverage-v8": "4.1.6",
+ "@vitest/ui": "4.1.6",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
@@ -3593,18 +4265,20 @@
}
},
"node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
+ "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
+ "dev": true,
"license": "BSD-2-Clause",
"engines": {
- "node": ">=12"
+ "node": ">=20"
}
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
@@ -3614,25 +4288,28 @@
}
},
"node_modules/whatwg-mimetype": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
- "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
+ "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
},
"node_modules/whatwg-url": {
- "version": "14.2.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
- "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
+ "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "tr46": "^5.1.0",
- "webidl-conversions": "^7.0.0"
+ "@exodus/bytes": "^1.11.0",
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.1"
},
"engines": {
- "node": ">=18"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/which": {
@@ -3651,10 +4328,37 @@
"node": ">= 8"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ws": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
- "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -3688,18 +4392,19 @@
"license": "MIT"
},
"node_modules/xmlhttprequest-ssl": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
- "integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+ "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yaml": {
- "version": "2.8.4",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
- "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
"license": "ISC",
+ "peer": true,
"bin": {
"yaml": "bin.mjs"
},
@@ -3715,6 +4420,7 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=10"
},
@@ -3723,9 +4429,9 @@
}
},
"node_modules/zimmerframe": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
- "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
+ "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
"dev": true,
"license": "MIT"
}
diff --git a/package.json b/package.json
index 82fa7f21a5..fb10e66c1e 100644
--- a/package.json
+++ b/package.json
@@ -8,38 +8,42 @@
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
+ "test": "vitest run",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.1",
"@sveltejs/adapter-static": "^3.0.10",
- "@sveltejs/kit": "^2.59.0",
- "@sveltejs/vite-plugin-svelte": "^7.0.0",
+ "@sveltejs/kit": "^2.59.1",
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
"@types/uuid": "^11.0.0",
- "@typescript-eslint/eslint-plugin": "^8.59.0",
- "@typescript-eslint/parser": "^8.59.2",
+ "@typescript-eslint/eslint-plugin": "^8.59.3",
+ "@typescript-eslint/parser": "^8.59.3",
"eslint": "^10.3.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.17.1",
"globals": "^17.6.0",
"prettier": "^3.8.3",
- "prettier-plugin-svelte": "^3.5.1",
+ "fake-indexeddb": "^6.2.5",
+ "jsdom": "^29.1.1",
+ "prettier-plugin-svelte": "^3.5.2",
"svelte": "^5.55.5",
- "svelte-check": "^4.4.7",
- "svelte-preprocess": "^6.0.3",
+ "svelte-check": "^4.4.8",
"tslib": "^2.8.1",
- "typescript": "^5.9.3",
- "typescript-eslint": "^8.59.2",
- "vite": "^8.0.10"
+ "typescript": "^6.0.3",
+ "typescript-eslint": "^8.59.3",
+ "vite": "^8.0.12",
+ "vitest": "^4.1.6"
},
"type": "module",
"dependencies": {
"blockly": "^12.5.1",
+ "idb": "^8.0.3",
"socket.io": "^4.8.3",
"socket.io-client": "^4.8.3",
"uuid": "^14.0.0",
- "yaml": "^2.8.4"
+ "yaml": "^2.9.0"
},
"overrides": {
"cookie": "1.0.2"
diff --git a/src/components/sidebar/SidebarEntry.svelte b/src/components/sidebar/SidebarEntry.svelte
index 100f521a40..e5cf4ab901 100644
--- a/src/components/sidebar/SidebarEntry.svelte
+++ b/src/components/sidebar/SidebarEntry.svelte
@@ -1,344 +1,374 @@
-
-
-
-
-
-{#if deleting}
-
- Do you really want to delete {data?.name}?
-
-
-{/if}
-
-
\ No newline at end of file
+
diff --git a/src/data/attribute-store.ts b/src/data/attribute-store.ts
index 0f718c1e6b..df0591a115 100644
--- a/src/data/attribute-store.ts
+++ b/src/data/attribute-store.ts
@@ -1,258 +1,252 @@
-import type { Writable } from 'svelte/store';
-import { get, writable } from 'svelte/store';
-import { browser } from '$app/environment';
-import YAML from 'yaml';
-import FabledAttribute from '$api/fabled-attribute.svelte';
+import type { Writable } from 'svelte/store';
+import { get, writable } from 'svelte/store';
+import FabledAttribute from '$api/fabled-attribute.svelte';
import type { MultiAttributeYamlData } from '$api/types';
-import { sort } from '$api/api';
-import { parseYaml } from '$api/yaml';
-import { active, saveError } from './store';
-import { base } from '$app/paths';
-import { goto } from '$app/navigation';
-import { socketService } from '$api/socket/socket-connector';
-import { classStore } from './class-store.svelte';
+import { sort } from '$api/api';
+import { parseYaml } from '$api/yaml';
+import { active, saveError } from './store';
+import { base } from '$app/paths';
+import { goto } from '$app/navigation';
+import { socketService } from '$api/socket/socket-connector';
+import { classStore } from './class-store.svelte';
+import {
+ getPersistedAttribute,
+ listPersistedAttributeRecords,
+ savePersistedAttributes
+} from './editor-persistence';
+import { getPersistenceFailureMessage } from './persistence-state';
class AttributeStore {
- tooBig: Writable = writable(false);
- acknowledged: Writable = writable(false);
-
- loadAttributesFromServer = async () => {
- let serverAttributes: string = '';
- try {
- serverAttributes = await socketService.getAttributeYaml();
- } catch (_) {
- return;
- }
-
- this.loadAttributesText(serverAttributes, 'server');
- };
-
- removeServerAttributes = () => {
- const tempAttributes = get(this.attributes);
- this.attributes.set(tempAttributes.filter((attr) => attr.location !== 'server'));
- };
-
- constructor() {
- socketService.onConnect(this.loadAttributesFromServer);
- socketService.onDisconnect(this.removeServerAttributes);
-
- this.getDefaultAttributes().then((defaultAttributes) => {
- setTimeout(() => {
- const attributes = get(this.attributes);
- if (attributes.length === 0) {
- this.attributes.set(defaultAttributes);
- }
- }, 500);
- });
- }
-
- private setupAttributeStore = (
- key: string,
- def: T,
- mapper: (data: string) => T,
- setAction: (data: T) => T,
- postLoad?: (saved: T) => void): Writable => {
- let saved: T = def;
- if (browser) {
- const stored = localStorage.getItem(key);
- if (stored) {
- saved = mapper(stored);
- if (postLoad) postLoad(saved);
- }
- }
-
- const {
- subscribe,
- set,
- update
- } = writable(saved);
- return {
- subscribe,
- set: (value: T) => {
- if (setAction) value = setAction(value);
- return set(value);
- },
- update
- };
- };
-
- getDefaultAttributes = async (): Promise => {
- const yaml = parseYaml(await fetch('https://raw.githubusercontent.com/magemonkeystudio/fabled/dev/src/main/resources/attributes.yml').then(r => r.text()));
- if (!yaml) return [];
- return Object.keys(yaml).map((key: string) => {
- const attrib: FabledAttribute = new FabledAttribute({ name: key });
- attrib.load(yaml[key]);
- return attrib;
- });
-
- };
-
- attributes: Writable = this.setupAttributeStore(
- 'attribs',
- [],
- (data: string) => {
- if (data.split('\n').length < 3 && data.charAt(0) !== '{') { // Old format
- return data.replace('\n', '').split(',').map((key: string) => new FabledAttribute({ name: key }));
- }
- const yaml = parseYaml(data);
- if (!yaml) return [];
- return Object.keys(yaml).map((key: string) => {
- const attrib: FabledAttribute = new FabledAttribute({ name: key });
- attrib.load(yaml[key]);
- return attrib;
- });
- },
- (value: FabledAttribute[]) => {
- classStore.updateAllAttributes(value.map((attr: FabledAttribute) => attr.name));
- return sort(value);
- });
-
- getAttributeNames = (): string[] => {
- return get(this.attributes).map((attr) => attr.name);
- };
-
- getAttribute = (name: string): FabledAttribute | undefined => {
- for (const c of get(this.attributes)) {
- if (c.name == name) return c;
- }
-
- return undefined;
- };
-
- isAttributeNameTaken = (name: string): boolean => !!this.getAttribute(name);
-
- addAttribute = (name?: string): FabledAttribute => {
- const allAttributes = get(this.attributes);
- let index = allAttributes.length + 1;
- while (!name && this.isAttributeNameTaken(name || 'attribute ' + index)) {
- index++;
- }
- const attrib = new FabledAttribute({ name: (name || 'attribute ' + index) });
- allAttributes.push(attrib);
-
- this.attributes.set(allAttributes);
- attrib.save();
- return attrib;
- };
-
-
- loadAttributes = (e: ProgressEvent) => {
- const text: string = e.target?.result;
- if (!text) return;
-
- this.loadAttributesText(text);
- };
-
- /**
- * Loads attribute data from a file
- * e - event details
- */
- loadAttributesText = (text: string, location: 'local' | 'server' = 'local') => {
- const yaml = parseYaml(text);
- if (!yaml) return;
-
- // Get the current attributes
- const currentAttributes = get(this.attributes);
- // Create a map of current attributes for easy lookup
- const currentAttributesMap = new Map(currentAttributes.map(attr => [attr.name, attr]));
-
- // Merge the current attributes with the new ones
- const mergedAttributes = [...currentAttributes];
- Object.keys(yaml).forEach((key: string) => {
- // If the attribute already exists, ignore it
- if (!currentAttributesMap.has(key)) {
- // Otherwise, create a new attribute
- const newAttribute = new FabledAttribute({ name: key, location });
- newAttribute.load(yaml[key]);
- mergedAttributes.push(newAttribute);
- }
- });
-
- this.attributes.set(mergedAttributes);
- this.refreshAttributes();
- };
-
- loadAttribute = (data: FabledAttribute) => {
- if (data.loaded) return;
-
- if (data.location === 'local') {
- const yamlData = parseYaml(localStorage.getItem('attribs') || '');
- if (!yamlData) return;
- const attrib = yamlData[data.name];
- data.load(attrib);
- }
- };
-
- cloneAttribute = (data: FabledAttribute): FabledAttribute => {
- if (!data.loaded) this.loadAttribute(data);
-
- const attr: FabledAttribute[] = get(this.attributes);
- let name = data.name + ' (Copy)';
- let i = 1;
- while (this.isAttributeNameTaken(name)) {
- name = data.name + ' (Copy ' + i + ')';
- i++;
- }
- const attribute = new FabledAttribute();
- const yamlData = data.serializeYaml();
- attribute.load(yamlData);
- attribute.name = name;
- attr.push(attribute);
-
- this.attributes.set(attr);
- attribute.save();
- return attribute;
- };
-
- refreshAttributes = () => this.attributes.set(sort(get(this.attributes)));
-
- deleteAttribute = (data: FabledAttribute) => {
- const filtered = get(this.attributes).filter(c => c != data);
- const act = get(active);
- this.attributes.set(filtered);
- this.saveAll();
-
- if (!(act instanceof FabledAttribute)) return;
-
- if (filtered.length === 0) {
- goto(`${base}/`).then(() => {
- });
- } else if (!filtered.find(attr => attr === get(active))) {
- goto(`${base}/attribute/${filtered[0].name}/edit`).then(() => {
- });
- }
- };
-
- saveAll = () => {
- if (get(this.tooBig)) return;
-
- if (get(this.tooBig) && !get(this.acknowledged)) {
- saveError.set({ name: 'Attributes', acknowledged: false });
- return;
- }
-
- const attributeYaml: MultiAttributeYamlData = {};
- for (const attr of get(this.attributes)) {
- attributeYaml[attr.name] = attr.serializeYaml();
- }
- const yaml = YAML.stringify(attributeYaml, { lineWidth: 0, aliasDuplicateObjects: false });
-
- try {
- localStorage.setItem('attribs', yaml);
- this.tooBig.set(false);
- } catch (e: any) {
- // If the data is too big
- if (!e?.message?.includes('quota')) {
- console.error('Attributes Save error', e);
- } else {
- localStorage.removeItem('attribs');
- this.tooBig.set(true);
- saveError.set({ name: 'Attributes', acknowledged: false });
- }
- }
-
- console.log('Saved attributes 😎');
- };
+ loadAttributesFromServer = async () => {
+ let serverAttributes: string = '';
+ try {
+ serverAttributes = await socketService.getAttributeYaml();
+ } catch (_) {
+ return;
+ }
+
+ this.loadAttributesText(serverAttributes, 'server');
+ };
+
+ removeServerAttributes = () => {
+ const tempAttributes = get(this.attributes);
+ this.attributes.set(tempAttributes.filter((attr) => attr.location !== 'server'));
+ };
+
+ constructor() {
+ socketService.onConnect(this.loadAttributesFromServer);
+ socketService.onDisconnect(this.removeServerAttributes);
+
+ this.getDefaultAttributes().then((defaultAttributes) => {
+ setTimeout(() => {
+ const attributes = get(this.attributes);
+ if (attributes.length === 0) {
+ this.attributes.set(defaultAttributes);
+ }
+ }, 500);
+ });
+ }
+
+ private setupAttributeStore = (
+ _key: string,
+ def: T,
+ mapper: (data: string) => T,
+ setAction: (data: T) => T,
+ postLoad?: (saved: T) => void
+ ): Writable => {
+ let saved: T = def;
+ if (postLoad) postLoad(saved);
+
+ const { subscribe, set, update } = writable(saved);
+ return {
+ subscribe,
+ set: (value: T) => {
+ if (setAction) value = setAction(value);
+ return set(value);
+ },
+ update
+ };
+ };
+
+ hydratePersistedData = async () => {
+ const attributes = listPersistedAttributeRecords().map((record) => {
+ const attribute = new FabledAttribute({ name: record.name, location: 'local' });
+ attribute.load(record.data);
+ return attribute;
+ });
+
+ this.attributes.set(sort(attributes));
+ };
+
+ getDefaultAttributes = async (): Promise => {
+ const yaml = parseYaml(
+ await fetch(
+ 'https://raw.githubusercontent.com/magemonkeystudio/fabled/dev/src/main/resources/attributes.yml'
+ ).then((r) => r.text())
+ );
+ if (!yaml) return [];
+ return Object.keys(yaml).map((key: string) => {
+ const attrib: FabledAttribute = new FabledAttribute({ name: key });
+ attrib.load(yaml[key]);
+ return attrib;
+ });
+ };
+
+ attributes: Writable = this.setupAttributeStore(
+ 'attributes',
+ [],
+ (_data: string) => [],
+ (value: FabledAttribute[]) => {
+ classStore.updateAllAttributes(value.map((attr: FabledAttribute) => attr.name));
+ return sort(value);
+ }
+ );
+
+ getAttributeNames = (): string[] => {
+ return get(this.attributes).map((attr) => attr.name);
+ };
+
+ getAttribute = (name: string): FabledAttribute | undefined => {
+ for (const c of get(this.attributes)) {
+ if (c.name == name) return c;
+ }
+
+ return undefined;
+ };
+
+ isAttributeNameTaken = (name: string): boolean => !!this.getAttribute(name);
+
+ addAttribute = (name?: string): FabledAttribute => {
+ const allAttributes = get(this.attributes);
+ let index = allAttributes.length + 1;
+ while (!name && this.isAttributeNameTaken(name || 'attribute ' + index)) {
+ index++;
+ }
+ const attrib = new FabledAttribute({ name: name || 'attribute ' + index });
+ allAttributes.push(attrib);
+
+ this.attributes.set(allAttributes);
+ attrib.save();
+ return attrib;
+ };
+
+ loadAttributes = (e: ProgressEvent) => {
+ const text: string = e.target?.result;
+ if (!text) return;
+
+ this.loadAttributesText(text);
+ };
+
+ /**
+ * Loads attribute data from a file
+ * e - event details
+ */
+ loadAttributesText = (text: string, location: 'local' | 'server' = 'local') => {
+ const yaml = parseYaml(text);
+ if (!yaml) return;
+
+ // Get the current attributes
+ const currentAttributes = get(this.attributes);
+ // Create a map of current attributes for easy lookup
+ const currentAttributesMap = new Map(currentAttributes.map((attr) => [attr.name, attr]));
+
+ // Merge the current attributes with the new ones
+ const mergedAttributes = [...currentAttributes];
+ Object.keys(yaml).forEach((key: string) => {
+ // If the attribute already exists, ignore it
+ if (!currentAttributesMap.has(key)) {
+ // Otherwise, create a new attribute
+ const newAttribute = new FabledAttribute({ name: key, location });
+ newAttribute.load(yaml[key]);
+ mergedAttributes.push(newAttribute);
+ }
+ });
+
+ this.attributes.set(mergedAttributes);
+ this.refreshAttributes();
+ };
+
+ loadAttribute = async (data: FabledAttribute) => {
+ if (data.loaded) return;
+
+ if (data.location === 'local') {
+ const yamlData = await getPersistedAttribute(data.name);
+ if (!yamlData) return;
+ data.load(yamlData);
+ }
+ };
+
+ cloneAttribute = async (data: FabledAttribute): Promise => {
+ if (!data.loaded) {
+ await this.loadAttribute(data);
+ }
+
+ if (!data.loaded) {
+ throw new Error(`Cannot clone unloaded attribute "${data.name}". Load it before cloning.`);
+ }
+
+ const attr: FabledAttribute[] = get(this.attributes);
+ let name = data.name + ' (Copy)';
+ let i = 1;
+ while (this.isAttributeNameTaken(name)) {
+ name = data.name + ' (Copy ' + i + ')';
+ i++;
+ }
+ const attribute = new FabledAttribute();
+ const yamlData = data.serializeYaml();
+ attribute.load(yamlData);
+ attribute.name = name;
+ attr.push(attribute);
+
+ this.attributes.set(attr);
+ attribute.save();
+ return attribute;
+ };
+
+ refreshAttributes = () => this.attributes.set(sort(get(this.attributes)));
+
+ deleteAttribute = (data: FabledAttribute) => {
+ const filtered = get(this.attributes).filter((c) => c != data);
+ const act = get(active);
+ this.attributes.set(filtered);
+ this.saveAll();
+
+ if (!(act instanceof FabledAttribute)) return;
+
+ if (filtered.length === 0) {
+ goto(`${base}/`).then(() => {});
+ } else if (!filtered.find((attr) => attr === get(active))) {
+ goto(`${base}/attribute/${filtered[0].name}/edit`).then(() => {});
+ }
+ };
+
+ saveAll = () => {
+ const attributeYaml: MultiAttributeYamlData = {};
+ for (const attr of get(this.attributes)) {
+ attributeYaml[attr.name] = attr.serializeYaml();
+ }
+
+ void savePersistedAttributes(
+ Object.entries(attributeYaml).map(([name, data]) => ({
+ name,
+ data
+ }))
+ ).then((result) => {
+ if (!result.ok) {
+ console.error('Attributes Save error', result.error);
+ saveError.set({
+ name: 'Attributes',
+ message: getPersistenceFailureMessage(result)
+ });
+ return;
+ }
+
+ if (get(saveError)?.name === 'Attributes') {
+ saveError.set(undefined);
+ }
+ console.log('Saved attributes 😎');
+ });
+ };
}
-export const attributeStore = new AttributeStore();
\ No newline at end of file
+export const attributeStore = new AttributeStore();
diff --git a/src/data/class-store.svelte.ts b/src/data/class-store.svelte.ts
index 31c7b63127..e91fbc7d1e 100644
--- a/src/data/class-store.svelte.ts
+++ b/src/data/class-store.svelte.ts
@@ -1,713 +1,734 @@
-import type { Writable } from 'svelte/store';
-import { get, writable } from 'svelte/store';
-import { active } from './store';
-import { parseBool, sort, toEditorCase, toProperCase } from '$api/api';
-import { parseYaml } from '$api/yaml';
-import {
- browser
-} from '$app/environment';
-import {
- goto
-} from '$app/navigation';
-import { base } from '$app/paths';
-import type { ClassYamlData, FabledClassData, IAttribute, Icon, MultiClassYamlData, Serializable } from '$api/types';
-import YAML from 'yaml';
-import {
- socketService
-} from '$api/socket/socket-connector';
-import {
- notify
-} from '$api/notification-service';
+import type { Writable } from 'svelte/store';
+import { get, writable } from 'svelte/store';
+import { active, saveError } from './store';
+import { parseBool, sort, toEditorCase, toProperCase } from '$api/api';
+import { parseYaml } from '$api/yaml';
+import { goto } from '$app/navigation';
+import { base } from '$app/paths';
import type {
- SkillTree
-} from '$api/SkillTree';
-import FabledSkill, {
- skillStore
-} from './skill-store.svelte';
+ ClassYamlData,
+ FabledClassData,
+ IAttribute,
+ Icon,
+ MultiClassYamlData,
+ Serializable
+} from '$api/types';
+import { socketService } from '$api/socket/socket-connector';
+import { notify } from '$api/notification-service';
+import type { SkillTree } from '$api/SkillTree';
+import FabledSkill, { skillStore } from './skill-store.svelte';
+import { FabledFolder, folderStore, type FolderProperties } from './folder-store.svelte';
+import { getPersistenceFailureMessage } from './persistence-state';
import {
- FabledFolder,
- folderStore
-} from './folder-store.svelte';
+ deletePersistedClass,
+ getPersistedClass,
+ getPersistedFolders,
+ listPersistedClassNames,
+ savePersistedClass,
+ savePersistedFolders
+} from './editor-persistence';
export default class FabledClass implements Serializable {
- dataType = 'class';
- location: 'local' | 'server' = 'local';
- loaded = $state(false);
-
- isClass = true;
- public key = {};
- name: string = $state('');
- previousName: string = '';
- prefix = $state('');
- group = $state('class');
- manaName = $state('&2Mana');
- maxLevel = $state(40);
- parent?: FabledClass = $state();
- parentStr = $state(this.parent?.name);
-
- permission = $state(false);
- expSources = $state(273);
- manaRegen = $state(1);
- health: IAttribute = $state({ name: 'health', base: 20, scale: 1 });
- mana: IAttribute = $state({ name: 'mana', base: 20, scale: 1 });
- attributes: IAttribute[] = $state([]);
- skillTree: SkillTree = $state('Requirement');
- skills: FabledSkill[] = $state([]);
- icon: Icon = $state({
- material: 'Pumpkin',
- customModelData: 0
- });
- unusableItems: string[] = $state([]);
- actionBar = $state('');
-
- lInverted = $state(true);
- rInverted = $state(true);
- lsInverted = $state(true);
- rsInverted = $state(true);
- sInverted = $state(true);
- pInverted = $state(true);
- qInverted = $state(true);
- fInverted = $state(true);
-
- lWhitelist: string[] = $state([]);
- rWhitelist: string[] = $state([]);
- lsWhitelist: string[] = $state([]);
- rsWhitelist: string[] = $state([]);
- sWhitelist: string[] = $state([]);
- pWhitelist: string[] = $state([]);
- qWhitelist: string[] = $state([]);
- fWhitelist: string[] = $state([]);
-
- constructor(data?: FabledClassData) {
- this.name = data?.name || 'Class';
- this.prefix = data?.prefix || '&6' + this.name;
- if (!data) return;
- if (data?.location) this.location = data.location;
- if (data?.group) this.group = data.group;
- if (data?.manaName) this.manaName = data.manaName;
- if (data?.maxLevel) this.maxLevel = data.maxLevel;
- if (data?.parent) this.parent = data.parent;
- if (data?.permission !== undefined) this.permission = data.permission;
- if (data?.expSources) this.expSources = data.expSources;
- if (data?.health) this.health = data.health;
- if (data?.mana) this.mana = data.mana;
- if (data?.manaRegen) this.manaRegen = data.manaRegen;
- if (data?.attributes) this.attributes = data.attributes;
- if (data?.skillTree) this.skillTree = data.skillTree;
- if (data?.skills) this.skills = data.skills;
- if (data?.icon) this.icon = data.icon;
- if (data?.unusableItems) this.unusableItems = data.unusableItems;
- if (data?.actionBar) this.actionBar = data.actionBar;
-
- // Combo starters
- if (data?.lInverted !== undefined) this.lInverted = data.lInverted;
- if (data?.rInverted !== undefined) this.rInverted = data.rInverted;
- if (data?.lsInverted !== undefined) this.lsInverted = data.lsInverted;
- if (data?.rsInverted !== undefined) this.rsInverted = data.rsInverted;
- if (data?.pInverted !== undefined) this.pInverted = data.pInverted;
- if (data?.qInverted !== undefined) this.qInverted = data.qInverted;
- if (data?.fInverted !== undefined) this.fInverted = data.fInverted;
- if (data?.lWhitelist) this.lWhitelist = data.lWhitelist;
- if (data?.rWhitelist) this.rWhitelist = data.rWhitelist;
- if (data?.lsWhitelist) this.lsWhitelist = data.lsWhitelist;
- if (data?.rsWhitelist) this.rsWhitelist = data.rsWhitelist;
- if (data?.pWhitelist) this.pWhitelist = data.pWhitelist;
- if (data?.qWhitelist) this.qWhitelist = data.qWhitelist;
- if (data?.fWhitelist) this.fWhitelist = data.fWhitelist;
- }
-
- /**
- * Reads all the reactive state elements to act as a chane detector
- */
- public changed = () => {
- return {
- name: this.name,
- prefix: this.prefix,
- group: this.group,
- manaName: this.manaName,
- maxLevel: this.maxLevel,
- parent: this.parent,
- permission: this.permission,
- expSources: this.expSources,
- health: this.health,
- mana: this.mana,
- attributes: this.attributes,
- skillTree: this.skillTree,
- skills: this.skills,
- icon: this.icon,
- unusableItems: this.unusableItems,
- actionBar: this.actionBar,
- lInverted: this.lInverted,
- rInverted: this.rInverted,
- lsInverted: this.lsInverted,
- rsInverted: this.rsInverted,
- sInverted: this.sInverted,
- pInverted: this.pInverted,
- qInverted: this.qInverted,
- fInverted: this.fInverted,
- lWhitelist: this.lWhitelist,
- rWhitelist: this.rWhitelist,
- lsWhitelist: this.lsWhitelist,
- rsWhitelist: this.rsWhitelist,
- sWhitelist: this.sWhitelist,
- pWhitelist: this.pWhitelist,
- qWhitelist: this.qWhitelist,
- fWhitelist: this.fWhitelist
- };
- };
-
- public updateAttributes = (attribs: string[]) => {
- const included: string[] = [];
- this.attributes = this.attributes.filter(a => {
- if (attribs?.includes(a.name)) {
- included.push(a.name);
- return true;
- }
- return false;
- });
-
- attribs = attribs.filter(a => !included.includes(a));
-
- for (const attrib of attribs) {
- this.attributes.push({ name: attrib, base: 0, scale: 0 });
- }
- };
-
- public serializeYaml = (): ClassYamlData => {
- const health = {
- base: this.health.base,
- scale: this.health.scale
- };
- const mana = {
- base: this.mana.base,
- scale: this.mana.scale
- };
-
- // Attempt to convert health/mana base & scale to numbers, if applicable
- if (typeof (health.base) === 'string') {
- const base = parseFloat(health.base);
- if (!isNaN(base)) {
- health.base = base;
- }
- }
- if (typeof (health.scale) === 'string') {
- const scale = parseFloat(health.scale);
- if (!isNaN(scale)) {
- health.scale = scale;
- }
- }
- if (typeof (mana.base) === 'string') {
- const base = parseFloat(mana.base);
- if (!isNaN(base)) {
- mana.base = base;
- }
- }
- if (typeof (mana.scale) === 'string') {
- const scale = parseFloat(mana.scale);
- if (!isNaN(scale)) {
- mana.scale = scale;
- }
- }
-
- const yaml = {
- name: this.name,
- 'action-bar': this.actionBar,
- prefix: this.prefix,
- group: this.group,
- mana: this.manaName,
- 'max-level': this.maxLevel,
- parent: this.parent?.name || '',
- 'needs-permission': this.permission,
- attributes: {
- 'health-base': health.base ?? 20,
- 'health-scale': health.scale ?? 0,
- 'mana-base': mana.base ?? 20,
- 'mana-scale': mana.scale ?? 0
- },
- 'mana-regen': this.manaRegen,
- 'skill-tree': this.skillTree.toUpperCase().replace(/ /g, '_'),
- blacklist: this.unusableItems,
- skills: this.skills.map(s => s.name),
- icon: this.icon.material,
- 'icon-data': this.icon.customModelData,
- 'icon-lore': this.icon.lore,
- 'exp-source': this.expSources,
- 'combo-starters': {
- L: { inverted: this.lInverted, whitelist: this.lWhitelist },
- R: { inverted: this.rInverted, whitelist: this.rWhitelist },
- LS: { inverted: this.lsInverted, whitelist: this.lsWhitelist },
- RS: { inverted: this.rsInverted, whitelist: this.rsWhitelist },
- S: { inverted: this.sInverted, whitelist: this.sWhitelist },
- P: { inverted: this.pInverted, whitelist: this.pWhitelist },
- Q: { inverted: this.qInverted, whitelist: this.qWhitelist },
- F: { inverted: this.fInverted, whitelist: this.fWhitelist }
- }
- };
-
- this.attributes.forEach(attr => {
- if (typeof attr.base === 'string') {
- const base = parseFloat(attr.base);
- if (!isNaN(base)) {
- attr.base = base;
- }
- }
- if (typeof attr.scale === 'string') {
- const scale = parseFloat(attr.scale);
- if (!isNaN(scale)) {
- attr.scale = scale;
- }
- }
-
- yaml.attributes[`${attr.name.toLowerCase()}-base`] = attr.base || 0;
- yaml.attributes[`${attr.name.toLowerCase()}-scale`] = attr.scale || 0;
- });
-
- return yaml;
- };
-
- public updateParent = (classes: FabledClass[]) => {
- if (!this.parentStr) return;
- this.parent = classes.find(c => c.name === this.parentStr);
- };
-
- public load = (yaml: ClassYamlData) => {
- if (yaml.name) this.name = yaml.name;
- if (yaml['action-bar'] !== undefined) this.actionBar = yaml['action-bar'];
- if (yaml.mana !== undefined) this.manaName = yaml.mana;
- if (yaml.prefix !== undefined) this.prefix = yaml.prefix;
- if (yaml.group) this.group = yaml.group;
- if (yaml['max-level']) this.maxLevel = yaml['max-level'];
- if (yaml.parent) this.parentStr = yaml.parent;
- this.permission = parseBool(yaml['needs-permission']);
-
- if (yaml.attributes) {
- const attributes = yaml.attributes;
- this.health = {
- name: 'health',
- base: attributes['health-base'] ?? 20,
- scale: attributes['health-scale'] ?? 1
- };
- this.mana = { name: 'mana', base: attributes['mana-base'] ?? 20, scale: attributes['mana-scale'] ?? 1 };
-
- const map: { [key: string]: IAttribute } = {};
- for (const attrId of Object.keys(attributes)) {
- const split = attrId.split('-');
- const name = split[0];
- if (map[name] || name === 'health' || name === 'mana') continue;
-
- map[name] = { name, base: attributes[`${name}-base`], scale: attributes[`${name}-scale`] };
- }
- this.attributes = Object.values(map);
- }
-
- if (yaml['mana-regen']) this.manaRegen = yaml['mana-regen'];
- if (yaml['skill-tree']) this.skillTree = toProperCase(yaml['skill-tree']);
- if (yaml.blacklist) this.unusableItems = yaml.blacklist;
- if (yaml.skills) this.skills = yaml.skills.map(s => skillStore.getSkill(s)).filter(s => !!s);
- if (yaml.icon) this.icon.material = toEditorCase(yaml.icon);
- if (yaml['icon-data']) this.icon.customModelData = yaml['icon-data'];
- if (yaml['icon-lore']) this.icon.lore = yaml['icon-lore'];
- if (yaml['exp-source'] !== null) this.expSources = yaml['exp-source'];
-
- if (yaml['combo-starters']) {
- // Combo starters
- const combos = yaml['combo-starters'];
- if (combos) {
- this.lInverted = parseBool(combos.L?.inverted);
- this.rInverted = parseBool(combos.R?.inverted);
- this.lsInverted = parseBool(combos.LS?.inverted);
- this.rsInverted = parseBool(combos.RS?.inverted);
- this.sInverted = parseBool(combos.S?.inverted);
- this.pInverted = parseBool(combos.P?.inverted);
- this.qInverted = parseBool(combos.Q?.inverted);
- this.fInverted = parseBool(combos.F?.inverted);
- this.lWhitelist = combos.L?.whitelist || [];
- this.rWhitelist = combos.R?.whitelist || [];
- this.lsWhitelist = combos.LS?.whitelist || [];
- this.rsWhitelist = combos.RS?.whitelist || [];
- this.sWhitelist = combos.S?.whitelist || [];
- this.pWhitelist = combos.P?.whitelist || [];
- this.qWhitelist = combos.Q?.whitelist || [];
- this.fWhitelist = combos.F?.whitelist || [];
- }
- }
-
- this.loaded = true;
- this.save();
- };
-
- public save = () => {
- if (!this.name) return;
-
- if (this.location === 'server') {
- return;
- }
-
- this.changed();
-
- const yaml = YAML.stringify({ [this.name]: this.serializeYaml() }, { lineWidth: 0, aliasDuplicateObjects: false });
-
- if (this.previousName && this.previousName !== this.name) {
- localStorage.removeItem('sapi.class.' + this.previousName);
- }
- this.previousName = this.name;
- localStorage.setItem('sapi.class.' + this.name, yaml);
-
- console.log('Saved ' + this.name + ' 😎');
- };
+ dataType = 'class';
+ location: 'local' | 'server' = 'local';
+ loaded = $state(false);
+
+ isClass = true;
+ public key = {};
+ name: string = $state('');
+ previousName: string = '';
+ prefix = $state('');
+ group = $state('class');
+ manaName = $state('&2Mana');
+ maxLevel = $state(40);
+ parent?: FabledClass = $state();
+ parentStr = $state(this.parent?.name);
+
+ permission = $state(false);
+ expSources = $state(273);
+ manaRegen = $state(1);
+ health: IAttribute = $state({ name: 'health', base: 20, scale: 1 });
+ mana: IAttribute = $state({ name: 'mana', base: 20, scale: 1 });
+ attributes: IAttribute[] = $state([]);
+ skillTree: SkillTree = $state('Requirement');
+ skills: FabledSkill[] = $state([]);
+ icon: Icon = $state({
+ material: 'Pumpkin',
+ customModelData: 0
+ });
+ unusableItems: string[] = $state([]);
+ actionBar = $state('');
+
+ lInverted = $state(true);
+ rInverted = $state(true);
+ lsInverted = $state(true);
+ rsInverted = $state(true);
+ sInverted = $state(true);
+ pInverted = $state(true);
+ qInverted = $state(true);
+ fInverted = $state(true);
+
+ lWhitelist: string[] = $state([]);
+ rWhitelist: string[] = $state([]);
+ lsWhitelist: string[] = $state([]);
+ rsWhitelist: string[] = $state([]);
+ sWhitelist: string[] = $state([]);
+ pWhitelist: string[] = $state([]);
+ qWhitelist: string[] = $state([]);
+ fWhitelist: string[] = $state([]);
+
+ constructor(data?: FabledClassData) {
+ this.name = data?.name || 'Class';
+ this.prefix = data?.prefix || '&6' + this.name;
+ if (!data) return;
+ if (data?.location) this.location = data.location;
+ if (data?.group) this.group = data.group;
+ if (data?.manaName) this.manaName = data.manaName;
+ if (data?.maxLevel) this.maxLevel = data.maxLevel;
+ if (data?.parent) this.parent = data.parent;
+ if (data?.permission !== undefined) this.permission = data.permission;
+ if (data?.expSources) this.expSources = data.expSources;
+ if (data?.health) this.health = data.health;
+ if (data?.mana) this.mana = data.mana;
+ if (data?.manaRegen) this.manaRegen = data.manaRegen;
+ if (data?.attributes) this.attributes = data.attributes;
+ if (data?.skillTree) this.skillTree = data.skillTree;
+ if (data?.skills) this.skills = data.skills;
+ if (data?.icon) this.icon = data.icon;
+ if (data?.unusableItems) this.unusableItems = data.unusableItems;
+ if (data?.actionBar) this.actionBar = data.actionBar;
+
+ // Combo starters
+ if (data?.lInverted !== undefined) this.lInverted = data.lInverted;
+ if (data?.rInverted !== undefined) this.rInverted = data.rInverted;
+ if (data?.lsInverted !== undefined) this.lsInverted = data.lsInverted;
+ if (data?.rsInverted !== undefined) this.rsInverted = data.rsInverted;
+ if (data?.pInverted !== undefined) this.pInverted = data.pInverted;
+ if (data?.qInverted !== undefined) this.qInverted = data.qInverted;
+ if (data?.fInverted !== undefined) this.fInverted = data.fInverted;
+ if (data?.lWhitelist) this.lWhitelist = data.lWhitelist;
+ if (data?.rWhitelist) this.rWhitelist = data.rWhitelist;
+ if (data?.lsWhitelist) this.lsWhitelist = data.lsWhitelist;
+ if (data?.rsWhitelist) this.rsWhitelist = data.rsWhitelist;
+ if (data?.pWhitelist) this.pWhitelist = data.pWhitelist;
+ if (data?.qWhitelist) this.qWhitelist = data.qWhitelist;
+ if (data?.fWhitelist) this.fWhitelist = data.fWhitelist;
+ }
+
+ /**
+ * Reads all the reactive state elements to act as a chane detector
+ */
+ public changed = () => {
+ return {
+ name: this.name,
+ prefix: this.prefix,
+ group: this.group,
+ manaName: this.manaName,
+ maxLevel: this.maxLevel,
+ parent: this.parent,
+ permission: this.permission,
+ expSources: this.expSources,
+ health: this.health,
+ mana: this.mana,
+ attributes: this.attributes,
+ skillTree: this.skillTree,
+ skills: this.skills,
+ icon: this.icon,
+ unusableItems: this.unusableItems,
+ actionBar: this.actionBar,
+ lInverted: this.lInverted,
+ rInverted: this.rInverted,
+ lsInverted: this.lsInverted,
+ rsInverted: this.rsInverted,
+ sInverted: this.sInverted,
+ pInverted: this.pInverted,
+ qInverted: this.qInverted,
+ fInverted: this.fInverted,
+ lWhitelist: this.lWhitelist,
+ rWhitelist: this.rWhitelist,
+ lsWhitelist: this.lsWhitelist,
+ rsWhitelist: this.rsWhitelist,
+ sWhitelist: this.sWhitelist,
+ pWhitelist: this.pWhitelist,
+ qWhitelist: this.qWhitelist,
+ fWhitelist: this.fWhitelist
+ };
+ };
+
+ public updateAttributes = (attribs: string[]) => {
+ const included: string[] = [];
+ this.attributes = this.attributes.filter((a) => {
+ if (attribs?.includes(a.name)) {
+ included.push(a.name);
+ return true;
+ }
+ return false;
+ });
+
+ attribs = attribs.filter((a) => !included.includes(a));
+
+ for (const attrib of attribs) {
+ this.attributes.push({ name: attrib, base: 0, scale: 0 });
+ }
+ };
+
+ public serializeYaml = (): ClassYamlData => {
+ const health = {
+ base: this.health.base,
+ scale: this.health.scale
+ };
+ const mana = {
+ base: this.mana.base,
+ scale: this.mana.scale
+ };
+
+ // Attempt to convert health/mana base & scale to numbers, if applicable
+ if (typeof health.base === 'string') {
+ const base = parseFloat(health.base);
+ if (!isNaN(base)) {
+ health.base = base;
+ }
+ }
+ if (typeof health.scale === 'string') {
+ const scale = parseFloat(health.scale);
+ if (!isNaN(scale)) {
+ health.scale = scale;
+ }
+ }
+ if (typeof mana.base === 'string') {
+ const base = parseFloat(mana.base);
+ if (!isNaN(base)) {
+ mana.base = base;
+ }
+ }
+ if (typeof mana.scale === 'string') {
+ const scale = parseFloat(mana.scale);
+ if (!isNaN(scale)) {
+ mana.scale = scale;
+ }
+ }
+
+ const yaml = {
+ name: this.name,
+ 'action-bar': this.actionBar,
+ prefix: this.prefix,
+ group: this.group,
+ mana: this.manaName,
+ 'max-level': this.maxLevel,
+ parent: this.parent?.name || '',
+ 'needs-permission': this.permission,
+ attributes: {
+ 'health-base': health.base ?? 20,
+ 'health-scale': health.scale ?? 0,
+ 'mana-base': mana.base ?? 20,
+ 'mana-scale': mana.scale ?? 0
+ },
+ 'mana-regen': this.manaRegen,
+ 'skill-tree': this.skillTree.toUpperCase().replace(/ /g, '_'),
+ blacklist: this.unusableItems,
+ skills: this.skills.map((s) => s.name),
+ icon: this.icon.material,
+ 'icon-data': this.icon.customModelData,
+ 'icon-lore': this.icon.lore,
+ 'exp-source': this.expSources,
+ 'combo-starters': {
+ L: { inverted: this.lInverted, whitelist: this.lWhitelist },
+ R: { inverted: this.rInverted, whitelist: this.rWhitelist },
+ LS: { inverted: this.lsInverted, whitelist: this.lsWhitelist },
+ RS: { inverted: this.rsInverted, whitelist: this.rsWhitelist },
+ S: { inverted: this.sInverted, whitelist: this.sWhitelist },
+ P: { inverted: this.pInverted, whitelist: this.pWhitelist },
+ Q: { inverted: this.qInverted, whitelist: this.qWhitelist },
+ F: { inverted: this.fInverted, whitelist: this.fWhitelist }
+ }
+ };
+
+ this.attributes.forEach((attr) => {
+ if (typeof attr.base === 'string') {
+ const base = parseFloat(attr.base);
+ if (!isNaN(base)) {
+ attr.base = base;
+ }
+ }
+ if (typeof attr.scale === 'string') {
+ const scale = parseFloat(attr.scale);
+ if (!isNaN(scale)) {
+ attr.scale = scale;
+ }
+ }
+
+ yaml.attributes[`${attr.name.toLowerCase()}-base`] = attr.base || 0;
+ yaml.attributes[`${attr.name.toLowerCase()}-scale`] = attr.scale || 0;
+ });
+
+ return yaml;
+ };
+
+ public updateParent = (classes: FabledClass[]) => {
+ if (!this.parentStr) return;
+ this.parent = classes.find((c) => c.name === this.parentStr);
+ };
+
+ public load = (yaml: ClassYamlData) => {
+ if (yaml.name) this.name = yaml.name;
+ if (yaml['action-bar'] !== undefined) this.actionBar = yaml['action-bar'];
+ if (yaml.mana !== undefined) this.manaName = yaml.mana;
+ if (yaml.prefix !== undefined) this.prefix = yaml.prefix;
+ if (yaml.group) this.group = yaml.group;
+ if (yaml['max-level']) this.maxLevel = yaml['max-level'];
+ if (yaml.parent) this.parentStr = yaml.parent;
+ this.permission = parseBool(yaml['needs-permission']);
+
+ if (yaml.attributes) {
+ const attributes = yaml.attributes;
+ this.health = {
+ name: 'health',
+ base: attributes['health-base'] ?? 20,
+ scale: attributes['health-scale'] ?? 1
+ };
+ this.mana = {
+ name: 'mana',
+ base: attributes['mana-base'] ?? 20,
+ scale: attributes['mana-scale'] ?? 1
+ };
+
+ const map: { [key: string]: IAttribute } = {};
+ for (const attrId of Object.keys(attributes)) {
+ const split = attrId.split('-');
+ const name = split[0];
+ if (map[name] || name === 'health' || name === 'mana') continue;
+
+ map[name] = { name, base: attributes[`${name}-base`], scale: attributes[`${name}-scale`] };
+ }
+ this.attributes = Object.values(map);
+ }
+
+ if (yaml['mana-regen']) this.manaRegen = yaml['mana-regen'];
+ if (yaml['skill-tree']) this.skillTree = toProperCase(yaml['skill-tree']);
+ if (yaml.blacklist) this.unusableItems = yaml.blacklist;
+ if (yaml.skills)
+ this.skills = (
+ yaml.skills.map((s) => skillStore.getSkill(s)).filter((s) => !!s)
+ );
+ if (yaml.icon) this.icon.material = toEditorCase(yaml.icon);
+ if (yaml['icon-data']) this.icon.customModelData = yaml['icon-data'];
+ if (yaml['icon-lore']) this.icon.lore = yaml['icon-lore'];
+ if (yaml['exp-source'] !== null) this.expSources = yaml['exp-source'];
+
+ if (yaml['combo-starters']) {
+ // Combo starters
+ const combos = yaml['combo-starters'];
+ if (combos) {
+ this.lInverted = parseBool(combos.L?.inverted);
+ this.rInverted = parseBool(combos.R?.inverted);
+ this.lsInverted = parseBool(combos.LS?.inverted);
+ this.rsInverted = parseBool(combos.RS?.inverted);
+ this.sInverted = parseBool(combos.S?.inverted);
+ this.pInverted = parseBool(combos.P?.inverted);
+ this.qInverted = parseBool(combos.Q?.inverted);
+ this.fInverted = parseBool(combos.F?.inverted);
+ this.lWhitelist = combos.L?.whitelist || [];
+ this.rWhitelist = combos.R?.whitelist || [];
+ this.lsWhitelist = combos.LS?.whitelist || [];
+ this.rsWhitelist = combos.RS?.whitelist || [];
+ this.sWhitelist = combos.S?.whitelist || [];
+ this.pWhitelist = combos.P?.whitelist || [];
+ this.qWhitelist = combos.Q?.whitelist || [];
+ this.fWhitelist = combos.F?.whitelist || [];
+ }
+ }
+
+ this.loaded = true;
+ this.save();
+ };
+
+ public save = () => {
+ if (!this.name) return;
+
+ if (this.location === 'server') {
+ return;
+ }
+
+ this.changed();
+
+ void savePersistedClass(this.name, this.serializeYaml(), this.previousName || undefined).then(
+ (result) => {
+ if (!result.ok) {
+ console.error(this.name + ' Save error', result.error);
+ saveError.set({
+ name: this.name,
+ message: getPersistenceFailureMessage(result)
+ });
+ return;
+ }
+
+ this.previousName = this.name;
+ if (get(saveError)?.name === this.name) {
+ saveError.set(undefined);
+ }
+
+ console.log('Saved ' + this.name + ' 😎');
+ }
+ );
+ };
}
class ClassStoreSvelte {
- isLegacy = false;
-
- private loadClassesFromServer = async () => {
- let serverClasses: string[];
- try {
- serverClasses = await socketService.getClasses();
- } catch (_) {
- return;
- }
-
- const tempFolders = get(this.classFolders);
- const tempClasses = get(this.classes);
- serverClasses.forEach(c => {
- const parts = c.split('/');
- const name = parts.pop();
- if (!name) return;
-
- let previous: FabledFolder | undefined;
- let folder: FabledFolder | undefined;
- parts.forEach(part => {
- folder = previous ? previous.getSubfolder(part) : tempFolders.find(f => f.name === part);
- if (!folder) {
- folder = new FabledFolder();
- folder.name = part;
- folder.location = 'server';
- if (previous) {
- previous.add(folder);
- folder.updateParent(previous);
- }
- }
- if (!previous && !tempFolders.includes(folder)) tempFolders.push(folder);
- previous = folder;
- });
-
- // If we already have this class, don't add it
- if (tempClasses.find(cl => cl.name === c)) return;
-
- const clazz = new FabledClass({ name, location: 'server' });
- if (folder) folder.add(clazz);
- tempClasses.push(clazz);
- });
- this.classes.set(tempClasses);
- };
-
- private removeServerClasses = () => {
- const tempClasses = get(this.classes);
- this.classes.set(tempClasses.filter(c => c.location !== 'server'));
-
- const tempFolders = get(this.classFolders);
- tempFolders.filter(f => f.location === 'server').forEach(f => this.deleteClassFolder(f, (sb) => sb.location === 'server'));
- };
-
- constructor() {
- socketService.onConnect(this.loadClassesFromServer);
- socketService.onDisconnect(this.removeServerClasses);
-
- if (this.isLegacy) {
- get(this.classes).forEach(clazz => {
- if (clazz.location === 'local') clazz.save();
- });
- this.persistClasses();
- }
- }
-
- private loadClassTextToArray = (text: string): FabledClass[] => {
- const list: FabledClass[] = [];
- // Load classes
- const data = parseYaml(text);
- const keys = Object.keys(data);
-
- let clazz: FabledClass;
- // If we only have one class, and it is the current YAML,
- // the structure is a bit different
- if (keys.length == 1) {
- const key = keys[0];
- if (key === 'loaded') return list;
- clazz = new FabledClass({ name: key });
- clazz.load(data[key]);
- list.push(clazz);
- return list;
- }
-
- for (const key of Object.keys(data)) {
- if (key != 'loaded') {
- clazz = new FabledClass({ name: key });
- clazz.load(data[key]);
- list.push(clazz);
- }
- }
- return list;
- };
-
- private setupClassStore = (key: string,
- def: T,
- mapper: (data: string) => T,
- setAction: (data: T) => T,
- postLoad?: (saved: T) => void): Writable => {
- let saved: T = def;
- if (browser) {
- const stored = localStorage.getItem(key);
- if (stored) {
- saved = mapper(stored);
- if (postLoad) postLoad(saved);
- }
- }
-
- const {
- subscribe,
- set,
- update
- } = writable(saved);
- return {
- subscribe,
- set: (value: T) => {
- if (setAction) value = setAction(value);
- return set(value);
- },
- update
- };
- };
-
- classes: Writable = this.setupClassStore(
- browser && localStorage.getItem('classNames') ? 'classNames' : 'classData', [],
- (data: string) => {
- if (localStorage.getItem('classNames')) {
- return data.split(', ').map(name => new FabledClass({
- name,
- location: 'local'
- })).filter(cl => localStorage.getItem('sapi.class.' + cl.name));
- } else {
- localStorage.removeItem('classData');
- this.isLegacy = true;
- return sort(this.loadClassTextToArray(data));
- }
- },
- (value: FabledClass[]) => {
- this.persistClasses(value);
- value.forEach(c => c.updateParent(value));
- return sort(value);
- },
- (saved: FabledClass[]) => saved.forEach(c => c.updateParent(saved))); // This will be the gotcha here
-
- getClass = (name: string): FabledClass | undefined => {
- for (const c of get(this.classes)) {
- if (c.name == name) return c;
- }
-
- return undefined;
- };
-
- classFolders: Writable = this.setupClassStore('classFolders', [],
- (data: string) => {
- if (!data || data === 'null') return [];
-
- try {
- return JSON.parse(data, (key: string, value) => {
- if (!value) return;
- if (/\d+/.test(key)) {
- if (typeof (value) === 'string') {
- return this.getClass(value);
- }
-
- const folder = new FabledFolder(value.data);
- folder.name = value.name;
- return folder;
- }
- return value;
- });
- } catch (e) {
- console.error('Error loading class folders. Folder data: ' + data, e);
- notify('Error loading class folders. ' + JSON.stringify(e) + '\nFolder data: ' + data);
- return [];
- }
- },
- (value: FabledFolder[]) => {
- const data = JSON.stringify(value, (key, value: FabledFolder | FabledClass | FabledSkill) => {
- if (value instanceof FabledClass || value instanceof FabledSkill) return value.name;
- else if (key === 'parent') return undefined;
- return value;
- });
- localStorage.setItem('classFolders', data);
- return sort(value);
- });
-
- updateAllAttributes = (attributes: string[]) =>
- get(this.classes).forEach(c => c.updateAttributes(attributes));
-
- isClassNameTaken = (name: string): boolean => !!this.getClass(name);
-
- addClass = (name?: string): FabledClass => {
- const cl = get(this.classes);
- let index = cl.length + 1;
- while (!name && this.isClassNameTaken(name || 'Class ' + index)) {
- index++;
- }
- const clazz = new FabledClass({ name: (name || 'Class ' + index) });
- cl.push(clazz);
-
- this.classes.set(cl);
- clazz.save();
- return clazz;
- };
-
- loadClass = async (data: FabledClass) => {
- if (data.loaded) return;
- let yamlData: MultiClassYamlData;
-
- if (data.location === 'local') {
- yamlData = parseYaml(localStorage.getItem(`sapi.class.${data.name}`) || '');
- } else {
- const yaml = await socketService.getClassYaml(data.name);
- if (!yaml) return;
- yamlData = YAML.parse(yaml);
- }
-
- if (yamlData === null || Object.values(yamlData).length == 0) {
- console.warn(`Failed to parse yaml for class ${data.name}`, localStorage.getItem(`sapi.class.${data.name}`));
- return;
- }
-
- const clazz = Object.values(yamlData)[0];
- data.load(clazz);
-
- data.updateParent(get(this.classes));
- data.loaded = true;
- };
-
- cloneClass = async (data: FabledClass): Promise => {
- if (!data.loaded) await this.loadClass(data);
-
- const cl: FabledClass[] = get(this.classes);
- let name = data.name + ' (Copy)';
- let i = 1;
- while (this.isClassNameTaken(name)) {
- name = data.name + ' (Copy ' + i + ')';
- i++;
- }
- const clazz = new FabledClass();
- const yamlData = data.serializeYaml();
- clazz.load(yamlData);
- clazz.name = name;
- cl.push(clazz);
-
- this.classes.set(cl);
- clazz.save();
- return clazz;
- };
-
- addClassFolder = (folder: FabledFolder) => {
- const folders = get(this.classFolders);
- if (folders.includes(folder)) return;
-
- folderStore.rename(folder, folders);
-
- folders.push(folder);
- folders.sort((a, b) => a.name.localeCompare(b.name));
- this.classFolders.set(folders);
- };
-
- deleteClassFolder = (folder: FabledFolder, deleteCheck?: (subfolder: FabledFolder) => boolean) => {
- const folders = get(this.classFolders).filter(f => f != folder);
-
- folder.data.forEach(d => {
- if (d instanceof FabledFolder) {
- if (deleteCheck && deleteCheck(d)) {
- this.deleteClassFolder(d, deleteCheck);
- return;
- }
- if (folder.parent) folder.parent.add(d);
- else {
- d.updateParent();
- folders.push(d);
- }
- } else if (folder.parent)
- folder.parent.add(d); // Add the class to the parent folder
- });
-
- this.classFolders.set(folders);
- };
-
- deleteClass = (data: FabledClass) => {
- const filtered = get(this.classes).filter(c => c != data);
- const act = get(active);
- this.classes.set(filtered);
- localStorage.removeItem('sapi.class.' + data.name);
-
- if (!(act instanceof FabledClass)) return;
-
- if (filtered.length === 0) goto(`${base}/`);
- else if (!filtered.find(cl => cl === get(active))) goto(`${base}/class/${filtered[0].name}/edit`).then(() => {
- });
- };
-
- refreshClasses = () => this.classes.set(sort(get(this.classes)));
- refreshClassFolders = () => {
- this.classFolders.set(sort(get(this.classFolders)));
- this.refreshClasses();
- };
-
-
- /**
- * Loads class data from a string
- */
- loadClassText = (text: string, fromServer: boolean = false) => {
- // Load new classes
- const data = parseYaml(text);
-
- if (!data || Object.keys(data).length === 0) {
- // If there is no data or the object is empty... return
- return;
- }
-
- const keys = Object.keys(data);
-
- let clazz: FabledClass;
- // If we only have one class, and it is the current YAML,
- // the structure is a bit different
- if (keys.length == 1) {
- const key: string = keys[0];
- clazz = ((this.isClassNameTaken(key)
- ? this.getClass(key)
- : this.addClass(key)));
- if (fromServer) clazz.location = 'server';
- clazz.load(data[key]);
- this.refreshClasses();
- return;
- }
-
- for (const key of Object.keys(data)) {
- if (key != 'loaded' && !this.isClassNameTaken(key)) {
- clazz = ((this.isClassNameTaken(key)
- ? this.getClass(key)
- : this.addClass(key)));
- clazz.load(data[key]);
- }
- }
- this.refreshClasses();
- };
-
- loadClasses = (e: ProgressEvent) => {
- const text: string = e.target?.result;
- if (!text) return;
-
- this.loadClassText(text);
- };
-
- persistClasses = (list?: FabledClass[]) => {
- const classList = (list || get(this.classes)).filter(c => c.location === 'local');
- localStorage.setItem('classNames', classList.map(c => c.name).join(', '));
- };
+ isLegacy = false;
+
+ private loadClassesFromServer = async () => {
+ let serverClasses: string[];
+ try {
+ serverClasses = await socketService.getClasses();
+ } catch (_) {
+ return;
+ }
+
+ const tempFolders = get(this.classFolders);
+ const tempClasses = get(this.classes);
+ serverClasses.forEach((c) => {
+ const parts = c.split('/');
+ const name = parts.pop();
+ if (!name) return;
+
+ let previous: FabledFolder | undefined;
+ let folder: FabledFolder | undefined;
+ parts.forEach((part) => {
+ folder = previous ? previous.getSubfolder(part) : tempFolders.find((f) => f.name === part);
+ if (!folder) {
+ folder = new FabledFolder();
+ folder.name = part;
+ folder.location = 'server';
+ if (previous) {
+ previous.add(folder);
+ folder.updateParent(previous);
+ }
+ }
+ if (!previous && !tempFolders.includes(folder)) tempFolders.push(folder);
+ previous = folder;
+ });
+
+ // If we already have this class, don't add it
+ if (tempClasses.find((cl) => cl.name === name)) return;
+
+ const clazz = new FabledClass({ name, location: 'server' });
+ if (folder) folder.add(clazz);
+ tempClasses.push(clazz);
+ });
+ this.classes.set(tempClasses);
+ };
+
+ private removeServerClasses = () => {
+ const tempClasses = get(this.classes);
+ this.classes.set(tempClasses.filter((c) => c.location !== 'server'));
+
+ const tempFolders = get(this.classFolders);
+ tempFolders
+ .filter((f) => f.location === 'server')
+ .forEach((f) => this.deleteClassFolder(f, (sb) => sb.location === 'server'));
+ };
+
+ constructor() {
+ socketService.onConnect(this.loadClassesFromServer);
+ socketService.onDisconnect(this.removeServerClasses);
+ }
+
+ private loadClassTextToArray = (text: string): FabledClass[] => {
+ const list: FabledClass[] = [];
+ // Load classes
+ const data = parseYaml(text);
+ const keys = Object.keys(data);
+
+ let clazz: FabledClass;
+ // If we only have one class, and it is the current YAML,
+ // the structure is a bit different
+ if (keys.length == 1) {
+ const key = keys[0];
+ if (key === 'loaded') return list;
+ clazz = new FabledClass({ name: key });
+ clazz.load(data[key]);
+ list.push(clazz);
+ return list;
+ }
+
+ for (const key of Object.keys(data)) {
+ if (key != 'loaded') {
+ clazz = new FabledClass({ name: key });
+ clazz.load(data[key]);
+ list.push(clazz);
+ }
+ }
+ return list;
+ };
+
+ private setupClassStore = (
+ _key: string,
+ def: T,
+ mapper: (data: string) => T,
+ setAction: (data: T) => T,
+ postLoad?: (saved: T) => void
+ ): Writable => {
+ let saved: T = def;
+ if (postLoad) postLoad(saved);
+
+ const { subscribe, set, update } = writable(saved);
+ return {
+ subscribe,
+ set: (value: T) => {
+ if (setAction) value = setAction(value);
+ return set(value);
+ },
+ update
+ };
+ };
+
+ private deserializeClassFolders = (data: string | FolderProperties[]): FabledFolder[] => {
+ const serialized = typeof data === 'string' ? data : JSON.stringify(data);
+ if (!serialized || serialized === 'null') return [];
+
+ try {
+ return JSON.parse(serialized, (key: string, value) => {
+ if (value == null) return;
+ if (/\d+/.test(key)) {
+ if (typeof value === 'string') {
+ return this.getClass(value);
+ }
+
+ const folder = new FabledFolder(value.data);
+ folder.name = value.name;
+ folder.location = value.location || 'local';
+ folder.open = !!value.open;
+ return folder;
+ }
+ return value;
+ });
+ } catch (e) {
+ console.error('Error loading class folders. Folder data: ' + serialized, e);
+ notify('Error loading class folders. ' + JSON.stringify(e) + '\nFolder data: ' + serialized);
+ return [];
+ }
+ };
+
+ hydratePersistedData = async () => {
+ const classes = listPersistedClassNames().map(
+ (name) =>
+ new FabledClass({
+ name,
+ location: 'local'
+ })
+ );
+
+ this.classes.set(sort(classes));
+ this.classFolders.set(
+ sort(this.deserializeClassFolders(getPersistedFolders('class')))
+ );
+ };
+
+ classes: Writable = this.setupClassStore(
+ 'classes',
+ [],
+ (_data: string) => [],
+ (value: FabledClass[]) => {
+ this.persistClasses(value);
+ value.forEach((c) => c.updateParent(value));
+ return sort(value);
+ },
+ (saved: FabledClass[]) => saved.forEach((c) => c.updateParent(saved))
+ ); // This will be the gotcha here
+
+ getClass = (name: string): FabledClass | undefined => {
+ for (const c of get(this.classes)) {
+ if (c.name == name) return c;
+ }
+
+ return undefined;
+ };
+
+ classFolders: Writable = this.setupClassStore(
+ 'class-folders',
+ [],
+ (_data: string) => [],
+ (value: FabledFolder[]) => {
+ void savePersistedFolders(
+ 'class',
+ value.filter((folder) => folder.location === 'local').map((folder) => folder.toJSON())
+ ).then((result) => {
+ if (result.ok) {
+ if (get(saveError)?.name === 'Classes') {
+ saveError.set(undefined);
+ }
+ return;
+ }
+ console.error('Class folder save error', result.error);
+ saveError.set({
+ name: 'Classes',
+ message: getPersistenceFailureMessage(result)
+ });
+ });
+ return sort(value);
+ }
+ );
+
+ updateAllAttributes = (attributes: string[]) =>
+ get(this.classes).forEach((c) => c.updateAttributes(attributes));
+
+ isClassNameTaken = (name: string): boolean => !!this.getClass(name);
+
+ addClass = (name?: string): FabledClass => {
+ const cl = get(this.classes);
+ let index = cl.length + 1;
+ while (!name && this.isClassNameTaken(name || 'Class ' + index)) {
+ index++;
+ }
+ const clazz = new FabledClass({ name: name || 'Class ' + index });
+ cl.push(clazz);
+
+ this.classes.set(cl);
+ clazz.save();
+ return clazz;
+ };
+
+ loadClass = async (data: FabledClass) => {
+ if (data.loaded) return;
+
+ if (data.location === 'local') {
+ const yamlData = await getPersistedClass(data.name);
+ if (!yamlData) return;
+ data.load(yamlData);
+ } else {
+ const yaml = await socketService.getClassYaml(data.name);
+ if (!yaml) return;
+ const yamlData = parseYaml(yaml);
+ if (yamlData === null || Object.values(yamlData).length == 0) {
+ console.warn(`Failed to parse yaml for class ${data.name}`, yaml);
+ return;
+ }
+
+ const clazz = Object.values(yamlData)[0];
+ data.load(clazz);
+ }
+
+ data.updateParent(get(this.classes));
+ data.loaded = true;
+ };
+
+ cloneClass = async (data: FabledClass): Promise => {
+ if (!data.loaded) await this.loadClass(data);
+
+ const cl: FabledClass[] = get(this.classes);
+ let name = data.name + ' (Copy)';
+ let i = 1;
+ while (this.isClassNameTaken(name)) {
+ name = data.name + ' (Copy ' + i + ')';
+ i++;
+ }
+ const clazz = new FabledClass();
+ const yamlData = data.serializeYaml();
+ clazz.load(yamlData);
+ clazz.name = name;
+ cl.push(clazz);
+
+ this.classes.set(cl);
+ clazz.save();
+ return clazz;
+ };
+
+ addClassFolder = (folder: FabledFolder) => {
+ const folders = get(this.classFolders);
+ if (folders.includes(folder)) return;
+
+ folderStore.rename(folder, folders);
+
+ folders.push(folder);
+ folders.sort((a, b) => a.name.localeCompare(b.name));
+ this.classFolders.set(folders);
+ };
+
+ deleteClassFolder = (
+ folder: FabledFolder,
+ deleteCheck?: (subfolder: FabledFolder) => boolean
+ ) => {
+ const folders = get(this.classFolders).filter((f) => f != folder);
+
+ folder.data.forEach((d) => {
+ if (d instanceof FabledFolder) {
+ if (deleteCheck && deleteCheck(d)) {
+ this.deleteClassFolder(d, deleteCheck);
+ return;
+ }
+ if (folder.parent) folder.parent.add(d);
+ else {
+ d.updateParent();
+ folders.push(d);
+ }
+ } else if (folder.parent) folder.parent.add(d); // Add the class to the parent folder
+ });
+
+ this.classFolders.set(folders);
+ };
+
+ deleteClass = (data: FabledClass) => {
+ const filtered = get(this.classes).filter((c) => c != data);
+ const act = get(active);
+ this.classes.set(filtered);
+ void deletePersistedClass(data.name);
+
+ if (!(act instanceof FabledClass)) return;
+
+ if (filtered.length === 0) goto(`${base}/`);
+ else if (!filtered.find((cl) => cl === get(active)))
+ goto(`${base}/class/${filtered[0].name}/edit`).then(() => {});
+ };
+
+ refreshClasses = () => this.classes.set(sort(get(this.classes)));
+ refreshClassFolders = () => {
+ this.classFolders.set(sort(get(this.classFolders)));
+ this.refreshClasses();
+ };
+
+ /**
+ * Loads class data from a string
+ */
+ loadClassText = (text: string, fromServer: boolean = false) => {
+ // Load new classes
+ const data = parseYaml(text);
+
+ if (!data || Object.keys(data).length === 0) {
+ // If there is no data or the object is empty... return
+ return;
+ }
+
+ const keys = Object.keys(data);
+
+ let clazz: FabledClass;
+ // If we only have one class, and it is the current YAML,
+ // the structure is a bit different
+ if (keys.length == 1) {
+ const key: string = keys[0];
+ clazz = (this.isClassNameTaken(key) ? this.getClass(key) : this.addClass(key));
+ if (fromServer) clazz.location = 'server';
+ clazz.load(data[key]);
+ this.refreshClasses();
+ return;
+ }
+
+ for (const key of Object.keys(data)) {
+ if (key != 'loaded' && !this.isClassNameTaken(key)) {
+ clazz = (this.isClassNameTaken(key) ? this.getClass(key) : this.addClass(key));
+ clazz.load(data[key]);
+ }
+ }
+ this.refreshClasses();
+ };
+
+ loadClasses = (e: ProgressEvent) => {
+ const text: string = e.target?.result;
+ if (!text) return;
+
+ this.loadClassText(text);
+ };
+
+ persistClasses = (_list?: FabledClass[]) => {};
}
export const classStore = new ClassStoreSvelte();
diff --git a/src/data/editor-persistence-db.ts b/src/data/editor-persistence-db.ts
new file mode 100644
index 0000000000..79e854b2e7
--- /dev/null
+++ b/src/data/editor-persistence-db.ts
@@ -0,0 +1,206 @@
+import { browser } from '$app/environment';
+import { deleteDB, openDB, type IDBPDatabase } from 'idb';
+import type { PersistenceWriteResult } from './persistence-state';
+import { isStorageQuotaError } from './persistence-state';
+import {
+ ATTRIBUTES_STORE,
+ CLASSES_STORE,
+ CLASS_FOLDERS_KEY,
+ DB_NAME,
+ DB_VERSION,
+ type EditorPersistenceSchema,
+ type EntityStoreName,
+ type MetaRecord,
+ META_STORE,
+ MIGRATION_KEY,
+ normalizeForPersistence,
+ type PersistedAttributeRecord,
+ type PersistedClassRecord,
+ type PersistedSkillRecord,
+ type ReplaceEditorDataInput,
+ SKILLS_STORE,
+ SKILL_FOLDERS_KEY,
+ type StoreName
+} from './editor-persistence-shared';
+
+export interface LoadedEditorData {
+ skills: PersistedSkillRecord[];
+ classes: PersistedClassRecord[];
+ attributes: PersistedAttributeRecord[];
+ meta: MetaRecord[];
+}
+
+let databasePromise: Promise> | undefined;
+
+const createStorageResult = (error?: unknown): PersistenceWriteResult => ({
+ ok: !error,
+ quotaExceeded: !!error && isStorageQuotaError(error),
+ error
+});
+
+export const openEditorDatabase = (): Promise> => {
+ if (!browser || typeof indexedDB === 'undefined') {
+ return Promise.reject(new Error('IndexedDB is unavailable.'));
+ }
+
+ if (!databasePromise) {
+ databasePromise = openDB(DB_NAME, DB_VERSION, {
+ upgrade(db) {
+ if (!db.objectStoreNames.contains(SKILLS_STORE)) {
+ db.createObjectStore(SKILLS_STORE, { keyPath: 'name' });
+ }
+ if (!db.objectStoreNames.contains(CLASSES_STORE)) {
+ db.createObjectStore(CLASSES_STORE, { keyPath: 'name' });
+ }
+ if (!db.objectStoreNames.contains(ATTRIBUTES_STORE)) {
+ db.createObjectStore(ATTRIBUTES_STORE, { keyPath: 'name' });
+ }
+ if (!db.objectStoreNames.contains(META_STORE)) {
+ db.createObjectStore(META_STORE, { keyPath: 'key' });
+ }
+ }
+ });
+ }
+
+ return databasePromise;
+};
+
+const putAllIndexedDbRecords = async <
+ T extends MetaRecord | PersistedSkillRecord | PersistedClassRecord | PersistedAttributeRecord
+>(
+ db: IDBPDatabase,
+ storeName: StoreName,
+ records: T[]
+): Promise => {
+ const transaction = db.transaction(storeName, 'readwrite');
+ const store = transaction.store;
+ records.forEach((record) => store.put(normalizeForPersistence(record)));
+ await transaction.done;
+};
+
+const deleteIndexedDbKeys = async (
+ db: IDBPDatabase,
+ storeName: StoreName,
+ keys: string[]
+): Promise => {
+ if (keys.length === 0) return;
+
+ const transaction = db.transaction(storeName, 'readwrite');
+ const store = transaction.store;
+ keys.forEach((key) => store.delete(key));
+ await transaction.done;
+};
+
+const syncIndexedDbEntityStore = async <
+ T extends PersistedSkillRecord | PersistedClassRecord | PersistedAttributeRecord
+>(
+ db: IDBPDatabase,
+ storeName: EntityStoreName,
+ records: T[],
+ existingNames: string[]
+): Promise => {
+ const incomingNames = new Set(records.map((record) => record.name));
+ await deleteIndexedDbKeys(
+ db,
+ storeName,
+ existingNames.filter((name) => !incomingNames.has(name))
+ );
+ await putAllIndexedDbRecords(db, storeName, records);
+};
+
+export const loadEditorDbData = async (
+ db: IDBPDatabase
+): Promise => {
+ const [skills, classes, attributes, meta] = await Promise.all([
+ db.getAll(SKILLS_STORE),
+ db.getAll(CLASSES_STORE),
+ db.getAll(ATTRIBUTES_STORE),
+ db.getAll(META_STORE)
+ ]);
+
+ return {
+ skills,
+ classes,
+ attributes,
+ meta
+ };
+};
+
+export const replaceIndexedDbData = async (
+ db: IDBPDatabase,
+ data: ReplaceEditorDataInput,
+ existing: {
+ skills: string[];
+ classes: string[];
+ attributes: string[];
+ }
+): Promise => {
+ await syncIndexedDbEntityStore(db, SKILLS_STORE, data.skills, existing.skills);
+ await syncIndexedDbEntityStore(db, CLASSES_STORE, data.classes, existing.classes);
+ await syncIndexedDbEntityStore(db, ATTRIBUTES_STORE, data.attributes, existing.attributes);
+ await putAllIndexedDbRecords(db, META_STORE, [
+ { key: SKILL_FOLDERS_KEY, value: data.skillFolders },
+ { key: CLASS_FOLDERS_KEY, value: data.classFolders },
+ { key: MIGRATION_KEY, value: true }
+ ]);
+};
+
+export const writeIndexedDbRecord = async (
+ storeName: EntityStoreName,
+ record: PersistedSkillRecord | PersistedClassRecord | PersistedAttributeRecord,
+ previousName?: string
+): Promise => {
+ try {
+ const db = await openEditorDatabase();
+ const transaction = db.transaction(storeName, 'readwrite');
+ const store = transaction.store;
+ const cloneableRecord = normalizeForPersistence(record);
+ if (previousName && previousName !== record.name) {
+ store.delete(previousName);
+ }
+ store.put(cloneableRecord);
+ await transaction.done;
+ return createStorageResult();
+ } catch (error) {
+ return createStorageResult(error);
+ }
+};
+
+export const writeIndexedDbMeta = async (
+ key: string,
+ value: T
+): Promise => {
+ try {
+ const db = await openEditorDatabase();
+ const cloneableRecord = normalizeForPersistence({ key, value });
+ await db.put(META_STORE, cloneableRecord);
+ return createStorageResult();
+ } catch (error) {
+ return createStorageResult(error);
+ }
+};
+
+export const deleteIndexedDbRecord = async (
+ storeName: EntityStoreName,
+ name: string
+): Promise => {
+ const db = await openEditorDatabase();
+ await db.delete(storeName, name);
+};
+
+export const resetEditorDatabaseForTests = async () => {
+ if (!browser || typeof indexedDB === 'undefined') {
+ databasePromise = undefined;
+ return;
+ }
+
+ const db = await databasePromise?.catch(() => undefined);
+ db?.close();
+ databasePromise = undefined;
+
+ await deleteDB(DB_NAME, {
+ blocked() {
+ return;
+ }
+ });
+};
diff --git a/src/data/editor-persistence-legacy.ts b/src/data/editor-persistence-legacy.ts
new file mode 100644
index 0000000000..102c496313
--- /dev/null
+++ b/src/data/editor-persistence-legacy.ts
@@ -0,0 +1,208 @@
+import { browser } from '$app/environment';
+import type {
+ AttributeYamlData,
+ ClassYamlData,
+ MultiAttributeYamlData,
+ MultiClassYamlData,
+ MultiSkillYamlData,
+ SkillYamlData
+} from '$api/types';
+import { parseYaml } from '$api/yaml';
+import type { FolderProperties } from './folder-store.svelte';
+import type {
+ PersistedAttributeRecord,
+ PersistedClassRecord,
+ PersistedSkillRecord,
+ ReplaceEditorDataInput
+} from './editor-persistence-shared';
+import {
+ CLASS_FOLDERS_KEY,
+ SKILL_FOLDERS_KEY
+} from './editor-persistence-shared';
+
+const SKILL_PREFIX = 'sapi.skill.';
+const CLASS_PREFIX = 'sapi.class.';
+
+const defaultAttributeYaml = (name: string): AttributeYamlData => ({
+ display: name,
+ max: 999,
+ cost_base: 1,
+ cost_modifier: 0,
+ icon: 'Ink sac',
+ 'icon-data': 0,
+ 'icon-lore': [],
+ global: {
+ target: {},
+ condition: {},
+ mechanic: {}
+ },
+ stats: {}
+});
+
+const normalizeMultiYamlRecords = (
+ data: MultiSkillYamlData | MultiClassYamlData | undefined
+): Array<{ name: string; data: T }> => {
+ if (!data) return [];
+
+ return Object.entries(data)
+ .filter(([name]) => name !== 'loaded')
+ .map(([name, value]) => ({
+ name,
+ data: value as T
+ }));
+};
+
+const getLegacyNamedKeys = (prefix: string, metadataKey: string): string[] => {
+ if (!browser) return [];
+
+ const names = localStorage.getItem(metadataKey);
+ if (names) {
+ return names
+ .split(', ')
+ .map((name) => name.trim())
+ .filter((name) => name.length > 0);
+ }
+
+ const fromKeys: string[] = [];
+ for (let i = 0; i < localStorage.length; i++) {
+ const key = localStorage.key(i);
+ if (!key?.startsWith(prefix)) continue;
+ fromKeys.push(key.substring(prefix.length));
+ }
+ return fromKeys;
+};
+
+const readLegacySkillRecords = (): PersistedSkillRecord[] => {
+ if (!browser) return [];
+
+ const names = getLegacyNamedKeys(SKILL_PREFIX, 'skillNames');
+ if (names.length > 0) {
+ return names
+ .map((name) => {
+ const stored = localStorage.getItem(`${SKILL_PREFIX}${name}`);
+ if (!stored) return undefined;
+ const parsed = parseYaml(stored) as MultiSkillYamlData | undefined;
+ const record = normalizeMultiYamlRecords(parsed)[0];
+ if (!record) return undefined;
+ return { name: record.name, data: record.data };
+ })
+ .filter((record): record is PersistedSkillRecord => !!record);
+ }
+
+ const legacyData = localStorage.getItem('skillData');
+ if (!legacyData) return [];
+ return normalizeMultiYamlRecords(parseYaml(legacyData) as MultiSkillYamlData).map(
+ (record) => ({
+ name: record.name,
+ data: record.data
+ })
+ );
+};
+
+const readLegacyClassRecords = (): PersistedClassRecord[] => {
+ if (!browser) return [];
+
+ const names = getLegacyNamedKeys(CLASS_PREFIX, 'classNames');
+ if (names.length > 0) {
+ return names
+ .map((name) => {
+ const stored = localStorage.getItem(`${CLASS_PREFIX}${name}`);
+ if (!stored) return undefined;
+ const parsed = parseYaml(stored) as MultiClassYamlData | undefined;
+ const record = normalizeMultiYamlRecords(parsed)[0];
+ if (!record) return undefined;
+ return { name: record.name, data: record.data };
+ })
+ .filter((record): record is PersistedClassRecord => !!record);
+ }
+
+ const legacyData = localStorage.getItem('classData');
+ if (!legacyData) return [];
+ return normalizeMultiYamlRecords(parseYaml(legacyData) as MultiClassYamlData).map(
+ (record) => ({
+ name: record.name,
+ data: record.data
+ })
+ );
+};
+
+const readLegacyAttributeRecords = (): PersistedAttributeRecord[] => {
+ if (!browser) return [];
+
+ const stored = localStorage.getItem('attribs');
+ if (!stored) return [];
+
+ if (stored.split('\n').length < 3 && stored.charAt(0) !== '{') {
+ return stored
+ .replace('\n', '')
+ .split(',')
+ .map((name) => name.trim())
+ .filter((name) => name.length > 0)
+ .map((name) => ({
+ name,
+ data: defaultAttributeYaml(name)
+ }));
+ }
+
+ const parsed = parseYaml(stored) as MultiAttributeYamlData | undefined;
+ if (!parsed) return [];
+
+ return Object.entries(parsed).map(([name, data]) => ({
+ name,
+ data
+ }));
+};
+
+const parseFolderMeta = (key: string): FolderProperties[] => {
+ if (!browser) return [];
+
+ const stored = localStorage.getItem(key);
+ if (!stored || stored === 'null') return [];
+
+ try {
+ return JSON.parse(stored) as FolderProperties[];
+ } catch (error) {
+ console.error(`Failed to parse ${key} from localStorage`, error);
+ return [];
+ }
+};
+
+export const hasLegacyEditorData = () => {
+ if (!browser) return false;
+
+ return (
+ getLegacyNamedKeys(SKILL_PREFIX, 'skillNames').length > 0 ||
+ getLegacyNamedKeys(CLASS_PREFIX, 'classNames').length > 0 ||
+ !!localStorage.getItem('skillData') ||
+ !!localStorage.getItem('classData') ||
+ !!localStorage.getItem('attribs') ||
+ !!localStorage.getItem(SKILL_FOLDERS_KEY) ||
+ !!localStorage.getItem(CLASS_FOLDERS_KEY)
+ );
+};
+
+export const collectLegacyEditorData = (): ReplaceEditorDataInput => ({
+ skills: readLegacySkillRecords(),
+ classes: readLegacyClassRecords(),
+ attributes: readLegacyAttributeRecords(),
+ skillFolders: parseFolderMeta(SKILL_FOLDERS_KEY),
+ classFolders: parseFolderMeta(CLASS_FOLDERS_KEY)
+});
+
+export const clearLegacyEditorStorage = () => {
+ if (!browser) return;
+
+ const skillNames = getLegacyNamedKeys(SKILL_PREFIX, 'skillNames');
+ const classNames = getLegacyNamedKeys(CLASS_PREFIX, 'classNames');
+
+ skillNames.forEach((name) => localStorage.removeItem(`${SKILL_PREFIX}${name}`));
+ classNames.forEach((name) => localStorage.removeItem(`${CLASS_PREFIX}${name}`));
+
+ localStorage.removeItem('skillNames');
+ localStorage.removeItem('classNames');
+ localStorage.removeItem('skillData');
+ localStorage.removeItem('classData');
+ localStorage.removeItem('attribs');
+ localStorage.removeItem(SKILL_FOLDERS_KEY);
+ localStorage.removeItem(CLASS_FOLDERS_KEY);
+};
diff --git a/src/data/editor-persistence-shared.ts b/src/data/editor-persistence-shared.ts
new file mode 100644
index 0000000000..8cf5b9bc07
--- /dev/null
+++ b/src/data/editor-persistence-shared.ts
@@ -0,0 +1,74 @@
+import type {
+ AttributeYamlData,
+ ClassYamlData,
+ SkillYamlData
+} from '$api/types';
+import type { DBSchema } from 'idb';
+import type { FolderProperties } from './folder-store.svelte';
+
+export const DB_NAME = 'fabled-editor';
+export const DB_VERSION = 1;
+
+export const SKILLS_STORE = 'skills';
+export const CLASSES_STORE = 'classes';
+export const ATTRIBUTES_STORE = 'attributes';
+export const META_STORE = 'meta';
+
+export const MIGRATION_KEY = 'editor-storage-migrated';
+export const SKILL_FOLDERS_KEY = 'skillFolders';
+export const CLASS_FOLDERS_KEY = 'classFolders';
+
+export type PersistenceMode = 'indexeddb' | 'unsupported';
+
+export interface PersistedSkillRecord {
+ name: string;
+ data: SkillYamlData;
+}
+
+export interface PersistedClassRecord {
+ name: string;
+ data: ClassYamlData;
+}
+
+export interface PersistedAttributeRecord {
+ name: string;
+ data: AttributeYamlData;
+}
+
+export interface MetaRecord {
+ key: string;
+ value: T;
+}
+
+export interface ReplaceEditorDataInput {
+ skills: PersistedSkillRecord[];
+ classes: PersistedClassRecord[];
+ attributes: PersistedAttributeRecord[];
+ skillFolders: FolderProperties[];
+ classFolders: FolderProperties[];
+}
+
+export interface EditorPersistenceSchema extends DBSchema {
+ [SKILLS_STORE]: {
+ key: string;
+ value: PersistedSkillRecord;
+ };
+ [CLASSES_STORE]: {
+ key: string;
+ value: PersistedClassRecord;
+ };
+ [ATTRIBUTES_STORE]: {
+ key: string;
+ value: PersistedAttributeRecord;
+ };
+ [META_STORE]: {
+ key: string;
+ value: MetaRecord;
+ };
+}
+
+export type EntityStoreName = typeof SKILLS_STORE | typeof CLASSES_STORE | typeof ATTRIBUTES_STORE;
+export type StoreName = EntityStoreName | typeof META_STORE;
+
+export const normalizeForPersistence = (value: T): T =>
+ JSON.parse(JSON.stringify(value)) as T;
diff --git a/src/data/editor-persistence-unsupported.test.ts b/src/data/editor-persistence-unsupported.test.ts
new file mode 100644
index 0000000000..9a5b4a3dbd
--- /dev/null
+++ b/src/data/editor-persistence-unsupported.test.ts
@@ -0,0 +1,60 @@
+import 'fake-indexeddb/auto';
+
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import type { SkillYamlData } from '$api/types';
+
+vi.mock('$app/environment', () => ({
+ browser: true
+}));
+
+const skillData: SkillYamlData = {
+ name: 'Meteor',
+ type: 'Dynamic',
+ 'max-level': 5,
+ 'skill-req': '',
+ 'skill-req-lvl': 0,
+ 'needs-permission': false,
+ 'cooldown-message': true,
+ msg: 'cast',
+ combo: '',
+ icon: 'stone',
+ 'icon-data': 0,
+ 'icon-lore': [],
+ attributes: {
+ 'level-base': 1,
+ 'level-scale': 0,
+ 'cost-base': 1,
+ 'cost-scale': 0,
+ 'cooldown-base': 1,
+ 'cooldown-scale': 0,
+ 'mana-base': 0,
+ 'mana-scale': 0,
+ 'points-spent-req-base': 0,
+ 'points-spent-req-scale': 0
+ },
+ incompatible: [],
+ components: {}
+};
+
+describe('editor persistence unsupported browser handling', () => {
+ beforeEach(() => {
+ vi.resetModules();
+ vi.stubGlobal('indexedDB', undefined);
+ localStorage.clear();
+ });
+
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ });
+
+ it('does not fall back to localStorage when IndexedDB is unavailable', async () => {
+ const persistence = await import('./editor-persistence');
+ const result = await persistence.savePersistedSkill('Meteor', skillData);
+
+ expect(result.ok).toBe(false);
+ expect(persistence.getEditorPersistenceMode()).toBe('unsupported');
+ expect(localStorage.getItem('skillNames')).toBeNull();
+ expect(localStorage.getItem('sapi.skill.Meteor')).toBeNull();
+ expect(persistence.listPersistedSkillNames()).toEqual([]);
+ });
+});
diff --git a/src/data/editor-persistence.test.ts b/src/data/editor-persistence.test.ts
new file mode 100644
index 0000000000..06683eebf7
--- /dev/null
+++ b/src/data/editor-persistence.test.ts
@@ -0,0 +1,223 @@
+import 'fake-indexeddb/auto';
+
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import YAML from 'yaml';
+import type { AttributeYamlData, ClassYamlData, SkillYamlData } from '$api/types';
+import { ATTRIBUTES_STORE, SKILLS_STORE } from './editor-persistence-shared';
+
+vi.mock('$app/environment', () => ({
+ browser: true
+}));
+
+const skillData: SkillYamlData = {
+ name: 'Meteor',
+ type: 'Dynamic',
+ 'max-level': 5,
+ 'skill-req': '',
+ 'skill-req-lvl': 0,
+ 'needs-permission': false,
+ 'cooldown-message': true,
+ msg: 'cast',
+ combo: '',
+ icon: 'stone',
+ 'icon-data': 0,
+ 'icon-lore': [],
+ attributes: {
+ 'level-base': 1,
+ 'level-scale': 0,
+ 'cost-base': 1,
+ 'cost-scale': 0,
+ 'cooldown-base': 1,
+ 'cooldown-scale': 0,
+ 'mana-base': 0,
+ 'mana-scale': 0,
+ 'points-spent-req-base': 0,
+ 'points-spent-req-scale': 0
+ },
+ incompatible: [],
+ components: {}
+};
+
+const classData: ClassYamlData = {
+ name: 'Mage',
+ 'action-bar': '',
+ prefix: '&6Mage',
+ group: 'class',
+ mana: '&2Mana',
+ 'max-level': 40,
+ parent: '',
+ 'needs-permission': false,
+ attributes: {
+ 'health-base': 20,
+ 'health-scale': 1,
+ 'mana-base': 20,
+ 'mana-scale': 1
+ },
+ 'mana-regen': 1,
+ 'skill-tree': 'REQUIREMENT',
+ blacklist: [],
+ skills: ['Meteor'],
+ icon: 'stone',
+ 'icon-data': 0,
+ 'icon-lore': [],
+ 'exp-source': 273,
+ 'combo-starters': {}
+};
+
+const attributeData: AttributeYamlData = {
+ display: 'Spirit',
+ max: 999,
+ cost_base: 1,
+ cost_modifier: 0,
+ icon: 'Ink sac',
+ 'icon-data': 0,
+ 'icon-lore': [],
+ global: {
+ target: {},
+ condition: {},
+ mechanic: {}
+ },
+ stats: {}
+};
+
+describe('editor persistence', () => {
+ beforeEach(async () => {
+ localStorage.clear();
+ const persistence = await import('./editor-persistence');
+ await persistence.resetEditorPersistenceForTests();
+ });
+
+ it('migrates legacy localStorage editor data into IndexedDB-backed cache', async () => {
+ localStorage.setItem('skillNames', 'Meteor');
+ localStorage.setItem(
+ 'sapi.skill.Meteor',
+ YAML.stringify({ Meteor: skillData }, { lineWidth: 0, aliasDuplicateObjects: false })
+ );
+ localStorage.setItem('classNames', 'Mage');
+ localStorage.setItem(
+ 'sapi.class.Mage',
+ YAML.stringify({ Mage: classData }, { lineWidth: 0, aliasDuplicateObjects: false })
+ );
+ localStorage.setItem(
+ 'attribs',
+ YAML.stringify({ Spirit: attributeData }, { lineWidth: 0, aliasDuplicateObjects: false })
+ );
+ localStorage.setItem(
+ 'skillFolders',
+ JSON.stringify([
+ {
+ location: 'local',
+ dataType: 'folder',
+ name: 'Magic',
+ data: ['Meteor'],
+ open: false
+ }
+ ])
+ );
+
+ const persistence = await import('./editor-persistence');
+ await persistence.ensureEditorPersistence();
+
+ expect(persistence.getEditorPersistenceMode()).toBe('indexeddb');
+ expect(persistence.listPersistedSkillNames()).toEqual(['Meteor']);
+ expect(persistence.listPersistedClassNames()).toEqual(['Mage']);
+ expect(persistence.listPersistedAttributeRecords()).toEqual([
+ { name: 'Spirit', data: attributeData }
+ ]);
+ expect(persistence.getPersistedFolders('skill')).toEqual([
+ {
+ location: 'local',
+ dataType: 'folder',
+ name: 'Magic',
+ data: ['Meteor'],
+ open: false
+ }
+ ]);
+ expect(localStorage.getItem('skillNames')).toBeNull();
+ expect(localStorage.getItem('sapi.skill.Meteor')).toBeNull();
+ });
+
+ it('normalizes proxy-backed class data into structured-clone-safe values', async () => {
+ const persistence = await import('./editor-persistence');
+ const proxiedClassData: ClassYamlData = {
+ ...classData,
+ blacklist: new Proxy(['stick'], {}),
+ 'icon-lore': new Proxy(['Line 1'], {}),
+ 'combo-starters': {
+ L: {
+ inverted: true,
+ whitelist: new Proxy(['wand'], {})
+ }
+ }
+ };
+
+ expect(() => structuredClone({ name: 'Mage', data: proxiedClassData })).toThrow();
+
+ const normalized = persistence.normalizeForPersistence({
+ name: 'Mage',
+ data: proxiedClassData
+ });
+
+ expect(() => structuredClone(normalized)).not.toThrow();
+ expect(normalized).toEqual({
+ name: 'Mage',
+ data: {
+ ...classData,
+ blacklist: ['stick'],
+ 'icon-lore': ['Line 1'],
+ 'combo-starters': {
+ L: {
+ inverted: true,
+ whitelist: ['wand']
+ }
+ }
+ }
+ });
+ expect(Array.isArray(normalized.data.blacklist)).toBe(true);
+ expect(Array.isArray(normalized.data['icon-lore'])).toBe(true);
+ expect(normalized.data['combo-starters']).toEqual({
+ L: {
+ inverted: true,
+ whitelist: ['wand']
+ }
+ });
+ expect(normalized.data).toEqual({
+ ...classData,
+ blacklist: ['stick'],
+ 'icon-lore': ['Line 1'],
+ 'combo-starters': {
+ L: {
+ inverted: true,
+ whitelist: ['wand']
+ }
+ }
+ });
+ });
+
+ it('does not overwrite newer persisted skills when saving attributes', async () => {
+ const persistence = await import('./editor-persistence');
+ const updatedSkillData: SkillYamlData = {
+ ...skillData,
+ msg: 'updated'
+ };
+ const { openEditorDatabase, writeIndexedDbRecord } = await import('./editor-persistence-db');
+
+ await persistence.savePersistedSkill('Meteor', skillData);
+ await writeIndexedDbRecord(SKILLS_STORE, {
+ name: 'Meteor',
+ data: updatedSkillData
+ });
+
+ await persistence.savePersistedAttributes([{ name: 'Spirit', data: attributeData }]);
+
+ const db = await openEditorDatabase();
+ expect(await db.get(SKILLS_STORE, 'Meteor')).toEqual({
+ name: 'Meteor',
+ data: updatedSkillData
+ });
+ expect(await db.get(ATTRIBUTES_STORE, 'Spirit')).toEqual({
+ name: 'Spirit',
+ data: attributeData
+ });
+ });
+});
diff --git a/src/data/editor-persistence.ts b/src/data/editor-persistence.ts
new file mode 100644
index 0000000000..0fa68bdb77
--- /dev/null
+++ b/src/data/editor-persistence.ts
@@ -0,0 +1,369 @@
+import { browser } from '$app/environment';
+import { writable } from 'svelte/store';
+import {
+ clearLegacyEditorStorage,
+ collectLegacyEditorData,
+ hasLegacyEditorData
+} from './editor-persistence-legacy';
+import {
+ deleteIndexedDbRecord,
+ loadEditorDbData,
+ openEditorDatabase,
+ replaceIndexedDbData,
+ resetEditorDatabaseForTests,
+ writeIndexedDbMeta,
+ writeIndexedDbRecord
+} from './editor-persistence-db';
+import {
+ ATTRIBUTES_STORE,
+ CLASS_FOLDERS_KEY,
+ CLASSES_STORE,
+ MIGRATION_KEY,
+ normalizeForPersistence,
+ type PersistedAttributeRecord,
+ type PersistenceMode,
+ type ReplaceEditorDataInput,
+ SKILL_FOLDERS_KEY,
+ SKILLS_STORE
+} from './editor-persistence-shared';
+import type { AttributeYamlData, ClassYamlData, SkillYamlData } from '$api/types';
+import type { FolderProperties } from './folder-store.svelte';
+import type { PersistenceWriteResult } from './persistence-state';
+import { isStorageQuotaError } from './persistence-state';
+
+const cache = {
+ skills: new Map(),
+ classes: new Map(),
+ attributes: new Map(),
+ meta: new Map()
+};
+
+export const editorPersistenceUnsupported = writable(null);
+
+let persistenceMode: PersistenceMode = 'indexeddb';
+let initializationPromise: Promise | undefined;
+
+const resetCache = () => {
+ cache.skills.clear();
+ cache.classes.clear();
+ cache.attributes.clear();
+ cache.meta.clear();
+};
+
+const unsupportedPersistenceError = (cause?: unknown) =>
+ new Error(
+ cause instanceof Error && cause.message
+ ? `IndexedDB is unavailable in this browser: ${cause.message}`
+ : 'IndexedDB is unavailable in this browser.'
+ );
+
+const loadCache = async () => {
+ const db = await openEditorDatabase();
+ const data = await loadEditorDbData(db);
+
+ resetCache();
+ data.skills.forEach((record) => cache.skills.set(record.name, record.data));
+ data.classes.forEach((record) => cache.classes.set(record.name, record.data));
+ data.attributes.forEach((record) => cache.attributes.set(record.name, record.data));
+ data.meta.forEach((record) => cache.meta.set(record.key, record.value));
+};
+
+const replacePersistedAttributeCache = (records: PersistedAttributeRecord[]) => {
+ cache.attributes.clear();
+ records.forEach((record) => cache.attributes.set(record.name, record.data));
+};
+
+const migrateLegacyLocalStorage = async (): Promise => {
+ if (!browser || persistenceMode !== 'indexeddb') return;
+ if (cache.meta.get(MIGRATION_KEY)) return;
+
+ if (!hasLegacyEditorData()) {
+ await writeIndexedDbMeta(MIGRATION_KEY, true);
+ cache.meta.set(MIGRATION_KEY, true);
+ return;
+ }
+
+ const data = collectLegacyEditorData();
+ const db = await openEditorDatabase();
+ await replaceIndexedDbData(db, data, {
+ skills: [...cache.skills.keys()],
+ classes: [...cache.classes.keys()],
+ attributes: [...cache.attributes.keys()]
+ });
+ clearLegacyEditorStorage();
+};
+
+export const ensureEditorPersistence = async (): Promise => {
+ if (!browser) return 'unsupported';
+ if (initializationPromise) {
+ await initializationPromise;
+ return persistenceMode;
+ }
+
+ initializationPromise = (async () => {
+ editorPersistenceUnsupported.set(null);
+
+ if (typeof indexedDB === 'undefined') {
+ persistenceMode = 'unsupported';
+ resetCache();
+ editorPersistenceUnsupported.set('This browser does not support IndexedDB persistence.');
+ return;
+ }
+
+ try {
+ await loadCache();
+ await migrateLegacyLocalStorage();
+ await loadCache();
+ } catch (error) {
+ console.error('IndexedDB unavailable for editor persistence.', error);
+ persistenceMode = 'unsupported';
+ resetCache();
+ editorPersistenceUnsupported.set(
+ error instanceof Error && error.message
+ ? `IndexedDB persistence is unavailable: ${error.message}`
+ : 'IndexedDB persistence is unavailable in this browser.'
+ );
+ }
+ })();
+
+ await initializationPromise;
+ return persistenceMode;
+};
+
+export const getEditorPersistenceMode = (): PersistenceMode => persistenceMode;
+
+export const listPersistedSkillNames = (): string[] =>
+ [...cache.skills.keys()].sort((left, right) => left.localeCompare(right));
+
+export const listPersistedClassNames = (): string[] =>
+ [...cache.classes.keys()].sort((left, right) => left.localeCompare(right));
+
+export const listPersistedAttributeRecords = (): PersistedAttributeRecord[] =>
+ [...cache.attributes.entries()]
+ .sort(([left], [right]) => left.localeCompare(right))
+ .map(([name, data]) => ({ name, data }));
+
+export const getPersistedSkill = async (name: string): Promise => {
+ await ensureEditorPersistence();
+ return cache.skills.get(name);
+};
+
+export const getPersistedClass = async (name: string): Promise => {
+ await ensureEditorPersistence();
+ return cache.classes.get(name);
+};
+
+export const getPersistedAttribute = async (
+ name: string
+): Promise => {
+ await ensureEditorPersistence();
+ return cache.attributes.get(name);
+};
+
+export const getPersistedFolders = (type: 'skill' | 'class'): FolderProperties[] =>
+ (
+ (cache.meta.get(
+ type === 'skill' ? SKILL_FOLDERS_KEY : CLASS_FOLDERS_KEY
+ ) as FolderProperties[]) || []
+ ).map((folder) => structuredClone(folder));
+
+const unsupportedResult = (): PersistenceWriteResult => ({
+ ok: false,
+ quotaExceeded: false,
+ error: unsupportedPersistenceError()
+});
+
+export const savePersistedSkill = async (
+ name: string,
+ data: SkillYamlData,
+ previousName?: string
+): Promise => {
+ await ensureEditorPersistence();
+ if (persistenceMode !== 'indexeddb') {
+ return unsupportedResult();
+ }
+
+ const result = await writeIndexedDbRecord(SKILLS_STORE, { name, data }, previousName);
+ if (result.ok) {
+ if (previousName && previousName !== name) cache.skills.delete(previousName);
+ cache.skills.set(name, normalizeForPersistence(data));
+ }
+ return result;
+};
+
+export const savePersistedClass = async (
+ name: string,
+ data: ClassYamlData,
+ previousName?: string
+): Promise => {
+ await ensureEditorPersistence();
+ if (persistenceMode !== 'indexeddb') {
+ return unsupportedResult();
+ }
+
+ const result = await writeIndexedDbRecord(CLASSES_STORE, { name, data }, previousName);
+ if (result.ok) {
+ if (previousName && previousName !== name) cache.classes.delete(previousName);
+ cache.classes.set(name, normalizeForPersistence(data));
+ }
+ return result;
+};
+
+export const savePersistedAttributes = async (
+ records: PersistedAttributeRecord[]
+): Promise => {
+ await ensureEditorPersistence();
+ if (persistenceMode !== 'indexeddb') {
+ return unsupportedResult();
+ }
+
+ try {
+ const db = await openEditorDatabase();
+ const normalizedRecords = normalizeForPersistence(records);
+ const transaction = db.transaction(ATTRIBUTES_STORE, 'readwrite');
+ const store = transaction.store;
+ const incomingNames = new Set(normalizedRecords.map((record) => record.name));
+
+ [...cache.attributes.keys()]
+ .filter((name) => !incomingNames.has(name))
+ .forEach((name) => store.delete(name));
+ normalizedRecords.forEach((record) => store.put(record));
+
+ await transaction.done;
+ replacePersistedAttributeCache(normalizedRecords);
+ return { ok: true, quotaExceeded: false };
+ } catch (error) {
+ return { ok: false, quotaExceeded: isStorageQuotaError(error), error };
+ }
+};
+
+export const savePersistedFolders = async (
+ type: 'skill' | 'class',
+ folders: FolderProperties[]
+): Promise => {
+ await ensureEditorPersistence();
+ if (persistenceMode !== 'indexeddb') {
+ return unsupportedResult();
+ }
+
+ const key = type === 'skill' ? SKILL_FOLDERS_KEY : CLASS_FOLDERS_KEY;
+ const result = await writeIndexedDbMeta(key, folders);
+ if (result.ok) {
+ cache.meta.set(key, normalizeForPersistence(folders));
+ }
+ return result;
+};
+
+export const deletePersistedSkill = async (name: string): Promise => {
+ await ensureEditorPersistence();
+ cache.skills.delete(name);
+ if (persistenceMode !== 'indexeddb') return;
+ await deleteIndexedDbRecord(SKILLS_STORE, name);
+};
+
+export const deletePersistedClass = async (name: string): Promise => {
+ await ensureEditorPersistence();
+ cache.classes.delete(name);
+ if (persistenceMode !== 'indexeddb') return;
+ await deleteIndexedDbRecord(CLASSES_STORE, name);
+};
+
+export const deletePersistedAttribute = async (name: string): Promise => {
+ await ensureEditorPersistence();
+ const previousAttribute = cache.attributes.get(name);
+ cache.attributes.delete(name);
+ if (persistenceMode !== 'indexeddb') return;
+
+ try {
+ const result = await savePersistedAttributes(listPersistedAttributeRecords());
+ if (!result.ok) {
+ if (previousAttribute !== undefined) {
+ cache.attributes.set(name, previousAttribute);
+ }
+ throw new Error(`Failed to persist deletion of attribute "${name}"`);
+ }
+ } catch (error) {
+ if (previousAttribute !== undefined && !cache.attributes.has(name)) {
+ cache.attributes.set(name, previousAttribute);
+ }
+ throw error;
+ }
+};
+
+export const replacePersistedEditorData = async (data: ReplaceEditorDataInput): Promise => {
+ await ensureEditorPersistence();
+ if (persistenceMode !== 'indexeddb') {
+ throw unsupportedPersistenceError();
+ }
+
+ const db = await openEditorDatabase();
+ await replaceIndexedDbData(db, data, {
+ skills: [...cache.skills.keys()],
+ classes: [...cache.classes.keys()],
+ attributes: [...cache.attributes.keys()]
+ });
+ await loadCache();
+};
+
+export const importLegacyMigrationData = async (input: {
+ skillData: string;
+ classData: string;
+ attributes: string;
+ skillFolders: string;
+ classFolders: string;
+}): Promise => {
+ const { parseYaml } = await import('$api/yaml');
+ const skills = Object.entries((parseYaml(input.skillData) as Record) || {})
+ .filter(([name]) => name !== 'loaded')
+ .map(([name, data]) => ({
+ name,
+ data
+ }));
+ const classes = Object.entries(
+ (parseYaml(input.classData) as Record) || {}
+ )
+ .filter(([name]) => name !== 'loaded')
+ .map(([name, data]) => ({
+ name,
+ data
+ }));
+ const attributes = Object.entries(
+ (parseYaml(input.attributes) as Record) || {}
+ ).map(([name, data]) => ({
+ name,
+ data
+ }));
+
+ let skillFolders: FolderProperties[] = [];
+ let classFolders: FolderProperties[] = [];
+ try {
+ skillFolders = input.skillFolders ? (JSON.parse(input.skillFolders) as FolderProperties[]) : [];
+ } catch (_) {
+ skillFolders = [];
+ }
+
+ try {
+ classFolders = input.classFolders ? (JSON.parse(input.classFolders) as FolderProperties[]) : [];
+ } catch (_) {
+ classFolders = [];
+ }
+
+ await replacePersistedEditorData({
+ skills,
+ classes,
+ attributes,
+ skillFolders,
+ classFolders
+ });
+};
+
+export { normalizeForPersistence };
+
+export const resetEditorPersistenceForTests = async () => {
+ resetCache();
+ initializationPromise = undefined;
+ persistenceMode = 'indexeddb';
+ editorPersistenceUnsupported.set(null);
+
+ await resetEditorDatabaseForTests();
+};
diff --git a/src/data/editor-session.ts b/src/data/editor-session.ts
new file mode 100644
index 0000000000..dbd3617696
--- /dev/null
+++ b/src/data/editor-session.ts
@@ -0,0 +1,26 @@
+import { browser } from '$app/environment';
+import { attributeStore } from './attribute-store';
+import { classStore } from './class-store.svelte';
+import { ensureEditorPersistence } from './editor-persistence';
+import { skillStore } from './skill-store.svelte';
+
+let hydrationPromise: Promise | undefined;
+
+export const hydrateEditorData = async (): Promise => {
+ if (!browser) return;
+
+ if (!hydrationPromise) {
+ hydrationPromise = (async () => {
+ await ensureEditorPersistence();
+ await skillStore.hydratePersistedData();
+ await classStore.hydratePersistedData();
+ await attributeStore.hydratePersistedData();
+ })();
+ }
+
+ await hydrationPromise;
+};
+
+export const resetEditorHydrationForTests = () => {
+ hydrationPromise = undefined;
+};
diff --git a/src/data/persistence-state.test.ts b/src/data/persistence-state.test.ts
new file mode 100644
index 0000000000..90953e34d1
--- /dev/null
+++ b/src/data/persistence-state.test.ts
@@ -0,0 +1,34 @@
+import { describe, expect, it } from 'vitest';
+import { getPersistenceFailureMessage, isStorageQuotaError } from './persistence-state';
+
+describe('storage helpers', () => {
+ it('recognizes quota exceeded errors by name and message', () => {
+ expect(isStorageQuotaError(new DOMException('Quota exceeded', 'QuotaExceededError'))).toBe(
+ true
+ );
+ expect(isStorageQuotaError({ message: 'The quota has been exceeded.' })).toBe(true);
+ expect(isStorageQuotaError(new Error('disk full'))).toBe(false);
+ });
+});
+
+describe('persistence failure messaging', () => {
+ it('describes quota failures in user-facing terms', () => {
+ expect(
+ getPersistenceFailureMessage({
+ ok: false,
+ quotaExceeded: true
+ })
+ ).toBe('Browser storage is full. Export before refreshing or closing this page.');
+ });
+
+ it('describes non-quota failures generically', () => {
+ expect(
+ getPersistenceFailureMessage({
+ ok: false,
+ quotaExceeded: false
+ })
+ ).toBe(
+ "The editor couldn't persist this change to browser storage. Your latest edits remain only in memory until you refresh or close this page."
+ );
+ });
+});
diff --git a/src/data/persistence-state.ts b/src/data/persistence-state.ts
new file mode 100644
index 0000000000..df2f1764d4
--- /dev/null
+++ b/src/data/persistence-state.ts
@@ -0,0 +1,37 @@
+// Shared helpers for browser-persistence failures. IndexedDB owns editor storage, but
+// the UI still needs a consistent way to classify failures and explain them to users.
+export interface PersistenceSaveError {
+ name: string;
+ message: string;
+}
+
+export interface PersistenceWriteResult {
+ ok: boolean;
+ quotaExceeded: boolean;
+ error?: unknown;
+}
+
+const storageQuotaNames = new Set(['QuotaExceededError', 'NS_ERROR_DOM_QUOTA_REACHED']);
+const storageQuotaCodes = new Set([22, 1014]);
+
+export const isStorageQuotaError = (error: unknown): boolean => {
+ if (!error || typeof error !== 'object') return false;
+
+ const maybeDomException = error as { name?: string; code?: number; message?: string };
+ if (maybeDomException.name && storageQuotaNames.has(maybeDomException.name)) {
+ return true;
+ }
+
+ if (typeof maybeDomException.code === 'number' && storageQuotaCodes.has(maybeDomException.code)) {
+ return true;
+ }
+
+ if (typeof maybeDomException.message !== 'string') return false;
+
+ return maybeDomException.message.toLowerCase().includes('quota');
+};
+
+export const getPersistenceFailureMessage = (result: PersistenceWriteResult): string =>
+ result.quotaExceeded
+ ? 'Browser storage is full. Export before refreshing or closing this page.'
+ : "The editor couldn't persist this change to browser storage. Your latest edits remain only in memory until you refresh or close this page.";
diff --git a/src/data/skill-store.svelte.ts b/src/data/skill-store.svelte.ts
index 0d2988a3e7..cefb4d1e5c 100644
--- a/src/data/skill-store.svelte.ts
+++ b/src/data/skill-store.svelte.ts
@@ -1,710 +1,704 @@
import type { Unsubscriber, Writable } from 'svelte/store';
-import { get, writable } from 'svelte/store';
-import { sort, toEditorCase } from '$api/api';
-import { parseYaml } from '$api/yaml';
-import { browser } from '$app/environment';
-import { active, saveError } from './store';
-import { goto } from '$app/navigation';
-import { base } from '$app/paths';
-import Registry, { initialized } from '$api/components/registry';
+import { get, writable } from 'svelte/store';
+import { sort, toEditorCase } from '$api/api';
+import { parseYaml } from '$api/yaml';
+import { active, saveError } from './store';
+import { goto } from '$app/navigation';
+import { base } from '$app/paths';
+import Registry, { initialized } from '$api/components/registry';
import type {
- FabledSkillData,
- IAttribute,
- Icon,
- MultiSkillYamlData,
- Serializable,
- SkillYamlData,
- YamlComponentData
-} from '$api/types';
-import { socketService } from '$api/socket/socket-connector';
-import { notify } from '$api/notification-service';
-import FabledTrigger from '$api/components/triggers.svelte';
-import type FabledComponent from '$api/components/fabled-component.svelte';
-import { FabledFolder, folderStore } from './folder-store.svelte';
-import YAML from 'yaml';
+ FabledSkillData,
+ IAttribute,
+ Icon,
+ MultiSkillYamlData,
+ Serializable,
+ SkillYamlData,
+ YamlComponentData
+} from '$api/types';
+import { socketService } from '$api/socket/socket-connector';
+import { notify } from '$api/notification-service';
+import FabledTrigger from '$api/components/triggers.svelte';
+import type FabledComponent from '$api/components/fabled-component.svelte';
+import { FabledFolder, folderStore, type FolderProperties } from './folder-store.svelte';
+import { getPersistenceFailureMessage } from './persistence-state';
+import {
+ deletePersistedSkill,
+ getPersistedFolders,
+ getPersistedSkill,
+ listPersistedSkillNames,
+ savePersistedFolders,
+ savePersistedSkill
+} from './editor-persistence';
export default class FabledSkill implements Serializable {
- dataType = 'skill';
- location: 'local' | 'server' = 'local';
- loaded = false;
- tooBig = false;
- acknowledged = false;
-
- isSkill = true;
- public key = {};
- name: string = $state('');
- previousName: string = '';
- type = $state('Dynamic');
- maxLevel = $state(5);
- skillReq?: FabledSkill = $state();
- skillReqLevel = $state(0);
- attributeRequirements: IAttribute[] = $state([]);
- permission: boolean = $state(false);
- levelReq: IAttribute = $state({ name: 'level', base: 1, scale: 0 });
- cost: IAttribute = $state({ name: 'cost', base: 1, scale: 0 });
- cooldown: IAttribute = $state({ name: 'cooldown', base: 1, scale: 0 });
- cooldownMessage: boolean = $state(true);
- mana: IAttribute = $state({ name: 'mana', base: 0, scale: 0 });
- minSpent: IAttribute = $state({ name: 'points-spent-req', base: 0, scale: 0 });
- castMessage = $state('&6{player} &2has cast &6{skill}');
- combo = $state('');
- icon: Icon = $state({
- material: 'Pumpkin',
- customModelData: 0,
- lore: [
- '&d{name} &7({level}/{max})',
- '&2Type: &6{type}',
- '',
- '{req:level}Level: {attr:level}',
- '{req:cost}Cost: {attr:cost}',
- '',
- '&2Mana: {attr:mana}',
- '&2Cooldown: {attr:cooldown}'
- ]
- });
- incompatible: FabledSkill[] = $state([]);
- triggers: FabledTrigger[] = $state([]);
-
- private skillReqStr = '';
- private incompStr: string[] = [];
-
- constructor(data?: FabledSkillData) {
- this.name = data?.name || 'Skill';
- if (!data) return;
- if (data.location) this.location = data.location;
- if (data.type) this.type = data.type;
- if (data.maxLevel) this.maxLevel = data.maxLevel;
- if (data.skillReq) this.skillReq = data.skillReq;
- if (data.skillReqLevel) this.skillReqLevel = data.skillReqLevel;
- if (data.attributeRequirements) this.attributeRequirements = data.attributeRequirements.map(a => ({
- name: a.name,
- base: a.base,
- scale: a.scale
- }));
- if (data.permission !== undefined) this.permission = data.permission;
- if (data.levelReq) this.levelReq = data.levelReq;
- if (data.cost) this.cost = data.cost;
- if (data.cooldown) this.cooldown = data.cooldown;
- if (data.cooldownMessage !== undefined) this.cooldownMessage = data.cooldownMessage;
- if (data.mana) this.mana = data.mana;
- if (data.minSpent) this.minSpent = data.minSpent;
- if (data.castMessage) this.castMessage = data.castMessage;
- if (data.combo) this.combo = data.combo;
- if (data.icon) this.icon = data.icon;
- if (data.incompatible) this.incompatible = data.incompatible;
- if (data.triggers) this.triggers = data.triggers;
- }
-
- /**
- * Reads all the reactive state elements to act as a chane detector
- */
- public changed = () => {
- return {
- name: this.name,
- type: this.type,
- 'max-level': this.maxLevel,
- 'skill-req': this.skillReq?.name,
- 'skill-req-lvl': this.skillReqLevel,
- 'needs-permission': this.permission,
- 'cooldown-message': this.cooldownMessage,
- msg: this.castMessage,
- combo: this.combo,
- icon: this.icon.material,
- 'icon-data': this.icon.customModelData,
- 'icon-lore': this.icon.lore,
- attributes: {
- 'level-base': this.levelReq.base,
- 'level-scale': this.levelReq.scale,
- 'cost-base': this.cost.base,
- 'cost-scale': this.cost.scale,
- 'cooldown-base': this.cooldown.base,
- 'cooldown-scale': this.cooldown.scale,
- 'mana-base': this.mana.base,
- 'mana-scale': this.mana.scale,
- 'points-spent-req-base': this.minSpent.base,
- 'points-spent-req-scale': this.minSpent.scale
- },
- incompatible: this.incompatible,
- components: this.triggers
- };
- };
-
- public addComponent = (comp: FabledComponent) => {
- if (comp instanceof FabledTrigger) {
- this.triggers = [...this.triggers, comp];
- return;
- }
-
- if (this.triggers.length === 0) {
- this.triggers.push(Registry.getTriggerByName('cast')?.new());
- }
-
- this.triggers[0].addComponent(comp);
- this.triggers = [...this.triggers];
- };
-
- public removeComponent = (comp: FabledComponent) => {
- if (comp instanceof FabledTrigger && this.triggers.includes(comp)) {
- this.triggers.splice(this.triggers.indexOf(comp), 1);
- return;
- }
-
- for (const trigger of this.triggers) {
- if (trigger.contains(comp))
- trigger.removeComponent(comp);
- }
-
- this.triggers = [...this.triggers];
- };
-
- private nextChar = (c: string) => {
- if (/z$/.test(c)) {
- return c.replaceAll(/z$/g, 'a') + 'a';
- }
- return c.substring(0, c.length - 1) + String.fromCharCode(c.charCodeAt(c.length - 1) + 1);
- };
-
- public serializeYaml = (): SkillYamlData => {
- const compData = {};
-
- for (const comp of this.triggers) {
- const yamlData = comp.toYamlObj();
- let name = comp.name;
- let suffix = 'a';
- while (compData[name]) {
- suffix = this.nextChar(suffix);
- name = comp.name + '-' + suffix;
- }
- compData[name] = yamlData;
- }
- const data = {
- name: this.name,
- type: this.type,
- 'max-level': this.maxLevel,
- 'skill-req': this.skillReq?.name,
- 'skill-req-lvl': this.skillReqLevel,
- 'needs-permission': this.permission,
- 'cooldown-message': this.cooldownMessage,
- msg: this.castMessage,
- combo: this.combo,
- icon: this.icon.material,
- 'icon-data': this.icon.customModelData,
- 'icon-lore': this.icon.lore,
- attributes: {
- 'level-base': this.levelReq.base,
- 'level-scale': this.levelReq.scale,
- 'cost-base': this.cost.base,
- 'cost-scale': this.cost.scale,
- 'cooldown-base': this.cooldown.base,
- 'cooldown-scale': this.cooldown.scale,
- 'mana-base': this.mana.base,
- 'mana-scale': this.mana.scale,
- 'points-spent-req-base': this.minSpent.base,
- 'points-spent-req-scale': this.minSpent.scale
- },
- incompatible: this.incompatible.map(s => s.name),
- components: compData
- };
-
- this.attributeRequirements.forEach(attr => {
- data.attributes[`${attr.name.toLowerCase()}-base`] = attr.base;
- data.attributes[`${attr.name.toLowerCase()}-scale`] = attr.scale;
- });
-
- return data;
- };
-
- public load = async (yaml: SkillYamlData) => {
- if (yaml.name) this.name = yaml.name;
- if (yaml.type) this.type = yaml.type;
- if (yaml['max-level']) this.maxLevel = yaml['max-level'];
- if (yaml['skill-req']) this.skillReqStr = yaml['skill-req'];
- if (yaml['skill-req-lvl']) this.skillReqLevel = yaml['skill-req-lvl'];
- if (yaml['needs-permission'] !== undefined) this.permission = yaml['needs-permission'];
- if (yaml['cooldown-message'] !== undefined) this.cooldownMessage = yaml['cooldown-message'];
- if (yaml.msg) this.castMessage = yaml.msg;
- if (yaml.combo) this.combo = yaml.combo;
-
- if (yaml.attributes) {
- const attributes = yaml.attributes;
- this.levelReq = { name: 'level', base: attributes['level-base'], scale: attributes['level-scale'] };
- this.cost = { name: 'cost', base: attributes['cost-base'], scale: attributes['cost-scale'] };
- this.cooldown = { name: 'cooldown', base: attributes['cooldown-base'], scale: attributes['cooldown-scale'] };
- this.mana = { name: 'mana', base: attributes['mana-base'], scale: attributes['mana-scale'] };
- this.minSpent = {
- name: 'points-spent-req',
- base: attributes['points-spent-req-base'],
- scale: attributes['points-spent-req-scale']
- };
-
- const reserved = ['level', 'cost', 'cooldown', 'mana', 'points-spent-req', 'incompatible'];
- const names = new Set(Object.keys(attributes).map(k => k.replace(/-(base|scale)/i, '')).filter(name => !reserved.includes(name)));
- this.attributeRequirements = [...names].map(name => ({
- name,
- base: attributes[`${name}-base`],
- scale: attributes[`${name}-scale`]
- }));
- }
-
- if (yaml.incompatible) this.incompStr = yaml.incompatible;
-
- if (yaml.icon) this.icon.material = toEditorCase(yaml.icon);
- if (yaml['icon-data']) this.icon.customModelData = yaml['icon-data'];
- if (yaml['icon-lore']) this.icon.lore = yaml['icon-lore'];
-
- let unsub: Unsubscriber | undefined = undefined;
-
- return new Promise((resolve) => {
- unsub = initialized.subscribe(init => {
- if (!init) return;
- if (yaml.components) this.triggers = Registry.deserializeComponents(yaml.components);
-
- if (unsub) {
- unsub();
- }
-
- this.loaded = true;
- resolve();
- });
- });
- };
-
- public postLoad = () => {
- this.skillReq = skillStore.getSkill(this.skillReqStr);
- this.incompatible = this.incompStr.map(s => skillStore.getSkill(s)).filter(s => !!s);
- };
-
- private saveDebounceTimeout: number | undefined;
- public save = () => {
- if (!this.name || this.tooBig) return;
-
- if (this.tooBig && !this.acknowledged) {
- saveError.set(this);
- return;
- }
-
- if (this.location === 'server') {
- return;
- }
-
- if (this.saveDebounceTimeout) {
- window.clearTimeout(this.saveDebounceTimeout);
- }
-
- this.changed();
- this.saveDebounceTimeout = window.setTimeout(() => {
- skillStore.isSaving.set(true);
-
- if (this.previousName && this.previousName !== this.name) {
- localStorage.removeItem('sapi.skill.' + this.previousName);
- }
- this.previousName = this.name;
-
- try {
- const yaml = YAML.stringify({ [this.name]: this.serializeYaml() }, {
- lineWidth: 0,
- aliasDuplicateObjects: false
- });
- localStorage.setItem('sapi.skill.' + this.name, yaml);
- this.tooBig = false;
- } catch (e: any) {
- // If the data is too big
- if (!e?.message?.includes('quota')) {
- console.error(this.name + ' Save error', e);
- } else {
- localStorage.removeItem('sapi.skill.' + this.name);
- this.tooBig = true;
- saveError.set(this);
- }
- }
-
- this.saveDebounceTimeout = undefined;
- skillStore.isSaving.set(false);
- console.log('Saved ' + this.name + ' 😎');
- }, 600); // Adjust the debounce delay as needed
- };
+ dataType = 'skill';
+ location: 'local' | 'server' = 'local';
+ loaded = false;
+
+ isSkill = true;
+ public key = {};
+ name: string = $state('');
+ previousName: string = '';
+ type = $state('Dynamic');
+ maxLevel = $state(5);
+ skillReq?: FabledSkill = $state();
+ skillReqLevel = $state(0);
+ attributeRequirements: IAttribute[] = $state([]);
+ permission: boolean = $state(false);
+ levelReq: IAttribute = $state({ name: 'level', base: 1, scale: 0 });
+ cost: IAttribute = $state({ name: 'cost', base: 1, scale: 0 });
+ cooldown: IAttribute = $state({ name: 'cooldown', base: 1, scale: 0 });
+ cooldownMessage: boolean = $state(true);
+ mana: IAttribute = $state({ name: 'mana', base: 0, scale: 0 });
+ minSpent: IAttribute = $state({ name: 'points-spent-req', base: 0, scale: 0 });
+ castMessage = $state('&6{player} &2has cast &6{skill}');
+ combo = $state('');
+ icon: Icon = $state({
+ material: 'Pumpkin',
+ customModelData: 0,
+ lore: [
+ '&d{name} &7({level}/{max})',
+ '&2Type: &6{type}',
+ '',
+ '{req:level}Level: {attr:level}',
+ '{req:cost}Cost: {attr:cost}',
+ '',
+ '&2Mana: {attr:mana}',
+ '&2Cooldown: {attr:cooldown}'
+ ]
+ });
+ incompatible: FabledSkill[] = $state([]);
+ triggers: FabledTrigger[] = $state([]);
+
+ private skillReqStr = '';
+ private incompStr: string[] = [];
+
+ constructor(data?: FabledSkillData) {
+ this.name = data?.name || 'Skill';
+ if (!data) return;
+ if (data.location) this.location = data.location;
+ if (data.type) this.type = data.type;
+ if (data.maxLevel) this.maxLevel = data.maxLevel;
+ if (data.skillReq) this.skillReq = data.skillReq;
+ if (data.skillReqLevel) this.skillReqLevel = data.skillReqLevel;
+ if (data.attributeRequirements)
+ this.attributeRequirements = data.attributeRequirements.map((a) => ({
+ name: a.name,
+ base: a.base,
+ scale: a.scale
+ }));
+ if (data.permission !== undefined) this.permission = data.permission;
+ if (data.levelReq) this.levelReq = data.levelReq;
+ if (data.cost) this.cost = data.cost;
+ if (data.cooldown) this.cooldown = data.cooldown;
+ if (data.cooldownMessage !== undefined) this.cooldownMessage = data.cooldownMessage;
+ if (data.mana) this.mana = data.mana;
+ if (data.minSpent) this.minSpent = data.minSpent;
+ if (data.castMessage) this.castMessage = data.castMessage;
+ if (data.combo) this.combo = data.combo;
+ if (data.icon) this.icon = data.icon;
+ if (data.incompatible) this.incompatible = data.incompatible;
+ if (data.triggers) this.triggers = data.triggers;
+ }
+
+ /**
+ * Reads all the reactive state elements to act as a chane detector
+ */
+ public changed = () => {
+ return {
+ name: this.name,
+ type: this.type,
+ 'max-level': this.maxLevel,
+ 'skill-req': this.skillReq?.name,
+ 'skill-req-lvl': this.skillReqLevel,
+ 'needs-permission': this.permission,
+ 'cooldown-message': this.cooldownMessage,
+ msg: this.castMessage,
+ combo: this.combo,
+ icon: this.icon.material,
+ 'icon-data': this.icon.customModelData,
+ 'icon-lore': this.icon.lore,
+ attributes: {
+ 'level-base': this.levelReq.base,
+ 'level-scale': this.levelReq.scale,
+ 'cost-base': this.cost.base,
+ 'cost-scale': this.cost.scale,
+ 'cooldown-base': this.cooldown.base,
+ 'cooldown-scale': this.cooldown.scale,
+ 'mana-base': this.mana.base,
+ 'mana-scale': this.mana.scale,
+ 'points-spent-req-base': this.minSpent.base,
+ 'points-spent-req-scale': this.minSpent.scale
+ },
+ incompatible: this.incompatible,
+ components: this.triggers
+ };
+ };
+
+ public addComponent = (comp: FabledComponent) => {
+ if (comp instanceof FabledTrigger) {
+ this.triggers = [...this.triggers, comp];
+ return;
+ }
+
+ if (this.triggers.length === 0) {
+ this.triggers.push(Registry.getTriggerByName('cast')?.new());
+ }
+
+ this.triggers[0].addComponent(comp);
+ this.triggers = [...this.triggers];
+ };
+
+ public removeComponent = (comp: FabledComponent) => {
+ if (comp instanceof FabledTrigger && this.triggers.includes(comp)) {
+ this.triggers.splice(this.triggers.indexOf(comp), 1);
+ return;
+ }
+
+ for (const trigger of this.triggers) {
+ if (trigger.contains(comp)) trigger.removeComponent(comp);
+ }
+
+ this.triggers = [...this.triggers];
+ };
+
+ private nextChar = (c: string) => {
+ if (/z$/.test(c)) {
+ return c.replaceAll(/z$/g, 'a') + 'a';
+ }
+ return c.substring(0, c.length - 1) + String.fromCharCode(c.charCodeAt(c.length - 1) + 1);
+ };
+
+ public serializeYaml = (): SkillYamlData => {
+ const compData = {};
+
+ for (const comp of this.triggers) {
+ const yamlData = comp.toYamlObj();
+ let name = comp.name;
+ let suffix = 'a';
+ while (compData[name]) {
+ suffix = this.nextChar(suffix);
+ name = comp.name + '-' + suffix;
+ }
+ compData[name] = yamlData;
+ }
+ const data = {
+ name: this.name,
+ type: this.type,
+ 'max-level': this.maxLevel,
+ 'skill-req': this.skillReq?.name,
+ 'skill-req-lvl': this.skillReqLevel,
+ 'needs-permission': this.permission,
+ 'cooldown-message': this.cooldownMessage,
+ msg: this.castMessage,
+ combo: this.combo,
+ icon: this.icon.material,
+ 'icon-data': this.icon.customModelData,
+ 'icon-lore': this.icon.lore,
+ attributes: {
+ 'level-base': this.levelReq.base,
+ 'level-scale': this.levelReq.scale,
+ 'cost-base': this.cost.base,
+ 'cost-scale': this.cost.scale,
+ 'cooldown-base': this.cooldown.base,
+ 'cooldown-scale': this.cooldown.scale,
+ 'mana-base': this.mana.base,
+ 'mana-scale': this.mana.scale,
+ 'points-spent-req-base': this.minSpent.base,
+ 'points-spent-req-scale': this.minSpent.scale
+ },
+ incompatible: this.incompatible.map((s) => s.name),
+ components: compData
+ };
+
+ this.attributeRequirements.forEach((attr) => {
+ data.attributes[`${attr.name.toLowerCase()}-base`] = attr.base;
+ data.attributes[`${attr.name.toLowerCase()}-scale`] = attr.scale;
+ });
+
+ return data;
+ };
+
+ public load = async (yaml: SkillYamlData) => {
+ if (yaml.name) this.name = yaml.name;
+ if (yaml.type) this.type = yaml.type;
+ if (yaml['max-level']) this.maxLevel = yaml['max-level'];
+ if (yaml['skill-req']) this.skillReqStr = yaml['skill-req'];
+ if (yaml['skill-req-lvl']) this.skillReqLevel = yaml['skill-req-lvl'];
+ if (yaml['needs-permission'] !== undefined) this.permission = yaml['needs-permission'];
+ if (yaml['cooldown-message'] !== undefined) this.cooldownMessage = yaml['cooldown-message'];
+ if (yaml.msg) this.castMessage = yaml.msg;
+ if (yaml.combo) this.combo = yaml.combo;
+
+ if (yaml.attributes) {
+ const attributes = yaml.attributes;
+ this.levelReq = {
+ name: 'level',
+ base: attributes['level-base'],
+ scale: attributes['level-scale']
+ };
+ this.cost = { name: 'cost', base: attributes['cost-base'], scale: attributes['cost-scale'] };
+ this.cooldown = {
+ name: 'cooldown',
+ base: attributes['cooldown-base'],
+ scale: attributes['cooldown-scale']
+ };
+ this.mana = { name: 'mana', base: attributes['mana-base'], scale: attributes['mana-scale'] };
+ this.minSpent = {
+ name: 'points-spent-req',
+ base: attributes['points-spent-req-base'],
+ scale: attributes['points-spent-req-scale']
+ };
+
+ const reserved = ['level', 'cost', 'cooldown', 'mana', 'points-spent-req', 'incompatible'];
+ const names = new Set(
+ Object.keys(attributes)
+ .map((k) => k.replace(/-(base|scale)/i, ''))
+ .filter((name) => !reserved.includes(name))
+ );
+ this.attributeRequirements = [...names].map((name) => ({
+ name,
+ base: attributes[`${name}-base`],
+ scale: attributes[`${name}-scale`]
+ }));
+ }
+
+ if (yaml.incompatible) this.incompStr = yaml.incompatible;
+
+ if (yaml.icon) this.icon.material = toEditorCase(yaml.icon);
+ if (yaml['icon-data']) this.icon.customModelData = yaml['icon-data'];
+ if (yaml['icon-lore']) this.icon.lore = yaml['icon-lore'];
+
+ let unsub: Unsubscriber | undefined = undefined;
+
+ return new Promise((resolve) => {
+ unsub = initialized.subscribe((init) => {
+ if (!init) return;
+ if (yaml.components)
+ this.triggers = Registry.deserializeComponents(yaml.components);
+
+ if (unsub) {
+ unsub();
+ }
+
+ this.loaded = true;
+ resolve();
+ });
+ });
+ };
+
+ public postLoad = () => {
+ this.skillReq = skillStore.getSkill(this.skillReqStr);
+ this.incompatible = (
+ this.incompStr.map((s) => skillStore.getSkill(s)).filter((s) => !!s)
+ );
+ };
+
+ private saveDebounceTimeout: number | undefined;
+ public save = () => {
+ if (!this.name) return;
+
+ if (this.location === 'server') {
+ return;
+ }
+
+ if (this.saveDebounceTimeout) {
+ window.clearTimeout(this.saveDebounceTimeout);
+ }
+
+ this.changed();
+ this.saveDebounceTimeout = window.setTimeout(async () => {
+ skillStore.isSaving.set(true);
+ const result = await savePersistedSkill(
+ this.name,
+ this.serializeYaml(),
+ this.previousName || undefined
+ );
+ if (!result.ok) {
+ console.error(this.name + ' Save error', result.error);
+ saveError.set({
+ name: this.name,
+ message: getPersistenceFailureMessage(result)
+ });
+ } else {
+ this.previousName = this.name;
+ if (get(saveError)?.name === this.name) {
+ saveError.set(undefined);
+ }
+ }
+
+ this.saveDebounceTimeout = undefined;
+ skillStore.isSaving.set(false);
+ console.log('Saved ' + this.name + ' 😎');
+ }, 600); // Adjust the debounce delay as needed
+ };
}
class SkillStore {
- isLegacy = false;
- private loadSkillsFromServer = async () => {
- let serverSkills: string[];
- try {
- serverSkills = await socketService.getSkills();
- } catch (_) {
- return;
- }
-
- const tempFolders = get(this.skillFolders);
- const tempSkills = get(this.skills);
- // Skills come through with some sort of path before their name A/B/C/Skill
- // We need to create folders for each of these
- serverSkills.forEach(sk => {
- const parts = sk.split('/');
- const name = parts.pop();
- if (!name) return;
-
- let previous: FabledFolder | undefined;
- let folder: FabledFolder | undefined;
- parts.forEach(part => {
- folder = previous ? previous.getSubfolder(part) : tempFolders.find(f => f.name === part);
- if (!folder) {
- folder = new FabledFolder();
- folder.name = part;
- folder.location = 'server';
- if (previous) {
- previous.add(folder);
- folder.updateParent(previous);
- }
- }
- if (!previous && !tempFolders.includes(folder)) tempFolders.push(folder);
- previous = folder;
- });
-
- // If we already have this skill, don't add it
- if (tempSkills.find(sk => sk.name === name)) return;
-
- const skill = new FabledSkill({ name, location: 'server' });
- if (folder) folder.add(skill);
- tempSkills.push(skill);
- });
-
- this.skills.set(tempSkills);
- this.skillFolders.set(tempFolders);
- };
-
- private removeServerSkills = () => {
- const tempSkills = get(this.skills);
- this.skills.set(tempSkills.filter(c => c.location !== 'server'));
-
- const tempFolders = get(this.skillFolders);
- tempFolders.filter(f => f.location === 'server').forEach(f => this.deleteSkillFolder(f, (sb) => sb.location === 'server'));
- };
-
- constructor() {
- socketService.onConnect(this.loadSkillsFromServer);
- socketService.onDisconnect(this.removeServerSkills);
-
- get(this.skills).forEach(sk => {
- if (sk.loaded) {
- sk.postLoad();
- }
- });
-
- if (this.isLegacy) {
- const sub = initialized.subscribe(init => {
- if (!init) return;
- get(this.skills).forEach(sk => {
- if (sk.location === 'local') sk.save();
- });
- this.persistSkills();
- if (sub) sub();
- });
- }
- }
-
- private loadSkillTextToArray = (text: string): FabledSkill[] => {
- const list: FabledSkill[] = [];
- // Load skills
- const data = parseYaml(text);
- if (!data || Object.keys(data).length === 0) {
- // If there is no data or the object is empty... return
- return list;
- }
-
- const keys = Object.keys(data);
-
- let skill: FabledSkill;
- // If we only have one skill, and it is the current YAML,
- // the structure is a bit different
- if (keys.length == 1) {
- const key = keys[0];
- if (key === 'loaded') return list;
- skill = new FabledSkill({ name: key });
- skill.load(data[key]).then(() => {
- });
- list.push(skill);
- return list;
- }
-
- for (const key of Object.keys(data)) {
- if (key != 'loaded') {
- skill = new FabledSkill({ name: key });
- skill.load(data[key]).then(() => {
- });
- list.push(skill);
- }
- }
- return list;
- };
-
- private setupSkillStore = (key: string,
- def: T,
- mapper: (data: string) => T,
- setAction: (data: T) => T,
- postLoad?: (saved: T) => void): Writable => {
- let saved: T = def;
- if (browser) {
- const stored = localStorage.getItem(key);
- if (stored) {
- saved = mapper(stored);
- if (postLoad) postLoad(saved);
- }
- }
-
- const {
- subscribe,
- set,
- update
- } = writable(saved);
- return {
- subscribe,
- set: (value: T) => {
- if (setAction) value = setAction(value);
- return set(value);
- },
- update
- };
- };
-
- skills: Writable = this.setupSkillStore(
- browser && localStorage.getItem('skillNames') ? 'skillNames' : 'skillData',
- [],
- (data: string) => {
- if (localStorage.getItem('skillNames')) {
- return data.split(', ').map(name => new FabledSkill({
- name,
- location: 'local'
- })).filter(sk => localStorage.getItem('sapi.skill.' + sk.name));
- } else {
- localStorage.removeItem('skillData');
- this.isLegacy = true;
- return sort(this.loadSkillTextToArray(data));
- }
- },
- (value: FabledSkill[]) => {
- this.persistSkills();
- return sort(value);
- });
-
- getSkill = (name: string): FabledSkill | undefined => {
- for (const c of get(this.skills)) {
- if (c.name == name) return c;
- }
-
- return undefined;
- };
-
- skillFolders: Writable = this.setupSkillStore('skillFolders', [],
- (data: string) => {
- if (!data || data === 'null') return [];
-
- try {
- return JSON.parse(data, (key: string, value) => {
- if (!value) return;
- if (/\d+/.test(key)) {
- if (typeof (value) === 'string') {
- return this.getSkill(value);
- }
-
- const folder = new FabledFolder(value.data);
- folder.name = value.name;
- return folder;
- }
- return value;
- });
- } catch (e) {
- console.error('Error loading skill folders. Folder data: ' + data, e);
- notify('Error loading skill folders. ' + JSON.stringify(e) + '\nFolder data: ' + data);
- return [];
- }
- },
- (value: FabledFolder[]) => {
- const data = JSON.stringify(value, (key, value: FabledFolder | FabledSkill) => {
- if (value instanceof FabledSkill) return value.name;
- else if (key === 'parent') return undefined;
- return value;
- });
- localStorage.setItem('skillFolders', data);
- return sort(value);
- });
-
- isSkillNameTaken = (name: string): boolean => !!this.getSkill(name);
-
- addSkill = (name?: string): FabledSkill => {
- const allSkills = get(this.skills);
- let index = allSkills.length + 1;
- while (!name && this.isSkillNameTaken(name || 'Skill ' + index)) {
- index++;
- }
- const skill = new FabledSkill({ name: (name || 'Skill ' + index) });
- allSkills.push(skill);
-
- this.skills.set(allSkills);
- skill.save();
- return skill;
- };
-
- loadSkill = async (data: FabledSkill) => {
- if (data.loaded) return;
- let yamlData: MultiSkillYamlData;
-
- if (data.location === 'local') {
- yamlData = parseYaml(localStorage.getItem(`sapi.skill.${data.name}`) || '');
- } else {
- const yaml = await socketService.getSkillYaml(data.name);
- if (!yaml) return;
-
- yamlData = parseYaml(yaml);
- }
-
- // Get the first entry in the object
- const skill = Object.values(yamlData)[0];
- await data.load(skill);
-
- data.postLoad();
- };
-
- cloneSkill = async (data: FabledSkill): Promise => {
- if (!data.loaded) await this.loadSkill(data);
-
- const sk: FabledSkill[] = get(this.skills);
- let name = data.name + ' (Copy)';
- let i = 1;
- while (this.isSkillNameTaken(name)) {
- name = data.name + ' (Copy ' + i + ')';
- i++;
- }
- const skill = new FabledSkill();
- const yamlData = data.serializeYaml();
- await skill.load(yamlData);
- skill.name = name;
- sk.push(skill);
-
- this.skills.set(sk);
- skill.save();
- return skill;
- };
-
- addSkillFolder = (folder: FabledFolder) => {
- const folders = get(this.skillFolders);
- if (folders.includes(folder)) return;
-
- folderStore.rename(folder, folders);
-
- folders.push(folder);
- folders.sort((a, b) => a.name.localeCompare(b.name));
- this.skillFolders.set(folders);
- };
-
-
- deleteSkillFolder = (folder: FabledFolder, deleteCheck?: (subfolder: FabledFolder) => boolean) => {
- const folders = get(this.skillFolders).filter(f => f != folder);
-
- // If there are any subfolders or skills, move them to the parent or root
- folder.data.forEach(d => {
- if (d instanceof FabledFolder) {
- if (deleteCheck && deleteCheck(d)) {
- this.deleteSkillFolder(d, deleteCheck);
- return;
- }
- if (folder.parent) folder.parent.add(d);
- else {
- d.updateParent();
- folders.push(d);
- }
- } else if (folder.parent)
- folder.parent.add(d); // Add the skill to the parent folder
- });
-
- this.skillFolders.set(folders);
- };
-
- deleteSkill = (data: FabledSkill) => {
- const filtered = get(this.skills).filter(c => c != data);
- const act = get(active);
- this.skills.set(filtered);
- localStorage.removeItem('sapi.skill.' + data.name);
-
- if (!(act instanceof FabledSkill)) return;
-
- if (filtered.length === 0) goto(`${base}/`).then(() => {
- });
- else if (!filtered.find(sk => sk === get(active))) goto(`${base}/skill/${filtered[0].name}`).then(() => {
- });
- };
-
- refreshSkills = () => this.skills.set(sort(get(this.skills)));
- refreshSkillFolders = () => {
- this.skillFolders.set(sort(get(this.skillFolders)));
- this.refreshSkills();
- };
-
-
- /**
- * Loads skill data from a string
- */
- loadSkillText = async (text: string, fromServer: boolean = false) => {
- // Load new skills
- const data = parseYaml(text);
-
- if (!data || Object.keys(data).length === 0) {
- // If there is no data or the object is empty... return
- return;
- }
-
- const keys = Object.keys(data);
-
- let skill: FabledSkill;
- // If we only have one skill, and it is the current YAML,
- // the structure is a bit different
- if (keys.length == 1) {
- const key: string = keys[0];
- skill = ((this.isSkillNameTaken(key)
- ? this.getSkill(key)
- : this.addSkill(key)));
- if (fromServer) skill.location = 'server';
- await skill.load(data[key]);
- skill.save();
- this.refreshSkills();
- return;
- }
-
- for (const key of Object.keys(data)) {
- if (key != 'loaded' && !this.isSkillNameTaken(key)) {
- skill = ((this.isSkillNameTaken(key)
- ? this.getSkill(key)
- : this.addSkill(key)));
- await skill.load(data[key]);
- skill.save();
- }
- }
- this.refreshSkills();
- };
-
- loadSkills = async (e: ProgressEvent) => {
- const text: string = e.target?.result;
- if (!text) return;
-
- await this.loadSkillText(text);
- };
-
- isSaving: Writable = writable(false);
- saveTask: number = 0;
-
- persistSkills = (list?: FabledSkill[]) => {
- if (get(this.isSaving) && this.saveTask) {
- clearTimeout(this.saveTask);
- }
-
- this.isSaving.set(true);
-
- this.saveTask = window.setTimeout(() => {
- const skillList = (list || get(this.skills)).filter(sk => sk.location === 'local');
- localStorage.setItem('skillNames', skillList.map(sk => sk.name).join(', '));
- this.isSaving.set(false);
- });
- };
+ isLegacy = false;
+ private loadSkillsFromServer = async () => {
+ let serverSkills: string[];
+ try {
+ serverSkills = await socketService.getSkills();
+ } catch (_) {
+ return;
+ }
+
+ const tempFolders = get(this.skillFolders);
+ const tempSkills = get(this.skills);
+ // Skills come through with some sort of path before their name A/B/C/Skill
+ // We need to create folders for each of these
+ serverSkills.forEach((sk) => {
+ const parts = sk.split('/');
+ const name = parts.pop();
+ if (!name) return;
+
+ let previous: FabledFolder | undefined;
+ let folder: FabledFolder | undefined;
+ parts.forEach((part) => {
+ folder = previous ? previous.getSubfolder(part) : tempFolders.find((f) => f.name === part);
+ if (!folder) {
+ folder = new FabledFolder();
+ folder.name = part;
+ folder.location = 'server';
+ if (previous) {
+ previous.add(folder);
+ folder.updateParent(previous);
+ }
+ }
+ if (!previous && !tempFolders.includes(folder)) tempFolders.push(folder);
+ previous = folder;
+ });
+
+ // If we already have this skill, don't add it
+ if (tempSkills.find((sk) => sk.name === name)) return;
+
+ const skill = new FabledSkill({ name, location: 'server' });
+ if (folder) folder.add(skill);
+ tempSkills.push(skill);
+ });
+
+ this.skills.set(tempSkills);
+ this.skillFolders.set(tempFolders);
+ };
+
+ private removeServerSkills = () => {
+ const tempSkills = get(this.skills);
+ this.skills.set(tempSkills.filter((c) => c.location !== 'server'));
+
+ const tempFolders = get(this.skillFolders);
+ tempFolders
+ .filter((f) => f.location === 'server')
+ .forEach((f) => this.deleteSkillFolder(f, (sb) => sb.location === 'server'));
+ };
+
+ constructor() {
+ socketService.onConnect(this.loadSkillsFromServer);
+ socketService.onDisconnect(this.removeServerSkills);
+
+ get(this.skills).forEach((sk) => {
+ if (sk.loaded) {
+ sk.postLoad();
+ }
+ });
+ }
+
+ private loadSkillTextToArray = (text: string): FabledSkill[] => {
+ const list: FabledSkill[] = [];
+ // Load skills
+ const data = parseYaml(text);
+ if (!data || Object.keys(data).length === 0) {
+ // If there is no data or the object is empty... return
+ return list;
+ }
+
+ const keys = Object.keys(data);
+
+ let skill: FabledSkill;
+ // If we only have one skill, and it is the current YAML,
+ // the structure is a bit different
+ if (keys.length == 1) {
+ const key = keys[0];
+ if (key === 'loaded') return list;
+ skill = new FabledSkill({ name: key });
+ skill.load(data[key]).then(() => {});
+ list.push(skill);
+ return list;
+ }
+
+ for (const key of Object.keys(data)) {
+ if (key != 'loaded') {
+ skill = new FabledSkill({ name: key });
+ skill.load(data[key]).then(() => {});
+ list.push(skill);
+ }
+ }
+ return list;
+ };
+
+ private setupSkillStore = (
+ _key: string,
+ def: T,
+ mapper: (data: string) => T,
+ setAction: (data: T) => T,
+ postLoad?: (saved: T) => void
+ ): Writable => {
+ let saved: T = def;
+ if (postLoad) postLoad(saved);
+
+ const { subscribe, set, update } = writable(saved);
+ return {
+ subscribe,
+ set: (value: T) => {
+ if (setAction) value = setAction(value);
+ return set(value);
+ },
+ update
+ };
+ };
+
+ private deserializeSkillFolders = (data: string | FolderProperties[]): FabledFolder[] => {
+ const serialized = typeof data === 'string' ? data : JSON.stringify(data);
+ if (!serialized || serialized === 'null') return [];
+
+ try {
+ return JSON.parse(serialized, (key: string, value) => {
+ if (value == null) return;
+ if (/\d+/.test(key)) {
+ if (typeof value === 'string') {
+ return this.getSkill(value);
+ }
+
+ const folder = new FabledFolder(value.data);
+ folder.name = value.name;
+ folder.location = value.location || 'local';
+ folder.open = !!value.open;
+ return folder;
+ }
+ return value;
+ });
+ } catch (e) {
+ console.error('Error loading skill folders. Folder data: ' + serialized, e);
+ notify('Error loading skill folders. ' + JSON.stringify(e) + '\nFolder data: ' + serialized);
+ return [];
+ }
+ };
+
+ hydratePersistedData = async () => {
+ const skills = listPersistedSkillNames().map(
+ (name) =>
+ new FabledSkill({
+ name,
+ location: 'local'
+ })
+ );
+
+ this.skills.set(sort(skills));
+ this.skillFolders.set(
+ sort(this.deserializeSkillFolders(getPersistedFolders('skill')))
+ );
+ };
+
+ skills: Writable = this.setupSkillStore(
+ 'skills',
+ [],
+ (_data: string) => [],
+ (value: FabledSkill[]) => {
+ this.persistSkills();
+ return sort(value);
+ }
+ );
+
+ getSkill = (name: string): FabledSkill | undefined => {
+ for (const c of get(this.skills)) {
+ if (c.name == name) return c;
+ }
+
+ return undefined;
+ };
+
+ skillFolders: Writable = this.setupSkillStore(
+ 'skill-folders',
+ [],
+ (_data: string) => [],
+ (value: FabledFolder[]) => {
+ void savePersistedFolders(
+ 'skill',
+ value.filter((folder) => folder.location === 'local').map((folder) => folder.toJSON())
+ ).then((result) => {
+ if (result.ok) {
+ if (get(saveError)?.name === 'Skills') {
+ saveError.set(undefined);
+ }
+ return;
+ }
+ console.error('Skill folder save error', result.error);
+ saveError.set({
+ name: 'Skills',
+ message: getPersistenceFailureMessage(result)
+ });
+ });
+ return sort(value);
+ }
+ );
+
+ isSkillNameTaken = (name: string): boolean => !!this.getSkill(name);
+
+ addSkill = (name?: string): FabledSkill => {
+ const allSkills = get(this.skills);
+ let index = allSkills.length + 1;
+ while (!name && this.isSkillNameTaken(name || 'Skill ' + index)) {
+ index++;
+ }
+ const skill = new FabledSkill({ name: name || 'Skill ' + index });
+ allSkills.push(skill);
+
+ this.skills.set(allSkills);
+ skill.save();
+ return skill;
+ };
+
+ loadSkill = async (data: FabledSkill) => {
+ if (data.loaded) return;
+
+ if (data.location === 'local') {
+ const yamlData = await getPersistedSkill(data.name);
+ if (!yamlData) return;
+ await data.load(yamlData);
+ } else {
+ const yaml = await socketService.getSkillYaml(data.name);
+ if (!yaml) return;
+ const yamlData = parseYaml(yaml);
+ const skill = Object.values(yamlData)[0];
+ await data.load(skill);
+ }
+
+ data.postLoad();
+ };
+
+ cloneSkill = async (data: FabledSkill): Promise => {
+ if (!data.loaded) await this.loadSkill(data);
+
+ const sk: FabledSkill[] = get(this.skills);
+ let name = data.name + ' (Copy)';
+ let i = 1;
+ while (this.isSkillNameTaken(name)) {
+ name = data.name + ' (Copy ' + i + ')';
+ i++;
+ }
+ const skill = new FabledSkill();
+ const yamlData = data.serializeYaml();
+ await skill.load(yamlData);
+ skill.name = name;
+ sk.push(skill);
+
+ this.skills.set(sk);
+ skill.save();
+ return skill;
+ };
+
+ addSkillFolder = (folder: FabledFolder) => {
+ const folders = get(this.skillFolders);
+ if (folders.includes(folder)) return;
+
+ folderStore.rename(folder, folders);
+
+ folders.push(folder);
+ folders.sort((a, b) => a.name.localeCompare(b.name));
+ this.skillFolders.set(folders);
+ };
+
+ deleteSkillFolder = (
+ folder: FabledFolder,
+ deleteCheck?: (subfolder: FabledFolder) => boolean
+ ) => {
+ const folders = get(this.skillFolders).filter((f) => f != folder);
+
+ // If there are any subfolders or skills, move them to the parent or root
+ folder.data.forEach((d) => {
+ if (d instanceof FabledFolder) {
+ if (deleteCheck && deleteCheck(d)) {
+ this.deleteSkillFolder(d, deleteCheck);
+ return;
+ }
+ if (folder.parent) folder.parent.add(d);
+ else {
+ d.updateParent();
+ folders.push(d);
+ }
+ } else if (folder.parent) folder.parent.add(d); // Add the skill to the parent folder
+ });
+
+ this.skillFolders.set(folders);
+ };
+
+ deleteSkill = (data: FabledSkill) => {
+ const filtered = get(this.skills).filter((c) => c != data);
+ const act = get(active);
+ this.skills.set(filtered);
+ void deletePersistedSkill(data.name);
+
+ if (!(act instanceof FabledSkill)) return;
+
+ if (filtered.length === 0) goto(`${base}/`).then(() => {});
+ else if (!filtered.find((sk) => sk === get(active)))
+ goto(`${base}/skill/${filtered[0].name}`).then(() => {});
+ };
+
+ refreshSkills = () => this.skills.set(sort(get(this.skills)));
+ refreshSkillFolders = () => {
+ this.skillFolders.set(sort(get(this.skillFolders)));
+ this.refreshSkills();
+ };
+
+ /**
+ * Loads skill data from a string
+ */
+ loadSkillText = async (text: string, fromServer: boolean = false) => {
+ // Load new skills
+ const data = parseYaml(text);
+
+ if (!data || Object.keys(data).length === 0) {
+ // If there is no data or the object is empty... return
+ return;
+ }
+
+ const keys = Object.keys(data);
+
+ let skill: FabledSkill;
+ // If we only have one skill, and it is the current YAML,
+ // the structure is a bit different
+ if (keys.length == 1) {
+ const key: string = keys[0];
+ skill = (this.isSkillNameTaken(key) ? this.getSkill(key) : this.addSkill(key));
+ if (fromServer) skill.location = 'server';
+ await skill.load(data[key]);
+ skill.save();
+ this.refreshSkills();
+ return;
+ }
+
+ for (const key of Object.keys(data)) {
+ if (key != 'loaded' && !this.isSkillNameTaken(key)) {
+ skill = (this.isSkillNameTaken(key) ? this.getSkill(key) : this.addSkill(key));
+ await skill.load(data[key]);
+ skill.save();
+ }
+ }
+ this.refreshSkills();
+ };
+
+ loadSkills = async (e: ProgressEvent) => {
+ const text: string = e.target?.result;
+ if (!text) return;
+
+ await this.loadSkillText(text);
+ };
+
+ isSaving: Writable = writable(false);
+ saveTask: number = 0;
+
+ persistSkills = (_list?: FabledSkill[]) => {};
}
-export const skillStore = new SkillStore();
\ No newline at end of file
+export const skillStore = new SkillStore();
diff --git a/src/data/store.ts b/src/data/store.ts
index 4822c13f17..129bf9156c 100644
--- a/src/data/store.ts
+++ b/src/data/store.ts
@@ -1,298 +1,329 @@
-import type { Readable, Writable } from 'svelte/store';
-import { derived, get, writable } from 'svelte/store';
-import { localStore } from '$api/api';
-import { attributeStore } from './attribute-store';
-import type FabledComponent
- from '$api/components/fabled-component.svelte';
+import type { Readable, Writable } from 'svelte/store';
+import { derived, get, writable } from 'svelte/store';
+import { localStore } from '$api/api';
+import { attributeStore } from './attribute-store';
+import type FabledComponent from '$api/components/fabled-component.svelte';
import type { MultiAttributeYamlData, MultiClassYamlData, MultiSkillYamlData } from '$api/types';
-import { socketService } from '$api/socket/socket-connector';
-import YAML from 'yaml';
-import FabledAttribute from '$api/fabled-attribute.svelte';
-import { Tab } from '$api/tab';
-import FabledSkill, { skillStore } from './skill-store.svelte';
-import FabledClass, { classStore } from './class-store.svelte';
-import { FabledFolder, folderStore } from './folder-store.svelte';
-
-export const active: Writable = writable(undefined);
-export const activeType: Readable<'class' | 'skill' | 'attribute' | ''> = derived(
- active,
- $active => {
- if ($active?.dataType === 'class') {
- return 'class';
- } else if ($active?.dataType === 'skill') {
- return 'skill';
- } else if ($active?.dataType === 'attribute') {
- return 'attribute';
- } else {
- return '';
- }
- }
+import { socketService } from '$api/socket/socket-connector';
+import YAML from 'yaml';
+import FabledAttribute from '$api/fabled-attribute.svelte';
+import { Tab } from '$api/tab';
+import FabledSkill, { skillStore } from './skill-store.svelte';
+import FabledClass, { classStore } from './class-store.svelte';
+import { FabledFolder, folderStore } from './folder-store.svelte';
+
+export const active: Writable =
+ writable(undefined);
+export const activeType: Readable<'class' | 'skill' | 'attribute' | ''> = derived(
+ active,
+ ($active) => {
+ if ($active?.dataType === 'class') {
+ return 'class';
+ } else if ($active?.dataType === 'skill') {
+ return 'skill';
+ } else if ($active?.dataType === 'attribute') {
+ return 'attribute';
+ } else {
+ return '';
+ }
+ }
);
-export const dragging: Writable = writable();
-export const draggingComponent: Writable = writable();
-export const showSidebar: Writable = localStore('sidebarOpen', true);
-export const sidebarOpen: Writable = writable(true);
-export const shownTab: Writable = writable(Tab.CLASSES);
-export const importing: Writable = writable(false);
-export const localSyncList: Writable