diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..b36186e --- /dev/null +++ b/bun.lock @@ -0,0 +1,1097 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "supra-app", + "dependencies": { + "@battlefieldduck/xterm-svelte": "^2.2.1", + "@dnd-kit-svelte/core": "^0.0.11", + "@dnd-kit-svelte/svelte": "0.1.3", + "@dnd-kit/abstract": "^0.2.4", + "@dnd-kit/helpers": "^0.2.4", + "@tanstack/match-sorter-utils": "^8.19.4", + "@xterm/xterm": "^5.5.0", + "@yume-chan/adb": "^2.6.0", + "@yume-chan/adb-credential-web": "^2.1.0", + "@yume-chan/adb-daemon-webusb": "^2.3.2", + "@yume-chan/adb-scrcpy": "^2.3.2", + "@yume-chan/scrcpy": "^2.3.0", + "@yume-chan/stream-extra": "^2.5.3", + "animejs": "^4.3.6", + "firebase": "^12.14.0", + "idb": "^8.0.3", + "mode-watcher": "^1.1.0", + "usb": "^2.17.0", + "uuid": "^13.0.0", + "xterm-addon-fit": "^0.8.0", + "zod": "^4.3.6", + }, + "devDependencies": { + "@chromatic-com/storybook": "^4.1.3", + "@internationalized/date": "^3.12.0", + "@lucide/svelte": "^0.561.0", + "@storybook/addon-a11y": "^10.3.5", + "@storybook/addon-docs": "^10.3.5", + "@storybook/addon-svelte-csf": "^5.1.2", + "@storybook/addon-vitest": "^10.3.5", + "@storybook/sveltekit": "^10.3.5", + "@sveltejs/adapter-auto": "^7.0.1", + "@sveltejs/adapter-node": "^5.5.4", + "@sveltejs/kit": "^2.57.0", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tailwindcss/forms": "^0.5.11", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.2.2", + "@tanstack/table-core": "^8.21.3", + "@types/node": "^22.19.17", + "@vitest/browser": "^3.2.4", + "bits-ui": "^2.17.3", + "clsx": "^2.1.1", + "paneforge": "^1.0.2", + "playwright": "^1.59.1", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.1", + "prettier-plugin-tailwindcss": "^0.7.2", + "storybook": "^10.3.5", + "svelte": "^5.55.2", + "svelte-adapter-bun": "^1.0.1", + "svelte-check": "^4.4.6", + "svelte-sonner": "^1.1.0", + "tailwind-merge": "^3.5.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.2.2", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "vaul-svelte": "^1.0.0-next.7", + "vite": "^7.3.2", + "vite-plugin-devtools-json": "^1.0.0", + "vitest": "^4.1.4", + "vitest-browser-svelte": "^2.1.0", + }, + }, + }, + "packages": { + "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="], + + "@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/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + + "@battlefieldduck/xterm-svelte": ["@battlefieldduck/xterm-svelte@2.2.1", "", { "dependencies": { "@xterm/addon-attach": "^0.12.0", "@xterm/addon-clipboard": "^0.2.0", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-image": "^0.9.0", "@xterm/addon-progress": "^0.2.0", "@xterm/addon-search": "^0.16.0", "@xterm/addon-serialize": "^0.14.0", "@xterm/addon-unicode11": "^0.9.0", "@xterm/addon-web-links": "^0.12.0", "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^6.0.0" }, "peerDependencies": { "svelte": "^5.46.4" } }, "sha512-Wy9w7upfHr0breWIiZUskvhQEmjST3quANMx8CwCLvE4lI3/4SRcz4E3ySFLdzYzpZi/4pQZvqAAM3zg15oaMQ=="], + + "@chromatic-com/storybook": ["@chromatic-com/storybook@4.1.3", "", { "dependencies": { "@neoconfetti/react": "^1.0.0", "chromatic": "^13.3.3", "filesize": "^10.0.12", "jsonfile": "^6.1.0", "strip-ansi": "^7.1.0" }, "peerDependencies": { "storybook": "^0.0.0-0 || ^9.0.0 || ^9.1.0-0 || ^9.2.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0" } }, "sha512-hc0HO9GAV9pxqDE6fTVOV5KeLpTiCfV8Jrpk5ogKLiIgeq2C+NPjpt74YnrZTjiK8E19fYcMP+2WY9ZtX7zHmw=="], + + "@dnd-kit-svelte/accessibility": ["@dnd-kit-svelte/accessibility@0.0.11", "", { "dependencies": { "@dnd-kit-svelte/utilities": "latest", "esm-env": "^1.2.2", "runed": "^0.23.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-YkMjzabpnwELyeOSC2COV6EIGWZM8m4kgi/wIMwG0g/ZO3WWuzkMiLRLhYZqRkrEdpoz5tZnVclw+x/kcUjQHw=="], + + "@dnd-kit-svelte/core": ["@dnd-kit-svelte/core@0.0.11", "", { "dependencies": { "@dnd-kit-svelte/accessibility": "latest", "@dnd-kit-svelte/utilities": "latest", "runed": "^0.23.0", "svelte-toolbelt": "^0.7.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-bVqutZhSOk3nQsjwJ0SveUrPeBgUO/wajfon1AiKA8mxfQhaUG97CfoQLs7tLW3YxSkHkLPhYRw9WQat7OMB7g=="], + + "@dnd-kit-svelte/svelte": ["@dnd-kit-svelte/svelte@0.1.3", "", { "dependencies": { "@dnd-kit/abstract": "^0.1.21", "@dnd-kit/collision": "^0.1.21", "@dnd-kit/dom": "^0.1.21", "@dnd-kit/state": "^0.1.21" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-2Zz0y+YsvppwY1YwY4y7nHx3ge4nB4Zd0mgJOlGgDSM6Rz+CuRMIOq+fNq6Ah6giRl15xiXfXBTvbHITnXJYmg=="], + + "@dnd-kit-svelte/utilities": ["@dnd-kit-svelte/utilities@0.0.11", "", { "dependencies": { "svelte-toolbelt": "^0.7.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-Y9Z016gLdieHiAVoteSBa1tvrgug1/Rl5wdKw/2g1Fd4G0q9HWIyPe2evKrv8H5ePfcNegA1jN7fneDVmHd3pQ=="], + + "@dnd-kit/abstract": ["@dnd-kit/abstract@0.2.4", "", { "dependencies": { "@dnd-kit/geometry": "^0.2.4", "@dnd-kit/state": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-1F3JQaD8upvTNpfDrhvec57cldTEaUggmVrVJZIpRTIdZR7trKqS7vBYZR1mW3qNjWsxC91eEQAXYWv0vxXXuw=="], + + "@dnd-kit/collision": ["@dnd-kit/collision@0.1.21", "", { "dependencies": { "@dnd-kit/abstract": "^0.1.21", "@dnd-kit/geometry": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-9AJ4NbuwGDexxMCZXZyKdNQhbAe93p6C6IezQaDaWmdCqZHMHmC3+ul7pGefBQfOooSarGwIf8Bn182o9SMa1A=="], + + "@dnd-kit/dom": ["@dnd-kit/dom@0.1.21", "", { "dependencies": { "@dnd-kit/abstract": "^0.1.21", "@dnd-kit/collision": "^0.1.21", "@dnd-kit/geometry": "^0.1.21", "@dnd-kit/state": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-6UDc1y2Y3oLQKArGlgCrZxz5pdEjRSiQujXOn5JdbuWvKqTdUR5RTYDeicr+y2sVm3liXjTqs3WlUoV+eqhqUQ=="], + + "@dnd-kit/geometry": ["@dnd-kit/geometry@0.2.4", "", { "dependencies": { "@dnd-kit/state": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-S8wVj4rQIErimDQoRT0n1+lgxPvlO/gvzT9/1vzyyw1U4bB/S8avtrI+REVw/jH84c/dii+GwrrzVkDDoIluNw=="], + + "@dnd-kit/helpers": ["@dnd-kit/helpers@0.2.4", "", { "dependencies": { "@dnd-kit/abstract": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-xKnkVPVH5YOeyerJGsqgzivTZq1lGSqnBiP5T+3G2jnM/KY5FCZBJSVcwNzFXcaIrMlj35N/sxlFHCX8kKgXEA=="], + + "@dnd-kit/state": ["@dnd-kit/state@0.1.21", "", { "dependencies": { "@preact/signals-core": "^1.10.0", "tslib": "^2.6.2" } }, "sha512-pdhntEPvn/QttcF295bOJpWiLsRqA/Iczh1ODOJUxGiR+E4GkYVz9VapNNm9gDq6ST0tr/e1Q2xBztUHlJqQgA=="], + + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "@firebase/ai": ["@firebase/ai@2.13.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.4", "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-types": "0.x" } }, "sha512-nJJDQKqjAcbkZdZGT/5WTVLrGZ+pYhWbwKC90nNzmvtoRTtnOJaNS34fhKSHQeB9SALgD2kxuWT5I4AkytdZ/Q=="], + + "@firebase/analytics": ["@firebase/analytics@0.10.22", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/installations": "0.6.22", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-8BSaq/QRGU1+xyi8L2PTLTJU7MH9aMA72RQdIxrbhWFauOZY9OXo8f2YDN/972xA8d588tlnNVEQ2Mo69pT9Ow=="], + + "@firebase/analytics-compat": ["@firebase/analytics-compat@0.2.28", "", { "dependencies": { "@firebase/analytics": "0.10.22", "@firebase/analytics-types": "0.8.4", "@firebase/component": "0.7.3", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-lIAlqUUbBu93FJMlQfslryQtBwwzdzvp23ePC6FNgymXk6Ook5v4Uvc0vdutvoIeqmyA3LfP0ZeRFK8+11kOOQ=="], + + "@firebase/analytics-types": ["@firebase/analytics-types@0.8.4", "", {}, "sha512-zQ+XTgkwH6CY/eUSHJRP7e4LxM30RCxlCmob5sy2axs25GE3Ny0XdgpDscMTHHQIGqWkxPXad4w2Mw9sCgT8zQ=="], + + "@firebase/app": ["@firebase/app@0.14.13", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "idb": "7.1.1", "tslib": "^2.1.0" } }, "sha512-H89Jeyp31+EZk9GPu6vaeL9mEmoXgM3nASB7UPBYYS/lqAks21mO1BU1dF8NbsVTL6tgGZkGUtiGJgxtDiwHkw=="], + + "@firebase/app-check": ["@firebase/app-check@0.11.4", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-G8EsbVJV9gSfoibx0dNoNOUrvr+PkL7J//+W/BST/oUassimkZeq9bjj3bKkB0pn4og5GMQ9qs7FefwP00kkgg=="], + + "@firebase/app-check-compat": ["@firebase/app-check-compat@0.4.4", "", { "dependencies": { "@firebase/app-check": "0.11.4", "@firebase/app-check-types": "0.5.4", "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-9iP0MvmaVagulNXmrca96U3tqNAI3j98wsC1z7rj62nnOTajlrHM//jjB9VoHqRw6/islMskp6RsKnM7vhLDqA=="], + + "@firebase/app-check-interop-types": ["@firebase/app-check-interop-types@0.3.4", "", {}, "sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg=="], + + "@firebase/app-check-types": ["@firebase/app-check-types@0.5.4", "", {}, "sha512-xV7JsIyzVr15aA7f3Pi0rB9gdBuVubs89FGA8VkRYA4g0l78poADgdfrScgf7NndSg9mm7cR7PJyY0+t22KaGw=="], + + "@firebase/app-compat": ["@firebase/app-compat@0.5.13", "", { "dependencies": { "@firebase/app": "0.14.13", "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" } }, "sha512-pn3FvXwUR34kWPccDQfCKsNZcM2wD1OS+J1jeEgzM1ZNXoxR2NaF6e5DjDuRrnTwR6LN2XQQt0IqE6yKmgpCQg=="], + + "@firebase/app-types": ["@firebase/app-types@0.9.5", "", { "dependencies": { "@firebase/logger": "0.5.1" } }, "sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A=="], + + "@firebase/auth": ["@firebase/auth@1.13.2", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@react-native-async-storage/async-storage": "^2.2.0 || ^3.0.0" }, "optionalPeers": ["@react-native-async-storage/async-storage"] }, "sha512-B4w0iS7MxRg28oIh2fJFTE6cM0lYdBrW19eHpc42jqEcloUjlYyVrpPqZvqA4+v9KFEVSKEs2SfWyta7hbzkJQ=="], + + "@firebase/auth-compat": ["@firebase/auth-compat@0.6.7", "", { "dependencies": { "@firebase/auth": "1.13.2", "@firebase/auth-types": "0.13.1", "@firebase/component": "0.7.3", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-XgKnOgY1Siq7gylAmLkYtHAlRxNeWEAspH+nO3gJZJnfHqoTHbr9UjJ3nHNFALYXV5CfpQlyPROyB2ztySBHBQ=="], + + "@firebase/auth-interop-types": ["@firebase/auth-interop-types@0.2.5", "", {}, "sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg=="], + + "@firebase/auth-types": ["@firebase/auth-types@0.13.1", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-0c1Mnid0uMDfGJHeUS4zfvBa4/CedJXotGy/n/NZJnBjwiJawt0ZYU+wH2VAVLiRCEfG2ncCkAX3yd1/2nrB7g=="], + + "@firebase/component": ["@firebase/component@0.7.3", "", { "dependencies": { "@firebase/util": "1.15.1", "tslib": "^2.1.0" } }, "sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew=="], + + "@firebase/data-connect": ["@firebase/data-connect@0.7.1", "", { "dependencies": { "@firebase/auth-interop-types": "0.2.5", "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-2LbUU8mmSA63HknxQMmWHjpzuNLBKflvVwQc2tpoVKg0biWleNEJX031ELks0vzFs+dDjOUkCJR72RP6mQHFOg=="], + + "@firebase/database": ["@firebase/database@1.1.3", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.4", "@firebase/auth-interop-types": "0.2.5", "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw=="], + + "@firebase/database-compat": ["@firebase/database-compat@2.1.4", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/database": "1.1.3", "@firebase/database-types": "1.0.20", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" } }, "sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg=="], + + "@firebase/database-types": ["@firebase/database-types@1.0.20", "", { "dependencies": { "@firebase/app-types": "0.9.5", "@firebase/util": "1.15.1" } }, "sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw=="], + + "@firebase/firestore": ["@firebase/firestore@4.15.0", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "@firebase/webchannel-wrapper": "1.0.6", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-Fj9osqYkz2Rqr7kW3/A8BRd8CyJ7yA5K8YjhihRdyJWbL+FsELVcR6DpoCplrp1IyU+xeGgTubo1UOySXpY+EA=="], + + "@firebase/firestore-compat": ["@firebase/firestore-compat@0.4.10", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/firestore": "4.15.0", "@firebase/firestore-types": "3.0.4", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-yMP3FADDjikdrQv4YmvL4EkIny6Hw+N+a2O5T40rlHiniyMpRPxgYkKiFOvMZnsqKLqBVnKqCAElC0pa/IZtdw=="], + + "@firebase/firestore-types": ["@firebase/firestore-types@3.0.4", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-jGn+JSS4X9zZsrfu7Yw66v5YRdOLD1oyQh4USR0xWl4CUqV/DA6bNIXRPpxH/cUl3iVTNiP6MN7g+EL42A4qfA=="], + + "@firebase/functions": ["@firebase/functions@0.13.5", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.4", "@firebase/auth-interop-types": "0.2.5", "@firebase/component": "0.7.3", "@firebase/messaging-interop-types": "0.2.5", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-bWCx713f4kE/uFV7gdFOLBS7lDoiZj48MRkbAqe35gkXcCeWF4QjRNO07Jhmve7EJIoQOBczL29y2r8VRuN1kw=="], + + "@firebase/functions-compat": ["@firebase/functions-compat@0.4.5", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/functions": "0.13.5", "@firebase/functions-types": "0.6.4", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-10qlUXGY25G5/1g9UihqksPp2po+ZqSE7LEizsrdUP7vrTmkysXxGSZCDyojSEp6mQe/ecRDdDDI+z4XRdb4wQ=="], + + "@firebase/functions-types": ["@firebase/functions-types@0.6.4", "", {}, "sha512-zV6kgqtduR4rUAdC/ilS7kmb93XD7bEZoJDlVBZqlOw2uGGGCNBQBuleww2rr0Ulr3L9o2TDjumEt68/l1f9DQ=="], + + "@firebase/installations": ["@firebase/installations@0.6.22", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/util": "1.15.1", "idb": "7.1.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-ef6nn3GGQTdReCfotRMG77PJZu8CqEbiK5pEoBnM0gTu/Z9v0i/az2p3HABsa/1beQmmyh1OsOjf7P5+pgwdZw=="], + + "@firebase/installations-compat": ["@firebase/installations-compat@0.2.22", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/installations": "0.6.22", "@firebase/installations-types": "0.5.4", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-C/zpAuTP5S9OgKSPvXRupw3hoY/JZSlA1wFjD/Sb7LIQE0FNbcMdO8Y4KXVEkjVzma/DDDDIAzxEXqKMAzc88w=="], + + "@firebase/installations-types": ["@firebase/installations-types@0.5.4", "", { "peerDependencies": { "@firebase/app-types": "0.x" } }, "sha512-U2eFapdHwjb43Vx9o+Pmj4dFfvcHEK1IirEFLqMtWrTHvmdrS3gBpBD1kmJk/9HjsOtoHZxJ2Paoe79e+L1ZPg=="], + + "@firebase/logger": ["@firebase/logger@0.5.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ=="], + + "@firebase/messaging": ["@firebase/messaging@0.13.0", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/installations": "0.6.22", "@firebase/messaging-interop-types": "0.2.5", "@firebase/util": "1.15.1", "idb": "7.1.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-GZoo0uGRvEbszo83xcgbjJp4FpkmBEr4l8Z4hi8gl+P1Spn/MTK3HapanMzSX4yUHuTEiF5hasWRxOaz+o5sxQ=="], + + "@firebase/messaging-compat": ["@firebase/messaging-compat@0.2.27", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/messaging": "0.13.0", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-JNOiu1PPgdHzEPEtoFiNxQuu0x9bm4bfETSQCpGfcTlgWkhlSK7uh7nlsjC10TQLUNgYetLmuutaYTh8aeYLVA=="], + + "@firebase/messaging-interop-types": ["@firebase/messaging-interop-types@0.2.5", "", {}, "sha512-tUEKnaAP2Y/MNIqgnriPpV6e5l13Vs/+p2yrd6NGlncPJT9O3a8muYZtdnWe+IJ4fgKLHJVC79n/asxk/N5Msw=="], + + "@firebase/performance": ["@firebase/performance@0.7.12", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/installations": "0.6.22", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0", "web-vitals": "^4.2.4" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-fe7nV8teUU3OBHlMUZ9Lw4gLhCW2k4m5Uc3pfWGV+fl8uwJQBGp9Q3lqsJ+HSrFu3Q2pJyLAgrClPGSKyDeYgQ=="], + + "@firebase/performance-compat": ["@firebase/performance-compat@0.2.25", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/performance": "0.7.12", "@firebase/performance-types": "0.2.4", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-q6NjTXpIPoFuUmCmMN/maCdTgzT6aExs9xZo+PxfVLj6uLVGvpyAD6XWjmcrb7jChsFBYbq7E5dyNDF7Zhy9kA=="], + + "@firebase/performance-types": ["@firebase/performance-types@0.2.4", "", {}, "sha512-kJSEk7b0uhpcPRyL4SQ/GPujLqk52XNKcXlnsKDbWGAb9vugcLvOU3u6zfEdwd+d8hWJb5S5ZizV1JFFI0nkKg=="], + + "@firebase/remote-config": ["@firebase/remote-config@0.8.4", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/installations": "0.6.22", "@firebase/logger": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-lslywR5lGvHWTu4z/MPoYs3UwS3CKdeY+ELXY87087VsOpBpkD+9Orra23tA9GW683arPTDOM3CM6eKmtiOO3g=="], + + "@firebase/remote-config-compat": ["@firebase/remote-config-compat@0.2.25", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/logger": "0.5.1", "@firebase/remote-config": "0.8.4", "@firebase/remote-config-types": "0.5.1", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-FnA5S4IxFJAAFrCnYzWlO0FCaizlYdqhe42ygFMA+wE/mUP+w36iXzHyKj1OO1A+2gyMFjeRHyg8HhkJ6c5vRA=="], + + "@firebase/remote-config-types": ["@firebase/remote-config-types@0.5.1", "", {}, "sha512-cX/1LT6KQwkXzck2eSzeKnuvXZCyr8qaPpDcikoJs7jmI+oBOXixpDLeDtWj1U6GNMkIoXrEDNoyT2Ypcyp5/A=="], + + "@firebase/storage": ["@firebase/storage@0.14.3", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-YX4/YL6P6/fufSSeGnVhjWddcIXbFq2cWIhMKFTZo1E/Rtcl2mJj/BYUQTwJfcE1Tl8un1FOya4L05jcSLN/Eg=="], + + "@firebase/storage-compat": ["@firebase/storage-compat@0.4.3", "", { "dependencies": { "@firebase/component": "0.7.3", "@firebase/storage": "0.14.3", "@firebase/storage-types": "0.8.4", "@firebase/util": "1.15.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "sha512-gruVqjtUGX8tEoeNbaWXZm0Zfcfcb7fvmDmBxV8yPAbWvExRnZYLO2+qw9idxNE7BvPXt5csyjSYHy//dAizxw=="], + + "@firebase/storage-types": ["@firebase/storage-types@0.8.4", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-BT7cwxJOx8SWwlQfrlC+bD/Sk3Cw+1odCi8UZNFNWTVZoPsBnA5W+mqtZzVnvsdJpXCFGSGQ7R7vOR6dtM/BRA=="], + + "@firebase/util": ["@firebase/util@1.15.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g=="], + + "@firebase/webchannel-wrapper": ["@firebase/webchannel-wrapper@1.0.6", "", {}, "sha512-Vr/Mqu79dMwGRAyGbJ4uN4+BtXB3/mRTdzetD1daWNeG8QaWuzhhbG77GltO5c0yYmYls8i250iX73624GJd7Q=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], + + "@grpc/grpc-js": ["@grpc/grpc-js@1.9.15", "", { "dependencies": { "@grpc/proto-loader": "^0.7.8", "@types/node": ">=12.12.47" } }, "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ=="], + + "@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="], + + "@internationalized/date": ["@internationalized/date@3.12.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ=="], + + "@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/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@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=="], + + "@lucide/svelte": ["@lucide/svelte@0.561.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-vofKV2UFVrKE6I4ewKJ3dfCXSV6iP6nWVmiM83MLjsU91EeJcEg7LoWUABLp/aOTxj1HQNbJD1f3g3L0JQgH9A=="], + + "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@neoconfetti/react": ["@neoconfetti/react@1.0.0", "", {}, "sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A=="], + + "@oxc-project/types": ["@oxc-project/types@0.129.0", "", {}, "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@preact/signals-core": ["@preact/signals-core@1.14.1", "", {}, "sha512-vxPpfXqrwUe9lpjqfYNjAF/0RF/eFGeLgdJzdmIIZjpOnTmGmAB4BjWone562mJGMRP4frU6iZ6ei3PDsu52Ng=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.1", "", {}, "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0", "", { "os": "android", "cpu": "arm64" }, "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0", "", { "os": "linux", "cpu": "arm" }, "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0", "", { "os": "none", "cpu": "arm64" }, "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0", "", { "os": "win32", "cpu": "x64" }, "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0", "", {}, "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ=="], + + "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.2", "", { "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-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg=="], + + "@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.60.3", "", { "os": "android", "cpu": "arm" }, "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.3", "", { "os": "android", "cpu": "arm64" }, "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.3", "", { "os": "linux", "cpu": "arm" }, "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.3", "", { "os": "linux", "cpu": "arm" }, "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.3", "", { "os": "linux", "cpu": "x64" }, "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.3", "", { "os": "none", "cpu": "arm64" }, "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.3", "", { "os": "win32", "cpu": "x64" }, "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.3", "", { "os": "win32", "cpu": "x64" }, "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@storybook/addon-a11y": ["@storybook/addon-a11y@10.3.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.3.6" } }, "sha512-cbwXIT5CeHZ9AFbTKQ6YB7Ct6TAl/kKOgALbvzzVtFfRvm51JYygGaiJaB7PbPWn9wgJP2olJcFt+erlEc6cRw=="], + + "@storybook/addon-docs": ["@storybook/addon-docs@10.3.6", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.3.6", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.3.6", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.3.6" } }, "sha512-TvIdADVPtauxW0LzXIpIv7X6GxwetorhyNh+6+7MHC27XSBCWVxxRUwL63YeLlHTuXsIk0quG3b1xgwVRzWOJA=="], + + "@storybook/addon-svelte-csf": ["@storybook/addon-svelte-csf@5.1.2", "", { "dependencies": { "@storybook/csf": "^0.1.13", "dedent": "^1.5.3", "es-toolkit": "^1.26.1", "esrap": "^1.2.2", "magic-string": "^0.30.12", "svelte-ast-print": "^0.4.0", "zimmerframe": "^1.1.2" }, "peerDependencies": { "@storybook/svelte": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0", "@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "storybook": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-NpImknEb48J7yr/ArTYpvhDSvGUrgm5Nuybu9PCicjSKTACsXX7cln2R19572ORtns399RTE+t20BBOKxSPm2g=="], + + "@storybook/addon-vitest": ["@storybook/addon-vitest@10.3.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.3.6", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-HXj7RrPJY+xzoNjL+xZu2oLw1fI5BA87Noh1NAXMPuECHR5R5fuRM/tTsJuIGXHFMO06FjSi/rekDIfCj1fL4w=="], + + "@storybook/builder-vite": ["@storybook/builder-vite@10.3.6", "", { "dependencies": { "@storybook/csf-plugin": "10.3.6", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.3.6", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-gpvR/sE4BcrFtmQZ+Ker7zD23oQzoVeqD9nF6cK6yzY+Q0svJXyX2EPmFG4y+EwygD5/vNzDpP84gGMut8VRwg=="], + + "@storybook/csf": ["@storybook/csf@0.1.13", "", { "dependencies": { "type-fest": "^2.19.0" } }, "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q=="], + + "@storybook/csf-plugin": ["@storybook/csf-plugin@10.3.6", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.3.6", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-9kBf7VRdRqTSIYo+rPtVn5yjYYyK8kP2QhEYx3oiXvfwy4RexmbJnhk/tXa/lNiTqukA1TqaWQ2+5MqF4fu6YQ=="], + + "@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="], + + "@storybook/icons": ["@storybook/icons@2.0.2", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-KZBCpXsshAIjczYNXR/rlxEtCUX/eAbpFNwKi8bcOomrLA4t/SyPz5RF+lVPO2oZBUE4sAkt43mfJUevQDSEEw=="], + + "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.3.6", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.3.6" } }, "sha512-/Tu1gPu+Fw+zOnAGmxRmOD30FX3a04LxcTAKflEtdpmtIMVR5bA3qpjy+f5YhoyDCecbXyKmL1OeIU2FIIZHqQ=="], + + "@storybook/svelte": ["@storybook/svelte@10.3.6", "", { "dependencies": { "ts-dedent": "^2.0.0", "type-fest": "~2.19" }, "peerDependencies": { "storybook": "^10.3.6", "svelte": "^5.0.0" } }, "sha512-XE+wNIiztpX6SapuJjYOgZajYWKDMDy/4LVbcqqypOoiYXnO/YOO2p9RdDgD8ta+J88Nap+/qiP7rBbzKOOrOA=="], + + "@storybook/svelte-vite": ["@storybook/svelte-vite@10.3.6", "", { "dependencies": { "@storybook/builder-vite": "10.3.6", "@storybook/svelte": "10.3.6", "magic-string": "^0.30.0", "svelte2tsx": "^0.7.44", "typescript": "^4.9.4 || ^5.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "storybook": "^10.3.6", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-R+Z0TMqLe9XI7yJyfUel9qeZCh2pXUCl4C7SVWec53j9q0qEVcWVleh4Yob1p0dL0cBNMfU5bQN6d56nKVrwTA=="], + + "@storybook/sveltekit": ["@storybook/sveltekit@10.3.6", "", { "dependencies": { "@storybook/builder-vite": "10.3.6", "@storybook/svelte": "10.3.6", "@storybook/svelte-vite": "10.3.6" }, "peerDependencies": { "storybook": "^10.3.6", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-pyQxMcJRhirtF2aWUNEfuSM5MCg4FXcis0Xk9duHIizOQ3pT0rPcY533g9EByzmi3Rm9xYOZVRKllla+G0kV1A=="], + + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="], + + "@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@7.0.1", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ=="], + + "@sveltejs/adapter-node": ["@sveltejs/adapter-node@5.5.4", "", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "rollup": "^4.59.0" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0" } }, "sha512-45X92CXW+2J8ZUzPv3eLlKWEzINKiiGeFWTjyER4ZN4sGgNoaoeSkCY/QYNxHpPXy71QPsctwccBo9jJs0ySPQ=="], + + "@sveltejs/kit": ["@sveltejs/kit@2.59.1", "", { "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.4", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "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 || ^7.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3 || ^6.0.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-d8OON70AphLdDesuTIl//M2O6fRTIicX8aYv8vhCiYEhTTI2OboKqey0Hu1A4VFhqwgqtq0vKDmPFGkw8kKmgw=="], + + "@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-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.2", "", { "dependencies": { "obug": "^2.1.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig=="], + + "@swc/helpers": ["@swc/helpers@0.5.21", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg=="], + + "@tailwindcss/forms": ["@tailwindcss/forms@0.5.11", "", { "dependencies": { "mini-svg-data-uri": "^1.2.3" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" } }, "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.4", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="], + + "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.2.4", "", { "dependencies": { "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "tailwindcss": "4.2.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw=="], + + "@tanstack/match-sorter-utils": ["@tanstack/match-sorter-utils@8.19.4", "", { "dependencies": { "remove-accents": "0.5.0" } }, "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg=="], + + "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], + + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], + + "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="], + + "@testing-library/svelte-core": ["@testing-library/svelte-core@1.0.0", "", { "peerDependencies": { "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0" } }, "sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ=="], + + "@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], + + "@types/node": ["@types/node@22.19.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-9v00a+dn2yWVsYDEunWC4g/TcRKVq3r8N5FuZp7u0SGrPvdN9c2yXI9bBuf5Fl0hNCb+QTIePTn5pJs2pwBOQQ=="], + + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + + "@types/w3c-web-usb": ["@types/w3c-web-usb@1.0.14", "", {}, "sha512-Qu3Nn6JFuF4+sHKYl+IcX9vYiI40ogleXzFFSxoE1W94rG98o/kXs8uJ0QSfFzuwBCZWlGfUGpPkgwuuX4PchA=="], + + "@vitest/browser": ["@vitest/browser@3.2.4", "", { "dependencies": { "@testing-library/dom": "^10.4.0", "@testing-library/user-event": "^14.6.1", "@vitest/mocker": "3.2.4", "@vitest/utils": "3.2.4", "magic-string": "^0.30.17", "sirv": "^3.0.1", "tinyrainbow": "^2.0.0", "ws": "^8.18.2" }, "peerDependencies": { "playwright": "*", "vitest": "3.2.4", "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0" }, "optionalPeers": ["playwright", "webdriverio"] }, "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw=="], + + "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.5", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g=="], + + "@vitest/runner": ["@vitest/runner@4.1.5", "", { "dependencies": { "@vitest/utils": "4.1.5", "pathe": "^2.0.3" } }, "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.5", "", { "dependencies": { "@vitest/pretty-format": "4.1.5", "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ=="], + + "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@webcontainer/env": ["@webcontainer/env@1.1.1", "", {}, "sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng=="], + + "@xterm/addon-attach": ["@xterm/addon-attach@0.12.0", "", {}, "sha512-1lxvXM4JYSm60lbFmE8WMOy2oF2ip3Ye8jWorSAmwy7x8FiC53netEJ5RguL8+FSRj79MUQsNCb2hprY2QA2ig=="], + + "@xterm/addon-clipboard": ["@xterm/addon-clipboard@0.2.0", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-Dl31BCtBhLaUEECUbEiVcCLvLBbaeGYdT7NofB8OJkGTD3MWgBsaLjXvfGAD4tQNHhm6mbKyYkR7XD8kiZsdNg=="], + + "@xterm/addon-fit": ["@xterm/addon-fit@0.11.0", "", {}, "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g=="], + + "@xterm/addon-image": ["@xterm/addon-image@0.9.0", "", {}, "sha512-oYWA8/QAr5/Emwl1xL7WCoOqeG3IZfpzEz/OVq1j4Oi9934TQmHiyubClikRf0D/jL3JNiNuz/Lsqx0kXQ02BA=="], + + "@xterm/addon-progress": ["@xterm/addon-progress@0.2.0", "", {}, "sha512-94uxxYyv30z3+6QIqJhCgALrzZfH7z2ounuZWQvb5Lp8dA7bWZmsUZGi5V7lKsq3Fyif4hTbaxq8YoCsQRtXgg=="], + + "@xterm/addon-search": ["@xterm/addon-search@0.16.0", "", {}, "sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA=="], + + "@xterm/addon-serialize": ["@xterm/addon-serialize@0.14.0", "", {}, "sha512-uteyTU1EkrQa2Ux6P/uFl2fzmXI46jy5uoQMKEOM0fKTyiW7cSn0WrFenHm5vO5uEXX/GpwW/FgILvv3r0WbkA=="], + + "@xterm/addon-unicode11": ["@xterm/addon-unicode11@0.9.0", "", {}, "sha512-FxDnYcyuXhNl+XSqGZL/t0U9eiNb/q3EWT5rYkQT/zuig8Gz/VagnQANKHdDWFM2lTMk9ly0EFQxxxtZUoRetw=="], + + "@xterm/addon-web-links": ["@xterm/addon-web-links@0.12.0", "", {}, "sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw=="], + + "@xterm/addon-webgl": ["@xterm/addon-webgl@0.19.0", "", {}, "sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A=="], + + "@xterm/xterm": ["@xterm/xterm@5.5.0", "", {}, "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="], + + "@yume-chan/adb": ["@yume-chan/adb@2.6.0", "", { "dependencies": { "@yume-chan/async": "^4.1.3", "@yume-chan/event": "^2.0.0", "@yume-chan/no-data-view": "^2.0.0", "@yume-chan/stream-extra": "^2.5.3", "@yume-chan/struct": "^2.3.2" } }, "sha512-1bM4/YwLUr7UHkvC3pe5fIPNNIgOQ97nUIvC4ooPZwRKG9pyHG39EihivZVmu3pm28XwHSGh5PC3sQi4ycoUXA=="], + + "@yume-chan/adb-credential-web": ["@yume-chan/adb-credential-web@2.1.0", "", { "dependencies": { "@yume-chan/adb": "^2.1.0" } }, "sha512-ps5XWt+xxm6Mi9eF80ePprPJ/uRFMhfR437betQE8hixrjdZpkZgMlqZU6GlnuebMi0djrbP8GWB49jJekJVYA=="], + + "@yume-chan/adb-daemon-webusb": ["@yume-chan/adb-daemon-webusb@2.3.2", "", { "dependencies": { "@types/w3c-web-usb": "^1.0.12", "@yume-chan/adb": "^2.3.1", "@yume-chan/event": "^2.0.0", "@yume-chan/stream-extra": "^2.1.0", "@yume-chan/struct": "^2.3.2" } }, "sha512-5ANaqsRJWFc5kKMnGALjYCrSXo5eC4u51dvYppvuBCLVjv12n7he2TPy5yEZ3XtlToRFS0kWCQC0XS8M60lRSA=="], + + "@yume-chan/adb-scrcpy": ["@yume-chan/adb-scrcpy@2.3.2", "", { "dependencies": { "@yume-chan/adb": "^2.3.1", "@yume-chan/async": "^4.1.3", "@yume-chan/event": "^2.0.0", "@yume-chan/scrcpy": "^2.3.0", "@yume-chan/stream-extra": "^2.1.0", "@yume-chan/struct": "^2.3.2" } }, "sha512-wg45dep+dYy4MAVMtsooPAo/aXKiGbpGHzcQWoHnl4uE4YJKrZK0sK5L++rPNM0WbwlPdSpZ32jMJT+i0u1Mdw=="], + + "@yume-chan/async": ["@yume-chan/async@4.1.3", "", {}, "sha512-0vzhNJMkWUPyjKzUK4rqHEeCU6YQtF78RsB1kFRB6Y2BLupmEQNxcSb0mjKabPL9jZpCCiLa5KL8oTOJClUVaw=="], + + "@yume-chan/event": ["@yume-chan/event@2.0.0", "", { "dependencies": { "@yume-chan/async": "^4.0.2" } }, "sha512-z56MDOcX1QlgLUCuA6th3r10negVb7A3gzY//TwSC9ZOvzuRlrAqXcxZf1T3hHfNMk/NFO9RIgQgegXYSfaqLw=="], + + "@yume-chan/no-data-view": ["@yume-chan/no-data-view@2.0.0", "", {}, "sha512-0GRJrrt6wtZlbiE92jocHOnaAvjQ+Y7xwwhwOPqLkwf90Kj1JIHJ5Zh4wJVQSQIkzfRSOpM+jeEQdC2K15snlA=="], + + "@yume-chan/scrcpy": ["@yume-chan/scrcpy@2.3.0", "", { "dependencies": { "@yume-chan/async": "^4.1.3", "@yume-chan/no-data-view": "^2.0.0", "@yume-chan/stream-extra": "^2.1.0", "@yume-chan/struct": "^2.0.1" } }, "sha512-rpsNvy7H8mf6gVsWYRSkIXmk0wVsP3XOdw3kASokwZkSWtygB2Z1wjoPaEAvaayVCjsz+y/YfG35TOcykEihGw=="], + + "@yume-chan/stream-extra": ["@yume-chan/stream-extra@2.5.3", "", { "dependencies": { "@yume-chan/async": "^4.1.3", "@yume-chan/struct": "^2.3.2" } }, "sha512-eRZcl0+SMIt/cAp9UZfBbh74mYe+DZf5l3C6eX8zYYA1cx71nZce2NCf8Vp+/XNw3UtPaEz6IiES9o7Ax5xJdg=="], + + "@yume-chan/struct": ["@yume-chan/struct@2.3.2", "", { "dependencies": { "@yume-chan/async": "^4.1.3", "@yume-chan/no-data-view": "^2.0.0" } }, "sha512-afoCnSKV+5HRK7e4innVd9YTYDyNWdjA1CVQa1j8rWYnmr7HGZfdkHMQr+AESLk6GxWFMzOIPQIG6nYOTGMFIw=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "animejs": ["animejs@4.4.1", "", {}, "sha512-XimhJw4vPb6tAd6Zaoa6BaspJ8UteQMKvL7Pvrt6MW/6u2UvJs0TB/LLqR1sigaiYvpVPC3Tokr9emcCMhWFhw=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], + + "axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "bits-ui": ["bits-ui@2.18.1", "", { "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-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "chromatic": ["chromatic@13.3.5", "", { "peerDependencies": { "@chromatic-com/cypress": "^0.*.* || ^1.0.0", "@chromatic-com/playwright": "^0.*.* || ^1.0.0" }, "optionalPeers": ["@chromatic-com/cypress", "@chromatic-com/playwright"], "bin": { "chroma": "dist/bin.js", "chromatic": "dist/bin.js", "chromatic-cli": "dist/bin.js" } }, "sha512-MzPhxpl838qJUo0A55osCF2ifwPbjcIPeElr1d4SHcjnHoIcg7l1syJDrAYK/a+PcCBrOGi06jPNpQAln5hWgw=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "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=="], + + "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], + + "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], + + "dedent-js": ["dedent-js@1.0.1", "", {}, "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devalue": ["devalue@5.8.0", "", {}, "sha512-2zA9pFEsnp7vWBZbXF5JAgAq0fsUIt/1XPbRiAmRV3lp/2C3upzH+sADiyy66aFCihoLEsrQHxNM5w1gIDfsBg=="], + + "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "enhanced-resolve": ["enhanced-resolve@5.21.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-8p7DUVq6XJnZEz9W4oSwiwycxBIjHjRzYb3Je3zVN+geKTRQKzAkR/K4PBExlS0090d9nshak6phMUxr3PDjmQ=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + + "es-toolkit": ["es-toolkit@1.46.1", "", {}, "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ=="], + + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esrap": ["esrap@1.4.9", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "faye-websocket": ["faye-websocket@0.11.4", "", { "dependencies": { "websocket-driver": ">=0.5.1" } }, "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "filesize": ["filesize@10.1.6", "", {}, "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w=="], + + "firebase": ["firebase@12.14.0", "", { "dependencies": { "@firebase/ai": "2.13.0", "@firebase/analytics": "0.10.22", "@firebase/analytics-compat": "0.2.28", "@firebase/app": "0.14.13", "@firebase/app-check": "0.11.4", "@firebase/app-check-compat": "0.4.4", "@firebase/app-compat": "0.5.13", "@firebase/app-types": "0.9.5", "@firebase/auth": "1.13.2", "@firebase/auth-compat": "0.6.7", "@firebase/data-connect": "0.7.1", "@firebase/database": "1.1.3", "@firebase/database-compat": "2.1.4", "@firebase/firestore": "4.15.0", "@firebase/firestore-compat": "0.4.10", "@firebase/functions": "0.13.5", "@firebase/functions-compat": "0.4.5", "@firebase/installations": "0.6.22", "@firebase/installations-compat": "0.2.22", "@firebase/messaging": "0.13.0", "@firebase/messaging-compat": "0.2.27", "@firebase/performance": "0.7.12", "@firebase/performance-compat": "0.2.25", "@firebase/remote-config": "0.8.4", "@firebase/remote-config-compat": "0.2.25", "@firebase/storage": "0.14.3", "@firebase/storage-compat": "0.4.3", "@firebase/util": "1.15.1" } }, "sha512-aEZ/lniDR1hOCYpx/x/V8Nrrqq9pepKDNkqP/4WGZFC69gTv6F59Z4/54W/SUP4L/hFlrRNmWj35aweQq+IHow=="], + + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "http-parser-js": ["http-parser-js@0.5.10", "", {}, "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA=="], + + "idb": ["idb@8.0.3", "", {}, "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="], + + "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], + + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + + "js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + + "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=="], + + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], + + "mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="], + + "mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + + "node-addon-api": ["node-addon-api@8.7.0", "", {}, "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "paneforge": ["paneforge@1.0.2", "", { "dependencies": { "runed": "^0.23.4", "svelte-toolbelt": "^0.9.2" }, "peerDependencies": { "svelte": "^5.29.0" } }, "sha512-KzmIXQH1wCfwZ4RsMohD/IUtEjVhteR+c+ulb/CHYJHX8SuDXoJmChtsc/Xs5Wl8NHS4L5Q7cxL8MG40gSU1bA=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="], + + "playwright-core": ["playwright-core@1.59.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg=="], + + "postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="], + + "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], + + "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + + "prettier-plugin-svelte": ["prettier-plugin-svelte@3.5.1", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg=="], + + "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.4", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-UKii4RjY05SNt/WQi6/NcOn/LsT0/ILLXsxygjbRg5/YZelsSu5jTqorYHPDGq4nZy5q5hpCu+XdGZ1xaJEQgw=="], + + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + + "protobufjs": ["protobufjs@7.5.6", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.1", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg=="], + + "react": ["react@19.2.6", "", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="], + + "react-dom": ["react-dom@19.2.6", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.6" } }, "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + + "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=="], + + "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + + "remove-accents": ["remove-accents@0.5.0", "", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "rolldown": ["rolldown@1.0.0", "", { "dependencies": { "@oxc-project/types": "=0.129.0", "@rolldown/pluginutils": "1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0", "@rolldown/binding-darwin-arm64": "1.0.0", "@rolldown/binding-darwin-x64": "1.0.0", "@rolldown/binding-freebsd-x64": "1.0.0", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", "@rolldown/binding-linux-arm64-gnu": "1.0.0", "@rolldown/binding-linux-arm64-musl": "1.0.0", "@rolldown/binding-linux-ppc64-gnu": "1.0.0", "@rolldown/binding-linux-s390x-gnu": "1.0.0", "@rolldown/binding-linux-x64-gnu": "1.0.0", "@rolldown/binding-linux-x64-musl": "1.0.0", "@rolldown/binding-openharmony-arm64": "1.0.0", "@rolldown/binding-wasm32-wasi": "1.0.0", "@rolldown/binding-win32-arm64-msvc": "1.0.0", "@rolldown/binding-win32-x64-msvc": "1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA=="], + + "rollup": ["rollup@4.60.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.3", "@rollup/rollup-android-arm64": "4.60.3", "@rollup/rollup-darwin-arm64": "4.60.3", "@rollup/rollup-darwin-x64": "4.60.3", "@rollup/rollup-freebsd-arm64": "4.60.3", "@rollup/rollup-freebsd-x64": "4.60.3", "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", "@rollup/rollup-linux-arm-musleabihf": "4.60.3", "@rollup/rollup-linux-arm64-gnu": "4.60.3", "@rollup/rollup-linux-arm64-musl": "4.60.3", "@rollup/rollup-linux-loong64-gnu": "4.60.3", "@rollup/rollup-linux-loong64-musl": "4.60.3", "@rollup/rollup-linux-ppc64-gnu": "4.60.3", "@rollup/rollup-linux-ppc64-musl": "4.60.3", "@rollup/rollup-linux-riscv64-gnu": "4.60.3", "@rollup/rollup-linux-riscv64-musl": "4.60.3", "@rollup/rollup-linux-s390x-gnu": "4.60.3", "@rollup/rollup-linux-x64-gnu": "4.60.3", "@rollup/rollup-linux-x64-musl": "4.60.3", "@rollup/rollup-openbsd-x64": "4.60.3", "@rollup/rollup-openharmony-arm64": "4.60.3", "@rollup/rollup-win32-arm64-msvc": "4.60.3", "@rollup/rollup-win32-ia32-msvc": "4.60.3", "@rollup/rollup-win32-x64-gnu": "4.60.3", "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "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=="], + + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "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=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + + "storybook": ["storybook@10.3.6", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-vbSz7g/1rGMC1uAULqMZjALkIuLu2QABqfhRYhyr/11kzyesi+vAmwyJLukZP1FfecxGOgMwOh6GS0YsGpHAvQ=="], + + "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=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "svelte": ["svelte@5.55.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw=="], + + "svelte-adapter-bun": ["svelte-adapter-bun@1.0.1", "", { "dependencies": { "rolldown": "^1.0.0-beta.38" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0", "typescript": "^5" } }, "sha512-tNOvfm8BGgG+rmEA7hkmqtq07v7zoo4skLQc+hIoQ79J+1fkEMpJEA2RzCIe3aPc8JdrsMJkv3mpiZPMsgahjA=="], + + "svelte-ast-print": ["svelte-ast-print@0.4.2", "", { "dependencies": { "esrap": "1.2.2", "zimmerframe": "1.1.2" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-hRHHufbJoArFmDYQKCpCvc0xUuIEfwYksvyLYEQyH+1xb5LD5sM/IthfooCdXZQtOIqXz6xm7NmaqdfwG4kh6w=="], + + "svelte-check": ["svelte-check@4.4.8", "", { "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-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w=="], + + "svelte-sonner": ["svelte-sonner@1.1.1", "", { "dependencies": { "runed": "^0.28.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-5cd3p7wa4cq0NsqslMwdlPb7x1JglEZ/GKrLePWNr5bCxR1nagAVrY01FRFrXfUGs41miLt3C327+8XJo5BzZw=="], + + "svelte-toolbelt": ["svelte-toolbelt@0.10.6", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.35.1", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ=="], + + "svelte2tsx": ["svelte2tsx@0.7.55", "", { "dependencies": { "dedent-js": "^1.0.1", "scule": "^1.3.0" }, "peerDependencies": { "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0", "typescript": "^4.9.4 || ^5.0.0 || ^6.0.0" } }, "sha512-JWzgeM3lqySRNfqcsesvVEh8LhTWBxQJ9RMjzJ+VepdmXtVnNd0SbtGctG6+/fbHq0N6mhwSd823gszw9JHeGQ=="], + + "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], + + "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], + + "tailwind-variants": ["tailwind-variants@3.2.2", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="], + + "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + + "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], + + "usb": ["usb@2.17.0", "", { "dependencies": { "@types/w3c-web-usb": "^1.0.6", "node-addon-api": "^8.0.0", "node-gyp-build": "^4.5.0" } }, "sha512-UuFgrlglgDn5ll6d5l7kl3nDb2Yx43qLUGcDq+7UNLZLtbNug0HZBb2Xodhgx2JZB1LqvU+dOGqLEeYUeZqsHg=="], + + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@13.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw=="], + + "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.3", "", { "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-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA=="], + + "vite-plugin-devtools-json": ["vite-plugin-devtools-json@1.0.0", "", { "dependencies": { "uuid": "^11.1.0" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw=="], + + "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], + + "vitest": ["vitest@4.1.5", "", { "dependencies": { "@vitest/expect": "4.1.5", "@vitest/mocker": "4.1.5", "@vitest/pretty-format": "4.1.5", "@vitest/runner": "4.1.5", "@vitest/snapshot": "4.1.5", "@vitest/spy": "4.1.5", "@vitest/utils": "4.1.5", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.5", "@vitest/browser-preview": "4.1.5", "@vitest/browser-webdriverio": "4.1.5", "@vitest/coverage-istanbul": "4.1.5", "@vitest/coverage-v8": "4.1.5", "@vitest/ui": "4.1.5", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg=="], + + "vitest-browser-svelte": ["vitest-browser-svelte@2.1.1", "", { "dependencies": { "@testing-library/svelte-core": "^1.0.0" }, "peerDependencies": { "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", "vitest": "^4.0.0" } }, "sha512-qbunYRSm+N92r9bfTkdDTpBZESLmp4QFz2SluV3n/x8U7ysosfeXYJZ4vXbJ0Y0LzoqqDnV5LHprmFgn4Eo+Ug=="], + + "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "websocket-driver": ["websocket-driver@0.7.4", "", { "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg=="], + + "websocket-extensions": ["websocket-extensions@0.1.4", "", {}, "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "wrap-ansi": ["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=="], + + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + + "xterm": ["xterm@5.3.0", "", {}, "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg=="], + + "xterm-addon-fit": ["xterm-addon-fit@0.8.0", "", { "peerDependencies": { "xterm": "^5.0.0" } }, "sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], + + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@battlefieldduck/xterm-svelte/@xterm/xterm": ["@xterm/xterm@6.0.0", "", {}, "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg=="], + + "@dnd-kit-svelte/accessibility/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], + + "@dnd-kit-svelte/core/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], + + "@dnd-kit-svelte/core/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=="], + + "@dnd-kit-svelte/svelte/@dnd-kit/abstract": ["@dnd-kit/abstract@0.1.21", "", { "dependencies": { "@dnd-kit/geometry": "^0.1.21", "@dnd-kit/state": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-6sJut6/D21xPIK8EFMu+JJeF+fBCOmQKN1BRpeUYFi5m9P1CJpTYbBwfI107h7PHObI6a5bsckiKkRpF2orHpw=="], + + "@dnd-kit-svelte/utilities/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=="], + + "@dnd-kit/abstract/@dnd-kit/state": ["@dnd-kit/state@0.2.4", "", { "dependencies": { "@preact/signals-core": "^1.10.0", "tslib": "^2.6.2" } }, "sha512-mPtQfmqBZBcVKa+Fi8AV2ocQWa+phvZa9Muq3PxFM3WIyjrFiEosGGPiPcx5hC3K+i2gkmr6Msc7iPrrpYkB5g=="], + + "@dnd-kit/collision/@dnd-kit/abstract": ["@dnd-kit/abstract@0.1.21", "", { "dependencies": { "@dnd-kit/geometry": "^0.1.21", "@dnd-kit/state": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-6sJut6/D21xPIK8EFMu+JJeF+fBCOmQKN1BRpeUYFi5m9P1CJpTYbBwfI107h7PHObI6a5bsckiKkRpF2orHpw=="], + + "@dnd-kit/collision/@dnd-kit/geometry": ["@dnd-kit/geometry@0.1.21", "", { "dependencies": { "@dnd-kit/state": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-Tir97wNJbopN2HgkD7AjAcoB3vvrVuUHvwdPALmNDUH0fWR637c4MKQ66YjjZAbUEAR8KL6mlDiHH4MzTLd7CQ=="], + + "@dnd-kit/dom/@dnd-kit/abstract": ["@dnd-kit/abstract@0.1.21", "", { "dependencies": { "@dnd-kit/geometry": "^0.1.21", "@dnd-kit/state": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-6sJut6/D21xPIK8EFMu+JJeF+fBCOmQKN1BRpeUYFi5m9P1CJpTYbBwfI107h7PHObI6a5bsckiKkRpF2orHpw=="], + + "@dnd-kit/dom/@dnd-kit/geometry": ["@dnd-kit/geometry@0.1.21", "", { "dependencies": { "@dnd-kit/state": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-Tir97wNJbopN2HgkD7AjAcoB3vvrVuUHvwdPALmNDUH0fWR637c4MKQ66YjjZAbUEAR8KL6mlDiHH4MzTLd7CQ=="], + + "@dnd-kit/geometry/@dnd-kit/state": ["@dnd-kit/state@0.2.4", "", { "dependencies": { "@preact/signals-core": "^1.10.0", "tslib": "^2.6.2" } }, "sha512-mPtQfmqBZBcVKa+Fi8AV2ocQWa+phvZa9Muq3PxFM3WIyjrFiEosGGPiPcx5hC3K+i2gkmr6Msc7iPrrpYkB5g=="], + + "@firebase/app/idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="], + + "@firebase/installations/idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="], + + "@firebase/messaging/idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="], + + "@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], + + "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "@vitest/pretty-format/tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "@vitest/runner/@vitest/utils": ["@vitest/utils@4.1.5", "", { "dependencies": { "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug=="], + + "@vitest/snapshot/@vitest/utils": ["@vitest/utils@4.1.5", "", { "dependencies": { "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug=="], + + "@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="], + + "mode-watcher/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=="], + + "paneforge/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], + + "paneforge/svelte-toolbelt": ["svelte-toolbelt@0.9.3", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.29.0", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw=="], + + "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "svelte/esrap": ["esrap@2.2.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-WN0clHt0a4mzC780UBVVBpsj4vSSjOFNRd2WjYtduB9HeKxm1sjHMNUwLEHVjI3FdCQD/Hurgz9ftbKEzP79Ow=="], + + "svelte-ast-print/esrap": ["esrap@1.2.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@types/estree": "^1.0.1" } }, "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw=="], + + "svelte-ast-print/zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], + + "svelte-sonner/runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="], + + "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/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "vite-plugin-devtools-json/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + + "vitest/@vitest/expect": ["@vitest/expect@4.1.5", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.5", "@vitest/utils": "4.1.5", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw=="], + + "vitest/@vitest/mocker": ["@vitest/mocker@4.1.5", "", { "dependencies": { "@vitest/spy": "4.1.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw=="], + + "vitest/@vitest/spy": ["@vitest/spy@4.1.5", "", {}, "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ=="], + + "vitest/@vitest/utils": ["@vitest/utils@4.1.5", "", { "dependencies": { "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug=="], + + "vitest/tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@dnd-kit-svelte/svelte/@dnd-kit/abstract/@dnd-kit/geometry": ["@dnd-kit/geometry@0.1.21", "", { "dependencies": { "@dnd-kit/state": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-Tir97wNJbopN2HgkD7AjAcoB3vvrVuUHvwdPALmNDUH0fWR637c4MKQ66YjjZAbUEAR8KL6mlDiHH4MzTLd7CQ=="], + + "@dnd-kit-svelte/utilities/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], + + "@vitest/runner/@vitest/utils/tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "@vitest/snapshot/@vitest/utils/tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "mode-watcher/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], + + "paneforge/svelte-toolbelt/runed": ["runed@0.29.2", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA=="], + + "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "vitest/@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/bun.lockb b/bun.lockb deleted file mode 100755 index 52bad83..0000000 Binary files a/bun.lockb and /dev/null differ diff --git a/package.json b/package.json index e8cdbaa..eb20ce4 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@yume-chan/scrcpy": "^2.3.0", "@yume-chan/stream-extra": "^2.5.3", "animejs": "^4.3.6", - "firebase": "^12.11.0", + "firebase": "^12.14.0", "idb": "^8.0.3", "mode-watcher": "^1.1.0", "usb": "^2.17.0", diff --git a/src/app.d.ts b/src/app.d.ts index da08e6d..ef3fdab 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -10,4 +10,9 @@ declare global { } } +declare module '*?raw' { + const content: string; + export default content; +} + export {}; diff --git a/src/lib/components/android-recipe-export-view.svelte b/src/lib/components/android-recipe-export-view.svelte new file mode 100644 index 0000000..435478c --- /dev/null +++ b/src/lib/components/android-recipe-export-view.svelte @@ -0,0 +1,618 @@ + + +
+
+
+
+

Android Recipe Export

+ + + {isAdbConnected ? 'ADB Connected' : 'ADB Offline'} + + {#if isAdbConnected} + + + {isAndroidSocketConnected ? 'Socket Connected' : 'ADB File Mode'} + + {/if} +
+

+ Receive exported recipe data from Machine. + {#if lastLoadedAt} + Last loaded {lastLoadedAt} + {/if} +

+
+ +
+
+ + +
+ {#if isAdbConnected} + + {/if} + {#if exportPayload} + + {/if} + {#if !isAdbConnected} + + {/if} +
+
+ + {#if error} +
+ {error} +
+ {/if} + + {#if parsed.headers.length > 0} +
+
+ {#if totalFilteredRows !== totalRows} + Showing {visibleRows.length} of {totalFilteredRows} matching rows. Page {currentPage} of + {totalPages}. + {:else} + Showing rows {pageStartIndex + 1}-{pageStartIndex + visibleRows.length} of {totalRows}. + Page {currentPage} of {totalPages}. + {/if} +
+ +
+
+ {versionLabel} +
+ + + {#if fieldRow} + + + {#each fieldRow.cells as cell, cellIndex} + + {/each} + + + {/if} + + {#each visibleRows as row} + + {#each row.cells as cell, cellIndex} + {@const emptyCell = isEmptyCell(cell)} + {@const ingredientCell = !emptyCell && isIngredientColumn(cellIndex)} + + {/each} + + {/each} + +
+ {cell || `Column ${cellIndex + 1}`} +
+ {#if cellIndex === 0} + + {:else} + {cell || '-'} + {/if} +
+
+ +
+

+ Page {currentPage} / {totalPages} +

+
+ + +
+
+
+ {:else if !isAdbConnected} +
+

Connect Android board first

+

+ Connect ADB to load the exported recipe file from Android. +

+ +
+ {:else if !isAndroidSocketConnected} +
+

Load exported recipe file

+

+ ADB is connected. Load the latest exported file from + {ANDROID_RECIPE_EXPORT_PATH}. +

+
+ +
+
+ {:else if parsingExport} +
+

Preparing recipe table

+

Recipe export received. Preparing the table pages.

+
+ {:else if parsed.headers.length === 0} +
+

Preparing recipe data

+

Please wait while the latest recipe list is loading.

+
+ {/if} +
+ + + + {@const selectedIngredients = getMenuIngredients(selectedMenuRow)} + + +
+
+ + + +
+
+

+ {selectedMenuRow?.cells[0] || 'Menu'} +

+

+ {#if selectedIngredients.length > 0} + {selectedIngredients.length} ingredient{selectedIngredients.length > 1 ? 's' : ''} used + {:else} + No ingredients + {/if} +

+
+
+ + + {#if selectedIngredients.length > 0} +
+ {#each selectedIngredients as ingredient, i} +
+
+ {i + 1} +
+
+

{ingredient.name}

+
+
+ + {ingredient.value} + +
+
+ {/each} +
+ {:else} +
+
+ + + +
+

No ingredients found

+

This menu has no ingredient values

+
+ {/if} + + +
+ +
+
+
diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte index d9fa564..d7475dd 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -3,9 +3,7 @@ import { onDestroy, type ComponentProps } from 'svelte'; import { asset } from '$app/paths'; import AppAccountSelect from './app-account-select.svelte'; - import { needPermission } from '$lib/core/handlers/permissionHandler'; import { - Code, LayoutDashboard, LucideEye, CherryIcon, @@ -13,21 +11,43 @@ BugIcon, CupSodaIcon, Shield, - FileSpreadsheet + FileSpreadsheet, + MonitorSmartphone, + PlusCircle, + ImageUp, + Video, + Sun, + Moon } from '@lucide/svelte/icons'; import TaobinLogo from '$lib/assets/logo.svelte'; import { goto } from '$app/navigation'; import Button from '$lib/components/ui/button/button.svelte'; import { sidebarStore } from '$lib/core/stores/sidebar'; import { auth } from '$lib/core/stores/auth'; + import { permission as permissionStore } from '$lib/core/stores/permissions'; import { isUserAdmin } from '$lib/core/admin/adminService'; import { referenceFromPage } from '$lib/core/stores/recipeStore'; import { env } from '$env/dynamic/public'; + import { toggleMode, mode } from 'mode-watcher'; let sideBar: HTMLElement | null = $state(null); let isSideBarOpen: boolean = $state(true); let isAdmin: boolean = $state(false); + // Helper function to check permission (reactive version) + function checkPermission(requiredPerm: string, userPerms: string[]): boolean { + if (!requiredPerm) return true; + + const reqParts = requiredPerm.split('.'); + return userPerms.some((userPerm) => { + const userParts = userPerm.split('.'); + if (userParts.length !== reqParts.length) return false; + return reqParts.every( + (part, i) => part === '*' || userParts[i] === '*' || part === userParts[i] + ); + }); + } + const app_version = env.PUBLIC_APP_VERSION; const data = { @@ -75,11 +95,35 @@ icon: CupSodaIcon, requirePerm: '' }, + { + title: 'Create Menu', + url: '/tools/create-menu', + icon: PlusCircle, + requirePerm: '' + }, { title: 'Debug', url: '/tools/debug', icon: BugIcon, requirePerm: '' + }, + { + title: 'Android Recipes', + url: '/tools/android-recipe', + icon: MonitorSmartphone, + requirePerm: '' + }, + { + title: 'Image Upload', + url: '/tools/image-upload', + icon: ImageUp, + requirePerm: '' + }, + { + title: 'Adv Upload', + url: '/tools/adv-upload', + icon: Video, + requirePerm: '' } ] }, @@ -136,13 +180,13 @@ unsubSidebar(); }); + // Reactive: re-filter when $permissionStore changes let authorizedNavMain = $derived( data.navMain .map((nav) => { const filteredItems = nav.items.filter((item) => { if (!item.requirePerm) return true; - - return needPermission(item.requirePerm); + return checkPermission(item.requirePerm, $permissionStore); }); return { ...nav, items: filteredItems }; @@ -160,8 +204,8 @@
-

@@ -212,17 +256,7 @@ {#snippet child({ props })} - { - if (nav.title === 'Sheet') { - e.preventDefault(); - referenceFromPage.set('sheet'); - goto(sub.url); - } - }} - > + {#if sub.icon} {/if} @@ -238,6 +272,26 @@ {/if} +

diff --git a/src/lib/components/recipe-details/recipe-detail.svelte b/src/lib/components/recipe-details/recipe-detail.svelte index 6e68b03..4832ac2 100644 --- a/src/lib/components/recipe-details/recipe-detail.svelte +++ b/src/lib/components/recipe-details/recipe-detail.svelte @@ -112,7 +112,14 @@ async function getCurrentQueue() { let inst = adb.getAdbInstance(); if (inst) { - let current_brewing = await adb.pull(env.PUBLIC_BREW_CURRENT_RECIPE); + const currentRecipePath = env.PUBLIC_BREW_CURRENT_RECIPE; + if (!currentRecipePath) { + return { + error: 'PUBLIC_BREW_CURRENT_RECIPE is not configured' + }; + } + + let current_brewing = await adb.pull(currentRecipePath); // console.log(`current brewing queue: ${current_brewing}`); if (current_brewing === '') { current_brewing = '{}'; diff --git a/src/lib/components/recipe-editor-dialog.svelte b/src/lib/components/recipe-editor-dialog.svelte index 1a4ab83..e593d4b 100644 --- a/src/lib/components/recipe-editor-dialog.svelte +++ b/src/lib/components/recipe-editor-dialog.svelte @@ -298,6 +298,21 @@ } } + async function saveRecipeMenuFileToAndroid() { + if (refPage != 'brew') return; + + const recipeMenu = ready_to_send_brew[0] ?? $state.snapshot(currentData); + if (!recipeMenu?.productCode) { + addNotification('ERR:Recipe data is not ready to save'); + return; + } + + const sent = await adb.sendRecipeMenuFileToAndroid(recipeMenu); + if (sent) { + addNotification(`INFO:Save recipe menu file request sent: ${recipeMenu.productCode}`); + } + } + function onCloseDialog() { currentEditingRecipeProductCode.set(''); callback_revert_value_if_not_save(save_change); @@ -404,6 +419,7 @@ save_change = true; callback_revert_value_if_not_save(save_change); + await saveRecipeMenuFileToAndroid(); addNotification('INFO:Save recipe'); }} diff --git a/src/lib/core/adb/adb.ts b/src/lib/core/adb/adb.ts index 6abe7ea..2cab7d5 100644 --- a/src/lib/core/adb/adb.ts +++ b/src/lib/core/adb/adb.ts @@ -14,9 +14,53 @@ import { handleAdbPayload } from '../handlers/adbPayloadHandler'; import { adbWriter } from '../stores/adbWriter'; import { WritableStream } from '@yume-chan/stream-extra'; import { env } from '$env/dynamic/public'; -import type Dice_2 from '@lucide/svelte/icons/dice-2'; +import { get } from 'svelte/store'; let syncConnection: any = null; +let syncOperation: Promise = Promise.resolve(); +let recipeMenuAdbConnectPromise: Promise | null = null; +let recipeMenuAndroidServerConnectPromise: Promise | null = null; +let recipeMenuAndroidServerRetryTimer: ReturnType | null = null; + +async function runSyncOperation(operation: () => Promise) { + const run = syncOperation.then(operation, operation); + syncOperation = run.catch(() => {}); + return await run; +} + +function clearRecipeMenuAndroidServerRetry() { + if (recipeMenuAndroidServerRetryTimer) { + clearTimeout(recipeMenuAndroidServerRetryTimer); + recipeMenuAndroidServerRetryTimer = null; + } +} + +function scheduleRecipeMenuAndroidServerReconnect(delayMs = 2000) { + if (recipeMenuAndroidServerRetryTimer || !getAdbInstance()) return; + + recipeMenuAndroidServerRetryTimer = setTimeout(() => { + recipeMenuAndroidServerRetryTimer = null; + void connectToAndroidRecipeMenuServer(false); + }, delayMs); +} + +async function connectRecipeMenuWebUsbDevice( + device: AdbDaemonWebUsbDevice, + credentialStore: AdbWebCredentialStore +) { + const connection = await device.connect(); + const transport = await AdbDaemonTransport.authenticate({ + connection: connection, + serial: device.serial, + credentialStore: credentialStore + }); + + const adb = new Adb(transport); + await saveAdbInstance(adb); + await connectToAndroidRecipeMenuServer(); + + return adb; +} function isRecoverableError(error: any): boolean { if (!error) return false; @@ -91,7 +135,7 @@ async function connectWithRetry( ); } -export async function connnectViaWebUSB() { +export async function connnectViaWebUSB(connectAndroidServer = true) { const device = await AdbDaemonWebUsbDeviceManager.BROWSER?.requestDevice(); console.log('usb ok', globalThis.navigator.usb); if (device) { @@ -109,7 +153,9 @@ export async function connnectViaWebUSB() { const adb = new Adb(transport); await saveAdbInstance(adb); - await connectToAndroidServer(); + if (connectAndroidServer) { + await connectToAndroidServer(); + } // save device info await deviceCredentialManager.saveDeviceInfo(device); @@ -129,7 +175,8 @@ export async function connnectViaWebUSB() { export async function connectDeviceByCred( device: AdbDaemonWebUsbDevice, - credStore: AdbWebCredentialStore + credStore: AdbWebCredentialStore, + connectAndroidServer = true ) { try { const connection = await device.connect(); @@ -142,7 +189,9 @@ export async function connectDeviceByCred( const adb = new Adb(transport); await saveAdbInstance(adb); - await connectToAndroidServer(); + if (connectAndroidServer) { + await connectToAndroidServer(); + } return true; } catch (error) { @@ -159,6 +208,112 @@ export function getAdbInstance() { return AdbInstance.instance; } +export async function connectRecipeMenuViaWebUSB() { + const currentInstance = getAdbInstance(); + if (currentInstance) { + await connectToAndroidRecipeMenuServer(); + return currentInstance; + } + if (recipeMenuAdbConnectPromise) return await recipeMenuAdbConnectPromise; + + const device = await AdbDaemonWebUsbDeviceManager.BROWSER?.requestDevice(); + console.log('recipe menu usb ok', 'usb' in globalThis.navigator); + if (device) { + console.log('recipe menu connect ', device.name); + + try { + const credentialStore = new AdbWebCredentialStore(); + recipeMenuAdbConnectPromise = connectRecipeMenuWebUsbDevice(device, credentialStore); + const adb = await recipeMenuAdbConnectPromise; + + await deviceCredentialManager.saveDeviceInfo(device); + return adb; + } catch (e: any) { + console.error('recipe menu connect error', e); + + if (e instanceof AdbDaemonWebUsbDevice.DeviceBusyError) { + addNotification( + 'ERR:Device is already in use by another program, please close the program and try again' + ); + } + + throw e; + } finally { + recipeMenuAdbConnectPromise = null; + } + } +} + +export async function connectRecipeMenuDeviceByCred( + device: AdbDaemonWebUsbDevice, + credStore: AdbWebCredentialStore +) { + const currentInstance = getAdbInstance(); + if (currentInstance) { + await connectToAndroidRecipeMenuServer(); + return true; + } + + if (recipeMenuAdbConnectPromise) { + await recipeMenuAdbConnectPromise; + return Boolean(getAdbInstance()); + } + + try { + recipeMenuAdbConnectPromise = connectRecipeMenuWebUsbDevice(device, credStore); + await recipeMenuAdbConnectPromise; + + return true; + } catch (error) { + throw error; + } finally { + recipeMenuAdbConnectPromise = null; + } +} + +export async function reconnectAndroidRecipeMenuServer() { + await connectToAndroidRecipeMenuServer(true); +} + +export async function sendRecipeMenuFileToAndroid(recipe: any) { + return await sendRecipeMenuMessageToAndroid({ + type: 'save_recipe_menu_file', + payload: { + time: new Date().toLocaleTimeString(), + data: recipe + } + }); +} + +export async function sendRecipeMenuMessageToAndroid(message: any) { + let writer: any = get(adbWriter); + + if (!writer) { + if (getAdbInstance()) { + await connectToAndroidRecipeMenuServer(false); + } else { + await connectRecipeMenuViaWebUSB(); + } + writer = get(adbWriter); + } + + if (!writer) { + addNotification('ERR:No active Android recipe connection'); + return false; + } + + try { + const encoder = new TextEncoder(); + await writer.write(encoder.encode(JSON.stringify(message) + '\n')); + console.log('recipe menu sent! ', JSON.stringify(message).length); + return true; + } catch (error) { + console.error('recipe menu write failed', error); + addNotification(`ERR:Failed to send recipe menu\n${error}`); + return false; + } +} + export async function executeCmd(command: string) { let instance = getAdbInstance(); @@ -232,64 +387,117 @@ export async function cleanupSync() { } export async function pull(filename: string, timeoutMs: number = 5000) { - let instance = getAdbInstance(); + return await runSyncOperation(async () => { + let instance = getAdbInstance(); - await cleanupSync(); - - try { - if (instance) { - let chunkList: Uint8Array[] = []; - const syncProm = instance.sync(); - const timeoutProm = new Promise((_, reject) => { - setTimeout(() => reject(new Error('sync timeout')), timeoutMs); - }); - - syncConnection = await Promise.race([syncProm, timeoutProm]); - const content = syncConnection.read(filename); - let result_string = ''; - - for await (const chunk of content) { - result_string += new TextDecoder().decode(chunk); - } - - return result_string; - } - } catch (pull_error: any) { - console.log('pulling error', pull_error); - } finally { await cleanupSync(); - } + + try { + if (instance) { + let chunkList: Uint8Array[] = []; + const syncProm = instance.sync(); + const timeoutProm = new Promise((_, reject) => { + setTimeout(() => reject(new Error('sync timeout')), timeoutMs); + }); + + syncConnection = await Promise.race([syncProm, timeoutProm]); + const content = syncConnection.read(filename); + let result_string = ''; + + for await (const chunk of content) { + result_string += new TextDecoder().decode(chunk); + } + + return result_string; + } + } catch (pull_error: any) { + console.log('pulling error', pull_error); + } finally { + await cleanupSync(); + } + }); } export async function push(path: string, obj: string) { - let instance = getAdbInstance(); - if (instance) { - let sync = await instance.sync(); - const encoder = new TextEncoder(); + return await runSyncOperation(async () => { + let instance = getAdbInstance(); + if (instance) { + let sync = await instance.sync(); + const encoder = new TextEncoder(); + const file: ReadableStream> = new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array(encoder.encode(obj))); + controller.close(); + } + }); + + try { + console.log('support push v2', sync.supportsSendReceiveV2); + + await sync.write({ + filename: path, + file + }); + } catch (error) { + console.log('error while trying to write to machine', error); + } finally { + await sync.dispose(); + } + } + }); +} + +// Push a binary file (e.g. an .mp4 video) to the machine. Unlike push() which +// text-encodes a string, this streams raw bytes in chunks so binary data is not +// corrupted, and reports progress. +export async function pushBinary( + path: string, + data: Uint8Array, + onProgress?: (sent: number, total: number) => void +): Promise { + return await runSyncOperation(async () => { + let instance = getAdbInstance(); + if (!instance) return false; + + const total = data.byteLength; + onProgress?.(0, total); + + let sync = await instance.sync(); + + // Mirror the working text push(): a single enqueue then close. @yume-chan + // packetizes internally for the ADB protocol; a multi-chunk or pull-based + // stream can stall the transfer. const file: ReadableStream> = new ReadableStream({ start(controller) { - controller.enqueue(new Uint8Array(encoder.encode(obj))); + controller.enqueue(data); controller.close(); } }); try { - console.log('support push v2', sync.supportsSendReceiveV2); - - await sync.write({ - filename: path, - file - }); + const writeProm = sync.write({ filename: path, file }); + // Safety net so a stalled transfer can't hang the UI forever. + const timeoutProm = new Promise((_, reject) => + setTimeout(() => reject(new Error('push write timeout (120s)')), 120000) + ); + await Promise.race([writeProm, timeoutProm]); + onProgress?.(total, total); + return true; } catch (error) { - console.log('error while trying to write to machine', error); + console.log('error while pushing binary to machine', error); + return false; } finally { await sync.dispose(); } - } + }); } // NOTE: adb reverse is not work by unavailable features support +export async function reconnectAndroidServer() { + await connectToAndroidServer(); +} + async function connectToAndroidServer(maxRetries = 5) { let lastError: any; for (let attempt = 0; attempt < maxRetries; attempt++) { @@ -300,10 +508,12 @@ async function connectToAndroidServer(maxRetries = 5) { return; } + const brewConnectionPort = env.PUBLIC_BREW_CONN_PORT || 'tcp:36588'; + // add retry mechanism const stream = await connectWithRetry( - async () => inst.transport.connect(env.PUBLIC_BREW_CONN_PORT), - `connect to Android server port ${env.PUBLIC_BREW_CONN_PORT}`, + async () => inst.transport.connect(brewConnectionPort), + `connect to Android server port ${brewConnectionPort}`, 3, 500 ); @@ -316,22 +526,24 @@ async function connectToAndroidServer(maxRetries = 5) { if (writer) { addNotification('INFO:Enable Brewing Mode T on machine'); - try { - while (true) { - const { value, done } = await reader.read(); - if (done) break; - handleAdbPayload(new TextDecoder().decode(value)); + (async () => { + try { + while (true) { + const { value, done } = await reader.read(); + if (done) break; + handleAdbPayload(new TextDecoder().decode(value)); + } + } catch (e) { + console.error('read error', e); + if (isRecoverableError(e)) { + void connectToAndroidServer(); + } + } finally { + adbWriter.set(null); + addNotification('WARN:Brewing Mode T Offline ...'); } - } catch (e) { - console.error('read error', e); - if (isRecoverableError(e)) { - throw e; - } - throw e; - } finally { - adbWriter.set(null); - addNotification('WARN:Brewing Mode T Offline ...'); - } + })(); + return; } else { addNotification('WARN:Brewing Mode T unavailable'); @@ -366,6 +578,94 @@ async function connectToAndroidServer(maxRetries = 5) { } } +async function connectToAndroidRecipeMenuServer(notifyFailure = true, retryOnFailure = false) { + if (recipeMenuAndroidServerConnectPromise) return recipeMenuAndroidServerConnectPromise; + + recipeMenuAndroidServerConnectPromise = connectToAndroidRecipeMenuServerOnce( + notifyFailure, + retryOnFailure + ).finally(() => { + recipeMenuAndroidServerConnectPromise = null; + }); + + return recipeMenuAndroidServerConnectPromise; +} + +async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryOnFailure = false) { + try { + let inst = getAdbInstance(); + if (!inst) { + console.warn('recipe menu adb instance not found'); + return; + } + + const brewConnectionPort = env.PUBLIC_BREW_CONN_PORT || 'tcp:36588'; + + clearRecipeMenuAndroidServerRetry(); + const stream = await inst.transport.connect(brewConnectionPort); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + + console.log('checking recipe menu writer ', writer); + adbWriter.set(writer); + if (writer) { + addNotification('INFO:Enable Android recipe menu channel'); + } else { + addNotification('WARN:Android recipe menu channel unavailable'); + + setTimeout(async () => { + console.log('reconnecting android recipe menu server'); + await connectToAndroidRecipeMenuServer(); + }, 5000); + } + + (async () => { + try { + let messageBuffer = ''; + const decoder = new TextDecoder(); + while (true) { + const { value, done } = await reader.read(); + if (done) break; + + const decoded = decoder.decode(value, { stream: true }); + console.log('[ADB Reader] Received raw:', decoded.slice(0, 200)); + messageBuffer += decoded; + const messages = messageBuffer.split('\n'); + messageBuffer = messages.pop() ?? ''; + + for (const message of messages) { + const trimmedMessage = message.trim(); + if (trimmedMessage) { + console.log('[ADB Reader] Processing message:', trimmedMessage.slice(0, 200)); + handleAdbPayload(trimmedMessage); + } + } + } + + const remainingMessage = messageBuffer.trim(); + if (remainingMessage) { + handleAdbPayload(remainingMessage); + } + } catch (e) { + console.error('recipe menu read error', e); + } finally { + adbWriter.set(null); + addNotification('WARN:Android recipe menu channel offline ...'); + if (retryOnFailure) { + scheduleRecipeMenuAndroidServerReconnect(); + } + } + })(); + } catch (err) { + console.error('Recipe menu connection failed. Suspect java running or not', err); + adbWriter.set(null); + if (notifyFailure) addNotification('ERR:Fail to enable Android recipe menu channel'); + if (retryOnFailure) { + scheduleRecipeMenuAndroidServerReconnect(); + } + } +} + // logcat stream // TODO: screen mirror diff --git a/src/lib/core/auth/domainBlocker.ts b/src/lib/core/auth/domainBlocker.ts index 3558bdc..f703f48 100644 --- a/src/lib/core/auth/domainBlocker.ts +++ b/src/lib/core/auth/domainBlocker.ts @@ -7,7 +7,6 @@ export async function checkAllowAccess(userDomain: string): Promise { if (snapshot.exists()) { let domains = snapshot.data(); - // console.log(`domains: ${JSON.stringify(domains)}`); return domains['account_email'].includes(userDomain); } diff --git a/src/lib/core/brew/command.ts b/src/lib/core/brew/command.ts index 9d44f81..80b2d51 100644 --- a/src/lib/core/brew/command.ts +++ b/src/lib/core/brew/command.ts @@ -18,8 +18,11 @@ async function sendCommand(type: string, params?: string[]) { let inst = adb.getAdbInstance(); if (inst) { try { + const commandPath = env.PUBLIC_BREW_CMD_WEB; + if (!commandPath) throw new BrewCommandError('PUBLIC_BREW_CMD_WEB is not configured'); + let cmd = type + ' ' + (params?.join(' ') ?? ''); - await adb.push(env.PUBLIC_BREW_CMD_WEB, cmd); + await adb.push(commandPath, cmd); } catch (e) { throw new BrewCommandError('Command failed', `${e}`); } @@ -32,9 +35,18 @@ async function sendReset() { let inst = adb.getAdbInstance(); if (inst) { try { - await adb.push(env.PUBLIC_BREW_CMD_WEB, ''); - await adb.push(env.PUBLIC_BREW_CURRENT_RECIPE, ''); - await adb.push(env.PUBLIC_BREW_WEB_STATUS, ''); + const commandPath = env.PUBLIC_BREW_CMD_WEB; + const currentRecipePath = env.PUBLIC_BREW_CURRENT_RECIPE; + const statusPath = env.PUBLIC_BREW_WEB_STATUS; + + if (!commandPath) throw new BrewCommandError('PUBLIC_BREW_CMD_WEB is not configured'); + if (!currentRecipePath) + throw new BrewCommandError('PUBLIC_BREW_CURRENT_RECIPE is not configured'); + if (!statusPath) throw new BrewCommandError('PUBLIC_BREW_WEB_STATUS is not configured'); + + await adb.push(commandPath, ''); + await adb.push(currentRecipePath, ''); + await adb.push(statusPath, ''); } catch (e) { throw new BrewCommandError('Reset failed', `${e}`); } diff --git a/src/lib/core/handlers/adbPayloadHandler.ts b/src/lib/core/handlers/adbPayloadHandler.ts index 2c84e6d..fbc3f2b 100644 --- a/src/lib/core/handlers/adbPayloadHandler.ts +++ b/src/lib/core/handlers/adbPayloadHandler.ts @@ -1,44 +1,75 @@ import { get } from 'svelte/store'; import { updateMachineStatus } from '../stores/machineInfoStore'; import { addNotification } from '../stores/noti'; +import { + loadAndroidRecipeExportFromDevice, + saveAndroidRecipeExportPayload +} from '../services/androidRecipeExportService'; import { handleIncomingMessages } from './messageHandler'; +import { setMenuSaved, setMenuSaveError } from '../stores/menuSaveStore'; import { recipeFromMachineQuery } from '../stores/recipeStore'; type AdbPayload = { type: string; payload: any }; async function handleAdbPayload(raw_payload: string) { - console.log('get payload', raw_payload); + console.log('[ADB] Received payload:', raw_payload.slice(0, 300)); try { const payload: AdbPayload = JSON.parse(raw_payload); + console.log('[ADB] Parsed type:', payload.type, 'payload:', payload.payload); switch (payload.type) { case 'log': let log_level = payload.payload['level'] ?? 'INFO'; let log_message = payload.payload['msg'] ?? ''; - if (log_message !== '') addNotification(`${log_level}`); + if (log_message !== '') { + console.log('[ADB LOG]', log_level, log_message); + addNotification(`${log_level}:${log_message}`); + } break; case 'response': - if (payload.payload instanceof String) { + if (typeof payload.payload === 'string' || payload.payload instanceof String) { // single message response let raw_payload = payload.payload.toString(); - if (raw_payload.startsWith('save_recipe_machine')) { + if ( + raw_payload.startsWith('save_recipe_machine') || + raw_payload.startsWith('save_recipe_menu_file') + ) { let res = raw_payload.split('/'); let pd = res[1] ?? ''; let action = res[2] ?? ''; let uiAction = res[3] ?? ''; - handleIncomingMessages( - JSON.stringify({ - type: 'ui_action', - payload: { - action: uiAction, - from: 'brew', - ref: `${pd}.${action}` - } - }) - ); + console.log('[ADB] Save response parsed:', { pd, action, uiAction, raw_payload }); + + // Track menu save status + if (raw_payload.startsWith('save_recipe_menu_file') && pd) { + if (action === 'success' || action === 'ok' || uiAction === 'refreshNow') { + setMenuSaved(pd); + addNotification(`INFO:Menu saved: ${pd}`); + } else if (action === 'error' || action === 'fail') { + setMenuSaveError(pd, 'Save failed'); + addNotification(`ERR:Failed to save menu: ${pd}`); + } else { + // Assume success if we get a response + setMenuSaved(pd); + addNotification(`INFO:Menu saved: ${pd}`); + } + } + + if (raw_payload.startsWith('save_recipe_machine')) { + handleIncomingMessages( + JSON.stringify({ + type: 'ui_action', + payload: { + action: uiAction, + from: 'brew', + ref: `${pd}.${action}` + } + }) + ); + } } else if (raw_payload.startsWith('state')) { let res = raw_payload.split('/'); let new_machine_state = res[1] ?? ''; @@ -98,6 +129,39 @@ async function handleAdbPayload(raw_payload: string) { addNotification(`ERR:${payload.payload}`); // send message to server if needed break; + case 'recipe-export': + if (payload.payload?.content) { + saveAndroidRecipeExportPayload({ + content: payload.payload.content, + exportedAt: payload.payload.exportedAt, + source: payload.payload.source, + fileSizeBytes: payload.payload.fileSizeBytes, + lineCount: payload.payload.lineCount, + message: payload.payload.message + }); + addNotification('INFO:Recipe export received from Android'); + } else if (payload.payload?.message) { + addNotification(`ERR:${payload.payload.message}`); + } + break; + case 'recipe-export-ready': + if (payload.payload?.message && payload.payload?.fileSizeBytes === 0) { + addNotification(`ERR:${payload.payload.message}`); + break; + } + + void loadAndroidRecipeExportFromDevice({ + exportedAt: payload.payload?.exportedAt, + source: payload.payload?.source, + fileSizeBytes: payload.payload?.fileSizeBytes, + lineCount: payload.payload?.lineCount, + message: payload.payload?.message + }) + .then(() => addNotification('INFO:Recipe export loaded from Android')) + .catch((error: any) => + addNotification(`ERR:${error?.message ?? 'Unable to load recipe export from Android'}`) + ); + break; default: } } catch (error: any) { diff --git a/src/lib/core/handlers/messageHandler.ts b/src/lib/core/handlers/messageHandler.ts index 80b8c6b..b875029 100644 --- a/src/lib/core/handlers/messageHandler.ts +++ b/src/lib/core/handlers/messageHandler.ts @@ -15,6 +15,24 @@ import { toppingGroupFromServerQuery, toppingListFromServerQuery } from '../stores/recipeStore'; +import { + handleSheetStreamStart, + handleSheetStreamChunk, + handleSheetStreamEnd, + handleSheetStreamError, + handleCatalogsResponse, + handleListMenuResponse, + sheetCatalogsLoading, + handleRawStreamHeader, + handleRawStreamChunk, + handleRawStreamEnd +} from '../stores/sheetStore'; +import { + handleGenLayoutBatchStart, + handleGenLayoutFile, + handleGenLayoutBatchEnd, + handleGenLayoutError +} from '../stores/genLayoutStore'; import { buildOverviewFromServer } from '$lib/data/recipeService'; import { auth } from '../client/firebase'; import { type RecipeVersion } from '$lib/models/recipe_version.model'; @@ -208,22 +226,108 @@ const handlers: Record void> = { }, stream_patch_update: (p) => {}, notify: (p) => { - let noti_level = p.level ?? 'INFO'; - let msg = p.msg ?? `Notify from ${p.from}`; - let target = p.to; + const from = p.from; + const level = p.level ?? 'INFO'; + const msg = p.msg; + const target = p.to; + // Handle list-menu response + if (from === 'list-menu') { + const currentUid = auth.currentUser?.uid; + if (target && currentUid && target === currentUid && p.value) { + handleListMenuResponse({ codes: p.value }); + } + return; + } + + // Handle gen-service responses + if (from === 'gen-service') { + switch (level) { + case 'batch_start': + handleGenLayoutBatchStart({ + batch_id: p.batch_id, + total_files: p.total_files, + total_size_bytes: p.total_size_bytes + }); + addNotification(`INFO:Gen Layout started (${p.total_files} files)`); + break; + case 'file': + handleGenLayoutFile({ + batch_id: p.batch_id, + file_index: p.file_index, + total_files: p.total_files, + file: p.file, + content: p.content, + is_chunked: p.is_chunked, + part_index: p.part_index, + total_parts: p.total_parts, + is_last_part: p.is_last_part + }); + break; + case 'batch_end': + handleGenLayoutBatchEnd({ + batch_id: p.batch_id, + total_files: p.total_files + }); + addNotification('INFO:Gen Layout complete'); + break; + case 'ERROR': + handleGenLayoutError(msg); + addNotification(`ERR:Gen Layout error: ${msg}`); + break; + default: + console.log('[GenService] Received:', level, msg); + } + return; + } + + if (from === 'sheet-service' && level === 'content') { + const currentUid = auth.currentUser?.uid; + + if (target && currentUid && target === currentUid) { + if (!msg && p.content?.catalogs) { + handleCatalogsResponse(p.content); + addNotification(`INFO:Loaded ${p.content.catalogs?.length || 0} catalogs`); + return; + } + + // Handle streaming messages (with msg field) + switch (msg) { + case 'start': + handleSheetStreamStart(p); + addNotification('INFO:Sheet data streaming started'); + break; + case 'chunk': + handleSheetStreamChunk(p); + break; + case 'end': + handleSheetStreamEnd(p); + addNotification('INFO:Sheet data streaming complete'); + break; + case 'error': + handleSheetStreamError(p); + addNotification(`ERR:Sheet streaming error: ${p.content?.error_detail}`); + break; + default: + // Handle other content notifications from sheet-service + console.log('[Sheet] Received content:', p.content); + } + } + return; + } + + // Default notification handling let from_service = p.from ?? ''; let ref_service = p.ref ?? ''; if (target) { - // let currentUsername = auth.currentUser?.displayName; if (currentUsername && currentUsername === target) { - addNotification(`${noti_level}:${msg}`); + addNotification(`${level}:${msg}`); } } else { // broadcast to all - addNotification(`${noti_level}:${msg}`); + addNotification(`${level}:${msg}`); } }, ui_action: (p) => { @@ -348,12 +452,33 @@ const handlers: Record void> = { socketConnectionOfflineCount.set(0); socketAlreadySendHeartbeat.set(0); console.log('heartbeat reset offline count'); + }, + // Raw stream handlers for sheet data (e.g., price) + raw_stream: (p) => { + // Format: raw_stream with subtype in payload + // Header: { subtype: 'price', request_id, header?, country? } + const subtype = p.subtype; + if (subtype) { + handleRawStreamHeader(subtype, p); + } + }, + raw_stream_price: (p) => { + // Header for price stream + handleRawStreamHeader('price', p); + }, + raw_stream_chunk_price: (p) => { + // Chunk for price stream + handleRawStreamChunk('price', p); + }, + raw_stream_end_price: (p) => { + // End for price stream + handleRawStreamEnd('price', p); } }; export function handleIncomingMessages(raw: string) { const msg: WSMessage = JSON.parse(raw); - // console.log(`${new Date().toLocaleTimeString()}:ws msg`, msg); + // console.log(`[WS MSG] type=${msg.type}`, msg.payload); if (msg == null) { // error response addNotification('ERR:No response from server'); diff --git a/src/lib/core/handlers/ws_messageSender.ts b/src/lib/core/handlers/ws_messageSender.ts index 0ba1fdf..5d3dd01 100644 --- a/src/lib/core/handlers/ws_messageSender.ts +++ b/src/lib/core/handlers/ws_messageSender.ts @@ -18,7 +18,7 @@ function getServiceName(cmdReq: CommandRequest) { } // Websocket message wrapper for commands like `sheet`, `command` -export function sendCommandRequest(target: CommandRequest, values: any) { +export function sendCommandRequest(target: CommandRequest, values: any): boolean { let srv_name = getServiceName(target); let curr_user = get(auth); @@ -31,7 +31,7 @@ export function sendCommandRequest(target: CommandRequest, values: any) { }; } - sendMessage({ + return sendMessage({ type: target, payload: { user_info: user_info ?? {}, diff --git a/src/lib/core/services/androidRecipeExportService.ts b/src/lib/core/services/androidRecipeExportService.ts new file mode 100644 index 0000000..66c91ed --- /dev/null +++ b/src/lib/core/services/androidRecipeExportService.ts @@ -0,0 +1,284 @@ +import { browser } from '$app/environment'; +import { writable } from 'svelte/store'; + +export const ANDROID_RECIPE_EXPORT_PATH = '/mnt/sdcard/recipe_export_all.tsv'; +const ANDROID_RECIPE_EXPORT_CACHE_KEY = 'android_recipe_export_payload_v1'; +const ANDROID_RECIPE_EXPORT_DB_NAME = 'android_recipe_export_cache'; +const ANDROID_RECIPE_EXPORT_STORE_NAME = 'payloads'; + +export type AndroidRecipeExportPayload = { + content: string; + exportedAt?: number; + source?: string; + fileSizeBytes?: number; + lineCount?: number; + message?: string; +}; + +export type AndroidRecipeExportRow = { + lineNumber: number; + cells: string[]; + values: Record; +}; + +export type AndroidRecipeExportData = { + headers: string[]; + rows: AndroidRecipeExportRow[]; + lineCount: number; +}; + +export const androidRecipeExportPayload = writable(null); +let deviceExportLoadPromise: Promise | null = null; + +function openAndroidRecipeExportDb(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(ANDROID_RECIPE_EXPORT_DB_NAME, 1); + + request.onupgradeneeded = () => { + request.result.createObjectStore(ANDROID_RECIPE_EXPORT_STORE_NAME); + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); +} + +async function readCachedPayloadFromIndexedDb(): Promise { + const db = await openAndroidRecipeExportDb(); + + return new Promise((resolve, reject) => { + const transaction = db.transaction(ANDROID_RECIPE_EXPORT_STORE_NAME, 'readonly'); + const store = transaction.objectStore(ANDROID_RECIPE_EXPORT_STORE_NAME); + const request = store.get(ANDROID_RECIPE_EXPORT_CACHE_KEY); + + request.onsuccess = () => resolve((request.result as AndroidRecipeExportPayload) ?? null); + request.onerror = () => reject(request.error); + transaction.oncomplete = () => db.close(); + transaction.onerror = () => { + db.close(); + reject(transaction.error); + }; + }); +} + +async function writeCachedPayloadToIndexedDb(payload: AndroidRecipeExportPayload): Promise { + const db = await openAndroidRecipeExportDb(); + + return new Promise((resolve, reject) => { + const transaction = db.transaction(ANDROID_RECIPE_EXPORT_STORE_NAME, 'readwrite'); + const store = transaction.objectStore(ANDROID_RECIPE_EXPORT_STORE_NAME); + + store.put(payload, ANDROID_RECIPE_EXPORT_CACHE_KEY); + transaction.oncomplete = () => { + db.close(); + resolve(); + }; + transaction.onerror = () => { + db.close(); + reject(transaction.error); + }; + }); +} + +async function deleteCachedPayloadFromIndexedDb(): Promise { + const db = await openAndroidRecipeExportDb(); + + return new Promise((resolve, reject) => { + const transaction = db.transaction(ANDROID_RECIPE_EXPORT_STORE_NAME, 'readwrite'); + const store = transaction.objectStore(ANDROID_RECIPE_EXPORT_STORE_NAME); + + store.delete(ANDROID_RECIPE_EXPORT_CACHE_KEY); + transaction.oncomplete = () => { + db.close(); + resolve(); + }; + transaction.onerror = () => { + db.close(); + reject(transaction.error); + }; + }); +} + +export async function loadCachedAndroidRecipeExport(): Promise { + if (!browser) return null; + + try { + const payload = await readCachedPayloadFromIndexedDb(); + if (!payload?.content) return null; + + androidRecipeExportPayload.set(payload); + return payload; + } catch (error) { + console.error('failed to load cached android recipe export from IndexedDB', error); + } + + try { + const cached = localStorage.getItem(ANDROID_RECIPE_EXPORT_CACHE_KEY); + if (!cached) return null; + + const payload = JSON.parse(cached) as AndroidRecipeExportPayload; + if (!payload?.content) return null; + + androidRecipeExportPayload.set(payload); + void writeCachedPayloadToIndexedDb(payload); + localStorage.removeItem(ANDROID_RECIPE_EXPORT_CACHE_KEY); + return payload; + } catch (error) { + console.error('failed to load legacy android recipe export cache', error); + return null; + } +} + +export function saveAndroidRecipeExportPayload(payload: AndroidRecipeExportPayload) { + androidRecipeExportPayload.set(payload); + + if (!browser) return; + + void writeCachedPayloadToIndexedDb(payload).catch((error) => { + console.error('failed to cache android recipe export', error); + }); +} + +export function clearCachedAndroidRecipeExport() { + androidRecipeExportPayload.set(null); + + if (!browser) return; + + localStorage.removeItem(ANDROID_RECIPE_EXPORT_CACHE_KEY); + void deleteCachedPayloadFromIndexedDb().catch((error) => { + console.error('failed to clear android recipe export cache', error); + }); +} + +function normalizeHeader(value: string, index: number): string { + const header = value.trim().replace(/^\uFEFF/, ''); + return header || `Column ${index + 1}`; +} + +function splitTsvLine(line: string): string[] { + return line.split('\t').map((cell) => cell.trim()); +} + +function collectNonEmptyLines(raw: string, maxLines: number): string[] { + const lines: string[] = []; + let lineStart = 0; + + for (let index = 0; index <= raw.length; index += 1) { + const isEnd = index === raw.length; + const char = raw[index]; + + if (!isEnd && char !== '\n') continue; + + const lineEnd = index > lineStart && raw[index - 1] === '\r' ? index - 1 : index; + const line = raw.slice(lineStart, lineEnd); + + if (line.trim().length > 0) { + lines.push(line); + if (lines.length >= maxLines) break; + } + + lineStart = index + 1; + } + + return lines; +} + +function buildUniqueHeaders(rawHeaders: string[], maxColumns: number): string[] { + const headers = [...rawHeaders]; + + for (let i = headers.length; i < maxColumns; i += 1) { + headers.push(`Column ${i + 1}`); + } + + const seen = new Map(); + return headers.map((header, index) => { + const normalized = normalizeHeader(header, index); + const count = seen.get(normalized) ?? 0; + seen.set(normalized, count + 1); + + return count === 0 ? normalized : `${normalized} ${count + 1}`; + }); +} + +export function parseAndroidRecipeExport( + raw: string, + maxRows = Number.POSITIVE_INFINITY +): AndroidRecipeExportData { + const maxLines = Number.isFinite(maxRows) ? Math.max(1, maxRows + 1) : Number.MAX_SAFE_INTEGER; + const lines = collectNonEmptyLines(raw, maxLines); + + if (lines.length === 0) { + return { + headers: [], + rows: [], + lineCount: 0 + }; + } + + const parsedLines = lines.map(splitTsvLine); + const maxColumns = Math.max(...parsedLines.map((line) => line.length)); + const headers = buildUniqueHeaders(parsedLines[0], maxColumns); + + const rows = parsedLines.slice(1).map((cells, index) => { + const paddedCells = [...cells]; + + for (let cellIndex = paddedCells.length; cellIndex < headers.length; cellIndex += 1) { + paddedCells.push(''); + } + + const values = Object.fromEntries( + headers.map((header, cellIndex) => [header, paddedCells[cellIndex] ?? '']) + ); + + return { + lineNumber: index + 2, + cells: paddedCells, + values + }; + }); + + return { + headers, + rows, + lineCount: lines.length + }; +} + +export async function pullAndroidRecipeExport(timeoutMs = 15000): Promise { + const adb = await import('$lib/core/adb/adb'); + const instance = adb.getAdbInstance(); + + if (!instance) { + throw new Error('ADB device is not connected'); + } + + const content = await adb.pull(ANDROID_RECIPE_EXPORT_PATH, timeoutMs); + + if (content === undefined) { + throw new Error(`Unable to pull ${ANDROID_RECIPE_EXPORT_PATH}`); + } + + return content; +} + +export async function loadAndroidRecipeExportFromDevice( + meta: Partial = {} +): Promise { + if (deviceExportLoadPromise) return deviceExportLoadPromise; + + deviceExportLoadPromise = (async () => { + const content = await pullAndroidRecipeExport(30000); + + saveAndroidRecipeExportPayload({ + content, + exportedAt: meta.exportedAt ?? Date.now(), + source: meta.source ?? ANDROID_RECIPE_EXPORT_PATH, + fileSizeBytes: meta.fileSizeBytes, + lineCount: meta.lineCount, + message: meta.message + }); + })().finally(() => { + deviceExportLoadPromise = null; + }); + + return deviceExportLoadPromise; +} diff --git a/src/lib/core/services/sheetService.ts b/src/lib/core/services/sheetService.ts new file mode 100644 index 0000000..2e15f35 --- /dev/null +++ b/src/lib/core/services/sheetService.ts @@ -0,0 +1,251 @@ +import { sendCommandRequest, sendMessage } from '../handlers/ws_messageSender'; +import { get } from 'svelte/store'; +import { auth } from '../stores/auth'; +import { + productCodesLoading, + hasSheetPriceBeenSent, + markSheetPriceAsSent, + sheetPriceLoading, + streamingRawData, + setPendingProductCodesCountry +} from '../stores/sheetStore'; +import { setGenLayoutGenerating } from '../stores/genLayoutStore'; + +export function requestCatalogs(country: string): boolean { + return sendCommandRequest('sheet', { + country: country, + param: 'catalogs' + }); +} + +export function enterRoom(country: string, catalog: string): boolean { + return sendCommandRequest('sheet', { + country: country, + catalog: catalog, + param: 'enter' + }); +} + +export function sendHeartbeat(country: string, catalog: string): boolean { + return sendCommandRequest('sheet', { + country: country, + catalog: catalog, + param: 'heartbeat' + }); +} + +export function exitRoom(country: string, catalog: string): boolean { + return sendCommandRequest('sheet', { + country: country, + catalog: catalog, + param: 'exit' + }); +} + +export function requestCatalogMenu(country: string, catalog: string): boolean { + return sendCommandRequest('sheet', { + country: country, + catalog: catalog, + param: 'catalog/menu' + }); +} + +export function updateMenu(country: string, catalog: string, content: any[]): boolean { + return sendCommandRequest('sheet', { + country: country, + catalog: catalog, + content: content, + param: 'update/menu' + }); +} + +export function addMenu(country: string, catalog: string, content: any[]): boolean { + console.log('[sheetService] Adding menu:', { country, catalog, content }); + const sent = sendCommandRequest('sheet', { + country: country, + catalog: catalog, + content: content, + param: 'add/menu' + }); + console.log('[sheetService] Add menu sent:', sent); + return sent; +} + +export function deleteMenu(country: string, catalog: string, targetIds: number[]): boolean { + const content = targetIds.map((id) => ({ target_id: id })); + return sendCommandRequest('sheet', { + country: country, + catalog: catalog, + content: content, + param: 'delete/menu' + }); +} + +export function swapMenu( + country: string, + catalog: string, + swaps: { source_id: number; target_id: number }[] +): boolean { + return sendCommandRequest('sheet', { + country: country, + catalog: catalog, + content: swaps, + param: 'swap/menu' + }); +} + +export function requestListMenu(country: string, boxid?: string): boolean { + const curr_user = get(auth); + + let user_info: any = {}; + if (curr_user) { + user_info = { + displayName: curr_user.displayName, + email: curr_user.email, + uid: curr_user.uid + }; + } + + productCodesLoading.set(true); + setPendingProductCodesCountry(country); + + console.log('[sheetService] Sending list_menu request for country:', country, 'boxid:', boxid); + + return sendMessage({ + type: 'list_menu', + payload: { + user_info, + country, + boxid: boxid || undefined + } + }); +} + +export function requestGenLayout(country: string): boolean { + const curr_user = get(auth); + + let user_info: any = {}; + if (curr_user) { + user_info = { + displayName: curr_user.displayName, + email: curr_user.email, + uid: curr_user.uid + }; + } + + setGenLayoutGenerating(); + + console.log('[sheetService] Sending gen-layout request for country:', country); + + return sendMessage({ + type: 'command', + payload: { + user_info, + srv_name: 'gen-service', + values: { + file_layout: 'sheet', + file_desc: 'sheet', + country: country, + param: 'new-inter-v3-multi-promotion-other_catalog-supra_app' + } + } + }); +} + +/** + * Request price data from sheet for specific product codes + * NOTE: Can only send once per type (price). Use hasSheetPriceBeenSent to check. + */ +export function requestSheetPrice(country: string, productCodes: string[]): boolean { + // Check if already sent + if (hasSheetPriceBeenSent('price')) { + console.warn('[sheetService] Price request already sent, skipping'); + return false; + } + + if (!productCodes || productCodes.length === 0) { + console.warn('[sheetService] No product codes to request price for'); + return false; + } + + // Generate request_id (UUID v4) + const request_id = crypto.randomUUID(); + + // Store request_id and country in streamingRawData for tracking + streamingRawData.update((data) => ({ + ...data, + price: { + request_id, + country, + chunks: [], + rawParts: [] + } + })); + + sheetPriceLoading.set(true); + + // Convert to array of objects (backend expects objects, not strings) + const content = productCodes.map((code) => ({ product_code: code })); + + console.log('[sheetService] Sending sheet price request for country:', country, 'codes:', productCodes.length, 'request_id:', request_id); + + const sent = sendCommandRequest('sheet', { + country: country, + content: content, + param: 'price', + stream: true, + request_id + }); + + if (sent) { + markSheetPriceAsSent('price'); + } else { + sheetPriceLoading.set(false); + } + + return sent; +} + +/** + * Update price data in sheet + * content: [{ row_index: number, cells: [{ value: string, coord: { row: number, col: number } }] }] + */ +export function updateSheetPrice( + country: string, + content: { row_index: number; cells: { value: string; coord: { row: number; col: number } }[] }[] +): boolean { + if (!content || content.length === 0) { + console.warn('[sheetService] No content to update'); + return false; + } + + console.log('[sheetService] Updating sheet price for country:', country, 'items:', content.length); + + return sendCommandRequest('sheet', { + country: country, + content: content, + param: 'update/price' + }); +} + +/** + * Add new price rows to sheet (for product codes that don't exist in price sheet) + * content: [{ cells: [product_code, name_en, name_th, ..., price, ...] }] + */ +export function addSheetPrice( + country: string, + content: { cells: string[] }[] +): boolean { + if (!content || content.length === 0) { + console.warn('[sheetService] No content to add'); + return false; + } + + console.log('[sheetService] Adding price rows for country:', country, 'items:', content.length, content); + + return sendCommandRequest('sheet', { + country: country, + content: content, + param: 'add/price' + }); +} diff --git a/src/lib/core/stores/adbWriter.ts b/src/lib/core/stores/adbWriter.ts index ec3f0a7..d36d54f 100644 --- a/src/lib/core/stores/adbWriter.ts +++ b/src/lib/core/stores/adbWriter.ts @@ -7,16 +7,27 @@ async function sendToAndroid(message: any) { let writer: any = get(adbWriter); console.log('writer', writer); if (!writer) { - // addNotification('ERR:No active connection'); - return; + addNotification('ERR:No active Android connection'); + return false; } try { const encoder = new TextEncoder(); - // console.log(writer); - await writer.write(encoder.encode(JSON.stringify(message) + '\n')); - console.log('sent! ', JSON.stringify(message).length); + const serializedMessage = JSON.stringify(message); + await writer.write(encoder.encode(serializedMessage + '\n')); + console.log('[ADB] sent', { + type: message?.type, + bytes: serializedMessage.length, + productCode: message?.payload?.data?.productCode, + batchCount: Array.isArray(message?.payload?.data) ? message.payload.data.length : undefined, + batchProductCodes: Array.isArray(message?.payload?.data) + ? message.payload.data.map((menu: any) => menu?.productCode) + : undefined + }); + return true; } catch (error) { console.error('write failed', error); + addNotification('ERR:Failed to send message to Android'); + return false; } } diff --git a/src/lib/core/stores/genLayoutStore.ts b/src/lib/core/stores/genLayoutStore.ts new file mode 100644 index 0000000..6053573 --- /dev/null +++ b/src/lib/core/stores/genLayoutStore.ts @@ -0,0 +1,249 @@ +import { writable, get } from 'svelte/store'; + +export interface GenLayoutFile { + file: string; + content: string; + file_index: number; +} + +export interface GenLayoutBatch { + batch_id: string; + total_files: number; + total_size_bytes: number; + status: 'idle' | 'generating' | 'receiving' | 'complete' | 'error'; + files: GenLayoutFile[]; + error?: string; +} + +// Track chunked file parts: Map> +interface ChunkedFileTracker { + file: string; + file_index: number; + total_parts: number; + parts: Map; +} + +const initialState: GenLayoutBatch = { + batch_id: '', + total_files: 0, + total_size_bytes: 0, + status: 'idle', + files: [] +}; + +export const genLayoutBatch = writable(initialState); + +// Track chunked files being assembled +const chunkedFiles = new Map(); + +// Callbacks for when batch completes +let onBatchCompleteCallback: ((files: GenLayoutFile[]) => void) | null = null; + +export function setOnBatchCompleteCallback(cb: (files: GenLayoutFile[]) => void) { + onBatchCompleteCallback = cb; +} + +export function clearOnBatchCompleteCallback() { + onBatchCompleteCallback = null; +} + +export function handleGenLayoutBatchStart(payload: { + batch_id: string; + total_files: number; + total_size_bytes: number; +}) { + genLayoutBatch.set({ + batch_id: payload.batch_id, + total_files: payload.total_files, + total_size_bytes: payload.total_size_bytes, + status: 'receiving', + files: [] + }); + console.log('[GenLayout] Batch started:', payload.batch_id, 'total files:', payload.total_files); +} + +export function handleGenLayoutFile(payload: { + batch_id: string; + file_index: number; + total_files: number; + file: string; + content: string; + is_chunked?: boolean; + part_index?: number; + total_parts?: number; + is_last_part?: boolean; +}) { + const batch = get(genLayoutBatch); + if (batch.batch_id !== payload.batch_id) return; + + if (payload.is_chunked) { + const fileIndex = payload.file_index; + const partIndex = payload.part_index ?? 0; + const totalParts = payload.total_parts ?? 1; + + // Get or create tracker for this file + let tracker = chunkedFiles.get(fileIndex); + if (!tracker) { + tracker = { + file: payload.file, + file_index: fileIndex, + total_parts: totalParts, + parts: new Map() + }; + chunkedFiles.set(fileIndex, tracker); + } + + // Store this chunk + tracker.parts.set(partIndex, payload.content); + + console.log( + '[GenLayout] Received chunk:', + partIndex + 1, + '/', + totalParts, + 'for file', + fileIndex + 1, + '/', + payload.total_files, + payload.file + ); + + // Check if all parts received + if (tracker.parts.size === totalParts) { + // Assemble the complete file content + const sortedParts: string[] = []; + for (let i = 0; i < totalParts; i++) { + sortedParts.push(tracker.parts.get(i) ?? ''); + } + const completeContent = sortedParts.join(''); + + // Add to files + addFileToStore(payload.batch_id, { + file: payload.file, + content: completeContent, + file_index: fileIndex + }, payload.total_files); + + // Clean up tracker + chunkedFiles.delete(fileIndex); + + console.log( + '[GenLayout] Assembled chunked file:', + fileIndex + 1, + '/', + payload.total_files, + payload.file + ); + } + } else { + // Handle non-chunked file + addFileToStore(payload.batch_id, { + file: payload.file, + content: payload.content, + file_index: payload.file_index + }, payload.total_files); + + console.log( + '[GenLayout] Received file:', + payload.file_index + 1, + '/', + payload.total_files, + payload.file + ); + } +} + +function addFileToStore(batchId: string, file: GenLayoutFile, totalFiles: number) { + genLayoutBatch.update((batch) => { + if (batch.batch_id !== batchId) return batch; + + const existingIndex = batch.files.findIndex((f) => f.file_index === file.file_index); + const newFiles = + existingIndex >= 0 + ? batch.files.map((f, index) => (index === existingIndex ? file : f)) + : [...batch.files, file]; + const sortedFiles = newFiles.sort((a, b) => a.file_index - b.file_index); + + return { + ...batch, + total_files: totalFiles || batch.total_files, + files: sortedFiles + }; + }); +} + +export function handleGenLayoutBatchEnd(payload: { batch_id: string; total_files: number }) { + const batch = get(genLayoutBatch); + + if (batch.batch_id !== payload.batch_id) return; + + const expectedTotal = payload.total_files || batch.total_files; + const sortedFiles = [...batch.files].sort((a, b) => a.file_index - b.file_index); + const missingIndexes = getMissingFileIndexes(sortedFiles, expectedTotal); + + if (missingIndexes.length > 0) { + // const error = `Gen Layout incomplete: received ${sortedFiles.length}/${expectedTotal} files. Missing file index: ${missingIndexes.join(', ')}`; + const error = `ไฟล์ไม่ครับ ทั้งหมด: ${sortedFiles.length}/${expectedTotal}` + genLayoutBatch.update((b) => ({ + ...b, + total_files: expectedTotal, + status: 'error', + files: sortedFiles, + error + })); + console.warn('[GenLayout] Batch incomplete:', error, sortedFiles); + return; + } + + genLayoutBatch.update((b) => ({ + ...b, + total_files: expectedTotal, + status: 'complete', + files: sortedFiles + })); + + console.log('[GenLayout] Batch complete, received', sortedFiles.length, 'files'); + + if (onBatchCompleteCallback) { + onBatchCompleteCallback(sortedFiles); + } +} + +function getMissingFileIndexes(files: GenLayoutFile[], totalFiles: number) { + if (totalFiles <= 0) return []; + + const receivedIndexes = new Set(files.map((file) => file.file_index)); + const indexes = [...receivedIndexes]; + const startsAtOne = indexes.length > 0 && Math.min(...indexes) >= 1; + const firstIndex = startsAtOne ? 1 : 0; + const lastIndex = startsAtOne ? totalFiles : totalFiles - 1; + const missingIndexes: number[] = []; + + for (let index = firstIndex; index <= lastIndex; index += 1) { + if (!receivedIndexes.has(index)) { + missingIndexes.push(index); + } + } + + return missingIndexes; +} + +export function handleGenLayoutError(error: string) { + genLayoutBatch.update((batch) => ({ + ...batch, + status: 'error', + error + })); +} + +export function resetGenLayoutBatch() { + genLayoutBatch.set(initialState); + chunkedFiles.clear(); +} + +export function setGenLayoutGenerating() { + genLayoutBatch.update((batch) => ({ + ...batch, + status: 'generating' + })); +} diff --git a/src/lib/core/stores/menuSaveStore.ts b/src/lib/core/stores/menuSaveStore.ts new file mode 100644 index 0000000..f6e465e --- /dev/null +++ b/src/lib/core/stores/menuSaveStore.ts @@ -0,0 +1,85 @@ +import { writable, get } from 'svelte/store'; + +export type MenuSaveStatus = 'idle' | 'saving' | 'saved' | 'error'; + +export interface MenuSaveState { + productCode: string; + status: MenuSaveStatus; + error?: string; + savedAt?: number; +} + +// Store for tracking menu save status +export const menuSaveStates = writable>(new Map()); + +// Callback to be called when a menu is saved successfully +let onMenuSavedCallback: ((productCode: string) => void) | null = null; + +export function setOnMenuSavedCallback(callback: (productCode: string) => void) { + onMenuSavedCallback = callback; +} + +export function clearOnMenuSavedCallback() { + onMenuSavedCallback = null; +} + +export function setMenuSaving(productCode: string) { + menuSaveStates.update((states) => { + const newStates = new Map(states); + newStates.set(productCode, { + productCode, + status: 'saving' + }); + return newStates; + }); +} + +export function setMenuSaved(productCode: string) { + menuSaveStates.update((states) => { + const newStates = new Map(states); + newStates.set(productCode, { + productCode, + status: 'saved', + savedAt: Date.now() + }); + return newStates; + }); + + // Call the callback if registered + if (onMenuSavedCallback) { + onMenuSavedCallback(productCode); + } +} + +export function setMenuSaveError(productCode: string, error: string) { + menuSaveStates.update((states) => { + const newStates = new Map(states); + newStates.set(productCode, { + productCode, + status: 'error', + error + }); + return newStates; + }); +} + +export function clearMenuSaveState(productCode: string) { + menuSaveStates.update((states) => { + const newStates = new Map(states); + newStates.delete(productCode); + return newStates; + }); +} + +export function getMenuSaveStatus(productCode: string): MenuSaveStatus { + const states = get(menuSaveStates); + return states.get(productCode)?.status ?? 'idle'; +} + +export function isMenuSaving(productCode: string): boolean { + return getMenuSaveStatus(productCode) === 'saving'; +} + +export function isMenuSaved(productCode: string): boolean { + return getMenuSaveStatus(productCode) === 'saved'; +} diff --git a/src/lib/core/stores/sheetStore.ts b/src/lib/core/stores/sheetStore.ts new file mode 100644 index 0000000..5a2e0dc --- /dev/null +++ b/src/lib/core/stores/sheetStore.ts @@ -0,0 +1,805 @@ +import { writable, get } from 'svelte/store'; + +// Catalog types +export interface Catalog { + catalog: string; + row_index: number; + status: 'free' | 'locked'; + locked_by: string | null; +} + +export interface CatalogsResponse { + status: string; + country: string; + catalogs: Catalog[]; +} + +export const sheetCatalogs = writable([]); +export const sheetCatalogsLoading = writable(false); + +export const countryPrimaryLanguageMap: Record = { + THAI: 'Thai', + tha: 'Thai', + cocktail_tha: 'Thai', + counter01: 'Thai', + MYS: 'Malaysia', + mys: 'Malaysia', + IDR: 'Indonesian', + idr: 'Indonesian', + AUS: 'English', + aus: 'English', + USA_PEPSI: 'English', + usa_pepsi: 'English', + SG: 'English', + SGP: 'English', + sgp: 'English', + UAE_DUBAI: 'Arabic', + uae_dubai: 'Arabic', + dubai: 'Arabic', + HKG: 'Cantonese', + hkg: 'Cantonese', + GBR: 'English', + gbr: 'English', + LTU: 'Lithuanian', + ltu: 'Lithuanian', + ROU: 'Romanian', + rou: 'Romanian', + LVA: 'Latvian', + lva: 'Latvian', + EST: 'Estonian', + est: 'Estonian' +}; + +export function getCountryPrimaryLanguage(countryCode: string): string { + return ( + countryPrimaryLanguageMap[countryCode] ?? + countryPrimaryLanguageMap[countryCode.toUpperCase()] ?? + 'Unknown' + ); +} + +// Sheet column configuration by country for new_layout_v2 +// Maps language keys to column indices and product code columns +export const SHEET_COLUMN_CONFIG_BY_COUNTRY: Record; + productCode: { hot: number; cold: number; blend: number }; + primaryLanguage: string; +}> = { + tha: { + language: { en: 3, th: 4, zh: 5, my: 8 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'th' + }, + aus: { + language: { en: 3, th: 4 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'en' + }, + gbr: { + language: { en: 3 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'en' + }, + hkg: { + language: { en: 3, zh_hans: 4, zh_hant: 5, th: 6 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'zh_hant' + }, + ltu: { + language: { en: 3, th: 4, lt: 5, ro: 6 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'lt' + }, + rou: { + language: { en: 3, th: 4, lt: 5, ro: 6 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'ro' + }, + lva: { + language: { en: 3, th: 4, lt: 5, ro: 6 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'en' + }, + est: { + language: { en: 3, th: 4, lt: 5, ro: 6 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'en' + }, + mys: { + language: { en: 3, th: 4, ms: 7 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'ms' + }, + sgp: { + language: { en: 3, th: 4 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'en' + }, + uae_dubai: { + language: { en: 3, ar: 4 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'ar' + }, + dubai: { + language: { en: 3, ar: 4 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'ar' + }, + default: { + language: { en: 3, th: 4 }, + productCode: { hot: 9, cold: 10, blend: 11 }, + primaryLanguage: 'en' + } +}; + +export function getSheetColumnConfig(countryCode: string) { + return SHEET_COLUMN_CONFIG_BY_COUNTRY[countryCode.toLowerCase()] + || SHEET_COLUMN_CONFIG_BY_COUNTRY.default; +} + +export function handleCatalogsResponse(content: CatalogsResponse) { + if (content && content.catalogs) { + sheetCatalogs.set(content.catalogs); + } + sheetCatalogsLoading.set(false); +} + +export interface SheetStreamMeta { + batch_id: string; + total_chunks: number; + total_items: number; + current_chunk: number; + status: 'idle' | 'streaming' | 'complete' | 'error'; + error?: string; +} + +export interface SheetMenuItem { + new_layout_v2: { + row_index: number; + cells: { value: string; coord: { row: number; col: number } }[]; + }[]; + name_desc_v2: { + key: string; + row_index: number; + cells: { value: string; coord: { row: number; col: number } }[]; + }[]; +} + +// Store for streaming metadata +export const sheetStreamMeta = writable(null); + +// Store for accumulated sheet data +export const sheetData = writable([]); + +// Store for loading state +export const sheetLoading = writable(false); + +// Store for error state +export const sheetError = writable(null); + +// Handler functions for sheet-service streaming +export function handleSheetStreamStart(payload: { + batch_id: string; + total_chunks: number; + total_items: number; + content: { message: string }; +}) { + sheetStreamMeta.set({ + batch_id: payload.batch_id, + total_chunks: payload.total_chunks, + total_items: payload.total_items, + current_chunk: 0, + status: 'streaming' + }); + sheetData.set([]); + sheetLoading.set(true); + sheetError.set(null); +} + +export function handleSheetStreamChunk(payload: { + batch_id: string; + current_chunk: number; + total_chunks: number; + total_items: number; + content: SheetMenuItem[]; +}) { + const meta = get(sheetStreamMeta); + + // Verify batch_id matches + if (meta && meta.batch_id === payload.batch_id) { + // Append new data + sheetData.update((current) => [...current, ...payload.content]); + + // Update progress + sheetStreamMeta.set({ + ...meta, + current_chunk: payload.current_chunk, + total_chunks: payload.total_chunks, + total_items: payload.total_items + }); + } +} + +export function handleSheetStreamEnd(payload: { + batch_id: string; + total_chunks: number; + total_items: number; + content: { message: string }; +}) { + const meta = get(sheetStreamMeta); + + if (meta && meta.batch_id === payload.batch_id) { + sheetStreamMeta.set({ + ...meta, + status: 'complete', + current_chunk: payload.total_chunks + }); + sheetLoading.set(false); + } +} + +export function handleSheetStreamError(payload: { + batch_id: string; + content: { error_detail: string }; +}) { + const meta = get(sheetStreamMeta); + + if (meta && meta.batch_id === payload.batch_id) { + sheetStreamMeta.set({ + ...meta, + status: 'error', + error: payload.content.error_detail + }); + sheetError.set(payload.content.error_detail); + sheetLoading.set(false); + } +} + +// Reset all sheet stores +export function resetSheetStore() { + sheetStreamMeta.set(null); + sheetData.set([]); + sheetLoading.set(false); + sheetError.set(null); +} + +// Store for existing product codes (for duplicate checking) +export const existingProductCodes = writable>(new Set()); +export const productCodesLoading = writable(false); + +// ───────────────────────────────────────── +// Sheet Price Streaming +// ───────────────────────────────────────── + +export interface GristCell { + coord: { + col: number; + row: number; + }; + value: string; +} + +export interface SheetPriceItem { + product_code: string; + cells: GristCell[]; +} + +// Price sheet header name mappings by country +// Maps our field names to the actual header names in the sheet +export const PRICE_HEADER_NAMES_BY_COUNTRY: Record = { + tha: { + cash_price: ['Price'], + non_cash_price: ['MainPrice'] + }, + mys: { + cash_price: ['F', 'Price'], + non_cash_price: ['MainPrice'] + }, + aus: { + cash_price: ['AUD', 'Price'], + non_cash_price: ['MainPrice'] + }, + sgp: { + cash_price: ['SGD', 'Price'], + non_cash_price: ['MainPrice'] + }, + hkg: { + cash_price: ['HK Final Px', 'Price'], + non_cash_price: ['MainPrice'] + }, + gbr: { + cash_price: ['GBR', 'Price'], + non_cash_price: ['MainPrice'] + }, + uae_dubai: { + cash_price: ['AED', 'Price'], + non_cash_price: ['MainPrice'] + }, + dubai: { + cash_price: ['AED', 'Price'], + non_cash_price: ['MainPrice'] + }, + ltu: { + cash_price: ['Price in Euro', 'Price'], + non_cash_price: ['MainPrice'] + }, + rou: { + cash_price: ['Price From LTU (EUR)', 'Price in RON'], + non_cash_price: ['MainPrice'] + }, + lva: { + cash_price: ['Price in Euro', 'Price'], + non_cash_price: ['MainPrice'] + }, + est: { + cash_price: ['Price in Euro', 'Price'], + non_cash_price: ['MainPrice'] + }, + // Default fallback for other countries + default: { + cash_price: ['Price', 'Price in Euro', 'CashPrice', 'AUD', 'SGD', 'GBR', 'AED', 'F'], + non_cash_price: ['MainPrice', 'NonCashPrice'] + } +}; + +// Find column index from header array by matching header names +export function findHeaderIndex(headerArray: string[], possibleNames: string[]): number { + for (const name of possibleNames) { + const idx = headerArray.findIndex(h => h.toLowerCase() === name.toLowerCase()); + if (idx !== -1) { + // Return col index (header index + 1 because cells start from col 1) + return idx + 1; + } + } + return -1; +} + +// Store: lastRequestSheetPrice[country][product_code] = cells (first row only, for display) +export const lastRequestSheetPrice = writable>>({}); + +// Store: sheetPriceHeader[country] = header array +export const sheetPriceHeader = writable>({}); + +// Store: sheetPriceAllRows[country][product_code] = array of {row, cells} (ALL rows for duplicates) +export const sheetPriceAllRows = writable>>({}); + +// Helper function to get price value from cells using dynamic header lookup +export function getPriceFromCells( + country: string, + cells: GristCell[], + priceType: 'cash_price' | 'non_cash_price' = 'cash_price' +): string | null { + const headers = get(sheetPriceHeader)[country]; + if (!headers || headers.length === 0) { + console.warn(`[getPriceFromCells] No header found for country: ${country}`); + return null; + } + + // Get possible header names for this country + const headerNames = PRICE_HEADER_NAMES_BY_COUNTRY[country] || PRICE_HEADER_NAMES_BY_COUNTRY.default; + const possibleNames = priceType === 'cash_price' ? headerNames.cash_price : headerNames.non_cash_price; + + // Find the column index for this price type + const colIdx = findHeaderIndex(headers, possibleNames); + //console.log(`[getPriceFromCells] ${country} ${priceType}: colIdx=${colIdx}, headers=`, headers, 'possibleNames=', possibleNames); + + if (colIdx < 0) { + console.warn(`[getPriceFromCells] No ${priceType} column found for ${country}, tried:`, possibleNames); + return null; + } + + // Find the cell with matching column index + const priceCell = cells.find((c) => c.coord?.col === colIdx); + //console.log(`[getPriceFromCells] Found cell for col ${colIdx}:`, priceCell); + return priceCell?.value ?? null; +} + +// Store for tracking streaming state +export const sheetPriceStreamMeta = writable<{ + request_id: string; + country: string; + status: 'idle' | 'streaming' | 'complete' | 'error'; + error?: string; +} | null>(null); + +export const sheetPriceLoading = writable(false); + +// Track sent request types (can only send once per type) +export const sheetPriceSentTypes = writable>(new Set()); + +// Raw streaming data accumulator +export const streamingRawData = writable< + Record< + string, + { + request_id: string; + header?: string[]; + country?: string; + chunks: any[]; + rawParts: string[]; // For accumulating raw JSON string parts + } + > +>({}); + +// Handler: raw_stream header (e.g., raw_stream_price) +export function handleRawStreamHeader(subtype: string, payload: any) { + console.log(`[RawStream] Header for ${subtype}:`, payload); + + // Get existing stream data to preserve country from request + const currentData = get(streamingRawData); + const existingData = currentData[subtype]; + + streamingRawData.update((data) => ({ + ...data, + [subtype]: { + request_id: payload.request_id, + header: payload.header || payload.headers, + country: payload.country || existingData?.country || '', + chunks: [], + rawParts: [] // Initialize for accumulating raw JSON string parts + } + })); + + if (subtype === 'price') { + sheetPriceStreamMeta.set({ + request_id: payload.request_id, + country: payload.country || existingData?.country || '', + status: 'streaming' + }); + sheetPriceLoading.set(true); + } +} + +// Handler: raw_stream chunk (e.g., raw_stream_chunk_price) +export function handleRawStreamChunk(subtype: string, payload: any) { + console.log(`[RawStream] Chunk ${payload.idx} for ${subtype}, raw length:`, payload.raw?.length); + + const currentData = get(streamingRawData); + const streamData = currentData[subtype]; + + if (!streamData || streamData.request_id !== payload.request_id) { + console.warn(`[RawStream] Chunk received for unknown stream: ${subtype}`); + return; + } + + // Check if data is in 'raw' field as JSON string (chunked) + if (payload.raw && typeof payload.raw === 'string') { + // Accumulate raw parts - will be joined and parsed in handleRawStreamEnd + streamingRawData.update((data) => ({ + ...data, + [subtype]: { + ...streamData, + country: payload.country || streamData.country, + rawParts: [...(streamData.rawParts || []), payload.raw] + } + })); + console.log(`[RawStream] Accumulated chunk ${payload.idx} for ${subtype}`); + return; + } + + // Handle non-chunked payload (already parsed content) + const content = payload.content || payload.data || payload.rows || []; + const contentArray = Array.isArray(content) ? content : [content]; + + streamingRawData.update((data) => ({ + ...data, + [subtype]: { + ...streamData, + country: payload.country || streamData.country, + chunks: [...streamData.chunks, ...contentArray] + } + })); + + console.log(`[RawStream] Chunk for ${subtype}: +${contentArray.length} items`); +} + +// Handler: raw_stream end (e.g., raw_stream_end_price) +export function handleRawStreamEnd(subtype: string, payload: any) { + console.log(`[RawStream] End payload for ${subtype}:`, payload); + + const currentData = get(streamingRawData); + const streamData = currentData[subtype]; + + if (!streamData || streamData.request_id !== payload.request_id) { + console.warn(`[RawStream] End received for unknown stream: ${subtype}`); + return; + } + + // Get country from stored stream data or payload + const country = streamData.country || payload.country || ''; + + // If we have accumulated raw parts, join and parse them now + let chunks = streamData.chunks || []; + if (streamData.rawParts && streamData.rawParts.length > 0) { + const fullRawJson = streamData.rawParts.join(''); + console.log( + `[RawStream] Joining ${streamData.rawParts.length} raw parts, total length: ${fullRawJson.length}` + ); + + try { + const parsed = JSON.parse(fullRawJson); + console.log(`[RawStream] Parsed combined raw data, keys:`, Object.keys(parsed)); + + // Extract content from nested structure: { payload: { content: [...] } } + const content = parsed?.payload?.content || parsed?.content || parsed || []; + chunks = Array.isArray(content) ? content : [content]; + + // Log first item to see actual structure + if (chunks.length > 0) { + console.log(`[RawStream] First content item:`, JSON.stringify(chunks[0]).substring(0, 200)); + } + } catch (e) { + console.error(`[RawStream] Failed to parse combined raw JSON:`, e); + } + } + + console.log(`[RawStream] End for ${subtype}: total ${chunks.length} items, country: ${country}`); + + if (subtype === 'price') { + processSheetPriceData(country, streamData.header || [], chunks); + sheetPriceStreamMeta.update((meta) => (meta ? { ...meta, status: 'complete' } : null)); + sheetPriceLoading.set(false); + } + + // Clear the streaming data + streamingRawData.update((data) => { + const newData = { ...data }; + delete newData[subtype]; + return newData; + }); +} + +// Process and store sheet price data +function processSheetPriceData(country: string, header: string[], chunks: any[]) { + console.log(`[SheetPrice] Processing data for ${country}:`, { + header, + chunksCount: chunks.length + }); + console.log(`[SheetPrice] Sample chunk:`, chunks[0]); + + // Try to extract header from first chunk item if not provided separately + // Backend sends header embedded in each item: { header: [...], key: "...", payload: [...] } + let effectiveHeader = header; + if ((!effectiveHeader || effectiveHeader.length === 0) && chunks.length > 0) { + const firstChunk = chunks[0]; + if (firstChunk?.header && Array.isArray(firstChunk.header)) { + effectiveHeader = firstChunk.header; + console.log(`[SheetPrice] Extracted header from first chunk:`, effectiveHeader); + } + } + + // Save header for dynamic column lookup later + if (effectiveHeader && effectiveHeader.length > 0) { + sheetPriceHeader.update((data) => ({ + ...data, + [country]: effectiveHeader + })); + console.log(`[SheetPrice] Saved header for ${country}:`, effectiveHeader); + } + + // Find column indices dynamically from header + // product_code header is typically "ProductCode" or similar + const productCodeIdx = findHeaderIndex(effectiveHeader, ['ProductCode', 'Product_Code', 'product_code', 'Code']); + console.log(`[SheetPrice] productCodeIdx from header:`, productCodeIdx, 'header:', effectiveHeader); + + const priceByProductCode: Record = {}; + // Track ALL rows per product code (for duplicates) + const allRowsByProductCode: Record = {}; + + for (const row of chunks) { + if (!row) { + console.log(`[SheetPrice] Row is null/undefined`); + continue; + } + + // Support new structure: {key, payload} where key=product_code + // payload can be: [{cells: [...], row_index: ...}, ...] - multiple entries for duplicates + if (row.key !== undefined) { + const productCode = row.key; + + // Handle payload structure - iterate ALL entries in payload for duplicates + if (Array.isArray(row.payload) && row.payload.length > 0) { + // Check if payload[0] has cells property (nested structure with row_index) + if (row.payload[0]?.cells) { + // payload: [{cells: [...], row_index: ...}, {cells: [...], row_index: ...}, ...] + // Store first one for backward compatibility + priceByProductCode[productCode] = row.payload[0].cells; + + // Store ALL rows for duplicate handling + if (!allRowsByProductCode[productCode]) { + allRowsByProductCode[productCode] = []; + } + for (const entry of row.payload) { + if (entry.cells && entry.row_index !== undefined) { + allRowsByProductCode[productCode].push({ + row: entry.row_index, + cells: entry.cells + }); + } + } + } else if (row.payload[0]?.coord) { + // payload: [{coord: {...}, value: ...}, ...] - flat cells array + priceByProductCode[productCode] = row.payload; + // Extract row from first cell's coord + const rowNum = row.payload[0]?.coord?.row; + if (rowNum !== undefined) { + allRowsByProductCode[productCode] = [{ row: rowNum, cells: row.payload }]; + } + } + } + continue; + } + + // Fallback: Check if row has cells or if row itself is the cells array + let cells: GristCell[] = row.cells || row; + + if (!Array.isArray(cells)) { + console.log(`[SheetPrice] Unknown row structure:`, row); + continue; + } + + // Find product_code from cells by column index + if (productCodeIdx >= 0) { + const productCodeCell = cells.find((c: GristCell) => c.coord?.col === productCodeIdx); + const productCode = productCodeCell?.value; + + if (productCode) { + priceByProductCode[productCode] = cells; + // Extract row from first cell's coord + const rowNum = cells[0]?.coord?.row; + if (rowNum !== undefined) { + if (!allRowsByProductCode[productCode]) { + allRowsByProductCode[productCode] = []; + } + allRowsByProductCode[productCode].push({ row: rowNum, cells }); + } + } + } + } + + lastRequestSheetPrice.update((data) => ({ + ...data, + [country]: { + ...(data[country] || {}), + ...priceByProductCode + } + })); + + // Update sheetPriceAllRows for duplicate handling + sheetPriceAllRows.update((data) => ({ + ...data, + [country]: { + ...(data[country] || {}), + ...allRowsByProductCode + } + })); + + console.log( + `[SheetPrice] Processed ${Object.keys(priceByProductCode).length} prices for ${country}` + ); + console.log(`[SheetPrice] Sample product codes:`, Object.keys(priceByProductCode).slice(0, 5)); + // Log duplicates info + const duplicates = Object.entries(allRowsByProductCode).filter(([_, rows]) => rows.length > 1); + if (duplicates.length > 0) { + console.log(`[SheetPrice] Found ${duplicates.length} product codes with duplicate rows:`, duplicates.slice(0, 3)); + } + if (chunks.length > 0 && Object.keys(priceByProductCode).length > 0) { + const sampleKey = Object.keys(priceByProductCode)[0]; + console.log(`[SheetPrice] Sample cells for ${sampleKey}:`, priceByProductCode[sampleKey]); + } +} + +// Reset sheet price stores +export function resetSheetPriceStore() { + sheetPriceStreamMeta.set(null); + sheetPriceLoading.set(false); + streamingRawData.set({}); +} + +// Check if a request type has already been sent +export function hasSheetPriceBeenSent(type: string): boolean { + return get(sheetPriceSentTypes).has(type); +} + +// Mark a request type as sent +export function markSheetPriceAsSent(type: string) { + sheetPriceSentTypes.update((types) => { + const newTypes = new Set(types); + newTypes.add(type); + return newTypes; + }); +} + +// Clear sent types (for reset) +export function clearSheetPriceSentTypes() { + sheetPriceSentTypes.set(new Set()); +} + +/** + * Add a newly generated product code to the store (local tracking before server sync) + */ +export function addGeneratedProductCode(code: string) { + existingProductCodes.update((codes) => { + const newSet = new Set(codes); + newSet.add(code); + return newSet; + }); + console.log('[sheetStore] Added generated code:', code); +} + +const PRODUCT_CODES_STORAGE_KEY = 'supra_product_codes'; + +// Track current/pending country for product codes +let currentProductCodesCountry = ''; +let pendingProductCodesCountry = ''; + +// Set pending country when making a request +export function setPendingProductCodesCountry(country: string) { + pendingProductCodesCountry = country; + console.log('[sheetStore] Pending product codes country:', country); +} + +// Load product codes from localStorage for specific country +export function loadProductCodesFromCache(country?: string): boolean { + try { + const cached = localStorage.getItem(PRODUCT_CODES_STORAGE_KEY); + if (cached) { + const data = JSON.parse(cached); + // Only load if country matches (or no country filter specified) + if (data.codes && Array.isArray(data.codes)) { + if (country && data.country && data.country !== country) { + console.log('[sheetStore] Cache is for different country:', data.country, '!= requested:', country); + // Clear the store for different country + existingProductCodes.set(new Set()); + return false; + } + existingProductCodes.set(new Set(data.codes)); + currentProductCodesCountry = data.country || ''; + console.log('[sheetStore] Loaded', data.codes.length, 'product codes from cache for', data.country || 'unknown'); + return true; + } + } + } catch (e) { + console.warn('[sheetStore] Failed to load from cache:', e); + } + // Clear store if no valid cache + existingProductCodes.set(new Set()); + return false; +} + +// Clear product codes (call when switching countries) +export function clearProductCodes() { + existingProductCodes.set(new Set()); + currentProductCodesCountry = ''; + console.log('[sheetStore] Cleared product codes'); +} + +export function handleListMenuResponse(payload: { codes: string[]; country?: string }) { + // Use pending country if not in payload + const country = payload.country || pendingProductCodesCountry; + console.log('[sheetStore] Received list_menu_response for', country, ':', payload.codes?.length, 'codes'); + + if (payload && payload.codes) { + existingProductCodes.set(new Set(payload.codes)); + currentProductCodesCountry = country; + + // Save to localStorage with country + try { + localStorage.setItem( + PRODUCT_CODES_STORAGE_KEY, + JSON.stringify({ + codes: payload.codes, + country: country, + timestamp: Date.now() + }) + ); + console.log('[sheetStore] Saved', payload.codes.length, 'product codes to cache for', country); + } catch (e) { + console.warn('[sheetStore] Failed to save to cache:', e); + } + } + productCodesLoading.set(false); +} diff --git a/src/lib/core/stores/websocketStore.ts b/src/lib/core/stores/websocketStore.ts index f3f7a18..720fb7e 100644 --- a/src/lib/core/stores/websocketStore.ts +++ b/src/lib/core/stores/websocketStore.ts @@ -17,6 +17,38 @@ export const socketConnectionOfflineCount = writable(0); export const socketAlreadySendHeartbeat = writable(0); export const socketStore = writable(null); +export function waitForOpenSocket(timeoutMs = 8000): Promise { + const currentSocket = get(socketStore); + if (currentSocket?.readyState === WebSocket.OPEN) { + return Promise.resolve(currentSocket); + } + + return new Promise((resolve) => { + let settled = false; + let unsubscribe = () => {}; + const timeout = setTimeout(() => { + if (settled) return; + settled = true; + unsubscribe(); + resolve(null); + }, timeoutMs); + + function settle(nextSocket: WebSocket | null) { + if (settled) return; + settled = true; + clearTimeout(timeout); + unsubscribe(); + resolve(nextSocket); + } + + unsubscribe = socketStore.subscribe((nextSocket) => { + if (nextSocket?.readyState === WebSocket.OPEN) { + settle(nextSocket); + } + }); + }); +} + export function connectToWebsocket(id_token?: string) { if (browser) { // console.log('connecting to ', env.PUBLIC_WSS); @@ -42,6 +74,13 @@ export function connectToWebsocket(id_token?: string) { let auth_data = get(authStore); let perms = get(permission); + // Debug: check if auth_data has uid + console.log('[WS Auth] Sending auth with:', { + uid: auth_data?.uid, + name: auth_data?.displayName, + email: auth_data?.email + }); + sendMessage({ type: 'auth', payload: { @@ -54,6 +93,7 @@ export function connectToWebsocket(id_token?: string) { } }); } + console.log(socket); // heartbeat 10s socketCheck = setInterval(() => { diff --git a/src/lib/core/types/catalogData.ts b/src/lib/core/types/catalogData.ts new file mode 100644 index 0000000..94bd076 --- /dev/null +++ b/src/lib/core/types/catalogData.ts @@ -0,0 +1,12 @@ +export interface MenuItem { + row_index: number; + name_th: string; + name_en: string; + product_codes: string[]; +} + +export interface CatalogDetail { + country: string; + catalog: string; + items: MenuItem[]; +} diff --git a/src/lib/core/types/outMessage.ts b/src/lib/core/types/outMessage.ts index bd14e6e..f51bd46 100644 --- a/src/lib/core/types/outMessage.ts +++ b/src/lib/core/types/outMessage.ts @@ -54,6 +54,15 @@ export type OutMessage = values: any; }; } + | { + type: 'list_menu'; + payload: { + user_info: any; + country: string; + boxid?: string; + }; + } + | { type: 'price'; payload: { diff --git a/src/lib/core/utils/productCode.ts b/src/lib/core/utils/productCode.ts new file mode 100644 index 0000000..0194d66 --- /dev/null +++ b/src/lib/core/utils/productCode.ts @@ -0,0 +1,162 @@ +import { get } from 'svelte/store'; +import { existingProductCodes } from '../stores/sheetStore'; + +// Country code mapping (country short from Android -> 2-digit product code prefix) +// Country short is read from /sdcard/coffeevending/country/short +export const countryCodeMap: Record = { + // Thailand + THAI: '12', + tha: '12', + // Malaysia + MYS: '12', + mys: '12', + // Indonesia + IDR: '13', + idr: '13', + // Australia + AUS: '51', + aus: '51', + // Singapore + SGP: '52', + sgp: '52', + SG: '52', // obsolete + // UAE Dubai + UAE_DUBAI: '53', + uae_dubai: '53', + dubai: '53', + // Hong Kong + HKG: '54', + hkg: '54', + // UK + GBR: '55', + gbr: '55', + // Romania + ROU: '56', + rou: '56', + // Latvia + LVA: '57', + lva: '57', + // Estonia + EST: '58', + est: '58', + // Lithuania + LTU: '59', + ltu: '59', + // USA Pepsi (uses Thai prefix) + USA_PEPSI: '12', + usa_pepsi: '12', + // Counter machines + counter01: '19' +}; + +// Temperature codes +export const tempCodes: Record = { + hot: '01', + cold: '02', + blend: '03' +}; + +// Category/Drink type codes +export const categoryOptions = [ + { value: '01', label: 'Coffee V1' }, + { value: '21', label: 'Coffee V2' }, + { value: '02', label: 'Tea' }, + { value: '03', label: 'Milk' }, + { value: '04', label: 'Whey' }, + { value: '05', label: 'Soda & Other' } +] as const; + +export type TempType = 'hot' | 'cold' | 'blend'; +export type CategoryCode = (typeof categoryOptions)[number]['value']; + +/** + * Get all existing product code suffixes (last 4 digits) + * @param category - Optional category code to filter by (e.g., '01' for Coffee V1) + */ +export function getExistingCodeSuffixes(category?: string): Set { + const codes = get(existingProductCodes); + + if (category) { + // Filter codes by category (2nd segment: XX-[category]-XX-XXXX) + return new Set( + [...codes] + .filter((code) => { + const parts = code.split('-'); + return parts[1] === category; + }) + .map((code) => code.split('-')[3]) + ); + } + + return new Set([...codes].map((code) => code.split('-')[3])); +} + +/** + * Generate next suffix by finding max existing + 1 for a specific category + * @param category - Optional category code to find max within (e.g., '01' for Coffee V1) + */ +export function generateNextSuffix(category?: string): string { + const suffixes = getExistingCodeSuffixes(category); + let maxSuffix = 0; + + for (const suffix of suffixes) { + const num = parseInt(suffix, 10); + if (!isNaN(num) && num > maxSuffix) { + maxSuffix = num; + } + } + + const nextSuffix = maxSuffix + 1; + + if (nextSuffix > 9999) { + throw new Error('Product code suffix exceeded 9999'); + } + + return String(nextSuffix).padStart(4, '0'); +} + +/** + * Generate a complete product code + * @param country - Country code (e.g., 'tha') + * @param category - Category code (e.g., '01' for Coffee V1) + * @param temp - Temperature type ('hot', 'cold', 'blend') + * @param suffix - Optional specific suffix, otherwise auto-generate + * @returns Product code like '12-01-01-0006' + */ +export function generateProductCode( + country: string, + category: string, + temp: TempType, + suffix?: string +): string { + const countryCode = countryCodeMap[country] || '99'; + const tempCode = tempCodes[temp]; + const codeSuffix = suffix ?? generateNextSuffix(); + + return `${countryCode}-${category}-${tempCode}-${codeSuffix}`; +} + +/** + * Check if a product code already exists + */ +export function isProductCodeExists(code: string): boolean { + const codes = get(existingProductCodes); + return codes.has(code); +} + +/** + * Generate a unique product code (auto-retry if exists) + */ +export function generateUniqueProductCode( + country: string, + category: string, + temp: TempType +): string { + const code = generateProductCode(country, category, temp); + + if (isProductCodeExists(code)) { + throw new Error(`Product code already exists: ${code}`); + } + + return code; +} diff --git a/src/lib/data/productcode.txt b/src/lib/data/productcode.txt new file mode 100644 index 0000000..fa6c13c --- /dev/null +++ b/src/lib/data/productcode.txt @@ -0,0 +1 @@ +["12-01-01-0003", "12-01-01-0001", "12-11-01-0001", "12-01-01-0004", "12-01-01-0005", "12-03-01-0005", "12-01-01-0006", "12-03-01-0002", "12-05-01-0002", "12-11-02-0001", "12-01-02-0001", "12-01-02-0002", "12-11-02-0002", "12-01-02-0003", "12-03-02-0002", "12-02-02-0002", "12-01-02-0004", "12-05-01-0001", "12-02-01-0007", "12-02-02-0003", "12-02-02-0007", "12-05-02-0010", "12-05-02-0007", "12-05-02-0021", "12-05-02-0020", "12-05-02-0036", "12-01-02-0018", "12-05-02-0035", "12-05-02-0037", "12-05-02-0034", "12-05-02-0001", "12-02-01-0003", "12-03-02-0006", "12-05-02-0002", "12-05-02-0003", "12-05-02-0009", "12-05-02-0008", "12-05-02-0015", "12-03-02-0001", "12-02-02-0010", "12-02-02-0009", "12-03-01-0004", "12-03-01-0001", "12-04-02-0002", "12-05-02-0017", "12-05-02-0014", "12-02-01-0001", "12-02-02-0001", "12-05-02-0018", "12-05-02-0016", "12-01-01-0007", "12-01-01-0009", "12-01-01-0010", "12-01-02-0006", "12-01-02-0007", "12-02-02-0004", "12-05-02-0019", "12-05-02-0012", "12-05-02-0011", "12-05-02-0013", "12-05-02-0004", "12-02-01-0004", "12-02-02-0008", "12-02-02-0006", "12-05-02-0006", "12-01-01-0008", "12-01-01-0011", "12-01-01-0012", "12-03-01-0003", "12-02-01-0002", "12-02-01-0006", "12-02-01-0008", "12-02-01-0009", "12-02-01-0010", "12-04-02-0001", "12-01-02-0005", "12-01-02-0008", "12-01-02-0009", "12-03-02-0003", "12-03-02-0004", "12-03-02-0005", "12-04-02-0003", "12-04-02-0008", "12-04-02-0004", "12-04-02-0005", "12-04-02-0006", "12-04-02-0007", "12-04-02-0009", "12-04-02-0010", "12-04-02-0011", "12-02-01-0011", "12-02-02-0012", "12-02-01-0012", "12-03-02-0007", "12-02-02-0011", "12-01-01-0014", "12-01-02-0011", "12-05-02-0022", "12-01-01-0013", "12-05-02-0023", "12-02-02-0013", "12-01-02-0012", "12-02-02-0015", "12-02-01-0016", "12-02-01-0017", "12-02-02-0017", "12-05-02-0024", "12-05-02-0025", "12-05-02-0026", "12-05-02-0027", "12-05-02-0028", "12-05-02-0029", "12-05-02-0030", "12-05-02-0031", "12-05-02-0032", "12-05-02-0033", "12-02-01-0019", "12-02-02-0019", "12-01-01-0017", "12-01-02-0017", "1", "12-04-02-0012", "12-02-01-0020", "12-02-01-0021", "12-02-02-0020", "12-05-02-0038", "12-05-02-0039", "12-05-02-0041", "12-04-02-0013", "12-02-02-0022", "12-02-01-0022", "12-01-02-0020", "12-01-01-0019", "12-01-02-0019", "12-01-01-0020", "12-03-03-0009", "12-01-02-0022", "12-01-01-0021", "12-01-02-0021", "12-01-01-0022", "12-03-02-0010", "12-02-02-0023", "12-03-02-0011", "12-04-02-0014", "12-05-01-0040", "12-06-01-0001", "12-06-01-0002", "12-01-02-0026", "12-01-01-0025", "12-01-02-0025", "12-01-01-0026", "12-01-02-0024", "12-01-01-0023", "12-01-02-0023", "12-01-01-0024", "12-01-02-0028", "12-01-01-0027", "12-01-02-0027", "12-01-01-0028", "12-01-01-0029", "12-01-01-0030", "12-01-01-0031", "12-01-02-0029", "12-01-02-0030", "12-01-02-0031", "12-02-01-0024", "12-02-01-0025", "12-02-01-0026", "12-02-01-0027", "12-02-01-0028", "12-02-02-0024", "12-02-02-0025", "12-02-02-0026", "12-02-02-0027", "12-02-02-0028", "12-02-02-0029", "12-02-02-0030", "12-02-02-0031", "12-02-02-0032", "12-04-02-0015", "12-04-02-0016", "12-04-02-0017", "12-04-02-1018", "12-04-02-1019", "12-04-02-1020", "12-04-02-1021", "12-04-02-1022", "12-01-02-0033", "12-01-01-0032", "12-01-02-0032", "12-01-01-0033", "12-01-01-0034", "12-01-02-0034", "12-02-01-0033", "12-02-01-0034", "12-02-02-0033", "12-02-02-0034", "12-03-01-0012", "12-03-02-0012", "12-05-01-0042", "12-05-02-0042", "12-05-02-0043", "12-02-01-0032", "12-02-01-0031", "12-03-02-0013", "12-02-01-0029", "12-02-01-0030", "12-01-02-0036", "12-01-01-0035", "12-01-02-0035", "12-01-01-0036", "12-03-03-0020", "12-03-01-0019", "12-03-02-0019", "12-01-02-0037", "12-01-01-0038", "12-01-02-0038", "12-01-01-0037", "12-03-03-0021", "12-03-01-0022", "12-03-02-0022", "12-05-02-0049", "12-05-02-0048", "12-05-02-0050", "12-05-02-0046", "12-05-02-0047", "12-01-05-0044", "12-05-02-0044", "12-01-01-0039", "12-01-02-0039", "12-02-01-0035", "12-04-02-0018", "12-01-02-0041", "12-05-02-0045", "12-02-01-0036", "12-02-02-0036", "12-01-01-1003", "12-01-01-1004", "12-01-02-1001", "12-01-02-1002", "12-01-02-1003", "12-02-02-1025", "12-02-02-1026", "12-02-02-1027", "12-02-02-1030", "12-02-02-1024", "12-03-01-1002", "12-03-02-1002", "12-05-02-1010", "12-01-01-1006", "12-02-01-1024", "12-02-01-1025", "12-02-01-1026", "12-02-01-1029", "12-02-01-1030", "12-02-01-1031", "12-02-01-1027", "12-02-02-1029", "12-02-02-1031", "12-03-01-1005", "12-03-02-1005", "12-05-01-1001", "12-05-02-1001", "12-01-02-1099", "12-01-01-1099", "12-21-01-0001", "12-21-01-0004", "12-21-01-0005", "12-51-01-0005", "12-07-01-0005", "12-21-01-0006", "12-21-01-0007", "12-21-01-0008", "12-21-01-0009", "12-21-01-0010", "12-21-01-0011", "12-21-01-0012", "12-21-01-0029", "12-21-01-0030", "12-21-01-0031", "12-21-01-0039", "12-21-02-0001", "12-21-02-0002", "12-21-02-0003", "12-21-02-0004", "12-21-02-0005", "12-21-02-0006", "12-21-02-0007", "12-21-02-0008", "12-21-02-0029", "12-21-02-0030", "12-21-02-0031", "12-21-02-0039", "12-21-02-0041", "12-31-01-0001", "12-31-02-0001", "12-31-02-0002", "12-21-01-0003", "12-21-01-0017", "12-21-02-0017", "12-02-01-0037", "12-02-02-0037", "12-02-01-1037", "12-02-02-1037", "12-21-01-1003", "12-21-01-1004", "12-21-01-1006", "12-21-01-1099", "12-21-02-1001", "12-21-02-1002", "12-21-02-1003", "12-21-02-1099", "12-01-01-0040", "12-01-02-0040", "12-02-01-0038", "12-02-02-0038", "12-03-02-0025", "12-03-01-0025", "12-01-01-0042", "12-01-02-0042", "12-21-01-0040", "12-21-02-0040", "12-05-03-0040", "12-01-02-0045", "12-01-01-0044", "12-01-02-0044", "12-01-01-0043", "12-03-01-0023", "12-03-01-0024", "12-03-02-0023", "12-03-02-0024", "12-21-01-0042", "12-21-02-0042", "12-03-03-0027", "12-03-03-0028", "12-03-03-0029", "12-03-03-0030", "12-03-03-0031", "12-01-03-0043", "12-02-03-0044", "12-99-03-0040", "12-05-02-0053", "12-05-02-0054", "12-01-02-0048", "12-01-01-0047", "12-01-02-0047", "12-01-01-0046", "12-01-02-0051", "12-01-01-0050", "12-01-02-0050", "12-01-01-0049", "12-01-02-0054", "12-01-02-0053", "12-02-02-0043", "12-02-03-0043", "12-02-02-0044", "12-03-02-0026", "12-05-02-0055", "12-05-02-0056", "12-02-01-0041", "12-02-01-0042", "12-02-02-0041", "12-02-02-0042", "12-05-02-0051", "12-05-02-0052", "12-03-03-0032", "12-02-01-0045", "12-99-01-0042", "12-02-01-0046", "12-99-01-0043", "12-02-02-0045", "12-99-02-0044", "12-02-02-0046", "12-99-02-0045", "12-02-01-0039", "12-02-02-0039", "12-02-02-0040", "12-99-03-0041", "12-02-01-0040", "12-02-01-0047", "12-02-01-1038", "12-02-01-1039", "12-02-01-1040", "12-02-02-1038", "12-02-02-1039", "12-02-02-1040", "12-02-02-0047", "12-01-01-0055", "12-01-02-0055", "12-04-02-0055", "12-21-01-0055", "12-21-02-0055", "12-99-02-0009", "12-99-02-0002", "12-99-02-0006", "12-99-02-0007", "12-99-02-0008", "12-99-02-0001", "12-99-02-0003", "12-99-02-0005", "12-99-01-0010", "12-99-02-0011", "12-99-02-0012", "12-99-02-0013", "12-99-02-0014", "12-99-02-0015", "12-99-02-0016", "12-99-03-0017", "12-99-02-0018", "12-99-02-0019", "12-99-03-0020", "12-99-03-0021", "12-99-03-0022", "12-99-03-0023", "12-99-03-0024", "12-99-03-0025", "12-99-03-0026", "12-99-03-0027", "12-99-03-0028", "12-99-03-0029", "12-99-03-0030", "12-99-03-0031", "12-99-03-0033", "12-99-03-0034", "12-99-03-0035", "12-99-03-0036", "12-99-03-0038", "12-99-03-0037", "12-99-03-0039", "12-99-03-0047", "12-02-03-0048", "12-99-03-0048", "12-02-01-0048", "12-99-01-0049", "12-02-02-0049", "12-99-02-0050", "12-05-02-0059", "12-99-05-0051", "12-05-02-0061", "12-99-05-0052", "12-05-02-0062", "12-01-02-0058", "12-05-02-0060", "12-05-02-0063", "12-21-01-0057", "12-21-02-0057", "12-01-02-0057", "12-02-02-0050", "12-05-02-0064", "12-05-02-0065", "12-21-02-0058", "12-99-05-0053", "12-99-05-0054", "12-99-05-0055", "12-99-05-0056", "12-99-05-0057", "12-99-05-0058", "12-99-05-0059", "12-99-05-0060", "12-99-05-0061", "12-99-05-0062", "12-99-05-0063", "12-99-05-0064", "12-99-05-0065", "12-99-03-0066", "12-01-01-0057", "12-01-01-0058", "12-21-01-0058", "12-02-01-0055", "12-02-01-0050", "12-03-01-0035", "12-03-01-0036", "12-05-02-0066", "12-05-02-0067", "12-03-02-0035", "12-03-02-0036", "12-03-02-0037", "12-03-82-0041", "12-03-82-0042", "12-03-82-0043", "12-03-83-0041", "12-03-83-0042", "12-03-83-0043", "12-01-01-0059", "12-21-01-0059", "12-01-02-0059", "12-21-02-0059", "12-01-01-0060", "12-21-01-0060", "12-02-01-0051", "12-02-02-0051", "12-03-01-0038", "12-03-02-0038", "12-05-02-0069", "12-03-02-0039", "12-05-02-0070", "12-05-02-0071", "12-05-02-0072", "12-02-02-0052", "12-03-03-0040", "12-05-02-0068", "12-01-02-0060", "12-21-02-0060", "12-05-02-0073", "12-03-03-0034", "12-41-03-0001", "12-41-03-0002", "12-41-03-0003", "12-41-03-0004", "12-41-03-0005", "12-41-03-0006", "12-41-03-0007", "12-41-03-0008", "12-41-03-0009", "12-41-03-0010", "12-41-03-0011", "12-41-03-0012", "12-41-03-0013", "12-41-03-0014", "12-41-03-0015", "12-41-03-0016", "12-41-03-0017", "12-41-03-0018", "12-41-03-0019", "12-41-03-0020", "12-41-03-0021", "12-41-03-0022", "12-41-03-0023", "12-41-03-0024", "12-41-03-0025", "12-03-03-0041", "12-03-03-0042", "12-03-03-0043", "12-05-02-0078", "12-02-02-0055", "12-01-02-0062", "12-02-02-0053", "12-02-02-0054", "12-03-03-0044", "12-03-03-0045", "12-01-02-0061", "12-21-02-0062", "12-21-02-0061", "12-41-03-0026", "12-21-62-0002", "12-21-62-0003", "12-21-62-0004", "12-03-62-0002", "12-03-62-0005", "12-03-62-0006", "12-05-62-0007", "12-05-62-0009", "12-05-62-0025", "12-05-62-0035", "12-05-62-0046", "12-02-62-0026", "12-02-62-0025", "12-02-62-0039", "12-02-62-0047", "12-41-03-0028", "12-02-62-0031", "12-41-03-0027", "12-01-02-0068", "12-21-02-0068", "12-03-01-0058", "12-03-02-0058", "12-03-03-0058", "12-03-01-0059", "12-03-02-0059", "12-03-03-0059", "12-02-02-0060", "12-02-02-0061", "12-02-02-0062", "12-02-02-0063", "12-02-02-0064", "12-02-02-0065", "12-05-02-0090", "12-05-02-0091", "12-02-03-0060", "12-02-03-0061", "12-02-03-0062", "12-02-03-0063", "12-02-03-0064", "12-02-03-0065", "12-02-01-0061", "12-02-01-0062", "12-02-01-0063", "12-02-01-0064", "12-02-01-0065", "12-03-01-0048", "12-03-02-0048", "12-03-03-0049", "12-05-02-0094", "12-05-02-0095", "12-05-02-0093", "12-05-03-0092", "12-05-02-0096", "12-05-02-0097", "12-01-02-0069", "12-01-02-0070", "12-21-02-0069", "12-21-02-0070", "12-05-02-0100", "12-05-02-0101", "12-05-02-0102", "12-05-02-0103", "12-05-02-0115", "12-05-02-0116", "12-05-02-0104", "12-05-02-0105", "12-05-02-0106", "12-05-02-0107", "12-05-02-0108", "12-05-02-0109", "12-05-02-0114", "12-03-03-0060", "12-05-02-0117", "12-01-02-0064", "12-21-02-0064", "12-21-02-0071", "12-03-03-0061", "12-04-02-0056", "12-04-02-0057", "12-04-02-0058", "12-99-03-0068", "12-05-02-0118", "12-05-02-0119", "12-05-02-0120", "12-03-02-0065", "12-03-02-0066", "12-03-02-0067", "12-03-02-0068", "12-03-02-0069", "12-03-01-0068", "12-05-02-0121", "12-03-02-0070", "12-03-02-0071", "12-03-02-0074", "12-21-02-0073", "12-03-02-0073", "12-02-02-0067", "12-21-02-0074", "12-01-02-0074", "12-01-02-0073", "12-05-03-0064", "12-02-02-0056", "12-03-03-0046", "12-02-03-0053", "12-05-03-0115", "12-21-02-0075", "12-01-02-0075", "12-03-02-0075", "12-05-02-0124", "12-02-02-0068", "12-05-02-0125", "12-03-02-0076", "12-02-02-0069", "12-05-02-0126", "12-05-02-0127", "12-02-02-0070", "12-99-05-0073", "12-03-03-0075", "12-02-03-0068", "12-05-03-0124", "12-03-03-0076", "12-02-03-0069", "12-05-03-0126", "12-05-03-0127", "12-02-03-0070", "12-01-02-0081", "12-21-02-0081", "12-01-01-0081", "12-21-01-0081", "12-02-02-0080", "12-05-03-0128", "12-03-03-0084", "12-03-03-0085", "12-02-02-0082", "12-02-01-0079", "12-03-03-0081", "12-01-02-0082", "12-21-02-0082", "12-03-02-0080", "12-03-03-0080", "12-02-01-0078", "12-01-02-0083", "12-21-02-0083", "12-03-03-0082", "12-03-03-0083", "12-03-03-0065", "12-03-03-0067", "12-01-03-0078", "12-21-03-0078", "12-02-03-0057", "12-02-03-0058", "12-99-03-0078", "12-99-03-0079", "12-99-03-0032", "12-99-03-0058", "12-99-03-0011", "12-99-03-0059", "12-99-03-0062", "12-99-03-0004", "12-99-03-0010", "12-99-03-0127", "12-99-03-0065", "12-99-03-0067", "12-99-03-0074", "12-99-03-0075", "12-99-03-0076", "12-99-03-0077", "12-02-03-0056", "12-03-03-0086", "12-03-03-0026", "12-03-03-0087", "12-03-02-0081", "12-03-03-0088", "12-03-03-0089", "12-01-02-0084", "12-21-02-0084", "12-02-02-0081", "12-02-01-0083", "12-02-02-0083", "12-02-03-0083", "12-02-01-0084", "12-02-02-0084", "12-02-03-0084", "12-02-01-0085", "12-02-02-0085", "12-02-03-0085", "12-03-03-0094", "12-03-03-0095", "12-05-02-0133", "12-99-05-0075", "12-99-05-0074", "12-01-03-0085", "12-21-03-0085", "12-02-03-0086", "12-02-03-0087", "12-03-03-0090", "12-03-03-0091", "12-03-03-0093", "12-03-03-0092", "12-01-01-0074", "12-01-03-0074", "12-21-01-0074", "12-21-03-0074", "12-03-01-0010", "12-05-02-0040", "12-05-03-0023", "12-03-03-0100", "12-03-03-0101", "12-03-03-0102", "12-05-02-0136", "12-05-03-0136", "12-05-02-0137", "12-05-03-0137", "12-05-02-0138", "12-05-03-0138", "12-05-02-0139", "12-05-03-0139", "12-05-02-0140", "12-05-03-0140", "12-05-02-0141", "12-05-03-0141", "12-05-02-0142", "12-05-03-0142", "12-05-02-0143", "12-05-03-0143", "12-05-02-0144", "12-05-03-0144", "12-05-02-0145", "12-05-03-0145", "12-05-02-0146", "12-05-03-0146", "12-01-02-0090", "12-21-02-0090", "12-02-01-0066", "12-02-02-0066", "12-02-03-0066", "12-02-02-0090", "12-02-01-0089", "12-02-02-0089", "12-02-03-0089", "12-01-03-0090", "12-21-03-0090", "12-03-03-0103", "12-03-03-0104", "12-03-03-0105", "12-03-03-0106", "12-02-02-0099", "12-02-01-0091", "12-02-02-0091", "12-02-03-0091", "12-02-01-0092", "12-02-02-0092", "12-02-03-0092", "12-02-01-0093", "12-02-02-0093", "12-02-03-0093", "12-02-01-0094", "12-02-02-0094", "12-02-03-0094", "12-02-01-0095", "12-02-02-0095", "12-02-03-0095", "12-02-01-0096", "12-02-02-0096", "12-02-03-0096", "12-02-01-0097", "12-02-02-0097", "12-02-03-0097", "12-02-02-0098", "12-05-02-0147", "12-05-02-0057", "12-05-02-0058", "12-05-02-0148", "12-05-02-0149", "12-05-02-0150", "12-02-01-0103", "12-02-01-0104", "12-02-02-0103", "12-02-02-0104", "12-02-03-0103", "12-02-03-0104", "12-05-03-0133", "12-05-03-106", "12-05-03-0057", "12-05-03-0058", "12-05-03-0148", "12-05-03-0149", "12-05-03-0150", "12-05-03-0106", "12-01-07-0001", "12-00-07-0000", "12-01-03-0092", "12-21-03-0092", "12-02-03-0105", "12-02-03-0106", "12-02-03-0107", "12-02-03-0108", "12-03-03-0108", "12-03-03-0109", "12-03-03-0110", "12-01-01-0088", "12-01-02-0088", "12-01-03-0088", "12-21-01-0088", "12-21-02-0088", "12-21-03-0088", "12-01-02-0091", "12-21-02-0091", "12-02-01-0088", "12-02-02-0088", "12-02-03-0088", "12-02-01-0102", "12-02-02-0102", "12-02-03-0102", "12-02-01-0101", "12-02-02-0101", "12-02-03-0101", "12-05-01-0134", "12-05-02-0134", "12-05-03-0134", "12-05-02-0135", "12-05-03-0135", "12-05-02-0151", "12-05-03-0151", "12-03-03-0107", "12-05-01-0152", "12-05-02-0152", "12-05-03-0152", "12-06-03-0001", "12-06-03-0002", "12-06-03-0003", "12-06-03-0004", "12-06-03-0005", "12-06-03-0006", "12-02-01-0109", "12-02-02-0109", "12-02-03-0109", "12-02-01-0110", "12-02-02-0110", "12-02-03-0110", "12-02-01-0111", "12-02-02-0111", "12-02-03-0111", "12-02-01-0112", "12-02-02-0112", "12-02-03-0112", "12-02-01-0113", "12-02-02-0113", "12-02-03-0113", "12-02-01-0114", "12-02-02-0114", "12-02-01-0115", "12-02-02-0115", "12-02-01-0116", "12-02-02-0116", "12-02-01-0117", "12-02-02-0117", "12-02-01-0118", "12-02-02-0118", "12-01-01-0093", "12-01-02-0093", "12-21-01-0093", "12-21-02-0093", "12-01-01-0094", "12-01-02-0094", "12-21-02-0094", "12-21-01-0094", "12-01-01-0095", "12-01-02-0095", "12-21-01-0095", "12-21-02-0095", "12-01-01-0096", "12-01-02-0096", "12-21-01-0096", "12-21-02-0096", "12-01-01-0097", "12-01-02-0097", "12-21-01-0097", "12-21-02-0097", "12-01-01-0098", "12-01-02-0098", "12-21-01-0098", "12-21-02-0098", "12-01-01-0068", "12-21-01-0068", "12-99-05-0077", "12-05-02-0160", "12-99-05-0076", "12-05-03-0160", "12-01-02-0099", "12-21-02-0099", "12-03-02-0111", "12-03-02-0112", "12-03-03-0111", "12-03-03-0112", "12-05-02-0161", "12-05-03-0161", "12-01-02-0103", "12-21-02-0103", "12-01-02-0104", "12-21-02-0104", "12-01-02-0105", "12-21-02-0105", "12-01-02-0106", "12-21-02-0106", "12-01-02-0107", "12-21-02-0107", "12-01-02-0108", "12-21-02-0108", "12-01-02-0109", "12-21-02-0109", "12-01-02-0110", "12-21-02-0110", "12-02-02-0119", "12-02-02-0120", "12-02-02-0121", "12-03-02-0113", "12-05-02-0162", "12-05-02-0163", "12-05-02-0164", "12-05-02-0165", "12-01-02-0100", "12-21-02-0100", "12-01-02-0101", "12-21-02-0101", "12-01-02-0102", "12-21-02-0102", "12-02-03-0119", "12-02-03-0120", "12-02-03-0121", "12-03-03-0113", "12-05-03-0162", "12-05-03-0163", "12-05-03-0164", "12-05-03-0165", "12-01-02-0111", "12-21-02-0111", "12-01-02-0112", "12-21-02-0112", "12-02-02-0122", "12-02-02-0123", "12-05-02-0166", "12-05-02-0167", "12-01-03-0112", "12-21-03-0112", "12-02-03-0122", "12-02-03-0123", "12-03-03-0114", "12-05-03-0166", "12-05-03-0167", "12-03-02-0115", "12-03-03-0115", "12-02-02-0125", "12-02-03-0126", "12-02-02-0127", "12-02-03-0128", "12-02-02-0129", "12-02-03-0130", "12-05-02-0169", "12-05-02-0170", "12-05-02-0171", "12-05-02-0172", "12-05-02-0173", "12-05-02-174", "12-05-02-0174", "12-03-03-0116", "12-05-02-0177", "12-05-02-0178", "12-05-02-0179", "12-05-02-0180", "12-05-02-0181", "12-05-02-0182", "12-05-02-0183", "12-05-02-0184", "12-05-03-0178", "12-05-03-0179", "12-05-03-0180", "12-05-03-0181", "12-05-03-0182", "12-05-03-0183", "12-05-03-0184", "12-05-03-0185", "12-02-01-0132", "12-02-02-0132", "12-05-02-0188", "12-05-02-0189", "12-05-02-0190", "12-05-02-0191", "12-01-02-0116", "12-21-02-0116", "12-03-03-0122", "12-03-03-0123", "12-01-01-0117", "12-01-02-0117", "12-21-01-0117", "12-21-02-0117", "12-02-01-0133", "12-02-02-0133", "12-02-01-0134", "12-02-02-0134", "12-02-01-0135", "12-02-02-0135", "12-03-03-0124", "12-03-03-0125", "12-03-03-0126", "12-05-01-0192", "12-05-02-0192", "12-05-03-0192", "12-05-02-0193", "12-05-03-0193", "11-99-05-0060", "11-99-05-0061", "11-99-05-0062", "11-99-05-0063", "11-99-05-0064", "11-99-05-0065", "12-99-02-0064", "12-99-02-0065", "12-99-03-0080", "12-99-02-0066", "12-99-02-0067", "12-99-03-0081", "12-99-03-0082", "12-03-01-0129", "12-01-02-0092", "12-21-02-0092", "12-03-03-0047", "12-03-02-0047", "12-03-01-0130", "12-03-02-0130", "12-03-03-0130", "12-03-01-0131", "12-03-02-0131", "12-03-03-0131", "12-03-01-0047", "12-04-02-0059", "12-04-02-0060", "12-04-02-0061", "12-04-02-0062", "12-04-02-0063", "12-04-02-0064", "12-04-02-0065", "12-04-02-0066", "12-04-02-0067", "12-04-02-0068", "12-04-02-0069", "12-04-02-0070", "12-04-02-0071", "12-04-02-0072", "12-04-02-0073", "12-04-02-0074", "12-02-02-0148", "12-02-02-0149", "12-02-02-0150", "12-02-02-0151", "12-02-02-0152", "12-02-02-0153", "12-02-02-0154", "12-02-02-0155", "12-02-02-0156", "12-02-02-0157", "12-02-02-0158", "12-03-03-0132", "12-03-03-0133", "12-01-03-0119", "12-21-03-0119", "12-03-03-0134", "12-02-03-0159", "12-02-03-0160", "12-02-03-0161", "12-03-03-0135", "12-03-03-0136", "12-03-03-0137", "12-03-03-0138", "12-03-03-0139", "12-03-03-0140", "12-03-03-0141", "12-03-03-0142", "12-03-03-0143", "12-03-03-0144", "12-03-03-0145", "12-99-02-0062", "12-99-02-0133", "12-99-02-0160", "12-99-02-0094", "12-99-02-0177", "12-99-02-0178", "12-99-02-0179", "12-99-02-0180", "12-99-02-0181", "12-99-02-0182", "12-99-02-0183", "12-99-02-0184", "12-99-02-0106", "12-99-02-0114", "12-99-02-0104", "12-99-02-0161", "12-99-02-0136", "12-99-03-0103", "12-99-02-0010", "12-99-02-0134", "12-99-02-0192", "12-99-03-0104", "12-99-03-0105", "12-99-02-0111", "12-99-02-0070", "12-99-02-0069", "12-99-02-0191", "12-99-02-0188", "12-99-02-0040", "12-99-02-0193", "12-99-02-0004", "12-99-02-0195", "12-99-02-0196", "12-99-02-0197", "12-99-02-0198", "12-99-02-0199", "12-99-02-0200", "12-99-03-0186", "12-99-03-0187", "12-99-03-0188"] diff --git a/src/lib/workers/androidRecipeExport.worker.ts b/src/lib/workers/androidRecipeExport.worker.ts new file mode 100644 index 0000000..9727d6e --- /dev/null +++ b/src/lib/workers/androidRecipeExport.worker.ts @@ -0,0 +1,121 @@ +type AndroidRecipeExportRow = { + lineNumber: number; + cells: string[]; + values: Record; +}; + +type AndroidRecipeExportData = { + headers: string[]; + rows: AndroidRecipeExportRow[]; + lineCount: number; +}; + +type ParseRequest = { + id: number; + raw: string; + maxRows: number; +}; + +function normalizeHeader(value: string, index: number): string { + const header = value.trim().replace(/^\uFEFF/, ''); + return header || `Column ${index + 1}`; +} + +function splitTsvLine(line: string): string[] { + return line.split('\t').map((cell) => cell.trim()); +} + +function collectNonEmptyLines(raw: string, maxLines: number): string[] { + const lines: string[] = []; + let lineStart = 0; + + for (let index = 0; index <= raw.length; index += 1) { + const isEnd = index === raw.length; + const char = raw[index]; + + if (!isEnd && char !== '\n') continue; + + const lineEnd = index > lineStart && raw[index - 1] === '\r' ? index - 1 : index; + const line = raw.slice(lineStart, lineEnd); + + if (line.trim().length > 0) { + lines.push(line); + if (lines.length >= maxLines) break; + } + + lineStart = index + 1; + } + + return lines; +} + +function buildUniqueHeaders(rawHeaders: string[], maxColumns: number): string[] { + const headers = [...rawHeaders]; + + for (let i = headers.length; i < maxColumns; i += 1) { + headers.push(`Column ${i + 1}`); + } + + const seen = new Map(); + return headers.map((header, index) => { + const normalized = normalizeHeader(header, index); + const count = seen.get(normalized) ?? 0; + seen.set(normalized, count + 1); + + return count === 0 ? normalized : `${normalized} ${count + 1}`; + }); +} + +function parseAndroidRecipeExport(raw: string, maxRows: number): AndroidRecipeExportData { + const maxLines = Number.isFinite(maxRows) ? Math.max(1, maxRows + 1) : Number.MAX_SAFE_INTEGER; + const lines = collectNonEmptyLines(raw, maxLines); + + if (lines.length === 0) { + return { + headers: [], + rows: [], + lineCount: 0 + }; + } + + const parsedLines = lines.map(splitTsvLine); + const maxColumns = Math.max(...parsedLines.map((line) => line.length)); + const headers = buildUniqueHeaders(parsedLines[0], maxColumns); + + const rows = parsedLines.slice(1).map((cells, index) => { + const paddedCells = [...cells]; + + for (let cellIndex = paddedCells.length; cellIndex < headers.length; cellIndex += 1) { + paddedCells.push(''); + } + + const values = Object.fromEntries( + headers.map((header, cellIndex) => [header, paddedCells[cellIndex] ?? '']) + ); + + return { + lineNumber: index + 2, + cells: paddedCells, + values + }; + }); + + return { + headers, + rows, + lineCount: lines.length + }; +} + +self.onmessage = (event: MessageEvent) => { + const { id, raw, maxRows } = event.data; + + try { + const parsed = parseAndroidRecipeExport(raw, maxRows); + self.postMessage({ id, parsed }); + } catch (error: any) { + self.postMessage({ id, error: error?.message ?? 'Unable to parse recipe export.' }); + } +}; + +export {}; diff --git a/src/routes/(authed)/+layout.svelte b/src/routes/(authed)/+layout.svelte index 5bf2f28..34b2e1b 100644 --- a/src/routes/(authed)/+layout.svelte +++ b/src/routes/(authed)/+layout.svelte @@ -7,20 +7,38 @@ import '../layout.css'; import ErrorLayout from '$lib/components/error-layout.svelte'; import { sidebarStore } from '$lib/core/stores/sidebar'; - import { onMount } from 'svelte'; import { auth } from '$lib/core/stores/auth'; - import { get } from 'svelte/store'; import { connectToWebsocket } from '$lib/core/stores/websocketStore'; import * as adb from '$lib/core/adb/adb'; import { addNotification } from '$lib/core/stores/noti'; - import { AdbDaemonWebUsbDeviceManager } from '@yume-chan/adb-daemon-webusb'; + import { page } from '$app/stores'; + import { + AdbDaemonWebUsbDevice, + AdbDaemonWebUsbDeviceManager + } from '@yume-chan/adb-daemon-webusb'; import AdbWebCredentialStore from '@yume-chan/adb-credential-web'; import { deviceCredentialManager } from '$lib/core/adb/deviceCredManager'; let { children } = $props(); + let websocketConnectedForUid = $state(''); + let adbReconnectTriedForUid = $state(''); + + function getAutoConnectChannel(pathname: string) { + if (pathname.startsWith('/tools/create-menu')) { + return 'recipe'; + } + + if (pathname.startsWith('/tools/brew')) { + return 'brew'; + } + + return 'adb'; + } async function tryAutoConnect() { try { + if (adb.getAdbInstance()) return true; + if (!('usb' in navigator) || !AdbDaemonWebUsbDeviceManager.BROWSER) { throw new Error('WebUSB not supported, try using fallback or different browser'); } @@ -38,7 +56,12 @@ const credStore = new AdbWebCredentialStore(); try { - await adb.connectDeviceByCred(device, credStore); + const channel = getAutoConnectChannel($page.url.pathname); + if (channel === 'recipe') { + await adb.connectRecipeMenuDeviceByCred(device, credStore); + } else { + await adb.connectDeviceByCred(device, credStore, channel === 'brew'); + } return true; } catch (e: any) { if (e.message === 'CREDENTIAL_EXPIRED') { @@ -61,24 +84,28 @@ } } - onMount(async () => { - let currentUser = get(auth); - // console.log(`on mount layout current user: ${JSON.stringify(currentUser)}`); - if (currentUser) { - // console.log('id', await currentUser.getIdToken()); - - console.log('connect ws on mount'); - connectToWebsocket(await currentUser.getIdToken()); - await tryAutoConnect(); - } - }); - $effect(() => { - console.log('connect ws on effect'); + const currentUser = $auth; - setTimeout(async () => { - connectToWebsocket(await get(auth)?.getIdToken()); - }, 100); + if (!currentUser) { + websocketConnectedForUid = ''; + adbReconnectTriedForUid = ''; + return; + } + + if (websocketConnectedForUid !== currentUser.uid) { + websocketConnectedForUid = currentUser.uid; + console.log('connect ws after auth ready'); + + void currentUser.getIdToken().then((idToken) => { + connectToWebsocket(idToken); + }); + } + + if (adbReconnectTriedForUid !== currentUser.uid && !adb.getAdbInstance()) { + adbReconnectTriedForUid = currentUser.uid; + void tryAutoConnect(); + } }); @@ -94,7 +121,7 @@ }} > -
+
{@render children()}
diff --git a/src/routes/(authed)/admin/+layout.svelte b/src/routes/(authed)/admin/+layout.svelte index 3730cf4..ba25fe8 100644 --- a/src/routes/(authed)/admin/+layout.svelte +++ b/src/routes/(authed)/admin/+layout.svelte @@ -72,10 +72,14 @@

Admin Panel

-

Manage users, roles, and system settings

+

Manage users, roles, and system settings

- + diff --git a/src/routes/(authed)/departments/+page.svelte b/src/routes/(authed)/departments/+page.svelte index 869544a..e2e80f6 100644 --- a/src/routes/(authed)/departments/+page.svelte +++ b/src/routes/(authed)/departments/+page.svelte @@ -26,7 +26,7 @@ departmentStore.set(cnt); if (refPage === 'sheet') { - await goto('/sheet/overview'); + await goto(`/sheet/overview/${cnt}`); } else { await goto('/recipe/overview'); } diff --git a/src/routes/(authed)/sheet/add/[country]/[catalog]/+page.svelte b/src/routes/(authed)/sheet/add/[country]/[catalog]/+page.svelte new file mode 100644 index 0000000..3a88e2f --- /dev/null +++ b/src/routes/(authed)/sheet/add/[country]/[catalog]/+page.svelte @@ -0,0 +1,846 @@ + + + +
+ +
+
+
+ +
+

+ Add Menu: {getCatalogDisplayName(catalog)} +

+

+ {country.toUpperCase()} • {catalog} +

+
+
+ +
+ 10 ? 'default' : 'destructive'}> + Lock: {lockTimeout}s + + + + +
+
+
+ + +
+
+ + + + +
+ 1 +
+ Basic Information +
+ Enter menu name and description +
+ +
+
+ + +
+ {#if langConfig.secondary} +
+ + +
+ {/if} +
+
+
+ + +
+ {#if langConfig.secondary} +
+ + +
+ {/if} +
+
+
+ + + + + +
+ 2 +
+ Product Codes +
+ + Click on each field to select category and generate code + {#if existingCodeSet.size > 0} + ({existingCodeSet.size} existing codes) + {/if} + +
+ +
+ +
+ + + {:else} + Click to add... + + {/if} + +
+ + +
+ + + {:else} + Click to add... + + {/if} + +
+ + +
+ + + {:else} + Click to add... + + {/if} + +
+
+ + + +
+
+ + + + + +
+ 3 +
+ Image & Categories +
+ Set image filename and menu categories +
+ +
+
+ + +
+
+ + +
+
+
+ + + +
+
+
+ + +
+ + +
+
+
+
+ + + + + + + Select {tempLabels[codePopupType]} Product Code + + + Choose from existing product codes (Server, Draft, or Machine) + + + + +
+
+ + +
+ +
+ + +
+ {#if loadingCodes} +
+ + Loading codes... +
+ {:else if filteredCodes().length === 0} +
+ {#if codeSearchQuery} + No codes found matching "{codeSearchQuery}" + {:else} + No {tempLabels[codePopupType].toLowerCase()} product codes available + {/if} +
+ {:else} + {#each filteredCodes() as item} + + {/each} + {/if} +
+ +
+
+ {filteredCodes().length} code{filteredCodes().length !== 1 ? 's' : ''} available +
+ +
+
+
diff --git a/src/routes/(authed)/sheet/edit/[country]/[catalog]/+page.svelte b/src/routes/(authed)/sheet/edit/[country]/[catalog]/+page.svelte new file mode 100644 index 0000000..d8faf31 --- /dev/null +++ b/src/routes/(authed)/sheet/edit/[country]/[catalog]/+page.svelte @@ -0,0 +1,2656 @@ + + + +
+ +
+
+
+ +
+

+ Edit: {getCatalogDisplayName(catalog)} +

+

+ {country.toUpperCase()} • Primary language: {getCountryPrimaryLanguage(country)} • {catalog} +

+
+
+ +
+ + 10 ? 'default' : 'destructive'}> + Lock: {lockTimeout}s + + + + + + + + + + +
+
+
+ + +
+ {#if loading} +
+ +

Loading menu items...

+ {#if streamMeta} +
+ +

+ Chunk {streamMeta.current_chunk} / {streamMeta.total_chunks} + ({streamMeta.total_items} items) +

+
+ {/if} +
+ {:else if isSwapMode} +
+
+
+

Swap Menu Positions

+ +
+
+ + +
+
+ +
+ {#each swapItems as item, index (item.row_index)} +
handleSwapDragStart(event, index)} + ondragover={handleSwapDragOver} + ondrop={(event) => handleSwapDrop(event, index)} + ondragend={handleSwapDragEnd} + class:opacity-50={draggingSwapIndex === index} + > + + +
+ + + + +
+ +
+ {#if item.image_url} + {#key `${item.row_index}-${item.image_name ?? item.image_url}`} + + {/key} + {:else} +
+ No image +
+ {/if} +
+ +
+

+ {formatSheetText(item.primary_name || item.name_en, 'Untitled menu')} +

+ {#if item.secondary_name} +

+ {formatSheetText(item.secondary_name)} +

+ {/if} +
+
+
+
+ {/each} +
+
+ {:else if isEditMode && editingItem} + +
+ + +
+
+ {#if editingItem.image_url} + {#key `${editingItem.row_index}-${editingItem.image_name ?? editingItem.image_url}`} + + {/key} + {:else} +
+ No image +
+ {/if} +
+ +
+
+

+ Item #{editingItem.row_index} +

+

+ {formatSheetText( + editingItem.primary_name || editingItem.name_en, + 'Untitled menu' + )} +

+ {#if editingItem.secondary_name} +

+ {formatSheetText(editingItem.secondary_name)} +

+ {/if} + {#if editingItem.image_name} +

+ {formatSheetText(editingItem.image_name)} +

+ {/if} +
+ + {#if editingItem.product_codes && editingItem.product_codes.length > 0} +
+ {#each editingItem.product_codes as code} + + {formatSheetText(code)} + + {/each} +
+ {/if} + + + {#if getItemPrices(editingItem).hot || getItemPrices(editingItem).cold || getItemPrices(editingItem).blend} + {@const displayPrices = getItemPrices(editingItem)} +
+
+ {#if displayPrices.hot} + Hot {displayPrices.hot} + {/if} + {#if displayPrices.cold} + Cold {displayPrices.cold} + {/if} + {#if displayPrices.blend} + Blend {displayPrices.blend} + {/if} +
+ +
+ {:else if editingItem.product_codes?.length > 0} + + {/if} + +
+
+ + +
+
+ + +
+
+
+ +
+ + +
+
+
+
+ + {#if getVisibleSections(editingItem.new_layout_v2).length > 0} + + + Menu Data + + + {#each getVisibleSections(editingItem.new_layout_v2) as section} +
+
+

{getSectionTitle(section)}

+ Row {section.row_index} +
+
+ {#each getVisibleCells(section) as cell} +
+ + +
+ {/each} +
+
+ {/each} +
+
+ {/if} + + {#if getVisibleSections(editingItem.name_desc_v2).length > 0} + + + Translations + + + {#each getVisibleSections(editingItem.name_desc_v2) as section} +
+
+

{section.key}

+ Row {section.row_index} +
+
+ {#each getVisibleCells(section) as cell} +
+ + +
+ {/each} +
+
+ {/each} +
+
+ {/if} +
+ {:else} + +
+ {#each visibleMenuItems as item, index (item.row_index)} + + + + {#if newlyAddedRowIndices.has(item.row_index)} + NEW + {/if} + +
+ + +
+ +
+ {#if item.image_url} + {#key `${item.row_index}-${item.image_name ?? item.image_url}`} + + {/key} + {:else} +
+ No image +
+ {/if} +
+ +
+ +

+ {formatSheetText(item.primary_name || item.name_en, 'Untitled menu')} +

+ {#if item.secondary_name} +

+ {formatSheetText(item.secondary_name)} +

+ {/if} + +
+ + + {@const prices = getItemPrices(item)} + {#if prices.hot || prices.cold || prices.blend} +
+ {#if prices.hot} + Hot {prices.hot} + {/if} + {#if prices.cold} + Cold {prices.cold} + {/if} + {#if prices.blend} + Blend {prices.blend} + {/if} +
+ {/if} + + {#if item.product_codes && item.product_codes.length > 0} +
+
+ {#each item.product_codes.slice(0, 4) as code} + + {formatSheetText(code)} + + {/each} + {#if item.product_codes.length > 4} + + +{item.product_codes.length - 4} + + {/if} +
+
+ {/if} +
+
+ {/each} +
+ {/if} +
+
+ + + + + + + + Delete Menu + + + Are you sure you want to delete this menu item? This action cannot be undone. + + + + {#if itemToDelete} +
+

+ {formatSheetText(itemToDelete.primary_name || itemToDelete.name_en, 'Untitled')} +

+ {#if itemToDelete.secondary_name} +

+ {formatSheetText(itemToDelete.secondary_name)} +

+ {/if} +

Item #{itemToDelete.row_index}

+
+ {/if} + +
+ + +
+
+
+ + + + +
+
+ + + + Preview Online Menus + + + + +
+ {#each availablePreviewLanguageOptions as option} + + {/each} +
+
+ +
+ {#if previewLoading} +
+ +

Loading menus from machine...

+
+ {:else if previewMenus.length === 0} +
+

No online menus found

+
+ {:else if visiblePreviewMenus.length === 0} +
+

All menus have HIDE prices

+
+ {:else} +
+ {#each visiblePreviewMenus as menu (menu.row_index)} + {@const prices = getMenuPrices(menu)} + + + +
+ {#if menu.image_url} + {#key `${menu.productCode}-${menu.image_name ?? menu.image_url}`} + + {/key} + {:else} +
+ No image +
+ {/if} +
+ + +
+

+ {getPreviewMenuName(menu)} +

+ {#if getPreviewMenuDescription(menu)} +

+ {getPreviewMenuDescription(menu)} +

+ {/if} +
+ + +
+
+
+ {getVariantLabel('hot')} + {prices.hot} +
+
+ {getVariantLabel('cold')} + {prices.cold} +
+
+ {getVariantLabel('blend')} + {prices.blend} +
+
+
+
+ + {getVariantProductCode(menu, 'hot') || '—'} + +
+
+ + {getVariantProductCode(menu, 'cold') || '—'} + +
+
+ + {getVariantProductCode(menu, 'blend') || '—'} + +
+
+
+
+
+ {/each} +
+ {/if} +
+ + +
+
+
+

Gen Layout

+

+ Generate and push layout menu files to Machine +

+
+
+ + + {#if genLayoutStatus === 'complete' && genLayoutFiles.length > 0} + + {/if} + + +
+
+ + + {#if genLayoutStatus === 'receiving'} +
+ +

+ Receiving files: {$genLayoutBatch.files.length} / {$genLayoutBatch.total_files} +

+
+ {/if} + + {#if pushingToAndroid} +
+ 0 ? (pushProgress.current / pushProgress.total) * 100 : 0} + max={100} + class="h-2" + /> +

+ Pushing to Android: {pushProgress.current} / {pushProgress.total} +

+
+ {/if} + + + {#if genLayoutStatus === 'complete' && genLayoutFiles.length > 0} +
+

+ Generated {genLayoutFiles.length} files: +

+
+ {#each genLayoutFiles as file} +
+ {file.file.split('/').pop()} +
+ {/each} +
+
+ {/if} + + {#if genLayoutStatus === 'error'} +
+

{$genLayoutBatch.error}

+
+ {/if} +
+ + + + +
+
+
+ + + + + + + + Restart XMLEngine App + + + + +
+

+ {#if restartDialogSource === 'push-complete'} + Layout files were pushed successfully. Restart XMLEngine app to apply changes. + {:else} + The app will shut down and relaunch immediately. + {/if} +

+
+ +
+ + +
+
+
+ + + + + + Edit Prices + + + +
+ {#each priceEditData as item, index} +
+
+
+ + {item.type} + + {item.code} + {#if item.isNew} + NO PRICE + {/if} +
+
+
+ {getCurrencySymbol()} + +
+
+ {/each} + + {#if priceEditData.length === 0} +
+ No product codes found +
+ {/if} +
+ +
+ + +
+
+
diff --git a/src/routes/(authed)/sheet/overview/+page.server.ts b/src/routes/(authed)/sheet/overview/+page.server.ts new file mode 100644 index 0000000..baad7b2 --- /dev/null +++ b/src/routes/(authed)/sheet/overview/+page.server.ts @@ -0,0 +1,11 @@ +import { redirect } from '@sveltejs/kit'; +import { referenceFromPage } from '$lib/core/stores/recipeStore'; +import { get } from 'svelte/store'; + +export function load() { + // Set reference so departments page knows to redirect to sheet + referenceFromPage.set('sheet'); + + // Redirect to departments page to select country + throw redirect(302, '/departments'); +} diff --git a/src/routes/(authed)/sheet/overview/+page.svelte b/src/routes/(authed)/sheet/overview/+page.svelte index 7afa9d2..36c27e5 100644 --- a/src/routes/(authed)/sheet/overview/+page.svelte +++ b/src/routes/(authed)/sheet/overview/+page.svelte @@ -1,66 +1,149 @@
-
+
-

Layout overview [ {refDepartment} ]

+

Sheet Overview

- Display menus from the spreadsheet current selected country + Catalogs for {selectedCountry.toUpperCase()}

-
- +
+
- - - - - - + +
+ {#if loading} +
+ +

+ Loading catalogs for {selectedCountry}... +

+
+ {:else if error} +
+

{error}

+ +
+ {:else if catalogs.length === 0} +
+

No catalogs found for {selectedCountry}

+
+ {:else} + + + + Catalog Name + Status + Locked By + Actions + + + + {#each catalogs as catalog} + + {catalog.catalog} + + + {catalog.status.toUpperCase()} + + + + {catalog.locked_by || '-'} + + + + + + {/each} + + + {/if} +
diff --git a/src/routes/(authed)/sheet/overview/[country]/+page.svelte b/src/routes/(authed)/sheet/overview/[country]/+page.svelte new file mode 100644 index 0000000..6da4a55 --- /dev/null +++ b/src/routes/(authed)/sheet/overview/[country]/+page.svelte @@ -0,0 +1,279 @@ + + +
+
+
+
+

+ Sheet Overview [ {selectedCountry.toUpperCase()} ] +

+

+ View available catalogs for {selectedCountry.toUpperCase()} +

+
+ +
+ +
+ {#if enabledCountries.length === 0} +

+ No countries available. Please check your permissions. +

+ {:else} + { + if (v) { + selectedCountry = v; + goto(`/sheet/overview/${v}`); + } + }} + > + + {selectedCountry.toUpperCase()} + + + {#each enabledCountries as country} + + {country.toUpperCase()} + + {/each} + + + {/if} +
+ +
+ {#if loading} +
+ +

+ Loading catalogs for {selectedCountry}... +

+
+ {:else if error} +
+

{error}

+ +
+ {:else if sortedCatalogs.length === 0} +
+

No catalogs found for {selectedCountry}

+
+ {:else} +
+ {#each sortedCatalogs as catalog} + + +
+ + {getCatalogDisplayName(catalog.catalog)} group + + {#if isMainCatalog(catalog.catalog)} + + + Main + + {/if} +
+ +
+ +
+ {#if catalog.status === 'free'} + + + Available + + + + Ready to edit + + {:else} + + + Locked + + + {catalog.locked_by || 'In use'} + + {/if} +
+
+ + + +
+ {/each} +
+ {/if} +
+
+
diff --git a/src/routes/(authed)/tools/adv-upload/+page.svelte b/src/routes/(authed)/tools/adv-upload/+page.svelte new file mode 100644 index 0000000..ec63447 --- /dev/null +++ b/src/routes/(authed)/tools/adv-upload/+page.svelte @@ -0,0 +1,833 @@ + + +
+ +
+
+
+

Adv Upload

+

Upload advertisement videos (.mp4) to the server

+
+ +
+ + {isAdbConnected ? 'Machine connected' : 'Machine offline'} + + + {#if files.length > 0} + + {/if} + + + + + + +
+
+
+ + +
+
+ + + + Upload Settings + + +
+ + + + {COUNTRIES.find((c) => c.value === selectedCountry)?.label || 'Select country'} + + + {#each COUNTRIES as country} + {country.label} + {/each} + + + +
+ +
+
+ + + + + Naming & Size Rules + + +
+
+

taobin_adv_menu_*.mp4

+

Menu banner ad

+ 1080 × 380 +
+
+

taobin_adv_*.mp4

+

Fullscreen / Idle ad

+ 1080 × 608 +
+
+
+
+ + + + + + + + + + {#if pushingToMachine} +
+ 0 ? (pushProgress.current / pushProgress.total) * 100 : 0} + max={100} + class="h-2" + /> +

+ + Sending to machine ({pushProgress.current}/{pushProgress.total}): {pushProgress.name} +

+
+ {/if} + + + {#if uploading} +
+ 0 + ? (uploadProgress.current / uploadProgress.total) * 100 + : 0} + max={100} + class="h-2" + /> +

+ Uploading: {uploadProgress.current} / {uploadProgress.total} +

+
+ {/if} + + + {#if generatingManifest} +

+ + Generating sync_1.file on machine and uploading... +

+ {/if} + + + {#if files.length > 0} + + + + Selected Files ({files.length}) +
+ {#if files.some((f) => f.status === 'success')} + + {files.filter((f) => f.status === 'success').length} uploaded + + {/if} + {#if files.some((f) => f.status === 'error')} + + {files.filter((f) => f.status === 'error').length} invalid/failed + + {/if} +
+
+
+ +
+ {#each files as item (item.id)} +
+ +
+ + + + {#if item.status === 'uploading'} +
+ +
+ {:else if item.status === 'success'} +
+ +
+ {/if} + + + {#if item.status !== 'uploading'} + + {/if} +
+ + +
+

+ {item.file.name} +

+
+ + {item.category === 'menu' ? 'Menu banner' : 'Fullscreen'} + + {#if !item.dimChecked} + Reading… + {:else if item.dimOk} + + {item.width}×{item.height} + + {:else} + + {item.width ?? '?'}×{item.height ?? '?'} + + {/if} + + {(item.file.size / (1024 * 1024)).toFixed(1)} MB + +
+

+ Expected {item.expectedWidth}×{item.expectedHeight} +

+ {#if item.error} +

+ + {item.error} +

+ {/if} +
+
+ {/each} +
+
+
+ {/if} +
+
+
diff --git a/src/routes/(authed)/tools/android-recipe/+page.svelte b/src/routes/(authed)/tools/android-recipe/+page.svelte new file mode 100644 index 0000000..e2f198c --- /dev/null +++ b/src/routes/(authed)/tools/android-recipe/+page.svelte @@ -0,0 +1,42 @@ + + +{#if AndroidRecipeExportView} + +{:else} +
+
+

Android Recipe Export

+

Preparing recipe export viewer.

+
+ +
+ {#if loadError} +

Unable to load viewer

+

{loadError}

+ {:else} +
+ +
+

Processing

+

Loading Android recipe tools...

+
+
+ {/if} +
+
+{/if} diff --git a/src/routes/(authed)/tools/brew/+page.svelte b/src/routes/(authed)/tools/brew/+page.svelte index 1bbba35..eddda99 100644 --- a/src/routes/(authed)/tools/brew/+page.svelte +++ b/src/routes/(authed)/tools/brew/+page.svelte @@ -1,7 +1,8 @@ + +
+
+

Create Menu

+

Create and manage menu drafts for Android

+
+ + +
+ {#if !isAdbConnected} + + {:else if !isRecipeLoaded} + + {#if !isAndroidSocketConnected} + + {/if} + {:else} + + + {/if} + + + {#if isAdbConnected} +
+ {#if countryLoading} + Loading country... + {:else if detectedCountry} + + Country: {detectedCountry} ({detectedCountryCode}) + + {/if} +
+ {/if} +
+ + + {#if stagedMenus.length > 0} +
+
+
+

Draft Menus

+

+ Menus created on web. Save to Android when ready. +

+
+ +
+
+ {#each stagedMenus as menu (menu.productCode)} + {@const saveState = $menuSaveStates.get(menu.productCode)} + {@const isSaving = saveState?.status === 'saving'} + {@const isSaved = saveState?.status === 'saved'} + {@const isError = saveState?.status === 'error'} + {@const isVerifying = pendingSaveVerification.has(menu.productCode)} + {@const isProcessing = isSaving || isVerifying} +
+
+
+
{menu.productCode}
+ {#if isSaving} +
+ + Saving... +
+ {:else if isVerifying} +
+ + Verifying... +
+ {:else if isSaved} +
Saved
+ {:else if isError} +
+ {saveState?.error ?? 'Save failed'} +
+ {/if} +
+
{menu.name || menu.otherName}
+
+ {getActiveRecipeStepCount(menu)} recipe steps - {getActiveToppingSetCount(menu)} + topping groups +
+
+
+ + + + +
+
+ {/each} +
+
+ {:else} +
+ No draft menus yet. Click "Create New Menu" to get started. +
+ {/if} +
+ + + + + + Create New Menu + Select temperature types and category + + +
+ + {#if detectedCountry} +
+
Machine Country
+
{detectedCountry} (prefix: {detectedCountryCode})
+
+ {/if} + + +
+ +
+ {#each ['hot', 'cold', 'blend'] as temp} + + {/each} +
+
+ + +
+ +
+ {#each categoryOptions as cat} + + {/each} +
+
+ + + {#if selectedTempCount > 0 && selectedCategory} +
+ +
+ {#if generatedCodes.hot}
Hot: {generatedCodes.hot}
{/if} + {#if generatedCodes.cold}
Cold: {generatedCodes.cold}
{/if} + {#if generatedCodes.blend}
Blend: {generatedCodes.blend}
{/if} +
+
+ {/if} +
+ + + + + +
+
+ + + + + + {editingDraftProductCode ? 'Edit Draft Menu' : 'Create Menu'} + + {editingDraftProductCode + ? 'Update the draft menu.' + : `Creating ${tempForms.length} menu(s). Menu info is separate for each temperature.`} + + + +
+ + {#if tempForms.length > 1} +
+ {#each tempForms as form, index} + + {/each} +
+ {/if} + + + {#if activeForm} +
+
+

+ Menu Info ({activeForm.temp.charAt(0).toUpperCase() + activeForm.temp.slice(1)}) +

+
+
+ + updateActiveFormField('name', event.currentTarget.value)} + /> +
+
+ + updateActiveFormField('otherName', event.currentTarget.value)} + /> +
+
+
+
+ + + updateActiveFormField('description', event.currentTarget.value)} + /> +
+
+ + + updateActiveFormField('otherDescription', event.currentTarget.value)} + /> +
+
+
+
+ + updateActiveFormField('cashPrice', event.currentTarget.value)} + /> +
+
+ + + updateActiveFormField('nonCashPrice', event.currentTarget.value)} + /> +
+
+ + updateActiveFormField('image', event.currentTarget.value)} + /> +
+
+ +
+ + +
+ + +
+ + +
+
+
+

Materials

+

Recipe steps for {activeForm.temp}

+
+ +
+ + {#if activeMaterials.length === 0} +
+ No active materials found. Load recipe data from Android first. +
+ {:else} +
+ {#each activeForm.recipeSteps as step, index} +
+
+
Step {index + 1}
+ {#if activeForm.recipeSteps.length > 1} + + {/if} +
+
+
+ + +
+
+
+ + + updateRecipeStepNumber(index, 'powderGram', e.currentTarget.value)} + /> +
+
+ + + updateRecipeStepNumber(index, 'powderTime', e.currentTarget.value)} + /> +
+
+ + + updateRecipeStepNumber(index, 'syrupGram', e.currentTarget.value)} + /> +
+
+ + + updateRecipeStepNumber(index, 'syrupTime', e.currentTarget.value)} + /> +
+
+ + + updateRecipeStepNumber(index, 'waterYield', e.currentTarget.value)} + /> +
+
+ + + updateRecipeStepNumber(index, 'waterCold', e.currentTarget.value)} + /> +
+
+ + + updateRecipeStepNumber(index, 'stirTime', e.currentTarget.value)} + /> +
+
+ + + updateRecipeStepNumber(index, 'MixOrder', e.currentTarget.value)} + /> +
+
+ + + updateRecipeStepNumber(index, 'FeedParameter', e.currentTarget.value)} + /> +
+
+ + + updateRecipeStepNumber(index, 'FeedPattern', e.currentTarget.value)} + /> +
+
+
+
+ {/each} +
+ {/if} +
+ + +
+
+
+

Toppings

+

Toppings for {activeForm.temp}

+
+ +
+ + {#if activeToppingGroups.length === 0} +
+ No topping data found. +
+ {:else if activeForm.toppingOptions.length === 0} +
+ No toppings added yet. +
+ {:else} +
+ {#each activeForm.toppingOptions as topping, index} +
+
+
Topping {index + 1}
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ {/each} +
+ {/if} +
+
+ {/if} +
+ + + + + +
+
+ + + + + + Confirm Brew + Review the recipe before brewing + + + {#if pendingBrewMenu} +
+
+
Product Code
+
{pendingBrewMenu.productCode}
+
+ +
+
Name
+
+ {pendingBrewMenu.name || pendingBrewMenu.otherName} +
+
+ + {#if getPendingBrewMaterials().length > 0} +
+
Materials
+
+ {#each getPendingBrewMaterials() as mat} +
+ {mat.name} + + {mat.powderGram}g / {mat.waterYield}ml + +
+ {/each} +
+
+ {/if} + + {#if getPendingBrewToppings().length > 0} +
+
Toppings
+
+ {#each getPendingBrewToppings() as topping} +
+
{topping.group}
+
{topping.topping}
+
+ {/each} +
+
+ {/if} +
+ {/if} + + + + + +
+
diff --git a/src/routes/(authed)/tools/image-upload/+page.svelte b/src/routes/(authed)/tools/image-upload/+page.svelte new file mode 100644 index 0000000..757eb84 --- /dev/null +++ b/src/routes/(authed)/tools/image-upload/+page.svelte @@ -0,0 +1,442 @@ + + +
+ +
+
+
+

Image Upload

+

Upload menu images to the server

+
+ +
+ {#if files.length > 0} + + {/if} + +
+
+
+ + +
+
+ + + + Upload Settings + + +
+
+ + + + {COUNTRIES.find((c) => c.value === selectedCountry)?.label || 'Select country'} + + + {#each COUNTRIES as country} + {country.label} + {/each} + + +
+ +
+ + + + {ALLOWED_FOLDERS.find((f) => f.value === selectedFolder)?.label || 'Select folder'} + + + {#each ALLOWED_FOLDERS as folder} + {folder.label} + {/each} + + +
+
+ +
+ +
+
+
+ + + + + + + + + + {#if uploading} +
+ 0 + ? (uploadProgress.current / uploadProgress.total) * 100 + : 0} + max={100} + class="h-2" + /> +

+ Uploading: {uploadProgress.current} / {uploadProgress.total} +

+
+ {/if} + + + {#if files.length > 0} + + + + Selected Files ({files.length}) +
+ {#if files.some((f) => f.status === 'success')} + + {files.filter((f) => f.status === 'success').length} uploaded + + {/if} + {#if files.some((f) => f.status === 'error')} + + {files.filter((f) => f.status === 'error').length} failed + + {/if} + {#if files.some((f) => f.status === 'pending')} + + {files.filter((f) => f.status === 'pending').length} pending + + {/if} +
+
+
+ +
+ {#each files as item (item.id)} +
+ +
+ {item.file.name} +
+ + + {#if item.status === 'uploading'} +
+ +
+ {:else if item.status === 'success'} +
+ +
+ {:else if item.status === 'error'} +
+ +
+ {/if} + + + {#if item.status !== 'uploading'} + + {/if} + + +
+

+ {item.file.name} +

+

+ {(item.file.size / 1024).toFixed(1)} KB +

+ {#if item.error} +

+ {item.error} +

+ {/if} +
+
+ {/each} +
+
+
+ {/if} +
+
+
diff --git a/src/routes/api/adv-manifest/+server.ts b/src/routes/api/adv-manifest/+server.ts new file mode 100644 index 0000000..c532f1e --- /dev/null +++ b/src/routes/api/adv-manifest/+server.ts @@ -0,0 +1,42 @@ +import { json, error } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { env } from '$env/dynamic/public'; + +// Method 2: forward a machine-generated sync_1.file to the adv FTP server. +const ADV_API_BASE = env.PUBLIC_POST_IMAGE; + +export const POST: RequestHandler = async ({ request }) => { + try { + const formData = await request.formData(); + + const country = formData.get('country') as string; + const uid = formData.get('uid') as string; + const displayName = formData.get('displayName') as string; + const email = formData.get('email') as string; + const file = formData.get('file') as File; + + if (!country || !uid || !displayName || !email || !file) { + throw error(400, 'Missing required fields'); + } + + const endpoint = `${ADV_API_BASE}/adv/manifest/${encodeURIComponent(country)}/${encodeURIComponent(uid)}/${encodeURIComponent(displayName)}/${encodeURIComponent(email)}`; + + const uploadFormData = new FormData(); + uploadFormData.append('file', file); + + const response = await fetch(endpoint, { method: 'POST', body: uploadFormData }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ detail: response.statusText })); + throw error(response.status, errorData.detail || 'Manifest upload failed'); + } + + return json(await response.json()); + } catch (err) { + console.error('[Adv Manifest Proxy] Error:', err); + if (err && typeof err === 'object' && 'status' in err) { + throw err; + } + throw error(500, err instanceof Error ? err.message : 'Internal server error'); + } +}; diff --git a/src/routes/api/adv-upload/+server.ts b/src/routes/api/adv-upload/+server.ts new file mode 100644 index 0000000..877a12d --- /dev/null +++ b/src/routes/api/adv-upload/+server.ts @@ -0,0 +1,55 @@ +import { json, error } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { env } from '$env/dynamic/public'; + +// Adv videos are served by the same taobin_image service as menu images. +const ADV_API_BASE = env.PUBLIC_POST_IMAGE; + +export const POST: RequestHandler = async ({ request }) => { + try { + const formData = await request.formData(); + + const country = formData.get('country') as string; + const uid = formData.get('uid') as string; + const displayName = formData.get('displayName') as string; + const email = formData.get('email') as string; + const file = formData.get('file') as File; + // 'false' when the manifest is generated on a machine (method 2). + const regenerate = (formData.get('regenerate') as string) ?? 'true'; + + if (!country || !uid || !displayName || !email || !file) { + throw error(400, 'Missing required fields'); + } + + const endpoint = + `${ADV_API_BASE}/adv/upload/${encodeURIComponent(country)}/${encodeURIComponent(uid)}/${encodeURIComponent(displayName)}/${encodeURIComponent(email)}` + + `?regenerate=${encodeURIComponent(regenerate)}`; + + console.log('[Adv Upload Proxy] Endpoint:', endpoint, 'file:', file.name); + + // Upstream expects the multipart field name `files`. + const uploadFormData = new FormData(); + uploadFormData.append('files', file); + + const response = await fetch(endpoint, { + method: 'POST', + body: uploadFormData + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ detail: response.statusText })); + throw error(response.status, errorData.detail || 'Upload failed'); + } + + const result = await response.json(); + return json(result); + } catch (err) { + console.error('[Adv Upload Proxy] Error:', err); + + if (err && typeof err === 'object' && 'status' in err) { + throw err; + } + + throw error(500, err instanceof Error ? err.message : 'Internal server error'); + } +}; diff --git a/src/routes/api/image-upload/+server.ts b/src/routes/api/image-upload/+server.ts new file mode 100644 index 0000000..aabd5bb --- /dev/null +++ b/src/routes/api/image-upload/+server.ts @@ -0,0 +1,58 @@ +import { json, error } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { env } from '$env/dynamic/public'; + +const IMAGE_API_BASE = env.PUBLIC_POST_IMAGE; + +export const POST: RequestHandler = async ({ request }) => { + try { + const formData = await request.formData(); + + const country = formData.get('country') as string; + const folder = formData.get('folder') as string; + const uid = formData.get('uid') as string; + const displayName = formData.get('displayName') as string; + const email = formData.get('email') as string; + const file = formData.get('file') as File; + + if (!folder || !uid || !displayName || !email || !file) { + throw error(400, 'Missing required fields'); + } + + + // Build the upload endpoint + let endpoint: string; + if (country) { + endpoint = `${IMAGE_API_BASE}/inter/${encodeURIComponent(country)}/image/${encodeURIComponent(folder)}/upload/${encodeURIComponent(uid)}/${encodeURIComponent(displayName)}/${encodeURIComponent(email)}`; + } else { + endpoint = `${IMAGE_API_BASE}/image/${encodeURIComponent(folder)}/upload/${encodeURIComponent(uid)}/${encodeURIComponent(displayName)}/${encodeURIComponent(email)}`; + } + + console.log('[Image Upload Proxy] Endpoint:', endpoint); + + // Create new FormData for the upstream request + const uploadFormData = new FormData(); + uploadFormData.append('files', file); + + const response = await fetch(endpoint, { + method: 'POST', + body: uploadFormData + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ detail: response.statusText })); + throw error(response.status, errorData.detail || 'Upload failed'); + } + + const result = await response.json(); + return json(result); + } catch (err) { + console.error('[Image Upload Proxy] Error:', err); + + if (err && typeof err === 'object' && 'status' in err) { + throw err; + } + + throw error(500, err instanceof Error ? err.message : 'Internal server error'); + } +}; diff --git a/src/routes/api/sheet/stream/+server.ts b/src/routes/api/sheet/stream/+server.ts new file mode 100644 index 0000000..5ecd79c --- /dev/null +++ b/src/routes/api/sheet/stream/+server.ts @@ -0,0 +1,91 @@ +import { json } from '@sveltejs/kit'; + +// In-memory store for streamed catalog data +// Format: { batchId: { chunks: [...], status: 'collecting'|'complete'|'error' } } +const streamCache = new Map(); + +export async function POST({ request }) { + try { + const data = await request.json(); + const { batch_id, msg, content, current_chunk, total_chunks } = data.payload; + + // Initialize or update batch + if (!streamCache.has(batch_id)) { + streamCache.set(batch_id, { + chunks: [], + status: 'collecting', + total_chunks, + createdAt: Date.now() + }); + } + + const batch = streamCache.get(batch_id); + + // Handle different message types + if (msg === 'start') { + console.log(`[API] Stream started for batch ${batch_id}`); + } else if (msg === 'chunk') { + batch.chunks.push(content); + console.log(`[API] Received chunk ${current_chunk}/${total_chunks} for batch ${batch_id}`); + } else if (msg === 'end') { + batch.status = 'complete'; + console.log(`[API] Stream complete for batch ${batch_id}, total chunks: ${batch.chunks.length}`); + } else if (msg === 'error') { + batch.status = 'error'; + batch.error = content; + console.log(`[API] Stream error for batch ${batch_id}:`, content); + } + + return json({ status: 'received', batch_id }); + } catch (error) { + console.error('[API] Error processing stream:', error); + return json({ status: 'error', message: String(error) }, { status: 500 }); + } +} + +export function GET({ url }) { + const batchId = url.searchParams.get('batch_id'); + + // Clean up old cache entries (older than 5 minutes) + const now = Date.now(); + for (const [key, value] of streamCache.entries()) { + if (now - value.createdAt > 5 * 60 * 1000) { + streamCache.delete(key); + } + } + + // If batch_id specified, return that specific batch + if (batchId) { + const batch = streamCache.get(batchId); + + if (!batch) { + return json({ status: 'not_found' }, { status: 404 }); + } + + return json({ + batch_id: batchId, + status: batch.status, + chunks: batch.chunks, + total_chunks: batch.total_chunks, + error: batch.error || null, + createdAt: new Date(batch.createdAt).toISOString() + }); + } + + // Otherwise return list of all recent batches + const batches = Array.from(streamCache.entries()).map(([batchId, batch]) => ({ + batch_id: batchId, + status: batch.status, + chunks_count: batch.chunks.length, + total_chunks: batch.total_chunks, + error: batch.error || null, + createdAt: new Date(batch.createdAt).toISOString() + })); + + return json({ + status: 'success', + batches: batches.sort( + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ) + }); +} diff --git a/vite.config.ts b/vite.config.ts index aa4d2d9..0b49238 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -21,7 +21,7 @@ export default defineConfig({ environment: 'browser', browser: { enabled: true, - provider: 'playwright', + //provider: 'playwright', instances: [{ browser: 'chromium' }] }, include: ['src/**/*.svelte.{test,spec}.{js,ts}'],