Compare commits
45 Commits
tomfoolery
...
4c50a5faab
| Author | SHA1 | Date | |
|---|---|---|---|
|
4c50a5faab
|
|||
|
b684ab790b
|
|||
|
1bcdb77c1d
|
|||
|
8968a5038d
|
|||
|
0a56992095
|
|||
|
e00df59358
|
|||
|
68c23b5f63
|
|||
|
63145c128e
|
|||
|
c0ff938bf3
|
|||
|
3944cf6ff4
|
|||
|
6fca5bae39
|
|||
|
cd443b974b
|
|||
|
31414c5874
|
|||
|
da3ab81ae6
|
|||
|
7e3a22f14b
|
|||
|
86827777f4
|
|||
|
5e7534af35
|
|||
|
11a4239fdc
|
|||
|
cdd90016f8
|
|||
|
864a11fac4
|
|||
|
48e5719813
|
|||
|
e0d6e5bc32
|
|||
|
fc0f04f29c
|
|||
|
9e83b72139
|
|||
|
f47f6e6cd8
|
|||
|
197b9267c4
|
|||
|
ed9fcbe116
|
|||
|
9f0234e00e
|
|||
|
c8220eec02
|
|||
|
99e6fd8eb4
|
|||
|
57320232a6
|
|||
|
892f43381b
|
|||
|
bfae55afa3
|
|||
|
c8de948e7f
|
|||
|
cfd45b6815
|
|||
|
4fb1d7865f
|
|||
|
a4cf9356a8
|
|||
|
f9fe6a2d11
|
|||
|
aea41df214
|
|||
|
9126e34f38
|
|||
|
4e680c6697
|
|||
|
28d6231f21
|
|||
|
a9008627a1
|
|||
|
72ee0260d9
|
|||
|
2bf5aeb1c0
|
@@ -1,2 +1,4 @@
|
|||||||
DB_FILE_NAME=static/data/amq.sqlite
|
DB_FILE_NAME=static/data/amq.sqlite
|
||||||
MAL_CLIENT_ID=
|
MAL_CLIENT_ID=
|
||||||
|
SENTRY_AUTH_TOKEN=
|
||||||
|
PUBLIC_SENTRY_DSN=
|
||||||
|
|||||||
8
.vscode/mcp.json
vendored
Normal file
8
.vscode/mcp.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"servers": {
|
||||||
|
"Sentry": {
|
||||||
|
"url": "https://mcp.sentry.dev/mcp/t-e5y/amqtrain",
|
||||||
|
"type": "http"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
468
bun.lock
468
bun.lock
@@ -4,23 +4,20 @@
|
|||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "amqtrain",
|
"name": "amqtrain",
|
||||||
"dependencies": {
|
|
||||||
"@libsql/client": "^0.17.0",
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.3.14",
|
"@biomejs/biome": "^2.3.14",
|
||||||
"@internationalized/date": "^3.10.0",
|
"@internationalized/date": "^3.10.0",
|
||||||
"@lucide/svelte": "^0.561.0",
|
"@lucide/svelte": "^0.561.0",
|
||||||
|
"@sentry/sveltekit": "^10",
|
||||||
"@sveltejs/adapter-auto": "^7.0.0",
|
"@sveltejs/adapter-auto": "^7.0.0",
|
||||||
"@sveltejs/adapter-cloudflare": "^7.2.6",
|
"@sveltejs/adapter-cloudflare": "^7.2.6",
|
||||||
"@sveltejs/adapter-node": "^5.5.2",
|
|
||||||
"@sveltejs/kit": "^2.50.1",
|
"@sveltejs/kit": "^2.50.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@types/node": "^25.2.0",
|
"@types/node": "^25.2.0",
|
||||||
|
"better-sqlite3": "^12.6.2",
|
||||||
"bits-ui": "^2.14.4",
|
"bits-ui": "^2.14.4",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dotenv": "^17.2.4",
|
|
||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.8",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"lefthook": "^2.1.0",
|
"lefthook": "^2.1.0",
|
||||||
@@ -33,6 +30,7 @@
|
|||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
|
"vaul-svelte": "^1.0.0-next.7",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"wrangler": "^4.62.0",
|
"wrangler": "^4.62.0",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
@@ -43,6 +41,42 @@
|
|||||||
"runed@0.37.1": "patches/runed@0.37.1.patch",
|
"runed@0.37.1": "patches/runed@0.37.1.patch",
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
|
"@apm-js-collab/code-transformer": ["@apm-js-collab/code-transformer@0.8.2", "", {}, "sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA=="],
|
||||||
|
|
||||||
|
"@apm-js-collab/tracing-hooks": ["@apm-js-collab/tracing-hooks@0.3.1", "", { "dependencies": { "@apm-js-collab/code-transformer": "^0.8.0", "debug": "^4.4.1", "module-details-from-path": "^1.0.4" } }, "sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw=="],
|
||||||
|
|
||||||
|
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||||
|
|
||||||
|
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||||
|
|
||||||
|
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||||
|
|
||||||
|
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||||
|
|
||||||
|
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||||
|
|
||||||
|
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||||
|
|
||||||
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||||
|
|
||||||
|
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
||||||
|
|
||||||
|
"@babel/parser": ["@babel/parser@7.26.9", "", { "dependencies": { "@babel/types": "^7.26.9" }, "bin": "./bin/babel-parser.js" }, "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A=="],
|
||||||
|
|
||||||
|
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||||
|
|
||||||
|
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||||
|
|
||||||
|
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||||
|
|
||||||
"@biomejs/biome": ["@biomejs/biome@2.3.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.14", "@biomejs/cli-darwin-x64": "2.3.14", "@biomejs/cli-linux-arm64": "2.3.14", "@biomejs/cli-linux-arm64-musl": "2.3.14", "@biomejs/cli-linux-x64": "2.3.14", "@biomejs/cli-linux-x64-musl": "2.3.14", "@biomejs/cli-win32-arm64": "2.3.14", "@biomejs/cli-win32-x64": "2.3.14" }, "bin": { "biome": "bin/biome" } }, "sha512-QMT6QviX0WqXJCaiqVMiBUCr5WRQ1iFSjvOLoTk6auKukJMvnMzWucXpwZB0e8F00/1/BsS9DzcKgWH+CLqVuA=="],
|
"@biomejs/biome": ["@biomejs/biome@2.3.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.14", "@biomejs/cli-darwin-x64": "2.3.14", "@biomejs/cli-linux-arm64": "2.3.14", "@biomejs/cli-linux-arm64-musl": "2.3.14", "@biomejs/cli-linux-x64": "2.3.14", "@biomejs/cli-linux-x64-musl": "2.3.14", "@biomejs/cli-win32-arm64": "2.3.14", "@biomejs/cli-win32-x64": "2.3.14" }, "bin": { "biome": "bin/biome" } }, "sha512-QMT6QviX0WqXJCaiqVMiBUCr5WRQ1iFSjvOLoTk6auKukJMvnMzWucXpwZB0e8F00/1/BsS9DzcKgWH+CLqVuA=="],
|
||||||
|
|
||||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UJGPpvWJMkLxSRtpCAKfKh41Q4JJXisvxZL8ChN1eNW3m/WlPFJ6EFDCE7YfUb4XS8ZFi3C1dFpxUJ0Ety5n+A=="],
|
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UJGPpvWJMkLxSRtpCAKfKh41Q4JJXisvxZL8ChN1eNW3m/WlPFJ6EFDCE7YfUb4XS8ZFi3C1dFpxUJ0Ety5n+A=="],
|
||||||
@@ -197,6 +231,8 @@
|
|||||||
|
|
||||||
"@internationalized/date": ["@internationalized/date@3.11.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q=="],
|
"@internationalized/date": ["@internationalized/date@3.11.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q=="],
|
||||||
|
|
||||||
|
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||||
@@ -207,35 +243,73 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
"@libsql/client": ["@libsql/client@0.17.0", "", { "dependencies": { "@libsql/core": "^0.17.0", "@libsql/hrana-client": "^0.9.0", "js-base64": "^3.7.5", "libsql": "^0.5.22", "promise-limit": "^2.7.0" } }, "sha512-TLjSU9Otdpq0SpKHl1tD1Nc9MKhrsZbCFGot3EbCxRa8m1E5R1mMwoOjKMMM31IyF7fr+hPNHLpYfwbMKNusmg=="],
|
|
||||||
|
|
||||||
"@libsql/core": ["@libsql/core@0.17.0", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-hnZRnJHiS+nrhHKLGYPoJbc78FE903MSDrFJTbftxo+e52X+E0Y0fHOCVYsKWcg6XgB7BbJYUrz/xEkVTSaipw=="],
|
|
||||||
|
|
||||||
"@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.5.22", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA=="],
|
|
||||||
|
|
||||||
"@libsql/darwin-x64": ["@libsql/darwin-x64@0.5.22", "", { "os": "darwin", "cpu": "x64" }, "sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA=="],
|
|
||||||
|
|
||||||
"@libsql/hrana-client": ["@libsql/hrana-client@0.9.0", "", { "dependencies": { "@libsql/isomorphic-ws": "^0.1.5", "cross-fetch": "^4.0.0", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-pxQ1986AuWfPX4oXzBvLwBnfgKDE5OMhAdR/5cZmRaB4Ygz5MecQybvwZupnRz341r2CtFmbk/BhSu7k2Lm+Jw=="],
|
|
||||||
|
|
||||||
"@libsql/isomorphic-ws": ["@libsql/isomorphic-ws@0.1.5", "", { "dependencies": { "@types/ws": "^8.5.4", "ws": "^8.13.0" } }, "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg=="],
|
|
||||||
|
|
||||||
"@libsql/linux-arm-gnueabihf": ["@libsql/linux-arm-gnueabihf@0.5.22", "", { "os": "linux", "cpu": "arm" }, "sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA=="],
|
|
||||||
|
|
||||||
"@libsql/linux-arm-musleabihf": ["@libsql/linux-arm-musleabihf@0.5.22", "", { "os": "linux", "cpu": "arm" }, "sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg=="],
|
|
||||||
|
|
||||||
"@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.5.22", "", { "os": "linux", "cpu": "arm64" }, "sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA=="],
|
|
||||||
|
|
||||||
"@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.5.22", "", { "os": "linux", "cpu": "arm64" }, "sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw=="],
|
|
||||||
|
|
||||||
"@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.5.22", "", { "os": "linux", "cpu": "x64" }, "sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew=="],
|
|
||||||
|
|
||||||
"@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.5.22", "", { "os": "linux", "cpu": "x64" }, "sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg=="],
|
|
||||||
|
|
||||||
"@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.5.22", "", { "os": "win32", "cpu": "x64" }, "sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA=="],
|
|
||||||
|
|
||||||
"@lucide/svelte": ["@lucide/svelte@0.561.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-vofKV2UFVrKE6I4ewKJ3dfCXSV6iP6nWVmiM83MLjsU91EeJcEg7LoWUABLp/aOTxj1HQNbJD1f3g3L0JQgH9A=="],
|
"@lucide/svelte": ["@lucide/svelte@0.561.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-vofKV2UFVrKE6I4ewKJ3dfCXSV6iP6nWVmiM83MLjsU91EeJcEg7LoWUABLp/aOTxj1HQNbJD1f3g3L0JQgH9A=="],
|
||||||
|
|
||||||
"@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.211.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.5.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/core": ["@opentelemetry/core@2.5.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.211.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.211.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.58.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-connect": ["@opentelemetry/instrumentation-connect@0.54.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.38" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-43RmbhUhqt3uuPnc16cX6NsxEASEtn8z/cYV8Zpt6EP4p2h9s4FNuJ4Q9BbEQ2C0YlCCB/2crO1ruVz/hWt8fA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-dataloader": ["@opentelemetry/instrumentation-dataloader@0.28.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ExXGBp0sUj8yhm6Znhf9jmuOaGDsYfDES3gswZnKr4MCqoBWQdEFn6EoDdt5u+RdbxQER+t43FoUihEfTSqsjA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-express": ["@opentelemetry/instrumentation-express@0.59.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pMKV/qnHiW/Q6pmbKkxt0eIhuNEtvJ7sUAyee192HErlr+a1Jx+FZ3WjfmzhQL1geewyGEiPGkmjjAgNY8TgDA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-fs": ["@opentelemetry/instrumentation-fs@0.30.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-n3Cf8YhG7reaj5dncGlRIU7iT40bxPOjsBEA5Bc1a1g6e9Qvb+JFJ7SEiMlPbUw4PBmxE3h40ltE8LZ3zVt6OA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-generic-pool": ["@opentelemetry/instrumentation-generic-pool@0.54.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-8dXMBzzmEdXfH/wjuRvcJnUFeWzZHUnExkmFJ2uPfa31wmpyBCMxO59yr8f/OXXgSogNgi/uPo9KW9H7LMIZ+g=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-graphql": ["@opentelemetry/instrumentation-graphql@0.58.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+yWVVY7fxOs3j2RixCbvue8vUuJ1inHxN2q1sduqDB0Wnkr4vOzVKRYl/Zy7B31/dcPS72D9lo/kltdOTBM3bQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-hapi": ["@opentelemetry/instrumentation-hapi@0.57.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Os4THbvls8cTQTVA8ApLfZZztuuqGEeqog0XUnyRW7QVF0d/vOVBEcBCk1pazPFmllXGEdNbbat8e2fYIWdFbw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.211.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/instrumentation": "0.211.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.59.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-875UxzBHWkW+P4Y45SoFM2AR8f8TzBMD8eO7QXGCyFSCUMP5s9vtt/BS8b/r2kqLyaRPK6mLbdnZznK3XzQWvw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-kafkajs": ["@opentelemetry/instrumentation-kafkajs@0.20.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.30.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-yJXOuWZROzj7WmYCUiyT27tIfqBrVtl1/TwVbQyWPz7rL0r1Lu7kWjD0PiVeTCIL6CrIZ7M2s8eBxsTAOxbNvw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-knex": ["@opentelemetry/instrumentation-knex@0.55.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.33.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FtTL5DUx5Ka/8VK6P1VwnlUXPa3nrb7REvm5ddLUIeXXq4tb9pKd+/ThB1xM/IjefkRSN3z8a5t7epYw1JLBJQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-koa": ["@opentelemetry/instrumentation-koa@0.59.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.36.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-K9o2skADV20Skdu5tG2bogPKiSpXh4KxfLjz6FuqIVvDJNibwSdu5UvyyBzRVp1rQMV6UmoIk6d3PyPtJbaGSg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-lru-memoizer": ["@opentelemetry/instrumentation-lru-memoizer@0.55.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FDBfT7yDGcspN0Cxbu/k8A0Pp1Jhv/m7BMTzXGpcb8ENl3tDj/51U65R5lWzUH15GaZA15HQ5A5wtafklxYj7g=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mongodb": ["@opentelemetry/instrumentation-mongodb@0.64.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pFlCJjweTqVp7B220mCvCld1c1eYKZfQt1p3bxSbcReypKLJTwat+wbL2YZoX9jPi5X2O8tTKFEOahO5ehQGsA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mongoose": ["@opentelemetry/instrumentation-mongoose@0.57.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MthiekrU/BAJc5JZoZeJmo0OTX6ycJMiP6sMOSRTkvz5BrPMYDqaJos0OgsLPL/HpcgHP7eo5pduETuLguOqcg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mysql": ["@opentelemetry/instrumentation-mysql@0.57.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/mysql": "2.15.27" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HFS/+FcZ6Q7piM7Il7CzQ4VHhJvGMJWjx7EgCkP5AnTntSN5rb5Xi3TkYJHBKeR27A0QqPlGaCITi93fUDs++Q=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-mysql2": ["@opentelemetry/instrumentation-mysql2@0.57.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@opentelemetry/sql-common": "^0.41.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-nHSrYAwF7+aV1E1V9yOOP9TchOodb6fjn4gFvdrdQXiRE7cMuffyLLbCZlZd4wsspBzVwOXX8mpURdRserAhNA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-pg": ["@opentelemetry/instrumentation-pg@0.63.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@opentelemetry/sql-common": "^0.41.2", "@types/pg": "8.15.6", "@types/pg-pool": "2.0.7" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-dKm/ODNN3GgIQVlbD6ZPxwRc3kleLf95hrRWXM+l8wYo+vSeXtEpQPT53afEf6VFWDVzJK55VGn8KMLtSve/cg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-redis": ["@opentelemetry/instrumentation-redis@0.59.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-JKv1KDDYA2chJ1PC3pLP+Q9ISMQk6h5ey+99mB57/ARk0vQPGZTTEb4h4/JlcEpy7AYT8HIGv7X6l+br03Neeg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-tedious": ["@opentelemetry/instrumentation-tedious@0.30.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/tedious": "^4.0.14" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-bZy9Q8jFdycKQ2pAsyuHYUHNmCxCOGdG6eg1Mn75RvQDccq832sU5OWOBnc12EFUELI6icJkhR7+EQKMBam2GA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/instrumentation-undici": ["@opentelemetry/instrumentation-undici@0.21.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.24.0" }, "peerDependencies": { "@opentelemetry/api": "^1.7.0" } }, "sha512-gok0LPUOTz2FQ1YJMZzaHcOzDFyT64XJ8M9rNkugk923/p6lDGms/cRW1cqgqp6N6qcd6K6YdVHwPEhnx9BWbw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/redis-common": ["@opentelemetry/redis-common@0.38.2", "", {}, "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resources": ["@opentelemetry/resources@2.5.1", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.5.1", "", { "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/resources": "2.5.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="],
|
||||||
|
|
||||||
|
"@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.2", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="],
|
||||||
|
|
||||||
|
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||||
|
|
||||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||||
|
|
||||||
@@ -245,13 +319,7 @@
|
|||||||
|
|
||||||
"@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
|
"@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
|
||||||
|
|
||||||
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.9", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA=="],
|
"@prisma/instrumentation": ["@prisma/instrumentation@7.2.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, "sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g=="],
|
||||||
|
|
||||||
"@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="],
|
|
||||||
|
|
||||||
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
|
|
||||||
|
|
||||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
|
||||||
|
|
||||||
@@ -303,6 +371,54 @@
|
|||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
|
||||||
|
|
||||||
|
"@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.38.0", "", { "dependencies": { "@sentry/core": "10.38.0" } }, "sha512-UOJtYmdcxHCcV0NPfXFff/a95iXl/E0EhuQ1y0uE0BuZDMupWSF5t2BgC4HaE5Aw3RTjDF3XkSHWoIF6ohy7eA=="],
|
||||||
|
|
||||||
|
"@sentry-internal/feedback": ["@sentry-internal/feedback@10.38.0", "", { "dependencies": { "@sentry/core": "10.38.0" } }, "sha512-JXneg9zRftyfy1Fyfc39bBlF/Qd8g4UDublFFkVvdc1S6JQPlK+P6q22DKz3Pc8w3ySby+xlIq/eTu9Pzqi4KA=="],
|
||||||
|
|
||||||
|
"@sentry-internal/replay": ["@sentry-internal/replay@10.38.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.38.0", "@sentry/core": "10.38.0" } }, "sha512-YWIkL6/dnaiQyFiZXJ/nN+NXGv/15z45ia86bE/TMq01CubX/DUOilgsFz0pk2v/pg3tp/U2MskLO9Hz0cnqeg=="],
|
||||||
|
|
||||||
|
"@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.38.0", "", { "dependencies": { "@sentry-internal/replay": "10.38.0", "@sentry/core": "10.38.0" } }, "sha512-OXWM9jEqNYh4VTvrMu7v+z1anz+QKQ/fZXIZdsO7JTT2lGNZe58UUMeoq386M+Saxen8F9SUH7yTORy/8KI5qw=="],
|
||||||
|
|
||||||
|
"@sentry/babel-plugin-component-annotate": ["@sentry/babel-plugin-component-annotate@4.9.1", "", {}, "sha512-0gEoi2Lb54MFYPOmdTfxlNKxI7kCOvNV7gP8lxMXJ7nCazF5OqOOZIVshfWjDLrc0QrSV6XdVvwPV9GDn4wBMg=="],
|
||||||
|
|
||||||
|
"@sentry/browser": ["@sentry/browser@10.38.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.38.0", "@sentry-internal/feedback": "10.38.0", "@sentry-internal/replay": "10.38.0", "@sentry-internal/replay-canvas": "10.38.0", "@sentry/core": "10.38.0" } }, "sha512-3phzp1YX4wcQr9mocGWKbjv0jwtuoDBv7+Y6Yfrys/kwyaL84mDLjjQhRf4gL5SX7JdYkhBp4WaiNlR0UC4kTA=="],
|
||||||
|
|
||||||
|
"@sentry/bundler-plugin-core": ["@sentry/bundler-plugin-core@4.9.1", "", { "dependencies": { "@babel/core": "^7.18.5", "@sentry/babel-plugin-component-annotate": "4.9.1", "@sentry/cli": "^2.57.0", "dotenv": "^16.3.1", "find-up": "^5.0.0", "glob": "^10.5.0", "magic-string": "0.30.8", "unplugin": "1.0.1" } }, "sha512-moii+w7N8k8WdvkX7qCDY9iRBlhgHlhTHTUQwF2FNMhBHuqlNpVcSJJqJMjFUQcjYMBDrZgxhfKV18bt5ixwlQ=="],
|
||||||
|
|
||||||
|
"@sentry/cli": ["@sentry/cli@2.58.4", "", { "dependencies": { "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", "progress": "^2.0.3", "proxy-from-env": "^1.1.0", "which": "^2.0.2" }, "optionalDependencies": { "@sentry/cli-darwin": "2.58.4", "@sentry/cli-linux-arm": "2.58.4", "@sentry/cli-linux-arm64": "2.58.4", "@sentry/cli-linux-i686": "2.58.4", "@sentry/cli-linux-x64": "2.58.4", "@sentry/cli-win32-arm64": "2.58.4", "@sentry/cli-win32-i686": "2.58.4", "@sentry/cli-win32-x64": "2.58.4" }, "bin": { "sentry-cli": "bin/sentry-cli" } }, "sha512-ArDrpuS8JtDYEvwGleVE+FgR+qHaOp77IgdGSacz6SZy6Lv90uX0Nu4UrHCQJz8/xwIcNxSqnN22lq0dH4IqTg=="],
|
||||||
|
|
||||||
|
"@sentry/cli-darwin": ["@sentry/cli-darwin@2.58.4", "", { "os": "darwin" }, "sha512-kbTD+P4X8O+nsNwPxCywtj3q22ecyRHWff98rdcmtRrvwz8CKi/T4Jxn/fnn2i4VEchy08OWBuZAqaA5Kh2hRQ=="],
|
||||||
|
|
||||||
|
"@sentry/cli-linux-arm": ["@sentry/cli-linux-arm@2.58.4", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm" }, "sha512-rdQ8beTwnN48hv7iV7e7ZKucPec5NJkRdrrycMJMZlzGBPi56LqnclgsHySJ6Kfq506A2MNuQnKGaf/sBC9REA=="],
|
||||||
|
|
||||||
|
"@sentry/cli-linux-arm64": ["@sentry/cli-linux-arm64@2.58.4", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm64" }, "sha512-0g0KwsOozkLtzN8/0+oMZoOuQ0o7W6O+hx+ydVU1bktaMGKEJLMAWxOQNjsh1TcBbNIXVOKM/I8l0ROhaAb8Ig=="],
|
||||||
|
|
||||||
|
"@sentry/cli-linux-i686": ["@sentry/cli-linux-i686@2.58.4", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "ia32" }, "sha512-NseoIQAFtkziHyjZNPTu1Gm1opeQHt7Wm1LbLrGWVIRvUOzlslO9/8i6wETUZ6TjlQxBVRgd3Q0lRBG2A8rFYA=="],
|
||||||
|
|
||||||
|
"@sentry/cli-linux-x64": ["@sentry/cli-linux-x64@2.58.4", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "x64" }, "sha512-d3Arz+OO/wJYTqCYlSN3Ktm+W8rynQ/IMtSZLK8nu0ryh5mJOh+9XlXY6oDXw4YlsM8qCRrNquR8iEI1Y/IH+Q=="],
|
||||||
|
|
||||||
|
"@sentry/cli-win32-arm64": ["@sentry/cli-win32-arm64@2.58.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-bqYrF43+jXdDBh0f8HIJU3tbvlOFtGyRjHB8AoRuMQv9TEDUfENZyCelhdjA+KwDKYl48R1Yasb4EHNzsoO83w=="],
|
||||||
|
|
||||||
|
"@sentry/cli-win32-i686": ["@sentry/cli-win32-i686@2.58.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-3triFD6jyvhVcXOmGyttf+deKZcC1tURdhnmDUIBkiDPJKGT/N5xa4qAtHJlAB/h8L9jgYih9bvJnvvFVM7yug=="],
|
||||||
|
|
||||||
|
"@sentry/cli-win32-x64": ["@sentry/cli-win32-x64@2.58.4", "", { "os": "win32", "cpu": "x64" }, "sha512-cSzN4PjM1RsCZ4pxMjI0VI7yNCkxiJ5jmWncyiwHXGiXrV1eXYdQ3n1LhUYLZ91CafyprR0OhDcE+RVZ26Qb5w=="],
|
||||||
|
|
||||||
|
"@sentry/cloudflare": ["@sentry/cloudflare@10.38.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@sentry/core": "10.38.0" }, "peerDependencies": { "@cloudflare/workers-types": "^4.x" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-g008TNjxPbS5csEem3u6jBO40qNY4Vky5q1hJXlUjoNnCDt+5vMLPMzVqJVVbAzWWU+dwjdiMzGeNjwn0RYwcQ=="],
|
||||||
|
|
||||||
|
"@sentry/core": ["@sentry/core@10.38.0", "", {}, "sha512-1pubWDZE5y5HZEPMAZERP4fVl2NH3Ihp1A+vMoVkb3Qc66Diqj1WierAnStlZP7tCx0TBa0dK85GTW/ZFYyB9g=="],
|
||||||
|
|
||||||
|
"@sentry/node": ["@sentry/node@10.38.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.5.0", "@opentelemetry/core": "^2.5.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/instrumentation-amqplib": "0.58.0", "@opentelemetry/instrumentation-connect": "0.54.0", "@opentelemetry/instrumentation-dataloader": "0.28.0", "@opentelemetry/instrumentation-express": "0.59.0", "@opentelemetry/instrumentation-fs": "0.30.0", "@opentelemetry/instrumentation-generic-pool": "0.54.0", "@opentelemetry/instrumentation-graphql": "0.58.0", "@opentelemetry/instrumentation-hapi": "0.57.0", "@opentelemetry/instrumentation-http": "0.211.0", "@opentelemetry/instrumentation-ioredis": "0.59.0", "@opentelemetry/instrumentation-kafkajs": "0.20.0", "@opentelemetry/instrumentation-knex": "0.55.0", "@opentelemetry/instrumentation-koa": "0.59.0", "@opentelemetry/instrumentation-lru-memoizer": "0.55.0", "@opentelemetry/instrumentation-mongodb": "0.64.0", "@opentelemetry/instrumentation-mongoose": "0.57.0", "@opentelemetry/instrumentation-mysql": "0.57.0", "@opentelemetry/instrumentation-mysql2": "0.57.0", "@opentelemetry/instrumentation-pg": "0.63.0", "@opentelemetry/instrumentation-redis": "0.59.0", "@opentelemetry/instrumentation-tedious": "0.30.0", "@opentelemetry/instrumentation-undici": "0.21.0", "@opentelemetry/resources": "^2.5.0", "@opentelemetry/sdk-trace-base": "^2.5.0", "@opentelemetry/semantic-conventions": "^1.39.0", "@prisma/instrumentation": "7.2.0", "@sentry/core": "10.38.0", "@sentry/node-core": "10.38.0", "@sentry/opentelemetry": "10.38.0", "import-in-the-middle": "^2.0.6", "minimatch": "^9.0.0" } }, "sha512-wriyDtWDAoatn8EhOj0U4PJR1WufiijTsCGALqakOHbFiadtBJANLe6aSkXoXT4tegw59cz1wY4NlzHjYksaPw=="],
|
||||||
|
|
||||||
|
"@sentry/node-core": ["@sentry/node-core@10.38.0", "", { "dependencies": { "@apm-js-collab/tracing-hooks": "^0.3.1", "@sentry/core": "10.38.0", "@sentry/opentelemetry": "10.38.0", "import-in-the-middle": "^2.0.6" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/resources": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" } }, "sha512-ErXtpedrY1HghgwM6AliilZPcUCoNNP1NThdO4YpeMq04wMX9/GMmFCu46TnCcg6b7IFIOSr2S4yD086PxLlHQ=="],
|
||||||
|
|
||||||
|
"@sentry/opentelemetry": ["@sentry/opentelemetry@10.38.0", "", { "dependencies": { "@sentry/core": "10.38.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" } }, "sha512-YPVhWfYmC7nD3EJqEHGtjp4fp5LwtAbE5rt9egQ4hqJlYFvr8YEz9sdoqSZxO0cZzgs2v97HFl/nmWAXe52G2Q=="],
|
||||||
|
|
||||||
|
"@sentry/svelte": ["@sentry/svelte@10.38.0", "", { "dependencies": { "@sentry/browser": "10.38.0", "@sentry/core": "10.38.0", "magic-string": "^0.30.0" }, "peerDependencies": { "svelte": "3.x || 4.x || 5.x" } }, "sha512-z/l7lb5BrUtw9Bmuk+m/gCphUvXxH4k5BUjrgGqVN6qAEsDzVk8X0jqmK93N53P5CM4M2wKnBhqr6aror2a13g=="],
|
||||||
|
|
||||||
|
"@sentry/sveltekit": ["@sentry/sveltekit@10.38.0", "", { "dependencies": { "@babel/parser": "7.26.9", "@sentry/cloudflare": "10.38.0", "@sentry/core": "10.38.0", "@sentry/node": "10.38.0", "@sentry/svelte": "10.38.0", "@sentry/vite-plugin": "^4.8.0", "magic-string": "0.30.7", "recast": "0.23.11", "sorcery": "1.0.0" }, "peerDependencies": { "@sveltejs/kit": "2.x", "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-ZJ0+YPf56E6uNntnxpmZLNPTGeMNxWRYUwKRu0K9mnMsL26Lu5JdP667BDIiegoDwkNfIHiE5DTKxpNkMNzddA=="],
|
||||||
|
|
||||||
|
"@sentry/vite-plugin": ["@sentry/vite-plugin@4.9.1", "", { "dependencies": { "@sentry/bundler-plugin-core": "4.9.1", "unplugin": "1.0.1" } }, "sha512-Tlyg2cyFYp/icX58GWvfpvZr9NLdLs2/xyFVyS8pQ0faZWmoXic3FMzoXYHV1gsdMbL1Yy5WQvGJy8j1rS8LGA=="],
|
||||||
|
|
||||||
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
|
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
|
||||||
|
|
||||||
"@speed-highlight/core": ["@speed-highlight/core@1.2.14", "", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="],
|
"@speed-highlight/core": ["@speed-highlight/core@1.2.14", "", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="],
|
||||||
@@ -317,8 +433,6 @@
|
|||||||
|
|
||||||
"@sveltejs/adapter-cloudflare": ["@sveltejs/adapter-cloudflare@7.2.6", "", { "dependencies": { "@cloudflare/workers-types": "^4.20250507.0", "worktop": "0.8.0-next.18" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0", "wrangler": "^4.0.0" } }, "sha512-PmaWW6EdMue8s24bUwa9EMsnjMaCS1HroM8HwlvwSxO8Cq5LldAxnnaUS5cnJ3RdVRorJZtL71eMTs+wbuXHgw=="],
|
"@sveltejs/adapter-cloudflare": ["@sveltejs/adapter-cloudflare@7.2.6", "", { "dependencies": { "@cloudflare/workers-types": "^4.20250507.0", "worktop": "0.8.0-next.18" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0", "wrangler": "^4.0.0" } }, "sha512-PmaWW6EdMue8s24bUwa9EMsnjMaCS1HroM8HwlvwSxO8Cq5LldAxnnaUS5cnJ3RdVRorJZtL71eMTs+wbuXHgw=="],
|
||||||
|
|
||||||
"@sveltejs/adapter-node": ["@sveltejs/adapter-node@5.5.2", "", { "dependencies": { "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "rollup": "^4.9.5" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0" } }, "sha512-L15Djwpr7HrSAPj/Z8PYfc0pa9A1tllrr18phKI0WJHJeoWw45yinPf0IGgVTmakqx1B3JQ+C/OFl9ZwmxHU1Q=="],
|
|
||||||
|
|
||||||
"@sveltejs/kit": ["@sveltejs/kit@2.50.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA=="],
|
"@sveltejs/kit": ["@sveltejs/kit@2.50.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA=="],
|
||||||
|
|
||||||
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA=="],
|
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA=="],
|
||||||
@@ -357,15 +471,21 @@
|
|||||||
|
|
||||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
|
||||||
|
|
||||||
|
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
|
||||||
|
|
||||||
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
|
"@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="],
|
"@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="],
|
||||||
|
|
||||||
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
|
"@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="],
|
||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
"@types/pg-pool": ["@types/pg-pool@2.0.7", "", { "dependencies": { "@types/pg": "*" } }, "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng=="],
|
||||||
|
|
||||||
|
"@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="],
|
||||||
|
|
||||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||||
|
|
||||||
@@ -373,14 +493,32 @@
|
|||||||
|
|
||||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||||
|
|
||||||
|
"acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="],
|
||||||
|
|
||||||
|
"agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||||
|
|
||||||
|
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||||
|
|
||||||
|
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||||
|
|
||||||
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||||
|
|
||||||
|
"ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
|
||||||
|
|
||||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
|
|
||||||
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||||
|
|
||||||
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
||||||
|
|
||||||
"better-sqlite3": ["better-sqlite3@12.6.2", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA=="],
|
"better-sqlite3": ["better-sqlite3@12.6.2", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA=="],
|
||||||
|
|
||||||
|
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||||
|
|
||||||
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
|
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
|
||||||
|
|
||||||
"bits-ui": ["bits-ui@2.15.5", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-WhS+P+E//ClLfKU6KqjKC17nGDRLnz+vkwoP6ClFUPd5m1fFVDxTElPX8QVsduLj5V1KFDxlnv6sW2G5Lqk+vw=="],
|
"bits-ui": ["bits-ui@2.15.5", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-WhS+P+E//ClLfKU6KqjKC17nGDRLnz+vkwoP6ClFUPd5m1fFVDxTElPX8QVsduLj5V1KFDxlnv6sW2G5Lqk+vw=="],
|
||||||
@@ -389,27 +527,39 @@
|
|||||||
|
|
||||||
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
|
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
|
||||||
|
|
||||||
|
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
|
|
||||||
|
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||||
|
|
||||||
|
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||||
|
|
||||||
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||||
|
|
||||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
||||||
|
|
||||||
|
"caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
|
||||||
|
|
||||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||||
|
|
||||||
"chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
|
"chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
|
||||||
|
|
||||||
|
"cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="],
|
||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
"coincident": ["coincident@1.2.3", "", { "dependencies": { "@ungap/structured-clone": "^1.2.0", "@ungap/with-resolvers": "^0.1.0", "gc-hook": "^0.3.1", "proxy-target": "^3.0.2" }, "optionalDependencies": { "ws": "^8.16.0" } }, "sha512-Uxz3BMTWIslzeWjuQnizGWVg0j6khbvHUQ8+5BdM7WuJEm4ALXwq3wluYoB+uF68uPBz/oUOeJnYURKyfjexlA=="],
|
"coincident": ["coincident@1.2.3", "", { "dependencies": { "@ungap/structured-clone": "^1.2.0", "@ungap/with-resolvers": "^0.1.0", "gc-hook": "^0.3.1", "proxy-target": "^3.0.2" }, "optionalDependencies": { "ws": "^8.16.0" } }, "sha512-Uxz3BMTWIslzeWjuQnizGWVg0j6khbvHUQ8+5BdM7WuJEm4ALXwq3wluYoB+uF68uPBz/oUOeJnYURKyfjexlA=="],
|
||||||
|
|
||||||
"commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
|
|
||||||
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
|
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||||
|
|
||||||
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
||||||
|
|
||||||
"cross-fetch": ["cross-fetch@4.1.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw=="],
|
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||||
|
|
||||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
|
||||||
|
|
||||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||||
|
|
||||||
@@ -425,12 +575,18 @@
|
|||||||
|
|
||||||
"devalue": ["devalue@5.6.2", "", {}, "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg=="],
|
"devalue": ["devalue@5.6.2", "", {}, "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg=="],
|
||||||
|
|
||||||
"dotenv": ["dotenv@17.2.4", "", {}, "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw=="],
|
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||||
|
|
||||||
"drizzle-kit": ["drizzle-kit@0.31.8", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="],
|
"drizzle-kit": ["drizzle-kit@0.31.8", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="],
|
||||||
|
|
||||||
"drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="],
|
"drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="],
|
||||||
|
|
||||||
|
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||||
|
|
||||||
|
"electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
|
||||||
|
|
||||||
|
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||||
|
|
||||||
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
|
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
|
||||||
|
|
||||||
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
||||||
@@ -441,55 +597,85 @@
|
|||||||
|
|
||||||
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||||
|
|
||||||
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
||||||
|
|
||||||
"esrap": ["esrap@2.2.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ=="],
|
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||||
|
|
||||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"esrap": ["esrap@2.2.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ=="],
|
||||||
|
|
||||||
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
|
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
|
||||||
|
|
||||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
|
||||||
|
|
||||||
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
|
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
|
||||||
|
|
||||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||||
|
|
||||||
|
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||||
|
|
||||||
|
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||||
|
|
||||||
|
"forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="],
|
||||||
|
|
||||||
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
|
||||||
|
|
||||||
"gc-hook": ["gc-hook@0.3.1", "", {}, "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A=="],
|
"gc-hook": ["gc-hook@0.3.1", "", {}, "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A=="],
|
||||||
|
|
||||||
|
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||||
|
|
||||||
"get-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="],
|
"get-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="],
|
||||||
|
|
||||||
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
||||||
|
|
||||||
|
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||||
|
|
||||||
|
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
|
"globalyzer": ["globalyzer@0.1.0", "", {}, "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="],
|
||||||
|
|
||||||
|
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
"https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
|
||||||
|
|
||||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||||
|
|
||||||
|
"import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="],
|
||||||
|
|
||||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
||||||
|
|
||||||
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
||||||
|
|
||||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
|
||||||
|
|
||||||
"is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
|
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||||
|
|
||||||
|
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
|
|
||||||
|
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||||
|
|
||||||
|
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||||
|
|
||||||
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
||||||
|
|
||||||
|
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
|
||||||
|
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||||
|
|
||||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||||
|
|
||||||
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
|
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
|
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||||
|
|
||||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||||
|
|
||||||
@@ -515,8 +701,6 @@
|
|||||||
|
|
||||||
"lefthook-windows-x64": ["lefthook-windows-x64@2.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-XbO/5nAZQLpUn0tPpgCYfFBFJHnymSglQ73jD6wymNrR1j8I5EcXGlP6YcLhnZ83yzsdLC+gup+N6IqUeiyRdw=="],
|
"lefthook-windows-x64": ["lefthook-windows-x64@2.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-XbO/5nAZQLpUn0tPpgCYfFBFJHnymSglQ73jD6wymNrR1j8I5EcXGlP6YcLhnZ83yzsdLC+gup+N6IqUeiyRdw=="],
|
||||||
|
|
||||||
"libsql": ["libsql@0.5.22", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.22", "@libsql/darwin-x64": "0.5.22", "@libsql/linux-arm-gnueabihf": "0.5.22", "@libsql/linux-arm-musleabihf": "0.5.22", "@libsql/linux-arm64-gnu": "0.5.22", "@libsql/linux-arm64-musl": "0.5.22", "@libsql/linux-x64-gnu": "0.5.22", "@libsql/linux-x64-musl": "0.5.22", "@libsql/win32-x64-msvc": "0.5.22" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA=="],
|
|
||||||
|
|
||||||
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||||
|
|
||||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||||
@@ -543,18 +727,28 @@
|
|||||||
|
|
||||||
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
||||||
|
|
||||||
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
|
|
||||||
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
|
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
"magic-string": ["magic-string@0.30.7", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA=="],
|
||||||
|
|
||||||
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
||||||
|
|
||||||
"miniflare": ["miniflare@4.20260131.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.18.2", "workerd": "1.20260131.0", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-CtObRzlAzOUpCFH+MgImykxmDNKthrgIYtC+oLC3UGpve6bGLomKUW4u4EorTvzlQFHe66/9m/+AYbBbpzG0mQ=="],
|
"miniflare": ["miniflare@4.20260131.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.18.2", "workerd": "1.20260131.0", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-CtObRzlAzOUpCFH+MgImykxmDNKthrgIYtC+oLC3UGpve6bGLomKUW4u4EorTvzlQFHe66/9m/+AYbBbpzG0mQ=="],
|
||||||
|
|
||||||
|
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||||
|
|
||||||
|
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||||
|
|
||||||
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
|
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
|
||||||
|
|
||||||
|
"module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
|
||||||
|
|
||||||
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||||
|
|
||||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||||
@@ -567,29 +761,57 @@
|
|||||||
|
|
||||||
"node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="],
|
"node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="],
|
||||||
|
|
||||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||||
|
|
||||||
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||||
|
|
||||||
|
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||||
|
|
||||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||||
|
|
||||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||||
|
|
||||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||||
|
|
||||||
|
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||||
|
|
||||||
|
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||||
|
|
||||||
|
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||||
|
|
||||||
|
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||||
|
|
||||||
|
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||||
|
|
||||||
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
|
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
|
||||||
|
|
||||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
|
||||||
|
|
||||||
|
"pg-protocol": ["pg-protocol@1.11.0", "", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="],
|
||||||
|
|
||||||
|
"pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
|
"postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
|
||||||
|
|
||||||
|
"postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
|
||||||
|
|
||||||
|
"postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
|
||||||
|
|
||||||
|
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
||||||
|
|
||||||
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
||||||
|
|
||||||
"promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="],
|
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
|
||||||
|
|
||||||
|
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||||
|
|
||||||
"proxy-target": ["proxy-target@3.0.2", "", {}, "sha512-FFE1XNwXX/FNC3/P8HiKaJSy/Qk68RitG/QEcLy/bVnTAPlgTAWPZKh0pARLAnpfXQPKyalBhk009NRTgsk8vQ=="],
|
"proxy-target": ["proxy-target@3.0.2", "", {}, "sha512-FFE1XNwXX/FNC3/P8HiKaJSy/Qk68RitG/QEcLy/bVnTAPlgTAWPZKh0pARLAnpfXQPKyalBhk009NRTgsk8vQ=="],
|
||||||
|
|
||||||
@@ -601,9 +823,11 @@
|
|||||||
|
|
||||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
|
"recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
|
||||||
|
|
||||||
"regexparam": ["regexparam@3.0.0", "", {}, "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q=="],
|
"regexparam": ["regexparam@3.0.0", "", {}, "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q=="],
|
||||||
|
|
||||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
"require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="],
|
||||||
|
|
||||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||||
|
|
||||||
@@ -621,12 +845,20 @@
|
|||||||
|
|
||||||
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
||||||
|
|
||||||
|
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||||
|
|
||||||
|
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||||
|
|
||||||
|
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||||
|
|
||||||
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
|
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
|
||||||
|
|
||||||
"simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
|
"simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
|
||||||
|
|
||||||
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
||||||
|
|
||||||
|
"sorcery": ["sorcery@1.0.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.14", "minimist": "^1.2.0", "tiny-glob": "^0.2.9" }, "bin": { "sorcery": "bin/sorcery" } }, "sha512-5ay9oJE+7sNmhzl3YNG18jEEEf4AOQCM/FAqR5wMmzqd1FtRorFbJXn3w3SKOhbiQaVgHM+Q1lszZspjri7bpA=="],
|
||||||
|
|
||||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
@@ -635,16 +867,22 @@
|
|||||||
|
|
||||||
"sqlocal": ["sqlocal@0.17.0", "", { "dependencies": { "@sqlite.org/sqlite-wasm": "^3.51.2-build6", "coincident": "^1.2.3" }, "peerDependencies": { "@angular/core": ">=17.0.0", "drizzle-orm": "*", "kysely": "*", "react": ">=18.0.0", "vite": ">=4.0.0", "vue": ">=3.0.0" }, "optionalPeers": ["@angular/core", "drizzle-orm", "kysely", "react", "vite", "vue"] }, "sha512-2t8Hc0Rx0YB539hotqKWXRtWbP9rc+/gRZiUHhyyORXjfPSMsHOXVVjSAr0GplJG34xPfoAf/26tZKL6F9vLow=="],
|
"sqlocal": ["sqlocal@0.17.0", "", { "dependencies": { "@sqlite.org/sqlite-wasm": "^3.51.2-build6", "coincident": "^1.2.3" }, "peerDependencies": { "@angular/core": ">=17.0.0", "drizzle-orm": "*", "kysely": "*", "react": ">=18.0.0", "vite": ">=4.0.0", "vue": ">=3.0.0" }, "optionalPeers": ["@angular/core", "drizzle-orm", "kysely", "react", "vite", "vue"] }, "sha512-2t8Hc0Rx0YB539hotqKWXRtWbP9rc+/gRZiUHhyyORXjfPSMsHOXVVjSAr0GplJG34xPfoAf/26tZKL6F9vLow=="],
|
||||||
|
|
||||||
|
"string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||||
|
|
||||||
|
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||||
|
|
||||||
|
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
"strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
|
"strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
|
||||||
|
|
||||||
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
|
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
|
||||||
|
|
||||||
"supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
"supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||||
|
|
||||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
|
||||||
|
|
||||||
"svelte": ["svelte@5.49.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.2", "esm-env": "^1.2.1", "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-PYLwnngYzyhKzqDlGVlCH4z+NVI8mC0/bTv15vw25CcdOhxENsOHIbQ36oj5DIf3oBazM+STbCAvaskpxtBmWA=="],
|
"svelte": ["svelte@5.49.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.2", "esm-env": "^1.2.1", "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-PYLwnngYzyhKzqDlGVlCH4z+NVI8mC0/bTv15vw25CcdOhxENsOHIbQ36oj5DIf3oBazM+STbCAvaskpxtBmWA=="],
|
||||||
|
|
||||||
"svelte-check": ["svelte-check@4.3.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q=="],
|
"svelte-check": ["svelte-check@4.3.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q=="],
|
||||||
@@ -665,8 +903,14 @@
|
|||||||
|
|
||||||
"tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
"tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
||||||
|
|
||||||
|
"tiny-glob": ["tiny-glob@0.2.9", "", { "dependencies": { "globalyzer": "0.1.0", "globrex": "^0.1.2" } }, "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg=="],
|
||||||
|
|
||||||
|
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
|
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||||
|
|
||||||
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||||
|
|
||||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||||
@@ -685,28 +929,48 @@
|
|||||||
|
|
||||||
"unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
"unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
||||||
|
|
||||||
|
"unplugin": ["unplugin@1.0.1", "", { "dependencies": { "acorn": "^8.8.1", "chokidar": "^3.5.3", "webpack-sources": "^3.2.3", "webpack-virtual-modules": "^0.5.0" } }, "sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA=="],
|
||||||
|
|
||||||
|
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||||
|
|
||||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
|
"vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="],
|
||||||
|
|
||||||
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
||||||
|
|
||||||
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||||
|
|
||||||
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
|
||||||
|
|
||||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||||
|
|
||||||
|
"webpack-sources": ["webpack-sources@3.3.4", "", {}, "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q=="],
|
||||||
|
|
||||||
|
"webpack-virtual-modules": ["webpack-virtual-modules@0.5.0", "", {}, "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw=="],
|
||||||
|
|
||||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||||
|
|
||||||
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
"workerd": ["workerd@1.20260131.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260131.0", "@cloudflare/workerd-darwin-arm64": "1.20260131.0", "@cloudflare/workerd-linux-64": "1.20260131.0", "@cloudflare/workerd-linux-arm64": "1.20260131.0", "@cloudflare/workerd-windows-64": "1.20260131.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-4zZxOdWeActbRfydQQlj7vZ2ay01AjjNC4K3stjmWC3xZHeXeN3EAROwsWE83SZHhtw4rn18srrhtXoQvQMw3Q=="],
|
"workerd": ["workerd@1.20260131.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260131.0", "@cloudflare/workerd-darwin-arm64": "1.20260131.0", "@cloudflare/workerd-linux-64": "1.20260131.0", "@cloudflare/workerd-linux-arm64": "1.20260131.0", "@cloudflare/workerd-windows-64": "1.20260131.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-4zZxOdWeActbRfydQQlj7vZ2ay01AjjNC4K3stjmWC3xZHeXeN3EAROwsWE83SZHhtw4rn18srrhtXoQvQMw3Q=="],
|
||||||
|
|
||||||
"worktop": ["worktop@0.8.0-next.18", "", { "dependencies": { "mrmime": "^2.0.0", "regexparam": "^3.0.0" } }, "sha512-+TvsA6VAVoMC3XDKR5MoC/qlLqDixEfOBysDEKnPIPou/NvoPWCAuXHXMsswwlvmEuvX56lQjvELLyLuzTKvRw=="],
|
"worktop": ["worktop@0.8.0-next.18", "", { "dependencies": { "mrmime": "^2.0.0", "regexparam": "^3.0.0" } }, "sha512-+TvsA6VAVoMC3XDKR5MoC/qlLqDixEfOBysDEKnPIPou/NvoPWCAuXHXMsswwlvmEuvX56lQjvELLyLuzTKvRw=="],
|
||||||
|
|
||||||
"wrangler": ["wrangler@4.62.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.12.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.0", "miniflare": "4.20260131.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260131.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260131.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-DogP9jifqw85g33BqwF6m21YBW5J7+Ep9IJLgr6oqHU0RkA79JMN5baeWXdmnIWZl+VZh6bmtNtR+5/Djd32tg=="],
|
"wrangler": ["wrangler@4.62.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.12.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.0", "miniflare": "4.20260131.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260131.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260131.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-DogP9jifqw85g33BqwF6m21YBW5J7+Ep9IJLgr6oqHU0RkA79JMN5baeWXdmnIWZl+VZh6bmtNtR+5/Djd32tg=="],
|
||||||
|
|
||||||
|
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||||
|
|
||||||
|
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||||
|
|
||||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||||
|
|
||||||
|
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||||
|
|
||||||
|
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
"youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
|
"youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
|
||||||
|
|
||||||
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
|
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
|
||||||
@@ -715,11 +979,35 @@
|
|||||||
|
|
||||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||||
|
|
||||||
|
"@babel/core/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||||
|
|
||||||
|
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"@babel/generator/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||||
|
|
||||||
|
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"@babel/template/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||||
|
|
||||||
|
"@babel/traverse/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||||
|
|
||||||
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
||||||
|
|
||||||
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||||
|
|
||||||
"@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
|
"@opentelemetry/instrumentation-http/@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="],
|
||||||
|
|
||||||
|
"@prisma/instrumentation/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA=="],
|
||||||
|
|
||||||
|
"@sentry/bundler-plugin-core/magic-string": ["magic-string@0.30.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ=="],
|
||||||
|
|
||||||
|
"@sentry/svelte/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
"@sveltejs/kit/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
"@sveltejs/vite-plugin-svelte/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||||
|
|
||||||
@@ -733,18 +1021,38 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"bits-ui/runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="],
|
"bits-ui/runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="],
|
||||||
|
|
||||||
"cross-fetch/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||||
|
|
||||||
"libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
|
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
|
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
|
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"svelte/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
"svelte-toolbelt/runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="],
|
"svelte-toolbelt/runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="],
|
||||||
|
|
||||||
|
"unplugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||||
|
|
||||||
|
"vaul-svelte/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
|
||||||
|
|
||||||
|
"vaul-svelte/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
||||||
|
|
||||||
"vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
|
"vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
|
||||||
|
|
||||||
"wrangler/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="],
|
"wrangler/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="],
|
||||||
|
|
||||||
|
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
|
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
|
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
"youch/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
"youch/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||||
|
|
||||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||||
@@ -791,6 +1099,12 @@
|
|||||||
|
|
||||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||||
|
|
||||||
|
"@prisma/instrumentation/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.207.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ=="],
|
||||||
|
|
||||||
|
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"unplugin/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||||
|
|
||||||
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
|
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
|
||||||
|
|
||||||
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
|
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
|
||||||
@@ -894,5 +1208,11 @@
|
|||||||
"wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ=="],
|
"wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ=="],
|
||||||
|
|
||||||
"wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg=="],
|
"wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg=="],
|
||||||
|
|
||||||
|
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
|
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"unplugin/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -8,8 +8,6 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "bun run build && wrangler dev",
|
"preview": "bun run build && wrangler dev",
|
||||||
"db:push": "drizzle-kit push",
|
"db:push": "drizzle-kit push",
|
||||||
"db:generate": "drizzle-kit generate",
|
|
||||||
"db:migrate": "drizzle-kit migrate",
|
|
||||||
"db:import": "bun run src/lib/db/import-amq.ts",
|
"db:import": "bun run src/lib/db/import-amq.ts",
|
||||||
"format": "biome check --write",
|
"format": "biome check --write",
|
||||||
"prepare": "svelte-kit sync || echo ''",
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
@@ -22,16 +20,16 @@
|
|||||||
"@biomejs/biome": "^2.3.14",
|
"@biomejs/biome": "^2.3.14",
|
||||||
"@internationalized/date": "^3.10.0",
|
"@internationalized/date": "^3.10.0",
|
||||||
"@lucide/svelte": "^0.561.0",
|
"@lucide/svelte": "^0.561.0",
|
||||||
|
"@sentry/sveltekit": "^10",
|
||||||
"@sveltejs/adapter-auto": "^7.0.0",
|
"@sveltejs/adapter-auto": "^7.0.0",
|
||||||
"@sveltejs/adapter-cloudflare": "^7.2.6",
|
"@sveltejs/adapter-cloudflare": "^7.2.6",
|
||||||
"@sveltejs/adapter-node": "^5.5.2",
|
|
||||||
"@sveltejs/kit": "^2.50.1",
|
"@sveltejs/kit": "^2.50.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@types/node": "^25.2.0",
|
"@types/node": "^25.2.0",
|
||||||
|
"better-sqlite3": "^12.6.2",
|
||||||
"bits-ui": "^2.14.4",
|
"bits-ui": "^2.14.4",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dotenv": "^17.2.4",
|
|
||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.8",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"lefthook": "^2.1.0",
|
"lefthook": "^2.1.0",
|
||||||
@@ -44,14 +42,12 @@
|
|||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
|
"vaul-svelte": "^1.0.0-next.7",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"wrangler": "^4.62.0",
|
"wrangler": "^4.62.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"runed@0.37.1": "patches/runed@0.37.1.patch"
|
"runed@0.37.1": "patches/runed@0.37.1.patch"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@libsql/client": "^0.17.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body
|
|
||||||
data-sveltekit-preload-data="hover"
|
<body data-sveltekit-preload-data="hover" class="flex min-h-screen flex-col items-center gap-8">
|
||||||
class="flex min-h-screen flex-col items-center gap-8 p-4 max-sm:px-2"
|
|
||||||
>
|
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
30
src/hooks.client.ts
Normal file
30
src/hooks.client.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { handleErrorWithSentry, replayIntegration } from "@sentry/sveltekit";
|
||||||
|
import * as Sentry from '@sentry/sveltekit';
|
||||||
|
import { PUBLIC_SENTRY_DSN } from "$env/static/public";
|
||||||
|
|
||||||
|
Sentry.init({
|
||||||
|
dsn: PUBLIC_SENTRY_DSN,
|
||||||
|
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
|
||||||
|
// Enable logs to be sent to Sentry
|
||||||
|
enableLogs: true,
|
||||||
|
|
||||||
|
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||||
|
// in development and sample at a lower rate in production
|
||||||
|
replaysSessionSampleRate: 0.1,
|
||||||
|
|
||||||
|
// If the entire session is not sampled, use the below sample rate to sample
|
||||||
|
// sessions when an error occurs.
|
||||||
|
replaysOnErrorSampleRate: 1.0,
|
||||||
|
|
||||||
|
// If you don't want to use Session Replay, just remove the line below:
|
||||||
|
integrations: [replayIntegration()],
|
||||||
|
|
||||||
|
// Enable sending user PII (Personally Identifiable Information)
|
||||||
|
// https://docs.sentry.io/platforms/javascript/guides/sveltekit/configuration/options/#sendDefaultPii
|
||||||
|
sendDefaultPii: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If you have a custom error handler, pass it to `handleErrorWithSentry`
|
||||||
|
export const handleError = handleErrorWithSentry();
|
||||||
@@ -1,6 +1,17 @@
|
|||||||
|
import { sequence } from "@sveltejs/kit/hooks";
|
||||||
|
import * as Sentry from "@sentry/sveltekit";
|
||||||
|
import { initCloudflareSentryHandle } from "@sentry/sveltekit";
|
||||||
|
import { PUBLIC_SENTRY_DSN } from "$env/static/public";
|
||||||
import type { Handle } from "@sveltejs/kit";
|
import type { Handle } from "@sveltejs/kit";
|
||||||
|
|
||||||
export const handle: Handle = async ({ event, resolve }) => {
|
export const handle: Handle = sequence(
|
||||||
|
initCloudflareSentryHandle({
|
||||||
|
dsn: PUBLIC_SENTRY_DSN,
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
enableLogs: true,
|
||||||
|
}),
|
||||||
|
Sentry.sentryHandle(),
|
||||||
|
async ({ event, resolve }) => {
|
||||||
const response = await resolve(event);
|
const response = await resolve(event);
|
||||||
|
|
||||||
// https://sqlocal.dev/guide/setup#cross-origin-isolation
|
// https://sqlocal.dev/guide/setup#cross-origin-isolation
|
||||||
@@ -12,4 +23,5 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
response.headers.set("Cross-Origin-Resource-Policy", "same-origin");
|
response.headers.set("Cross-Origin-Resource-Policy", "same-origin");
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
});
|
||||||
|
export const handleError = Sentry.handleErrorWithSentry();
|
||||||
BIN
src/lib/assets/artwork.jpg
Normal file
BIN
src/lib/assets/artwork.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
File diff suppressed because it is too large
Load Diff
@@ -6,14 +6,9 @@
|
|||||||
SkipForward,
|
SkipForward,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "@lucide/svelte";
|
} from "@lucide/svelte";
|
||||||
import {
|
import { player } from "$lib/player/store.svelte";
|
||||||
addToQueue,
|
|
||||||
hasTrack,
|
|
||||||
play,
|
|
||||||
playNext,
|
|
||||||
removeTrack,
|
|
||||||
} from "$lib/player/player.svelte";
|
|
||||||
import { type SongType, trackFromSongRow } from "$lib/player/types";
|
import { type SongType, trackFromSongRow } from "$lib/player/types";
|
||||||
|
import { songTypeNumberLabel } from "$lib/utils/amq";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
|
|
||||||
type SongEntryProps = {
|
type SongEntryProps = {
|
||||||
@@ -24,7 +19,9 @@
|
|||||||
songName: string;
|
songName: string;
|
||||||
artistName: string | null;
|
artistName: string | null;
|
||||||
fileName?: string | null;
|
fileName?: string | null;
|
||||||
globalPercent: number | null;
|
globalPercent: number;
|
||||||
|
dub: boolean;
|
||||||
|
rebroadcast: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -36,16 +33,11 @@
|
|||||||
artistName,
|
artistName,
|
||||||
fileName = null,
|
fileName = null,
|
||||||
globalPercent,
|
globalPercent,
|
||||||
|
dub,
|
||||||
|
rebroadcast,
|
||||||
}: SongEntryProps = $props();
|
}: SongEntryProps = $props();
|
||||||
|
|
||||||
const typeLabelMap: Record<number, string> = {
|
const displayTypeNumber = $derived(songTypeNumberLabel(type, number));
|
||||||
1: "OP",
|
|
||||||
2: "ED",
|
|
||||||
3: "INS",
|
|
||||||
};
|
|
||||||
|
|
||||||
const typeLabel = $derived(typeLabelMap[type] ?? `T${type}`);
|
|
||||||
const displayTypeNumber = $derived(`${typeLabel}${number || ""}`);
|
|
||||||
|
|
||||||
const artistDisplay = $derived.by(
|
const artistDisplay = $derived.by(
|
||||||
() => artistName?.trim() || "Unknown Artist",
|
() => artistName?.trim() || "Unknown Artist",
|
||||||
@@ -60,10 +52,13 @@
|
|||||||
songName,
|
songName,
|
||||||
artistName,
|
artistName,
|
||||||
fileName,
|
fileName,
|
||||||
|
dub,
|
||||||
|
rebroadcast,
|
||||||
|
globalPercent,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const isQueued = $derived(hasTrack(annSongId));
|
const isQueued = $derived(player.hasTrack(annSongId));
|
||||||
|
|
||||||
function requestGlobalAutoplay() {
|
function requestGlobalAutoplay() {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
@@ -91,12 +86,16 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-wrap w-fit items-baseline gap-x-2 gap-y-1">
|
<div class="flex flex-wrap w-fit items-baseline gap-x-2 gap-y-1">
|
||||||
{animeName}
|
{animeName}
|
||||||
<span class="rounded bg-muted px-2 py-0.5 text-sm text-muted-foreground"
|
<span class="tag">{displayTypeNumber}</span>
|
||||||
>{displayTypeNumber}</span
|
|
||||||
>
|
|
||||||
<span class="text-muted-foreground">
|
<span class="text-muted-foreground">
|
||||||
{globalPercent}%
|
{globalPercent}%
|
||||||
</span>
|
</span>
|
||||||
|
{#if dub}
|
||||||
|
<span title="Dub" class="tag">DUB</span>
|
||||||
|
{/if}
|
||||||
|
{#if rebroadcast}
|
||||||
|
<span title="Rebroadcast" class="tag">RB</span>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-1 w-fit text-foreground/80">
|
<div class="mt-1 w-fit text-foreground/80">
|
||||||
@@ -115,7 +114,7 @@
|
|||||||
class="btn-icon"
|
class="btn-icon"
|
||||||
title="Remove from queue"
|
title="Remove from queue"
|
||||||
aria-label="Remove from queue"
|
aria-label="Remove from queue"
|
||||||
onclick={() => removeTrack(annSongId)}
|
onclick={() => player.remove(annSongId)}
|
||||||
>
|
>
|
||||||
<Trash2 class="icon-btn" />
|
<Trash2 class="icon-btn" />
|
||||||
</button>
|
</button>
|
||||||
@@ -128,7 +127,7 @@
|
|||||||
aria-label="Play"
|
aria-label="Play"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
if (!track) return;
|
if (!track) return;
|
||||||
play(track);
|
player.add(track, true);
|
||||||
requestGlobalAutoplay();
|
requestGlobalAutoplay();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -143,7 +142,7 @@
|
|||||||
aria-label="Play next"
|
aria-label="Play next"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
if (!track) return;
|
if (!track) return;
|
||||||
playNext(track);
|
player.playNext(track);
|
||||||
requestGlobalAutoplay();
|
requestGlobalAutoplay();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -158,7 +157,7 @@
|
|||||||
aria-label="Add to queue"
|
aria-label="Add to queue"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
if (!track) return;
|
if (!track) return;
|
||||||
addToQueue(track);
|
player.add(track);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListPlus class="icon-btn" />
|
<ListPlus class="icon-btn" />
|
||||||
|
|||||||
97
src/lib/components/player/Controls.svelte
Normal file
97
src/lib/components/player/Controls.svelte
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Pause,
|
||||||
|
Play,
|
||||||
|
Repeat,
|
||||||
|
Repeat1,
|
||||||
|
Shuffle,
|
||||||
|
SkipBack,
|
||||||
|
SkipForward,
|
||||||
|
} from "@lucide/svelte";
|
||||||
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import { player } from "$lib/player/store.svelte";
|
||||||
|
import { getAudioContext } from "./ctx.svelte";
|
||||||
|
|
||||||
|
let audio = getAudioContext();
|
||||||
|
|
||||||
|
// Derived state for icons/labels
|
||||||
|
let isPlaying = $derived(!audio.paused);
|
||||||
|
let shuffleMode = $derived(player.isShuffled);
|
||||||
|
let repeatMode = $derived(player.repeatMode);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<!-- Shuffle -->
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class={shuffleMode
|
||||||
|
? "text-primary hover:bg-primary/20 hover:text-primary"
|
||||||
|
: "text-muted-foreground"}
|
||||||
|
onclick={() => player.toggleShuffle()}
|
||||||
|
title="Toggle Shuffle"
|
||||||
|
>
|
||||||
|
<Shuffle class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Prev -->
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onclick={() => {
|
||||||
|
if (audio.currentTime > 3) {
|
||||||
|
audio.seek(0);
|
||||||
|
} else {
|
||||||
|
player.prev();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={player.history.length <= 1 && audio.currentTime <= 3}
|
||||||
|
title="Previous"
|
||||||
|
>
|
||||||
|
<SkipBack class="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Play/Pause -->
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
class="h-10 w-10 rounded-full"
|
||||||
|
onclick={() => audio.toggle()}
|
||||||
|
disabled={!player.currentTrack}
|
||||||
|
title={isPlaying ? "Pause" : "Play"}
|
||||||
|
>
|
||||||
|
{#if isPlaying}
|
||||||
|
<Pause class="h-5 w-5" />
|
||||||
|
{:else}
|
||||||
|
<Play class="h-5 w-5 ml-0.5" />
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Next -->
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onclick={() => player.next()}
|
||||||
|
disabled={player.queue.length === 0}
|
||||||
|
title="Next"
|
||||||
|
>
|
||||||
|
<SkipForward class="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Repeat -->
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class={repeatMode !== "off"
|
||||||
|
? "text-primary hover:bg-primary/20 hover:text-primary"
|
||||||
|
: "text-muted-foreground"}
|
||||||
|
onclick={() => player.toggleRepeat()}
|
||||||
|
title="Toggle Repeat"
|
||||||
|
>
|
||||||
|
{#if repeatMode === "one"}
|
||||||
|
<Repeat1 class="h-4 w-4" />
|
||||||
|
{:else}
|
||||||
|
<Repeat class="h-4 w-4" />
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
117
src/lib/components/player/PlayerDesktop.svelte
Normal file
117
src/lib/components/player/PlayerDesktop.svelte
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Disc, Volume1, Volume2, VolumeX } from "@lucide/svelte";
|
||||||
|
|
||||||
|
import { player } from "$lib/player/store.svelte";
|
||||||
|
import { songTypeNumberLabel } from "$lib/utils/amq";
|
||||||
|
import Controls from "./Controls.svelte";
|
||||||
|
import { getAudioContext } from "./ctx.svelte";
|
||||||
|
import Queue from "./Queue.svelte";
|
||||||
|
import { formatTime } from "./utils";
|
||||||
|
|
||||||
|
const audio = getAudioContext();
|
||||||
|
|
||||||
|
const displayTypeNumber = $derived(
|
||||||
|
player.currentTrack
|
||||||
|
? songTypeNumberLabel(
|
||||||
|
player.currentTrack.type,
|
||||||
|
player.currentTrack.number,
|
||||||
|
)
|
||||||
|
: "",
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="h-full flex flex-col border-l bg-background/50 backdrop-blur w-full"
|
||||||
|
>
|
||||||
|
{#if player.currentTrack}
|
||||||
|
<div class="p-4 space-y-4 shrink-0">
|
||||||
|
<!-- Track Info -->
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<h2 class="text-lg font-bold leading-tight">
|
||||||
|
{player.currentTrack.animeName}
|
||||||
|
<span class="tag">{displayTypeNumber}</span>
|
||||||
|
</h2>
|
||||||
|
<p class="text-foreground/80 font-medium text-l">
|
||||||
|
{player.currentTrack.title}
|
||||||
|
<span class="text-muted-foreground"> · </span>
|
||||||
|
<span class="text-muted-foreground"
|
||||||
|
>{player.currentTrack.globalPercent}%</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
{player.currentTrack.artist}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
bind:value={audio.currentTime}
|
||||||
|
max={audio.duration || 100}
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="flex justify-between text-xs text-muted-foreground font-variant-numeric tabular-nums px-1"
|
||||||
|
>
|
||||||
|
<span>{formatTime(audio.currentTime)}</span>
|
||||||
|
<span>{formatTime(audio.duration)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls -->
|
||||||
|
<div class="flex justify-center gap-4 divide-x divide-accent">
|
||||||
|
<Controls />
|
||||||
|
<!-- Volume -->
|
||||||
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
|
<button
|
||||||
|
onclick={() => player.toggleMute()}
|
||||||
|
class="text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
title={player.isMuted ? "Unmute" : "Mute"}
|
||||||
|
>
|
||||||
|
{#if player.isMuted || player.volume === 0}
|
||||||
|
<VolumeX class="h-4 w-4" />
|
||||||
|
{:else if player.volume < 0.5}
|
||||||
|
<Volume1 class="h-4 w-4" />
|
||||||
|
{:else}
|
||||||
|
<Volume2 class="h-4 w-4" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
bind:value={player.volume}
|
||||||
|
max={1}
|
||||||
|
step={0.05}
|
||||||
|
class="flex-1 min-w-0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<div class="h-px bg-border mx-6"></div>
|
||||||
|
|
||||||
|
<!-- Queue (Scrollable) -->
|
||||||
|
<div class="flex-1 overflow-hidden relative p-4">
|
||||||
|
<div class="absolute inset-0 p-4 pt-0">
|
||||||
|
<div class="h-full overflow-hidden rounded-lg border bg-muted/20">
|
||||||
|
<Queue visible={!!player.currentTrack} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="flex-1 flex flex-col items-center justify-center text-muted-foreground gap-4 p-8 text-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="h-16 w-16 rounded-full bg-muted flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Disc class="h-8 w-8 opacity-50" />
|
||||||
|
</div>
|
||||||
|
<p>No track playing</p>
|
||||||
|
<p class="text-xs max-w-xs opacity-70">
|
||||||
|
Pick a song from the library to start listening.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
116
src/lib/components/player/PlayerMobile.svelte
Normal file
116
src/lib/components/player/PlayerMobile.svelte
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Disc } from "@lucide/svelte";
|
||||||
|
import * as Drawer from "$lib/components/ui/drawer";
|
||||||
|
import { player } from "$lib/player/store.svelte";
|
||||||
|
import { songTypeNumberLabel } from "$lib/utils/amq";
|
||||||
|
import Controls from "./Controls.svelte";
|
||||||
|
import { getAudioContext } from "./ctx.svelte";
|
||||||
|
import Queue from "./Queue.svelte";
|
||||||
|
import { formatTime } from "./utils";
|
||||||
|
|
||||||
|
const audio = getAudioContext();
|
||||||
|
let open = $state(false);
|
||||||
|
|
||||||
|
const displayTypeNumber = $derived(
|
||||||
|
player.currentTrack
|
||||||
|
? songTypeNumberLabel(
|
||||||
|
player.currentTrack.type,
|
||||||
|
player.currentTrack.number,
|
||||||
|
)
|
||||||
|
: "",
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="z-50 border-t bg-background/95 backdrop-blur shadow-2xl">
|
||||||
|
<div class="px-4 py-2 flex items-center justify-between gap-4 h-16">
|
||||||
|
<!-- Mini Player Info -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex items-center gap-3 overflow-hidden flex-1 text-left bg-transparent border-none p-0 cursor-pointer"
|
||||||
|
onclick={() => (open = true)}
|
||||||
|
>
|
||||||
|
<div class="flex flex-col overflow-hidden">
|
||||||
|
<div class="text-sm font-medium truncate leading-tight">
|
||||||
|
{player.currentTrack?.animeName || "Unknown"}
|
||||||
|
<span class="tag">{displayTypeNumber}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-muted-foreground truncate leading-tight">
|
||||||
|
{player.currentTrack?.title || "Unknown Title"}
|
||||||
|
<span class="text-muted-foreground"> · </span>
|
||||||
|
<span class="text-muted-foreground"
|
||||||
|
>{player.currentTrack?.globalPercent}%</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Mini Controls -->
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Controls />
|
||||||
|
<!-- Actually Controls has too many buttons for mini player. Just Play/Next? -->
|
||||||
|
<!-- We'll reimplement mini controls or pass props to Controls to show fewer buttons -->
|
||||||
|
<!-- Let's just use simplified controls here for now, or just Play/Pause -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Bar (thin line at top of bar) -->
|
||||||
|
<div class="absolute top-0 left-0 right-0 h-1 bg-muted">
|
||||||
|
<div
|
||||||
|
class="h-full bg-primary transition-all duration-100 ease-linear"
|
||||||
|
style="width: {(audio.currentTime / audio.duration) * 100}%"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Drawer.Root bind:open>
|
||||||
|
<Drawer.Content class="h-[96dvh] flex flex-col rounded-t-[10px]">
|
||||||
|
<div class="mx-auto w-full max-w-xl flex-1 flex flex-col p-4 gap-4">
|
||||||
|
<!-- Track Info -->
|
||||||
|
<div class="text-center space-y-1">
|
||||||
|
<h2 class="text-xl font-bold leading-tight">
|
||||||
|
{player.currentTrack?.animeName}
|
||||||
|
<span class="tag">{displayTypeNumber}</span>
|
||||||
|
</h2>
|
||||||
|
<p class="text-foreground/80 font-medium text-lg line-clamp-1">
|
||||||
|
{player.currentTrack?.title}
|
||||||
|
<span class="text-muted-foreground"> · </span>
|
||||||
|
<span class="text-muted-foreground"
|
||||||
|
>{player.currentTrack?.globalPercent}%</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
{player.currentTrack?.artist}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
bind:value={audio.currentTime}
|
||||||
|
max={audio.duration || 100}
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="flex justify-between text-xs text-muted-foreground font-variant-numeric tabular-nums"
|
||||||
|
>
|
||||||
|
<span>{formatTime(audio.currentTime)}</span>
|
||||||
|
<span>{formatTime(audio.duration)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Controls -->
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<Controls />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Volume? Or Queue toggle? -->
|
||||||
|
<!-- Queue -->
|
||||||
|
<div class="flex-1 overflow-hidden relative mt-auto">
|
||||||
|
<div class="absolute inset-0">
|
||||||
|
<Queue visible={open} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Drawer.Content>
|
||||||
|
</Drawer.Root>
|
||||||
117
src/lib/components/player/PlayerRoot.svelte
Normal file
117
src/lib/components/player/PlayerRoot.svelte
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount, setContext } from "svelte";
|
||||||
|
import artwork from "$lib/assets/artwork.jpg";
|
||||||
|
import { player } from "$lib/player/store.svelte";
|
||||||
|
import { songTypeNumberLabel } from "$lib/utils/amq";
|
||||||
|
import { AudioContext } from "./ctx.svelte";
|
||||||
|
import PlayerDesktop from "./PlayerDesktop.svelte";
|
||||||
|
import PlayerMobile from "./PlayerMobile.svelte";
|
||||||
|
|
||||||
|
// Initialize context
|
||||||
|
const audioCtx = new AudioContext();
|
||||||
|
setContext("amqtrain:player:audio-ctx", audioCtx);
|
||||||
|
|
||||||
|
let audioEl: HTMLAudioElement;
|
||||||
|
|
||||||
|
import { loadState, saveState } from "$lib/player/persist";
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
audioCtx.setElement(audioEl);
|
||||||
|
|
||||||
|
// Load state
|
||||||
|
const saved = loadState();
|
||||||
|
if (saved) {
|
||||||
|
player.init(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup MediaSession actions
|
||||||
|
if ("mediaSession" in navigator) {
|
||||||
|
navigator.mediaSession.setActionHandler("play", () => audioCtx.play());
|
||||||
|
navigator.mediaSession.setActionHandler("pause", () => audioCtx.pause());
|
||||||
|
navigator.mediaSession.setActionHandler("previoustrack", () =>
|
||||||
|
player.prev(),
|
||||||
|
);
|
||||||
|
navigator.mediaSession.setActionHandler("nexttrack", () => player.next());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update MediaSession metadata when track changes
|
||||||
|
$effect(() => {
|
||||||
|
const track = player.currentTrack;
|
||||||
|
if ("mediaSession" in navigator) {
|
||||||
|
if (track) {
|
||||||
|
const typeLabel = songTypeNumberLabel(track.type, track.number);
|
||||||
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
|
title: `${track.animeName} (${typeLabel}) — ${track.title}`,
|
||||||
|
artist: track.artist,
|
||||||
|
album: track.animeName ?? "",
|
||||||
|
artwork: [{ src: artwork }],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
navigator.mediaSession.metadata = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const track = player.currentTrack;
|
||||||
|
if (audioEl) {
|
||||||
|
if (track) {
|
||||||
|
const newSrc = track.src;
|
||||||
|
const currentSrc = audioEl.currentSrc;
|
||||||
|
|
||||||
|
if (currentSrc !== newSrc) {
|
||||||
|
audioEl.src = newSrc;
|
||||||
|
audioEl.play().catch((e) => {
|
||||||
|
console.warn("Autoplay blocked or failed", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
audioEl.removeAttribute("src");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update MediaSession playback state
|
||||||
|
if ("mediaSession" in navigator) {
|
||||||
|
navigator.mediaSession.playbackState = audioEl.paused
|
||||||
|
? "paused"
|
||||||
|
: "playing";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ... existing callbacks ...
|
||||||
|
|
||||||
|
// Bindings and Event Listeners
|
||||||
|
function onEnded() {
|
||||||
|
player.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync MediaSession playback state with bound paused state
|
||||||
|
$effect(() => {
|
||||||
|
if ("mediaSession" in navigator) {
|
||||||
|
navigator.mediaSession.playbackState = audioCtx.paused
|
||||||
|
? "paused"
|
||||||
|
: "playing";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<audio
|
||||||
|
bind:this={audioEl}
|
||||||
|
bind:currentTime={audioCtx.currentTime}
|
||||||
|
bind:duration={audioCtx.duration}
|
||||||
|
bind:paused={audioCtx.paused}
|
||||||
|
bind:volume={player.volume}
|
||||||
|
bind:muted={player.isMuted}
|
||||||
|
onended={onEnded}
|
||||||
|
class="hidden"
|
||||||
|
></audio>
|
||||||
|
|
||||||
|
<div class="contents">
|
||||||
|
<div class="lg:hidden w-full sticky bottom-0">
|
||||||
|
<PlayerMobile />
|
||||||
|
</div>
|
||||||
|
<div class="hidden lg:block sticky top-12 h-[calc(100dvh-4rem)]">
|
||||||
|
<PlayerDesktop />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
295
src/lib/components/player/Queue.svelte
Normal file
295
src/lib/components/player/Queue.svelte
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Download,
|
||||||
|
GripVertical,
|
||||||
|
LocateFixed,
|
||||||
|
Play,
|
||||||
|
Upload,
|
||||||
|
X,
|
||||||
|
} from "@lucide/svelte";
|
||||||
|
import { tick } from "svelte";
|
||||||
|
import { z } from "zod";
|
||||||
|
import * as AlertDialog from "$lib/components/ui/alert-dialog";
|
||||||
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import VirtualList from "$lib/components/ui/VirtualList.svelte";
|
||||||
|
import { trackSchema } from "$lib/player/persist";
|
||||||
|
import { player } from "$lib/player/store.svelte";
|
||||||
|
import type { Track } from "$lib/player/types";
|
||||||
|
import { songTypeNumberLabel } from "$lib/utils/amq";
|
||||||
|
|
||||||
|
let { visible = true }: { visible?: boolean } = $props();
|
||||||
|
|
||||||
|
let virtualList: ReturnType<typeof VirtualList>;
|
||||||
|
|
||||||
|
function scrollToCurrentlyPlaying() {
|
||||||
|
if (player.currentId == null) return;
|
||||||
|
const index = player.displayQueue.findIndex(
|
||||||
|
(t) => t.id === player.currentId,
|
||||||
|
);
|
||||||
|
if (index !== -1) virtualList?.scrollToIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (visible) {
|
||||||
|
tick().then(() => scrollToCurrentlyPlaying());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ITEM_HEIGHT = 64;
|
||||||
|
|
||||||
|
function onRemove(id: number) {
|
||||||
|
player.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onJump(track: Track) {
|
||||||
|
player.playId(track.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dragOverIndex = $state<number | null>(null);
|
||||||
|
|
||||||
|
function onDragStart(e: DragEvent, index: number) {
|
||||||
|
if (!e.dataTransfer) return;
|
||||||
|
e.dataTransfer.effectAllowed = "move";
|
||||||
|
e.dataTransfer.setData("text/plain", index.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragOver(e: DragEvent, index: number) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
||||||
|
dragOverIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDrop(e: DragEvent, toIndex: number) {
|
||||||
|
e.preventDefault();
|
||||||
|
dragOverIndex = null;
|
||||||
|
const fromIndexStr = e.dataTransfer?.getData("text/plain");
|
||||||
|
if (fromIndexStr) {
|
||||||
|
const fromIndex = parseInt(fromIndexStr, 10);
|
||||||
|
player.move(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportQueue() {
|
||||||
|
const dataStr = JSON.stringify(player.queue, null, 2);
|
||||||
|
const dataUri =
|
||||||
|
"data:application/json;charset=utf-8," + encodeURIComponent(dataStr);
|
||||||
|
const exportFileDefaultName = "amq-queue.json";
|
||||||
|
|
||||||
|
const linkElement = document.createElement("a");
|
||||||
|
linkElement.setAttribute("href", dataUri);
|
||||||
|
linkElement.setAttribute("download", exportFileDefaultName);
|
||||||
|
linkElement.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileInput: HTMLInputElement;
|
||||||
|
|
||||||
|
function importQueue(e: Event) {
|
||||||
|
const input = e.target as HTMLInputElement;
|
||||||
|
if (!input.files?.length) return;
|
||||||
|
|
||||||
|
const file = input.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (event) => {
|
||||||
|
try {
|
||||||
|
const content = event.target?.result as string;
|
||||||
|
const parsed = JSON.parse(content);
|
||||||
|
const result = z.array(trackSchema).safeParse(parsed);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
player.clearQueue();
|
||||||
|
player.isShuffled = false;
|
||||||
|
player.repeatMode = "off";
|
||||||
|
player.addAll(result.data);
|
||||||
|
} else {
|
||||||
|
console.error("Invalid queue format", result.error);
|
||||||
|
alert("Failed to import: Invalid queue format.");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error reading file", err);
|
||||||
|
alert("Failed to read file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset input so the same file can be selected again
|
||||||
|
input.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-col h-full w-full bg-background/50 backdrop-blur rounded-lg border overflow-hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="px-4 py-3 border-b flex text-sm items-center justify-between bg-muted/20"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<h3 class="font-semibold">Up Next</h3>
|
||||||
|
{#if player.displayQueue.length > 0}
|
||||||
|
<span class="text-muted-foreground font-normal ml-1"
|
||||||
|
>({player.displayQueue.length})</span
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="h-6 w-6 p-0"
|
||||||
|
aria-label="Scroll to currently playing"
|
||||||
|
onclick={scrollToCurrentlyPlaying}
|
||||||
|
>
|
||||||
|
<LocateFixed class="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1 text-muted-foreground pl-2">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
bind:this={fileInput}
|
||||||
|
onchange={importQueue}
|
||||||
|
class="hidden"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="h-6 w-6 p-0"
|
||||||
|
aria-label="Import Queue"
|
||||||
|
onclick={() => fileInput?.click()}
|
||||||
|
title="Import Queue"
|
||||||
|
>
|
||||||
|
<Upload class="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="h-6 w-6 p-0"
|
||||||
|
aria-label="Export Queue"
|
||||||
|
onclick={exportQueue}
|
||||||
|
disabled={player.queue.length === 0}
|
||||||
|
title="Export Queue"
|
||||||
|
>
|
||||||
|
<Download class="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
<AlertDialog.Root>
|
||||||
|
<AlertDialog.Trigger>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="h-6 w-6 p-0"
|
||||||
|
{...props}
|
||||||
|
title="Clear Queue"
|
||||||
|
>
|
||||||
|
<X class="h-3 w-3" aria-label="Clear" />
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
</AlertDialog.Trigger>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Header>
|
||||||
|
<AlertDialog.Title>Clear queue?</AlertDialog.Title>
|
||||||
|
<AlertDialog.Description>
|
||||||
|
This will remove all {player.displayQueue.length} songs from the queue.
|
||||||
|
</AlertDialog.Description>
|
||||||
|
</AlertDialog.Header>
|
||||||
|
<AlertDialog.Footer>
|
||||||
|
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
|
||||||
|
<AlertDialog.Action onclick={() => player.clearQueue()}
|
||||||
|
>Clear</AlertDialog.Action
|
||||||
|
>
|
||||||
|
</AlertDialog.Footer>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog.Root>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VirtualList
|
||||||
|
bind:this={virtualList}
|
||||||
|
items={player.displayQueue}
|
||||||
|
itemHeight={ITEM_HEIGHT}
|
||||||
|
overscan={5}
|
||||||
|
class="p-2"
|
||||||
|
key={(track) => track.id}
|
||||||
|
>
|
||||||
|
{#snippet row({ item: track, index: i })}
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
draggable="true"
|
||||||
|
ondragstart={(e) => onDragStart(e, i)}
|
||||||
|
ondragover={(e) => onDragOver(e, i)}
|
||||||
|
ondrop={(e) => onDrop(e, i)}
|
||||||
|
onclick={() => onJump(track)}
|
||||||
|
onkeydown={(e) => e.key === "Enter" && onJump(track)}
|
||||||
|
class="group flex items-center gap-2 px-3 h-full rounded-md hover:bg-muted/50 transition-colors cursor-pointer text-sm"
|
||||||
|
class:active={player.currentId === track.id}
|
||||||
|
class:border-t-2={dragOverIndex === i}
|
||||||
|
class:border-primary={dragOverIndex === i}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-6 shrink-0 flex items-center justify-center text-xs text-muted-foreground/60 font-mono"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="group-hover:hidden flex items-center justify-center w-full h-full"
|
||||||
|
>
|
||||||
|
{#if player.currentId === track.id}
|
||||||
|
<div class="w-2 h-2 bg-primary rounded-full animate-pulse"></div>
|
||||||
|
{:else}
|
||||||
|
<span>{i + 1}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="[@media(hover:hover)]:hidden group-hover:flex items-center justify-center w-full h-full cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-foreground"
|
||||||
|
aria-label="Drag to reorder"
|
||||||
|
>
|
||||||
|
<GripVertical class="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div
|
||||||
|
class="font-medium line-clamp-2 leading-tight"
|
||||||
|
class:text-primary={player.currentId === track.id}
|
||||||
|
>
|
||||||
|
{track.animeName}
|
||||||
|
<span class="tag"
|
||||||
|
>{songTypeNumberLabel(track.type, track.number)}</span
|
||||||
|
>
|
||||||
|
<span class="text-muted-foreground font-normal"
|
||||||
|
>{track.globalPercent}%</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-foreground/80 truncate">
|
||||||
|
{track.title} —
|
||||||
|
<span class="text-muted-foreground">{track.artist}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="h-6 w-6 opacity-50 group-hover:opacity-100 transition-opacity shrink-0"
|
||||||
|
onclick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onRemove(track.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X class="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet empty()}
|
||||||
|
<div class="text-center py-8 text-muted-foreground text-sm">
|
||||||
|
Queue is empty
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</VirtualList>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@reference "../../../routes/layout.css";
|
||||||
|
.active {
|
||||||
|
@apply bg-muted/40;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
46
src/lib/components/player/ctx.svelte.ts
Normal file
46
src/lib/components/player/ctx.svelte.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { getContext, setContext } from "svelte";
|
||||||
|
|
||||||
|
const AUDIO_CTX_KEY = "amqtrain:player:audio-ctx";
|
||||||
|
|
||||||
|
export class AudioContext {
|
||||||
|
currentTime = $state(0);
|
||||||
|
duration = $state(0);
|
||||||
|
paused = $state(true);
|
||||||
|
|
||||||
|
private audioEl: HTMLAudioElement | null = null;
|
||||||
|
|
||||||
|
setElement(el: HTMLAudioElement) {
|
||||||
|
this.audioEl = el;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bindings will handle state updates, but we need methods to control play/pause
|
||||||
|
// from other components.
|
||||||
|
// Since we bind to `this.paused`, toggling it here will trigger the audio element.
|
||||||
|
|
||||||
|
play() {
|
||||||
|
this.paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pause() {
|
||||||
|
this.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.paused = !this.paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
seek(time: number) {
|
||||||
|
// Seeking is done by updating currentTime, which is bound to the audio element.
|
||||||
|
this.currentTime = Math.max(0, Math.min(time, this.duration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setAudioContext() {
|
||||||
|
const ctx = new AudioContext();
|
||||||
|
setContext(AUDIO_CTX_KEY, ctx);
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAudioContext() {
|
||||||
|
return getContext<AudioContext>(AUDIO_CTX_KEY);
|
||||||
|
}
|
||||||
7
src/lib/components/player/utils.ts
Normal file
7
src/lib/components/player/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export function formatTime(seconds: number) {
|
||||||
|
if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
|
||||||
|
const s = Math.floor(seconds);
|
||||||
|
const m = Math.floor(s / 60);
|
||||||
|
const r = s % 60;
|
||||||
|
return `${m}:${String(r).padStart(2, "0")}`;
|
||||||
|
}
|
||||||
109
src/lib/components/ui/VirtualList.svelte
Normal file
109
src/lib/components/ui/VirtualList.svelte
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<!--
|
||||||
|
Generic fixed-height virtual list.
|
||||||
|
Usage:
|
||||||
|
<VirtualList items={myArray} itemHeight={64} overscan={5}>
|
||||||
|
{#snippet row({ item, index })}
|
||||||
|
<div>…</div>
|
||||||
|
{/snippet}
|
||||||
|
</VirtualList>
|
||||||
|
-->
|
||||||
|
<script lang="ts" generics="T">
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
items: T[];
|
||||||
|
itemHeight: number;
|
||||||
|
overscan?: number;
|
||||||
|
class?: string;
|
||||||
|
row: Snippet<[{ item: T; index: number }]>;
|
||||||
|
empty?: Snippet;
|
||||||
|
key?: (item: T, index: number) => unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
items,
|
||||||
|
itemHeight,
|
||||||
|
overscan = 5,
|
||||||
|
class: className = "",
|
||||||
|
row,
|
||||||
|
empty,
|
||||||
|
key,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let containerEl = $state<HTMLDivElement | null>(null);
|
||||||
|
let scrollTop = $state(0);
|
||||||
|
let containerHeight = $state(0);
|
||||||
|
|
||||||
|
const totalHeight = $derived(items.length * itemHeight);
|
||||||
|
|
||||||
|
const startIndex = $derived(
|
||||||
|
Math.max(0, Math.floor(scrollTop / itemHeight) - overscan),
|
||||||
|
);
|
||||||
|
const endIndex = $derived(
|
||||||
|
Math.min(
|
||||||
|
items.length,
|
||||||
|
Math.ceil((scrollTop + containerHeight) / itemHeight) + overscan,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const visibleItems = $derived(
|
||||||
|
items.slice(startIndex, endIndex).map((item, i) => ({
|
||||||
|
item,
|
||||||
|
index: startIndex + i,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
function onScroll(e: Event) {
|
||||||
|
scrollTop = (e.target as HTMLDivElement).scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scrollToIndex(index: number) {
|
||||||
|
if (!containerEl) return;
|
||||||
|
containerEl.scrollTop = Math.max(
|
||||||
|
0,
|
||||||
|
index * itemHeight - containerHeight / 2 + itemHeight / 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!containerEl) return;
|
||||||
|
const ro = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
containerHeight = entry.contentRect.height;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ro.observe(containerEl);
|
||||||
|
return () => ro.disconnect();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="virtual-list-container {className}"
|
||||||
|
bind:this={containerEl}
|
||||||
|
onscroll={onScroll}
|
||||||
|
>
|
||||||
|
{#if items.length === 0}
|
||||||
|
{@render empty?.()}
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="virtual-list-sentinel"
|
||||||
|
style="height: {totalHeight}px; position: relative;"
|
||||||
|
>
|
||||||
|
{#each visibleItems as entry (key ? key(entry.item, entry.index) : entry.index)}
|
||||||
|
<div
|
||||||
|
class="virtual-list-item"
|
||||||
|
style="position: absolute; top: {entry.index *
|
||||||
|
itemHeight}px; left: 0; right: 0; height: {itemHeight}px;"
|
||||||
|
>
|
||||||
|
{@render row(entry)}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.virtual-list-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
src/lib/components/ui/chip-group/chip-group.svelte
Normal file
45
src/lib/components/ui/chip-group/chip-group.svelte
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils";
|
||||||
|
|
||||||
|
type Item = {
|
||||||
|
label: string;
|
||||||
|
value: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
label,
|
||||||
|
items,
|
||||||
|
value = $bindable(),
|
||||||
|
type = "checkbox",
|
||||||
|
class: className,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
label?: string;
|
||||||
|
items: Item[];
|
||||||
|
value: any;
|
||||||
|
type?: "checkbox" | "radio";
|
||||||
|
class?: string;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("flex flex-col gap-2", className)}>
|
||||||
|
{#if label}
|
||||||
|
<span class="scn-label">{label}</span>
|
||||||
|
{/if}
|
||||||
|
<div class="chip-group" {...rest}>
|
||||||
|
{#each items as item}
|
||||||
|
<label class="chip">
|
||||||
|
{#if type === "checkbox"}
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:group={value}
|
||||||
|
value={item.value}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<input type="radio" bind:group={value} value={item.value} />
|
||||||
|
{/if}
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
src/lib/components/ui/chip-group/index.ts
Normal file
1
src/lib/components/ui/chip-group/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as ChipGroup } from "./chip-group.svelte";
|
||||||
7
src/lib/components/ui/dialog/dialog-close.svelte
Normal file
7
src/lib/components/ui/dialog/dialog-close.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||||
45
src/lib/components/ui/dialog/dialog-content.svelte
Normal file
45
src/lib/components/ui/dialog/dialog-content.svelte
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
import DialogPortal from "./dialog-portal.svelte";
|
||||||
|
import XIcon from "@lucide/svelte/icons/x";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
import * as Dialog from "./index.js";
|
||||||
|
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
|
import type { ComponentProps } from "svelte";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
portalProps,
|
||||||
|
children,
|
||||||
|
showCloseButton = true,
|
||||||
|
...restProps
|
||||||
|
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
|
||||||
|
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DialogPortal>>;
|
||||||
|
children: Snippet;
|
||||||
|
showCloseButton?: boolean;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPortal {...portalProps}>
|
||||||
|
<Dialog.Overlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
bind:ref
|
||||||
|
data-slot="dialog-content"
|
||||||
|
class={cn(
|
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
{#if showCloseButton}
|
||||||
|
<DialogPrimitive.Close
|
||||||
|
class="ring-offset-background focus:ring-ring absolute end-4 top-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||||
|
>
|
||||||
|
<XIcon />
|
||||||
|
<span class="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
{/if}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
17
src/lib/components/ui/dialog/dialog-description.svelte
Normal file
17
src/lib/components/ui/dialog/dialog-description.svelte
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DialogPrimitive.DescriptionProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
bind:ref
|
||||||
|
data-slot="dialog-description"
|
||||||
|
class={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
20
src/lib/components/ui/dialog/dialog-footer.svelte
Normal file
20
src/lib/components/ui/dialog/dialog-footer.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="dialog-footer"
|
||||||
|
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
src/lib/components/ui/dialog/dialog-header.svelte
Normal file
20
src/lib/components/ui/dialog/dialog-header.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="dialog-header"
|
||||||
|
class={cn("flex flex-col gap-2 text-center sm:text-start", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
src/lib/components/ui/dialog/dialog-overlay.svelte
Normal file
20
src/lib/components/ui/dialog/dialog-overlay.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DialogPrimitive.OverlayProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
bind:ref
|
||||||
|
data-slot="dialog-overlay"
|
||||||
|
class={cn(
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
7
src/lib/components/ui/dialog/dialog-portal.svelte
Normal file
7
src/lib/components/ui/dialog/dialog-portal.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ...restProps }: DialogPrimitive.PortalProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Portal {...restProps} />
|
||||||
17
src/lib/components/ui/dialog/dialog-title.svelte
Normal file
17
src/lib/components/ui/dialog/dialog-title.svelte
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DialogPrimitive.TitleProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
bind:ref
|
||||||
|
data-slot="dialog-title"
|
||||||
|
class={cn("text-lg leading-none font-semibold", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
7
src/lib/components/ui/dialog/dialog-trigger.svelte
Normal file
7
src/lib/components/ui/dialog/dialog-trigger.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||||
7
src/lib/components/ui/dialog/dialog.svelte
Normal file
7
src/lib/components/ui/dialog/dialog.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { open = $bindable(false), ...restProps }: DialogPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Root bind:open {...restProps} />
|
||||||
34
src/lib/components/ui/dialog/index.ts
Normal file
34
src/lib/components/ui/dialog/index.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import Root from "./dialog.svelte";
|
||||||
|
import Portal from "./dialog-portal.svelte";
|
||||||
|
import Title from "./dialog-title.svelte";
|
||||||
|
import Footer from "./dialog-footer.svelte";
|
||||||
|
import Header from "./dialog-header.svelte";
|
||||||
|
import Overlay from "./dialog-overlay.svelte";
|
||||||
|
import Content from "./dialog-content.svelte";
|
||||||
|
import Description from "./dialog-description.svelte";
|
||||||
|
import Trigger from "./dialog-trigger.svelte";
|
||||||
|
import Close from "./dialog-close.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Title,
|
||||||
|
Portal,
|
||||||
|
Footer,
|
||||||
|
Header,
|
||||||
|
Trigger,
|
||||||
|
Overlay,
|
||||||
|
Content,
|
||||||
|
Description,
|
||||||
|
Close,
|
||||||
|
//
|
||||||
|
Root as Dialog,
|
||||||
|
Title as DialogTitle,
|
||||||
|
Portal as DialogPortal,
|
||||||
|
Footer as DialogFooter,
|
||||||
|
Header as DialogHeader,
|
||||||
|
Trigger as DialogTrigger,
|
||||||
|
Overlay as DialogOverlay,
|
||||||
|
Content as DialogContent,
|
||||||
|
Description as DialogDescription,
|
||||||
|
Close as DialogClose,
|
||||||
|
};
|
||||||
7
src/lib/components/ui/drawer/drawer-close.svelte
Normal file
7
src/lib/components/ui/drawer/drawer-close.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.CloseProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Close bind:ref data-slot="drawer-close" {...restProps} />
|
||||||
40
src/lib/components/ui/drawer/drawer-content.svelte
Normal file
40
src/lib/components/ui/drawer/drawer-content.svelte
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
import DrawerPortal from "./drawer-portal.svelte";
|
||||||
|
import DrawerOverlay from "./drawer-overlay.svelte";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { ComponentProps } from "svelte";
|
||||||
|
import type { WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
portalProps,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: DrawerPrimitive.ContentProps & {
|
||||||
|
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DrawerPortal>>;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPortal {...portalProps}>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerPrimitive.Content
|
||||||
|
bind:ref
|
||||||
|
data-slot="drawer-content"
|
||||||
|
class={cn(
|
||||||
|
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
|
||||||
|
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
|
||||||
|
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
|
||||||
|
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:end-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-s data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
||||||
|
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:start-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-e data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block"
|
||||||
|
></div>
|
||||||
|
{@render children?.()}
|
||||||
|
</DrawerPrimitive.Content>
|
||||||
|
</DrawerPortal>
|
||||||
17
src/lib/components/ui/drawer/drawer-description.svelte
Normal file
17
src/lib/components/ui/drawer/drawer-description.svelte
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DrawerPrimitive.DescriptionProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Description
|
||||||
|
bind:ref
|
||||||
|
data-slot="drawer-description"
|
||||||
|
class={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
20
src/lib/components/ui/drawer/drawer-footer.svelte
Normal file
20
src/lib/components/ui/drawer/drawer-footer.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="drawer-footer"
|
||||||
|
class={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
src/lib/components/ui/drawer/drawer-header.svelte
Normal file
20
src/lib/components/ui/drawer/drawer-header.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="drawer-header"
|
||||||
|
class={cn("flex flex-col gap-1.5 p-4", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
12
src/lib/components/ui/drawer/drawer-nested.svelte
Normal file
12
src/lib/components/ui/drawer/drawer-nested.svelte
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
|
||||||
|
let {
|
||||||
|
shouldScaleBackground = true,
|
||||||
|
open = $bindable(false),
|
||||||
|
activeSnapPoint = $bindable(null),
|
||||||
|
...restProps
|
||||||
|
}: DrawerPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.NestedRoot {shouldScaleBackground} bind:open bind:activeSnapPoint {...restProps} />
|
||||||
20
src/lib/components/ui/drawer/drawer-overlay.svelte
Normal file
20
src/lib/components/ui/drawer/drawer-overlay.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DrawerPrimitive.OverlayProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Overlay
|
||||||
|
bind:ref
|
||||||
|
data-slot="drawer-overlay"
|
||||||
|
class={cn(
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
7
src/lib/components/ui/drawer/drawer-portal.svelte
Normal file
7
src/lib/components/ui/drawer/drawer-portal.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
|
||||||
|
let { ...restProps }: DrawerPrimitive.PortalProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Portal {...restProps} />
|
||||||
17
src/lib/components/ui/drawer/drawer-title.svelte
Normal file
17
src/lib/components/ui/drawer/drawer-title.svelte
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DrawerPrimitive.TitleProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Title
|
||||||
|
bind:ref
|
||||||
|
data-slot="drawer-title"
|
||||||
|
class={cn("text-foreground font-semibold", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
7
src/lib/components/ui/drawer/drawer-trigger.svelte
Normal file
7
src/lib/components/ui/drawer/drawer-trigger.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.TriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Trigger bind:ref data-slot="drawer-trigger" {...restProps} />
|
||||||
12
src/lib/components/ui/drawer/drawer.svelte
Normal file
12
src/lib/components/ui/drawer/drawer.svelte
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||||
|
|
||||||
|
let {
|
||||||
|
shouldScaleBackground = true,
|
||||||
|
open = $bindable(false),
|
||||||
|
activeSnapPoint = $bindable(null),
|
||||||
|
...restProps
|
||||||
|
}: DrawerPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerPrimitive.Root {shouldScaleBackground} bind:open bind:activeSnapPoint {...restProps} />
|
||||||
38
src/lib/components/ui/drawer/index.ts
Normal file
38
src/lib/components/ui/drawer/index.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import Root from "./drawer.svelte";
|
||||||
|
import Content from "./drawer-content.svelte";
|
||||||
|
import Description from "./drawer-description.svelte";
|
||||||
|
import Overlay from "./drawer-overlay.svelte";
|
||||||
|
import Footer from "./drawer-footer.svelte";
|
||||||
|
import Header from "./drawer-header.svelte";
|
||||||
|
import Title from "./drawer-title.svelte";
|
||||||
|
import NestedRoot from "./drawer-nested.svelte";
|
||||||
|
import Close from "./drawer-close.svelte";
|
||||||
|
import Trigger from "./drawer-trigger.svelte";
|
||||||
|
import Portal from "./drawer-portal.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
NestedRoot,
|
||||||
|
Content,
|
||||||
|
Description,
|
||||||
|
Overlay,
|
||||||
|
Footer,
|
||||||
|
Header,
|
||||||
|
Title,
|
||||||
|
Trigger,
|
||||||
|
Portal,
|
||||||
|
Close,
|
||||||
|
|
||||||
|
//
|
||||||
|
Root as Drawer,
|
||||||
|
NestedRoot as DrawerNestedRoot,
|
||||||
|
Content as DrawerContent,
|
||||||
|
Description as DrawerDescription,
|
||||||
|
Overlay as DrawerOverlay,
|
||||||
|
Footer as DrawerFooter,
|
||||||
|
Header as DrawerHeader,
|
||||||
|
Title as DrawerTitle,
|
||||||
|
Trigger as DrawerTrigger,
|
||||||
|
Portal as DrawerPortal,
|
||||||
|
Close as DrawerClose,
|
||||||
|
};
|
||||||
@@ -12,9 +12,6 @@
|
|||||||
<LabelPrimitive.Root
|
<LabelPrimitive.Root
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="label"
|
data-slot="label"
|
||||||
class={cn(
|
class={cn("scn-label", className)}
|
||||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,29 +9,17 @@ import { browser } from "$app/environment";
|
|||||||
* Exported to allow query helpers to accept the specific type without
|
* Exported to allow query helpers to accept the specific type without
|
||||||
* creating circular type references.
|
* creating circular type references.
|
||||||
*/
|
*/
|
||||||
|
export type ClientDb = ReturnType<typeof drizzle>;
|
||||||
|
|
||||||
// this is kinda mid
|
// this is kinda mid
|
||||||
export const sqlocal = !import.meta.env.SSR ? await initDb() : null;
|
export const sqlocal = browser ? await initDb() : null;
|
||||||
// wacky typecasting, but it works to give type hints for db queries
|
export const db = sqlocal ? drizzle(sqlocal.driver, sqlocal.batchDriver) : null;
|
||||||
export const db = sqlocal
|
|
||||||
? drizzle(sqlocal.driver, sqlocal.batchDriver)
|
|
||||||
: ((await serverDb()) as unknown as ReturnType<typeof drizzle>);
|
|
||||||
|
|
||||||
export type ClientDb = typeof db;
|
|
||||||
|
|
||||||
async function initDb() {
|
async function initDb() {
|
||||||
const { SQLocalDrizzle } = await import("sqlocal/drizzle");
|
const { SQLocalDrizzle } = await import("sqlocal/drizzle");
|
||||||
return new SQLocalDrizzle("database.sqlite3");
|
return new SQLocalDrizzle("database.sqlite3");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function serverDb() {
|
|
||||||
if (!import.meta.env.SSR) return null;
|
|
||||||
const { building } = await import("$app/environment");
|
|
||||||
if (building) return null;
|
|
||||||
const { initDb } = await import("../server");
|
|
||||||
return initDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getClientDb() {
|
export function getClientDb() {
|
||||||
if (!sqlocal || !db) throw "Client DB can only be accessed from the browser";
|
if (!sqlocal || !db) throw "Client DB can only be accessed from the browser";
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ export async function getAnimeWithSongsByAnnId(db: ClientDb, annId: number) {
|
|||||||
annSongId: animeSongLinks.annSongId,
|
annSongId: animeSongLinks.annSongId,
|
||||||
type: animeSongLinks.type,
|
type: animeSongLinks.type,
|
||||||
number: animeSongLinks.number,
|
number: animeSongLinks.number,
|
||||||
|
dub: animeSongLinks.dub,
|
||||||
|
rebroadcast: animeSongLinks.rebroadcast,
|
||||||
|
|
||||||
songName: songs.name,
|
songName: songs.name,
|
||||||
fileName: songs.fileName,
|
fileName: songs.fileName,
|
||||||
@@ -139,6 +141,8 @@ export async function getAnimeWithSongsByAnnId(db: ClientDb, annId: number) {
|
|||||||
annSongId: r.annSongId,
|
annSongId: r.annSongId,
|
||||||
type: r.type,
|
type: r.type,
|
||||||
number: r.number,
|
number: r.number,
|
||||||
|
dub: r.dub,
|
||||||
|
rebroadcast: r.rebroadcast,
|
||||||
songName: r.songName,
|
songName: r.songName,
|
||||||
fileName: r.fileName,
|
fileName: r.fileName,
|
||||||
globalPercent: r.globalPercent,
|
globalPercent: r.globalPercent,
|
||||||
@@ -173,6 +177,8 @@ export async function getSongsForMalAnimeIds(
|
|||||||
annSongId: animeSongLinks.annSongId,
|
annSongId: animeSongLinks.annSongId,
|
||||||
type: animeSongLinks.type,
|
type: animeSongLinks.type,
|
||||||
number: animeSongLinks.number,
|
number: animeSongLinks.number,
|
||||||
|
dub: animeSongLinks.dub,
|
||||||
|
rebroadcast: animeSongLinks.rebroadcast,
|
||||||
|
|
||||||
songName: songs.name,
|
songName: songs.name,
|
||||||
fileName: songs.fileName,
|
fileName: songs.fileName,
|
||||||
@@ -233,6 +239,8 @@ export async function getSongsWithFilters(
|
|||||||
|
|
||||||
type: animeSongLinks.type,
|
type: animeSongLinks.type,
|
||||||
number: animeSongLinks.number,
|
number: animeSongLinks.number,
|
||||||
|
dub: animeSongLinks.dub,
|
||||||
|
rebroadcast: animeSongLinks.rebroadcast,
|
||||||
|
|
||||||
animeAnnId: anime.annId,
|
animeAnnId: anime.annId,
|
||||||
animeMainName: anime.mainName,
|
animeMainName: anime.mainName,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { getClientDb } from "$lib/db/client-db";
|
|||||||
* This is intended for READ-ONLY browsing. Bump the version when you ship a new
|
* This is intended for READ-ONLY browsing. Bump the version when you ship a new
|
||||||
* snapshot so clients refresh.
|
* snapshot so clients refresh.
|
||||||
*/
|
*/
|
||||||
export const AMQ_DB_SEED_VERSION = 3;
|
export const AMQ_DB_SEED_VERSION = 5;
|
||||||
|
|
||||||
const SEED_ASSET_PATH = "/data/amq.sqlite";
|
const SEED_ASSET_PATH = "/data/amq.sqlite";
|
||||||
const seededStorageKey = (version: number) => `amq.sqlocal.seeded.v${version}`;
|
const seededStorageKey = (version: number) => `amq.sqlocal.seeded.v${version}`;
|
||||||
@@ -41,7 +41,6 @@ function ensureBrowser() {
|
|||||||
export async function ensureSeeded(
|
export async function ensureSeeded(
|
||||||
opts: { version?: number; force?: boolean; fetch?: typeof fetch } = {},
|
opts: { version?: number; force?: boolean; fetch?: typeof fetch } = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!browser) return;
|
|
||||||
ensureBrowser();
|
ensureBrowser();
|
||||||
|
|
||||||
const version = opts.version ?? AMQ_DB_SEED_VERSION;
|
const version = opts.version ?? AMQ_DB_SEED_VERSION;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import "dotenv/config";
|
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
@@ -13,6 +12,7 @@ import amqArtistsJson from "../../../data/amq-artists.json" with {
|
|||||||
};
|
};
|
||||||
import amqGroupsJson from "../../../data/amq-groups.json" with { type: "json" };
|
import amqGroupsJson from "../../../data/amq-groups.json" with { type: "json" };
|
||||||
import amqSongsJson from "../../../data/amq-songs.json" with { type: "json" };
|
import amqSongsJson from "../../../data/amq-songs.json" with { type: "json" };
|
||||||
|
import { db } from "./index";
|
||||||
import {
|
import {
|
||||||
anime,
|
anime,
|
||||||
animeGenres,
|
animeGenres,
|
||||||
@@ -30,7 +30,6 @@ import {
|
|||||||
songs,
|
songs,
|
||||||
tags,
|
tags,
|
||||||
} from "./schema";
|
} from "./schema";
|
||||||
import { initDb } from "./server";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AMQ JSON import routine
|
* AMQ JSON import routine
|
||||||
@@ -91,7 +90,7 @@ function zodErrorSummary(prefix: string, err: z.ZodError): string {
|
|||||||
return `${prefix}\n${lines.join("\n")}${more}`;
|
return `${prefix}\n${lines.join("\n")}${more}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function categoryNumberToText(
|
function _categoryNumberToText(
|
||||||
v: number | string | null | undefined,
|
v: number | string | null | undefined,
|
||||||
): string | null {
|
): string | null {
|
||||||
if (v === null || v === undefined) return null;
|
if (v === null || v === undefined) return null;
|
||||||
@@ -183,7 +182,6 @@ export async function importAmqData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2) Apply inserts in a transaction
|
// 2) Apply inserts in a transaction
|
||||||
const db = await initDb();
|
|
||||||
db.transaction(() => {
|
db.transaction(() => {
|
||||||
if (wipeFirst) {
|
if (wipeFirst) {
|
||||||
// Child tables first, then parents (respect FKs)
|
// Child tables first, then parents (respect FKs)
|
||||||
|
|||||||
4
src/lib/db/index.ts
Normal file
4
src/lib/db/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// import 'dotenv/config';
|
||||||
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||||
|
|
||||||
|
export const db = drizzle(process.env.DB_FILE_NAME);
|
||||||
@@ -37,7 +37,7 @@ export const songs = sqliteTable(
|
|||||||
fileName: text("file_name"),
|
fileName: text("file_name"),
|
||||||
fileName480: text("file_name_480"),
|
fileName480: text("file_name_480"),
|
||||||
fileName720: text("file_name_720"),
|
fileName720: text("file_name_720"),
|
||||||
globalPercent: integer("global_percent"),
|
globalPercent: integer("global_percent").notNull(),
|
||||||
meanVolume: real("mean_volume"),
|
meanVolume: real("mean_volume"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
|
||||||
import { browser, dev } from "$app/environment";
|
|
||||||
export async function initDb() {
|
|
||||||
// await import("dotenv/config");
|
|
||||||
// import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
|
|
||||||
|
|
||||||
let _db: LibSQLDatabase;
|
|
||||||
|
|
||||||
if (dev)
|
|
||||||
process.env.DB_FILE_NAME = "libsql://amq-v1-yurat.aws-us-west-2.turso.io";
|
|
||||||
|
|
||||||
if (!process.env.DB_FILE_NAME) {
|
|
||||||
console.error("[DEBUG] Environment check failed. DB_FILE_NAME is missing.");
|
|
||||||
console.error("[DEBUG] PWD:", process.cwd());
|
|
||||||
// Only log keys to avoid leaking secrets, filtering for DB_
|
|
||||||
console.error(
|
|
||||||
"[DEBUG] Keys in env:",
|
|
||||||
Object.keys(process.env).filter((k) => k.includes("DB")),
|
|
||||||
);
|
|
||||||
throw new Error("DB_FILE_NAME is not set");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info("Using LibSQL");
|
|
||||||
const { createClient } = await import("@libsql/client");
|
|
||||||
const { drizzle } = await import("drizzle-orm/libsql");
|
|
||||||
const client = createClient({
|
|
||||||
url: process.env.DB_FILE_NAME,
|
|
||||||
authToken:
|
|
||||||
"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhIjoicm8iLCJpYXQiOjE3NzA2ODcyMDQsImlkIjoiODI5Y2ZjZmUtNWI1YS00ZWE0LTg5ZTctYWY2ZDY4MzgzZjQ2IiwicmlkIjoiYzY3MWY1NmUtZTQ2MS00NTg1LTk4ZWQtNjk5N2RjMmFhMTJmIn0.QPphfziaAAi4br397gqpptEtk212ArpQdGWJ2BcVFYQxw5ANJf4LNX4JqXuAMAKAcxS6zNPd_xtr8EsI6K38AA",
|
|
||||||
});
|
|
||||||
_db = drizzle(client);
|
|
||||||
|
|
||||||
return _db;
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
import { browser } from "$app/environment";
|
|
||||||
import type { Track } from "./types";
|
|
||||||
|
|
||||||
export type MediaSessionHandlers = {
|
|
||||||
play: () => void;
|
|
||||||
pause: () => void;
|
|
||||||
next: () => void;
|
|
||||||
prev: () => void;
|
|
||||||
seekTo?: (timeSeconds: number) => void;
|
|
||||||
seekBy?: (deltaSeconds: number) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MediaSessionBindings = {
|
|
||||||
/**
|
|
||||||
* Call whenever the current track changes.
|
|
||||||
*/
|
|
||||||
setTrack: (track: Track | null) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call on play/pause changes if you want to keep Media Session "active" state
|
|
||||||
* aligned with your app (handlers still work regardless).
|
|
||||||
*/
|
|
||||||
setPlaybackState: (state: "none" | "paused" | "playing") => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call reasonably often (e.g. on `timeupdate`, `loadedmetadata`, `ratechange`)
|
|
||||||
* to keep lockscreen / OS UI in sync.
|
|
||||||
*/
|
|
||||||
updatePositionState: (args: {
|
|
||||||
duration: number;
|
|
||||||
position: number;
|
|
||||||
playbackRate?: number;
|
|
||||||
}) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters handlers. Optional — layout-scoped players typically never unmount.
|
|
||||||
*/
|
|
||||||
destroy: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
function canUseMediaSession() {
|
|
||||||
return (
|
|
||||||
browser &&
|
|
||||||
typeof navigator !== "undefined" &&
|
|
||||||
"mediaSession" in navigator &&
|
|
||||||
typeof (navigator as Navigator).mediaSession !== "undefined"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function canUseMetadata() {
|
|
||||||
return typeof MediaMetadata !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createMediaSessionBindings(
|
|
||||||
handlers: MediaSessionHandlers,
|
|
||||||
): MediaSessionBindings {
|
|
||||||
const mediaSession = canUseMediaSession()
|
|
||||||
? (navigator as Navigator).mediaSession
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const setActionHandler = (
|
|
||||||
action: MediaSessionAction,
|
|
||||||
handler: MediaSessionActionHandler | null,
|
|
||||||
) => {
|
|
||||||
if (!mediaSession) return;
|
|
||||||
try {
|
|
||||||
mediaSession.setActionHandler(action, handler);
|
|
||||||
} catch {
|
|
||||||
// Some browsers throw for unsupported actions; ignore.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const safeNumber = (n: number) => (Number.isFinite(n) ? n : 0);
|
|
||||||
|
|
||||||
const setTrack = (track: Track | null) => {
|
|
||||||
if (!mediaSession) return;
|
|
||||||
if (!canUseMetadata()) return;
|
|
||||||
|
|
||||||
if (!track) {
|
|
||||||
// Keep it simple: clear metadata.
|
|
||||||
mediaSession.metadata = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function typeNumberLabel(t: Track) {
|
|
||||||
const type = t.type;
|
|
||||||
const n = Number(t.number ?? 0);
|
|
||||||
|
|
||||||
let typeLabel: string | null = null;
|
|
||||||
if (typeof type === "number") {
|
|
||||||
if (type === 1) typeLabel = "OP";
|
|
||||||
else if (type === 2) typeLabel = "ED";
|
|
||||||
else if (type === 3) typeLabel = "INS";
|
|
||||||
else typeLabel = `T${type}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!typeLabel) return null;
|
|
||||||
return `${typeLabel}${n ? String(n) : ""}`;
|
|
||||||
}
|
|
||||||
mediaSession.metadata = new MediaMetadata({
|
|
||||||
title: `${track.animeName} (${typeNumberLabel(track)}) — ${track.title}`,
|
|
||||||
artist: track.artist,
|
|
||||||
album: track.album,
|
|
||||||
// You can add artwork later if/when you have it:
|
|
||||||
// artwork: [{ src: "/some.png", sizes: "512x512", type: "image/png" }]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setPlaybackState = (state: "none" | "paused" | "playing") => {
|
|
||||||
if (!mediaSession) return;
|
|
||||||
try {
|
|
||||||
mediaSession.playbackState = state;
|
|
||||||
} catch {
|
|
||||||
// Some browsers may not implement playbackState; ignore.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatePositionState = (args: {
|
|
||||||
duration: number;
|
|
||||||
position: number;
|
|
||||||
playbackRate?: number;
|
|
||||||
}) => {
|
|
||||||
if (!mediaSession) return;
|
|
||||||
|
|
||||||
const anySession = mediaSession as unknown as {
|
|
||||||
setPositionState?: (state: {
|
|
||||||
duration: number;
|
|
||||||
playbackRate?: number;
|
|
||||||
position: number;
|
|
||||||
}) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof anySession.setPositionState !== "function") return;
|
|
||||||
|
|
||||||
const duration = Math.max(0, safeNumber(args.duration));
|
|
||||||
const position = Math.max(0, safeNumber(args.position));
|
|
||||||
const playbackRate = args.playbackRate ?? 1;
|
|
||||||
|
|
||||||
try {
|
|
||||||
anySession.setPositionState({
|
|
||||||
duration,
|
|
||||||
position: Math.min(position, duration || position),
|
|
||||||
playbackRate,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// iOS Safari and some Chromium variants can throw on invalid values.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const installHandlers = () => {
|
|
||||||
if (!mediaSession) return;
|
|
||||||
|
|
||||||
setActionHandler("play", () => handlers.play());
|
|
||||||
setActionHandler("pause", () => handlers.pause());
|
|
||||||
setActionHandler("previoustrack", () => handlers.prev());
|
|
||||||
setActionHandler("nexttrack", () => handlers.next());
|
|
||||||
|
|
||||||
// Seeking (optional)
|
|
||||||
setActionHandler("seekto", (details) => {
|
|
||||||
if (!handlers.seekTo) return;
|
|
||||||
const d = details as MediaSessionActionDetails & { seekTime?: number };
|
|
||||||
if (typeof d.seekTime !== "number") return;
|
|
||||||
handlers.seekTo(d.seekTime);
|
|
||||||
});
|
|
||||||
|
|
||||||
setActionHandler("seekbackward", (details) => {
|
|
||||||
const d = details as MediaSessionActionDetails & { seekOffset?: number };
|
|
||||||
const offset = typeof d.seekOffset === "number" ? d.seekOffset : 10;
|
|
||||||
if (handlers.seekBy) handlers.seekBy(-offset);
|
|
||||||
else if (handlers.seekTo) handlers.seekTo(0); // fallback-ish
|
|
||||||
});
|
|
||||||
|
|
||||||
setActionHandler("seekforward", (details) => {
|
|
||||||
const d = details as MediaSessionActionDetails & { seekOffset?: number };
|
|
||||||
const offset = typeof d.seekOffset === "number" ? d.seekOffset : 10;
|
|
||||||
if (handlers.seekBy) handlers.seekBy(offset);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stop isn't as universally supported; map to pause.
|
|
||||||
setActionHandler("stop", () => handlers.pause());
|
|
||||||
};
|
|
||||||
|
|
||||||
const destroy = () => {
|
|
||||||
if (!mediaSession) return;
|
|
||||||
// Clear handlers we set.
|
|
||||||
setActionHandler("play", null);
|
|
||||||
setActionHandler("pause", null);
|
|
||||||
setActionHandler("previoustrack", null);
|
|
||||||
setActionHandler("nexttrack", null);
|
|
||||||
setActionHandler("seekto", null);
|
|
||||||
setActionHandler("seekbackward", null);
|
|
||||||
setActionHandler("seekforward", null);
|
|
||||||
setActionHandler("stop", null);
|
|
||||||
};
|
|
||||||
|
|
||||||
installHandlers();
|
|
||||||
|
|
||||||
return {
|
|
||||||
setTrack,
|
|
||||||
setPlaybackState,
|
|
||||||
updatePositionState,
|
|
||||||
destroy,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -2,231 +2,58 @@ import { z } from "zod";
|
|||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
import type { Track } from "./types";
|
import type { Track } from "./types";
|
||||||
|
|
||||||
/**
|
const STORAGE_KEY = "amqtrain:player:v2";
|
||||||
* Persistence for the global player.
|
|
||||||
*
|
|
||||||
* Persisted:
|
|
||||||
* - queue
|
|
||||||
* - currentIndex
|
|
||||||
* - shuffleEnabled
|
|
||||||
* - wrapEnabled
|
|
||||||
* - shuffle traversal bookkeeping (order/history/cursor)
|
|
||||||
* - volume
|
|
||||||
* - uiOpen
|
|
||||||
*
|
|
||||||
* Not persisted by design:
|
|
||||||
* - currentTime / playback position
|
|
||||||
* - isPlaying (we always restore paused)
|
|
||||||
*/
|
|
||||||
|
|
||||||
const STORAGE_KEY = "amqtrain:player:v1";
|
|
||||||
const STORAGE_VERSION = 1;
|
|
||||||
|
|
||||||
const TrackSchema = z
|
|
||||||
.object({
|
|
||||||
id: z.number().int().nonnegative(),
|
|
||||||
src: z.string().min(1),
|
|
||||||
title: z.string().default(""),
|
|
||||||
artist: z.string().default(""),
|
|
||||||
album: z.string().default(""),
|
|
||||||
|
|
||||||
|
export const trackSchema: z.ZodType<Track> = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
src: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
artist: z.string(),
|
||||||
|
album: z.string(),
|
||||||
animeName: z.string().optional(),
|
animeName: z.string().optional(),
|
||||||
type: z.number().optional(),
|
type: z.number(),
|
||||||
number: z.number().optional(),
|
number: z.number(),
|
||||||
fileName: z.string().nullable().optional(),
|
fileName: z.string().nullable().optional(),
|
||||||
})
|
dub: z.boolean(),
|
||||||
.strict();
|
rebroadcast: z.boolean(),
|
||||||
|
globalPercent: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
const PersistedSnapshotSchema = z
|
export const persistedStateSchema = z.object({
|
||||||
.object({
|
queue: z.array(trackSchema),
|
||||||
version: z.literal(STORAGE_VERSION),
|
currentId: z.number().nullable(),
|
||||||
|
volume: z.number(),
|
||||||
|
isMuted: z.boolean(),
|
||||||
|
minimized: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
queue: z.array(TrackSchema).default([]),
|
export type PersistedState = z.infer<typeof persistedStateSchema>;
|
||||||
currentIndex: z.number().int().nullable().default(null),
|
|
||||||
|
|
||||||
shuffleEnabled: z.boolean().default(false),
|
export function loadState(): PersistedState | null {
|
||||||
wrapEnabled: z.boolean().default(false),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shuffle traversal:
|
|
||||||
* - order: upcoming indices into `queue` in the order they will be visited
|
|
||||||
* - history: visited indices into `queue` in visit order
|
|
||||||
* - cursor: index into `history` pointing at the current item
|
|
||||||
*/
|
|
||||||
order: z.array(z.number().int().nonnegative()).default([]),
|
|
||||||
history: z.array(z.number().int().nonnegative()).default([]),
|
|
||||||
cursor: z.number().int().default(0),
|
|
||||||
|
|
||||||
volume: z.number().min(0).max(1).default(1),
|
|
||||||
uiOpen: z.boolean().default(false),
|
|
||||||
})
|
|
||||||
.strict();
|
|
||||||
|
|
||||||
export type PersistedSnapshot = z.infer<typeof PersistedSnapshotSchema>;
|
|
||||||
|
|
||||||
export type PersistablePlayerState = {
|
|
||||||
queue: Track[];
|
|
||||||
currentIndex: number | null;
|
|
||||||
|
|
||||||
shuffleEnabled: boolean;
|
|
||||||
wrapEnabled: boolean;
|
|
||||||
|
|
||||||
order: number[];
|
|
||||||
history: number[];
|
|
||||||
cursor: number;
|
|
||||||
|
|
||||||
volume: number;
|
|
||||||
uiOpen: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function loadPersistedPlayerState(): PersistablePlayerState | null {
|
|
||||||
if (!browser) return null;
|
if (!browser) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(STORAGE_KEY);
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
|
|
||||||
const parsed = PersistedSnapshotSchema.safeParse(JSON.parse(raw));
|
const parsed = JSON.parse(raw);
|
||||||
if (!parsed.success) return null;
|
const result = persistedStateSchema.safeParse(parsed);
|
||||||
|
if (result.success) {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
|
||||||
return sanitizePersistedState(parsed.data);
|
console.error("Failed to parse player state", result.error);
|
||||||
} catch {
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load player state", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function savePersistedPlayerState(state: PersistablePlayerState): void {
|
export function saveState(state: PersistedState) {
|
||||||
if (!browser) return;
|
|
||||||
|
|
||||||
const snapshot: PersistedSnapshot = {
|
|
||||||
version: STORAGE_VERSION,
|
|
||||||
|
|
||||||
queue: state.queue,
|
|
||||||
currentIndex: state.currentIndex,
|
|
||||||
|
|
||||||
shuffleEnabled: state.shuffleEnabled,
|
|
||||||
wrapEnabled: state.wrapEnabled,
|
|
||||||
|
|
||||||
order: state.order,
|
|
||||||
history: state.history,
|
|
||||||
cursor: state.cursor,
|
|
||||||
|
|
||||||
volume: clamp01(state.volume),
|
|
||||||
uiOpen: state.uiOpen,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(snapshot));
|
|
||||||
} catch {
|
|
||||||
// Ignore quota/security errors; persistence is a best-effort feature.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throttled saver (simple debounce). Call this from reactive effects.
|
|
||||||
*/
|
|
||||||
export function createPersistScheduler(delayMs = 250) {
|
|
||||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
|
|
||||||
return (state: PersistablePlayerState) => {
|
|
||||||
if (!browser) return;
|
|
||||||
|
|
||||||
if (timeout) clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
timeout = null;
|
|
||||||
savePersistedPlayerState(state);
|
|
||||||
}, delayMs);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearPersistedPlayerState(): void {
|
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(STORAGE_KEY);
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||||
} catch {
|
} catch (e) {
|
||||||
// ignore
|
console.error("Failed to save player state", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizePersistedState(
|
|
||||||
snapshot: PersistedSnapshot,
|
|
||||||
): PersistablePlayerState {
|
|
||||||
const queue = dedupeById(snapshot.queue);
|
|
||||||
|
|
||||||
const maxIndex = queue.length - 1;
|
|
||||||
const currentIndex =
|
|
||||||
snapshot.currentIndex == null
|
|
||||||
? null
|
|
||||||
: snapshot.currentIndex >= 0 && snapshot.currentIndex <= maxIndex
|
|
||||||
? snapshot.currentIndex
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const order = filterValidIndices(snapshot.order, queue.length);
|
|
||||||
const history = filterValidIndices(snapshot.history, queue.length);
|
|
||||||
|
|
||||||
// cursor points into history; if history is empty, cursor should be 0
|
|
||||||
const cursor =
|
|
||||||
history.length === 0
|
|
||||||
? 0
|
|
||||||
: Math.max(0, Math.min(snapshot.cursor, history.length - 1));
|
|
||||||
|
|
||||||
// If we have a currentIndex but history doesn't reflect it, try to repair:
|
|
||||||
// put currentIndex at end and point cursor there.
|
|
||||||
let repairedHistory = history;
|
|
||||||
let repairedCursor = cursor;
|
|
||||||
|
|
||||||
if (currentIndex != null) {
|
|
||||||
if (history.length === 0 || history[cursor] !== currentIndex) {
|
|
||||||
repairedHistory = [...history, currentIndex];
|
|
||||||
repairedCursor = repairedHistory.length - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure order doesn't contain items already visited up through cursor.
|
|
||||||
const visitedSet =
|
|
||||||
repairedHistory.length === 0
|
|
||||||
? new Set<number>()
|
|
||||||
: new Set(repairedHistory.slice(0, repairedCursor + 1));
|
|
||||||
const repairedOrder = order.filter((i) => !visitedSet.has(i));
|
|
||||||
|
|
||||||
return {
|
|
||||||
queue,
|
|
||||||
currentIndex,
|
|
||||||
|
|
||||||
shuffleEnabled: snapshot.shuffleEnabled,
|
|
||||||
wrapEnabled: snapshot.wrapEnabled,
|
|
||||||
|
|
||||||
order: repairedOrder,
|
|
||||||
history: repairedHistory,
|
|
||||||
cursor: repairedCursor,
|
|
||||||
|
|
||||||
volume: clamp01(snapshot.volume),
|
|
||||||
uiOpen: snapshot.uiOpen,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterValidIndices(indices: number[], length: number) {
|
|
||||||
const out: number[] = [];
|
|
||||||
for (const i of indices) {
|
|
||||||
if (Number.isInteger(i) && i >= 0 && i < length) out.push(i);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function dedupeById(tracks: Track[]) {
|
|
||||||
const seen = new Set<number>();
|
|
||||||
const out: Track[] = [];
|
|
||||||
for (const t of tracks) {
|
|
||||||
const id = Number(t.id);
|
|
||||||
if (!Number.isFinite(id)) continue;
|
|
||||||
if (seen.has(id)) continue;
|
|
||||||
seen.add(id);
|
|
||||||
out.push(t);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp01(n: number) {
|
|
||||||
if (!Number.isFinite(n)) return 1;
|
|
||||||
return Math.max(0, Math.min(1, n));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,893 +0,0 @@
|
|||||||
import { browser } from "$app/environment";
|
|
||||||
import {
|
|
||||||
createPersistScheduler,
|
|
||||||
loadPersistedPlayerState,
|
|
||||||
type PersistablePlayerState,
|
|
||||||
} from "./persist";
|
|
||||||
import {
|
|
||||||
buildInitialShuffleOrder,
|
|
||||||
injectNextIntoShuffleOrder,
|
|
||||||
reindexAfterMoveOrRemove,
|
|
||||||
} from "./shuffle";
|
|
||||||
import type { Track } from "./types";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global audio player state + queue/shuffle actions (Svelte 5 runes).
|
|
||||||
*
|
|
||||||
* This module is intended to be imported from UI components and pages.
|
|
||||||
* The actual <audio> element lives in a single GlobalPlayer component, which
|
|
||||||
* binds to the state here and calls actions.
|
|
||||||
*
|
|
||||||
* Canonical dedupe id: Track.id === annSongId (number).
|
|
||||||
*
|
|
||||||
* NOTE: Do NOT use module-level `$effect` here — consumers (e.g. GlobalPlayer)
|
|
||||||
* should subscribe via `subscribe()` and drive any side-effects (like persistence).
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type InsertMode = "play" | "playNext" | "add" | "jump";
|
|
||||||
|
|
||||||
export type PlayerSnapshot = {
|
|
||||||
queue: Track[];
|
|
||||||
currentIndex: number | null;
|
|
||||||
|
|
||||||
// derived-ish convenience
|
|
||||||
currentTrack: Track | null;
|
|
||||||
|
|
||||||
shuffleEnabled: boolean;
|
|
||||||
wrapEnabled: boolean;
|
|
||||||
|
|
||||||
// shuffle traversal
|
|
||||||
order: number[];
|
|
||||||
history: number[];
|
|
||||||
cursor: number;
|
|
||||||
|
|
||||||
volume: number;
|
|
||||||
|
|
||||||
// UI
|
|
||||||
uiOpen: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PlayerSubscriber = (snapshot: PlayerSnapshot) => void;
|
|
||||||
export type Unsubscribe = () => void;
|
|
||||||
|
|
||||||
const DEFAULT_VOLUME = 1;
|
|
||||||
|
|
||||||
function isMobileLike() {
|
|
||||||
if (!browser) return false;
|
|
||||||
return window.matchMedia?.("(max-width: 1023px)")?.matches ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp01(n: number) {
|
|
||||||
if (!Number.isFinite(n)) return DEFAULT_VOLUME;
|
|
||||||
return Math.max(0, Math.min(1, n));
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayRemoveAt<T>(arr: T[], index: number) {
|
|
||||||
if (index < 0 || index >= arr.length) return arr;
|
|
||||||
return [...arr.slice(0, index), ...arr.slice(index + 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayInsertAt<T>(arr: T[], index: number, item: T) {
|
|
||||||
const i = Math.max(0, Math.min(index, arr.length));
|
|
||||||
return [...arr.slice(0, i), item, ...arr.slice(i)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayMove<T>(arr: T[], from: number, to: number) {
|
|
||||||
if (from === to) return arr;
|
|
||||||
if (from < 0 || from >= arr.length) return arr;
|
|
||||||
if (to < 0 || to >= arr.length) return arr;
|
|
||||||
|
|
||||||
const copy = [...arr];
|
|
||||||
const [item] = copy.splice(from, 1);
|
|
||||||
copy.splice(to, 0, item);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
function dedupeTracks(tracks: Track[]) {
|
|
||||||
const seen = new Set<number>();
|
|
||||||
const out: Track[] = [];
|
|
||||||
for (const t of tracks) {
|
|
||||||
const id = Number(t.id);
|
|
||||||
if (!Number.isFinite(id)) continue;
|
|
||||||
if (seen.has(id)) continue;
|
|
||||||
seen.add(id);
|
|
||||||
out.push(t);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function visitedSet(history: number[], cursor: number) {
|
|
||||||
if (history.length === 0) return new Set<number>();
|
|
||||||
const end = Math.max(0, Math.min(cursor, history.length - 1));
|
|
||||||
return new Set(history.slice(0, end + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
function persistableState(): PersistablePlayerState {
|
|
||||||
return {
|
|
||||||
queue,
|
|
||||||
currentIndex,
|
|
||||||
|
|
||||||
shuffleEnabled,
|
|
||||||
wrapEnabled,
|
|
||||||
|
|
||||||
order,
|
|
||||||
history,
|
|
||||||
cursor,
|
|
||||||
|
|
||||||
volume,
|
|
||||||
uiOpen,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** --- Initialize state (restore persisted if present) --- */
|
|
||||||
|
|
||||||
const persisted = browser ? loadPersistedPlayerState() : null;
|
|
||||||
|
|
||||||
let queue = $state<Track[]>(persisted?.queue ?? []);
|
|
||||||
let currentIndex = $state<number | null>(persisted?.currentIndex ?? null);
|
|
||||||
|
|
||||||
let shuffleEnabled = $state<boolean>(persisted?.shuffleEnabled ?? false);
|
|
||||||
let wrapEnabled = $state<boolean>(persisted?.wrapEnabled ?? false);
|
|
||||||
|
|
||||||
let order = $state<number[]>(persisted?.order ?? []);
|
|
||||||
let history = $state<number[]>(persisted?.history ?? []);
|
|
||||||
let cursor = $state<number>(persisted?.cursor ?? 0);
|
|
||||||
|
|
||||||
let volume = $state<number>(clamp01(persisted?.volume ?? DEFAULT_VOLUME));
|
|
||||||
|
|
||||||
let uiOpen = $state<boolean>(
|
|
||||||
// Default based on the current viewport:
|
|
||||||
// - mobile: closed
|
|
||||||
// - desktop: open
|
|
||||||
//
|
|
||||||
// Note: we intentionally do NOT default from persisted `uiOpen`, so the UI
|
|
||||||
// always follows the current device/viewport expectation.
|
|
||||||
!isMobileLike(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentTrack = $derived<Track | null>(
|
|
||||||
currentIndex == null ? null : (queue[currentIndex] ?? null),
|
|
||||||
);
|
|
||||||
|
|
||||||
const snapshot = $derived<PlayerSnapshot>({
|
|
||||||
queue,
|
|
||||||
currentIndex,
|
|
||||||
currentTrack,
|
|
||||||
shuffleEnabled,
|
|
||||||
wrapEnabled,
|
|
||||||
order,
|
|
||||||
history,
|
|
||||||
cursor,
|
|
||||||
volume,
|
|
||||||
uiOpen,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** --- Lightweight subscription API (to avoid polling) --- */
|
|
||||||
const subscribers = new Set<PlayerSubscriber>();
|
|
||||||
|
|
||||||
function notifySubscribers() {
|
|
||||||
// Read `snapshot` here so every subscriber sees a consistent value.
|
|
||||||
const s = snapshot;
|
|
||||||
for (const fn of subscribers) fn(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe to player snapshot changes.
|
|
||||||
*
|
|
||||||
* - Calls `fn` immediately with the current snapshot
|
|
||||||
* - Returns an `unsubscribe` function
|
|
||||||
*/
|
|
||||||
export function subscribe(fn: PlayerSubscriber): Unsubscribe {
|
|
||||||
subscribers.add(fn);
|
|
||||||
fn(snapshot);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
subscribers.delete(fn);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify subscribers at the end of the current microtask.
|
|
||||||
* This coalesces multiple mutations within the same tick into one update.
|
|
||||||
*/
|
|
||||||
let notifyQueued = false;
|
|
||||||
function queueNotify() {
|
|
||||||
if (notifyQueued) return;
|
|
||||||
notifyQueued = true;
|
|
||||||
queueMicrotask(() => {
|
|
||||||
notifyQueued = false;
|
|
||||||
notifySubscribers();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persistence
|
|
||||||
*
|
|
||||||
* Persistence must be driven from a component (e.g. GlobalPlayer) via `subscribe()`
|
|
||||||
* to avoid orphaned module-level effects.
|
|
||||||
*/
|
|
||||||
const schedulePersist = createPersistScheduler(250);
|
|
||||||
|
|
||||||
export function schedulePersistNow(): void {
|
|
||||||
if (!browser) return;
|
|
||||||
schedulePersist(persistableState());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** --- Public reads --- */
|
|
||||||
|
|
||||||
export function getSnapshot(): PlayerSnapshot {
|
|
||||||
return snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasTrack(id: number): boolean {
|
|
||||||
const wanted = Number(id);
|
|
||||||
return queue.some((t) => t.id === wanted);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function indexOfTrack(id: number): number {
|
|
||||||
const wanted = Number(id);
|
|
||||||
return queue.findIndex((t) => t.id === wanted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** --- Queue traversal helpers --- */
|
|
||||||
|
|
||||||
function ensureTraversalStateForCurrent() {
|
|
||||||
// Ensure history/cursor reflect currentIndex when possible.
|
|
||||||
if (currentIndex == null) {
|
|
||||||
history = [];
|
|
||||||
cursor = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (history.length === 0) {
|
|
||||||
history = [currentIndex];
|
|
||||||
cursor = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cur = history[cursor];
|
|
||||||
if (cur !== currentIndex) {
|
|
||||||
// Repair without rewriting past: append and move cursor to it.
|
|
||||||
history = [...history, currentIndex];
|
|
||||||
cursor = history.length - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function rebuildShuffleOrderPreservingPast() {
|
|
||||||
if (!shuffleEnabled) {
|
|
||||||
order = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
|
|
||||||
const visited = visitedSet(history, cursor);
|
|
||||||
order = buildInitialShuffleOrder(queue.length, visited);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the next queue index according to shuffle/linear mode.
|
|
||||||
* Returns null if there is no next item (and wrap is disabled).
|
|
||||||
*/
|
|
||||||
function computeNextIndex(): number | null {
|
|
||||||
if (queue.length === 0) return null;
|
|
||||||
|
|
||||||
if (currentIndex == null) return 0;
|
|
||||||
|
|
||||||
if (!shuffleEnabled) {
|
|
||||||
const next = currentIndex + 1;
|
|
||||||
if (next < queue.length) return next;
|
|
||||||
return wrapEnabled ? 0 : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
|
|
||||||
// If user went backwards in history and presses next, prefer moving forward in history.
|
|
||||||
if (cursor < history.length - 1) {
|
|
||||||
return history[cursor + 1] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.length === 0) {
|
|
||||||
if (!wrapEnabled) return null;
|
|
||||||
|
|
||||||
// Wrap in shuffle mode: keep past history, but allow revisiting by
|
|
||||||
// generating a fresh future order excluding "past" only up through cursor.
|
|
||||||
// Since at end, visited is all history; to wrap, we treat visited as empty
|
|
||||||
// and start a new cycle, but we MUST NOT mutate history.
|
|
||||||
// Easiest: generate an order containing all indices except current first,
|
|
||||||
// then inject current out.
|
|
||||||
const all = new Set<number>();
|
|
||||||
// visited empty for wrap cycle
|
|
||||||
order = buildInitialShuffleOrder(queue.length, all);
|
|
||||||
order = order.filter((i) => i !== history[cursor]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return order[0] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the previous queue index according to shuffle history or linear mode.
|
|
||||||
* Returns null if there is no previous item (and wrap is disabled).
|
|
||||||
*/
|
|
||||||
function computePrevIndex(currentTimeSeconds = 0): number | null {
|
|
||||||
if (queue.length === 0) return null;
|
|
||||||
if (currentIndex == null) return null;
|
|
||||||
|
|
||||||
// Standard behavior: if you've listened a bit, restart track.
|
|
||||||
if (currentTimeSeconds > 3) return currentIndex;
|
|
||||||
|
|
||||||
if (!shuffleEnabled) {
|
|
||||||
const prev = currentIndex - 1;
|
|
||||||
if (prev >= 0) return prev;
|
|
||||||
return wrapEnabled ? queue.length - 1 : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
|
|
||||||
if (cursor > 0) return history[cursor - 1] ?? null;
|
|
||||||
if (!wrapEnabled) return null;
|
|
||||||
|
|
||||||
// Wrap backwards in shuffle mode:
|
|
||||||
// We can jump to the last item in history if it exists, otherwise pick any.
|
|
||||||
if (history.length > 0) return history[history.length - 1] ?? null;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyCurrentIndex(next: number | null) {
|
|
||||||
currentIndex = next;
|
|
||||||
|
|
||||||
if (next == null) {
|
|
||||||
// stop traversal, but keep queue
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shuffleEnabled) return;
|
|
||||||
|
|
||||||
// Ensure traversal exists and is anchored at *this* currentIndex.
|
|
||||||
// This is important for jump-to-index behavior in shuffle mode.
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
|
|
||||||
// If we jumped to an index that's not reflected at the current cursor,
|
|
||||||
// align cursor/history so that prev/next work relative to the jumped item.
|
|
||||||
// Strategy:
|
|
||||||
// - if next is already somewhere in history, move cursor there
|
|
||||||
// - otherwise, append it and set cursor to the end
|
|
||||||
const existingPos = history.indexOf(next);
|
|
||||||
if (existingPos !== -1) {
|
|
||||||
cursor = existingPos;
|
|
||||||
} else if (history[cursor] !== next) {
|
|
||||||
history = [...history, next];
|
|
||||||
cursor = history.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this index was scheduled in the future order, remove it so we don't revisit.
|
|
||||||
order = order.filter((i) => i !== next);
|
|
||||||
|
|
||||||
// If it was at the head, that's implicitly consumed as well.
|
|
||||||
if (order[0] === next) order = order.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** --- Public traversal actions --- */
|
|
||||||
|
|
||||||
export function next(): void {
|
|
||||||
const idx = computeNextIndex();
|
|
||||||
if (idx == null) {
|
|
||||||
currentIndex = null;
|
|
||||||
queueNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
applyCurrentIndex(idx);
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prev(currentTimeSeconds = 0): void {
|
|
||||||
const idx = computePrevIndex(currentTimeSeconds);
|
|
||||||
if (idx == null) return;
|
|
||||||
|
|
||||||
// If idx === currentIndex, we interpret that as "restart track"
|
|
||||||
applyCurrentIndex(idx);
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Jump to an existing queued track by id (does not reorder). */
|
|
||||||
export function jumpToTrack(id: number): void {
|
|
||||||
const i = indexOfTrack(id);
|
|
||||||
if (i === -1) return;
|
|
||||||
applyCurrentIndex(i);
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** --- Queue mutation primitives (keep traversal state consistent) --- */
|
|
||||||
|
|
||||||
function removeAt(index: number) {
|
|
||||||
if (index < 0 || index >= queue.length) return;
|
|
||||||
|
|
||||||
queue = arrayRemoveAt(queue, index);
|
|
||||||
|
|
||||||
// Fix currentIndex
|
|
||||||
if (currentIndex != null) {
|
|
||||||
if (currentIndex === index) {
|
|
||||||
// Removing current track -> advance to next if possible else stop
|
|
||||||
currentIndex = null;
|
|
||||||
} else if (currentIndex > index) {
|
|
||||||
currentIndex = currentIndex - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shuffleEnabled) {
|
|
||||||
const re = reindexAfterMoveOrRemove({
|
|
||||||
order,
|
|
||||||
history,
|
|
||||||
cursor,
|
|
||||||
currentIndex,
|
|
||||||
fromIndex: index,
|
|
||||||
toIndex: null,
|
|
||||||
});
|
|
||||||
order = re.order;
|
|
||||||
history = re.history;
|
|
||||||
cursor = re.cursor;
|
|
||||||
currentIndex = re.currentIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If current was removed, attempt to advance
|
|
||||||
if (currentIndex == null && queue.length > 0) {
|
|
||||||
const idx = computeNextIndex();
|
|
||||||
if (idx != null) applyCurrentIndex(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveIndex(from: number, to: number) {
|
|
||||||
if (from === to) return;
|
|
||||||
if (from < 0 || from >= queue.length) return;
|
|
||||||
if (to < 0 || to >= queue.length) return;
|
|
||||||
|
|
||||||
queue = arrayMove(queue, from, to);
|
|
||||||
|
|
||||||
// Fix currentIndex (linear)
|
|
||||||
if (currentIndex != null) {
|
|
||||||
if (currentIndex === from) currentIndex = to;
|
|
||||||
else if (to < from) {
|
|
||||||
// moved earlier
|
|
||||||
if (currentIndex >= to && currentIndex < from) currentIndex += 1;
|
|
||||||
} else {
|
|
||||||
// moved later
|
|
||||||
if (currentIndex > from && currentIndex <= to) currentIndex -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shuffleEnabled) {
|
|
||||||
const re = reindexAfterMoveOrRemove({
|
|
||||||
order,
|
|
||||||
history,
|
|
||||||
cursor,
|
|
||||||
currentIndex,
|
|
||||||
fromIndex: from,
|
|
||||||
toIndex: to,
|
|
||||||
});
|
|
||||||
order = re.order;
|
|
||||||
history = re.history;
|
|
||||||
cursor = re.cursor;
|
|
||||||
currentIndex = re.currentIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Insert a new track (that is NOT currently in queue) at index. */
|
|
||||||
function insertNewAt(index: number, track: Track) {
|
|
||||||
queue = arrayInsertAt(queue, index, track);
|
|
||||||
|
|
||||||
// Fix currentIndex if insertion occurs before/at current
|
|
||||||
if (currentIndex != null && index <= currentIndex) {
|
|
||||||
currentIndex += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shuffleEnabled) return;
|
|
||||||
|
|
||||||
// When shuffle is enabled, new item should be eligible for future play.
|
|
||||||
// We do not touch past history.
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
|
|
||||||
// We inserted at `index`, which shifts all existing indices >= index by +1.
|
|
||||||
// Reindex traversal state for a conceptual move "fromIndex = -1" doesn't fit,
|
|
||||||
// so perform a manual shift.
|
|
||||||
const shiftUp = (i: number) => (i >= index ? i + 1 : i);
|
|
||||||
|
|
||||||
history = history.map(shiftUp);
|
|
||||||
order = order.map(shiftUp);
|
|
||||||
|
|
||||||
// New track is at `index`. By default it should appear in the remaining order.
|
|
||||||
// We'll append; specific actions (play/playNext) will inject it as needed.
|
|
||||||
order = [...order, index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** --- Public queue actions --- */
|
|
||||||
|
|
||||||
export function clearQueue(): void {
|
|
||||||
queue = [];
|
|
||||||
currentIndex = null;
|
|
||||||
|
|
||||||
order = [];
|
|
||||||
history = [];
|
|
||||||
cursor = 0;
|
|
||||||
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeTrack(id: number): void {
|
|
||||||
const i = indexOfTrack(id);
|
|
||||||
if (i === -1) return;
|
|
||||||
removeAt(i);
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reorder an existing queued track by id.
|
|
||||||
*
|
|
||||||
* Semantics depend on shuffle:
|
|
||||||
* - Linear (shuffle off): reorder the underlying `queue` and adjust `currentIndex`.
|
|
||||||
* - Shuffle (shuffle on): reorder the UPCOMING play order (the `order` list), not the
|
|
||||||
* underlying `queue`, because the UI is presenting a shuffled traversal that users
|
|
||||||
* expect to be able to rearrange.
|
|
||||||
*
|
|
||||||
* In shuffle mode this only affects tracks that are still in the future schedule
|
|
||||||
* (`order`). Already-played history is left intact.
|
|
||||||
*
|
|
||||||
* Notes:
|
|
||||||
* - `toIndex` is a queue index when shuffle is OFF (destination position in `queue`).
|
|
||||||
* - `toIndex` is a queue index when shuffle is ON too, but it is interpreted as
|
|
||||||
* "place this track before the track currently at queue index `toIndex` in the
|
|
||||||
* UPCOMING order", i.e. it changes `order`, not `queue`.
|
|
||||||
*/
|
|
||||||
export function reorderTrackById(id: number, toIndex: number): void {
|
|
||||||
const from = indexOfTrack(id);
|
|
||||||
if (from === -1) return;
|
|
||||||
|
|
||||||
const clampedTo = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(queue.length - 1, Math.floor(toIndex)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Shuffle: reorder upcoming traversal schedule, not underlying queue.
|
|
||||||
if (shuffleEnabled) {
|
|
||||||
// Ensure traversal state exists
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
|
|
||||||
// Only reorder within the future schedule (`order`).
|
|
||||||
// If the dragged track isn't in `order`, it's either current, already played, or unscheduled.
|
|
||||||
const fromPos = order.indexOf(from);
|
|
||||||
if (fromPos === -1) {
|
|
||||||
queueNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine insertion position relative to the target queue index within `order`.
|
|
||||||
// If target isn't currently in `order`, we clamp to the end of the future schedule.
|
|
||||||
let toPos = order.indexOf(clampedTo);
|
|
||||||
if (toPos === -1) toPos = order.length;
|
|
||||||
|
|
||||||
// Moving an item forward past itself needs an index adjustment after removal.
|
|
||||||
const without = order.filter((i) => i !== from);
|
|
||||||
if (toPos > fromPos) toPos = Math.max(0, toPos - 1);
|
|
||||||
|
|
||||||
const nextPos = Math.max(0, Math.min(without.length, toPos));
|
|
||||||
order = [...without.slice(0, nextPos), from, ...without.slice(nextPos)];
|
|
||||||
|
|
||||||
queueNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linear: reorder the underlying queue
|
|
||||||
if (from === clampedTo) return;
|
|
||||||
|
|
||||||
moveIndex(from, clampedTo);
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Core insertion behavior per your rules.
|
|
||||||
*
|
|
||||||
* - "jump": jump to an existing queued track (does not reorder)
|
|
||||||
* - "play": move/insert to right-after-current and then skip to it
|
|
||||||
* - "playNext": move/insert to right-after-current but don't skip
|
|
||||||
* - "add": append (deduped)
|
|
||||||
*
|
|
||||||
* Dedupe semantics:
|
|
||||||
* - If exists, we MOVE it instead of duplicating (except "jump", which never moves).
|
|
||||||
*/
|
|
||||||
export function insertTrack(track: Track, mode: InsertMode): void {
|
|
||||||
// Normalize + basic guard
|
|
||||||
if (!track || !Number.isFinite(track.id) || !track.src) return;
|
|
||||||
|
|
||||||
// Clicking an already-queued song should MOVE PLAYHEAD to that queue position,
|
|
||||||
// not reshuffle the queue around the current track.
|
|
||||||
if (mode === "jump") {
|
|
||||||
const i = indexOfTrack(track.id);
|
|
||||||
if (i === -1) return;
|
|
||||||
|
|
||||||
applyCurrentIndex(i);
|
|
||||||
|
|
||||||
// In shuffle mode, make sure the future order is still valid relative to the
|
|
||||||
// new cursor position (i.e., "next" should come from order after this jump).
|
|
||||||
// We rebuild the remaining order while preserving already-played history.
|
|
||||||
if (shuffleEnabled) {
|
|
||||||
rebuildShuffleOrderPreservingPast();
|
|
||||||
}
|
|
||||||
|
|
||||||
queueNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user hits "Play" / "Play next" on the *currently playing* track,
|
|
||||||
// treat it as a no-op. This avoids trying to move the current track to
|
|
||||||
// "right after itself" and then skipping, which can produce confusing
|
|
||||||
// queue changes (and in some cases corrupt traversal state).
|
|
||||||
//
|
|
||||||
// NOTE: "Add to queue" still dedupes below, but we early-return there too.
|
|
||||||
if (currentIndex != null && queue[currentIndex]?.id === track.id) {
|
|
||||||
queueNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty queue behavior
|
|
||||||
if (queue.length === 0) {
|
|
||||||
queue = [track];
|
|
||||||
currentIndex = 0;
|
|
||||||
|
|
||||||
// Initialize traversal state
|
|
||||||
if (shuffleEnabled) {
|
|
||||||
history = [0];
|
|
||||||
cursor = 0;
|
|
||||||
order = [];
|
|
||||||
} else {
|
|
||||||
history = [];
|
|
||||||
cursor = 0;
|
|
||||||
order = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === "add" || mode === "playNext") {
|
|
||||||
// If user only adds, do not auto-start; but we still set currentIndex?
|
|
||||||
// Per your desired behavior: "start the queue if it's empty" occurs on Play,
|
|
||||||
// not necessarily on Add/PlayNext. We'll keep "currentIndex = 0" so it shows
|
|
||||||
// a selected track, but GlobalPlayer should remain paused until user hits play.
|
|
||||||
// NOTE: If you prefer currentIndex to remain null for add/playNext on empty,
|
|
||||||
// we can tweak later.
|
|
||||||
}
|
|
||||||
|
|
||||||
// For play: skipping to inserted is effectively current track already.
|
|
||||||
queueNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine insertion target: right after current, or at end if nothing selected.
|
|
||||||
const base =
|
|
||||||
currentIndex == null
|
|
||||||
? -1
|
|
||||||
: Math.max(-1, Math.min(currentIndex, queue.length - 1));
|
|
||||||
const targetIndex = base + 1;
|
|
||||||
|
|
||||||
const existingIndex = indexOfTrack(track.id);
|
|
||||||
|
|
||||||
if (mode === "add") {
|
|
||||||
if (existingIndex !== -1) return;
|
|
||||||
insertNewAt(queue.length, track);
|
|
||||||
// No traversal tweaks required
|
|
||||||
queueNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// play / playNext:
|
|
||||||
if (existingIndex === -1) {
|
|
||||||
// Insert as a new item at targetIndex
|
|
||||||
insertNewAt(targetIndex, track);
|
|
||||||
|
|
||||||
if (shuffleEnabled) {
|
|
||||||
// Ensure it becomes next in shuffled traversal
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
order = injectNextIntoShuffleOrder({
|
|
||||||
order,
|
|
||||||
history,
|
|
||||||
cursor,
|
|
||||||
nextIndex: targetIndex,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Move existing to targetIndex (account for removal shifting)
|
|
||||||
// If existing is before target, the "real" target after removal shifts by -1
|
|
||||||
const adjustedTarget =
|
|
||||||
existingIndex < targetIndex ? targetIndex - 1 : targetIndex;
|
|
||||||
|
|
||||||
if (existingIndex !== adjustedTarget) {
|
|
||||||
moveIndex(existingIndex, adjustedTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shuffleEnabled) {
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
order = injectNextIntoShuffleOrder({
|
|
||||||
order,
|
|
||||||
history,
|
|
||||||
cursor,
|
|
||||||
nextIndex: adjustedTarget,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === "play") {
|
|
||||||
// Skip current -> go to that "next"
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function insertTracks(tracks: Track[], mode: InsertMode): void {
|
|
||||||
const incoming = dedupeTracks(tracks).filter((t) => t?.src);
|
|
||||||
|
|
||||||
if (incoming.length === 0) return;
|
|
||||||
|
|
||||||
if (mode === "add") {
|
|
||||||
// Append in order
|
|
||||||
for (const t of incoming) insertTrack(t, "add");
|
|
||||||
queueNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For play/playNext with multiple tracks:
|
|
||||||
// Place them sequentially after current in the order provided.
|
|
||||||
// - playNext: do not skip
|
|
||||||
// - play: skip to first inserted (becomes next), but keep subsequent ones after it
|
|
||||||
const base =
|
|
||||||
queue.length === 0
|
|
||||||
? -1
|
|
||||||
: currentIndex == null
|
|
||||||
? queue.length - 1
|
|
||||||
: currentIndex;
|
|
||||||
|
|
||||||
let insertPos = base + 1;
|
|
||||||
|
|
||||||
for (const t of incoming) {
|
|
||||||
const existing = indexOfTrack(t.id);
|
|
||||||
if (existing === -1) {
|
|
||||||
insertNewAt(insertPos, t);
|
|
||||||
if (shuffleEnabled) {
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
order = injectNextIntoShuffleOrder({
|
|
||||||
order,
|
|
||||||
history,
|
|
||||||
cursor,
|
|
||||||
nextIndex: insertPos,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
insertPos += 1;
|
|
||||||
} else {
|
|
||||||
const adjustedTarget = existing < insertPos ? insertPos - 1 : insertPos;
|
|
||||||
if (existing !== adjustedTarget) moveIndex(existing, adjustedTarget);
|
|
||||||
if (shuffleEnabled) {
|
|
||||||
ensureTraversalStateForCurrent();
|
|
||||||
order = injectNextIntoShuffleOrder({
|
|
||||||
order,
|
|
||||||
history,
|
|
||||||
cursor,
|
|
||||||
nextIndex: adjustedTarget,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
insertPos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === "play") {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** --- Toggles / settings --- */
|
|
||||||
|
|
||||||
export function setVolume(v: number): void {
|
|
||||||
volume = clamp01(v);
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setUiOpen(open: boolean): void {
|
|
||||||
uiOpen = !!open;
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleUiOpen(): void {
|
|
||||||
uiOpen = !uiOpen;
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleWrap(): void {
|
|
||||||
wrapEnabled = !wrapEnabled;
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enableShuffle(enable: boolean): void {
|
|
||||||
const nextVal = !!enable;
|
|
||||||
if (shuffleEnabled === nextVal) return;
|
|
||||||
|
|
||||||
shuffleEnabled = nextVal;
|
|
||||||
|
|
||||||
if (!shuffleEnabled) {
|
|
||||||
order = [];
|
|
||||||
history = [];
|
|
||||||
cursor = 0;
|
|
||||||
queueNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turning shuffle on: preserve current as starting history point
|
|
||||||
if (currentIndex != null) {
|
|
||||||
history = [currentIndex];
|
|
||||||
cursor = 0;
|
|
||||||
} else {
|
|
||||||
history = [];
|
|
||||||
cursor = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
rebuildShuffleOrderPreservingPast();
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleShuffle(): void {
|
|
||||||
enableShuffle(!shuffleEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure traversal state is sane if queue was externally replaced.
|
|
||||||
* Not expected in normal usage, but handy if you implement "replace queue" later.
|
|
||||||
*/
|
|
||||||
export function setQueue(
|
|
||||||
tracks: Track[],
|
|
||||||
opts?: { startIndex?: number | null },
|
|
||||||
) {
|
|
||||||
queue = dedupeTracks(tracks);
|
|
||||||
currentIndex =
|
|
||||||
opts?.startIndex == null
|
|
||||||
? null
|
|
||||||
: opts.startIndex >= 0 && opts.startIndex < queue.length
|
|
||||||
? opts.startIndex
|
|
||||||
: null;
|
|
||||||
|
|
||||||
order = [];
|
|
||||||
history = [];
|
|
||||||
cursor = 0;
|
|
||||||
|
|
||||||
if (shuffleEnabled) rebuildShuffleOrderPreservingPast();
|
|
||||||
|
|
||||||
queueNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** --- Convenience wrappers that match UI wording --- */
|
|
||||||
|
|
||||||
export function play(track: Track): void {
|
|
||||||
insertTrack(track, "play");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function playNext(track: Track): void {
|
|
||||||
insertTrack(track, "playNext");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addToQueue(track: Track): void {
|
|
||||||
insertTrack(track, "add");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addAllToQueue(tracks: Track[]): void {
|
|
||||||
insertTracks(tracks, "add");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function playAllNext(tracks: Track[]): void {
|
|
||||||
insertTracks(tracks, "playNext");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimal "now playing" string helpers for UI.
|
|
||||||
* Keeping here avoids repeated null checks in templates.
|
|
||||||
*/
|
|
||||||
export function nowPlayingLabel(): string {
|
|
||||||
if (!currentTrack) return "Nothing playing";
|
|
||||||
const t = currentTrack;
|
|
||||||
const title = (t.title ?? "").trim() || "Unknown title";
|
|
||||||
const artist = (t.artist ?? "").trim() || "Unknown Artist";
|
|
||||||
return `${title} — ${artist}`;
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
export type ShuffleState = {
|
|
||||||
/**
|
|
||||||
* Upcoming indices into the *current queue* in the order they will be visited
|
|
||||||
* when calling `next()` while shuffle is enabled.
|
|
||||||
*/
|
|
||||||
order: number[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visited indices into the *current queue* in visit order.
|
|
||||||
* `cursor` points at the currently active entry within this array.
|
|
||||||
*/
|
|
||||||
history: number[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Index into `history` that represents the current item.
|
|
||||||
*/
|
|
||||||
cursor: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ReindexResult = {
|
|
||||||
order: number[];
|
|
||||||
history: number[];
|
|
||||||
cursor: number;
|
|
||||||
currentIndex: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a shuffled play order for the *future* without affecting past history.
|
|
||||||
*
|
|
||||||
* - `queueLength`: length of the queue.
|
|
||||||
* - `visited`: indices that are considered "already visited" (typically history[0..cursor]).
|
|
||||||
* - `rng`: optional deterministic RNG for tests.
|
|
||||||
*/
|
|
||||||
export function buildInitialShuffleOrder(
|
|
||||||
queueLength: number,
|
|
||||||
visited: Set<number>,
|
|
||||||
rng: () => number = Math.random,
|
|
||||||
): number[] {
|
|
||||||
const remaining: number[] = [];
|
|
||||||
for (let i = 0; i < queueLength; i += 1) {
|
|
||||||
if (!visited.has(i)) remaining.push(i);
|
|
||||||
}
|
|
||||||
return shuffleArray(remaining, rng);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a track so it becomes the "next" item, even while shuffle is enabled.
|
|
||||||
*
|
|
||||||
* This modifies ONLY the future order. Past history is preserved.
|
|
||||||
*
|
|
||||||
* - If `nextIndex` is already in `order`, it is moved to the front.
|
|
||||||
* - If `nextIndex` is currently in "visited" history (<= cursor), we do not
|
|
||||||
* reschedule it as next (that would violate history semantics). In that case,
|
|
||||||
* this function is a no-op.
|
|
||||||
* - If `track` is the current item (history[cursor]), no-op.
|
|
||||||
*/
|
|
||||||
export function injectNextIntoShuffleOrder(args: {
|
|
||||||
order: number[];
|
|
||||||
history: number[];
|
|
||||||
cursor: number;
|
|
||||||
nextIndex: number;
|
|
||||||
}): number[] {
|
|
||||||
const { order, history, cursor, nextIndex } = args;
|
|
||||||
|
|
||||||
if (!Number.isInteger(nextIndex) || nextIndex < 0) return order;
|
|
||||||
|
|
||||||
const current = history[cursor];
|
|
||||||
if (current === nextIndex) return order;
|
|
||||||
|
|
||||||
// Preserve past: don't schedule already-visited entries as "next"
|
|
||||||
const visited = new Set(history.slice(0, cursor + 1));
|
|
||||||
if (visited.has(nextIndex)) return order;
|
|
||||||
|
|
||||||
const nextOrder: number[] = [];
|
|
||||||
nextOrder.push(nextIndex);
|
|
||||||
|
|
||||||
for (const i of order) {
|
|
||||||
if (i === nextIndex) continue;
|
|
||||||
nextOrder.push(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reindex a shuffle traversal state after a queue mutation that changes indices.
|
|
||||||
*
|
|
||||||
* Provide:
|
|
||||||
* - `fromIndex`: original index of the moved/removed item
|
|
||||||
* - `toIndex`: new index of the moved item, or `null` if the item was removed
|
|
||||||
* - `currentIndex`: the current queue index before reindexing (may be null)
|
|
||||||
*
|
|
||||||
* This updates `history`, `order`, and `currentIndex` so they point to correct
|
|
||||||
* indices in the new queue.
|
|
||||||
*
|
|
||||||
* Notes:
|
|
||||||
* - This assumes a *single-item* move or remove.
|
|
||||||
* - For "insert new item" (not previously present), you typically don't need
|
|
||||||
* this; instead you just insert the index into `order` as desired.
|
|
||||||
*/
|
|
||||||
export function reindexAfterMoveOrRemove(args: {
|
|
||||||
order: number[];
|
|
||||||
history: number[];
|
|
||||||
cursor: number;
|
|
||||||
currentIndex: number | null;
|
|
||||||
|
|
||||||
fromIndex: number;
|
|
||||||
toIndex: number | null;
|
|
||||||
}): ReindexResult {
|
|
||||||
const { fromIndex, toIndex } = args;
|
|
||||||
|
|
||||||
const remap = (i: number): number | null => {
|
|
||||||
if (!Number.isInteger(i) || i < 0) return null;
|
|
||||||
|
|
||||||
// Removal
|
|
||||||
if (toIndex == null) {
|
|
||||||
if (i === fromIndex) return null;
|
|
||||||
if (i > fromIndex) return i - 1;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move
|
|
||||||
if (fromIndex === toIndex) return i;
|
|
||||||
|
|
||||||
// Moving earlier: items between [toIndex .. fromIndex-1] shift +1
|
|
||||||
if (toIndex < fromIndex) {
|
|
||||||
if (i === fromIndex) return toIndex;
|
|
||||||
if (i >= toIndex && i < fromIndex) return i + 1;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Moving later: items between [fromIndex+1 .. toIndex] shift -1
|
|
||||||
// (because we remove fromIndex then insert at toIndex)
|
|
||||||
if (i === fromIndex) return toIndex;
|
|
||||||
if (i > fromIndex && i <= toIndex) return i - 1;
|
|
||||||
return i;
|
|
||||||
};
|
|
||||||
|
|
||||||
const history = compactAndDedupePreservingOrder(
|
|
||||||
args.history.map((i) => remap(i)).filter((i): i is number => i != null),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Cursor points into history; keep it at the same logical "current" where possible.
|
|
||||||
// If the current item was removed, clamp.
|
|
||||||
let cursor = args.cursor;
|
|
||||||
if (history.length === 0) cursor = 0;
|
|
||||||
else cursor = Math.max(0, Math.min(cursor, history.length - 1));
|
|
||||||
|
|
||||||
const order = compactAndDedupePreservingOrder(
|
|
||||||
args.order.map((i) => remap(i)).filter((i): i is number => i != null),
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentIndex =
|
|
||||||
args.currentIndex == null ? null : remap(args.currentIndex);
|
|
||||||
|
|
||||||
return { order, history, cursor, currentIndex };
|
|
||||||
}
|
|
||||||
|
|
||||||
function shuffleArray<T>(arr: T[], rng: () => number) {
|
|
||||||
// Fisher–Yates shuffle (in place), return copy for convenience
|
|
||||||
const a = [...arr];
|
|
||||||
for (let i = a.length - 1; i > 0; i -= 1) {
|
|
||||||
const j = Math.floor(rng() * (i + 1));
|
|
||||||
const tmp = a[i];
|
|
||||||
a[i] = a[j];
|
|
||||||
a[j] = tmp;
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compactAndDedupePreservingOrder(indices: number[]) {
|
|
||||||
const seen = new Set<number>();
|
|
||||||
const out: number[] = [];
|
|
||||||
for (const i of indices) {
|
|
||||||
if (!Number.isInteger(i) || i < 0) continue;
|
|
||||||
if (seen.has(i)) continue;
|
|
||||||
seen.add(i);
|
|
||||||
out.push(i);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
412
src/lib/player/store.svelte.ts
Normal file
412
src/lib/player/store.svelte.ts
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
import { browser } from "$app/environment";
|
||||||
|
import type { Track } from "./types";
|
||||||
|
|
||||||
|
const STORAGE_KEY = "amqtrain:player:v2";
|
||||||
|
|
||||||
|
export type PlayerState = {
|
||||||
|
queue: Track[];
|
||||||
|
currentId: number | null;
|
||||||
|
history: number[]; // List of track IDs
|
||||||
|
shuffledIndices: number[]; // List of indices into queue (maintained for shuffle order)
|
||||||
|
isShuffled: boolean;
|
||||||
|
repeatMode: "off" | "all" | "one";
|
||||||
|
volume: number;
|
||||||
|
isMuted: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PlayerStore {
|
||||||
|
// State
|
||||||
|
queue = $state<Track[]>([]);
|
||||||
|
currentId = $state<number | null>(null);
|
||||||
|
history = $state<number[]>([]);
|
||||||
|
shuffledIndices = $state<number[]>([]);
|
||||||
|
isShuffled = $state(false);
|
||||||
|
repeatMode = $state<"off" | "all" | "one">("off");
|
||||||
|
volume = $state(1);
|
||||||
|
isMuted = $state(false);
|
||||||
|
uiOpen = $state(false); // Mobile UI state
|
||||||
|
|
||||||
|
// Debounce timer for save()
|
||||||
|
private _saveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
// O(1) index: track.id → index in queue (maintained imperatively)
|
||||||
|
private idToIndex = $state(new Map<number, number>());
|
||||||
|
|
||||||
|
/** Rebuild the full index from the queue array. */
|
||||||
|
private rebuildIndex() {
|
||||||
|
const map = new Map<number, number>();
|
||||||
|
for (let i = 0; i < this.queue.length; i++) {
|
||||||
|
map.set(this.queue[i].id, i);
|
||||||
|
}
|
||||||
|
this.idToIndex = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derived
|
||||||
|
currentTrack = $derived.by(() => {
|
||||||
|
if (this.currentId == null) return null;
|
||||||
|
const idx = this.idToIndex.get(this.currentId);
|
||||||
|
return idx !== undefined ? this.queue[idx] : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
currentIndex = $derived.by(() => {
|
||||||
|
if (this.currentId == null) return -1;
|
||||||
|
return this.idToIndex.get(this.currentId) ?? -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
displayQueue = $derived(
|
||||||
|
this.isShuffled
|
||||||
|
? this.shuffledIndices.map((i) => this.queue[i])
|
||||||
|
: this.queue,
|
||||||
|
);
|
||||||
|
|
||||||
|
hasTrack(id: number) {
|
||||||
|
return this.idToIndex.has(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (browser) {
|
||||||
|
this.load();
|
||||||
|
// Auto-save on changes
|
||||||
|
$effect.root(() => {
|
||||||
|
$effect(() => {
|
||||||
|
this.save();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(state: Partial<import("./persist").PersistedState>) {
|
||||||
|
if (state.queue) this.queue = state.queue;
|
||||||
|
if (state.currentId) this.currentId = state.currentId;
|
||||||
|
if (state.volume !== undefined) this.volume = state.volume;
|
||||||
|
if (state.isMuted !== undefined) this.isMuted = state.isMuted;
|
||||||
|
if (state.minimized !== undefined) this.uiOpen = !state.minimized;
|
||||||
|
this.rebuildIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
load() {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (stored) {
|
||||||
|
const data = JSON.parse(stored);
|
||||||
|
this.queue = data.queue || [];
|
||||||
|
this.currentId = data.currentId ?? null;
|
||||||
|
this.history = data.history || [];
|
||||||
|
this.shuffledIndices = data.shuffledIndices || [];
|
||||||
|
this.isShuffled = data.isShuffled || false;
|
||||||
|
this.repeatMode = data.repeatMode || "off";
|
||||||
|
this.volume = data.volume ?? 1;
|
||||||
|
this.isMuted = data.isMuted || false;
|
||||||
|
this.rebuildIndex();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load player state", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
// Read snapshots synchronously so $effect tracks reactive deps
|
||||||
|
const data: PlayerState = {
|
||||||
|
queue: $state.snapshot(this.queue),
|
||||||
|
currentId: $state.snapshot(this.currentId),
|
||||||
|
history: $state.snapshot(this.history),
|
||||||
|
shuffledIndices: $state.snapshot(this.shuffledIndices),
|
||||||
|
isShuffled: $state.snapshot(this.isShuffled),
|
||||||
|
repeatMode: $state.snapshot(this.repeatMode),
|
||||||
|
volume: $state.snapshot(this.volume),
|
||||||
|
isMuted: $state.snapshot(this.isMuted),
|
||||||
|
};
|
||||||
|
// Debounce only the serialization + write
|
||||||
|
if (this._saveTimer) clearTimeout(this._saveTimer);
|
||||||
|
this._saveTimer = setTimeout(() => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
|
||||||
|
add(track: Track, playNow = false) {
|
||||||
|
const exists = this.hasTrack(track.id);
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
if (playNow) {
|
||||||
|
this.playNext(track);
|
||||||
|
this.playId(track.id);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playNow) {
|
||||||
|
this.playNext(track);
|
||||||
|
this.playId(track.id);
|
||||||
|
} else {
|
||||||
|
// Add to end
|
||||||
|
this.queue.push(track);
|
||||||
|
this.idToIndex.set(track.id, this.queue.length - 1);
|
||||||
|
|
||||||
|
if (this.isShuffled) {
|
||||||
|
this.shuffledIndices.push(this.queue.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.queue.length === 1 && !this.currentId) {
|
||||||
|
this.currentId = track.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playNext(track: Track) {
|
||||||
|
const existingIdx = this.idToIndex.get(track.id) ?? -1;
|
||||||
|
const targetTrack = track;
|
||||||
|
|
||||||
|
if (existingIdx !== -1) {
|
||||||
|
// Move approach: remove then insert
|
||||||
|
this.remove(track.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert after current
|
||||||
|
// If playing: insert at currentIndex + 1
|
||||||
|
// If empty: insert at 0
|
||||||
|
const currentIdx = this.currentIndex;
|
||||||
|
const insertIdx = currentIdx === -1 ? 0 : currentIdx + 1;
|
||||||
|
|
||||||
|
this.queue.splice(insertIdx, 0, targetTrack);
|
||||||
|
// Rebuild index — splice shifts everything after insertIdx
|
||||||
|
this.rebuildIndex();
|
||||||
|
|
||||||
|
if (this.isShuffled) {
|
||||||
|
// Shift indices that are >= insertIdx because we inserted a new item
|
||||||
|
this.shuffledIndices = this.shuffledIndices.map((i) =>
|
||||||
|
i >= insertIdx ? i + 1 : i,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newIdx = insertIdx;
|
||||||
|
|
||||||
|
// Find where current is in shuffledIndices
|
||||||
|
const currentShufflePos = this.shuffledIndices.indexOf(currentIdx);
|
||||||
|
// Insert newIdx after it
|
||||||
|
if (currentShufflePos !== -1) {
|
||||||
|
this.shuffledIndices.splice(currentShufflePos + 1, 0, newIdx);
|
||||||
|
} else {
|
||||||
|
this.shuffledIndices.unshift(newIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addAll(tracks: Track[]) {
|
||||||
|
// Batch: collect new tracks, push all at once
|
||||||
|
const newTracks: Track[] = [];
|
||||||
|
for (const track of tracks) {
|
||||||
|
// Check existence inline to avoid O(n) per-track via add()
|
||||||
|
if (!this.hasTrack(track.id)) {
|
||||||
|
newTracks.push(track);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newTracks.length === 0) return;
|
||||||
|
|
||||||
|
const startIdx = this.queue.length;
|
||||||
|
this.queue.push(...newTracks);
|
||||||
|
// Only index the newly added tracks
|
||||||
|
for (let i = 0; i < newTracks.length; i++) {
|
||||||
|
this.idToIndex.set(newTracks[i].id, startIdx + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isShuffled) {
|
||||||
|
const newIndices = newTracks.map((_, i) => startIdx + i);
|
||||||
|
this.shuffledIndices.push(...newIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIdx === 0 && !this.currentId) {
|
||||||
|
this.currentId = newTracks[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playAllNext(tracks: Track[]) {
|
||||||
|
// Reverse iterate to maintain order when inserting after current
|
||||||
|
for (let i = tracks.length - 1; i >= 0; i--) {
|
||||||
|
this.playNext(tracks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(id: number) {
|
||||||
|
const idx = this.idToIndex.get(id);
|
||||||
|
if (idx === undefined) return;
|
||||||
|
|
||||||
|
const wasCurrent = this.currentId === id;
|
||||||
|
|
||||||
|
this.queue.splice(idx, 1);
|
||||||
|
// Rebuild index — splice shifts everything after idx
|
||||||
|
this.rebuildIndex();
|
||||||
|
|
||||||
|
if (wasCurrent) {
|
||||||
|
this.currentId = null; // Or auto-advance?
|
||||||
|
this.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix shuffle indices
|
||||||
|
// All indices > idx must be decremented
|
||||||
|
// The index `idx` itself must be removed
|
||||||
|
this.shuffledIndices = this.shuffledIndices
|
||||||
|
.filter((i) => i !== idx)
|
||||||
|
.map((i) => (i > idx ? i - 1 : i));
|
||||||
|
}
|
||||||
|
|
||||||
|
clearQueue() {
|
||||||
|
this.queue = [];
|
||||||
|
this.idToIndex = new Map();
|
||||||
|
this.currentId = null;
|
||||||
|
this.shuffledIndices = [];
|
||||||
|
this.history = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
playId(id: number) {
|
||||||
|
if (this.hasTrack(id)) {
|
||||||
|
this.currentId = id;
|
||||||
|
this.addToHistory(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
move(fromIdx: number, toIdx: number) {
|
||||||
|
if (fromIdx === toIdx) return;
|
||||||
|
|
||||||
|
if (this.isShuffled) {
|
||||||
|
const indices = [...this.shuffledIndices];
|
||||||
|
if (fromIdx < 0 || fromIdx >= indices.length) return;
|
||||||
|
if (toIdx < 0 || toIdx >= indices.length) return;
|
||||||
|
|
||||||
|
const [item] = indices.splice(fromIdx, 1);
|
||||||
|
indices.splice(toIdx, 0, item);
|
||||||
|
this.shuffledIndices = indices;
|
||||||
|
} else {
|
||||||
|
const q = [...this.queue];
|
||||||
|
if (fromIdx < 0 || fromIdx >= q.length) return;
|
||||||
|
if (toIdx < 0 || toIdx >= q.length) return;
|
||||||
|
|
||||||
|
const [item] = q.splice(fromIdx, 1);
|
||||||
|
q.splice(toIdx, 0, item);
|
||||||
|
this.queue = q;
|
||||||
|
this.rebuildIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Playback Controls
|
||||||
|
|
||||||
|
next() {
|
||||||
|
if (this.queue.length === 0) return;
|
||||||
|
|
||||||
|
let nextIdxInQueue: number | null = null;
|
||||||
|
const currentIdx = this.currentIndex;
|
||||||
|
|
||||||
|
if (this.isShuffled) {
|
||||||
|
const currentShufflePos = this.shuffledIndices.indexOf(currentIdx);
|
||||||
|
if (
|
||||||
|
currentShufflePos !== -1 &&
|
||||||
|
currentShufflePos < this.shuffledIndices.length - 1
|
||||||
|
) {
|
||||||
|
nextIdxInQueue = this.shuffledIndices[currentShufflePos + 1];
|
||||||
|
} else if (this.repeatMode === "all" && this.shuffledIndices.length > 0) {
|
||||||
|
nextIdxInQueue = this.shuffledIndices[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentIdx !== -1 && currentIdx < this.queue.length - 1) {
|
||||||
|
nextIdxInQueue = currentIdx + 1;
|
||||||
|
} else if (this.repeatMode === "all" && this.queue.length > 0) {
|
||||||
|
nextIdxInQueue = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextIdxInQueue !== null) {
|
||||||
|
const nextId = this.queue[nextIdxInQueue]?.id;
|
||||||
|
if (nextId) {
|
||||||
|
this.currentId = nextId;
|
||||||
|
this.addToHistory(nextId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prev() {
|
||||||
|
if (this.queue.length === 0) return;
|
||||||
|
|
||||||
|
let prevIdxInQueue: number | null = null;
|
||||||
|
const currentIdx = this.currentIndex;
|
||||||
|
|
||||||
|
if (this.isShuffled) {
|
||||||
|
const currentShufflePos = this.shuffledIndices.indexOf(currentIdx);
|
||||||
|
if (currentShufflePos > 0) {
|
||||||
|
prevIdxInQueue = this.shuffledIndices[currentShufflePos - 1];
|
||||||
|
} else if (this.repeatMode === "all" && this.shuffledIndices.length > 0) {
|
||||||
|
// Wrap to end? Or just stop.
|
||||||
|
// For now let's stop at start if not wrapping.
|
||||||
|
// If repeat all, wrap to end?
|
||||||
|
prevIdxInQueue = this.shuffledIndices[this.shuffledIndices.length - 1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentIdx > 0) {
|
||||||
|
prevIdxInQueue = currentIdx - 1;
|
||||||
|
} else if (this.repeatMode === "all" && this.queue.length > 0) {
|
||||||
|
prevIdxInQueue = this.queue.length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevIdxInQueue !== null) {
|
||||||
|
this.playId(this.queue[prevIdxInQueue]?.id);
|
||||||
|
} else {
|
||||||
|
// At start of queue.
|
||||||
|
// Just seek to 0? Store doesn't control audio.
|
||||||
|
// If we can't go back, we do nothing (UI likely handles the seek-to-0 comparison).
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addToHistory(id: number) {
|
||||||
|
const last = this.history[this.history.length - 1];
|
||||||
|
if (last !== id) {
|
||||||
|
this.history.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleShuffle() {
|
||||||
|
this.isShuffled = !this.isShuffled;
|
||||||
|
if (this.isShuffled) {
|
||||||
|
this.reshuffle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reshuffle() {
|
||||||
|
// Create indices 0..N-1
|
||||||
|
const indices = Array.from({ length: this.queue.length }, (_, i) => i);
|
||||||
|
|
||||||
|
// Fisher-Yates
|
||||||
|
for (let i = indices.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[indices[i], indices[j]] = [indices[j], indices[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep current first
|
||||||
|
if (this.currentId) {
|
||||||
|
const currentIdx = this.currentIndex;
|
||||||
|
const without = indices.filter((i) => i !== currentIdx);
|
||||||
|
this.shuffledIndices = [currentIdx, ...without];
|
||||||
|
} else {
|
||||||
|
this.shuffledIndices = indices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleRepeat() {
|
||||||
|
if (this.repeatMode === "off") this.repeatMode = "all";
|
||||||
|
else if (this.repeatMode === "all") this.repeatMode = "one";
|
||||||
|
else this.repeatMode = "off";
|
||||||
|
}
|
||||||
|
|
||||||
|
setVolume(v: number) {
|
||||||
|
this.volume = Math.max(0, Math.min(1, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMute() {
|
||||||
|
this.isMuted = !this.isMuted;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUiOpen(open: boolean) {
|
||||||
|
this.uiOpen = open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const player = new PlayerStore();
|
||||||
@@ -18,9 +18,12 @@ export type Track = {
|
|||||||
|
|
||||||
/** Optional extra context for rendering/debugging */
|
/** Optional extra context for rendering/debugging */
|
||||||
animeName?: string;
|
animeName?: string;
|
||||||
type?: SongType;
|
type: SongType;
|
||||||
number?: number;
|
number: number;
|
||||||
fileName?: string | null;
|
fileName?: string | null;
|
||||||
|
dub: boolean;
|
||||||
|
rebroadcast: boolean;
|
||||||
|
globalPercent: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SongRowLike = {
|
export type SongRowLike = {
|
||||||
@@ -31,6 +34,9 @@ export type SongRowLike = {
|
|||||||
songName: string;
|
songName: string;
|
||||||
artistName: string | null;
|
artistName: string | null;
|
||||||
fileName?: string | null;
|
fileName?: string | null;
|
||||||
|
dub: boolean;
|
||||||
|
rebroadcast: boolean;
|
||||||
|
globalPercent: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,5 +61,8 @@ export function trackFromSongRow(row: SongRowLike): Track | null {
|
|||||||
type: row.type,
|
type: row.type,
|
||||||
number: row.number,
|
number: row.number,
|
||||||
fileName,
|
fileName,
|
||||||
|
dub: row.dub,
|
||||||
|
rebroadcast: row.rebroadcast,
|
||||||
|
globalPercent: row.globalPercent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,28 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { AmqAnimeCategory, AmqAnimeGenre, AmqAnimeTag } from "./anime-extended";
|
import { AmqAnimeCategory, AmqAnimeGenre, AmqAnimeTag } from "./anime-extended";
|
||||||
|
|
||||||
export const Season = z.enum({
|
export const AmqSeasonMap = {
|
||||||
Winter: 0,
|
Winter: 0,
|
||||||
Spring: 1,
|
Spring: 1,
|
||||||
Summer: 2,
|
Summer: 2,
|
||||||
Fall: 3,
|
Fall: 3,
|
||||||
} as const);
|
} as const;
|
||||||
|
|
||||||
export const SongLinkType = z.enum({
|
export const AmqSeason = z.enum(AmqSeasonMap);
|
||||||
|
|
||||||
|
export const AmqSongLinkTypeMap = {
|
||||||
OP: 1,
|
OP: 1,
|
||||||
ED: 2,
|
ED: 2,
|
||||||
INS: 3,
|
INS: 3,
|
||||||
} as const);
|
} as const;
|
||||||
|
|
||||||
|
export const AmqSongLinkTypeMapReverse = {
|
||||||
|
1: "OP",
|
||||||
|
2: "ED",
|
||||||
|
3: "INS",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const AmqSongLinkType = z.enum(AmqSongLinkTypeMap);
|
||||||
|
|
||||||
const BooleanInt = z.enum({
|
const BooleanInt = z.enum({
|
||||||
false: 0,
|
false: 0,
|
||||||
@@ -23,7 +33,7 @@ const BooleanInt = z.enum({
|
|||||||
export const AmqSongLink = z.object({
|
export const AmqSongLink = z.object({
|
||||||
songId: z.int().positive(),
|
songId: z.int().positive(),
|
||||||
number: z.int().nonnegative(),
|
number: z.int().nonnegative(),
|
||||||
type: SongLinkType,
|
type: AmqSongLinkType,
|
||||||
annSongId: z.int().positive(),
|
annSongId: z.int().positive(),
|
||||||
uploaded: BooleanInt,
|
uploaded: BooleanInt,
|
||||||
rebroadcast: BooleanInt,
|
rebroadcast: BooleanInt,
|
||||||
@@ -53,7 +63,7 @@ export const AmqAnimeSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
year: z.int().positive(),
|
year: z.int().positive(),
|
||||||
seasonId: Season,
|
seasonId: AmqSeason,
|
||||||
songLinks: z.array(AmqSongLink),
|
songLinks: z.array(AmqSongLink),
|
||||||
opCount: z.int().nonnegative(),
|
opCount: z.int().nonnegative(),
|
||||||
edCount: z.int().nonnegative(),
|
edCount: z.int().nonnegative(),
|
||||||
|
|||||||
@@ -1,20 +1,5 @@
|
|||||||
export const SongTypeMap: Record<string, number> = {
|
import { AmqSongLinkTypeMapReverse } from "$lib/types/amq";
|
||||||
OP: 1,
|
|
||||||
ED: 2,
|
|
||||||
INS: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Map number back to string for displaying in UI
|
export function songTypeNumberLabel(type: number, number: number) {
|
||||||
export const SongTypeReverseMap: Record<number, string> = {
|
return `${AmqSongLinkTypeMapReverse[type as keyof typeof AmqSongLinkTypeMapReverse] ?? `T${type}`}${number ? number : ""}`;
|
||||||
1: "OP",
|
}
|
||||||
2: "ED",
|
|
||||||
3: "INS",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SongCategoryMap: Record<number, string> = {
|
|
||||||
0: "None",
|
|
||||||
1: "Instrumental",
|
|
||||||
2: "Chanting",
|
|
||||||
3: "Character",
|
|
||||||
4: "Standard",
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { resolve } from "$app/paths";
|
import { resolve } from "$app/paths";
|
||||||
import "./layout.css";
|
import "./layout.css";
|
||||||
import favicon from "$lib/assets/favicon.svg";
|
import favicon from "$lib/assets/favicon.svg";
|
||||||
import GlobalPlayer from "$lib/components/GlobalPlayer.svelte";
|
import PlayerRoot from "$lib/components/player/PlayerRoot.svelte";
|
||||||
import ClientOnly from "$lib/components/util/ClientOnly.svelte";
|
import ClientOnly from "$lib/components/util/ClientOnly.svelte";
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
- Desktop: 2-column grid, right column reserved for the in-flow player sidebar
|
- Desktop: 2-column grid, right column reserved for the in-flow player sidebar
|
||||||
-->
|
-->
|
||||||
<div
|
<div
|
||||||
class="min-h-dvh not-xl:min-w-[80dvw] lg:grid lg:grid-rows-[auto_1fr] gap-16 lg:grid-cols-[1fr_420px]"
|
class="flex flex-col min-h-dvh not-lg:w-full not-xl:min-w-[80dvw] lg:grid lg:grid-rows-[auto_1fr] gap-4 lg:grid-cols-[1fr_420px]"
|
||||||
>
|
>
|
||||||
<header
|
<header
|
||||||
class="sticky top-0 z-40 border-b bg-background/80 backdrop-blur lg:col-span-2"
|
class="sticky top-0 z-40 border-b bg-background/80 backdrop-blur lg:col-span-2"
|
||||||
@@ -34,18 +34,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="flex flex-col items-center]">
|
<main class="flex flex-col flex-1 p-4 max-sm:px-2">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Desktop sidebar column (in normal flow) -->
|
<!-- Desktop sidebar column (in normal flow) -->
|
||||||
<aside class="hidden lg:block">
|
<!-- PlayerRoot uses contents to inject its children into this grid -->
|
||||||
<ClientOnly showFallback={false}>
|
<ClientOnly showFallback={false}>
|
||||||
{#snippet children()}
|
{#snippet children()}
|
||||||
<GlobalPlayer />
|
<div class="contents">
|
||||||
|
<PlayerRoot />
|
||||||
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</aside>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile player UI is rendered via a portal from the single GlobalPlayer instance
|
<!-- Mobile player UI is rendered via a portal from the single GlobalPlayer instance
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
getClientDb,
|
getClientDb,
|
||||||
searchAnimeByName,
|
searchAnimeByName,
|
||||||
} from "$lib/db/client-db";
|
} from "$lib/db/client-db";
|
||||||
import { addAllToQueue, playAllNext } from "$lib/player/player.svelte";
|
import { player } from "$lib/player/store.svelte";
|
||||||
import { trackFromSongRow } from "$lib/player/types";
|
import { trackFromSongRow } from "$lib/player/types";
|
||||||
import { AmqBrowseSearchSchema } from "$lib/types/search/amq-browse";
|
import { AmqBrowseSearchSchema } from "$lib/types/search/amq-browse";
|
||||||
import { seasonName } from "$lib/utils/amq";
|
import { seasonName } from "$lib/utils/amq";
|
||||||
@@ -74,11 +74,14 @@
|
|||||||
songName: s.songName,
|
songName: s.songName,
|
||||||
artistName: s.artistName,
|
artistName: s.artistName,
|
||||||
fileName: s.fileName ?? null,
|
fileName: s.fileName ?? null,
|
||||||
|
dub: Boolean(s.dub),
|
||||||
|
rebroadcast: Boolean(s.rebroadcast),
|
||||||
|
globalPercent: s.globalPercent,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.filter((t) => t !== null);
|
.filter((t) => t !== null);
|
||||||
|
|
||||||
addAllToQueue(tracks);
|
player.addAll(tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function playAllNextForAnime(a: AnimeItem) {
|
async function playAllNextForAnime(a: AnimeItem) {
|
||||||
@@ -98,11 +101,14 @@
|
|||||||
songName: s.songName,
|
songName: s.songName,
|
||||||
artistName: s.artistName,
|
artistName: s.artistName,
|
||||||
fileName: s.fileName ?? null,
|
fileName: s.fileName ?? null,
|
||||||
|
dub: Boolean(s.dub),
|
||||||
|
rebroadcast: Boolean(s.rebroadcast),
|
||||||
|
globalPercent: s.globalPercent,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.filter((t) => t !== null);
|
.filter((t) => t !== null);
|
||||||
|
|
||||||
playAllNext(tracks);
|
player.playAllNext(tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { invalidate } from "$app/navigation";
|
import { invalidate } from "$app/navigation";
|
||||||
import SongEntry from "$lib/components/SongEntry.svelte";
|
import SongEntry from "$lib/components/SongEntry.svelte";
|
||||||
import { db as clientDb } from "$lib/db/client-db";
|
import { db as clientDb } from "$lib/db/client-db";
|
||||||
import { addAllToQueue, playAllNext } from "$lib/player/player.svelte";
|
import { player } from "$lib/player/store.svelte";
|
||||||
import { trackFromSongRow } from "$lib/player/types";
|
import { trackFromSongRow } from "$lib/player/types";
|
||||||
import { seasonName } from "$lib/utils/amq";
|
import { seasonName } from "$lib/utils/amq";
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
@@ -36,17 +36,20 @@
|
|||||||
songName: s.songName,
|
songName: s.songName,
|
||||||
artistName: s.artistName,
|
artistName: s.artistName,
|
||||||
fileName: s.fileName ?? null,
|
fileName: s.fileName ?? null,
|
||||||
|
dub: Boolean(s.dub),
|
||||||
|
rebroadcast: Boolean(s.rebroadcast),
|
||||||
|
globalPercent: s.globalPercent,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.filter((t) => t !== null);
|
.filter((t) => t !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function queueAll() {
|
function queueAll() {
|
||||||
addAllToQueue(playableTracks());
|
player.addAll(playableTracks());
|
||||||
}
|
}
|
||||||
|
|
||||||
function playAllNextFromAnime() {
|
function playAllNextFromAnime() {
|
||||||
playAllNext(playableTracks());
|
player.playAllNext(playableTracks());
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -64,7 +67,9 @@
|
|||||||
<p class="mt-3 text-sm text-muted-foreground">Loading anime…</p>
|
<p class="mt-3 text-sm text-muted-foreground">Loading anime…</p>
|
||||||
{:else}
|
{:else}
|
||||||
<header class="mt-2 space-y-2">
|
<header class="mt-2 space-y-2">
|
||||||
<h1 class="text-2xl font-semibold">{data.animeWithSongs.anime.mainName}</h1>
|
<h1 class="text-2xl font-semibold">
|
||||||
|
{data.animeWithSongs.anime.mainName}
|
||||||
|
</h1>
|
||||||
|
|
||||||
<p class="text-sm text-muted-foreground">
|
<p class="text-sm text-muted-foreground">
|
||||||
{data.animeWithSongs.anime.year}
|
{data.animeWithSongs.anime.year}
|
||||||
@@ -164,6 +169,8 @@
|
|||||||
artistName={s.artistName}
|
artistName={s.artistName}
|
||||||
fileName={s.fileName}
|
fileName={s.fileName}
|
||||||
globalPercent={s.globalPercent}
|
globalPercent={s.globalPercent}
|
||||||
|
dub={Boolean(s.dub)}
|
||||||
|
rebroadcast={Boolean(s.rebroadcast)}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -134,4 +134,40 @@
|
|||||||
.btn-icon-sm {
|
.btn-icon-sm {
|
||||||
@apply inline-flex h-8 w-8 items-center justify-center rounded border;
|
@apply inline-flex h-8 w-8 items-center justify-center rounded border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chip-group {
|
||||||
|
@apply flex flex-wrap items-stretch border border-input rounded-md overflow-hidden bg-background w-fit h-fit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip-group legend {
|
||||||
|
@apply flex items-center px-3 py-1.5 text-sm font-semibold border-r border-input;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
@apply relative inline-flex items-center px-4 py-2 border-r border-input last:border-r-0 bg-background cursor-pointer text-sm font-medium transition-colors hover:bg-muted/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip input {
|
||||||
|
@apply appearance-none;
|
||||||
|
cursor: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip:has(input:checked) {
|
||||||
|
@apply bg-primary text-primary-foreground;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip input:checked + span {
|
||||||
|
@apply font-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
@apply rounded bg-muted px-2 py-0.5 text-sm text-muted-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer scn {
|
||||||
|
/* Stolen styles from svelte-shadcn to make the final css look less tailwindy */
|
||||||
|
.scn-label {
|
||||||
|
@apply flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { invalidate } from "$app/navigation";
|
import { invalidate } from "$app/navigation";
|
||||||
import SongEntry from "$lib/components/SongEntry.svelte";
|
import SongEntry from "$lib/components/SongEntry.svelte";
|
||||||
import { db as clientDb } from "$lib/db/client-db";
|
import { db as clientDb } from "$lib/db/client-db";
|
||||||
import { addAllToQueue, playAllNext } from "$lib/player/player.svelte";
|
import { player } from "$lib/player/store.svelte";
|
||||||
import { trackFromSongRow } from "$lib/player/types";
|
import { trackFromSongRow } from "$lib/player/types";
|
||||||
import {
|
import {
|
||||||
MalAnimeListQuerySchema,
|
MalAnimeListQuerySchema,
|
||||||
@@ -62,6 +62,9 @@
|
|||||||
songName: r.songName,
|
songName: r.songName,
|
||||||
artistName: songArtistLabel(r),
|
artistName: songArtistLabel(r),
|
||||||
fileName: r.fileName,
|
fileName: r.fileName,
|
||||||
|
dub: Boolean(r.dub),
|
||||||
|
rebroadcast: Boolean(r.rebroadcast),
|
||||||
|
globalPercent: r.globalPercent,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.filter((t) => t !== null),
|
.filter((t) => t !== null),
|
||||||
@@ -139,7 +142,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded border px-3 py-2 text-sm"
|
class="rounded border px-3 py-2 text-sm"
|
||||||
onclick={() => addAllToQueue(tracksFromResults)}
|
onclick={() => player.addAll(tracksFromResults)}
|
||||||
disabled={tracksFromResults.length === 0}
|
disabled={tracksFromResults.length === 0}
|
||||||
>
|
>
|
||||||
Add all to queue
|
Add all to queue
|
||||||
@@ -148,7 +151,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded border px-3 py-2 text-sm"
|
class="rounded border px-3 py-2 text-sm"
|
||||||
onclick={() => playAllNext(tracksFromResults)}
|
onclick={() => player.playAllNext(tracksFromResults)}
|
||||||
disabled={tracksFromResults.length === 0}
|
disabled={tracksFromResults.length === 0}
|
||||||
>
|
>
|
||||||
Play all next
|
Play all next
|
||||||
@@ -204,6 +207,8 @@
|
|||||||
artistName={songArtistLabel(r)}
|
artistName={songArtistLabel(r)}
|
||||||
fileName={r.fileName}
|
fileName={r.fileName}
|
||||||
globalPercent={r.globalPercent}
|
globalPercent={r.globalPercent}
|
||||||
|
dub={Boolean(r.dub)}
|
||||||
|
rebroadcast={Boolean(r.rebroadcast)}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -5,19 +5,17 @@
|
|||||||
import { invalidate } from "$app/navigation";
|
import { invalidate } from "$app/navigation";
|
||||||
import SongEntry from "$lib/components/SongEntry.svelte";
|
import SongEntry from "$lib/components/SongEntry.svelte";
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import { ChipGroup } from "$lib/components/ui/chip-group";
|
||||||
import { Input } from "$lib/components/ui/input";
|
import { Input } from "$lib/components/ui/input";
|
||||||
import { Label } from "$lib/components/ui/label";
|
import { Label } from "$lib/components/ui/label";
|
||||||
import {
|
|
||||||
NativeSelect,
|
|
||||||
NativeSelectOption,
|
|
||||||
} from "$lib/components/ui/native-select";
|
|
||||||
import { db as clientDb } from "$lib/db/client-db";
|
import { db as clientDb } from "$lib/db/client-db";
|
||||||
import { addAllToQueue, playAllNext } from "$lib/player/player.svelte";
|
import { player } from "$lib/player/store.svelte";
|
||||||
import { trackFromSongRow } from "$lib/player/types";
|
import { trackFromSongRow } from "$lib/player/types";
|
||||||
|
import { AmqSongLinkTypeMap } from "$lib/types/amq";
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
import { SearchParamsSchemaClient } from "./schema";
|
import { SearchParamsSchema } from "./schema";
|
||||||
|
|
||||||
const params = useSearchParams(SearchParamsSchemaClient, {
|
const params = useSearchParams(SearchParamsSchema, {
|
||||||
pushHistory: false,
|
pushHistory: false,
|
||||||
showDefaults: false,
|
showDefaults: false,
|
||||||
});
|
});
|
||||||
@@ -51,6 +49,9 @@
|
|||||||
songName: r.songName,
|
songName: r.songName,
|
||||||
artistName: songArtistLabel(r),
|
artistName: songArtistLabel(r),
|
||||||
fileName: r.fileName,
|
fileName: r.fileName,
|
||||||
|
dub: Boolean(r.dub),
|
||||||
|
rebroadcast: Boolean(r.rebroadcast),
|
||||||
|
globalPercent: r.globalPercent,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.filter((t) => t !== null),
|
.filter((t) => t !== null),
|
||||||
@@ -122,15 +123,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<ChipGroup
|
||||||
<Label for="song-type">Song Type</Label>
|
label="Song Type"
|
||||||
<NativeSelect id="song-type" bind:value={params.songType}>
|
items={Object.keys(AmqSongLinkTypeMap).map((type) => ({
|
||||||
<NativeSelectOption value="0">All</NativeSelectOption>
|
label: type,
|
||||||
<NativeSelectOption value="1">OP</NativeSelectOption>
|
value: AmqSongLinkTypeMap[type as keyof typeof AmqSongLinkTypeMap],
|
||||||
<NativeSelectOption value="2">ED</NativeSelectOption>
|
}))}
|
||||||
<NativeSelectOption value="3">INS</NativeSelectOption>
|
bind:value={params.type}
|
||||||
</NativeSelect>
|
/>
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<Label for="songs-limit">Limit</Label>
|
<Label for="songs-limit">Limit</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
max="200"
|
max="200"
|
||||||
step="20"
|
step="20"
|
||||||
class="w-1/2"
|
class="w-1/2"
|
||||||
bind:value={params.songsLimit}
|
bind:value={params.limit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
onclick={() => addAllToQueue(tracksFromResults)}
|
onclick={() => player.addAll(tracksFromResults)}
|
||||||
disabled={tracksFromResults.length === 0}
|
disabled={tracksFromResults.length === 0}
|
||||||
>
|
>
|
||||||
Add all to queue
|
Add all to queue
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
onclick={() => playAllNext(tracksFromResults)}
|
onclick={() => player.playAllNext(tracksFromResults)}
|
||||||
disabled={tracksFromResults.length === 0}
|
disabled={tracksFromResults.length === 0}
|
||||||
>
|
>
|
||||||
Play all next
|
Play all next
|
||||||
@@ -191,6 +191,8 @@
|
|||||||
artistName={songArtistLabel(r)}
|
artistName={songArtistLabel(r)}
|
||||||
fileName={r.fileName}
|
fileName={r.fileName}
|
||||||
globalPercent={r.globalPercent}
|
globalPercent={r.globalPercent}
|
||||||
|
dub={Boolean(r.dub)}
|
||||||
|
rebroadcast={Boolean(r.rebroadcast)}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { SongFilters } from "$lib/db/client-db";
|
import type { SongFilters } from "$lib/db/client-db";
|
||||||
import { db, ensureSeeded, getSongsWithFilters } from "$lib/db/client-db";
|
import { db, ensureSeeded, getSongsWithFilters } from "$lib/db/client-db";
|
||||||
import type { PageLoad } from "./$types";
|
import type { PageLoad } from "./$types";
|
||||||
import { SearchParamsSchemaServer } from "./schema";
|
import { SearchParamsSchema } from "./schema";
|
||||||
|
|
||||||
export const load: PageLoad = async ({ url, fetch, depends }) => {
|
export const load: PageLoad = async ({ url, fetch, depends }) => {
|
||||||
depends("clientdb:songs");
|
depends("clientdb:songs");
|
||||||
|
|
||||||
const parsed = SearchParamsSchemaServer.safeParse(
|
const parsed = SearchParamsSchema.safeParse(
|
||||||
Object.fromEntries(url.searchParams.entries()),
|
Object.fromEntries(url.searchParams.entries()),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export const load: PageLoad = async ({ url, fetch, depends }) => {
|
|||||||
filters.globalPercentMin = parsed.data.gpm;
|
filters.globalPercentMin = parsed.data.gpm;
|
||||||
if (parsed.data.gpx !== undefined)
|
if (parsed.data.gpx !== undefined)
|
||||||
filters.globalPercentMax = parsed.data.gpx;
|
filters.globalPercentMax = parsed.data.gpx;
|
||||||
if (parsed.data.songType) filters.songTypes = [parsed.data.songType];
|
if (parsed.data.type) filters.songTypes = parsed.data.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client-only DB: on the server `db` is null, so return [] and let hydration re-run load in browser.
|
// Client-only DB: on the server `db` is null, so return [] and let hydration re-run load in browser.
|
||||||
@@ -32,11 +32,7 @@ export const load: PageLoad = async ({ url, fetch, depends }) => {
|
|||||||
|
|
||||||
await ensureSeeded({ fetch });
|
await ensureSeeded({ fetch });
|
||||||
|
|
||||||
const songRows = await getSongsWithFilters(
|
const songRows = await getSongsWithFilters(db, filters, parsed.data?.limit);
|
||||||
db,
|
|
||||||
filters,
|
|
||||||
parsed.data?.songsLimit,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: parsed.success ? parsed.data : {}, // Return original parsed data for form state
|
filters: parsed.success ? parsed.data : {}, // Return original parsed data for form state
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
AmqSongLinkType,
|
||||||
|
AmqSongLinkTypeMap,
|
||||||
|
AmqSongLinkTypeMapReverse,
|
||||||
|
} from "$lib/types/amq";
|
||||||
|
|
||||||
|
const SEP = ",";
|
||||||
|
|
||||||
|
const songTypesCodec = z.codec(z.string(), z.array(AmqSongLinkType), {
|
||||||
|
decode: (str) =>
|
||||||
|
str
|
||||||
|
? decodeURIComponent(str)
|
||||||
|
.split(SEP)
|
||||||
|
.map((s) => AmqSongLinkTypeMap[s as keyof typeof AmqSongLinkTypeMap])
|
||||||
|
: [],
|
||||||
|
encode: (arr) =>
|
||||||
|
arr
|
||||||
|
? encodeURIComponent(
|
||||||
|
arr.map((a) => AmqSongLinkTypeMapReverse[a]).join(SEP),
|
||||||
|
)
|
||||||
|
: "",
|
||||||
|
});
|
||||||
|
|
||||||
export const SearchParamsSchema = z.object({
|
export const SearchParamsSchema = z.object({
|
||||||
songsLimit: z.coerce.number().int().default(20),
|
limit: z.coerce.number().int().default(20),
|
||||||
song: z.string().optional().default(""),
|
song: z.string().optional().default(""),
|
||||||
artist: z.string().optional().default(""),
|
artist: z.string().optional().default(""),
|
||||||
anime: z.string().optional().default(""),
|
anime: z.string().optional().default(""),
|
||||||
gpm: z.coerce.number().int().optional().default(0),
|
gpm: z.coerce.number().int().optional().default(0),
|
||||||
gpx: z.coerce.number().int().optional().default(100),
|
gpx: z.coerce.number().int().optional().default(100),
|
||||||
});
|
type: songTypesCodec.default([]),
|
||||||
|
|
||||||
export const SearchParamsSchemaClient = SearchParamsSchema.extend({
|
|
||||||
songType: z.string().optional().default("0"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SearchParamsSchemaServer = SearchParamsSchema.extend({
|
|
||||||
songType: z.coerce.number().int().optional(),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import adapter from "@sveltejs/adapter-node";
|
import adapter from "@sveltejs/adapter-cloudflare";
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
@@ -7,6 +7,16 @@ const config = {
|
|||||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
adapter: adapter(),
|
adapter: adapter(),
|
||||||
|
|
||||||
|
experimental: {
|
||||||
|
tracing: {
|
||||||
|
server: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
instrumentation: {
|
||||||
|
server: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
|
import { sentrySvelteKit } from "@sentry/sveltekit";
|
||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import sqlocal from "sqlocal/vite";
|
import sqlocal from "sqlocal/vite";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import "dotenv/config";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(), sveltekit(), sqlocal()],
|
plugins: [sentrySvelteKit({
|
||||||
ssr: {
|
org: "cazzzer",
|
||||||
// Native modules must be externalized to avoid "dynamic require" errors in build
|
project: "amqtrain",
|
||||||
external: ["@libsql/client"],
|
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||||
|
}), tailwindcss(), sveltekit(), sqlocal()],
|
||||||
|
server: {
|
||||||
|
allowedHosts: ["cazzzer.pgrok.cazzzer.com"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,20 +7,22 @@
|
|||||||
"name": "amqtrain",
|
"name": "amqtrain",
|
||||||
"main": ".svelte-kit/cloudflare/_worker.js",
|
"main": ".svelte-kit/cloudflare/_worker.js",
|
||||||
"compatibility_date": "2026-01-31",
|
"compatibility_date": "2026-01-31",
|
||||||
"compatibility_flags": ["nodejs_compat"],
|
"compatibility_flags": [
|
||||||
|
"nodejs_compat",
|
||||||
|
],
|
||||||
"assets": {
|
"assets": {
|
||||||
"binding": "ASSETS",
|
"binding": "ASSETS",
|
||||||
"directory": ".svelte-kit/cloudflare"
|
"directory": ".svelte-kit/cloudflare",
|
||||||
},
|
},
|
||||||
"observability": {
|
"observability": {
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
},
|
},
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"pattern": "amqtrain.cazzzer.com",
|
"pattern": "amqtrain.cazzzer.com",
|
||||||
"custom_domain": true
|
"custom_domain": true,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
/**
|
/**
|
||||||
* Smart Placement
|
* Smart Placement
|
||||||
* https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
* https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
||||||
|
|||||||
Reference in New Issue
Block a user