diff --git a/index.html b/index.html index e8ae001..bb45323 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,12 @@ - + - + - Vite + React + TS + PhiFans Editor - +
diff --git a/package.json b/package.json index f2e1598..67dc411 100644 --- a/package.json +++ b/package.json @@ -8,44 +8,59 @@ "url": "https://github.com/PhiFans/editor.git" }, "scripts": { - "dev": "vite --config vite.config.web.ts", + "dev": "vite --config vite/config.web.ts", "dev:tauri": "tauri dev", - "build": "tsc -b && vite build --config vite.config.web.ts", + "build": "tsc -b && vite build --config vite/config.web.ts", "build:tauri": "tauri build", "lint": "eslint .", - "preview": "vite preview --config vite.config.web.ts" + "preview": "vite preview --config vite/config.web.ts" }, "dependencies": { - "@blueprintjs/core": "^5.16.4", + "@chakra-ui/react": "^3.26.0", + "@emotion/react": "^11.14.0", "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@phifans/audio": "^0.0.7", "@pixi/react": "8.0.0", + "@radix-ui/react-menubar": "^1.1.16", + "@tauri-apps/api": "~2", + "@tauri-apps/plugin-fs": "~2", + "@tauri-apps/plugin-sql": "~2.3.0", "@types/node": "^22.10.5", - "audio-decode": "^2.2.2", - "eventemitter3": "^5.0.1", - "hotkeys-js": "^3.13.9", + "@zenfs/core": "^2.4.0", + "@zenfs/dom": "^1.2.4", + "dockview": "^4.7.1", "i18next": "^24.2.3", "i18next-browser-languagedetector": "^8.0.4", "i18next-chained-backend": "^4.6.2", "i18next-http-backend": "^3.0.2", "i18next-localstorage-backend": "^4.2.0", + "idb-keyval": "^6.2.2", + "jszip": "^3.10.1", + "mobx-react-lite": "^4.1.0", + "mobx-state-tree": "^7.0.2", + "nanoid": "^5.1.5", + "next-themes": "^0.4.6", "normalize.css": "^8.0.1", "pixi.js": "^8.6.6", - "rc-dock": "^3.3.0", + "quick-lru": "^7.1.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-i18next": "^15.4.1", + "react-icons": "^5.5.0", "react-split-pane": "^0.1.92", - "spark-md5": "^3.0.2", - "uuid": "^11.0.4" + "setimmediate": "^1.0.5", + "styled-components": "^6.1.19", + "unstorage": "^1.17.1", + "zustand": "^5.0.8" }, "devDependencies": { + "@chakra-ui/cli": "^3.26.0", "@eslint/js": "^9.17.0", "@tauri-apps/cli": "^2.2.7", "@types/react": "^19.0.7", "@types/react-dom": "^19.0.3", - "@types/spark-md5": "^3.0.5", "@vitejs/plugin-react-swc": "^3.5.0", "eslint": "^9.17.0", "eslint-plugin-react": "^7.37.3", @@ -60,5 +75,6 @@ "patchedDependencies": { "react-split-pane": "patches/react-split-pane.patch" } - } + }, + "packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a83a45..1c6b450 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,9 +13,12 @@ importers: .: dependencies: - '@blueprintjs/core': - specifier: ^5.16.4 - version: 5.16.4(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@chakra-ui/react': + specifier: ^3.26.0 + version: 3.26.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@emotion/react': + specifier: ^11.14.0 + version: 11.14.0(@types/react@19.0.7)(react@19.0.0) '@fortawesome/fontawesome-svg-core': specifier: ^6.7.2 version: 6.7.2 @@ -25,21 +28,36 @@ importers: '@fortawesome/react-fontawesome': specifier: ^0.2.2 version: 0.2.2(@fortawesome/fontawesome-svg-core@6.7.2)(react@19.0.0) + '@phifans/audio': + specifier: ^0.0.7 + version: 0.0.7 '@pixi/react': specifier: 8.0.0 version: 8.0.0(@types/react@19.0.7)(pixi.js@8.6.6)(react@19.0.0) + '@radix-ui/react-menubar': + specifier: ^1.1.16 + version: 1.1.16(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tauri-apps/api': + specifier: ~2 + version: 2.8.0 + '@tauri-apps/plugin-fs': + specifier: ~2 + version: 2.4.2 + '@tauri-apps/plugin-sql': + specifier: ~2.3.0 + version: 2.3.0 '@types/node': specifier: ^22.10.5 version: 22.10.5 - audio-decode: - specifier: ^2.2.2 - version: 2.2.2 - eventemitter3: - specifier: ^5.0.1 - version: 5.0.1 - hotkeys-js: - specifier: ^3.13.9 - version: 3.13.9 + '@zenfs/core': + specifier: ^2.4.0 + version: 2.4.0 + '@zenfs/dom': + specifier: ^1.2.4 + version: 1.2.4(@zenfs/core@2.4.0)(kerium@1.3.6)(utilium@2.5.4) + dockview: + specifier: ^4.7.1 + version: 4.7.1(react@19.0.0) i18next: specifier: ^24.2.3 version: 24.2.3(typescript@5.6.3) @@ -55,15 +73,33 @@ importers: i18next-localstorage-backend: specifier: ^4.2.0 version: 4.2.0 + idb-keyval: + specifier: ^6.2.2 + version: 6.2.2 + jszip: + specifier: ^3.10.1 + version: 3.10.1 + mobx-react-lite: + specifier: ^4.1.0 + version: 4.1.0(mobx@6.13.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + mobx-state-tree: + specifier: ^7.0.2 + version: 7.0.2(mobx@6.13.7)(typescript@5.6.3) + nanoid: + specifier: ^5.1.5 + version: 5.1.5 + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) normalize.css: specifier: ^8.0.1 version: 8.0.1 pixi.js: specifier: ^8.6.6 version: 8.6.6 - rc-dock: - specifier: ^3.3.0 - version: 3.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + quick-lru: + specifier: ^7.1.0 + version: 7.1.0 react: specifier: ^19.0.0 version: 19.0.0 @@ -73,16 +109,28 @@ importers: react-i18next: specifier: ^15.4.1 version: 15.4.1(i18next@24.2.3(typescript@5.6.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react-icons: + specifier: ^5.5.0 + version: 5.5.0(react@19.0.0) react-split-pane: specifier: ^0.1.92 version: 0.1.92(patch_hash=15b3bc41b3c9ea6e7a5c245069978ef071c4655082c4e9b0e14eb5d4eb57da5c)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - spark-md5: - specifier: ^3.0.2 - version: 3.0.2 - uuid: - specifier: ^11.0.4 - version: 11.0.4 + setimmediate: + specifier: ^1.0.5 + version: 1.0.5 + styled-components: + specifier: ^6.1.19 + version: 6.1.19(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + unstorage: + specifier: ^1.17.1 + version: 1.17.1(idb-keyval@6.2.2) + zustand: + specifier: ^5.0.8 + version: 5.0.8(@types/react@19.0.7)(react@19.0.0)(use-sync-external-store@1.4.0(react@19.0.0)) devDependencies: + '@chakra-ui/cli': + specifier: ^3.26.0 + version: 3.26.0(@chakra-ui/react@3.26.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) '@eslint/js': specifier: ^9.17.0 version: 9.17.0 @@ -95,12 +143,9 @@ importers: '@types/react-dom': specifier: ^19.0.3 version: 19.0.3(@types/react@19.0.7) - '@types/spark-md5': - specifier: ^3.0.5 - version: 3.0.5 '@vitejs/plugin-react-swc': specifier: ^3.5.0 - version: 3.7.2(vite@6.0.9(@types/node@22.10.5)) + version: 3.7.2(@swc/helpers@0.5.17)(vite@6.0.9(@types/node@22.10.5)) eslint: specifier: ^9.17.0 version: 9.17.0 @@ -128,6 +173,41 @@ importers: packages: + '@ark-ui/react@5.22.0': + resolution: {integrity: sha512-cH3xVhKRn0ZsP2Jg2RZAziI38obIfTMC3Q6ZWtWeYL5k9fq6K8sa1XjdJclBRSD0vYYvR1ynHG9ThicWKKANtQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.26.0': resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} @@ -136,180 +216,396 @@ packages: resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==} engines: {node: '>=6.9.0'} - '@blueprintjs/colors@5.1.5': - resolution: {integrity: sha512-IUZvQLsQmfqumTTzeb5oQLnvskJcQYKcitzhDm5+iTWl42Jn5oyUqPDW96ubH/pfWoQGHzBp91ujq6KREl4GHQ==} + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} - '@blueprintjs/core@5.16.4': - resolution: {integrity: sha512-jgy3UJNDWPBdNn8nf2pDgzSKDJABVG4i1OPrcFJlOQl117UWMJD28/hAtyBQG1e+/6AE0a678Ko1aBmK4aWp6Q==} + '@chakra-ui/cli@3.26.0': + resolution: {integrity: sha512-BAnuzUCael9vjvq6bvNpX/UWJFu2I+pIfPqKT3I7ejo/c5JM6IDDqZQp7cs+10e0S4MXH743R/TfnVU96OOPBw==} hasBin: true peerDependencies: - '@types/react': ^16.14.41 || 17 || 18 - react: ^16.8 || 17 || 18 - react-dom: ^16.8 || 17 || 18 - peerDependenciesMeta: - '@types/react': - optional: true + '@chakra-ui/react': '>=3.0.0-next.0' + + '@chakra-ui/react@3.26.0': + resolution: {integrity: sha512-VuhFMLklzrjTWIst1B+uQggxOn9+GxVd+0LHLtsQKA+JtKUDqNfKymeWlb1/pKrmqH184+gwZJRjTtr6/+0cIQ==} + peerDependencies: + '@emotion/react': '>=11' + react: '>=18' + react-dom: '>=18' + + '@clack/core@0.5.0': + resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} + + '@clack/prompts@0.11.0': + resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} + + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} - '@blueprintjs/icons@5.17.1': - resolution: {integrity: sha512-jdwrfDb5N0KCKGFFaJn5KrQj0ZX1hqEXHor9ewsdrSgVGOzVCg9OJ/GYVjy85McHNhN9bYyYaH+8dxablEoYRA==} + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/is-prop-valid@1.2.2': + resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} + + '@emotion/is-prop-valid@1.4.0': + resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} + + '@emotion/memoize@0.8.1': + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} peerDependencies: - '@types/react': ^16.14.41 || 17 || 18 - react: ^16.8 || 17 || 18 - react-dom: ^16.8 || 17 || 18 + '@types/react': '*' + react: '>=16.8.0' peerDependenciesMeta: '@types/react': optional: true + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + '@esbuild/aix-ppc64@0.24.2': resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.24.2': resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.24.2': resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.24.2': resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.24.2': resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.24.2': resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.24.2': resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.24.2': resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.24.2': resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.24.2': resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.24.2': resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.24.2': resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.24.2': resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.24.2': resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.24.2': resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.24.2': resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.24.2': resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.24.2': resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.24.2': resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.24.2': resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.24.2': resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.24.2': resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eshaz/web-worker@1.2.2': resolution: {integrity: sha512-WxXiHFmD9u/owrzempiDlBB1ZYqiLnm9s6aPc8AlFQalq2tKmqdmMr9GXOupDgzXtqnBipj8Un0gkIm7Sjf8mw==} @@ -347,6 +643,21 @@ packages: resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/react-dom@2.1.6': + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@fortawesome/fontawesome-common-types@6.7.2': resolution: {integrity: sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==} engines: {node: '>=6'} @@ -385,6 +696,29 @@ packages: resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} engines: {node: '>=18.18'} + '@internationalized/date@3.8.2': + resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==} + + '@internationalized/number@3.6.4': + resolution: {integrity: sha512-P+/h+RDaiX8EGt3shB9AYM1+QgkvHmJ5rKi4/59k4sg9g58k9rqsRW0WxRO7jCoHyvVbFRRFKmVTdFYdehrxHg==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -397,6 +731,12 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@pandacss/is-valid-prop@0.54.0': + resolution: {integrity: sha512-UhRgg1k9VKRCBAHl+XUK3lvN0k9bYifzYGZOqajDid4L1DyU813A1L0ZwN4iV9WX5TX3PfUugqtgG9LnIeFGBQ==} + + '@phifans/audio@0.0.7': + resolution: {integrity: sha512-w/OfBMWjQ0H7MPrnQxG/grS3JUE1nCiiut9QBtf2hNO33J5I1aLsb9MY+NQptfst54A7KpMiE1wcE1Nb3Czf2A==} + '@pixi/colord@2.9.6': resolution: {integrity: sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==} @@ -406,62 +746,329 @@ packages: pixi.js: ^8.2.6 react: '>=19.0.0' - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - - '@rollup/rollup-android-arm-eabi@4.34.7': - resolution: {integrity: sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==} - cpu: [arm] - os: [android] + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} - '@rollup/rollup-android-arm64@4.34.7': - resolution: {integrity: sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==} - cpu: [arm64] - os: [android] + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - '@rollup/rollup-darwin-arm64@4.34.7': - resolution: {integrity: sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==} - cpu: [arm64] - os: [darwin] + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-darwin-x64@4.34.7': - resolution: {integrity: sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==} - cpu: [x64] - os: [darwin] + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-freebsd-arm64@4.34.7': - resolution: {integrity: sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==} - cpu: [arm64] - os: [freebsd] + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@rollup/rollup-freebsd-x64@4.34.7': - resolution: {integrity: sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==} - cpu: [x64] - os: [freebsd] + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.34.7': - resolution: {integrity: sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==} - cpu: [arm] - os: [linux] + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@rollup/rollup-linux-arm-musleabihf@4.34.7': - resolution: {integrity: sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==} - cpu: [arm] - os: [linux] + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-arm64-gnu@4.34.7': - resolution: {integrity: sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==} - cpu: [arm64] - os: [linux] + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@rollup/rollup-linux-arm64-musl@4.34.7': - resolution: {integrity: sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==} - cpu: [arm64] - os: [linux] + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.34.7': - resolution: {integrity: sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==} - cpu: [loong64] + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menubar@1.1.16': + resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@rollup/rollup-android-arm-eabi@4.34.7': + resolution: {integrity: sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.34.7': + resolution: {integrity: sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.34.7': + resolution: {integrity: sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.34.7': + resolution: {integrity: sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.34.7': + resolution: {integrity: sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.7': + resolution: {integrity: sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.34.7': + resolution: {integrity: sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.34.7': + resolution: {integrity: sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.34.7': + resolution: {integrity: sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.34.7': + resolution: {integrity: sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.34.7': + resolution: {integrity: sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==} + cpu: [loong64] os: [linux] '@rollup/rollup-linux-powerpc64le-gnu@4.34.7': @@ -504,6 +1111,10 @@ packages: cpu: [x64] os: [win32] + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + '@swc/core-darwin-arm64@1.10.3': resolution: {integrity: sha512-LFFCxAUKBy69AUE+01rgazQcafIXrYs6tBa9SyKPR51ft6Tp66dAVrWg9MTykaWskuXEe80LPUvUw1ga3bOH3A==} engines: {node: '>=10'} @@ -576,9 +1187,15 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@swc/types@0.1.17': resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} + '@tauri-apps/api@2.8.0': + resolution: {integrity: sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw==} + '@tauri-apps/cli-darwin-arm64@2.2.7': resolution: {integrity: sha512-54kcpxZ3X1Rq+pPTzk3iIcjEVY4yv493uRx/80rLoAA95vAC0c//31Whz75UVddDjJfZvXlXZ3uSZ+bnCOnt0A==} engines: {node: '>= 10'} @@ -644,6 +1261,12 @@ packages: engines: {node: '>= 10'} hasBin: true + '@tauri-apps/plugin-fs@2.4.2': + resolution: {integrity: sha512-YGhmYuTgXGsi6AjoV+5mh2NvicgWBfVJHHheuck6oHD+HC9bVWPaHvCP0/Aw4pHDejwrvT8hE3+zZAaWf+hrig==} + + '@tauri-apps/plugin-sql@2.3.0': + resolution: {integrity: sha512-JYwIocfsLaDWa41LMiZWuzts7yCJR+EpZPRmgpO7Gd7XiAS9S67dKz306j/k/d9XntB0YopMRBol2OIWMschuA==} + '@thi.ng/bitstream@2.4.8': resolution: {integrity: sha512-952h5EgVHDfQJDGydNCKCLQvtwEHbK4grl3I9N/1yWEDS05EGvwYwSaJxFBKM5kNGrE5Uxf6i4pUl0KHvsIKIg==} engines: {node: '>=18'} @@ -652,9 +1275,15 @@ packages: resolution: {integrity: sha512-O7Vrdn6mlzfm9iKhGiVs1TibrNXhNjkwguTpFkkU2awZG/5ssOYidXU6RVZMqOFOvW6IQfN0nDbhvtqoNYse/w==} engines: {node: '>=18'} + '@types/cli-table@0.3.4': + resolution: {integrity: sha512-GsALrTL69mlwbAw/MHF1IPTadSLZQnsxe7a80G8l4inN/iEXCOcVeT/S7aRc6hbhqzL9qZ314kHPDQnQ3ev+HA==} + '@types/css-font-loading-module@0.0.12': resolution: {integrity: sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==} + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/earcut@2.1.4': resolution: {integrity: sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==} @@ -664,9 +1293,18 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@22.10.5': resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} + '@types/node@24.3.1': + resolution: {integrity: sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/react-dom@19.0.3': resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==} peerDependencies: @@ -680,8 +1318,11 @@ packages: '@types/react@19.0.7': resolution: {integrity: sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==} - '@types/spark-md5@3.0.5': - resolution: {integrity: sha512-lWf05dnD42DLVKQJZrDHtWFidcLrHuip01CtnC2/S6AMhX4t9ZlEUj4iuRlAnts0PQk7KESOqKxeGE/b6sIPGg==} + '@types/setimmediate@1.0.4': + resolution: {integrity: sha512-rWPw1drMVf5zInxNpgH3nn/h6KkWqwgLT2y/ciAYQ16RAsbXOXe0AmtZ/HyzwPNw+r4GMJuI7IV7YNKO7Fs/xA==} + + '@types/stylis@4.2.5': + resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} '@typescript-eslint/eslint-plugin@8.18.2': resolution: {integrity: sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==} @@ -730,6 +1371,11 @@ packages: resolution: {integrity: sha512-zORcwn4C3trOWiCqFQP1x6G3xTRyZ1LYydnj51cRnJ6hxBlr/cKPckk+PKPUw/fXmvfKTcw7bwY3w9izgx5jZw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@visulima/boxen@2.0.2': + resolution: {integrity: sha512-eqm/OzwETl1Zd5ehW5CUXhYf8tqb+seBCkHBKXh1rEMS94n+OhyCY0KAlZv/17qPoN73WT2nGDN9SdYlvoWbTQ==} + engines: {node: '>=20.18 <=24.x'} + os: [darwin, linux, win32] + '@vitejs/plugin-react-swc@3.7.2': resolution: {integrity: sha512-y0byko2b2tSVVf5Gpng1eEhX1OvPC7x8yns1Fx8jDzlJp4LS6CMkCPfLw47cjyoMrshQDoQw4qcgjsU9VvlCew==} peerDependencies: @@ -751,131 +1397,436 @@ packages: resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} engines: {node: '>=10.0.0'} - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@xterm/xterm@5.5.0': + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true + '@zag-js/accordion@1.22.1': + resolution: {integrity: sha512-P3jsauxnAGKBhuqs9gdivjEiSu7N7KnKRlgWlIpyti35askz8swHsqxsfkc2ASs9tcPKnPvuZDHIxXmJmZSLuQ==} - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + '@zag-js/anatomy@1.22.1': + resolution: {integrity: sha512-I5OvOuJBt6hEqbpqVkWCOEoDfGMnKuLx+S0h7Un5SyAwnif3F1dSqDYujU28bCy8FtKs36vsq/izxufXyiXSEg==} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + '@zag-js/angle-slider@1.22.1': + resolution: {integrity: sha512-Nitjwwo2NVUEK+PabDnOfqizErnFIZZKThtcpQikAhE1J4MX3H128MANu1hJXNkvVYXyZmhTvzjt6XZc2j7YyQ==} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + '@zag-js/aria-hidden@1.22.1': + resolution: {integrity: sha512-vPfAE35BfYPS1UbYRcNw8/kMl7uayE7LyRncK/gPMnoQMjmEKW0nXmD5WlCHFLdGX9WFGYTIde8k4U8ay+oqcg==} - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} + '@zag-js/async-list@1.22.1': + resolution: {integrity: sha512-/evBfhDW3Rj3An5fHW8SYINM/pkxeOe/Uk7rRlBreHVn2PdAay4sj1gax4hlUUFEbqyvBgbHpR/atwfdxSuWYQ==} - array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} - engines: {node: '>= 0.4'} + '@zag-js/auto-resize@1.22.1': + resolution: {integrity: sha512-O+tKmqwLko74DCmwdouxBZqEtIQB6Rt2pyXdlyBXLB7UnYXEIvEUzf8XK39I5AHXp6NlLqx77GtLn1qiBtKrkQ==} - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} + '@zag-js/avatar@1.22.1': + resolution: {integrity: sha512-SAz9XaFD8jg4LODkS51s6KrNcYF/PvAcRkCE9TDiuiCeFdgB6+JFKBNk0iM9og8Tk4Doe/3qIA/I12qKNW9pAw==} - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} + '@zag-js/carousel@1.22.1': + resolution: {integrity: sha512-bFbCRe5xarBtD3NnozHmCmrGJ+nLRhqLQFq+RG13fl1hlhUJaJ5AsS7e8L1r2ZLdbVVrsB0lUuW/ocfJ/G4MSw==} - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} + '@zag-js/checkbox@1.22.1': + resolution: {integrity: sha512-A/cZb89Aeb2k/KGl3ITS2fuLBXwq6Rnq9aFirfKs/UHrY16fopRbRjfqOxF6wm8lWoFk3gqmRGgybo8qsIfxog==} - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} + '@zag-js/clipboard@1.22.1': + resolution: {integrity: sha512-rKTPRKvLtcJ1c/CDvnWDRpqAteFS20UQe+mQpO83ACMCRZAfkXP3UOzBL53mh59+LIVlDxgZbMlwRiNiqqKhmA==} - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} + '@zag-js/collapsible@1.22.1': + resolution: {integrity: sha512-vKfDe/fzm3ndDfaueqW/XgGaWCHVD8MuLFtRRyv3jX3ubdNYn5R/j7ftQURdYyqRlPI3Si50FWSAtOqtvs4y9Q==} - audio-buffer@5.0.0: - resolution: {integrity: sha512-gsDyj1wwUp8u7NBB+eW6yhLb9ICf+0eBmDX8NGaAS00w8/fLqFdxUlL5Ge/U8kB64DlQhdonxYC59dXy1J7H/w==} + '@zag-js/collection@1.22.1': + resolution: {integrity: sha512-jjeSKALTH3iK2vTI6uAh2NCtS9n+e2r1cGERKCfNkbt86U6VSp9xiXqalUsEI4ovNIPcgg0+/nzixoVwFO1Vgg==} - audio-decode@2.2.2: - resolution: {integrity: sha512-xyh7z6dpRT+5Ez4ggV2cEkSShkDvvIBBmVPR3kYY7uIBqRO1BGNjofip6JnjBnvezhrU3ypBGZjepyKFDZWnDw==} + '@zag-js/color-picker@1.22.1': + resolution: {integrity: sha512-vUx8Ef0CZ/VPARIPh2ur76HH1AL3FVObNgtX64kPNUDUI+Z/L/q6CBfIeGcElVQ/Y6QowrqAXjVyPGArmmohmw==} - audio-type@2.2.1: - resolution: {integrity: sha512-En9AY6EG1qYqEy5L/quryzbA4akBpJrnBZNxeKTqGHC2xT9Qc4aZ8b7CcbOMFTTc/MGdoNyp+SN4zInZNKxMYA==} - engines: {node: '>=14'} + '@zag-js/color-utils@1.22.1': + resolution: {integrity: sha512-Bee1KvYOV0yWQbODN+O2zPmdUaH+rymEmIHLfKNipPo5GVmxWqAe8oTQDyquzsUtoPE5MFgW5avg8tgSlCFcBA==} - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} + '@zag-js/combobox@1.22.1': + resolution: {integrity: sha512-N4tGTmezfHGaKB0+aDB5yMuVzBv2ShgsAx1uizom6ElcvlYD2rsQTr3xLc4wyOR7fx0z6fFDo1+63/Dt3y0t4A==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + '@zag-js/core@1.22.1': + resolution: {integrity: sha512-4BNrwO9Tadq2Z0d2xSSQs4O/o3OarEHzXM2FQqx46vrwSE57qUghnZex429ZQ51fuk8AL5Lowt26a9JxE9sVPg==} - bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + '@zag-js/date-picker@1.22.1': + resolution: {integrity: sha512-ja482LloO7AGfFYXTfGV+qV484QWUM1cnF3hWtROd4Vdx/NONwn0w7TEJH+XbO3HaoUC5XpeacWLFQugGCsRjg==} + peerDependencies: + '@internationalized/date': '>=3.0.0' - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + '@zag-js/date-utils@1.22.1': + resolution: {integrity: sha512-OWIWxihfFFyQDEaA35a/Fdfp3+GyGUgTUbutMD3BrbnPjKNLm0RyvAgZiq0zPTY7CzpYRbZ2J98GDU+CTERCjA==} + peerDependencies: + '@internationalized/date': '>=3.0.0' - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + '@zag-js/dialog@1.22.1': + resolution: {integrity: sha512-b5KwMPYKc9RenZwxrAAHu6aHPz7tqPy4Mxa/YR5zo1pXBV4amA7u2xnqyncRaK65Z7y5QKmpmDuBp+0PnXxNIA==} - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + '@zag-js/dismissable@1.22.1': + resolution: {integrity: sha512-0DzbykJu9QoXYw4Zcjte69Mtk6ThNRCXWxxCKBf930V8Bw3Ha7vfY5bgdb4RFT5K+BQP3E8vLT+PzIaDINn2Xw==} - call-bind-apply-helpers@1.0.1: - resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} - engines: {node: '>= 0.4'} + '@zag-js/dom-query@1.22.1': + resolution: {integrity: sha512-mtvGj2z3rkl40mkjd+QwoOHvxqpiOkY4mtVjzNzgzcbVtUN63Mz7giW8OZB+KLy37hwFX0B8JfiQncU8IOHNpw==} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} + '@zag-js/editable@1.22.1': + resolution: {integrity: sha512-NY7VeKYuNLQzi+yZYmWliif0Qd/2PTKtDeqtnVypv8XSHqTbVeS2N9dqTru1g4RP+eGQWx0za12hjmCVU4DuMQ==} - call-bound@1.0.3: - resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} - engines: {node: '>= 0.4'} + '@zag-js/file-upload@1.22.1': + resolution: {integrity: sha512-4iKpqxVLafLbQejcPoZcygtNURsezIlWRigHvVPd2pLsXPa8erbdcEZ8X4QvGp77xcW2QTkuSxB+BSCrEEAotA==} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + '@zag-js/file-utils@1.22.1': + resolution: {integrity: sha512-cZAJ5MAZCe7IfHfN+3xSNb9e6mA812U8BPJr/jNPN+qLQh/PkQDwKaGM33o2Me50r18iGTAswEkETnaFZt3wkw==} - camel-case@4.1.2: - resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + '@zag-js/floating-panel@1.22.1': + resolution: {integrity: sha512-YGjLoYt2xSk4pkTgsR0z/7U7V5OdaicSOZa0HDtskH4MkKPxQxrgf2G4e8dNsw8hnQwfVuoc0RGPGW0BArVr6A==} - capital-case@1.0.4: - resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} + '@zag-js/focus-trap@1.22.1': + resolution: {integrity: sha512-6W9cG0LEVICt0srVfWSpamKzsnRxXMdl3gV+GQ5HvkCCk1Sw6Io4tc3QvSSvaWcfyhM07feerOsa2ah7qiT/ig==} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + '@zag-js/focus-visible@1.22.1': + resolution: {integrity: sha512-TuBEux3UTivo9VXPPe79q9JfTwaP/uIshL1KPifg51ofGYesWjMGeE5S5MAuaSzUmH9+3CpnwP7h7f65s3D0kw==} - change-case@4.1.2: - resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} + '@zag-js/highlight-word@1.22.1': + resolution: {integrity: sha512-mcPg4/ED3MNDzj5b3t4EEIKkvdyvVUJ9pqbyRUoj76KI+ZWXXJIw5PNAkG5vUVVUXKKjfzPVninIqWv1Bh9Bvg==} - classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + '@zag-js/hover-card@1.22.1': + resolution: {integrity: sha512-sGcWASPrt0f8oOpBdyDyka0Mkya4TdlBEOvB9qOvnkcIX2bc6YFUtWQN1L1M/K6nv8D0wSZK0p18JBaqGlHmBQ==} - codec-parser@2.5.0: - resolution: {integrity: sha512-Ru9t80fV8B0ZiixQl8xhMTLru+dzuis/KQld32/x5T/+3LwZb0/YvQdSKytX9JqCnRdiupvAvyYJINKrXieziQ==} + '@zag-js/i18n-utils@1.22.1': + resolution: {integrity: sha512-45KUYB9tu1br6NmgtaNW9NviozYCYUxJ8aZTI/Y6vKotXK/Pn3bIlaiOaq4Zel7TalGYT8gVnwgPe2E6H5sqTg==} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + '@zag-js/interact-outside@1.22.1': + resolution: {integrity: sha512-+iZ3xHC9+jVo2FCC4B9c9ntcXv19shVOqQGDr2cD30Hwmwtm9kCOdVydMqv3Lp3UhR8a105MXEVUAKg53WbCoA==} - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + '@zag-js/json-tree-utils@1.22.1': + resolution: {integrity: sha512-z/15CTtXJHGUvecAAlPnUAaAK83Wxh5WlW9qEpgXlXdB5k7gnWVzH4qN9vDwlSShyZgqaFVqn+muxqaCTYv8Zg==} + + '@zag-js/listbox@1.22.1': + resolution: {integrity: sha512-M017Oq0s9PRR5ZwlJkmLhQHucEta/DZ5eHl/t+9yQqHnYRwWKo2ZXLyXquC1wihbHk81E0a1veDw8vBYpfRovA==} + + '@zag-js/live-region@1.22.1': + resolution: {integrity: sha512-xjrlCbcgIw+iXxSXnjXAv+WX9r/bMwp4HOIxWOD99360XvatQ2ZGhLH9lfixiXeHLvm6hjWsP92MjYefSLDFSA==} + + '@zag-js/menu@1.22.1': + resolution: {integrity: sha512-a5pgQgcpVTVyY6JM8k1WGqelHVKSPwV2CwOv2oGjHWXIr2fpRCAKqZRtytE5PvUP/CZArk8bCjatmgOWe1RdPQ==} + + '@zag-js/number-input@1.22.1': + resolution: {integrity: sha512-E4DROYvSo5TFJMkSmnq+f75wSTL/N7SK6MR8ssNlA2oQp69iVWXhIlFLe4knekX02QJzK1MF97aVU332kAYTeQ==} + + '@zag-js/pagination@1.22.1': + resolution: {integrity: sha512-Jeix+sXcfMPm5jer2W4PHSUCgu9a11aC/AOBk6dkxbX8XL23fYXJu5YyOVVq0iQIDWzX4Uij1N/vBha64ARmcA==} + + '@zag-js/password-input@1.22.1': + resolution: {integrity: sha512-EcCH0V2tbJbexy62nVDUXCMg/XVEcd0PGcBgUfziyaLlDnJz2HWkfe0MzpEiidJwfJfhvvf2DapX9mAyqzZhhw==} + + '@zag-js/pin-input@1.22.1': + resolution: {integrity: sha512-tyI5mVi+zmsDEVuZZTOA7fVyxxGwmD8A2snF6nRkFK11o5xnnZaXt44Z7XrPeljTMSLKt+rdF0y/9Q05Auc4tg==} + + '@zag-js/popover@1.22.1': + resolution: {integrity: sha512-27VVkhaEOtiHJYj2j++AzYlAzpMcW0ED05TV9wIT1q0EYzASWxweSBajbnCiQf9TIYzCImDiNVDaCMl5D+TamQ==} + + '@zag-js/popper@1.22.1': + resolution: {integrity: sha512-vBI5WpvE/3ugsimjZaNisOwcECiYfzc+3LIJwaU8od62kInZ1XF6m096BvV7JGwP0FjkMPJrgjcv7weDtY2iDQ==} + + '@zag-js/presence@1.22.1': + resolution: {integrity: sha512-9+pkKnjcHbNxk/80HzLdDjpiKGV/I208wAe0Njmej6q6Z79ED6cb7tXiOgAS7w/ZLWxwQW7B9oMJ3guVflBHwQ==} + + '@zag-js/progress@1.22.1': + resolution: {integrity: sha512-2U1IJLb1mhBLEgac8x8qaEv3qgr+pHdw6pn9mCCJVBcyFaSqliWps6X+vi+qKokFLrpjCjdAKuuf48ItNfFFcw==} + + '@zag-js/qr-code@1.22.1': + resolution: {integrity: sha512-HIRlNsPNcp5buiTZx7DrX/gCtouGAH4VJc8Q6HBUkaBbiiijVEuYN0aNAjZIdm2pDtrh4KaYjMPuIH8IrV554Q==} + + '@zag-js/radio-group@1.22.1': + resolution: {integrity: sha512-eqvY1y/Ui4nQOU8XE9tGShOCbI/YdSHFeH/tDJe2Yy+1kqO4bENxFJ3R1P097KusJgeb2SYzhID27whUslOq7g==} + + '@zag-js/rating-group@1.22.1': + resolution: {integrity: sha512-QxBK+hpfkQ4yFHUr1YOSwEQ3LuTrdS32J9zV8UyHu8HbgwzfR7L8ZAa1PUUmG65tupzua2pbn1NioOkMvDmBOQ==} + + '@zag-js/react@1.22.1': + resolution: {integrity: sha512-TcIKkNo9EFel+d92nb7104voKJNDiMkqq9nn7Ozq/TE8A62JPf5zk8y8zqoxTbGDTTk+tDjW7Sm1IKb4r6rX4w==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@zag-js/rect-utils@1.22.1': + resolution: {integrity: sha512-jtI03SR9kF0AcBffoFI/TKXn5KyhjNCtsGlqbWw0dKbhWTNy1v432FDC5opmmnH8W5LjjWebIzo4QtO5+632QQ==} + + '@zag-js/remove-scroll@1.22.1': + resolution: {integrity: sha512-2TrS8ljp8SADX5xRB/+KGBCBYbYTeH0k5IEalG2rt8ReNyNAW1JfCrm53KCVoCg9YmxKF3MrxPgPT83MNFsJhQ==} + + '@zag-js/scroll-area@1.22.1': + resolution: {integrity: sha512-BuWKGR3n1yMktYqfTx+U9iwpXkJJhDXW4yin7u/lLMAE0DXR4byyo8aollCkuzZdZbK7NmUG2zVQHUMZ1QaR6w==} + + '@zag-js/scroll-snap@1.22.1': + resolution: {integrity: sha512-kctqJiteALaavoHEpYBDSPgUErIdwAoY5jcrU4Mq5L8FjtI4tSNr8BWcXzSBK2UVqaKN+vDo+PDcj7XIXTUQJA==} + + '@zag-js/select@1.22.1': + resolution: {integrity: sha512-sWq0RqlJvmj0heJDpfS3OfM1ynSSCW+fYY5v3T/QyH4qneqB8OJjgh8EEBaHlOkbqv/oBsk855U8/o6jegfUxw==} + + '@zag-js/signature-pad@1.22.1': + resolution: {integrity: sha512-iD8tBCHSmRI6kdtHO8dNRZrfjGTxfWgweLlNXKu5JV2JkzPBhDCxpthHI9k8LJ0cgUM5/EW4HdEpjO9h47FsaA==} + + '@zag-js/slider@1.22.1': + resolution: {integrity: sha512-aricrX99r21RAS9TyPNTJL8gE8mNRSQMy7TIXTa9aoeRjN0Cf6+PSksKfmPdP9l249/nplGqvC25Ck7XUVJn6A==} + + '@zag-js/splitter@1.22.1': + resolution: {integrity: sha512-ZMuFlVvqO2WYD7AECEB51iiFpN7A30Q28NfkIVR98xugwUX1OJq1IizKRSbLgC/LmseHPp3OvotxjZX6FqkK4Q==} + + '@zag-js/steps@1.22.1': + resolution: {integrity: sha512-eJCHbHG9aGAbzb/IQCqpmk6fmwSmIfocAxNKVTljroD6OHkBtqgaZQVS3q4xyjz61nB/d/0ZlsvpCVjm1EhwBw==} + + '@zag-js/store@1.22.1': + resolution: {integrity: sha512-KrMWi/Fa4cqOjx2zDSMIu6vztFYik+V3K6VPWRVONM4FkboLpTqAEayzwgTTNqMK9iYYZIYjhiPhAVLW9iLuBg==} + + '@zag-js/switch@1.22.1': + resolution: {integrity: sha512-ipmBHEqtcrPYr5WS5Juj5dt4GFIqr81NYVNe8RHMW8jIHgHhRCRj3TokGXVlZ7HdseCKTTNNrcvRFBr1sJBbOw==} + + '@zag-js/tabs@1.22.1': + resolution: {integrity: sha512-B0WHW36uuR+pu/24X0yI4eyvSwo7WmqOc5C3ohZHOf03zkmMJdtMtVQSotKr7qhGMt5updCgs68MR7jAmmc1Lw==} + + '@zag-js/tags-input@1.22.1': + resolution: {integrity: sha512-/56pCeSIW+g+ish3Gjed7iNcPSbQEsBCBsCn6FU/JfjwyhLM0sAtn1vkE/eR92hvDX3klV12XzEMBGe4Egr3GQ==} + + '@zag-js/time-picker@1.22.1': + resolution: {integrity: sha512-7fqCtyDbuaelffLZ8q9infns+HQKqFMjL4k2V5zALAWdYu2NzvlMYHgj2Ue9AI4VI5QaE1nnwV6hxwS4Zpglvg==} + peerDependencies: + '@internationalized/date': '>=3.0.0' + + '@zag-js/timer@1.22.1': + resolution: {integrity: sha512-VmXnXjecuF4tXFdBRuMHxO8mQX3/vxagE4vx0M0gKwbGoGrXnhYGvULiPL3RlJj8OR8pIfYuP2lbCrt8XM625A==} + + '@zag-js/toast@1.22.1': + resolution: {integrity: sha512-cxcfbMftA//ggOAlxG3q04WZVL/mMVklvtQ2rSyj3oRmnwocJPYXtJzKIRazWBjji3u3BOA+ZeOI1AcGrfp/TQ==} + + '@zag-js/toggle-group@1.22.1': + resolution: {integrity: sha512-StxnGsPwzB60pGHTD7sNOqIMXjEPMl3lYQk0i2F5MIQWlTRkYdp4ivh73xBRYVtqK15gqacuWXw87EDzKcNwcA==} + + '@zag-js/toggle@1.22.1': + resolution: {integrity: sha512-KK9VK8ZkA/ep7KxQFaeVE/zHVm90fkp9q6q4inyQkUdURUg0vovTFI3c5q/c1zm9/g51vbNf5qCXWU4m9sQK8A==} + + '@zag-js/tooltip@1.22.1': + resolution: {integrity: sha512-0ub0p22CzYnaXv0prAnWNjqUBkdw4nO4yGk5qntaodajpLNQ4gSdq7Hj4afHzJqwbKAkwb3KzJFqcqIm9Y/dfw==} + + '@zag-js/tour@1.22.1': + resolution: {integrity: sha512-VhHC65NgBaCjlVsw1M4Me0P6PCtmD9oi9gRzN2fEUESdpM/QT5Yw6PAAPP1AEo5okv+V2rRBgSKOu9ZyYHa+IQ==} + + '@zag-js/tree-view@1.22.1': + resolution: {integrity: sha512-AQmOn1mB+nLJEaq0xdSVnTI8Vt3nB3OweqdB12jkbdIOcWI9eY0RfhiNHC0k0mgAw+dMjyn84op/gOd9VVdtmA==} + + '@zag-js/types@1.22.1': + resolution: {integrity: sha512-lvpDSMR96e7H7TdwOiVpMzj6css5Ydix1nBi7BlmjME6v5OPR0KZwVDGD6h5UtTeVjPq8dPaqM8TJWw+QwbQSw==} + + '@zag-js/utils@1.22.1': + resolution: {integrity: sha512-VXY4gjHaTENHW+wjnKKENZ2jcaW0vnG2a5lYEMuZR4dpNCKH217yFr/bCNrI44y2s1W3LWhWmpEjfZluP6udYg==} + + '@zenfs/core@2.4.0': + resolution: {integrity: sha512-Ik1qM9xkP6lisNwR0zWaTXXuvM4Ef9yQRvYjJGqKGJv7fLsdwXS3tclbHkfvd9YkqlpxC6ac6am9oe7AWgepEA==} + engines: {node: '>= 18'} + hasBin: true + + '@zenfs/dom@1.2.4': + resolution: {integrity: sha512-ftCviemTnU8W0yON8zKS1cUopCMRRq6+edWD9MwXR349h0wfeTsnXBhv7/ctLWpDorWe2nUXIHAvNAJvj32eqA==} + engines: {node: '>= 22'} + peerDependencies: + '@zenfs/core': ^2.3.11 + kerium: ^1.3.4 + utilium: ^2.5.0 + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.0: + resolution: {integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + audio-buffer@5.0.0: + resolution: {integrity: sha512-gsDyj1wwUp8u7NBB+eW6yhLb9ICf+0eBmDX8NGaAS00w8/fLqFdxUlL5Ge/U8kB64DlQhdonxYC59dXy1J7H/w==} + + audio-decode@2.2.2: + resolution: {integrity: sha512-xyh7z6dpRT+5Ez4ggV2cEkSShkDvvIBBmVPR3kYY7uIBqRO1BGNjofip6JnjBnvezhrU3ypBGZjepyKFDZWnDw==} + + audio-type@2.2.1: + resolution: {integrity: sha512-En9AY6EG1qYqEy5L/quryzbA4akBpJrnBZNxeKTqGHC2xT9Qc4aZ8b7CcbOMFTTc/MGdoNyp+SN4zInZNKxMYA==} + engines: {node: '>=14'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bundle-n-require@1.1.2: + resolution: {integrity: sha512-bEk2jakVK1ytnZ9R2AAiZEeK/GxPUM8jvcRxHZXifZDMcjkI4EG/GlsJ2YGSVYT9y/p/gA9/0yDY8rCGsSU6Tg==} + + call-bind-apply-helpers@1.0.1: + resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + cli-table@0.3.11: + resolution: {integrity: sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==} + engines: {node: '>= 0.2.0'} + + codec-parser@2.5.0: + resolution: {integrity: sha512-Ru9t80fV8B0ZiixQl8xhMTLru+dzuis/KQld32/x5T/+3LwZb0/YvQdSKytX9JqCnRdiupvAvyYJINKrXieziQ==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colors@1.0.3: + resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==} + engines: {node: '>=0.1.90'} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - constant-case@3.0.4: - resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} cross-fetch@4.0.0: resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} @@ -884,9 +1835,23 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -908,6 +1873,15 @@ packages: supports-color: optional: true + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -919,18 +1893,26 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - dom-align@1.12.4: - resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==} + dockview-core@4.7.1: + resolution: {integrity: sha512-Tia3vYHtqACMZTiZv86yQOabwKj5KrBhQqlSr7qXV0qmmRSZ8dNbaU63LIHYFprST7JgHupIm9JVES+OhqMoTQ==} - dom-helpers@5.2.1: - resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dockview@4.7.1: + resolution: {integrity: sha512-DgMzSKNjDvZzIQjFfAV6I6EDkqe40Sjz1Qgyf88KG4U1Kgp/bIIEDSLpz65BsW5ZD9Qi3y18TCISYTgsNvU9TA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -939,6 +1921,18 @@ packages: earcut@2.2.4: resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + es-abstract@1.23.9: resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} @@ -975,6 +1969,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1038,9 +2037,17 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1048,15 +2055,26 @@ packages: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fastq@1.18.0: resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1065,6 +2083,9 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1079,6 +2100,14 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1098,6 +2127,10 @@ packages: resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -1114,6 +2147,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1126,6 +2163,10 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} + globby@14.1.0: + resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} + engines: {node: '>=18'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1133,6 +2174,9 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + h3@1.15.4: + resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -1160,15 +2204,16 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - header-case@2.0.4: - resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} - - hotkeys-js@3.13.9: - resolution: {integrity: sha512-3TRCj9u9KUH6cKo25w4KIdBfdBfNRjfUwrljCLDC2XhmPDG0SjAZFcFZekpUZFmXzfYoGhFDcdx2gX/vUVtztQ==} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} html-parse-stringify@3.0.1: resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + i18next-browser-languagedetector@8.0.4: resolution: {integrity: sha512-f3frU3pIxD50/Tz20zx9TD9HobKYg47fmAETb117GKGPrhwcSSPJDoCposXlVycVebQ9GQohC3Efbpq7/nnJ5w==} @@ -1189,10 +2234,23 @@ packages: typescript: optional: true + idb-keyval@6.2.2: + resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1201,14 +2259,23 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-async-function@2.1.0: resolution: {integrity: sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==} engines: {node: '>= 0.4'} @@ -1217,6 +2284,10 @@ packages: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + is-boolean-object@1.2.1: resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==} engines: {node: '>= 0.4'} @@ -1245,6 +2316,10 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} @@ -1301,6 +2376,9 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -1319,6 +2397,9 @@ packages: peerDependencies: react: '>=18.0' + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1326,9 +2407,17 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -1339,6 +2428,12 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + + kerium@1.3.6: + resolution: {integrity: sha512-TEMYweNOPW79LOGV9G26biypM2q96s4iJYxcWMg3IxUzRUIFGFfWIxS7Yx2eREKfhLINcNNXGUa0OR6ONS9Dgg==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1346,6 +2441,12 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1353,20 +2454,23 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + look-it-up@2.1.0: + resolution: {integrity: sha512-nMoGWW2HurtuJf6XAL56FWTDCWLOTSsanrgwOyaR5Y4e3zfG5N/0cU5xWZSEU3tBxhQugRbV1xL9jb+ug7yZww==} loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + memium@0.3.8: + resolution: {integrity: sha512-spMrGHK7zNVBCZ2nhXxABC1yBtfe3HqUwEHD8j7oVMaLU+eqLrDoTZzMhx6bphw2/yA1EwOEdgeECtSfwRHk4w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1382,22 +2486,70 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - mpg123-decoder@1.0.0: - resolution: {integrity: sha512-WV+pyuMUhRqv7s8S6p/Ii4KQHdBD1pb3yaABxcKJRsNp+HQ/Y6z2iIBIaOZu0JMHPTOoICYt0REDZ7XfLu+n/g==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mobx-react-lite@4.1.0: + resolution: {integrity: sha512-QEP10dpHHBeQNv1pks3WnHRCem2Zp636lq54M2nKO2Sarr13pL4u6diQXf65yzXUn0mkk18SyIDCm9UOJYTi1w==} + peerDependencies: + mobx: ^6.9.0 + react: ^16.8.0 || ^17 || ^18 || ^19 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + mobx-state-tree@7.0.2: + resolution: {integrity: sha512-Qmqgo2Ho1/JRTquo0EWw0ZA/k4wTUNPMIBg98ALGN1V/0Gupic7dg4GDYUhNbiLsYHpp48VgpaCDpDIKV+jNkQ==} + peerDependencies: + mobx: ^6.3.0 + + mobx@6.13.7: + resolution: {integrity: sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==} + + mpg123-decoder@1.0.0: + resolution: {integrity: sha512-WV+pyuMUhRqv7s8S6p/Ii4KQHdBD1pb3yaABxcKJRsNp+HQ/Y6z2iIBIaOZu0JMHPTOoICYt0REDZ7XfLu+n/g==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} nanoid@3.3.8: resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.1.5: + resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + engines: {node: ^18 || >=20} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-eval@2.0.0: + resolution: {integrity: sha512-Ap+L9HznXAVeJj3TJ1op6M6bg5xtTq8L5CU/PJxtkhea/DrIxdTknGKIECKd/v/Lgql95iuMAYvIzBNd0pmcMg==} + engines: {node: '>= 4'} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} @@ -1408,10 +2560,21 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-mock-http@1.0.3: + resolution: {integrity: sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==} + node-wav@0.0.2: resolution: {integrity: sha512-M6Rm/bbG6De/gKGxOpeOobx/dnGuP0dz40adqx38boqHhlWssBJZgLCPBNtb9NkrmnKYiV04xELq+R6PFOnoLA==} engines: {node: '>=4.4.0'} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + normalize.css@8.0.1: resolution: {integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==} @@ -1443,6 +2606,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + ofetch@1.4.1: + resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} + ogg-opus-decoder@1.6.14: resolution: {integrity: sha512-RQpk9yFl/mqXFwcgf1BrEYWL92HZk++aU1fOO8mPZ1+1DUYbJdpdUQEFfbPE1xcBkRGU3p75DjEO+EDMNeikFQ==} @@ -1465,26 +2631,34 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - param-case@3.0.4: - resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + parse-svg-path@0.1.2: resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==} - pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - - path-case@3.0.4: - resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1492,6 +2666,21 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + path-type@6.0.0: + resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} + engines: {node: '>=18'} + + perfect-freehand@1.2.2: + resolution: {integrity: sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1499,6 +2688,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + pixi.js@8.6.6: resolution: {integrity: sha512-o5pw7G2yuIrnBx0G4npBlmFp+XGNcapI/Ufs62rRj/4XKxc1Zo74YJr/BtEXcXTraTKd+pQvYOLvnfxRjxBMvQ==} @@ -1506,6 +2699,13 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.2: resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==} engines: {node: ^10 || ^12 || >=14} @@ -1514,9 +2714,27 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-compare@3.0.1: + resolution: {integrity: sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==} + + proxy-memoize@3.0.1: + resolution: {integrity: sha512-VDdG/VYtOgdGkWJx7y0o7p+zArSf2383Isci8C+BP3YXgMYDoPd3cCBjw0JdWb6YBb9sFiOPbAADDVTPJnh+9g==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1527,88 +2745,18 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - rc-align@4.0.15: - resolution: {integrity: sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-dock@3.3.0: - resolution: {integrity: sha512-9rQAzHSLAdQz1ZpPqQGkZKlAt4YI6gNYjuqqpY6hIWBFDhVPccs0jYr7L7PP3OpmliZ/R60LgHfLFbrL9l+Tlg==} - peerDependencies: - react: '>=17.0.0' - react-dom: '>=17.0.0' - - rc-dropdown@4.0.1: - resolution: {integrity: sha512-OdpXuOcme1rm45cR0Jzgfl1otzmU4vuBVb+etXM8vcaULGokAKVpKlw8p6xzspG7jGd/XxShvq+N3VNEfk/l5g==} - peerDependencies: - react: '>=16.11.0' - react-dom: '>=16.11.0' - - rc-menu@9.6.4: - resolution: {integrity: sha512-6DiNAjxjVIPLZXHffXxxcyE15d4isRL7iQ1ru4MqYDH2Cqc5bW96wZOdMydFtGLyDdnmEQ9jVvdCE9yliGvzkw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-menu@9.8.4: - resolution: {integrity: sha512-lmw2j8I2fhdIzHmC9ajfImfckt0WDb2KVJJBBRIsxPEw2kGkEfjLMUoB1NgiNT/Q5cC8PdjGOGQjHJIJMwyNMw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-motion@2.9.5: - resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-new-window@0.1.13: - resolution: {integrity: sha512-KqANLQVfgNcfs+R4ntpzV5ELyqXMlAUimdSfFHapk2VwsoZX3y+BK2RjFBFb7q865yqAshP87g0PbIPqblKVTg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-overflow@1.4.1: - resolution: {integrity: sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-resize-observer@1.4.3: - resolution: {integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-tabs@11.16.1: - resolution: {integrity: sha512-bR7Dap23YyfzZQwtKomhiFEFzZuE7WaKWo+ypNRSGB9PDKSc6tM12VP8LWYkvmmQHthgwP0WRN8nFbSJWuqLYw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-trigger@5.3.4: - resolution: {integrity: sha512-mQv+vas0TwKcjAO2izNPkqR4j86OemLRmvL2nOzdP9OWNWA1ivoTt5hzFqYNW9zACwmTezRiN8bttrC7cZzYSw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' + quick-lru@7.1.0: + resolution: {integrity: sha512-Pzd/4IFnTb8E+I1P5rbLQoqpUHcXKg48qTYKi4EANg+sTPwGFEMOcYGiiZz6xuQcOMZP7MPsrdAPx+16Q8qahg==} + engines: {node: '>=18'} - rc-util@5.44.3: - resolution: {integrity: sha512-q6KCcOFk3rv/zD3MckhJteZxb0VjAIFuf622B7ElK4vfrZdAzs16XR5p3VTdy3+U5jfJU5ACz4QnhLSuAGe5dA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} react-dom@19.0.0: resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: react: ^19.0.0 - react-fast-compare@3.2.2: - resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - react-i18next@15.4.1: resolution: {integrity: sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==} peerDependencies: @@ -1622,28 +2770,43 @@ packages: react-native: optional: true + react-icons@5.5.0: + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} + peerDependencies: + react: '*' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} - react-popper@2.3.0: - resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} - peerDependencies: - '@popperjs/core': ^2.0.0 - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - react-reconciler@0.31.0: resolution: {integrity: sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==} engines: {node: '>=0.10.0'} peerDependencies: react: ^19.0.0 + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react-split-pane@0.1.92: resolution: {integrity: sha512-GfXP1xSzLMcLJI5BM36Vh7GgZBpy+U/X0no+VM3fxayv+p1Jly5HpMofZJraeaMl73b3hvlr+N9zJKvLB/uz9w==} peerDependencies: @@ -1653,18 +2816,12 @@ packages: react-style-proptype@3.2.2: resolution: {integrity: sha512-ywYLSjNkxKHiZOqNlso9PZByNEY+FTyh3C+7uuziK0xFXu9xzdyfHwg4S9iyiRRoPCR4k2LqaBBsWVmSBwCWYQ==} - react-transition-group@4.4.5: - resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} - peerDependencies: - react: '>=16.6.0' - react-dom: '>=16.6.0' - - react-uid@2.4.0: - resolution: {integrity: sha512-+MVs/25NrcZuGrmlVRWPOSsbS8y72GJOBsR7d68j3/wqOrRBF52U29XAw4+XSelw0Vm6s5VmGH5mCbTCPGVCVg==} + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true @@ -1673,6 +2830,21 @@ packages: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -1684,13 +2856,15 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - resize-observer-polyfill@1.5.1: - resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + resolve@2.0.0-next.5: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true @@ -1711,6 +2885,12 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -1722,6 +2902,9 @@ packages: scheduler@0.25.0: resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -1731,9 +2914,6 @@ packages: engines: {node: '>=10'} hasBin: true - sentence-case@3.0.4: - resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} - set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -1746,6 +2926,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + shallowequal@1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} @@ -1773,18 +2956,35 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-yenc@1.0.4: resolution: {integrity: sha512-5gvxpSd79e9a3V4QDYUqnqxeD4HGlhCakVpb6gMnDD7lexJggSBJRBO5h52y/iJrdXRilX9UCuDaIJhSWm5OWw==} - snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - spark-md5@3.0.2: - resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==} + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} string.prototype.matchall@4.0.12: resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} @@ -1805,10 +3005,42 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + styled-components@6.1.19: + resolution: {integrity: sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==} + engines: {node: '>= 16'} + peerDependencies: + react: '>= 16.8.0' + react-dom: '>= 16.8.0' + + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + + stylis@4.3.2: + resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1817,6 +3049,13 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1830,8 +3069,22 @@ packages: peerDependencies: typescript: '>=4.2.0' - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + ts-essentials@9.4.2: + resolution: {integrity: sha512-mB/cDhOvD7pg3YCLk2rOtejHjjdSi9in/IBYE13S+8WA5FBSraYf4V/ws55uvs0IvQ/l0wBOlXy5yBNZ9Bl8ZQ==} + peerDependencies: + typescript: '>=4.1.0' + peerDependenciesMeta: + typescript: + optional: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -1865,29 +3118,125 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - upper-case-first@2.0.2: - resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} - upper-case@2.0.2: - resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unstorage@1.17.1: + resolution: {integrity: sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6.0.3 || ^7.0.0 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + uqr@0.1.2: + resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + use-sync-external-store@1.4.0: resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - uuid@11.0.4: - resolution: {integrity: sha512-IzL6VtTTYcAhA/oghbFJ1Dkmqev+FpQWnCBaKq/gUluLxliWvO8DPFWfIviRmYbtaavtSQe4WBL++rFjdcGWEg==} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utilium@2.5.4: + resolution: {integrity: sha512-JeuMqv+yc2AGo0A3vVIiEwrYQcE/8SXftnvUq0HK0wnAmHtPCMZ/vln7nmNF9G63Qw9egijnTx3TxzdWZKYBmA==} + engines: {node: '>=22.0.0'} hasBin: true vite@6.0.9: @@ -1934,8 +3283,9 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} - warning@4.0.3: - resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -1968,12 +3318,142 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zustand@5.0.8: + resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: + '@ark-ui/react@5.22.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@internationalized/date': 3.8.2 + '@zag-js/accordion': 1.22.1 + '@zag-js/anatomy': 1.22.1 + '@zag-js/angle-slider': 1.22.1 + '@zag-js/async-list': 1.22.1 + '@zag-js/auto-resize': 1.22.1 + '@zag-js/avatar': 1.22.1 + '@zag-js/carousel': 1.22.1 + '@zag-js/checkbox': 1.22.1 + '@zag-js/clipboard': 1.22.1 + '@zag-js/collapsible': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/color-picker': 1.22.1 + '@zag-js/color-utils': 1.22.1 + '@zag-js/combobox': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/date-picker': 1.22.1(@internationalized/date@3.8.2) + '@zag-js/date-utils': 1.22.1(@internationalized/date@3.8.2) + '@zag-js/dialog': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/editable': 1.22.1 + '@zag-js/file-upload': 1.22.1 + '@zag-js/file-utils': 1.22.1 + '@zag-js/floating-panel': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/highlight-word': 1.22.1 + '@zag-js/hover-card': 1.22.1 + '@zag-js/i18n-utils': 1.22.1 + '@zag-js/json-tree-utils': 1.22.1 + '@zag-js/listbox': 1.22.1 + '@zag-js/menu': 1.22.1 + '@zag-js/number-input': 1.22.1 + '@zag-js/pagination': 1.22.1 + '@zag-js/password-input': 1.22.1 + '@zag-js/pin-input': 1.22.1 + '@zag-js/popover': 1.22.1 + '@zag-js/presence': 1.22.1 + '@zag-js/progress': 1.22.1 + '@zag-js/qr-code': 1.22.1 + '@zag-js/radio-group': 1.22.1 + '@zag-js/rating-group': 1.22.1 + '@zag-js/react': 1.22.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@zag-js/scroll-area': 1.22.1 + '@zag-js/select': 1.22.1 + '@zag-js/signature-pad': 1.22.1 + '@zag-js/slider': 1.22.1 + '@zag-js/splitter': 1.22.1 + '@zag-js/steps': 1.22.1 + '@zag-js/switch': 1.22.1 + '@zag-js/tabs': 1.22.1 + '@zag-js/tags-input': 1.22.1 + '@zag-js/time-picker': 1.22.1(@internationalized/date@3.8.2) + '@zag-js/timer': 1.22.1 + '@zag-js/toast': 1.22.1 + '@zag-js/toggle': 1.22.1 + '@zag-js/toggle-group': 1.22.1 + '@zag-js/tooltip': 1.22.1 + '@zag-js/tour': 1.22.1 + '@zag-js/tree-view': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.1.0 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.28.3': + dependencies: + '@babel/types': 7.28.2 + '@babel/runtime@7.26.0': dependencies: regenerator-runtime: 0.14.1 @@ -1982,112 +3462,308 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 - '@blueprintjs/colors@5.1.5': + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + + '@babel/traverse@7.28.3': dependencies: - tslib: 2.6.3 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@chakra-ui/cli@3.26.0(@chakra-ui/react@3.26.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))': + dependencies: + '@chakra-ui/react': 3.26.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@clack/prompts': 0.11.0 + '@pandacss/is-valid-prop': 0.54.0 + '@types/cli-table': 0.3.4 + '@types/debug': 4.1.12 + '@visulima/boxen': 2.0.2 + bundle-n-require: 1.1.2 + chokidar: 3.6.0 + cli-table: 0.3.11 + commander: 12.1.0 + debug: 4.4.1 + globby: 14.1.0 + https-proxy-agent: 7.0.6 + look-it-up: 2.1.0 + node-fetch: 3.3.2 + package-manager-detector: 1.3.0 + prettier: 3.6.2 + scule: 1.3.0 + sucrase: 3.35.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color - '@blueprintjs/core@5.16.4(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@chakra-ui/react@3.26.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@blueprintjs/colors': 5.1.5 - '@blueprintjs/icons': 5.17.1(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@popperjs/core': 2.11.8 - classnames: 2.5.1 - normalize.css: 8.0.1 + '@ark-ui/react': 5.22.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@emotion/is-prop-valid': 1.4.0 + '@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) + '@emotion/utils': 1.4.2 + '@pandacss/is-valid-prop': 0.54.0 + csstype: 3.1.3 + fast-safe-stringify: 2.1.1 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react-transition-group: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react-uid: 2.4.0(@types/react@19.0.7)(react@19.0.0) - tslib: 2.6.3 - use-sync-external-store: 1.4.0(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.7 - '@blueprintjs/icons@5.17.1(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@clack/core@0.5.0': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@0.11.0': + dependencies: + '@clack/core': 0.5.0 + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.27.1 + '@babel/runtime': 7.26.10 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + + '@emotion/is-prop-valid@1.4.0': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.8.1': {} + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0)': dependencies: - change-case: 4.1.2 - classnames: 2.5.1 + '@babel/runtime': 7.26.10 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - tslib: 2.6.3 optionalDependencies: '@types/react': 19.0.7 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.1.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/unitless@0.10.0': {} + + '@emotion/unitless@0.8.1': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} '@esbuild/aix-ppc64@0.24.2': optional: true + '@esbuild/aix-ppc64@0.25.9': + optional: true + '@esbuild/android-arm64@0.24.2': optional: true + '@esbuild/android-arm64@0.25.9': + optional: true + '@esbuild/android-arm@0.24.2': optional: true + '@esbuild/android-arm@0.25.9': + optional: true + '@esbuild/android-x64@0.24.2': optional: true + '@esbuild/android-x64@0.25.9': + optional: true + '@esbuild/darwin-arm64@0.24.2': optional: true + '@esbuild/darwin-arm64@0.25.9': + optional: true + '@esbuild/darwin-x64@0.24.2': optional: true + '@esbuild/darwin-x64@0.25.9': + optional: true + '@esbuild/freebsd-arm64@0.24.2': optional: true + '@esbuild/freebsd-arm64@0.25.9': + optional: true + '@esbuild/freebsd-x64@0.24.2': optional: true + '@esbuild/freebsd-x64@0.25.9': + optional: true + '@esbuild/linux-arm64@0.24.2': optional: true + '@esbuild/linux-arm64@0.25.9': + optional: true + '@esbuild/linux-arm@0.24.2': optional: true + '@esbuild/linux-arm@0.25.9': + optional: true + '@esbuild/linux-ia32@0.24.2': optional: true + '@esbuild/linux-ia32@0.25.9': + optional: true + '@esbuild/linux-loong64@0.24.2': optional: true + '@esbuild/linux-loong64@0.25.9': + optional: true + '@esbuild/linux-mips64el@0.24.2': optional: true + '@esbuild/linux-mips64el@0.25.9': + optional: true + '@esbuild/linux-ppc64@0.24.2': optional: true + '@esbuild/linux-ppc64@0.25.9': + optional: true + '@esbuild/linux-riscv64@0.24.2': optional: true + '@esbuild/linux-riscv64@0.25.9': + optional: true + '@esbuild/linux-s390x@0.24.2': optional: true + '@esbuild/linux-s390x@0.25.9': + optional: true + '@esbuild/linux-x64@0.24.2': optional: true + '@esbuild/linux-x64@0.25.9': + optional: true + '@esbuild/netbsd-arm64@0.24.2': optional: true + '@esbuild/netbsd-arm64@0.25.9': + optional: true + '@esbuild/netbsd-x64@0.24.2': optional: true + '@esbuild/netbsd-x64@0.25.9': + optional: true + '@esbuild/openbsd-arm64@0.24.2': optional: true + '@esbuild/openbsd-arm64@0.25.9': + optional: true + '@esbuild/openbsd-x64@0.24.2': optional: true + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + '@esbuild/sunos-x64@0.24.2': optional: true + '@esbuild/sunos-x64@0.25.9': + optional: true + '@esbuild/win32-arm64@0.24.2': optional: true + '@esbuild/win32-arm64@0.25.9': + optional: true + '@esbuild/win32-ia32@0.24.2': optional: true + '@esbuild/win32-ia32@0.25.9': + optional: true + '@esbuild/win32-x64@0.24.2': optional: true + '@esbuild/win32-x64@0.25.9': + optional: true + '@eshaz/web-worker@1.2.2': {} '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0)': @@ -2131,6 +3807,23 @@ snapshots: dependencies: levn: 0.4.1 + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@floating-ui/utils@0.2.10': {} + '@fortawesome/fontawesome-common-types@6.7.2': {} '@fortawesome/fontawesome-svg-core@6.7.2': @@ -2160,30 +3853,313 @@ snapshots: '@humanwhocodes/retry@0.4.1': {} + '@internationalized/date@3.8.2': + dependencies: + '@swc/helpers': 0.5.17 + + '@internationalized/number@3.6.4': + dependencies: + '@swc/helpers': 0.5.17 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@nodelib/fs.scandir@2.1.5': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.18.0 + + '@pandacss/is-valid-prop@0.54.0': {} + + '@phifans/audio@0.0.7': + dependencies: + '@types/setimmediate': 1.0.4 + audio-decode: 2.2.2 + setimmediate: 1.0.5 + + '@pixi/colord@2.9.6': {} + + '@pixi/react@8.0.0(@types/react@19.0.7)(pixi.js@8.6.6)(react@19.0.0)': + dependencies: + its-fine: 1.2.5(@types/react@19.0.7)(react@19.0.0) + pixi.js: 8.6.6 + react: 19.0.0 + react-reconciler: 0.31.0(react@19.0.0) + transitivePeerDependencies: + - '@types/react' + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.0.7)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@radix-ui/react-context@1.1.2(@types/react@19.0.7)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@radix-ui/react-direction@1.1.1(@types/react@19.0.7)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.0.7)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-id@1.1.1(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.7)(react@19.0.0) + aria-hidden: 1.2.6 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.7.1(@types/react@19.0.7)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/rect': 1.1.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-slot@1.2.3(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.0.7)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 - '@nodelib/fs.stat@2.0.5': {} + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 - '@nodelib/fs.walk@1.2.8': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.0.7)(react@19.0.0)': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.18.0 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 - '@pixi/colord@2.9.6': {} + '@radix-ui/react-use-rect@1.1.1(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 - '@pixi/react@8.0.0(@types/react@19.0.7)(pixi.js@8.6.6)(react@19.0.0)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.0.7)(react@19.0.0)': dependencies: - its-fine: 1.2.5(@types/react@19.0.7)(react@19.0.0) - pixi.js: 8.6.6 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.7)(react@19.0.0) react: 19.0.0 - react-reconciler: 0.31.0(react@19.0.0) - transitivePeerDependencies: - - '@types/react' + optionalDependencies: + '@types/react': 19.0.7 - '@popperjs/core@2.11.8': {} + '@radix-ui/rect@1.1.1': {} '@rollup/rollup-android-arm-eabi@4.34.7': optional: true @@ -2242,6 +4218,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.34.7': optional: true + '@sindresorhus/merge-streams@2.3.0': {} + '@swc/core-darwin-arm64@1.10.3': optional: true @@ -2272,7 +4250,7 @@ snapshots: '@swc/core-win32-x64-msvc@1.10.3': optional: true - '@swc/core@1.10.3': + '@swc/core@1.10.3(@swc/helpers@0.5.17)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.17 @@ -2287,13 +4265,20 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.10.3 '@swc/core-win32-ia32-msvc': 1.10.3 '@swc/core-win32-x64-msvc': 1.10.3 + '@swc/helpers': 0.5.17 '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + '@swc/types@0.1.17': dependencies: '@swc/counter': 0.1.3 + '@tauri-apps/api@2.8.0': {} + '@tauri-apps/cli-darwin-arm64@2.2.7': optional: true @@ -2337,24 +4322,46 @@ snapshots: '@tauri-apps/cli-win32-ia32-msvc': 2.2.7 '@tauri-apps/cli-win32-x64-msvc': 2.2.7 + '@tauri-apps/plugin-fs@2.4.2': + dependencies: + '@tauri-apps/api': 2.8.0 + + '@tauri-apps/plugin-sql@2.3.0': + dependencies: + '@tauri-apps/api': 2.8.0 + '@thi.ng/bitstream@2.4.8': dependencies: '@thi.ng/errors': 2.5.22 '@thi.ng/errors@2.5.22': {} + '@types/cli-table@0.3.4': {} + '@types/css-font-loading-module@0.0.12': {} + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + '@types/earcut@2.1.4': {} '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} + '@types/ms@2.1.0': {} + '@types/node@22.10.5': dependencies: undici-types: 6.20.0 + '@types/node@24.3.1': + dependencies: + undici-types: 7.10.0 + + '@types/parse-json@4.0.2': {} + '@types/react-dom@19.0.3(@types/react@19.0.7)': dependencies: '@types/react': 19.0.7 @@ -2367,7 +4374,9 @@ snapshots: dependencies: csstype: 3.1.3 - '@types/spark-md5@3.0.5': {} + '@types/setimmediate@1.0.4': {} + + '@types/stylis@4.2.5': {} '@typescript-eslint/eslint-plugin@8.18.2(@typescript-eslint/parser@8.18.2(eslint@9.17.0)(typescript@5.6.3))(eslint@9.17.0)(typescript@5.6.3)': dependencies: @@ -2446,9 +4455,11 @@ snapshots: '@typescript-eslint/types': 8.18.2 eslint-visitor-keys: 4.2.0 - '@vitejs/plugin-react-swc@3.7.2(vite@6.0.9(@types/node@22.10.5))': + '@visulima/boxen@2.0.2': {} + + '@vitejs/plugin-react-swc@3.7.2(@swc/helpers@0.5.17)(vite@6.0.9(@types/node@22.10.5))': dependencies: - '@swc/core': 1.10.3 + '@swc/core': 1.10.3(@swc/helpers@0.5.17) vite: 6.0.9(@types/node@22.10.5) transitivePeerDependencies: - '@swc/helpers' @@ -2472,12 +4483,550 @@ snapshots: '@xmldom/xmldom@0.8.10': {} + '@xterm/xterm@5.5.0': + optional: true + + '@zag-js/accordion@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/anatomy@1.22.1': {} + + '@zag-js/angle-slider@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/rect-utils': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/aria-hidden@1.22.1': {} + + '@zag-js/async-list@1.22.1': + dependencies: + '@zag-js/core': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/auto-resize@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/avatar@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/carousel@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/scroll-snap': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/checkbox@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/clipboard@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/collapsible@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/collection@1.22.1': + dependencies: + '@zag-js/utils': 1.22.1 + + '@zag-js/color-picker@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/color-utils': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/color-utils@1.22.1': + dependencies: + '@zag-js/utils': 1.22.1 + + '@zag-js/combobox@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/aria-hidden': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/core@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/date-picker@1.22.1(@internationalized/date@3.8.2)': + dependencies: + '@internationalized/date': 3.8.2 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/date-utils': 1.22.1(@internationalized/date@3.8.2) + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/live-region': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/date-utils@1.22.1(@internationalized/date@3.8.2)': + dependencies: + '@internationalized/date': 3.8.2 + + '@zag-js/dialog@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/aria-hidden': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/remove-scroll': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/dismissable@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + '@zag-js/interact-outside': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/dom-query@1.22.1': + dependencies: + '@zag-js/types': 1.22.1 + + '@zag-js/editable@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/interact-outside': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/file-upload@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/file-utils': 1.22.1 + '@zag-js/i18n-utils': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/file-utils@1.22.1': + dependencies: + '@zag-js/i18n-utils': 1.22.1 + + '@zag-js/floating-panel@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/rect-utils': 1.22.1 + '@zag-js/store': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/focus-trap@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/focus-visible@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/highlight-word@1.22.1': {} + + '@zag-js/hover-card@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/i18n-utils@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/interact-outside@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/json-tree-utils@1.22.1': {} + + '@zag-js/listbox@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/live-region@1.22.1': {} + + '@zag-js/menu@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/rect-utils': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/number-input@1.22.1': + dependencies: + '@internationalized/number': 3.6.4 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/pagination@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/password-input@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/pin-input@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/popover@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/aria-hidden': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/remove-scroll': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/popper@1.22.1': + dependencies: + '@floating-ui/dom': 1.7.4 + '@zag-js/dom-query': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/presence@1.22.1': + dependencies: + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + + '@zag-js/progress@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/qr-code@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + proxy-memoize: 3.0.1 + uqr: 0.1.2 + + '@zag-js/radio-group@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/rating-group@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/react@1.22.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@zag-js/core': 1.22.1 + '@zag-js/store': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@zag-js/rect-utils@1.22.1': {} + + '@zag-js/remove-scroll@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/scroll-area@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/scroll-snap@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/select@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/signature-pad@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + perfect-freehand: 1.2.2 + + '@zag-js/slider@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/splitter@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/steps@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/store@1.22.1': + dependencies: + proxy-compare: 3.0.1 + + '@zag-js/switch@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tabs@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tags-input@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/auto-resize': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/interact-outside': 1.22.1 + '@zag-js/live-region': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/time-picker@1.22.1(@internationalized/date@3.8.2)': + dependencies: + '@internationalized/date': 3.8.2 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/timer@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/toast@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/toggle-group@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/toggle@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tooltip@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tour@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/interact-outside': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tree-view@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/types@1.22.1': + dependencies: + csstype: 3.1.3 + + '@zag-js/utils@1.22.1': {} + + '@zenfs/core@2.4.0': + dependencies: + '@types/node': 24.3.1 + buffer: 6.0.3 + eventemitter3: 5.0.1 + kerium: 1.3.6 + memium: 0.3.8 + readable-stream: 4.7.0 + utilium: 2.5.4 + + '@zenfs/dom@1.2.4(@zenfs/core@2.4.0)(kerium@1.3.6)(utilium@2.5.4)': + dependencies: + '@zenfs/core': 2.4.0 + kerium: 1.3.6 + utilium: 2.5.4 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: acorn: 8.14.0 acorn@8.14.0: {} + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -2485,12 +5034,29 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + + ansi-regex@6.2.0: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.3 @@ -2565,9 +5131,17 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.26.10 + cosmiconfig: 7.1.0 + resolve: 1.22.10 + balanced-match@1.0.2: {} - bowser@2.11.0: {} + base64-js@1.5.1: {} + + binary-extensions@2.3.0: {} brace-expansion@1.1.11: dependencies: @@ -2582,6 +5156,16 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bundle-n-require@1.1.2: + dependencies: + esbuild: 0.25.9 + node-eval: 2.0.0 + call-bind-apply-helpers@1.0.1: dependencies: es-errors: 1.3.0 @@ -2601,38 +5185,32 @@ snapshots: callsites@3.1.0: {} - camel-case@4.1.2: - dependencies: - pascal-case: 3.1.2 - tslib: 2.6.3 - - capital-case@1.0.4: - dependencies: - no-case: 3.0.4 - tslib: 2.6.3 - upper-case-first: 2.0.2 + camelize@1.0.1: {} chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - change-case@4.1.2: + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: dependencies: - camel-case: 4.1.2 - capital-case: 1.0.4 - constant-case: 3.0.4 - dot-case: 3.0.4 - header-case: 2.0.4 - no-case: 3.0.4 - param-case: 3.0.4 - pascal-case: 3.1.2 - path-case: 3.0.4 - sentence-case: 3.0.4 - snake-case: 3.0.4 - tslib: 2.6.3 + readdirp: 4.1.2 - classnames@2.5.1: {} + cli-table@0.3.11: + dependencies: + colors: 1.0.3 codec-parser@2.5.0: {} @@ -2642,13 +5220,27 @@ snapshots: color-name@1.1.4: {} + colors@1.0.3: {} + + commander@12.1.0: {} + + commander@4.1.1: {} + concat-map@0.0.1: {} - constant-case@3.0.4: + convert-source-map@1.9.0: {} + + cookie-es@1.2.2: {} + + core-util-is@1.0.3: {} + + cosmiconfig@7.1.0: dependencies: - no-case: 3.0.4 - tslib: 2.6.3 - upper-case: 2.0.2 + '@types/parse-json': 4.0.2 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 cross-fetch@4.0.0: dependencies: @@ -2662,8 +5254,22 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-color-keywords@1.0.0: {} + + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + csstype@3.1.3: {} + data-uri-to-buffer@4.0.1: {} + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.3 @@ -2686,6 +5292,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.1: + dependencies: + ms: 2.1.3 + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -2700,21 +5310,22 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 + defu@6.1.4: {} + + destr@2.0.5: {} - dom-align@1.12.4: {} + detect-node-es@1.1.0: {} - dom-helpers@5.2.1: + dockview-core@4.7.1: {} + + dockview@4.7.1(react@19.0.0): dependencies: - '@babel/runtime': 7.26.0 - csstype: 3.1.3 + dockview-core: 4.7.1 + react: 19.0.0 - dot-case@3.0.4: + doctrine@2.1.0: dependencies: - no-case: 3.0.4 - tslib: 2.6.3 + esutils: 2.0.3 dunder-proto@1.0.1: dependencies: @@ -2724,6 +5335,16 @@ snapshots: earcut@2.2.4: {} + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + es-abstract@1.23.9: dependencies: array-buffer-byte-length: 1.0.2 @@ -2850,6 +5471,35 @@ snapshots: '@esbuild/win32-ia32': 0.24.2 '@esbuild/win32-x64': 0.24.2 + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + escape-string-regexp@4.0.0: {} eslint-plugin-react-hooks@5.1.0(eslint@9.17.0): @@ -2948,8 +5598,12 @@ snapshots: esutils@2.0.3: {} + event-target-shim@5.0.1: {} + eventemitter3@5.0.1: {} + events@3.3.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -2960,14 +5614,29 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fastq@1.18.0: dependencies: reusify: 1.0.4 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -2976,6 +5645,8 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-root@1.1.0: {} + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -2992,6 +5663,15 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + fsevents@2.3.3: optional: true @@ -3021,6 +5701,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -3040,6 +5722,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globals@14.0.0: {} globals@15.14.0: {} @@ -3049,10 +5740,31 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 + globby@14.1.0: + dependencies: + '@sindresorhus/merge-streams': 2.3.0 + fast-glob: 3.3.3 + ignore: 7.0.5 + path-type: 6.0.0 + slash: 5.1.0 + unicorn-magic: 0.3.0 + gopd@1.2.0: {} graphemer@1.4.0: {} + h3@1.15.4: + dependencies: + cookie-es: 1.2.2 + crossws: 0.3.5 + defu: 6.1.4 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.3 + radix3: 1.1.2 + ufo: 1.6.1 + uncrypto: 0.1.3 + has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -3075,17 +5787,21 @@ snapshots: dependencies: function-bind: 1.1.2 - header-case@2.0.4: + hoist-non-react-statics@3.3.2: dependencies: - capital-case: 1.0.4 - tslib: 2.6.3 - - hotkeys-js@3.13.9: {} + react-is: 16.13.1 html-parse-stringify@3.0.1: dependencies: void-elements: 3.1.0 + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + i18next-browser-languagedetector@8.0.4: dependencies: '@babel/runtime': 7.26.0 @@ -3110,8 +5826,16 @@ snapshots: optionalDependencies: typescript: 5.6.3 + idb-keyval@6.2.2: {} + + ieee754@1.2.1: {} + ignore@5.3.2: {} + ignore@7.0.5: {} + + immediate@3.0.6: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -3119,18 +5843,24 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.2 side-channel: 1.1.0 + iron-webcrypto@1.2.1: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 call-bound: 1.0.3 get-intrinsic: 1.2.7 + is-arrayish@0.2.1: {} + is-async-function@2.1.0: dependencies: call-bound: 1.0.3 @@ -3142,6 +5872,10 @@ snapshots: dependencies: has-bigints: 1.1.0 + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + is-boolean-object@1.2.1: dependencies: call-bound: 1.0.3 @@ -3170,6 +5904,8 @@ snapshots: dependencies: call-bound: 1.0.3 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.0: dependencies: call-bound: 1.0.3 @@ -3229,6 +5965,8 @@ snapshots: call-bound: 1.0.3 get-intrinsic: 1.2.7 + isarray@1.0.0: {} + isarray@2.0.5: {} isexe@2.0.0: {} @@ -3251,14 +5989,24 @@ snapshots: transitivePeerDependencies: - '@types/react' + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + js-tokens@4.0.0: {} js-yaml@4.1.0: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -3270,6 +6018,17 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + kerium@1.3.6: + dependencies: + utilium: 2.5.4 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -3279,24 +6038,33 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lie@3.3.0: + dependencies: + immediate: 3.0.6 + + lines-and-columns@1.2.4: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 lodash.merge@4.6.2: {} - lodash@4.17.21: {} + look-it-up@2.1.0: {} loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 - lower-case@2.0.2: - dependencies: - tslib: 2.6.3 + lru-cache@10.4.3: {} math-intrinsics@1.1.0: {} + memium@0.3.8: + dependencies: + kerium: 1.3.6 + utilium: 2.5.4 + merge2@1.4.1: {} micromatch@4.0.8: @@ -3312,27 +6080,72 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minipass@7.1.2: {} + + mobx-react-lite@4.1.0(mobx@6.13.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + mobx: 6.13.7 + react: 19.0.0 + use-sync-external-store: 1.4.0(react@19.0.0) + optionalDependencies: + react-dom: 19.0.0(react@19.0.0) + + mobx-state-tree@7.0.2(mobx@6.13.7)(typescript@5.6.3): + dependencies: + mobx: 6.13.7 + ts-essentials: 9.4.2(typescript@5.6.3) + transitivePeerDependencies: + - typescript + + mobx@6.13.7: {} + mpg123-decoder@1.0.0: dependencies: '@wasm-audio-decoders/common': 9.0.5 ms@2.1.3: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nanoid@3.3.8: {} + nanoid@5.1.5: {} + natural-compare@1.4.0: {} - no-case@3.0.4: + next-themes@0.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - lower-case: 2.0.2 - tslib: 2.6.3 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + node-domexception@1.0.0: {} + + node-eval@2.0.0: + dependencies: + path-is-absolute: 1.0.1 + + node-fetch-native@1.6.7: {} node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-mock-http@1.0.3: {} + node-wav@0.0.2: {} + normalize-path@3.0.0: {} + normalize.css@8.0.1: {} object-assign@4.1.1: {} @@ -3370,6 +6183,12 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + ofetch@1.4.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.1 + ogg-opus-decoder@1.6.14: dependencies: '@wasm-audio-decoders/common': 9.0.5 @@ -3403,37 +6222,50 @@ snapshots: dependencies: p-limit: 3.1.0 - param-case@3.0.4: - dependencies: - dot-case: 3.0.4 - tslib: 2.6.3 + package-json-from-dist@1.0.1: {} + + package-manager-detector@1.3.0: {} + + pako@1.0.11: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 - parse-svg-path@0.1.2: {} - - pascal-case@3.1.2: + parse-json@5.2.0: dependencies: - no-case: 3.0.4 - tslib: 2.6.3 + '@babel/code-frame': 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 - path-case@3.0.4: - dependencies: - dot-case: 3.0.4 - tslib: 2.6.3 + parse-svg-path@0.1.2: {} path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + path-type@6.0.0: {} + + perfect-freehand@1.2.2: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} + pirates@4.0.7: {} + pixi.js@8.6.6: dependencies: '@pixi/colord': 2.9.6 @@ -3448,6 +6280,14 @@ snapshots: possible-typed-array-names@1.0.0: {} + postcss-value-parser@4.2.0: {} + + postcss@8.4.49: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.2: dependencies: nanoid: 3.3.8 @@ -3456,12 +6296,24 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.6.2: {} + + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 + proxy-compare@3.0.1: {} + + proxy-memoize@3.0.1: + dependencies: + proxy-compare: 3.0.1 + punycode@2.3.1: {} qoa-format@1.0.1: @@ -3470,129 +6322,15 @@ snapshots: queue-microtask@1.2.3: {} - rc-align@4.0.15(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - classnames: 2.5.1 - dom-align: 1.12.4 - rc-util: 5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - resize-observer-polyfill: 1.5.1 - - rc-dock@3.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - classnames: 2.5.1 - lodash: 4.17.21 - rc-dropdown: 4.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-menu: 9.8.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-new-window: 0.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-tabs: 11.16.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - - rc-dropdown@4.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - classnames: 2.5.1 - rc-trigger: 5.3.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-util: 5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - - rc-menu@9.6.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-overflow: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-trigger: 5.3.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-util: 5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - shallowequal: 1.1.0 - - rc-menu@9.8.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-overflow: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-trigger: 5.3.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-util: 5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - - rc-motion@2.9.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - classnames: 2.5.1 - rc-util: 5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - - rc-new-window@0.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - bowser: 2.11.0 - classnames: 2.5.1 - lodash: 4.17.21 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - - rc-overflow@1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - classnames: 2.5.1 - rc-resize-observer: 1.4.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-util: 5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - - rc-resize-observer@1.4.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - classnames: 2.5.1 - rc-util: 5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - resize-observer-polyfill: 1.5.1 - - rc-tabs@11.16.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - classnames: 2.5.1 - rc-dropdown: 4.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-menu: 9.6.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-resize-observer: 1.4.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-util: 5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - - rc-trigger@5.3.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - classnames: 2.5.1 - rc-align: 4.0.15(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-motion: 2.9.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - rc-util: 5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + quick-lru@7.1.0: {} - rc-util@5.44.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - react-is: 18.3.1 + radix3@1.1.2: {} react-dom@19.0.0(react@19.0.0): dependencies: react: 19.0.0 scheduler: 0.25.0 - react-fast-compare@3.2.2: {} - react-i18next@15.4.1(i18next@24.2.3(typescript@5.6.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@babel/runtime': 7.26.0 @@ -3602,24 +6340,37 @@ snapshots: optionalDependencies: react-dom: 19.0.0(react@19.0.0) - react-is@16.13.1: {} + react-icons@5.5.0(react@19.0.0): + dependencies: + react: 19.0.0 - react-is@18.3.1: {} + react-is@16.13.1: {} react-lifecycles-compat@3.0.4: {} - react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-reconciler@0.31.0(react@19.0.0): dependencies: - '@popperjs/core': 2.11.8 react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - react-fast-compare: 3.2.2 - warning: 4.0.3 + scheduler: 0.25.0 - react-reconciler@0.31.0(react@19.0.0): + react-remove-scroll-bar@2.3.8(@types/react@19.0.7)(react@19.0.0): dependencies: react: 19.0.0 - scheduler: 0.25.0 + react-style-singleton: 2.2.3(@types/react@19.0.7)(react@19.0.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.0.7 + + react-remove-scroll@2.7.1(@types/react@19.0.7)(react@19.0.0): + dependencies: + react: 19.0.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.0.7)(react@19.0.0) + react-style-singleton: 2.2.3(@types/react@19.0.7)(react@19.0.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.0.7)(react@19.0.0) + use-sidecar: 1.1.3(@types/react@19.0.7)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 react-split-pane@0.1.92(patch_hash=15b3bc41b3c9ea6e7a5c245069978ef071c4655082c4e9b0e14eb5d4eb57da5c)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: @@ -3633,24 +6384,40 @@ snapshots: dependencies: prop-types: 15.8.1 - react-transition-group@4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@babel/runtime': 7.26.0 - dom-helpers: 5.2.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - - react-uid@2.4.0(@types/react@19.0.7)(react@19.0.0): + react-style-singleton@2.2.3(@types/react@19.0.7)(react@19.0.0): dependencies: + get-nonce: 1.0.1 react: 19.0.0 - tslib: 2.6.3 + tslib: 2.8.1 optionalDependencies: '@types/react': 19.0.7 react@19.0.0: {} + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -3673,10 +6440,14 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 - resize-observer-polyfill@1.5.1: {} - resolve-from@4.0.0: {} + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + resolve@2.0.0-next.5: dependencies: is-core-module: 2.16.1 @@ -3722,6 +6493,10 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -3735,16 +6510,12 @@ snapshots: scheduler@0.25.0: {} + scule@1.3.0: {} + semver@6.3.1: {} semver@7.6.3: {} - sentence-case@3.0.4: - dependencies: - no-case: 3.0.4 - tslib: 2.6.3 - upper-case-first: 2.0.2 - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -3767,6 +6538,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.0.0 + setimmediate@1.0.5: {} + shallowequal@1.1.0: {} shebang-command@2.0.0: @@ -3803,16 +6576,29 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@4.1.0: {} + simple-yenc@1.0.4: {} - snake-case@3.0.4: - dependencies: - dot-case: 3.0.4 - tslib: 2.6.3 + sisteransi@1.0.5: {} + + slash@5.1.0: {} source-map-js@1.2.1: {} - spark-md5@3.0.2: {} + source-map@0.5.7: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 string.prototype.matchall@4.0.12: dependencies: @@ -3858,14 +6644,66 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.2.0 + strip-json-comments@3.1.1: {} + styled-components@6.1.19(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@emotion/is-prop-valid': 1.2.2 + '@emotion/unitless': 0.8.1 + '@types/stylis': 4.2.5 + css-to-react-native: 3.2.0 + csstype: 3.1.3 + postcss: 8.4.49 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + shallowequal: 1.1.0 + stylis: 4.3.2 + tslib: 2.6.2 + + stylis@4.2.0: {} + + stylis@4.3.2: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 supports-preserve-symlinks-flag@1.0.0: {} + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -3876,7 +6714,15 @@ snapshots: dependencies: typescript: 5.6.3 - tslib@2.6.3: {} + ts-essentials@9.4.2(typescript@5.6.3): + optionalDependencies: + typescript: 5.6.3 + + ts-interface-checker@0.1.13: {} + + tslib@2.6.2: {} + + tslib@2.8.1: {} type-check@0.4.0: dependencies: @@ -3927,6 +6773,8 @@ snapshots: typescript@5.6.3: {} + ufo@1.6.1: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.3 @@ -3934,25 +6782,59 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + uncrypto@0.1.3: {} + undici-types@6.20.0: {} - upper-case-first@2.0.2: - dependencies: - tslib: 2.6.3 + undici-types@7.10.0: {} + + unicorn-magic@0.3.0: {} - upper-case@2.0.2: + unstorage@1.17.1(idb-keyval@6.2.2): dependencies: - tslib: 2.6.3 + anymatch: 3.1.3 + chokidar: 4.0.3 + destr: 2.0.5 + h3: 1.15.4 + lru-cache: 10.4.3 + node-fetch-native: 1.6.7 + ofetch: 1.4.1 + ufo: 1.6.1 + optionalDependencies: + idb-keyval: 6.2.2 + + uqr@0.1.2: {} uri-js@4.4.1: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.0.7)(react@19.0.0): + dependencies: + react: 19.0.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.0.7 + + use-sidecar@1.1.3(@types/react@19.0.7)(react@19.0.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.0.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.0.7 + use-sync-external-store@1.4.0(react@19.0.0): dependencies: react: 19.0.0 - uuid@11.0.4: {} + util-deprecate@1.0.2: {} + + utilium@2.5.4: + dependencies: + eventemitter3: 5.0.1 + optionalDependencies: + '@xterm/xterm': 5.5.0 vite@6.0.9(@types/node@22.10.5): dependencies: @@ -3965,9 +6847,7 @@ snapshots: void-elements@3.1.0: {} - warning@4.0.3: - dependencies: - loose-envify: 1.4.0 + web-streams-polyfill@3.3.3: {} webidl-conversions@3.0.1: {} @@ -4022,4 +6902,26 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yaml@1.10.2: {} + yocto-queue@0.1.0: {} + + zod@3.25.76: {} + + zustand@5.0.8(@types/react@19.0.7)(react@19.0.0)(use-sync-external-store@1.4.0(react@19.0.0)): + optionalDependencies: + '@types/react': 19.0.7 + react: 19.0.0 + use-sync-external-store: 1.4.0(react@19.0.0) diff --git a/public/icon-black.svg b/public/icon-black.svg new file mode 100644 index 0000000..0fca27f --- /dev/null +++ b/public/icon-black.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..42912fe --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..f71d353 Binary files /dev/null and b/public/logo.png differ diff --git a/public/skin-default.zip b/public/skin-default.zip new file mode 100644 index 0000000..d05fee0 Binary files /dev/null and b/public/skin-default.zip differ diff --git a/public/skin/note-drag-highlight.png b/public/skin/note-drag-highlight.png deleted file mode 100644 index 7b5772c..0000000 Binary files a/public/skin/note-drag-highlight.png and /dev/null differ diff --git a/public/skin/note-drag.png b/public/skin/note-drag.png deleted file mode 100644 index ffa0e06..0000000 Binary files a/public/skin/note-drag.png and /dev/null differ diff --git a/public/skin/note-flick-highlight.png b/public/skin/note-flick-highlight.png deleted file mode 100644 index 9786993..0000000 Binary files a/public/skin/note-flick-highlight.png and /dev/null differ diff --git a/public/skin/note-flick.png b/public/skin/note-flick.png deleted file mode 100644 index 323a3ba..0000000 Binary files a/public/skin/note-flick.png and /dev/null differ diff --git a/public/skin/note-hold-body-highlight.png b/public/skin/note-hold-body-highlight.png deleted file mode 100644 index fee0d9d..0000000 Binary files a/public/skin/note-hold-body-highlight.png and /dev/null differ diff --git a/public/skin/note-hold-body.png b/public/skin/note-hold-body.png deleted file mode 100644 index a39dcb3..0000000 Binary files a/public/skin/note-hold-body.png and /dev/null differ diff --git a/public/skin/note-hold-end.png b/public/skin/note-hold-end.png deleted file mode 100644 index d947aba..0000000 Binary files a/public/skin/note-hold-end.png and /dev/null differ diff --git a/public/skin/note-hold-head-highlight.png b/public/skin/note-hold-head-highlight.png deleted file mode 100644 index 3210e6c..0000000 Binary files a/public/skin/note-hold-head-highlight.png and /dev/null differ diff --git a/public/skin/note-hold-head.png b/public/skin/note-hold-head.png deleted file mode 100644 index 84d4e07..0000000 Binary files a/public/skin/note-hold-head.png and /dev/null differ diff --git a/public/skin/note-tap-highlight.png b/public/skin/note-tap-highlight.png deleted file mode 100644 index ee94efb..0000000 Binary files a/public/skin/note-tap-highlight.png and /dev/null differ diff --git a/public/skin/note-tap.png b/public/skin/note-tap.png deleted file mode 100644 index 74ce0b2..0000000 Binary files a/public/skin/note-tap.png and /dev/null differ diff --git a/public/skin/skin-bundle.json b/public/skin/skin-bundle.json deleted file mode 100644 index c29e4f8..0000000 --- a/public/skin/skin-bundle.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "skin-default", - "assets": [ - { - "alias": "note-tap", - "src": "skin/note-tap.png" - }, - { - "alias": "note-tap-highlight", - "src": "skin/note-tap-highlight.png" - }, - { - "alias": "note-drag", - "src": "skin/note-drag.png" - }, - { - "alias": "note-drag-highlight", - "src": "skin/note-drag-highlight.png" - }, - { - "alias": "note-hold-head", - "src": "skin/note-hold-head.png" - }, - { - "alias": "note-hold-head-highlight", - "src": "skin/note-hold-head-highlight.png" - }, - { - "alias": "note-hold-body", - "src": "skin/note-hold-body.png" - }, - { - "alias": "note-hold-body-highlight", - "src": "skin/note-hold-body-highlight.png" - }, - { - "alias": "note-hold-end", - "src": "skin/note-hold-end.png" - }, - { - "alias": "note-flick", - "src": "skin/note-flick.png" - }, - { - "alias": "note-flick-highlight", - "src": "skin/note-flick-highlight.png" - } - ] -} \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 8c67e03..b04a15c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -52,6 +52,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -119,6 +125,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -152,6 +167,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + [[package]] name = "bitflags" version = "1.3.2" @@ -179,12 +200,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -200,7 +215,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2", + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2 0.6.2", ] [[package]] @@ -228,9 +252,9 @@ dependencies = [ [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -239,9 +263,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -366,12 +390,12 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.21.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbd1fe9db3ebf71b89060adaf7b0504c2d6a425cf061313099547e382c2e472" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ "serde", - "toml 0.8.20", + "toml 0.9.5", ] [[package]] @@ -436,44 +460,29 @@ dependencies = [ ] [[package]] -name = "cocoa" -version = "0.26.0" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "bitflags 2.8.0", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", + "bytes", + "memchr", ] [[package]] -name = "cocoa-foundation" -version = "0.2.0" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "bitflags 2.8.0", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", + "crossbeam-utils", ] [[package]] -name = "combine" -version = "4.6.7" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" @@ -540,6 +549,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -558,6 +582,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -576,15 +609,15 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.27.2" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 0.4.8", + "itoa", "matches", - "phf 0.8.0", + "phf 0.10.1", "proc-macro2", "quote", "smallvec", @@ -646,6 +679,17 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -676,16 +720,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", + "subtle", ] [[package]] @@ -694,19 +731,7 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys 0.5.0", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.6", - "windows-sys 0.48.0", + "dirs-sys", ] [[package]] @@ -717,8 +742,8 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.0", - "windows-sys 0.59.0", + "redox_users", + "windows-sys 0.60.2", ] [[package]] @@ -727,6 +752,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.8.0", + "objc2 0.6.2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -740,9 +775,9 @@ dependencies = [ [[package]] name = "dlopen2" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" dependencies = [ "dlopen2_derive", "libc", @@ -761,6 +796,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "dpi" version = "0.1.1" @@ -797,16 +838,25 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + [[package]] name = "embed-resource" -version = "2.5.1" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68b6f9f63a0b6a38bc447d4ce84e2b388f3ec95c99c641c8ff0dd3ef89a6379" +checksum = "4c6d81016d6c977deefb2ef8d8290da019e27cc26167e102185da528e6c0ab38" dependencies = [ "cc", "memchr", "rustc_version", - "toml 0.8.20", + "toml 0.9.5", "vswhom", "winreg", ] @@ -843,6 +893,28 @@ dependencies = [ "typeid", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -881,12 +953,29 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.5.0" @@ -946,6 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -965,6 +1055,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -1147,10 +1248,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1333,6 +1432,20 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.2", +] [[package]] name = "heck" @@ -1352,18 +1465,43 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "html5ever" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ "log", "mac", "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", + "match_token", ] [[package]] @@ -1374,7 +1512,7 @@ checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", - "itoa 1.0.14", + "itoa", ] [[package]] @@ -1418,31 +1556,13 @@ dependencies = [ "http", "http-body", "httparse", - "itoa 1.0.14", + "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - [[package]] name = "hyper-util" version = "0.1.10" @@ -1487,9 +1607,9 @@ dependencies = [ [[package]] name = "ico" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" dependencies = [ "byteorder", "png", @@ -1664,9 +1784,9 @@ dependencies = [ [[package]] name = "infer" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" dependencies = [ "cfb", ] @@ -1677,12 +1797,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.14" @@ -1779,14 +1893,13 @@ dependencies = [ [[package]] name = "kuchikiki" -version = "0.8.2" +version = "0.8.8-speedreader" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser", "html5ever", - "indexmap 1.9.3", - "matches", + "indexmap 2.7.1", "selectors", ] @@ -1795,6 +1908,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libappindicator" @@ -1836,6 +1952,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.3" @@ -1844,6 +1966,18 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.8.0", "libc", + "redox_syscall", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", ] [[package]] @@ -1877,35 +2011,47 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf 0.11.3", + "phf_codegen 0.11.3", "string_cache", "string_cache_codegen", "tendril", ] +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "matches" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1950,22 +2096,23 @@ dependencies = [ [[package]] name = "muda" -version = "0.15.3" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdae9c00e61cc0579bcac625e8ad22104c60548a025bfc972dc83868a28e1484" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" dependencies = [ "crossbeam-channel", "dpi", "gtk", "keyboard-types", - "objc2", + "objc2 0.6.2", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-foundation 0.3.1", "once_cell", "png", "serde", - "thiserror 1.0.69", - "windows-sys 0.59.0", + "thiserror 2.0.11", + "windows-sys 0.60.2", ] [[package]] @@ -2010,6 +2157,23 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2017,12 +2181,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] -name = "num-traits" -version = "0.2.19" +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", ] [[package]] @@ -2055,23 +2240,11 @@ dependencies = [ "libc", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" -dependencies = [ - "cc", -] [[package]] name = "objc2" @@ -2083,80 +2256,89 @@ dependencies = [ "objc2-encode", ] +[[package]] +name = "objc2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + [[package]] name = "objc2-app-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags 2.8.0", - "block2", + "block2 0.6.1", "libc", - "objc2", + "objc2 0.6.2", + "objc2-cloud-kit", "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", + "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", ] [[package]] name = "objc2-cloud-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" dependencies = [ "bitflags 2.8.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", + "objc2 0.6.2", + "objc2-foundation 0.3.1", ] [[package]] -name = "objc2-contacts" -version = "0.2.2" +name = "objc2-core-data" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" dependencies = [ - "block2", - "objc2", - "objc2-foundation", + "bitflags 2.8.0", + "objc2 0.6.2", + "objc2-foundation 0.3.1", ] [[package]] -name = "objc2-core-data" -version = "0.2.2" +name = "objc2-core-foundation" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", + "dispatch2", + "objc2 0.6.2", ] [[package]] -name = "objc2-core-image" -version = "0.2.2" +name = "objc2-core-graphics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", + "bitflags 2.8.0", + "dispatch2", + "objc2 0.6.2", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] -name = "objc2-core-location" -version = "0.2.2" +name = "objc2-core-image" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" dependencies = [ - "block2", - "objc2", - "objc2-contacts", - "objc2-foundation", + "objc2 0.6.2", + "objc2-foundation 0.3.1", ] [[package]] @@ -2165,6 +2347,15 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + [[package]] name = "objc2-foundation" version = "0.2.2" @@ -2172,114 +2363,118 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.8.0", - "block2", + "block2 0.5.1", "libc", - "objc2", + "objc2 0.5.2", ] [[package]] -name = "objc2-link-presentation" -version = "0.2.2" +name = "objc2-foundation" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", + "bitflags 2.8.0", + "block2 0.6.1", + "libc", + "objc2 0.6.2", + "objc2-core-foundation", ] [[package]] -name = "objc2-metal" -version = "0.2.2" +name = "objc2-io-surface" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" dependencies = [ "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", + "objc2 0.6.2", + "objc2-core-foundation", ] [[package]] -name = "objc2-quartz-core" +name = "objc2-javascript-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9052cb1bb50a4c161d934befcf879526fb87ae9a68858f241e693ca46225cf5a" +dependencies = [ + "objc2 0.6.2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] -name = "objc2-symbols" +name = "objc2-quartz-core" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "objc2", - "objc2-foundation", + "bitflags 2.8.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", ] [[package]] -name = "objc2-ui-kit" -version = "0.2.2" +name = "objc2-quartz-core" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" dependencies = [ "bitflags 2.8.0", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", + "objc2 0.6.2", + "objc2-foundation 0.3.1", ] [[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" +name = "objc2-security" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +checksum = "e1f8e0ef3ab66b08c42644dcb34dba6ec0a574bbd8adbb8bdbdc7a2779731a44" dependencies = [ - "block2", - "objc2", - "objc2-foundation", + "bitflags 2.8.0", + "objc2 0.6.2", + "objc2-core-foundation", ] [[package]] -name = "objc2-user-notifications" -version = "0.2.2" +name = "objc2-ui-kit" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ "bitflags 2.8.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", + "objc2 0.6.2", + "objc2-core-foundation", + "objc2-foundation 0.3.1", ] [[package]] name = "objc2-web-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" +checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" dependencies = [ "bitflags 2.8.0", - "block2", - "objc2", + "block2 0.6.1", + "objc2 0.6.2", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "objc2-javascript-core", + "objc2-security", ] [[package]] @@ -2328,6 +2523,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -2351,6 +2552,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2363,9 +2573,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros 0.8.0", "phf_shared 0.8.0", - "proc-macro-hack", ] [[package]] @@ -2374,7 +2582,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ + "phf_macros 0.10.0", "phf_shared 0.10.0", + "proc-macro-hack", ] [[package]] @@ -2399,12 +2609,12 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_generator 0.11.3", + "phf_shared 0.11.3", ] [[package]] @@ -2439,12 +2649,12 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro-hack", "proc-macro2", "quote", @@ -2500,7 +2710,9 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-fs", "tauri-plugin-log", + "tauri-plugin-sql", ] [[package]] @@ -2515,6 +2727,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -2664,58 +2897,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quinn" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.11", - "tokio", - "tracing", -] - -[[package]] -name = "quinn-proto" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" -dependencies = [ - "bytes", - "getrandom 0.2.15", - "rand 0.8.5", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.11", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "quote" version = "1.0.38" @@ -2827,17 +3008,6 @@ dependencies = [ "bitflags 2.8.0", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 1.0.69", -] - [[package]] name = "redox_users" version = "0.5.0" @@ -2901,7 +3071,6 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-rustls", "hyper-util", "ipnet", "js-sys", @@ -2910,16 +3079,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", - "rustls-pemfile", - "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls", "tokio-util", "tower", "tower-service", @@ -2928,24 +3092,9 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", "windows-registry", ] -[[package]] -name = "ring" -version = "0.17.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - [[package]] name = "rkyv" version = "0.7.45" @@ -2975,6 +3124,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rust_decimal" version = "1.36.0" @@ -2997,12 +3166,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustc_version" version = "0.4.1" @@ -3012,49 +3175,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustls" -version = "0.23.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" -dependencies = [ - "web-time", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.19" @@ -3117,22 +3237,20 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "selectors" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ "bitflags 1.3.2", "cssparser", "derive_more", "fxhash", "log", - "matches", "phf 0.8.0", "phf_codegen 0.8.0", "precomputed-hash", "servo_arc", "smallvec", - "thin-slice", ] [[package]] @@ -3146,9 +3264,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -3166,9 +3284,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -3192,7 +3310,7 @@ version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ - "itoa 1.0.14", + "itoa", "memchr", "ryu", "serde", @@ -3218,6 +3336,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3225,7 +3352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.14", + "itoa", "ryu", "serde", ] @@ -3262,9 +3389,9 @@ dependencies = [ [[package]] name = "serialize-to-javascript" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" dependencies = [ "serde", "serde_json", @@ -3273,25 +3400,36 @@ dependencies = [ [[package]] name = "serialize-to-javascript-impl" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] name = "servo_arc" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" dependencies = [ "nodrop", "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -3309,6 +3447,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -3347,6 +3495,9 @@ name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -3370,9 +3521,9 @@ dependencies = [ "foreign-types", "js-sys", "log", - "objc2", - "objc2-foundation", - "objc2-quartz-core", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", "raw-window-handle", "redox_syscall", "wasm-bindgen", @@ -3387,23 +3538,234 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" dependencies = [ "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.2", + "hashlink", + "indexmap 2.7.1", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.11", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.98", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.98", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.8.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.11", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.8.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.11", + "time", + "tracing", + "whoami", ] [[package]] -name = "soup3-sys" -version = "0.5.0" +name = "sqlx-sqlite" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.11", + "time", + "tracing", + "url", ] [[package]] @@ -3437,6 +3799,17 @@ dependencies = [ "quote", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -3517,12 +3890,12 @@ dependencies = [ [[package]] name = "tao" -version = "0.31.1" +version = "0.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3731d04d4ac210cd5f344087733943b9bfb1a32654387dad4d1c70de21aee2c9" +checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7" dependencies = [ "bitflags 2.8.0", - "cocoa", + "block2 0.6.1", "core-foundation", "core-graphics", "crossbeam-channel", @@ -3539,7 +3912,9 @@ dependencies = [ "ndk", "ndk-context", "ndk-sys", - "objc", + "objc2 0.6.2", + "objc2-app-kit", + "objc2-foundation 0.3.1", "once_cell", "parking_lot", "raw-window-handle", @@ -3548,7 +3923,7 @@ dependencies = [ "unicode-segmentation", "url", "windows", - "windows-core 0.58.0", + "windows-core 0.61.2", "windows-version", "x11-dl", ] @@ -3578,17 +3953,17 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.2.5" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a998b6be84104ca05c7e9a21f2180ddec020c8b84ea59a8fc8530a2a19588d" +checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c" dependencies = [ "anyhow", "bytes", - "dirs 6.0.0", + "cookie", + "dirs", "dunce", "embed_plist", - "futures-util", - "getrandom 0.2.15", + "getrandom 0.3.1", "glob", "gtk", "heck 0.5.0", @@ -3598,9 +3973,11 @@ dependencies = [ "log", "mime", "muda", - "objc2", + "objc2 0.6.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.3.1", + "objc2-ui-kit", + "objc2-web-kit", "percent-encoding", "plist", "raw-window-handle", @@ -3628,13 +4005,13 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.5" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e950124f6779c6cf98e3260c7a6c8488a74aa6350dd54c6950fdaa349bca2df" +checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f" dependencies = [ "anyhow", "cargo_toml", - "dirs 5.0.1", + "dirs", "glob", "heck 0.5.0", "json-patch", @@ -3644,15 +4021,15 @@ dependencies = [ "serde_json", "tauri-utils", "tauri-winres", - "toml 0.8.20", + "toml 0.9.5", "walkdir", ] [[package]] name = "tauri-codegen" -version = "2.0.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77894f9ddb5cb6c04fcfe8c8869ebe0aded4dabf19917118d48be4a95599ab5" +checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a" dependencies = [ "base64 0.22.1", "brotli", @@ -3677,9 +4054,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3240a5caed760a532e8f687be6f05b2c7d11a1d791fb53ccc08cfeb3e5308736" +checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3691,9 +4068,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.0.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5841b9a0200e954ef7457f8d327091424328891e267a97b641dc246cc54d0dec" +checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f" dependencies = [ "anyhow", "glob", @@ -3702,10 +4079,32 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "toml 0.8.20", + "toml 0.9.5", "walkdir", ] +[[package]] +name = "tauri-plugin-fs" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ead0daec5d305adcefe05af9d970fc437bcc7996052d564e7393eb291252da" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.11", + "toml 0.8.20", + "url", +] + [[package]] name = "tauri-plugin-log" version = "2.2.1" @@ -3716,8 +4115,8 @@ dependencies = [ "byte-unit", "fern", "log", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "serde", "serde_json", "serde_repr", @@ -3729,37 +4128,63 @@ dependencies = [ ] [[package]] -name = "tauri-runtime" +name = "tauri-plugin-sql" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2274ef891ccc0a8d318deffa9d70053f947664d12d58b9c0d1ae5e89237e01f7" +checksum = "df059378695202fef1e274b8e7916fc3dffc44716ae4baf8c0226089b2f390ae" +dependencies = [ + "futures-core", + "indexmap 2.7.1", + "log", + "serde", + "serde_json", + "sqlx", + "tauri", + "tauri-plugin", + "thiserror 2.0.11", + "time", + "tokio", +] + +[[package]] +name = "tauri-runtime" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846" dependencies = [ + "cookie", "dpi", "gtk", "http", "jni", + "objc2 0.6.2", + "objc2-ui-kit", + "objc2-web-kit", "raw-window-handle", "serde", "serde_json", "tauri-utils", "thiserror 2.0.11", "url", + "webkit2gtk", + "webview2-com", "windows", ] [[package]] name = "tauri-runtime-wry" -version = "2.3.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3707b40711d3b9f6519150869e358ffbde7c57567fb9b5a8b51150606939b2a0" +checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" dependencies = [ "gtk", "http", "jni", "log", - "objc2", + "objc2 0.6.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.3.1", + "once_cell", "percent-encoding", "raw-window-handle", "softbuffer", @@ -3775,10 +4200,11 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.1.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fb10e7cc97456b2d5b9c03e335b5de5da982039a303a20d10006885e4523a0" +checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" dependencies = [ + "anyhow", "brotli", "cargo_metadata", "ctor", @@ -3803,7 +4229,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.11", - "toml 0.8.20", + "toml 0.9.5", "url", "urlpattern", "uuid", @@ -3812,12 +4238,12 @@ dependencies = [ [[package]] name = "tauri-winres" -version = "0.1.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" dependencies = [ "embed-resource", - "toml 0.7.8", + "toml 0.9.5", ] [[package]] @@ -3831,12 +4257,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thiserror" version = "1.0.69" @@ -3884,7 +4304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", - "itoa 1.0.14", + "itoa", "libc", "num-conv", "num_threads", @@ -3951,12 +4371,13 @@ dependencies = [ ] [[package]] -name = "tokio-rustls" -version = "0.26.1" +name = "tokio-stream" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ - "rustls", + "futures-core", + "pin-project-lite", "tokio", ] @@ -3975,26 +4396,29 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.8" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", + "serde_spanned 0.6.8", + "toml_datetime 0.6.8", + "toml_edit 0.22.24", ] [[package]] name = "toml" -version = "0.8.20" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ + "indexmap 2.7.1", "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.24", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow 0.7.13", ] [[package]] @@ -4006,6 +4430,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -4013,9 +4446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.7.1", - "serde", - "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.8", "winnow 0.5.40", ] @@ -4026,7 +4457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap 2.7.1", - "toml_datetime", + "toml_datetime 0.6.8", "winnow 0.5.40", ] @@ -4038,11 +4469,26 @@ checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap 2.7.1", "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.7.2", + "serde_spanned 0.6.8", + "toml_datetime 0.6.8", + "winnow 0.7.13", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow 0.7.13", ] +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + [[package]] name = "tower" version = "0.5.2" @@ -4076,10 +4522,23 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "tracing-core" version = "0.1.33" @@ -4091,22 +4550,23 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.19.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48a05076dd272615d03033bf04f480199f7d1b66a8ac64d75c625fc4a70c06b" +checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" dependencies = [ - "core-graphics", "crossbeam-channel", - "dirs 5.0.1", + "dirs", "libappindicator", "muda", - "objc2", + "objc2 0.6.2", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", "once_cell", "png", "serde", - "thiserror 1.0.69", + "thiserror 2.0.11", "windows-sys 0.59.0", ] @@ -4169,6 +4629,12 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.16" @@ -4176,16 +4642,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] -name = "unicode-segmentation" -version = "1.12.0" +name = "unicode-normalization" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] [[package]] -name = "untrusted" -version = "0.9.0" +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "url" @@ -4251,6 +4726,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.0" @@ -4323,6 +4804,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -4417,16 +4904,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webkit2gtk" version = "2.0.1" @@ -4471,25 +4948,16 @@ dependencies = [ "system-deps", ] -[[package]] -name = "webpki-roots" -version = "0.26.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webview2-com" -version = "0.34.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823e7ebcfaea51e78f72c87fc3b65a1e602c321f407a0b36dbb327d7bb7cd921" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" dependencies = [ "webview2-com-macros", "webview2-com-sys", "windows", - "windows-core 0.58.0", + "windows-core 0.61.2", "windows-implement", "windows-interface", ] @@ -4507,13 +4975,23 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.34.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a82bce72db6e5ee83c68b5de1e2cd6ea195b9fbff91cb37df5884cbe3222df4" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ - "thiserror 1.0.69", + "thiserror 2.0.11", "windows", - "windows-core 0.58.0", + "windows-core 0.61.2", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", ] [[package]] @@ -4549,13 +5027,14 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window-vibrancy" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831ad7678290beae36be6f9fad9234139c7f00f3b536347de7745621716be82d" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" dependencies = [ - "objc2", + "objc2 0.6.2", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-foundation 0.3.1", "raw-window-handle", "windows-sys 0.59.0", "windows-version", @@ -4563,12 +5042,24 @@ dependencies = [ [[package]] name = "windows" -version = "0.58.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -4582,22 +5073,33 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.58.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-result", - "windows-strings", - "windows-targets 0.52.6", + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link", + "windows-threading", ] [[package]] name = "windows-implement" -version = "0.58.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", @@ -4606,23 +5108,39 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.58.0" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn 2.0.98", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link", +] + [[package]] name = "windows-registry" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ - "windows-result", - "windows-strings", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] @@ -4635,16 +5153,34 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -4681,6 +5217,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -4729,10 +5274,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4743,13 +5289,22 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-version" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c12476c23a74725c539b24eae8bfc0dac4029c39cdb561d9f23616accd4ae26d" dependencies = [ - "windows-targets 0.53.0", + "windows-targets 0.53.3", ] [[package]] @@ -4943,21 +5498,21 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.2" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.52.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -4983,14 +5538,15 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wry" -version = "0.48.1" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e33c08b174442ff80d5c791020696f9f8b4e4a87b8cfc7494aad6167ec44e1" +checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90" dependencies = [ "base64 0.22.1", - "block2", + "block2 0.6.1", "cookie", "crossbeam-channel", + "dirs", "dpi", "dunce", "gdkx11", @@ -5002,9 +5558,10 @@ dependencies = [ "kuchikiki", "libc", "ndk", - "objc2", + "objc2 0.6.2", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-foundation 0.3.1", "objc2-ui-kit", "objc2-web-kit", "once_cell", @@ -5019,7 +5576,7 @@ dependencies = [ "webkit2gtk-sys", "webview2-com", "windows", - "windows-core 0.58.0", + "windows-core 0.61.2", "windows-version", "x11-dl", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 10a3cdb..f6a979c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -23,3 +23,5 @@ serde = { version = "1.0", features = ["derive"] } log = "0.4" tauri = { version = "2.2.4", features = [] } tauri-plugin-log = "2.0.0-rc" +tauri-plugin-fs = "2" +tauri-plugin-sql = { version = "2", features = ["sqlite"] } diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 795b9b7..d860e1e 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/src-tauri/capabilities/database.json b/src-tauri/capabilities/database.json new file mode 100644 index 0000000..700b253 --- /dev/null +++ b/src-tauri/capabilities/database.json @@ -0,0 +1,12 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "database", + "description": "enables database permissions", + "windows": [ + "*" + ], + "permissions": [ + "sql:default", + "sql:allow-execute" + ] +} \ No newline at end of file diff --git a/src-tauri/capabilities/fs.json b/src-tauri/capabilities/fs.json new file mode 100644 index 0000000..347632e --- /dev/null +++ b/src-tauri/capabilities/fs.json @@ -0,0 +1,16 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "fs", + "description": "enables fs permissions on certain path", + "windows": [ + "*" + ], + "permissions": [ + "fs:default", + "fs:scope-appconfig-recursive", + "fs:scope-appconfig-index", + "fs:allow-appconfig-read-recursive", + "fs:allow-appconfig-write-recursive", + "fs:allow-appconfig-meta-recursive" + ] +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 9c3118c..42a0394 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,16 +1,18 @@ #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() - .setup(|app| { - if cfg!(debug_assertions) { - app.handle().plugin( - tauri_plugin_log::Builder::default() - .level(log::LevelFilter::Info) - .build(), - )?; - } - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + tauri::Builder::default() + .plugin(tauri_plugin_sql::Builder::new().build()) + .plugin(tauri_plugin_fs::init()) + .setup(|app| { + if cfg!(debug_assertions) { + app.handle().plugin( + tauri_plugin_log::Builder::default() + .level(log::LevelFilter::Info) + .build(), + )?; + } + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index ad5fe83..69c3a72 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - app_lib::run(); + app_lib::run(); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 1f5e382..d9f6414 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -6,8 +6,8 @@ "build": { "frontendDist": "../dist", "devUrl": "http://localhost:5173/", - "beforeDevCommand": "pnpm dev --config vite.config.tauri.ts", - "beforeBuildCommand": "pnpm build --config vite.config.tauri.ts" + "beforeDevCommand": "pnpm dev --config vite/config.tauri.ts", + "beforeBuildCommand": "pnpm build --config vite/config.tauri.ts" }, "app": { "windows": [ @@ -20,6 +20,7 @@ } ], "security": { + "capabilities": ["database", "fs", "default"], "csp": null } }, diff --git a/src/App/App.ts b/src/App/App.ts deleted file mode 100644 index c8d4a38..0000000 --- a/src/App/App.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { EventEmitter } from "eventemitter3"; -import Audio, { Audio as AudioClass } from "@/Audio/Audio"; -import Chart, { ChartExported } from "@/Chart/Chart"; -import Assets, { AssetsManager } from '@/Assets/Assets'; -import { Hotkeys } from './Hotkeys'; -import { ChartInfo } from "@/Chart/types"; -import { Nullable } from "@/utils/types"; -import { Ticker } from "pixi.js"; -import { ImportChart } from "@/utils/file"; - -export class App { - readonly audio: AudioClass = Audio; - readonly assets: AssetsManager = Assets; - readonly events = new EventEmitter(); - readonly hotkeys = new Hotkeys(this.events); - private currentChart: Nullable = null; -} - -const app = new App(); -export default app; - -console.log(app); diff --git a/src/App/Hotkeys.ts b/src/App/Hotkeys.ts deleted file mode 100644 index 297c1b7..0000000 --- a/src/App/Hotkeys.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { EventEmitter } from 'eventemitter3'; -import hotkeys from 'hotkeys-js'; - -export class Hotkeys { - keymap: { [key: string]: string }; - event: EventEmitter; - - constructor(e: EventEmitter) { - this.keymap = {}; - this.event = e; - } - - add(key: string, id: string) { - this.keymap[key] = id; - hotkeys(key, (event: KeyboardEvent,) => { - event.preventDefault(); - this.event.emit(id); - }); - } - - remove(key: string) { - hotkeys.unbind(key); - delete this.keymap[key]; - } - - on(id: string, callback: () => void) { - this.event.on(id, callback); - } -} \ No newline at end of file diff --git a/src/Assets/Assets.ts b/src/Assets/Assets.ts deleted file mode 100644 index 5b3d813..0000000 --- a/src/Assets/Assets.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Assets } from 'pixi.js'; -import AudioClip from '@/Audio/Clip'; -import { AssetsBundle, Texture, UnresolvedAsset } from 'pixi.js'; - -const loadDefaultAssetsTexture = (): Promise> => new Promise(async (res, rej) => { - try { - const result: Record = {}; - const defaultBundle = (await (await fetch('skin/skin-bundle.json')).json()) as AssetsBundle; - await Assets.init({ manifest: { bundles: [ defaultBundle ] } }); - const bundleResult = await Assets.loadBundle('skin-default') as Record; - - for (const asset of (defaultBundle.assets as UnresolvedAsset[])) { - if (!asset.alias) continue; - result[asset.alias as string] = bundleResult[asset.alias as string]; - } - - res(result); - } catch (e) { - rej(e); - } -}); - -class AssetsManager { - readonly textures: Record = {}; - readonly sounds: Record = {}; - - constructor() { - loadDefaultAssetsTexture() - .then((e) => { - for (const name in e) { - this.textures[name] = e[name]; - } - }) - .catch(e => {throw e}); - } -} - -const assets = new AssetsManager(); -export default assets; -export { AssetsManager }; diff --git a/src/Audio/Audio.ts b/src/Audio/Audio.ts deleted file mode 100644 index 93aedba..0000000 --- a/src/Audio/Audio.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Ticker } from 'pixi.js'; -import AudioClock from './Clock'; -import AudioChannel from './Channel'; -import AudioClip from './Clip'; -import { resumeAudioCtx } from './utils'; - -const AudioCtx = window.AudioContext; -const GlobalAudioCtx = new AudioCtx({ latencyHint: 'interactive' }); -const GlobalAudioTicker = new Ticker(); -const GlobalAudioClock = new AudioClock(GlobalAudioCtx, GlobalAudioTicker, GlobalAudioCtx.baseLatency); - -export class Audio { - readonly audioCtx: AudioContext; - readonly clock: AudioClock; - - readonly masterGain: GainNode; - readonly channels: { - music: AudioChannel, - effect: AudioChannel, - }; - - constructor() { - this.audioCtx = GlobalAudioCtx; - this.clock = GlobalAudioClock; - - this.masterGain = this.audioCtx.createGain(); - this.masterGain.connect(this.audioCtx.destination); - this.channels = { - music: new AudioChannel(this, GlobalAudioTicker), - effect: new AudioChannel(this, GlobalAudioTicker), - }; - } - - static from(buffer: AudioBuffer) { - return new AudioClip(GlobalAudioCtx, GlobalAudioClock, buffer); - } - - get masterVolume() { - return this.masterGain.gain.value; - } - - set masterVolume(volume: number) { - this.masterGain.gain.value = volume; - } -} - -const audio = new Audio(); -export default audio; - -// Automatically resume AudioContext -const _resumeAudio = () => { - resumeAudioCtx(GlobalAudioCtx).catch(() => void 0); -}; -const handleWindowLoaded = () => { - window.removeEventListener('load', handleWindowLoaded); - - if (GlobalAudioCtx.state === 'running') return; - window.addEventListener('pointerdown', _resumeAudio); - window.addEventListener('pointerover', _resumeAudio); - window.addEventListener('pointerleave', _resumeAudio); -}; - -GlobalAudioCtx.addEventListener('statechange', () => { - if (GlobalAudioCtx.state !== 'running') return; - - console.info('[Audio]', 'Resume audio success'); - window.removeEventListener('pointerdown', _resumeAudio); - window.removeEventListener('pointerover', _resumeAudio); - window.removeEventListener('pointerleave', _resumeAudio); -}); -window.addEventListener('load', handleWindowLoaded); diff --git a/src/Audio/Channel.ts b/src/Audio/Channel.ts deleted file mode 100644 index 54e70dd..0000000 --- a/src/Audio/Channel.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Ticker } from 'pixi.js'; -import { Audio } from './Audio'; -import AudioClip from './Clip'; - -export default class AudioChannel { - private readonly audioCtx: AudioContext; - private readonly ticker: Ticker; - readonly gain: GainNode; - readonly playlist: Array = []; - - private isTickerStarted = false; - - constructor(audio: Audio, ticker: Ticker) { - this.audioCtx = audio.audioCtx; - this.ticker = ticker; - - this.gain = this.audioCtx.createGain(); - this.gain.connect(audio.masterGain); - - this.calcTick = this.calcTick.bind(this); - } - - startTicker() { - if (this.isTickerStarted) return; - // eslint-disable-next-line @typescript-eslint/unbound-method - this.ticker.add(this.calcTick); - this.isTickerStarted = true; - } - - stopTicker() { - if (!this.isTickerStarted) return; - // eslint-disable-next-line @typescript-eslint/unbound-method - this.ticker.remove(this.calcTick); - this.isTickerStarted = false; - } - - private calcTick() { - while(this.playlist.length > 0) { - const audio = this.playlist.shift()!; - const buffer = this.audioCtx.createBufferSource(); - - buffer.buffer = audio.source; - buffer.connect(this.gain); - buffer.start(); - } - } - - get volume() { - return this.gain.gain.value; - } - - set volume(value: number) { - this.gain.gain.value = value; - } -} diff --git a/src/Audio/Clip.ts b/src/Audio/Clip.ts deleted file mode 100644 index 7c0d7d0..0000000 --- a/src/Audio/Clip.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Nullable } from '@/utils/types'; -import AudioChannel from './Channel'; -import AudioClock from './Clock'; -import Audio from './Audio'; -import { ReadFileAsAudioBuffer } from '@/utils/file'; - -export enum EAudioClipStatus { - STOP = 0, - PLAY = 1, - PAUSE = 2, -} - -export default class AudioClip { - readonly source: AudioBuffer; - - private _timeOffset: number = 0; - private _channel: Nullable = null; - private buffer?: AudioBufferSourceNode; - private readonly audioCtx: AudioContext; - readonly clock: AudioClock; - - status: EAudioClipStatus = EAudioClipStatus.STOP; - startTime: number = NaN; - pauseTime: number = NaN; - - constructor(audioCtx: AudioContext, clock: AudioClock, audioBuffer: AudioBuffer, channel: Nullable = null) { - this.source = audioBuffer; - this._channel = channel; - - this.audioCtx = audioCtx; - this.clock = clock; - } - - static from(file: Blob, channel?: AudioChannel): Promise {return new Promise((res, rej) => { - ReadFileAsAudioBuffer(file) - .then((buffer) => res(new AudioClip(Audio.audioCtx, Audio.clock, buffer, channel))) - .catch((e) => rej(e)); - })} - - play() { - if (!this._channel) throw new Error('Cannot play a clip directly without any channel'); - if (this.status === EAudioClipStatus.PLAY) return; - - this.buffer = this.audioCtx.createBufferSource(); - this.buffer.buffer = this.source; - this.buffer.connect(this._channel.gain); - - if (isNaN(this.pauseTime)) { - this.startTime = this.clock.time; - this.buffer.start(0, 0); - } else { - const pausedTime = this.pauseTime - this.startTime; - this.startTime = this.clock.time - pausedTime; - this.buffer.start(0, pausedTime); - } - - this.pauseTime = NaN; - this.status = EAudioClipStatus.PLAY; - this.buffer.onended = () => this.stop(); - } - - pause() { - if (this.status !== EAudioClipStatus.PLAY) return; - - this.disconnectBuffer(); - this.pauseTime = this.clock.time; - this.status = EAudioClipStatus.PAUSE; - } - - stop() { - if (this.status === EAudioClipStatus.STOP) return; - - this.disconnectBuffer(); - this.startTime = NaN; - this.pauseTime = NaN; - this.status = EAudioClipStatus.STOP; - } - - /** - * - * @param {number} time Seek seconds - */ - seek(time: number) { - let _time = time + this._timeOffset; - if (_time < 0) _time = 0; - - if (this.status === EAudioClipStatus.STOP) { - const currentTime = this.clock.time; - this.startTime = currentTime - _time; - this.pauseTime = currentTime; - return; - } - - const isPlayingBefore = this.status === EAudioClipStatus.PLAY; - this.pause(); - this.startTime = this.pauseTime - _time; - - if (this.startTime > this.pauseTime) this.startTime = this.pauseTime; - if (isPlayingBefore) this.play(); - } - - destroy() { - if (!this._channel) return; - this.stop(); - } - - get channel() { - return this._channel; - } - - set channel(channel: Nullable) { - this._channel = channel; - } - - get time() { - if (isNaN(this.startTime)) return -this._timeOffset; - else if (isNaN(this.pauseTime)) return this.clock.time - this.startTime - this._timeOffset; - else return this.pauseTime - this.startTime - this._timeOffset; - } - - get duration() { - return this.source.duration - this._timeOffset; - } - - get timeOffset() { - return this._timeOffset; - } - - set timeOffset(offset: number) { - this._timeOffset = offset; - } - - get speed() { - return this.buffer ? this.buffer.playbackRate.value : 1; - } - - set speed(value: number) { - if (this.buffer) this.buffer.playbackRate.value = value; - } - - private disconnectBuffer() { - if (!this.buffer) return; - - this.buffer.stop(); - this.buffer.disconnect(); - this.buffer.onended = null; - this.buffer = (void 0); - } -} diff --git a/src/Audio/Clock.ts b/src/Audio/Clock.ts deleted file mode 100644 index 202b0bc..0000000 --- a/src/Audio/Clock.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Ticker } from 'pixi.js'; -import { resumeAudioCtx } from './utils'; - -export default class AudioClock { - /** - * The current audio time. - */ - public time: number = 0; - - private offsets: number[] = []; - private sum: number = 0; - - private readonly audioCtx: AudioContext; - private readonly ticker: Ticker; - private readonly baseOffset; - - constructor(audioCtx: AudioContext, ticker: Ticker, baseOffset: number = 0) { - this.audioCtx = audioCtx; - this.ticker = ticker; - this.baseOffset = baseOffset; - - this.calcTick = this.calcTick.bind(this); - this.init().catch(() => void 0); - } - - private async init() { - this.audioCtx.addEventListener('statechange', () => { - // eslint-disable-next-line @typescript-eslint/unbound-method - if (this.audioCtx.state === 'running') this.ticker.add(this.calcTick); - }); - - await resumeAudioCtx(this.audioCtx); - this.ticker.start(); - } - - private calcTick() { - const { audioCtx, baseOffset, offsets } = this; - const realTime = performance.now() / 1000; - const delta = realTime - audioCtx.currentTime - baseOffset; - - offsets.push(delta); - this.sum += delta; - - while(offsets.length > 60) { - this.sum -= offsets.shift()!; - } - - this.time = realTime - this.sum / offsets.length; - } -} diff --git a/src/Audio/utils.ts b/src/Audio/utils.ts deleted file mode 100644 index 822f030..0000000 --- a/src/Audio/utils.ts +++ /dev/null @@ -1,33 +0,0 @@ - -/** - * Resume an AudioContext. - * @link https://github.com/bemusic/bemuse/blob/master/bemuse/src/sampling-master/index.js#L276 - * @param audioCtx - * @returns {Promise} Return false if resume failed - */ -export const resumeAudioCtx = (audioCtx: AudioContext): Promise => { - if (audioCtx.state === 'running') return new Promise((res) => res(true)); - - console.info('[Audio]', 'Try resuming audio...'); - - const gain = audioCtx.createGain(); - const osc = audioCtx.createOscillator(); - - osc.frequency.value = 440; - - osc.start(audioCtx.currentTime + 0.1); - osc.stop(audioCtx.currentTime + 0.1); - - gain.connect(audioCtx.destination); - gain.disconnect(); - - return new Promise((res) => { - audioCtx.resume() - .then(() => res(true)) - .catch((e) => { - res(false); - console.error('[Audio]', 'Failed to resume audio'); - console.error(e); - }); - }); -}; diff --git a/src/Chart/BPM.ts b/src/Chart/BPM.ts deleted file mode 100644 index d3e5aa6..0000000 --- a/src/Chart/BPM.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import { BeatArrayToNumber, parseDoublePrecist } from '@/utils/math'; -import { BeatArray } from '@/utils/types'; - -export type ChartBPMExported = { - beat: BeatArray, - bpm: number, -}; - -export default class ChartBPM { - /** Internal property */ - readonly id: string; - - beat: BeatArray; - beatNum: number; - bpm: number; - - time: number ; - timePerBeat: number; - endBeat: BeatArray; - endBeatNum: number; - endTime: number; - - constructor(beat: BeatArray, bpm: number, id = uuid()) { - if (BeatArrayToNumber(beat) < 0) throw new Error('Cannot set a negative beat to BPM!'); - if (bpm <= 0) throw new Error('Cannot set a zero/negative BPM!'); - - this.id = id; - this.beat = beat; - this.beatNum = BeatArrayToNumber(this.beat); - this.bpm = bpm; - - /** - * NOTE: These will be generated by BPMList automatically, - * please do not edit them manually! - */ - this.time = NaN; - this.timePerBeat = parseDoublePrecist(60 / this.bpm, 6, -1); - this.endBeat = [ Infinity, 0, 1 ]; - this.endBeatNum = Infinity; - this.endTime = Infinity; - } - - update() { - this.beatNum = BeatArrayToNumber(this.beat); - this.timePerBeat = parseDoublePrecist(60 / this.bpm, 6, -1); - } - - get json(): ChartBPMExported { - return { - beat: this.beat, - bpm: this.bpm, - }; - } -} diff --git a/src/Chart/BPMList.ts b/src/Chart/BPMList.ts deleted file mode 100644 index 8384a2a..0000000 --- a/src/Chart/BPMList.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { BeatArrayToNumber, parseDoublePrecist } from '@/utils/math'; -import ChartBPM, { ChartBPMExported } from './BPM'; -import { BeatArray } from '@/utils/types'; - -const BPMSortFn = (a: ChartBPM, b: ChartBPM) => BeatArrayToNumber(a.beat) - BeatArrayToNumber(b.beat); - -export default class ChartBPMList extends Array { - add(beat: BeatArray, bpm: number) { - const newBPM = new ChartBPM(beat, bpm); - this.push(newBPM); - this.calcRealTime(); - return newBPM; - } - - remove(id: string) { - if (this.length === 1) return; - - const bpmIndex = this.findIndex((e) => e.id === id); - if (bpmIndex === -1) return; - const oldBPM = this[bpmIndex]; - - this.splice(bpmIndex, 1); - this.calcRealTime(); - return oldBPM; - } - - edit(id: string, newBPM?: number, newBeat?: BeatArray) { - const bpm = this.findByID(id); - if (!bpm) return; - - if (newBPM) bpm.bpm = newBPM; - if (newBeat) bpm.beat = newBeat; - - bpm.update(); - this.calcRealTime(); - return bpm; - } - - findByID(id: string) { - const bpm = this.find((e) => e.id === id); - return bpm; - } - - findByTime(beat: BeatArray) { - const beatNum = BeatArrayToNumber(beat); - const bpm = this.find((e) => e.beatNum === beatNum); - return bpm; - } - - getRealTime(beat: BeatArray | number) { - if (typeof beat === 'number') return this.getRealTimeByBeatNum(beat); - else return this.getRealTimeByBeatNum(BeatArrayToNumber(beat)); - } - - timeToBeatNum(time: number) { - if (this.length === 1 || time < 0) return parseDoublePrecist(time / this[0].timePerBeat, 6, -1); - - for (const bpm of this) { - if (bpm.endTime <= time) continue; - if (bpm.time > time) break; - - return parseDoublePrecist(bpm.beatNum + (time - bpm.time) / bpm.timePerBeat, 6, -1); - } - - throw new Error(`Cannot found beat number for time ${time}`); - } - - private calcRealTime() { - this.sort(BPMSortFn); - if (BeatArrayToNumber(this[0].beat) !== 0) { - this[0].beat = [ 0, 0, 1 ]; - this[0].beatNum = 0; - } - - let currentTimePerBeat = this[0].timePerBeat; - let bpmChangedBeat = 0; - let bpmChangedTime = 0; - - for (let i = 0; i < this.length; i++) { - const bpm = this[i]; - const bpmNext = this[i + 1]; - - bpmChangedTime = parseDoublePrecist(bpmChangedTime + ( - currentTimePerBeat * (bpm.beatNum - bpmChangedBeat) - ), 6, -1); - bpmChangedBeat = parseDoublePrecist(bpmChangedBeat + ( - bpm.beatNum - bpmChangedBeat - ), 6, -1); - currentTimePerBeat = bpm.timePerBeat; - - bpm.time = bpmChangedTime; - bpm.endBeat = bpmNext ? bpmNext.beat : [ Infinity, 0, 1 ]; - bpm.endBeatNum = bpmNext ? BeatArrayToNumber(bpmNext.beat) : Infinity; - bpm.endTime = bpmNext ? parseDoublePrecist(bpm.time + ( - bpm.timePerBeat * (bpm.endBeatNum - bpm.beatNum) - ), 6, -1) : Infinity; - } - } - - get json(): ChartBPMExported[] { - return this.map((e) => e.json); - } - - private getRealTimeByBeatNum(beat: number) { - if (!isFinite(beat)) return beat; - if (this.length <= 0) return parseDoublePrecist(beat * 0.5, 6, -1); - if (this.length === 1 || beat < 0) return parseDoublePrecist((beat - this[0].beatNum) * this[0].timePerBeat, 6, -1); - - for (const bpm of this) { - if (bpm.endBeatNum <= beat) continue; - if (bpm.beatNum > beat) break; - - return parseDoublePrecist(bpm.time + (beat - bpm.beatNum) * bpm.timePerBeat, 6, -1); - } - - throw new Error(`Cannot found BPM for beat ${JSON.stringify(beat)}`); - } -} diff --git a/src/Chart/Chart.ts b/src/Chart/Chart.ts deleted file mode 100644 index 1c8db70..0000000 --- a/src/Chart/Chart.ts +++ /dev/null @@ -1,482 +0,0 @@ -import { Container, Sprite, Texture, Ticker } from 'pixi.js'; -import { EventEmitter } from 'eventemitter3'; -import { v4 as uuid } from 'uuid'; -import Audio from '@/Audio/Audio'; -import AudioClip, { EAudioClipStatus } from '@/Audio/Clip'; -import Database from '@/Database/Database'; -import StorageFile from '@/Storage/File'; -import ChartBPMList from './BPMList'; -import ChartJudgeline, { ChartJudgelineExported } from './Judgeline'; -import ChartNote from './Note'; -import ChartTick from './Tick'; -import { ChartBookmark, ChartInfo, ChartInfoWithFile } from './types'; -import { BeatArray, Nullable, RendererSize } from '@/utils/types'; -import { CalculateRendererSize } from '@/utils/renderer'; -import { ChartBPMExported } from './BPM'; -import ChartHistory from './History/History'; -import { TChartJudgelineProps } from './JudgelineProps'; -import { ChartKeyframeExported } from './Keyframe'; -import { TProject } from '@/Database/types'; -import { ReadFileAsText } from '@/utils/file'; -import { TStorageFile } from '@/Storage/types'; - -type $ChartInfo = ChartInfoWithFile & { - id: string, -}; - -export type ChartExported = { - info: ChartInfo, - offset: number, - bpm: ChartBPMExported[], - lines: ChartJudgelineExported[], -}; - -class Chart { - // Chart data - bpm: ChartBPMList = new ChartBPMList(); - lines: ChartJudgeline[] = []; - notes: ChartNote[] = []; - - _offset: number = 0; - offsetBeat: number = 0; - - bookmarks: ChartBookmark[] = []; - histories: ChartHistory = new ChartHistory(this); - - rendererSize = CalculateRendererSize(1, 1); - readonly container = new Container(); - - readonly events = new EventEmitter(); - readonly ticker: Ticker; - readonly tick = ChartTick.bind(this); - - // Resources - private _info: Nullable<$ChartInfo> = null; - private _id: Nullable = null; - private _music: Nullable = null; - private _background: Nullable = null; - - constructor() { - // Init ticker - this.ticker = new Ticker(); - this.ticker.stop(); - this.ticker.add(this.tick); - } - - create(infoWithFile: ChartInfoWithFile) { - if (this._info !== null) return; - - this._info = { - ...infoWithFile, - id: uuid(), - }; - this._id = null; - - this.addBPM([ 0, 0, 1 ], 120, false, false); - this.addLine(true, false); - - this.loadFiles(); - this.events.emit('loaded', this); - } - - load(infoWithFile: ChartInfoWithFile, chartJson: ChartExported, projectID = uuid(), chartID = uuid()) { - if (this._info !== null) return; - - const addKeyframesToLine = (line: ChartJudgeline, type: keyof TChartJudgelineProps, keyframes: ChartKeyframeExported[]) => { - for (const keyframe of keyframes) { - line.addKeyframe(type, keyframe.beat, keyframe.value, keyframe.continuous, keyframe.easing); - } - }; - - this._info = { - ...infoWithFile, - id: projectID, - }; - this._id = chartID; - this._offset = chartJson.offset / 1000; - - for (const bpm of chartJson.bpm) { - this.addBPM(bpm.beat, bpm.bpm, false, false); - } - - for (const line of chartJson.lines) { - const newLine = this.addLine(false, false)!; - - for (const _name in line.props) { - const name = _name as keyof TChartJudgelineProps; - addKeyframesToLine(newLine, name, line.props[name]); - newLine.updateProp(name, true); - } - - newLine.calcFloorPositions(); - - for (const note of line.notes) { - newLine.addNote({ - ...note - }); - } - } - - this.loadFiles(); - this.events.emit('loaded', this); - } - - saveToDatabase(): Promise> {return new Promise(async (res) => { - if (!this._info) return res(null); - - const oldProject = await Database.project.get(this._info.id); - if (oldProject) { - const projectInfo: ChartInfo & Partial<$ChartInfo> & Partial = { - ...oldProject, - ...this._info, - }; - - delete projectInfo.music; - delete projectInfo.background; - - await StorageFile.update(this._id!, new Blob([ JSON.stringify(this.json) ])); - await Database.project.update(projectInfo.id!, projectInfo as TProject); - - return res(projectInfo as TProject); - } else { - const { id: musicID } = await StorageFile.add(this._info.music); - const { id: backgroundID } = await StorageFile.add(this._info.background); - const { id: chartID } = await StorageFile.add(new Blob([ JSON.stringify(this.json) ])); - - this._id = chartID; - - const projectInfo: ChartInfo & Partial<$ChartInfo> & Partial = { ...this._info }; - - delete projectInfo.music; - delete projectInfo.background; - - projectInfo.chartID = chartID; - projectInfo.musicID = musicID; - projectInfo.backgroundID = backgroundID; - projectInfo.filesID = uuid(); // TODO - - await Database.project.add(projectInfo as TProject); - return res(projectInfo as TProject); - } - })} - - clear() { - if (!this._info) return; - - this.ticker.stop(); - - this._music?.destroy(); - this._background?.destroy(); - - this._music = null; - this._background = null; - this._info = null; - this._id = null; - - this.bpm.length = 0; - this.lines.length = 0; - - this.events.emit('clear', null); - } - - loadFromDatabase(projectID: string) {return new Promise(async (res, rej) => { - if (this._info !== null) return res(null); - - const projectMeta = await Database.project.get(projectID); - if (!projectMeta) return rej(`No project found: ${projectID}`); - - const musicFile = await StorageFile.get(projectMeta.musicID) as Nullable; - if (!musicFile) return rej(`No file found: ${projectMeta.musicID}`); - - const backgroundFile = await StorageFile.get(projectMeta.backgroundID) as Nullable; - if (!backgroundFile) return rej(`No file found: ${projectMeta.backgroundID}`); - - const chartFile = await StorageFile.get(projectMeta.chartID) as Nullable; - if (!chartFile) return rej(`No file found: ${projectMeta.chartID}`); - - // TODO: Extra files - - const chartText = await ReadFileAsText(chartFile.blob); - const chartJson = JSON.parse(chartText) as ChartExported; - - this.load({ - name: projectMeta.name, - artist: projectMeta.artist, - illustration: projectMeta.illustration, - level: projectMeta.level, - designer: projectMeta.designer, - music: musicFile.blob, - background: backgroundFile.blob, - }, chartJson, projectMeta.id, projectMeta.chartID); - res(this); - })} - - async play() { - if (!this._info) return; - await this.waitAudio(); - this._music!.play(); - } - - async pause() { - if (!this._info) return; - await this.waitAudio(); - this._music!.pause(); - } - - async playOrPause() { - if (!this._info) return; - await this.waitAudio(); - if (this._music!.status === EAudioClipStatus.PLAY) this._music!.pause(); - else this._music!.play(); - } - - async stop() { - if (!this._info) return; - await this.waitAudio(); - this._music!.stop(); - } - - addLine(addDefaultKeyframes = true, addToHistory = true) { - if (!this._info) return; - const newLine = new ChartJudgeline(this, addDefaultKeyframes); - this.lines.push(newLine); - this.container.addChild(newLine.sprite); - if (addToHistory) this.histories.add({ - name: 'line', - action: 'add', - id: newLine.id, - after: newLine.json, - }); - - this.events.emit('lines.added', newLine); - this.events.emit('lines.updated', this.lines); - - return newLine; - } - - removeLine(id: string, emit = true, addToHistory = true) { - if (!this._info) return; - const lineIndex = this.lines.findIndex((e) => e.id === id); - if (lineIndex === -1) return; - - const line = this.lines[lineIndex]; - line.destroy(); - this.lines.splice(lineIndex, 1); - if (addToHistory) this.histories.add({ - name: 'line', - action: 'delete', - id: line.id, - before: line.json, - }); - - if (emit) { - this.events.emit('lines.removed', line); - this.events.emit('lines.updated', [ ...this.lines ]); - } - } - - addBPM(time: BeatArray, bpm: number, emit = true, addToHistory = true) { - if (!this._info) return; - const newBPM = this.bpm.add(time, bpm); - if (addToHistory) this.histories.add({ - name: 'bpm', - action: 'add', - id: newBPM.id, - after: newBPM.json, - }); - - this.updateLinesTime(); - this.updateOffsetBeat(); - if (emit) this.events.emit('bpms.updated', [ ...this.bpm ]); - } - - editBPM(id: string, newBeat?: BeatArray, newBPM?: number, emit = true) { - if (!this._info) return; - this.bpm.edit(id, newBPM, newBeat); - this.updateLinesTime(); - this.updateOffsetBeat(); - if (emit) this.events.emit('bpms.updated', [ ...this.bpm ]); - } - - removeBPM(id: string, emit = true, addToHistory = true) { - if (!this._info) return; - const oldBPM = this.bpm.remove(id); - if (!oldBPM) return; - if (addToHistory) this.histories.add({ - name: 'bpm', - action: 'delete', - id: oldBPM.id, - before: oldBPM.json, - }) - - this.updateLinesTime(); - this.updateOffsetBeat(); - if (emit) this.events.emit('bpms.updated', [ ...this.bpm ]); - } - - resize(size: RendererSize) { - this.rendererSize = size; - - this.container.position.set( - size.widthRealHalf, - size.heightHalf - ); - - for (const line of this.lines) { - line.resize(size); - } - } - - updateOffsetBeat() { - if (!this._info) return; - if (this.bpm.length <= 0) return; - - const offsetBeat = this.bpm.timeToBeatNum(this._offset); - if (this.offsetBeat === offsetBeat) return; - - this.offsetBeat = offsetBeat; - this.events.emit('offset.updated', { offset: this.offset, offsetBeat: offsetBeat }); - } - - get info(): Nullable { - if (!this._info) return null; - - const chartInfo: Partial & ChartInfo = { ...this._info }; - - delete chartInfo.music; - delete chartInfo.background; - - return chartInfo; - } - - get files() { - if (!this._info) return null; - - return { - music: this._info.music, - background: this._info.background, - chart: new Blob([ JSON.stringify(this.json) ]), - }; - } - - get json(): Nullable { - if (this._info === null) return null; - - return { - info: this.info!, - offset: this.offset, - bpm: this.bpm.json, - lines: this.lines.map((e) => e.json), - }; - } - - get status() { - if (!this._info) return EAudioClipStatus.STOP; - return this._music ? this._music.status : EAudioClipStatus.STOP; - } - - get time() { - if (!this._info) return 0; - return this._music ? this._music.time : 0; - } - - set time(time: number) { - if (!this._info) return; - this.waitAudio() - .then(() => this._music!.seek(time)) - .catch(() => void 0); - } - - get duration() { - if (!this._info) return 0; - return this._music ? this._music.duration : 0; - } - - get beatNum() { - if (!this._info) return 0; - return this.bpm.timeToBeatNum(this.time); - } - - set beatNum(beatNum: number) { - if (!this._info) return; - this.waitAudio() - .then(() => this._music!.seek(this.bpm.getRealTime(beatNum))) - .catch(() => void 0); - } - - get beatDuration() { - if (!this._info) return 0; - return this.bpm.timeToBeatNum(this.duration); - } - - get offset() { - if (!this._info) return 0; - return this._offset * 1000; - } - - set offset(offset: number) { - const _offset = offset / 1000; - this._offset = _offset; - - this.updateOffsetBeat(); - this.waitAudio() - .then((clip) => { - clip.timeOffset = _offset; - }) - .catch(() => void 0); - } - - private loadFiles() { - if (!this._info) return; - - AudioClip.from(this._info.music, Audio.channels.music) - .then((clip) => { - clip.timeOffset = this._offset; - this._music = clip; - - this.updateOffsetBeat(); - this.ticker.start(); - this.events.emit('music.loaded', this._music); - }) - .catch((e) => { throw e }); - - createImageBitmap(this._info.background) - .then((bitmap) => { - this._background = Sprite.from(Texture.from(bitmap)); - this._background.anchor.set(0.5); - - this.events.emit('background.loaded', this._background); - }) - .catch((e) => { throw e }); - } - - private waitAudio(): Promise {return new Promise((res) => { - if (this._music) return res(this._music); - const clockId = setInterval(() => { - if (!this._music) return; - res(this._music); - clearInterval(clockId); - }, 200); - })} - - private updateLinesTime() { - for (const line of this.lines) { - line.updateProp('speed', true); - line.updateProp('positionX', true); - line.updateProp('positionY', true); - line.updateProp('rotate', true); - line.updateProp('alpha', true); - - line.calcFloorPositions(); - for (const note of line.notes) { - line.calcNoteTime(note); - } - } - } -} - -const chart = new Chart(); -console.log(chart); - -export default chart; -export { Chart }; diff --git a/src/Chart/History/History.ts b/src/Chart/History/History.ts deleted file mode 100644 index 6af3e73..0000000 --- a/src/Chart/History/History.ts +++ /dev/null @@ -1,25 +0,0 @@ -import Chart from '../Chart'; -import { HistoryType } from './types'; - -class ChartHistory extends Array { - readonly chart: Chart; - currentIndex = 0; - - constructor(chart: Chart, histories?: HistoryType[]) { - super(); - - this.chart = chart; - if (histories) this.push(...histories); - } - - add(action: HistoryType) { - this.unshift(action); - this.currentIndex = 0; - // TODO: History count in settings - if (this.length > 20) this.length = 20; - } - - -} - -export default ChartHistory; diff --git a/src/Chart/History/types.ts b/src/Chart/History/types.ts deleted file mode 100644 index ddaff85..0000000 --- a/src/Chart/History/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ChartBPMExported } from "../BPM"; -import { ChartJudgelineExported } from "../Judgeline"; -import { TChartJudgelineProps } from "../JudgelineProps"; -import { ChartKeyframeExported } from "../Keyframe"; -import { ChartNoteExported } from "../Note"; - - -export type HistoryBaseAdd = { - id: string, - action: 'add', - after: Partial, -}; - -export type HistoryBaseEdit = { - id: string, - action: 'edit', - before: Partial, - after: Partial, -}; - -export type HistoryBaseDelete = { - id: string, - action: 'delete', - before: Partial, -}; - -export type HistoryBase = HistoryBaseAdd | HistoryBaseEdit | HistoryBaseDelete; - - -export type HistoryBPM = HistoryBase & { - name: 'bpm', -}; - -export type HistoryLine = HistoryBase & { - name: 'line', -}; - -export type HistoryKeyframe = HistoryBase & { - name: 'keyframe', - type: keyof TChartJudgelineProps, - lineID: string, -}; - -export type HistoryNote = HistoryBase & { - name: 'note', - lineID: string, -}; - -export type HistoryType = HistoryBPM | HistoryLine | HistoryKeyframe | HistoryNote; diff --git a/src/Chart/Judgeline.ts b/src/Chart/Judgeline.ts deleted file mode 100644 index 0525b72..0000000 --- a/src/Chart/Judgeline.ts +++ /dev/null @@ -1,388 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import { EventEmitter } from 'eventemitter3'; -import { BeatArray, RendererSize } from '@/utils/types'; -import JudgelineProps, { ChartJudgelinePropsExported, TChartJudgelineProps } from './JudgelineProps'; -import ChartKeyframe, { TChartKeyframe } from './Keyframe'; -import Note, { ChartNoteExported, ChartNoteProps } from './Note'; -import { BeatArrayToNumber, parseDoublePrecist } from '@/utils/math'; -import { getLinePropValue } from '@/utils/chart'; -import { Container, Sprite, Texture } from 'pixi.js'; -import Chart from './Chart'; -import { FloorPosition, NoteType } from './types'; - -const PropsSortFn = (a: ChartKeyframe, b: ChartKeyframe) => a.beatNum - b.beatNum; - -export type ChartJudgelineExported = { - props: ChartJudgelinePropsExported, - notes: ChartNoteExported[], -}; - -export default class ChartJudgeline { - /** Internal property */ - readonly id: string; - readonly chart: Chart; - - props: JudgelineProps; - floorPositions: FloorPosition[] = []; - notes: Note[] = []; - - // Used for live preview - _speed: number = 1; - _posX: number = 0; - _posY: number = 0; - _alpha: number = 1; - _rotate: number = 0; - - _fPos: number = 0; - _realPosX: number = 0; - _realPosY: number = 0; - _radian: number = 0; - _cosr: number = 0; - _sinr: number = 0; - - readonly events: EventEmitter = new EventEmitter(); - sprite!: Sprite; - - constructor(chart: Chart, addDefaultKeyframes = true, id = uuid()) { - this.id = id; - this.chart = chart; - - this.props = new JudgelineProps(addDefaultKeyframes); - - if (addDefaultKeyframes) { - this.updateProp('speed', true); - this.updateProp('positionX', true); - this.updateProp('positionY', true); - this.updateProp('rotate', true); - this.updateProp('alpha', true); - - this.calcFloorPositions(); - } - - this.createSprite(); - } - - addKeyframe( - type: keyof TChartJudgelineProps, - beat: BeatArray, - value: number, - continuous: boolean, - easing: number - ) { - const keyframeArr = this.props[type]; - if (!keyframeArr || !(keyframeArr instanceof Array)) throw new Error(`No such type: ${type}`); - if (this.findKeyframeByBeat(type, beat)) return; - - const newKeyframe = new ChartKeyframe(type, beat, value, continuous, easing); - keyframeArr.push(newKeyframe); - - this.updateProp(type); - if (type === 'speed') { - this.calcFloorPositions(); - this.updateNotesFloorPosition(); - } - - this.events.emit('props.updated', { type, keyframes: [ ...keyframeArr ] }); - } - - editKeyframe( - type: keyof TChartJudgelineProps, - id: string, - newProps: Partial = {} - ) { - const keyframeArr = this.props[type]; - if (!keyframeArr || !(keyframeArr instanceof Array)) throw new Error(`No such type: ${type}`); - - const keyframe = this.findKeyframeById(type, id); - if (!keyframe) throw new Error(`Cannot find keyframe ID: "${id}" for props ${type}`); - if (newProps.beat && this.findKeyframeByBeat(type, newProps.beat)) return; - - const prevKeyframe = keyframeArr.find((e) => e.nextKeyframe && e.nextKeyframe.id === keyframe.id); - if (prevKeyframe) prevKeyframe.nextKeyframe = null; - - for (const name in newProps) { - // XXX: This sucks - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - keyframe[name] = newProps[name]; - } - - keyframe.beatNum = BeatArrayToNumber(keyframe.beat); - keyframe.time = this.chart.bpm.getRealTime(keyframe.beat); - - this.updateProp(type); - if (type === 'speed') { - this.calcFloorPositions(); - this.updateNotesFloorPosition(); - } - - this.events.emit('props.updated', { type, keyframes: [ ...keyframeArr ] }); - } - - deleteKeyframe(type: keyof TChartJudgelineProps, id: string) { - const keyframeArr = this.props[type]; - if (!keyframeArr || !(keyframeArr instanceof Array)) throw new Error(`No such type: ${type}`); - if (keyframeArr.length === 1) return; - - const keyframeIndex = keyframeArr.findIndex((e) => e.id === id); - if (keyframeIndex === -1) throw new Error(`Cannot find keyframe ID: "${id}" for props ${type}`); - - const prevKeyframe = keyframeArr.find((e) => e.nextKeyframe && e.nextKeyframe.id === id); - if (prevKeyframe) prevKeyframe.nextKeyframe = null; - - // const keyframe = keyframeArr[keyframeIndex]; - keyframeArr.splice(keyframeIndex, 1); - - this.updateProp(type); - if (type === 'speed') { - this.calcFloorPositions(); - this.updateNotesFloorPosition(); - } - - this.events.emit('props.updated', { type, keyframes: [ ...keyframeArr ] }); - } - - findKeyframeByBeat(type: keyof TChartJudgelineProps, beat: BeatArray) { - const keyframeArr = this.props[type]; - if (!keyframeArr || !(keyframeArr instanceof Array)) throw new Error(`No such type: ${type}`); - - const beatNum = BeatArrayToNumber(beat); - return keyframeArr.find((e) => e.beatNum === beatNum); - } - - findKeyframeById(type: keyof TChartJudgelineProps, id: string) { - const keyframeArr = this.props[type]; - if (!keyframeArr || !(keyframeArr instanceof Array)) throw new Error(`No such type: ${type}`); - - return keyframeArr.find((e) => e.id === id); - } - - addNote(props: Omit, id = uuid()) { - const note = this.calcNoteTime(new Note({ - ...props, - line: this, - }, id)); - this.notes.push(note); - this.sortNotes(); - - note.resize(this.chart.rendererSize); - this.sprite.parent.addChild(note.sprite!); - - this.events.emit('note.added', note); - this.events.emit('notes.updated', [ ...this.notes ]); - - return note; - } - - editNote(id: string, newProps: Partial>) { - const note = this.findNoteById(id); - if (!note) throw new Error(`Cannot find note ID: "${id}" for line "${this.id}"`); - - for (const name in newProps) { - // XXX: This sucks - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - note[name] = newProps[name]; - } - - note.beatNum = BeatArrayToNumber(note.beat); - note.updateHoldProps(); - this.calcNoteTime(note); - this.sortNotes(); - - if (newProps['type'] !== (void 0)) note.createSprite(this.sprite.parent); - note.resize(this.chart.rendererSize); - - this.events.emit('notes.updated', [ ...this.notes ]); - } - - deleteNote(id: string) { - const noteIndex = this.notes.findIndex((e) => e.id === id); - if (noteIndex === -1) throw new Error(`Cannot find note ID: "${id}" for line "${this.id}"`); - - const note = this.notes[noteIndex]; - this.notes.splice(noteIndex, 1); - note.sprite!.removeFromParent(); - note.sprite!.destroy(); - - this.events.emit('note.removed', note); - this.events.emit('notes.updated', [ ...this.notes ]); - } - - findNoteById(id: string) { - return this.notes.find((e) => e.id === id); - } - - getFloorPosition(time: number) { - const getFloorPosition = (time: number) => { - for (const event of this.floorPositions) { - if (event.endTime <= time) continue; - if (event.time > time) break; - - return event; - } - - return { - time, - endTime: Infinity, - value: time, - }; - }; - - const speed = getLinePropValue(time, this.props.speed, 1); - const floorPosition = getFloorPosition(time); - - return parseDoublePrecist(floorPosition.value + (speed * (time - floorPosition.time)), 3, 1); - } - - createSprite(container?: Container) { - this.sprite = new Sprite(Texture.WHITE); - - this.sprite.width = 1920; - this.sprite.height = 3; - this.sprite.anchor.set(0.5); - - if (container) container.addChild(this.sprite); - return this.sprite; - } - - resize(size: RendererSize) { - const scaleX = Math.round((4000 / 1920) * (size.width / 1350) * 1920); - const scaleY = Math.round(size.lineScale * 18.75 * 0.008); - - this.sprite.scale.set(scaleX, scaleY); - - for (const note of this.notes) { - note.resize(size); - } - } - - updateProp(type: keyof TChartJudgelineProps, forceUpdateTime = false) { - this.props[type].sort(PropsSortFn); - this.props[type] = this.calcPropTime(this.props[type], forceUpdateTime); - return this.props[type]; - } - - destroy() { - this.sprite.removeFromParent(); - this.sprite.destroy(); - - for (const note of this.notes) { - if (!note.sprite) continue; - note.sprite.removeFromParent(); - note.sprite.destroy(); - } - } - - get json(): ChartJudgelineExported { - return { - props: this.props.json, - notes: this.notes.map((e) => e.json), - }; - } - - private calcPropTime(keyframes: ChartKeyframe[], forceUpdateTime = false) { - for (let i = 0; i < keyframes.length; i++) { - const keyframe = keyframes[i]; - const keyframeNext = keyframes[i + 1]; - - if (isNaN(keyframe.time) || forceUpdateTime) keyframe.time = this.chart.bpm.getRealTime(keyframe.beat); - - if (!keyframeNext) continue; - if (keyframeNext.continuous) { - keyframe.nextKeyframe = keyframeNext - } else { - keyframe.nextKeyframe = null; - } - } - - return keyframes; - } - - private sortNotes() { - this.notes.sort((a, b) => BeatArrayToNumber(a.beat) - BeatArrayToNumber(b.beat)); - } - - calcNoteTime(note: Note) { - note.time = this.chart.bpm.getRealTime(note.beat); - note.holdEndTime = this.chart.bpm.getRealTime(note.holdEndBeat); - this.updateNoteFloorPosition(note); - - return note; - } - - calcFloorPositions() { - this.floorPositions.length = 0; - - for (const keyframe of this.props.speed) { - const { nextKeyframe } = keyframe; - if (!nextKeyframe || nextKeyframe.value === keyframe.value) { - this.floorPositions.push({ - time: keyframe.time, - endTime: NaN, - value: NaN, - }); - continue; - } - - const beatBetween = nextKeyframe.beatNum - keyframe.beatNum; - for (let i = 0, count = Math.ceil(beatBetween / 0.125); i < count; i++) { - const beatPercent = i / count; - const beatPercentNext = i < count - 1 ? (i + 1) / count : 1; - const currentTime = keyframe.time * (1 - beatPercent) + nextKeyframe.time * beatPercent; - const nextTime = keyframe.time * (1 - beatPercentNext) + nextKeyframe.time * beatPercentNext; - - this.floorPositions.push({ - time: currentTime, - endTime: nextTime, - value: NaN, - }); - } - - this.floorPositions.push({ - time: nextKeyframe.time, - endTime: NaN, - value: NaN, - }); - } - - this.floorPositions.sort((a, b) => a.time - b.time); - if (this.floorPositions[0].time !== 0) { - this.floorPositions.unshift({ - time: 0, - endTime: this.floorPositions[0].time, - value: 0, - }); - } - - let currentFloorPosition = 0; - for (let i = 0; i < this.floorPositions.length; i++) { - const event = this.floorPositions[i]; - const eventNext = this.floorPositions[i + 1]; - - event.value = currentFloorPosition; - event.endTime = eventNext ? eventNext.time : Infinity; - - if (eventNext) currentFloorPosition = parseDoublePrecist(currentFloorPosition + - (eventNext.time - event.time) * getLinePropValue(event.time, this.props.speed, 1) - , 3, -1); - } - } - - private updateNoteFloorPosition(note: Note) { - note.floorPosition = this.getFloorPosition(note.time); - if (note.type === NoteType.HOLD) note.holdEndPosition = this.getFloorPosition(note.holdEndTime); - else note.holdEndPosition = note.floorPosition; - - note.updateHoldProps(); - } - - updateNotesFloorPosition() { - for (const note of this.notes) { - this.updateNoteFloorPosition(note); - note.resize(this.chart.rendererSize); - } - } -} diff --git a/src/Chart/JudgelineProps.ts b/src/Chart/JudgelineProps.ts deleted file mode 100644 index c040fe0..0000000 --- a/src/Chart/JudgelineProps.ts +++ /dev/null @@ -1,61 +0,0 @@ -import ChartKeyframe, { ChartKeyframeExported } from './Keyframe'; - -export type TChartJudgelineProps = { - speed: ChartKeyframe[], - positionX: ChartKeyframe[], - positionY: ChartKeyframe[], - alpha: ChartKeyframe[], - rotate: ChartKeyframe[], -}; - -export type ChartJudgelinePropsExported = { - speed: ChartKeyframeExported[], - positionX: ChartKeyframeExported[], - positionY: ChartKeyframeExported[], - rotate: ChartKeyframeExported[], - alpha: ChartKeyframeExported[], -}; - -export default class ChartJudgelineProps implements TChartJudgelineProps { - speed: ChartKeyframe[] = []; - /** - * Center: half of the screen width - * Unit: Percent - * Value: half of the screen width - */ - positionX: ChartKeyframe[] = []; - /** - * Center: half of the screen height - * Unit: Percent - * Value: half of the screen height - */ - positionY: ChartKeyframe[] = []; - /** - * Range: 0-255 - */ - alpha: ChartKeyframe[] = []; - /** - * Unit: Angle - */ - rotate: ChartKeyframe[] = []; - - constructor(addDefaultKeyframes = true) { - if (addDefaultKeyframes) { - this.speed.push(new ChartKeyframe('speed', [ 0, 0, 1 ], 1, false, 0)); - this.positionX.push(new ChartKeyframe('positionX', [ 0, 0, 1 ], 0, false, 0)); - this.positionY.push(new ChartKeyframe('positionY', [ 0, 0, 1 ], 0, false, 0)); - this.alpha.push(new ChartKeyframe('alpha', [ 0, 0, 1 ], 255, false, 0)); - this.rotate.push(new ChartKeyframe('rotate', [ 0, 0, 1 ], 0, false, 0)); - } - } - - get json(): ChartJudgelinePropsExported { - return { - speed: this.speed.map((e) => e.json), - positionX: this.positionX.map((e) => e.json), - positionY: this.positionY.map((e) => e.json), - rotate: this.rotate.map((e) => e.json), - alpha: this.alpha.map((e) => e.json), - }; - } -} diff --git a/src/Chart/Keyframe.ts b/src/Chart/Keyframe.ts deleted file mode 100644 index e7a75eb..0000000 --- a/src/Chart/Keyframe.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import { BeatArrayToNumber } from '@/utils/math'; -import { BeatArray, Nullable } from '@/utils/types'; -import ChartJudgelineProps from './JudgelineProps'; - -export type TChartKeyframe = { - beat: BeatArray; - beatNum: number; - value: number; - continuous: boolean; - easing: number; -}; - -export type ChartKeyframeExported = Omit; - -export default class ChartKeyframe implements TChartKeyframe { - /** Internal property */ - readonly id: string; - readonly type: keyof ChartJudgelineProps; - - beat: BeatArray; - beatNum: number; - value: number; - continuous: boolean; - easing: number; - - time: number; - nextKeyframe: Nullable = null; - - constructor( - type: keyof ChartJudgelineProps, - beat: BeatArray, - value: number, - continuous: boolean, - easing: number, - id = uuid() - ) { - if (BeatArrayToNumber(beat) < 0) throw new Error('Cannot set a negative beat to keyframe!'); - this.type = type; - this.id = id; - - this.beat = beat; - this.beatNum = BeatArrayToNumber(this.beat); - this.value = value; - this.continuous = continuous; - this.easing = easing; - - // TODO: Auto-generating these - this.time = NaN; - } - - get json(): ChartKeyframeExported { - return { - beat: this.beat, - value: this.value, - continuous: this.continuous, - easing: this.easing, - }; - } -} diff --git a/src/Chart/Note.ts b/src/Chart/Note.ts deleted file mode 100644 index a01c902..0000000 --- a/src/Chart/Note.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import ChartJudgeline from './Judgeline'; -import { BeatArrayToNumber } from '@/utils/math'; -import { NoteType } from './types'; -import { BeatArray, RendererSize } from '@/utils/types'; -import { Sprite, Container, Texture } from 'pixi.js'; - -const getNoteTexture = (type: NoteType) => { - if (type === NoteType.DRAG) return 'note-drag'; - else if (type === NoteType.FLICK) return 'note-flick'; - else return 'note-tap'; -}; - -export type ChartNoteProps = { - line: ChartJudgeline, - type: NoteType, - beat: BeatArray, - positionX: number, - speed: number, - isAbove: boolean, - - holdEndBeat?: BeatArray -}; - -export type ChartNotePropsNoLine = Omit; - -export type ChartNoteExported = ChartNotePropsNoLine & { holdEndBeat: BeatArray }; - -export default class ChartNote { - /** Internal property */ - readonly id: string; - - line: ChartJudgeline; - type: NoteType; - beat: BeatArray; - positionX: number; - speed: number; - isAbove: boolean; - - holdEndBeat: BeatArray; - - beatNum: number; - time: number; - holdEndBeatNum!: number; - holdEndTime: number; - holdLengthBeatNum!: number; - holdLengthTime!: number; - - floorPosition: number; - holdLength!: number; - holdEndPosition!: number; - - sprite?: Sprite | Container; - - _realLinePosX: number = 0; - _realLinePosY: number = 0; - _realPosX: number = 0; - _realPosY: number = 0; - - constructor({ - line, - type, - beat, - positionX, - speed, - isAbove, - - holdEndBeat - }: ChartNoteProps, id = uuid()) { - this.id = id; - - this.line = line; - this.type = type; - this.beat = beat; - this.positionX = positionX; - this.speed = speed; - this.isAbove = isAbove; - - this.beatNum = BeatArrayToNumber(this.beat); - this.holdEndBeat = this.type === NoteType.HOLD && holdEndBeat ? holdEndBeat : this.beat; - - // TODO: Auto-generating these - this.time = 0; - this.holdEndTime = 0; - this.floorPosition = 0; - this.holdEndPosition = 0; - - this.updateHoldProps(); - this.createSprite(); - } - - updateHoldProps() { - this.holdEndBeatNum = this.type === NoteType.HOLD ? BeatArrayToNumber(this.holdEndBeat) : this.beatNum; - if (this.holdEndBeatNum < this.beatNum) { - this.holdEndBeat = this.beat; - this.holdEndBeatNum = this.beatNum; - } - - this.holdLengthBeatNum = this.type === NoteType.HOLD ? this.holdEndBeatNum - this.beatNum : 0; - this.holdLengthTime = this.type === NoteType.HOLD ? this.holdEndTime - this.time : 0; - this.holdLength = this.type === NoteType.HOLD ? this.holdEndPosition - this.floorPosition : 0; - } - - createSprite(container?: Container) { - if (this.sprite) { - if (this.sprite.parent) this.sprite.parent.removeChild(this.sprite); - this.sprite.destroy(); - this.sprite = (void 0); - } - - if (this.type !== NoteType.HOLD) this.sprite = this.createSpriteNonHold(); - else this.sprite = this.createSpriteHold(); - - if (container) container.addChild(this.sprite); - return this.sprite; - } - - resize(size: RendererSize) { - if (!this.sprite) return; - - if (this.type === NoteType.HOLD) { - const holdLength = this.holdLength * this.speed * size.noteSpeed / size.noteScale; - this.sprite.children[1].height = holdLength; - this.sprite.children[2].position.y = -holdLength; - } - - this.sprite.scale.set(size.noteScale); - } - - get json(): ChartNoteExported { - return { - type: this.type, - beat: this.beat, - positionX: this.positionX, - speed: this.speed, - isAbove: this.isAbove, - holdEndBeat: this.holdEndBeat || this.beat, - }; - } - - private createSpriteNonHold() { - const sprite = new Sprite(Texture.from(getNoteTexture(this.type))); - sprite.anchor.set(0.5); - return sprite; - } - - private createSpriteHold() { - const container = new Container(); - const holdHead = new Sprite(Texture.from('note-hold-head')); - const holdBody = new Sprite(Texture.from('note-hold-body')); - const holdEnd = new Sprite(Texture.from('note-hold-end')); - - holdHead.anchor.set(0.5, 0); - holdBody.anchor.set(0.5, 1); - holdEnd.anchor.set(0.5, 1); - - container.addChild( - holdHead, - holdBody, - holdEnd - ); - return container; - } -} diff --git a/src/Chart/Tick.ts b/src/Chart/Tick.ts deleted file mode 100644 index ddd0628..0000000 --- a/src/Chart/Tick.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { Chart } from './Chart'; -import { getLinePropValue } from '@/utils/chart'; -import { NoteType } from './types'; - -function ChartTick(this: Chart) { - const { - rendererSize, - lines, - time, - container, - } = this; - - const { - widthHalf, - heightHalf, - noteSpeed, - noteScale, - } = rendererSize; - for (const line of lines) { - const { props, floorPositions, sprite, notes } = line; - - line._speed = getLinePropValue(time, props.speed, line._speed); - line._posX = getLinePropValue(time, props.positionX, line._posX); - line._posY = getLinePropValue(time, props.positionY, line._posY); - line._rotate = getLinePropValue(time, props.rotate, line._rotate); - line._alpha = getLinePropValue(time, props.alpha, line._alpha); - - for (let i = 0, l = floorPositions.length; i < l; i++) { - const event = floorPositions[i]; - - if (event.endTime < time) continue; - if (event.time > time) break; - - line._fPos = event.value + (time - event.time) * line._speed; - } - - line._realPosX = line._posX / 100 * widthHalf; - line._realPosY = line._posY / -100 * heightHalf; - - line._radian = line._rotate * (Math.PI / 180); - line._cosr = Math.cos(line._radian); - line._sinr = Math.sin(line._radian); - - sprite.position.set(line._realPosX, line._realPosY); - sprite.angle = line._rotate; - sprite.alpha = line._alpha / 255; - - for (const note of notes) { - const { - type, - time: noteTime, - holdEndTime, - positionX, - speed, - isAbove, - floorPosition, - holdLength, - holdEndPosition, - sprite, - } = note; - if (!sprite) continue; - - if ( - (noteTime <= time) && - (type !== NoteType.HOLD || holdEndTime <= time) - ) { - if (sprite.parent) sprite.removeFromParent(); - continue; - } - - const floorPositionDiff = (floorPosition - line._fPos) * speed; - if ( - floorPositionDiff > 2 || - (floorPositionDiff < 0 && noteTime > time) - ) { - if (sprite.parent) sprite.removeFromParent(); - continue; - } - - const posX = widthHalf * positionX / 100; - const posY = floorPositionDiff * noteSpeed * (isAbove ? -1 : 1); - const realXSin = posY * line._sinr * -1; - const realYCos = posY * line._cosr; - - note._realLinePosX = posX * line._cosr + line._realPosX; - note._realLinePosY = posX * line._sinr + line._realPosY; - - note._realPosX = note._realLinePosX + realXSin; - note._realPosY = note._realLinePosY + realYCos; - - if (type === NoteType.HOLD) { - const [ spriteHead, spriteBody, spriteEnd ] = sprite.children; - let realHoldLength = holdLength * speed * noteSpeed / noteScale; - - spriteHead.visible = true; - if (noteTime <= time) { - realHoldLength = (holdEndPosition - line._fPos) * speed * noteSpeed / noteScale; - - note._realPosX -= realXSin; - note._realPosY -= realYCos; - - spriteHead.visible = false; - } - - spriteBody.height = realHoldLength; - spriteEnd.position.y = -realHoldLength; - } - - sprite.position.set(note._realPosX, note._realPosY); - sprite.angle = line._rotate + (isAbove ? 0 : 180); - if (!sprite.parent) container.addChild(sprite); - } - } -} - -export default ChartTick; diff --git a/src/Chart/types.ts b/src/Chart/types.ts deleted file mode 100644 index 62f13bd..0000000 --- a/src/Chart/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Nullable } from '@/utils/types'; - -export type ChartInfo = { - name: string, - artist: string, - illustration: string, - level: string, - designer: string, -}; - -export type ChartInfoWithFile = ChartInfo & { - music: Blob, - background: Blob, -}; - -export type ChartBookmark = { - id: string; - beatNum: number; - label: string; - color: Nullable; -}; - -export enum NoteType { - TAP = 1, - DRAG = 2, - HOLD = 3, - FLICK = 4, -}; - -export type FloorPosition = { - time: number, - endTime: number, - value: number, -}; diff --git a/src/Database/Database.ts b/src/Database/Database.ts deleted file mode 100644 index 6c45395..0000000 --- a/src/Database/Database.ts +++ /dev/null @@ -1,16 +0,0 @@ -import DatabaseEngine from './Engine'; -import { TProject } from './types'; - -class Database { - readonly project = new DatabaseEngine('editor_db_project', 1, { - structures: [ - { name: 'id', options: { key: true, unique: true } }, - { name: 'filesID', options: { unique: true } }, - { name: 'chartID', options: { unique: true } }, - ], - }); -}; - -const database = new Database(); -export default database; -export { Database }; diff --git a/src/Database/Engine.ts b/src/Database/Engine.ts deleted file mode 100644 index 50ffc89..0000000 --- a/src/Database/Engine.ts +++ /dev/null @@ -1,182 +0,0 @@ - -export type TDatabaseDataType = string | boolean | number | Blob; - -export type TDatabaseStructure = { - name: string, - options?: Partial<{ - key: boolean, - index: boolean, - unique: boolean, - multiEntry: boolean, - }>, -}; - -export type TDatabase = { - structures: TDatabaseStructure[], - autoIncrement?: boolean, -}; - -export type TDatabaseData = { - [x: string]: TDatabaseDataType | TDatabaseDataType[], -}; - -export default class DatabaseEngine { - readonly name: string; - readonly version: number; - - private db!: IDBDatabase; - private isReady = false; - - constructor(name: string, version: number, options: TDatabase) { - this.name = name; - this.version = version; - - const request = window.indexedDB.open(this.name, this.version); - - request.onsuccess = () => { - const { result } = request; - if (!result) return; - - this.db = result; - if (this.db.version === version) this.isReady = true; - }; - - request.onerror = (e) => { - throw (e as unknown as Error); - }; - - request.onupgradeneeded = () => { - this.db = request.result; - - const indexes: TDatabaseStructure[] = []; - let keyPath = null; - - for (const structure of options.structures) { - const { options } = structure; - if (!options) continue; - - if (options.key) keyPath = structure.name; - else if (options.index) indexes.push(structure); - } - - const objectOptions = { keyPath: keyPath, autoIncrement: options.autoIncrement }; - const store = this.db.createObjectStore(this.name, objectOptions); - - for (const index of indexes) { - store.createIndex(index.name, index.name, { - unique: index.options!.unique, - multiEntry: index.options!.multiEntry, - }); - } - - this.isReady = true; - }; - } - - add(data: TData) {return new Promise(async (res, rej) => { - await this.waitReady(); - const { db } = this; - - const transaction = db.transaction([ this.name ], 'readwrite'); - const store = transaction.objectStore(this.name); - - transaction.oncomplete = () => { - res(transaction); - }; - transaction.onerror = (e) => { - rej(e); - }; - - store.add(data); - })} - - delete(index: string | number): Promise {return new Promise(async (res, rej) => { - await this.waitReady(); - const { db } = this; - - const transaction = db.transaction([ this.name ], 'readwrite'); - const store = transaction.objectStore(this.name); - const request = store.delete(index); - - request.onsuccess = () => { - res(request.result); - }; - request.onerror = (e) => { - rej(e); - }; - })} - - get(index: string | number, key?: string): Promise {return new Promise(async (res, rej) => { - await this.waitReady(); - const { db } = this; - - const transaction = db.transaction([ this.name ], 'readonly'); - const store = transaction.objectStore(this.name); - const request = key ? store.index(key).get(index) : store.get(index); - - request.onsuccess = () => { - res((request.result as TData) || null); - }; - request.onerror = (e) => { - rej(e); - }; - })} - - getAll(): Promise {return new Promise(async (res, rej) => { - await this.waitReady(); - const { db } = this; - const result: TData[] = []; - - const transaction = db.transaction([ this.name ], 'readonly'); - const store = transaction.objectStore(this.name); - const cursor = store.openCursor(); - - cursor.onsuccess = (e) => { - const _cur = (e.target as IDBRequest).result as (IDBCursorWithValue | null); - if (_cur) { - result.push((_cur.value as TData)); - _cur.continue(); - } else { - res(result); - } - }; - - cursor.onerror = (e) => { - rej(e); - }; - })} - - update(index: string | number, data: TData): Promise {return new Promise(async (res, rej) => { - await this.waitReady(); - const { db } = this; - - const transaction = db.transaction([ this.name ], 'readwrite'); - const store = transaction.objectStore(this.name); - const request = store.get(index); - - request.onsuccess = () => { - const oldData = request.result as TData; - const newData = { ...oldData, ...data }; - - const reqUpdate = store.put(newData); - reqUpdate.onsuccess = () => { - res(newData); - }; - reqUpdate.onerror = (e) => { - rej(e); - }; - }; - request.onerror = (e) => { - rej(e); - }; - })} - - private waitReady() {return new Promise((res) => { - if (this.isReady) return res(this.isReady); - const clockId = setInterval(() => { - if (!this.isReady) return; - res(this.isReady); - clearInterval(clockId); - }, 200); - })} -} diff --git a/src/Database/types.ts b/src/Database/types.ts deleted file mode 100644 index 59efc73..0000000 --- a/src/Database/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ChartInfo } from '@/Chart/types'; - -export type TProject = ChartInfo & { - id: string, - filesID: string, - chartID: string, - musicID: string, - backgroundID: string, -}; diff --git a/src/Settings/Settings.ts b/src/Settings/Settings.ts deleted file mode 100644 index bf06482..0000000 --- a/src/Settings/Settings.ts +++ /dev/null @@ -1,47 +0,0 @@ -import EventEmitter from 'eventemitter3'; -import { TSettings } from './types'; - -const STORAGE_KEY_NAME = 'editor-settings'; - -class Settings { - readonly storage = window.localStorage; - readonly event = new EventEmitter(); - private _current: TSettings = { - // Default settings goes here - noteScale: 8000, - }; - - constructor() { - const storagedSettings = JSON.parse(this.storage.getItem(STORAGE_KEY_NAME) ?? '{}') as TSettings; - - this._current = { - ...this._current, - ...storagedSettings, - }; - this.storage.setItem(STORAGE_KEY_NAME, JSON.stringify(this._current)); - this.event.emit('settings.updated', { ...this._current }); - } - - /** - * Get single setting's value - */ - get(name: keyof TSettings): TSettings[keyof TSettings] { - return this._current[name]; - } - - set(name: keyof TSettings, value: TSettings[keyof TSettings]) { - this._current[name] = value; - this.storage.setItem(STORAGE_KEY_NAME, JSON.stringify(this._current)); - - this.event.emit('settings.updated', { ...this._current }); - return this._current; - } - - get current() { - return this._current; - } -} - -const settings = new Settings(); -export default settings; -export { Settings }; diff --git a/src/Settings/types.tsx b/src/Settings/types.tsx deleted file mode 100644 index ec58d58..0000000 --- a/src/Settings/types.tsx +++ /dev/null @@ -1,4 +0,0 @@ - -export type TSettings = { - noteScale: number, -}; diff --git a/src/Storage/File.ts b/src/Storage/File.ts deleted file mode 100644 index c84e028..0000000 --- a/src/Storage/File.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import DatabaseEngine from '@/Database/Engine'; -import { GetFileMD5 } from '@/utils/file'; -import { TStorageFile } from './types'; -import { Nullable } from '@/utils/types'; - -class StorageFile { - readonly db = new DatabaseEngine('editor_storage_files', 1, { - structures: [ - { name: 'id', options: { key: true, unique: true } }, - { name: 'md5', options: { unique: true, index: true } }, - ] - }); - - add(blob: Blob): Promise<{ md5: string, id: string }> {return new Promise(async (res, rej) => { - const id = uuid(); - const md5 = await GetFileMD5(blob); - const fileData = await this.db.get(md5); - if (fileData) return res(fileData); - - this.db.add({ - id, md5, blob - }).then(() => res({ md5, id })) - .catch(e => rej(e)); - })} - - remove(md5: string) { - return this.db.delete(md5); - } - - get(id: string | string[]): Promise> { - if (typeof id === 'string') return this.db.get(id); - return new Promise(async (res) => { - const allFiles = await this.db.getAll(); - res(allFiles.filter((e) => id.includes(e.id))); - }); - } - - getByMD5(md5: string | string[]): Promise> { - if (typeof md5 === 'string') return this.db.get(md5, 'md5'); - return new Promise(async (res) => { - const allFiles = await this.db.getAll(); - res(allFiles.filter((e) => md5.includes(e.md5))); - }); - } - - update(id: string, file: Blob): Promise<{ md5: string }> {return new Promise(async (res, rej) => { - const md5 = await GetFileMD5(file); - - this.db.update(id, { - id, md5, blob: file, - }).then(() => res({ md5 })) - .catch((e) => rej(e)); - })} -} - -const file = new StorageFile; -export default file; -export { StorageFile }; diff --git a/src/Storage/Storage.ts b/src/Storage/Storage.ts deleted file mode 100644 index a21837b..0000000 --- a/src/Storage/Storage.ts +++ /dev/null @@ -1,41 +0,0 @@ -import DatabaseEngine from '@/Database/Engine'; -import StorageFile from './File'; -import { TStorage, TStorageStructure } from './types'; - -class Storage { - readonly file = StorageFile; - readonly db = new DatabaseEngine('editor_storage_structure', 1, { - structures: [ - { name: 'id', options: { key: true, unique: true } }, - { name: 'structure' } - ], - }); - - add(id: string, structure: TStorageStructure) { - return this.db.add({ - id, structure - }); - } - - remove(id: string) { - return this.db.delete(id); - } - - get(id: string) { - return this.db.get(id); - } - - getAll() { - return this.db.getAll(); - } - - update(id: string, structure: TStorageStructure) { - return this.db.update(id, { - id, structure - }); - } -} - -const storage = new Storage(); -export default storage; -export { Storage }; diff --git a/src/Storage/types.ts b/src/Storage/types.ts deleted file mode 100644 index 3b23eb2..0000000 --- a/src/Storage/types.ts +++ /dev/null @@ -1,32 +0,0 @@ - -export type TStorageItemBase = { - // UUID? - id: string, - name: string, - isPath: boolean, -}; - -export type TStorageItemFile = TStorageItemBase & { - isPath: false, - md5: string, -}; - -export type TStorageItemPath = TStorageItemBase & { - isPath: true, - files: TStorageItem[], -}; - -export type TStorageItem = TStorageItemFile | TStorageItemPath; - -export type TStorageStructure = Record; - -export type TStorage = { - id: string, - structure: TStorageStructure, -}; - -export type TStorageFile = { - id: string, - md5: string, - blob: Blob, -}; diff --git a/src/core/command/bpm/add.ts b/src/core/command/bpm/add.ts new file mode 100644 index 0000000..79da1a7 --- /dev/null +++ b/src/core/command/bpm/add.ts @@ -0,0 +1,24 @@ +import { getRelativePath, SnapshotIn } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { BPM } from '@/core/models/BPM'; +import { BeatArray } from '@/utils/types'; +import { Command } from '../command'; + +type AddBPMPayload = Omit, 'id'>; + +export class AddBPMCommand extends Command { + type: string = 'bpm.add'; + propName = (void 0); + + getState(project: IChart) { + return project; + } + + getStatePath(project: IChart) { + return getRelativePath(project, project.bpm); + } + + edit(payload: AddBPMPayload) { + this.state!.addBPM(payload.beat as BeatArray, payload.bpm); + } +} diff --git a/src/core/command/bpm/change.ts b/src/core/command/bpm/change.ts new file mode 100644 index 0000000..3cee266 --- /dev/null +++ b/src/core/command/bpm/change.ts @@ -0,0 +1,18 @@ +import { resolveIdentifier } from 'mobx-state-tree'; +import { SnapshotIn } from 'mobx-state-tree'; +import { BPM, IBPM } from '@/core/models/BPM'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; + +export class ChangeBPMCommand extends Command { + type: string = 'bpm.change'; + propName: keyof SnapshotIn & string = 'bpm'; + + getState(project: IChart, id: string) { + return resolveIdentifier(BPM, project.bpm, id); + } + + edit(payload: number) { + this.state!.updateProps({ bpm: payload }); + } +} diff --git a/src/core/command/bpm/move.ts b/src/core/command/bpm/move.ts new file mode 100644 index 0000000..22bd2ec --- /dev/null +++ b/src/core/command/bpm/move.ts @@ -0,0 +1,19 @@ +import { resolveIdentifier } from 'mobx-state-tree'; +import { SnapshotIn } from 'mobx-state-tree'; +import { BPM, IBPM } from '@/core/models/BPM'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; +import { BeatArray } from '@/utils/types'; + +export class MoveBPMCommand extends Command { + type: string = 'bpm.move'; + propName: keyof SnapshotIn & string = 'beat'; + + getState(project: IChart, id: string) { + return resolveIdentifier(BPM, project.bpm, id); + } + + edit(payload: BeatArray) { + this.state!.updateProps({ beat: payload }); + } +} diff --git a/src/core/command/bpm/remove.ts b/src/core/command/bpm/remove.ts new file mode 100644 index 0000000..fc28f42 --- /dev/null +++ b/src/core/command/bpm/remove.ts @@ -0,0 +1,21 @@ +import { getRelativePath, resolveIdentifier, getParent } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { BPM, IBPM } from '@/core/models/BPM'; +import { Command } from '../command'; + +export class RemoveBPMCommand extends Command { + type: string = 'bpm.remove'; + propName = (void 0); + + getState(project: IChart, id: string) { + return resolveIdentifier(BPM, project.bpm, id); + } + + getStatePath(project: IChart) { + return getRelativePath(project, project.bpm); + } + + edit() { + (getParent(this.state!, 2) as IChart).removeBPM(this.state!.id); + } +} diff --git a/src/core/command/bus.ts b/src/core/command/bus.ts new file mode 100644 index 0000000..8cb5958 --- /dev/null +++ b/src/core/command/bus.ts @@ -0,0 +1,51 @@ +import { store as ChartStore } from '../state/chartStore'; +import { commandBusStore } from './state'; +import { CommandExecutor } from './types'; + +export class CommandBus { + readonly history: CommandExecutor[] = []; + readonly redoStack: CommandExecutor[] = []; + + dispatch(executor: CommandExecutor) { + this.history.unshift(executor); + this.redoStack.length = 0; + + this.updateStore(); + } + + undo() { + const executor = this.history.shift(); + if (!executor) return; + + executor.undo(ChartStore); + this.redoStack.unshift(executor); + + this.updateStore(); + } + + redo() { + const executor = this.redoStack.shift(); + if (!executor) return; + + executor.redo(ChartStore); + this.history.unshift(executor); + + this.updateStore(); + } + + reset() { + this.history.length = 0; + this.redoStack.length = 0; + + this.updateStore(); + } + + private updateStore() { + commandBusStore.setState({ + history: this.history.map((e) => e.meta), + redoStack: this.redoStack.map((e) => e.meta), + }); + } +} + +export const commandBus = new CommandBus(); diff --git a/src/core/command/command.ts b/src/core/command/command.ts new file mode 100644 index 0000000..7d1a828 --- /dev/null +++ b/src/core/command/command.ts @@ -0,0 +1,98 @@ +import { applyPatch, getRelativePath, onPatch } from 'mobx-state-tree'; +import { IAnyStateTreeNode, IJsonPatch, IDisposer, SnapshotIn } from 'mobx-state-tree'; +import { CommandExecutor } from './types'; +import { IChart } from '../models/Chart'; +import { Nullable } from '@/utils/types'; + +const dedup = (patches: IJsonPatch[]) => { + const seen = new Set(); + const result: IJsonPatch[] = []; + + for (let i = patches.length - 1; i >= 0; i--) { + const p = patches[i]; + if (p.op === 'replace' && !seen.has(p.path)) { + seen.add(p.path); + result.unshift(p); + } else if (p.op !== 'replace') { + result.unshift(p); + } + } + + return result; +}; + +const apply = (target: IChart, patches: IJsonPatch[]) => applyPatch(target, patches); + +export abstract class Command { + // TODO: types + abstract readonly type: string; + timestamp: number = Date.now(); + abstract readonly propName?: keyof SnapshotIn & string; + + state: Nullable = null; + patchDisposer: Nullable = null; + + isRecording = true; + + patches: IJsonPatch[] = []; + inverse: IJsonPatch[] = []; + + abstract getState(project: IChart, id: string): T | undefined; + + getStatePath(project: IChart): string { + return this.state ? getRelativePath(project, this.state) : '/'; + } + + start(project: IChart, id: string) { + if (project.metadata === null) + throw new Error('Cannot start command before project initialized'); + if (this.patchDisposer) + throw new Error('Command already started'); + + this.state = this.getState(project, id) ?? null; + if (!this.state) throw new Error(`No such state found: ${id}`); + + const statePath = `${this.getStatePath(project)}${this.propName ? '/' + this.propName : ''}`; + this.patchDisposer = onPatch(project, (p, r) => { + if (!this.isRecording) return; + if (!p.path.startsWith(statePath)) return; + + this.patches.push(p); + this.inverse.push(r); + }); + } + + abstract edit(payload: P): void; + + end(merge: boolean = true) { + if (!this.patchDisposer) + throw new Error('Cannot call end() before calling start()'); + + this.patchDisposer(); + this.patchDisposer = null; + + if (merge) { + this.patches = dedup(this.patches); + this.inverse = dedup(this.inverse.reverse()); + } + + return this.executor; + } + + execute(project: IChart, id: string, payload: P, merge = true) { + this.start(project, id); + this.edit(payload) + return this.end(merge); + }; + + get executor(): CommandExecutor { + return { + meta: { + type: this.type, + timestamp: this.timestamp, + }, + undo: (target) => apply(target, this.inverse), + redo: (target) => apply(target, this.patches), + }; + } +} diff --git a/src/core/command/keyframe/add.ts b/src/core/command/keyframe/add.ts new file mode 100644 index 0000000..8456751 --- /dev/null +++ b/src/core/command/keyframe/add.ts @@ -0,0 +1,32 @@ +import { getRelativePath, resolveIdentifier, SnapshotIn } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { IKeyframeRecord } from '@/core/models/KeyframeRecord'; +import { JudgeLine } from '@/core/models/JudgeLine'; +import { Keyframe } from '@/core/models/Keyframe'; +import { BeatArray } from '@/utils/types'; +import { Command } from '../command'; + +type AddKeyframePayload = Omit, 'id'>; + +export class AddKeyframeCommand extends Command { + type: string = 'keyframe.add'; + propName = (void 0); + + getState(project: IChart, id: string) { + return resolveIdentifier(JudgeLine, project.judgelines, id)?.keyframes; + } + + getStatePath(project: IChart) { + return getRelativePath(project, this.state); + } + + edit(payload: AddKeyframePayload) { + this.state!.add( + payload.type, + payload.beat as BeatArray, + payload.value, + payload.continuous, + payload.easing + ); + } +} diff --git a/src/core/command/keyframe/changeEasing.ts b/src/core/command/keyframe/changeEasing.ts new file mode 100644 index 0000000..40e24ed --- /dev/null +++ b/src/core/command/keyframe/changeEasing.ts @@ -0,0 +1,19 @@ +import { resolveIdentifier, SnapshotIn } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { IKeyframe, Keyframe } from '@/core/models/Keyframe'; +import { Command } from '../command'; + +export class ChangeKeyframeEasingCommand extends Command { + type: string = 'keyframe.easing'; + propName: keyof SnapshotIn = 'easing'; + + getState(project: IChart, id: string) { + return resolveIdentifier(Keyframe, project.judgelines, id); + } + + edit(payload: number) { + this.state!.updateProps({ + easing: payload, + }); + } +} diff --git a/src/core/command/keyframe/changeValue.ts b/src/core/command/keyframe/changeValue.ts new file mode 100644 index 0000000..a17c38b --- /dev/null +++ b/src/core/command/keyframe/changeValue.ts @@ -0,0 +1,19 @@ +import { resolveIdentifier, SnapshotIn } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { IKeyframe, Keyframe } from '@/core/models/Keyframe'; +import { Command } from '../command'; + +export class ChangeKeyframeValueCommand extends Command { + type: string = 'keyframe.value'; + propName: keyof SnapshotIn = 'value'; + + getState(project: IChart, id: string) { + return resolveIdentifier(Keyframe, project.judgelines, id); + } + + edit(payload: number) { + this.state!.updateProps({ + value: payload, + }); + } +} diff --git a/src/core/command/keyframe/move.ts b/src/core/command/keyframe/move.ts new file mode 100644 index 0000000..b350baa --- /dev/null +++ b/src/core/command/keyframe/move.ts @@ -0,0 +1,19 @@ +import { resolveIdentifier } from 'mobx-state-tree'; +import { SnapshotIn } from 'mobx-state-tree'; +import { Keyframe, IKeyframe } from '@/core/models/Keyframe'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; +import { BeatArray } from '@/utils/types'; + +export class MoveKeyframeCommand extends Command { + type: string = 'keyframe.move'; + propName: keyof SnapshotIn & string = 'beat'; + + getState(project: IChart, id: string) { + return resolveIdentifier(Keyframe, project.judgelines, id); + } + + edit(payload: BeatArray) { + this.state!.updateProps({ beat: payload }); + } +} diff --git a/src/core/command/keyframe/remove.ts b/src/core/command/keyframe/remove.ts new file mode 100644 index 0000000..9e0fc90 --- /dev/null +++ b/src/core/command/keyframe/remove.ts @@ -0,0 +1,22 @@ +import { getRelativePath, resolveIdentifier, getParent } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; +import { IKeyframe, Keyframe } from '@/core/models/Keyframe'; +import { IKeyframeRecord } from '@/core/models/KeyframeRecord'; + +export class RemoveKeyframeCommand extends Command { + type: string = 'keyframe.remove'; + propName = (void 0); + + getState(project: IChart, id: string) { + return resolveIdentifier(Keyframe, project.judgelines, id); + } + + getStatePath(project: IChart) { + return getRelativePath(project, getParent(this.state!, 2)); + } + + edit() { + (getParent(this.state!, 2) as IKeyframeRecord).remove(this.state!.type, this.state!.id); + } +} diff --git a/src/core/command/keyframe/setContinuous.ts b/src/core/command/keyframe/setContinuous.ts new file mode 100644 index 0000000..978c2e8 --- /dev/null +++ b/src/core/command/keyframe/setContinuous.ts @@ -0,0 +1,19 @@ +import { resolveIdentifier, SnapshotIn } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { IKeyframe, Keyframe } from '@/core/models/Keyframe'; +import { Command } from '../command'; + +export class SetKeyframeContinuousCommand extends Command { + type: string = 'keyframe.continuous'; + propName: keyof SnapshotIn = 'continuous'; + + getState(project: IChart, id: string) { + return resolveIdentifier(Keyframe, project.judgelines, id); + } + + edit(payload: boolean) { + this.state!.updateProps({ + continuous: payload, + }); + } +} diff --git a/src/core/command/line/add.ts b/src/core/command/line/add.ts new file mode 100644 index 0000000..5402290 --- /dev/null +++ b/src/core/command/line/add.ts @@ -0,0 +1,20 @@ +import { getRelativePath } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; + +export class AddLineCommand extends Command { + type: string = 'line.add'; + propName = (void 0); + + getState(project: IChart) { + return project; + } + + getStatePath(project: IChart) { + return getRelativePath(project, project.judgelines); + } + + edit() { + this.state!.addJudgeLine(); + } +} diff --git a/src/core/command/line/remove.ts b/src/core/command/line/remove.ts new file mode 100644 index 0000000..93d65c1 --- /dev/null +++ b/src/core/command/line/remove.ts @@ -0,0 +1,21 @@ +import { getRelativePath, resolveIdentifier, getParent } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { JudgeLine, IJudgeLine } from '@/core/models/JudgeLine'; +import { Command } from '../command'; + +export class RemoveLineCommand extends Command { + type: string = 'line.remove'; + propName = (void 0); + + getState(project: IChart, id: string) { + return resolveIdentifier(JudgeLine, project.judgelines, id); + } + + getStatePath(project: IChart) { + return getRelativePath(project, project.judgelines); + } + + edit() { + (getParent(this.state!, 2) as IChart).removeJudgeLine(this.state!.id); + } +} diff --git a/src/core/command/note/add.ts b/src/core/command/note/add.ts new file mode 100644 index 0000000..0c7b216 --- /dev/null +++ b/src/core/command/note/add.ts @@ -0,0 +1,25 @@ +import { getRelativePath, resolveIdentifier } from 'mobx-state-tree'; +import { SnapshotIn } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { IJudgeLine, JudgeLine } from '@/core/models/JudgeLine'; +import { Note } from '@/core/models/Note'; +import { Command } from '../command'; + +type AddNotePayload = Omit, 'id'>; + +export class AddNoteCommand extends Command { + type: string = 'note.add'; + propName = (void 0); + + getState(project: IChart, id: string) { + return resolveIdentifier(JudgeLine, project.judgelines, id); + } + + getStatePath(project: IChart) { + return getRelativePath(project, this.state!.notes); + } + + edit(payload: AddNotePayload) { + this.state!.addNote(payload); + } +} diff --git a/src/core/command/note/changeHoldEnd.ts b/src/core/command/note/changeHoldEnd.ts new file mode 100644 index 0000000..45e83f2 --- /dev/null +++ b/src/core/command/note/changeHoldEnd.ts @@ -0,0 +1,20 @@ +import { resolveIdentifier, SnapshotIn } from 'mobx-state-tree'; +import { Note, INote } from '@/core/models/Note'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; +import { BeatArray } from '@/utils/types'; + +export class ChangeNoteHoldEndCommand extends Command { + type: string = 'note.holdEndBeat'; + propName: keyof SnapshotIn = 'holdEndBeat'; + + getState(project: IChart, id: string) { + return resolveIdentifier(Note, project.judgelines, id); + } + + edit(payload: BeatArray) { + this.state!.updateProps({ + holdEndBeat: payload, + }); + } +} diff --git a/src/core/command/note/changePositionX.ts b/src/core/command/note/changePositionX.ts new file mode 100644 index 0000000..5d2c71f --- /dev/null +++ b/src/core/command/note/changePositionX.ts @@ -0,0 +1,19 @@ +import { resolveIdentifier, SnapshotIn } from 'mobx-state-tree'; +import { Note, INote } from '@/core/models/Note'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; + +export class ChangeNotePositionXCommand extends Command { + type: string = 'note.positionX'; + propName: keyof SnapshotIn = 'positionX'; + + getState(project: IChart, id: string) { + return resolveIdentifier(Note, project.judgelines, id); + } + + edit(payload: number) { + this.state!.updateProps({ + positionX: payload, + }); + } +} diff --git a/src/core/command/note/changeSpeed.ts b/src/core/command/note/changeSpeed.ts new file mode 100644 index 0000000..c6e97b3 --- /dev/null +++ b/src/core/command/note/changeSpeed.ts @@ -0,0 +1,19 @@ +import { resolveIdentifier, SnapshotIn } from 'mobx-state-tree'; +import { Note, INote } from '@/core/models/Note'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; + +export class ChangeNoteSpeedCommand extends Command { + type: string = 'note.speed'; + propName: keyof SnapshotIn = 'speed'; + + getState(project: IChart, id: string) { + return resolveIdentifier(Note, project.judgelines, id); + } + + edit(payload: number) { + this.state!.updateProps({ + speed: payload, + }); + } +} diff --git a/src/core/command/note/changeType.ts b/src/core/command/note/changeType.ts new file mode 100644 index 0000000..6e4369f --- /dev/null +++ b/src/core/command/note/changeType.ts @@ -0,0 +1,20 @@ +import { resolveIdentifier, SnapshotIn } from 'mobx-state-tree'; +import { Note, INote } from '@/core/models/Note'; +import { IChart } from '@/core/models/Chart'; +import { NoteType } from '@/core/types'; +import { Command } from '../command'; + +export class ChangeNoteTypeCommand extends Command { + type: string = 'note.type'; + propName: keyof SnapshotIn = 'type'; + + getState(project: IChart, id: string) { + return resolveIdentifier(Note, project.judgelines, id); + } + + edit(payload: NoteType) { + this.state!.updateProps({ + type: payload, + }); + } +} diff --git a/src/core/command/note/move.ts b/src/core/command/note/move.ts new file mode 100644 index 0000000..acf91e3 --- /dev/null +++ b/src/core/command/note/move.ts @@ -0,0 +1,30 @@ +import { resolveIdentifier } from 'mobx-state-tree'; +import { Note, INote } from '@/core/models/Note'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; +import { BeatArray } from '@/utils/types'; +import { BeatArrayToNumber, BeatNumberToArray } from '@/utils/math'; + +export class MoveNoteCommand extends Command { + type: string = 'note.move'; + propName = (void 0); + + isRecording = false; + + getState(project: IChart, id: string) { + return resolveIdentifier(Note, project.judgelines, id); + } + + edit(payload: BeatArray) { + const newBeatNum = BeatArrayToNumber(payload); + let newHoldEndBeatNum = this.state!.holdEndBeatNum + (newBeatNum - this.state!.beatNum); + if (newHoldEndBeatNum < newBeatNum) newHoldEndBeatNum = newBeatNum; + + this.isRecording = true; + this.state!.updateProps({ + beat: payload, + holdEndBeat: BeatNumberToArray(newHoldEndBeatNum, payload[2]), + }); + this.isRecording = false; + } +} diff --git a/src/core/command/note/remove.ts b/src/core/command/note/remove.ts new file mode 100644 index 0000000..c1bffea --- /dev/null +++ b/src/core/command/note/remove.ts @@ -0,0 +1,22 @@ +import { getParent, getRelativePath, resolveIdentifier } from 'mobx-state-tree'; +import { IChart } from '@/core/models/Chart'; +import { INote, Note } from '@/core/models/Note'; +import { Command } from '../command'; +import { IJudgeLine } from '@/core/models/JudgeLine'; + +export class RemoveNoteCommand extends Command { + type: string = 'note.remove'; + propName = (void 0); + + getState(project: IChart, id: string) { + return resolveIdentifier(Note, project.judgelines, id); + } + + getStatePath(project: IChart) { + return getRelativePath(project, getParent(this.state!, 1)); + } + + edit() { + (getParent(this.state!, 2) as IJudgeLine).removeNote(this.state!.id); + } +} diff --git a/src/core/command/note/setAbove.ts b/src/core/command/note/setAbove.ts new file mode 100644 index 0000000..97b20bc --- /dev/null +++ b/src/core/command/note/setAbove.ts @@ -0,0 +1,19 @@ +import { resolveIdentifier, SnapshotIn } from 'mobx-state-tree'; +import { Note, INote } from '@/core/models/Note'; +import { IChart } from '@/core/models/Chart'; +import { Command } from '../command'; + +export class SetNoteAboveCommand extends Command { + type: string = 'note.isAbove'; + propName: keyof SnapshotIn = 'isAbove'; + + getState(project: IChart, id: string) { + return resolveIdentifier(Note, project.judgelines, id); + } + + edit(payload: boolean) { + this.state!.updateProps({ + isAbove: payload, + }); + } +} diff --git a/src/core/command/state.ts b/src/core/command/state.ts new file mode 100644 index 0000000..36f04c2 --- /dev/null +++ b/src/core/command/state.ts @@ -0,0 +1,17 @@ +import { createStore } from 'zustand/vanilla'; +import { useStore } from 'zustand'; +import { CommandMeta } from './types'; + +type CommandBusStore = { + history: CommandMeta[], + redoStack: CommandMeta[], +}; + +export const commandBusStore = createStore(() => ({ + history: [], + redoStack: [], +})); + +export const useCommandBusStore = ( + selector: (state: CommandBusStore) => U +) => useStore(commandBusStore, selector); diff --git a/src/core/command/types.ts b/src/core/command/types.ts new file mode 100644 index 0000000..ea53518 --- /dev/null +++ b/src/core/command/types.ts @@ -0,0 +1,12 @@ +import { IAnyStateTreeNode } from 'mobx-state-tree'; + +export type CommandMeta = { + type: string, // TODO: Types + timestamp: number, +}; + +export type CommandExecutor = { + meta: CommandMeta; + undo: (target: IAnyStateTreeNode) => void; + redo: (target: IAnyStateTreeNode) => void; +}; diff --git a/src/core/models/BPM.ts b/src/core/models/BPM.ts new file mode 100644 index 0000000..8319e7c --- /dev/null +++ b/src/core/models/BPM.ts @@ -0,0 +1,48 @@ +import { nanoid } from 'nanoid'; +import { types, getParent, Instance, SnapshotIn } from 'mobx-state-tree'; +import { IChart } from './Chart'; +import { BeatArray } from '@/core/utils/model'; +import { BeatArrayToNumber } from '@/utils/math'; + +const BPMNumber = types.custom({ + name: 'BPMNumber', + + fromSnapshot: (value) => value, + toSnapshot: (value) => value, + + isTargetType(value) { + return !isNaN(value) && value > 0; + }, + + getValidationMessage(snapshot) { + if (isNaN(snapshot)) return '\'NaN\' BPM is not allowed'; + if (snapshot <= 0) return 'BPM cannot smaller than 1'; + return ''; + }, +}); + +export const BPM = types.model('BPM', { + id: types.optional(types.identifier, nanoid), + beat: BeatArray, + bpm: BPMNumber, +}).views((self) => ({ + get beatNum() { + return BeatArrayToNumber(self.beat); + } +})).actions((self) => ({ + afterCreate() { + this.updateParentBPMCache(); + }, + + updateProps(props: Partial, 'id'>>) { + Object.assign(self, props); + this.updateParentBPMCache(); + }, + + updateParentBPMCache() { + const chart = getParent(self, 2) as IChart; + chart.updateBPMCache(); + }, +})); + +export type IBPM = Instance; diff --git a/src/core/models/Chart.ts b/src/core/models/Chart.ts new file mode 100644 index 0000000..9999255 --- /dev/null +++ b/src/core/models/Chart.ts @@ -0,0 +1,73 @@ +import { types, Instance, SnapshotIn, destroy } from 'mobx-state-tree'; +import { calculateBPMCache } from '../timeline/bpm'; +import { Metadata } from './Metadata'; +import { BPM } from './BPM'; +import { JudgeLine } from './JudgeLine'; +import { BeatArray } from '@/utils/types'; +import { BPMCache } from '../types'; + +export const Chart = types.model('Chart', { + // TODO: remove `maybeNull`? + metadata: types.maybeNull(Metadata), + offset: types.number, + bpm: types.optional(types.array(BPM), [{ + beat: [ 0, 0, 1 ], + bpm: 120, + }]), + judgelines: types.array(JudgeLine), +}).volatile(() => ({ + bpmCache: [] as BPMCache[], +})).actions((self) => ({ + afterCreate() { + this.updateBPMCache(); + }, + + updateMetadata(metadata: SnapshotIn) { + if (self.metadata === null) self.metadata = Metadata.create(metadata); + else self.metadata.updateProps(metadata); + }, + + removeMetadata() { + self.metadata = null; + }, + + updateOffset(offset: number) { + self.offset = offset; + }, + + addBPM(beat: BeatArray, bpm: number) { + self.bpm.push({ + beat, bpm, + }); + }, + + removeBPM(id: string) { + if (self.bpm.length === 1) { + throw new Error('Must have at least 1 bpm exists'); + } + + const index = self.bpm.findIndex(e => e.id === id); + if (index >= 0) destroy(self.bpm[index]); + this.updateBPMCache(); + }, + + addJudgeLine() { + self.judgelines.push({}); + }, + + removeJudgeLine(id: string) { + const index = self.judgelines.findIndex(e => e.id === id); + if (index >= 0) destroy(self.judgelines[index]); + }, + + updateBPMCache() { + self.bpmCache = calculateBPMCache(self.bpm); + this.updateTimeCache(); + }, + + updateTimeCache() { + for (const line of self.judgelines) line.updateTimeCache(); + } +})); + +export type IChart = Instance; diff --git a/src/core/models/JudgeLine.ts b/src/core/models/JudgeLine.ts new file mode 100644 index 0000000..432a2d4 --- /dev/null +++ b/src/core/models/JudgeLine.ts @@ -0,0 +1,34 @@ +import { nanoid } from 'nanoid'; +import { types, Instance, destroy } from 'mobx-state-tree'; +import { KeyframeRecord } from './KeyframeRecord'; +import { Note } from './Note'; +import { SnapshotIn } from 'mobx-state-tree'; + +export const JudgeLine = types.model('JudgeLine', { + id: types.optional(types.identifier, nanoid), + keyframes: types.optional(KeyframeRecord, {}), + notes: types.optional(types.array(Note), []), +}).actions((self) => ({ + addNote(props: SnapshotIn) { + self.notes.push(props); + }, + + editNote(id: string, props: Partial, 'id'>>) { + const note = self.notes.find(e => e.id === id); + if (!note) return; + + note.updateProps(props); + }, + + removeNote(id: string) { + const index = self.notes.findIndex(e => e.id === id); + if (index >= 0) destroy(self.notes[index]); + }, + + updateTimeCache() { + self.keyframes.updateTimeCache(); + for (const n of self.notes) n.updateTimeCache(); + } +})); + +export type IJudgeLine = Instance; diff --git a/src/core/models/Keyframe.ts b/src/core/models/Keyframe.ts new file mode 100644 index 0000000..8ae97e1 --- /dev/null +++ b/src/core/models/Keyframe.ts @@ -0,0 +1,43 @@ +import { nanoid } from 'nanoid'; +import { types, getParent, Instance, SnapshotIn } from 'mobx-state-tree'; +import { BeatArray } from '@/core/utils/model'; +import { getTimeByBeat } from '../timeline/bpm'; +import { BeatArrayToNumber } from '@/utils/math'; +import { KeyframeType } from '../types'; + +export const Keyframe = types.model('Keyframe', { + id: types.optional(types.identifier, nanoid), + type: types.enumeration('KeyframeType', Object.values(KeyframeType)), + beat: BeatArray, + value: types.number, + continuous: types.boolean, + easing: types.number, + hasEndValue: types.optional(types.boolean, false), + endValue: types.optional(types.number, 0), +}).views((self) => ({ + get line() { + // TODO: types + return getParent(self, 3); + }, + + get beatNum() { + return BeatArrayToNumber(self.beat); + } +})).volatile(() => ({ + time: 0, +})).actions((self) => ({ + afterCreate() { + this.updateTimeCache(); + }, + + updateProps(props: Partial, 'id' | 'type'>>) { + Object.assign(self, props); + if (props.beat !== (void 0)) this.updateTimeCache(); + }, + + updateTimeCache() { + self.time = getTimeByBeat(self.beatNum); + } +})); + +export type IKeyframe = Instance; diff --git a/src/core/models/KeyframeRecord.ts b/src/core/models/KeyframeRecord.ts new file mode 100644 index 0000000..43fb3a0 --- /dev/null +++ b/src/core/models/KeyframeRecord.ts @@ -0,0 +1,69 @@ +import { types, Instance, destroy } from 'mobx-state-tree'; +import { Keyframe } from './Keyframe'; +import { KeyframeType } from '../types'; +import { BeatArray } from '@/utils/types'; + +export const KeyframeRecord = types.model('KeyframeRecord', { + positionX: types.optional(types.array(Keyframe), [{ + type: KeyframeType.PositionX, + beat: [ 0, 0, 1 ], + value: 0, + continuous: false, + easing: 0, + }]), + positionY: types.optional(types.array(Keyframe), [{ + type: KeyframeType.PositionY, + beat: [ 0, 0, 1 ], + value: 0, + continuous: false, + easing: 0, + }]), + rotate: types.optional(types.array(Keyframe), [{ + type: KeyframeType.Rotate, + beat: [ 0, 0, 1 ], + value: 0, + continuous: false, + easing: 0, + }]), + alpha: types.optional(types.array(Keyframe), [{ + type: KeyframeType.Alpha, + beat: [ 0, 0, 1 ], + value: 255, + continuous: false, + easing: 0, + }]), + speed: types.optional(types.array(Keyframe), [{ + type: KeyframeType.Speed, + beat: [ 0, 0, 1 ], + value: 1, + continuous: false, + easing: 0, + }]), +}).actions((self) => ({ + add( + type: KeyframeType, + beat: BeatArray, + value: number, + continuous: boolean, + easing: number, + ) { + self[type].push({ + type, beat, value, continuous, easing + }); + }, + + remove(type: KeyframeType, id: string) { + const index = self[type].findIndex(e => e.id === id); + if (index >= 0) destroy(self[type][index]); + }, + + updateTimeCache() { + for (const k of self.positionX) k.updateTimeCache(); + for (const k of self.positionY) k.updateTimeCache(); + for (const k of self.rotate) k.updateTimeCache(); + for (const k of self.alpha) k.updateTimeCache(); + for (const k of self.speed) k.updateTimeCache(); + }, +})); + +export type IKeyframeRecord = Instance; diff --git a/src/core/models/Metadata.ts b/src/core/models/Metadata.ts new file mode 100644 index 0000000..baba832 --- /dev/null +++ b/src/core/models/Metadata.ts @@ -0,0 +1,17 @@ +import { types, Instance, SnapshotIn } from 'mobx-state-tree'; + +export const Metadata = types.model('Metadata', { + name: types.optional(types.string, 'Untitled'), + artist: types.optional(types.string, 'Unknown'), + illustration: types.optional(types.string, 'Unknown'), + level: types.optional(types.string, 'SP Lv.?'), + designer: types.optional(types.string, 'Unknown'), + musicFile: types.string, + backgroundFile: types.string, +}).actions((self) => ({ + updateProps(props: Partial>) { + Object.assign(self, props); + } +})); + +export type IMetadata = Instance; diff --git a/src/core/models/Note.ts b/src/core/models/Note.ts new file mode 100644 index 0000000..83a78d2 --- /dev/null +++ b/src/core/models/Note.ts @@ -0,0 +1,68 @@ +import { nanoid } from 'nanoid'; +import { types, getParent, Instance, SnapshotIn } from 'mobx-state-tree'; +import { BeatArray } from '@/core/utils/model'; +import { getTimeByBeat } from '../timeline/bpm'; +import { BeatArrayToNumber, parseDoublePrecist } from '@/utils/math'; +import { NoteType } from '../types'; +import { BeatArray as TBeatArray } from '@/utils/types'; + +export const Note = types.model('Note', { + id: types.optional(types.identifier, nanoid), + type: types.enumeration('NoteType', Object.values(NoteType)), + beat: BeatArray, + positionX: types.number, + speed: types.number, + isAbove: types.boolean, + + holdEndBeat: types.optional(BeatArray, [ 0, 0, 1 ]), +}).volatile(() => ({ + time: 0, + holdEndTime: 0, + holdLengthTime: 0, +})).postProcessSnapshot((self) => { + if ( + self.holdEndBeat && + BeatArrayToNumber(self.beat as TBeatArray) > BeatArrayToNumber(self.holdEndBeat as TBeatArray) + ) { + self.holdEndBeat = self.beat; + } +}).views((self) => ({ + get line() { + // TODO: types + return getParent(self, 2); + }, + + get beatNum() { + return BeatArrayToNumber(self.beat); + }, + + get holdEndBeatNum() { + if (self.type !== NoteType.Hold || !self.holdEndBeat) return BeatArrayToNumber(self.beat); + return BeatArrayToNumber(self.holdEndBeat); + }, + + get holdLengthBeatNum() { + if (self.type !== NoteType.Hold || !self.holdEndBeat) return 0; + return BeatArrayToNumber(self.holdEndBeat) - BeatArrayToNumber(self.beat); + } +})).actions((self) => ({ + afterCreate() { + this.updateTimeCache(); + }, + + updateProps(props: Omit>, 'id'>) { + if ( + props.beat !== (void 0) || + props.holdEndBeat !== (void 0) + ) this.updateTimeCache(props.beat as TBeatArray | undefined, props.holdEndBeat as TBeatArray | undefined); + Object.assign(self, props); + }, + + updateTimeCache(beat?: TBeatArray, holdEndBeat?: TBeatArray) { + self.time = getTimeByBeat(beat ? BeatArrayToNumber(beat) : self.beatNum); + self.holdEndTime = self.type === NoteType.Hold ? getTimeByBeat(holdEndBeat ? BeatArrayToNumber(holdEndBeat) : self.holdEndBeatNum) : self.time; + self.holdLengthTime = parseDoublePrecist(self.holdEndTime - self.time, 6, -1); + } +})); + +export type INote = Instance; diff --git a/src/core/project/create.ts b/src/core/project/create.ts new file mode 100644 index 0000000..474d07b --- /dev/null +++ b/src/core/project/create.ts @@ -0,0 +1,41 @@ +import { applySnapshot } from 'mobx-state-tree'; +import { runtimeCache } from '@/runtime/resources/cache'; +import { store as ChartStore } from '../state/chartStore'; +import { SnapshotIn } from 'mobx-state-tree'; +import { Chart } from '../models/Chart'; +import { Metadata } from '../models/Metadata'; + +const NewChartData: SnapshotIn = { + offset: 0, + bpm: [{ + beat: [ 0, 0, 1 ], + bpm: 120, + }], + judgelines: [{ + notes: [], + }], +}; + +export const createNewProject = (metadata: SnapshotIn) => { + if (!runtimeCache.has(metadata.musicFile)) { + throw new Error(`Music file ${metadata.musicFile} not found or not loaded`); + } + + if (!runtimeCache.has(metadata.backgroundFile)) { + throw new Error(`Background file ${metadata.backgroundFile} not found or not loaded`); + } + + const _chart: SnapshotIn = { + ...NewChartData, + metadata: { + name: `Untitled ${Date.now()}`, + artist: 'Unknown', + illustration: 'Unknown', + level: 'SP Lv.?', + designer: 'Unknown', + ...metadata, + }, + }; + + return applySnapshot(ChartStore, _chart); +}; diff --git a/src/core/state/chartStore.ts b/src/core/state/chartStore.ts new file mode 100644 index 0000000..ea75006 --- /dev/null +++ b/src/core/state/chartStore.ts @@ -0,0 +1,7 @@ +import { Chart, IChart } from '@/core/models/Chart'; + +export const store: IChart = Chart.create({ + metadata: null, + offset: 0, + judgelines: [], +}); diff --git a/src/core/timeline/bpm.ts b/src/core/timeline/bpm.ts new file mode 100644 index 0000000..54fd80c --- /dev/null +++ b/src/core/timeline/bpm.ts @@ -0,0 +1,65 @@ +import { store as ChartStore } from '../state/chartStore'; +import { sortByBeat, parseDoublePrecist } from '@/utils/math'; +import { IBPM } from '../models/BPM'; +import { BPMCache } from '../types'; + +export const calculateBPMCache = (bpms: IBPM[]): BPMCache[] => { + const _bpms = bpms.slice().sort(sortByBeat); + const result: BPMCache[] = []; + + for (const bpm of _bpms) { + const timePerBeat = parseDoublePrecist(60 / bpm.bpm, 6, -1); + + if (result.length === 0) { + result.push({ + time: 0, + beat: 0, + timePerBeat, + }); + continue; + } + + const lastBpmCache = result[result.length - 1]!; + result.push({ + time: parseDoublePrecist(( + lastBpmCache.beat + (lastBpmCache.timePerBeat * (bpm.beatNum - lastBpmCache.beat)) + ), 6, -1), + beat: bpm.beatNum, + timePerBeat, + }); + } + + return result; +}; + +export const getBeatByTime = (time: number) => { + const bpms = ChartStore.bpmCache; + let min = 0, max = bpms.length - 1; + + while (min <= max) { + const mid = (min + max) >> 1; + if (bpms[mid].time <= time) min = mid + 1; + else max = mid - 1; + } + + const result = bpms[Math.max(0, min - 1)]; + return parseDoublePrecist(( + result.beat + (time - result.time) / result.timePerBeat + ), 6, -1); +}; + +export const getTimeByBeat = (beat: number) => { + const bpms = ChartStore.bpmCache; + let min = 0, max = bpms.length - 1; + + while (min <= max) { + const mid = (min + max) >> 1; + if (bpms[mid].beat <= beat) min = mid + 1; + else max = mid - 1; + } + + const result = bpms[Math.max(0, min - 1)]; + return parseDoublePrecist(( + result.time + (beat - result.beat) * result.timePerBeat + ), 6, -1); +}; diff --git a/src/core/types.ts b/src/core/types.ts new file mode 100644 index 0000000..0dd4fb6 --- /dev/null +++ b/src/core/types.ts @@ -0,0 +1,21 @@ + +export type BPMCache = { + time: number, + beat: number, + timePerBeat: number, +}; + +export enum KeyframeType { + PositionX = 'positionX', + PositionY = 'positionY', + Rotate = 'rotate', + Alpha = 'alpha', + Speed = 'speed', +}; + +export enum NoteType { + Tap = 'tap', + Drag = 'drag', + Hold = 'hold', + Flick = 'flick', +}; diff --git a/src/core/utils/easings.ts b/src/core/utils/easings.ts new file mode 100644 index 0000000..1e01be6 --- /dev/null +++ b/src/core/utils/easings.ts @@ -0,0 +1,174 @@ + +type EasingFn = (x: number) => number; + +/** + * All these easings comes from https://easings.net/ + * @see https://easings.net/ + */ +const Easings: EasingFn[] = [ + /** 0: Linear */ + (x) => x, + /** 1: In Sine */ + (x) => 1 - Math.cos((x * Math.PI) / 2), + /** 2: Out Sine */ + (x) => Math.sin((x * Math.PI) / 2), + /** 3: In Out Sine */ + (x) => -(Math.cos(Math.PI * x) - 1) / 2, + /** 4: In Quad */ + (x) => x * x, + /** 5: Out Quad */ + (x) => 1 - (1 - x) * (1 - x), + /** 6: In Out Quad */ + (x) => x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2, + /** 7: In Cubic */ + (x) => x * x * x, + /** 8: Out Cubic */ + (x) => 1 - Math.pow(1 - x, 3), + /** 9: In Out Cubic */ + (x) => x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2, + /** 10: In Quart */ + (x) => x * x * x * x, + /** 11: Out Quart */ + (x) => 1 - Math.pow(1 - x, 4), + /** 12: In Out Quart */ + (x) => x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2, + /** 13: In Quint */ + (x) => x * x * x * x * x, + /** 14: Out Quint */ + (x) => 1 - Math.pow(1 - x, 5), + /** 15: In Out Quint */ + (x) => x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2, + /** 16: In Expo */ + (x) => x === 0 ? 0 : Math.pow(2, 10 * x - 10), + /** 17: Out Expo */ + (x) => x === 1 ? 1 : 1 - Math.pow(2, -10 * x), + /** 18: In Out Expo */ + (x) => (x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 + : (2 - Math.pow(2, -20 * x + 10)) / 2), + /** 19: In Circ */ + (x) => 1 - Math.sqrt(1 - Math.pow(x, 2)), + /** 20: Out Circ */ + (x) => Math.sqrt(1 - Math.pow(x - 1, 2)), + /** 21: In Out Circ */ + (x) => (x < 0.5 + ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 + : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2), + /** 22: In Back */ + (x) => { + const c1 = 1.70158; + const c3 = c1 + 1; + + return c3 * x * x * x - c1 * x * x + }, + /** 23: Out Back */ + (x) => { + const c1 = 1.70158; + const c3 = c1 + 1; + + return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); + }, + /** 24: In Out Back */ + (x) => { + const c1 = 1.70158; + const c2 = c1 * 1.525; + + return x < 0.5 + ? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2 + : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; + }, + /** 25: In Elastic */ + (x) => { + const c4 = (2 * Math.PI) / 3; + + return x === 0 + ? 0 + : x === 1 + ? 1 + : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4); + }, + /** 26: Out Elastic */ + (x) => { + const c4 = (2 * Math.PI) / 3; + + return x === 0 + ? 0 + : x === 1 + ? 1 + : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1; + }, + /** 27: In Out Elastic */ + (x) => { + const c5 = (2 * Math.PI) / 4.5; + + return x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 + ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + : (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1; + }, + /** 28: In Bounce */ + (x) => 1 - Easings[29](1 - x), + /** 29: Out Bounce */ + (x) => { + const n1 = 7.5625; + const d1 = 2.75; + + if (x < 1 / d1) { + return n1 * x * x; + } else if (x < 2 / d1) { + return n1 * (x -= 1.5 / d1) * x + 0.75; + } else if (x < 2.5 / d1) { + return n1 * (x -= 2.25 / d1) * x + 0.9375; + } else { + return n1 * (x -= 2.625 / d1) * x + 0.984375; + } + }, + /** 30: In Out Bounce */ + (x) => (x < 0.5 + ? (1 - Easings[29](1 - 2 * x)) / 2 + : (1 + Easings[29](2 * x - 1)) / 2) +]; + +const EasingNames: string[] = [ + 'Linear', + 'In Sine', + 'Out Sine', + 'In Out Sine', + 'In Quad', + 'Out Quad', + 'In Out Quad', + 'In Cubic', + 'Out Cubic', + 'In Out Cubic', + 'In Quart', + 'Out Quart', + 'In Out Quart', + 'In Quint', + 'Out Quint', + 'In Out Quint', + 'In Expo', + 'Out Expo', + 'In Out Expo', + 'In Circ', + 'Out Circ', + 'In Out Circ', + 'In Back', + 'Out Back', + 'In Out Back', + 'In Elastic', + 'Out Elastic', + 'In Out Elastic', + 'In Bounce', + 'Out Bounce', + 'In Out Bounce', +]; + +export default Easings; +export { EasingNames }; +export type { Easings }; diff --git a/src/core/utils/model.ts b/src/core/utils/model.ts new file mode 100644 index 0000000..44ae18f --- /dev/null +++ b/src/core/utils/model.ts @@ -0,0 +1,33 @@ +import { types } from 'mobx-state-tree'; +import { normalizeBeatArray, BeatArrayToNumber } from '@/utils/math'; +import { BeatArray as TBeatArray } from '../../utils/types'; + +export const BeatArray = types.custom({ + name: 'BeatArray', + + fromSnapshot(value: number[]) { + return normalizeBeatArray([ value[0], value[1], value[2] ]); + }, + + toSnapshot(value: TBeatArray) { + return [ ...value ]; + }, + + isTargetType(value: TBeatArray | number[]) { + return ( + value.length === 3 && + !isNaN(BeatArrayToNumber(value as TBeatArray)) + ); + }, + + getValidationMessage(snapshot: number[]) { + if (snapshot.length !== 3) return 'Invalid array length'; + if (isNaN(BeatArrayToNumber(snapshot as TBeatArray))) return 'Array contains NaN'; + if ( + snapshot[0] < 0 || + snapshot[1] < 0 || + snapshot[2] < 1 + ) return 'Array contains invaild number'; + return ''; + } +}); diff --git a/src/main.ts b/src/main.ts index 6e2214d..314f228 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,2 +1,4 @@ -import './App/App'; +import 'setimmediate'; // TODO: This is a workaround for upstream @phifans/audio +import '@/storage/init'; +import '@/runtime/init'; import './ui'; diff --git a/src/renderer/preview/app.ts b/src/renderer/preview/app.ts new file mode 100644 index 0000000..65a7ef0 --- /dev/null +++ b/src/renderer/preview/app.ts @@ -0,0 +1,3 @@ +import { PreviewApplication } from './classes/Application'; + +export const app = new PreviewApplication(); diff --git a/src/renderer/preview/classes/Application.ts b/src/renderer/preview/classes/Application.ts new file mode 100644 index 0000000..bc9fe1c --- /dev/null +++ b/src/renderer/preview/classes/Application.ts @@ -0,0 +1,136 @@ +import { Application, Container } from 'pixi.js'; +import { onPatch } from 'mobx-state-tree'; +import { store as ChartStore } from '@/core/state/chartStore'; +import { runtimeAudioStore } from '@/runtime/audio/state'; +import { skinStore } from '@/runtime/skin/state'; +import { settingsStore } from '@/runtime/settings/state'; +import { UpdateRendererTick } from '../ticker'; +import { JudgeLineSprite } from './JudgeLine'; +import { ApplicationOptions } from 'pixi.js'; +import { IJsonPatch } from 'mobx-state-tree'; +import { Clip } from '@phifans/audio'; +import { PreviewSize } from '@/renderer/types'; +import { Nullable } from '@/utils/types'; + +const JudgeLinePathReg = /^\/judgelines\/(\d+)$/; + +export class PreviewApplication extends Application { + readonly containerGame = new Container(); + readonly containerUI = new Container(); + + readonly size: PreviewSize = { + width: 0, + height: 0, + widthHalf: 0, + heightHalf: 0, + noteScale: 0, + lineScale: 0, + heightPercent: 0, + }; + + private musicClip: Nullable = null; + private lines: JudgeLineSprite[] = []; + private onInitCallbacks: (() => void)[] = []; + + constructor(rendererOptions: Partial = {}) { + super(); + + this.init({ + hello: true, + ...rendererOptions, + }).then(() => { + this.triggerInitCallback(); + + this.stage.addChild(this.containerGame); + this.stage.addChild(this.containerUI); + + this.ticker.add(() => { + UpdateRendererTick( + this.size, + this.lines, + this.musicClip, + this.containerGame, + ); + }); + }); + + onPatch(ChartStore, (patch) => this.handleChartPatch(patch)); + runtimeAudioStore.subscribe((s) => this.musicClip = s.musicClip); + skinStore.subscribe((s) => { + if (s.skinCount === (void 0)) return; + this.updateTexture(); + }); + settingsStore.subscribe(() => { + this.$resize(this.size.width, this.size.height); + }); + } + + $resize(width: number, height: number) { + if (!this.renderer) return; + + this.renderer.resize(width, height); + this.calculatePreviewSize(width, height); + this.render(); + } + + addOnInitCallback(callback: () => void) { + this.onInitCallbacks.push(callback); + + return (() => { + this.onInitCallbacks = this.onInitCallbacks.filter(e => e !== callback); + }); + } + + private handleChartPatch(patch: IJsonPatch) { + if (!patch.path.startsWith('/judgelines')) return; + + if (JudgeLinePathReg.test(patch.path)) { + const [ , idStr ] = JudgeLinePathReg.exec(patch.path)!; + const id = parseInt(idStr); + const lineState = ChartStore.judgelines[id]; + + if (patch.op === 'add') { + const lineSprite = new JudgeLineSprite(lineState, this); + lineSprite.resize(this.size); + this.containerGame.addChild(lineSprite); + this.lines[id] = lineSprite; + } + return; + } + } + + private updateTexture() { + for (const l of this.lines) l.updateTexture(); + } + + private triggerInitCallback() { + for (const cb of this.onInitCallbacks) { + cb(); + } + } + + private calculatePreviewSize(width: number, height: number) { + const settings = settingsStore.getState(); + const { size } = this; + + size.width = width; + size.height = height; + + size.widthHalf = size.width / 2; + size.heightHalf = size.height / 2; + + size.noteScale = size.width / settings['renderer.noteScale']; + size.lineScale = size.width > size.height * 0.75 ? size.height / 18.75 : size.width / 14.0625 + + size.heightPercent = size.height / 1080; + + // Apply size to containers + this.containerGame.x = size.widthHalf; + this.containerGame.y = size.heightHalf; + + // Apply size to lines + for (const line of this.lines) { + line.resize(size); + } + } +}; diff --git a/src/renderer/preview/classes/JudgeLine.ts b/src/renderer/preview/classes/JudgeLine.ts new file mode 100644 index 0000000..b46e6c4 --- /dev/null +++ b/src/renderer/preview/classes/JudgeLine.ts @@ -0,0 +1,215 @@ +import { Sprite, Texture } from 'pixi.js'; +import { onPatch } from 'mobx-state-tree'; +import { PreviewApplication } from './Application'; +import { PlainNoteSprite } from './PlainNote'; +import { LongNoteSprite } from './LongNote'; +import { SnapshotOut } from 'mobx-state-tree'; +import { IDisposer, IJsonPatch } from 'mobx-state-tree'; +import { IJudgeLine } from '@/core/models/JudgeLine'; +import { Keyframe, IKeyframe } from '@/core/models/Keyframe'; +import { FloorPosition, PreviewSize } from '@/renderer/types'; +import { KeyframeType, NoteType } from '@/core/types'; +import { calculateLineValue } from '@/renderer/utils'; +import { parseDoublePrecist } from '@/utils/math'; +import { INote, Note } from '@/core/models/Note'; + +type NoteSprite = PlainNoteSprite | LongNoteSprite; + +type $KeyframeCaches = { + [K in KeyframeType]: IKeyframe[]; +} + +const LineWidthBase = 4000; +const LineHeightBase = 18.75 * 0.008; + +const KeyframeTypeReg = /^\/keyframes\/([a-zA-Z]+)(?:\/(\d+))?(?:\/([a-zA-Z]+))?$/; +const NotePatchReg = /^\/notes(?:\/(\d+))?(?:\/([a-zA-Z]+))?$/; + +const createNoteSprite = (state: INote, line: JudgeLineSprite) => { + if (state.type !== NoteType.Hold) return new PlainNoteSprite(state, line); + else return new LongNoteSprite(state, line); +}; + +export class JudgeLineSprite extends Sprite { + private readonly state: IJudgeLine; + readonly app: PreviewApplication; + private readonly patchDisposer: IDisposer; + + readonly floorPositions: FloorPosition[] = []; + readonly keyframes: $KeyframeCaches = { + [KeyframeType.PositionX]: [], + [KeyframeType.PositionY]: [], + [KeyframeType.Rotate]: [], + [KeyframeType.Alpha]: [], + [KeyframeType.Speed]: [] + }; + readonly notes: NoteSprite[] = []; + + constructor(state: IJudgeLine, app: PreviewApplication) { + super(Texture.WHITE); + + this.state = state; + this.app = app; + this.updateAllKeyframesCache(); + + this.anchor.set(0.5); + + this.patchDisposer = onPatch(this.state, (p, r) => this.handlePatch(p, r)); + } + + updateTexture() { + for (const n of this.notes) n.updateTexture(); + } + + resize(size: PreviewSize) { + this.scale.x = Math.round(LineWidthBase * (size.width / 1350)); + this.scale.y = Math.round(size.lineScale * LineHeightBase); + + for (const n of this.notes) n.resize(size); + } + + $destroy() { + this.patchDisposer(); + this.destroy(); + } + + private handlePatch(patch: IJsonPatch, revPatch: IJsonPatch) { + if (KeyframeTypeReg.test(patch.path)) { + const matches = patch.path.match(KeyframeTypeReg)!; + const type = matches[1] as KeyframeType; + const keyframeIndex = matches[2] as string | undefined; + const prop = matches[3] as keyof SnapshotOut | undefined; + + if ( + (patch.op !== 'replace' || keyframeIndex === (void 0)) || + type === KeyframeType.Speed || + prop === 'beat' + ) { + this.updateKeyframesCache(type); + } + return; + } + + if (NotePatchReg.test(patch.path)) { + const matches = patch.path.match(NotePatchReg)!; + const noteIndex = matches[1] as string | undefined; + const prop = matches[2] as unknown as keyof SnapshotOut | undefined; + + if (!noteIndex) { + if (patch.op === 'replace') { + for (const note of this.notes) note.$destroy(); + this.notes.length = 0; + + const newNoteStates = patch.value as INote[]; + for (const noteState of newNoteStates) { + const noteSprite = createNoteSprite(noteState, this); + noteSprite.resize(this.app.size); + this.notes.push(noteSprite); + } + } + return; + } + + const index = parseInt(noteIndex); + + if (!!prop) { + if (prop === 'type') { + const newType = patch.value! as NoteType; + const prevType = revPatch.value! as NoteType; + + if (newType === NoteType.Hold || prevType === NoteType.Hold) { + this.notes[index].$destroy(); + this.notes[index] = createNoteSprite(this.state.notes[index], this); + this.notes[index].resize(this.app.size); + } + } + return; + } + + if (patch.op === 'add') { + const noteState = this.state.notes[index]; + const noteSprite = createNoteSprite(noteState, this); + noteSprite.resize(this.app.size); + this.notes[index] = noteSprite; + } + else if (patch.op === 'remove') { + const noteSprite = this.notes[index]; + if (!noteSprite) return; + noteSprite.$destroy(); + this.notes.splice(index, 1); + } + return; + } + } + + private updateFloorPositions() { + const keyframes = this.keyframes.speed; + this.floorPositions.length = 0; + + for (let i = 0; i < keyframes.length; i++) { + const keyframe = keyframes[i]; + const nextKeyframe = keyframes[i + 1]; + + if (!nextKeyframe || !nextKeyframe.continuous || nextKeyframe.value === keyframe.value) { + this.floorPositions.push({ + time: keyframe.time, + value: NaN, + }); + continue; + } + + const beatBetween = nextKeyframe.beatNum - keyframe.beatNum; + for (let i = 0, count = Math.ceil(beatBetween / 0.125); i < count; i++) { + const beatPercent = i / count; + const currentTime = keyframe.time * (1 - beatPercent) + nextKeyframe.time * beatPercent; + + this.floorPositions.push({ + time: currentTime, + value: NaN, + }); + } + + this.floorPositions.push({ + time: nextKeyframe.time, + value: NaN, + }); + } + + this.floorPositions.sort((a, b) => a.time - b.time); + if (this.floorPositions[0].time > 0) { + this.floorPositions.unshift({ + time: 0, + value: NaN, + }); + } + + let currentFloorPosition = parseDoublePrecist(( + this.floorPositions[0].time * calculateLineValue(this.floorPositions[0].time, keyframes) + ), 3, -1); + for (let i = 0; i < this.floorPositions.length; i++) { + const event = this.floorPositions[i]; + const eventNext = this.floorPositions[i + 1]; + + event.value = currentFloorPosition; + + if (eventNext) currentFloorPosition = parseDoublePrecist(currentFloorPosition + ( + (eventNext.time - event.time) + calculateLineValue(event.time, keyframes) + ), 3, -1); + } + + for (const n of this.notes) n.updateFloorPosition(); + } + + private updateKeyframesCache(type: KeyframeType) { + this.keyframes[type] = this.state.keyframes[type].slice().sort((a, b) => a.time - b.time); + if (type === KeyframeType.Speed) this.updateFloorPositions(); + } + + private updateAllKeyframesCache() { + this.updateKeyframesCache(KeyframeType.PositionX); + this.updateKeyframesCache(KeyframeType.PositionY); + this.updateKeyframesCache(KeyframeType.Rotate); + this.updateKeyframesCache(KeyframeType.Alpha); + this.updateKeyframesCache(KeyframeType.Speed); + } +} diff --git a/src/renderer/preview/classes/LongNote.ts b/src/renderer/preview/classes/LongNote.ts new file mode 100644 index 0000000..24dd6ba --- /dev/null +++ b/src/renderer/preview/classes/LongNote.ts @@ -0,0 +1,97 @@ +import { Container, Sprite, Texture } from 'pixi.js'; +import { onPatch } from 'mobx-state-tree'; +import { skinStore } from '@/runtime/skin/state'; +import { calculateFloorPosition, calculateLineValue } from '@/renderer/utils'; +import { INote, Note } from '@/core/models/Note'; +import { JudgeLineSprite } from './JudgeLine'; +import { PreviewSize } from '@/renderer/types'; +import { runtimeCache } from '@/runtime/resources/cache'; +import { IDisposer, IJsonPatch, SnapshotIn } from 'mobx-state-tree'; + +const PatchPathReg = /^\/([a-zA-Z]+)$/; + +export class LongNoteSprite extends Container { + readonly state: INote; + readonly line: JudgeLineSprite; + private readonly patchDisposer: IDisposer; + + readonly head = new Sprite(); + readonly body = new Sprite(); + readonly end = new Sprite(); + + floorPosition: number = 0; + endFloorPosition: number = 0; + holdLength: number = 0; + + onLinePosX: number = 0; + onLinePosY: number = 0; + + constructor(state: INote, line: JudgeLineSprite) { + super(); + + this.state = state; + this.line = line; + + this.head.anchor.set(0.5, 0); + this.body.anchor.set(0.5, 1); + this.end.anchor.set(0.5, 1); + this.addChild(this.head, this.body, this.end); + + this.zIndex = 100; + + this.updateFloorPosition(); + this.updateTexture(); + this.patchDisposer = onPatch(this.state, (p) => this.handlePatch(p)); + } + + resize(size: PreviewSize) { + const realLength = this.holdLength * this.state.speed * size.height / size.noteScale; + + this.head.visible = true; + this.body.scale.y = 1; + this.body.height = realLength; + this.end.position.y = -realLength; + + this.scale.set(size.noteScale); + } + + updateFloorPosition() { + this.floorPosition = calculateFloorPosition( + this.state.time, + this.line.floorPositions, + calculateLineValue(this.state.time, this.line.keyframes.speed) + ); + this.endFloorPosition = calculateFloorPosition( + this.state.holdEndTime, + this.line.floorPositions, + calculateLineValue(this.state.holdEndTime, this.line.keyframes.speed) + ); + this.holdLength = this.endFloorPosition - this.floorPosition; + this.resize(this.line.app.size); + } + + updateTexture() { + const { skinCount } = skinStore.getState(); + + this.head.texture = runtimeCache.get(`skin${skinCount}:HoldHead.png`) as Texture; + this.body.texture = runtimeCache.get(`skin${skinCount}:HoldBody.png`) as Texture; + this.end.texture = runtimeCache.get(`skin${skinCount}:HoldEnd.png`) as Texture; + } + + $destroy() { + this.patchDisposer(); + this.destroy({ children: true, texture: false }); + } + + private handlePatch(patch: IJsonPatch) { + const matches = patch.path.match(PatchPathReg); + if (!matches || matches.length <= 1) return; + + const prop = matches[1] as keyof SnapshotIn; + + if (prop === 'beat' || prop === 'holdEndBeat') + this.updateFloorPosition(); + if (prop === 'speed') + this.resize(this.line.app.size); + } +} diff --git a/src/renderer/preview/classes/PlainNote.ts b/src/renderer/preview/classes/PlainNote.ts new file mode 100644 index 0000000..b82e9be --- /dev/null +++ b/src/renderer/preview/classes/PlainNote.ts @@ -0,0 +1,80 @@ +import { Sprite, Texture } from 'pixi.js'; +import { onPatch } from 'mobx-state-tree'; +import { runtimeCache } from '@/runtime/resources/cache'; +import { skinStore } from '@/runtime/skin/state'; +import { calculateLineValue, calculateFloorPosition } from '@/renderer/utils'; +import { IDisposer, IJsonPatch, SnapshotOut } from 'mobx-state-tree'; +import { INote, Note } from '@/core/models/Note'; +import { NoteType } from '@/core/types'; +import { PreviewSize } from '@/renderer/types'; +import { JudgeLineSprite } from './JudgeLine'; + +const PatchPathReg = /^\/([a-zA-Z]+)$/; + +export class PlainNoteSprite extends Sprite { + readonly state: INote; + readonly line: JudgeLineSprite; + private readonly patchDisposer: IDisposer; + + floorPosition: number = 0; + + onLinePosX: number = 0; + onLinePosY: number = 0; + + constructor(state: INote, line: JudgeLineSprite) { + super(); + + this.state = state; + this.line = line; + + this.anchor.set(0.5); + this.zIndex = 100; + + this.updateFloorPosition(); + this.updateTexture(); + this.patchDisposer = onPatch(this.state, (p) => this.handlePatch(p)); + } + + resize(size: PreviewSize) { + this.scale.set(size.noteScale); + } + + updateFloorPosition() { + this.floorPosition = calculateFloorPosition( + this.state.time, + this.line.floorPositions, + calculateLineValue(this.state.time, this.line.keyframes.speed) + ); + } + + updateTexture() { + const { skinCount } = skinStore.getState(); + + let skinName = `skin${skinCount}:Tap.png`; + if (this.state.type === NoteType.Drag) skinName = `skin${skinCount}:Drag.png`; + if (this.state.type === NoteType.Flick) skinName = `skin${skinCount}:Flick.png`; + + const texture = runtimeCache.get(skinName) as Texture | undefined; + if (!texture) return; + + this.texture = texture; + } + + $destroy() { + this.patchDisposer(); + this.destroy(false); + } + + private handlePatch(patch: IJsonPatch) { + const matches = patch.path.match(PatchPathReg); + if (!matches || matches.length <= 1) return; + + const prop = matches[1] as keyof SnapshotOut; + const value = patch.value as SnapshotOut[typeof prop] | undefined; + + if (prop === 'beat') + this.updateFloorPosition(); + if (prop === 'type' && value !== NoteType.Hold) + this.updateTexture(); + } +} diff --git a/src/renderer/preview/ticker.ts b/src/renderer/preview/ticker.ts new file mode 100644 index 0000000..c8d6437 --- /dev/null +++ b/src/renderer/preview/ticker.ts @@ -0,0 +1,102 @@ +import { Clip } from '@phifans/audio'; +import { calculateFloorPosition, calculateLineValue } from '../utils'; +import { Container } from 'pixi.js'; +import { Nullable } from '@/utils/types'; +import { JudgeLineSprite } from './classes/JudgeLine'; +import { LongNoteSprite } from './classes/LongNote'; +import { PreviewSize } from '../types'; +import { NoteType } from '@/core/types'; + +const RADIAN = Math.PI / 180; + +export const UpdateRendererTick = ( + size: PreviewSize, + lines: JudgeLineSprite[], + clip: Nullable, + container: Container +) => { + if (lines.length === 0) return; + if (clip === null) return; + + const { + widthHalf, + heightHalf, + } = size; + const { currentTime } = clip; + + for (const line of lines) { + const { + keyframes, + floorPositions, + notes, + } = line; + + const lineSpeed = calculateLineValue(currentTime, keyframes.speed); + const linePosX = calculateLineValue(currentTime, keyframes.positionX) / 100 * widthHalf; + const linePosY = calculateLineValue(currentTime, keyframes.positionY) / -100 * heightHalf; + const lineRotate = calculateLineValue(currentTime, keyframes.rotate); + const lineAlpha = calculateLineValue(currentTime, keyframes.alpha) / 255; + const lineFloorPosition = calculateFloorPosition(currentTime, floorPositions, lineSpeed); + + const lineRadian = lineRotate * RADIAN; + const lineSinr = Math.sin(lineRadian); + const lineCosr = Math.cos(lineRadian); + + line.x = linePosX; + line.y = linePosY; + line.angle = lineRotate; + line.alpha = lineAlpha; + + for (const note of notes) { + const { state } = note; + + if ( + (state.time <= currentTime) && + (state.type !== NoteType.Hold || state.holdEndTime <= currentTime) + ) { + if (note.parent) note.removeFromParent(); + continue; + } + + const floorPositionDiff = (note.floorPosition - lineFloorPosition) * state.speed; + if (floorPositionDiff > 2 || (floorPositionDiff < 0 && currentTime < state.time)) { + if (note.parent) note.removeFromParent(); + continue; + } + + const posX = widthHalf * (state.positionX / 100); + const posY = floorPositionDiff * size.height * (state.isAbove ? -1 : 1); + const realXSin = posY * lineSinr * -1; + const realYCos = posY * lineCosr; + + note.onLinePosX = posX * lineCosr + linePosX; + note.onLinePosY = posX * lineSinr + linePosY; + + if (state.type === NoteType.Hold && floorPositionDiff <= 0) { + const _n = note as LongNoteSprite; + const currentLength = (_n.holdLength + floorPositionDiff) * state.speed * size.height / size.noteScale; + + if (_n.head.visible) _n.head.visible = false; + _n.body.height = currentLength; + _n.end.position.y = -currentLength; + + _n.position.set( + _n.onLinePosX, + _n.onLinePosY, + ); + } else { + note.position.set( + note.onLinePosX + realXSin, + note.onLinePosY + realYCos, + ); + + if (state.type === NoteType.Hold && !(note as LongNoteSprite).head.visible) + (note as LongNoteSprite).head.visible = true; + } + + note.angle = lineRotate + (state.isAbove ? 0 : 180); + + if (!note.parent) container.addChild(note); + } + } +}; diff --git a/src/renderer/types.ts b/src/renderer/types.ts new file mode 100644 index 0000000..40d762d --- /dev/null +++ b/src/renderer/types.ts @@ -0,0 +1,17 @@ + +export type PreviewSize = { + width: number, + height: number, + widthHalf: number, + heightHalf: number, + + noteScale: number, + lineScale: number, + + heightPercent: number, +}; + +export type FloorPosition = { + time: number, + value: number, +}; diff --git a/src/renderer/utils.ts b/src/renderer/utils.ts new file mode 100644 index 0000000..3d62735 --- /dev/null +++ b/src/renderer/utils.ts @@ -0,0 +1,41 @@ +import Easings from '@/utils/easings'; +import { IKeyframe } from '@/core/models/Keyframe'; +import { FloorPosition } from './types'; + +export const calculateLineValue = (time: number, keyframes: IKeyframe[]) => { + let min = 0, max = keyframes.length - 1; + + while (min <= max) { + const mid = (min + max) >> 1; + if (keyframes[mid].time <= time) min = mid + 1; + else max = mid - 1; + } + + const index = Math.max(0, min - 1); + const keyframe = keyframes[index]; + const keyframeNext = keyframes[index + 1]; + const startValue = keyframe.hasEndValue ? keyframe.endValue : keyframe.value; + + if (!keyframeNext || !keyframeNext.continuous || keyframeNext.value === startValue) { + return keyframe.value; + } + + const timePercentEnd = Easings[keyframeNext.easing]( + (time - keyframe.time) / (keyframeNext.time - keyframe.time) + ); + + return startValue * (1 - timePercentEnd) + keyframeNext.value * timePercentEnd; +}; + +export const calculateFloorPosition = (time: number, floorPositions: FloorPosition[], speed: number = 1) => { + let min = 0, max = floorPositions.length - 1; + + while (min <= max) { + const mid = (min + max) >> 1; + if (floorPositions[mid].time <= time) min = mid + 1; + else max = mid - 1; + } + + const fPos = floorPositions[Math.max(0, min - 1)]; + return fPos.value + (time - fPos.time) * speed; +}; diff --git a/src/runtime/audio/bindChart.ts b/src/runtime/audio/bindChart.ts new file mode 100644 index 0000000..d7c2399 --- /dev/null +++ b/src/runtime/audio/bindChart.ts @@ -0,0 +1,65 @@ +import { onPatch } from 'mobx-state-tree'; +import { channelMusic } from './bus'; +import { runtimeAudioStore, runtimeAudioTimeStore } from './state'; +import { ticker } from './ticker'; +import { store as ChartStore } from '@/core/state/chartStore'; +import { getBeatByTime } from '@/core/timeline/bpm'; +import { runtimeCache } from '@/runtime/resources/cache'; +import { SnapshotIn } from 'mobx-state-tree'; +import { Clip } from '@phifans/audio'; +import { Metadata } from '@/core/models/Metadata'; +import { Nullable } from '@/utils/types'; + +const applyNewClip = (clip?: Clip) => { + if (!clip) { + console.error('You must load audio file(s) before loading a chart file!'); + return; + } + + const { + musicClip: oldClip + } = runtimeAudioStore.getState(); + + if (oldClip) { + oldClip.stop(); + oldClip.channel = null; + } + clip.channel = channelMusic; + + runtimeAudioStore.setState({ + musicClip: clip, + status: -1, + duration: clip.duration, + durationBeat: getBeatByTime(clip.duration), + }); + runtimeAudioTimeStore.setState({ + currentTime: 0, + currentBeat: 0, + }); + if (!ticker.started) ticker.start(); +}; + +// Listen to chart metadata changes +onPatch(ChartStore, (patch) => { + if (patch.path === '/metadata' && patch.op === 'replace') { + const value = patch.value as Nullable>>; + + if (!value) return; + if (!value.musicFile) return; + + return applyNewClip(runtimeCache.get(value.musicFile) as Clip | undefined); + } + + if (patch.path === '/metadata/musicFile' && patch.op === 'replace') { + const value = patch.value as string | undefined; + if (!value) return; + + return applyNewClip(runtimeCache.get(value) as Clip | undefined); + } + + if (patch.path.startsWith('/bpm')) { + const { musicClip } = runtimeAudioStore.getState(); + if (!musicClip) return; + return runtimeAudioStore.setState({ durationBeat: getBeatByTime(musicClip.duration) }); + } +}); diff --git a/src/runtime/audio/bus.ts b/src/runtime/audio/bus.ts new file mode 100644 index 0000000..06f1b4f --- /dev/null +++ b/src/runtime/audio/bus.ts @@ -0,0 +1,8 @@ +import { Bus } from '@phifans/audio'; + +export const bus = new Bus(); + +export const channelMusic = bus.createChannel('music'); + +export const channelEffect = bus.createChannel('effect'); +channelEffect.startTick(); diff --git a/src/runtime/audio/state.ts b/src/runtime/audio/state.ts new file mode 100644 index 0000000..c06fa6e --- /dev/null +++ b/src/runtime/audio/state.ts @@ -0,0 +1,74 @@ +/** + * XXX: Should this state be seperated? + */ +import { createStore } from 'zustand/vanilla'; +import { useStore } from 'zustand'; +import { Clip } from '@phifans/audio'; +import { Nullable } from '@/utils/types'; +import './bindChart'; + +type RuntimeAudioStore = { + musicClip: Nullable, + status: -1 | 0 | 1, + duration: number, + durationBeat: number, + + play: () => void, + pause: () => void, + stop: () => void, + seek: (seconds: number) => void, +}; + +export const runtimeAudioStore = createStore((set, get) => ({ + musicClip: null, + status: -1, + duration: 0, + durationBeat: 0, + + play() { + const { musicClip } = get(); + if (!musicClip) return; + if (musicClip.status !== 1) musicClip.play(); + else musicClip.pause(); + setImmediate(() => set({ status: musicClip.status })); // TODO: This is a workaround for upstream @phifans/audio + }, + + pause() { + const { musicClip } = get(); + if (!musicClip) return; + if (musicClip.status === 1) musicClip.pause(); + else musicClip.play(); + setImmediate(() => set({ status: musicClip.status })); // TODO: This is a workaround for upstream @phifans/audio + }, + + stop() { + const { musicClip } = get(); + if (!musicClip) return; + musicClip.stop(); + setImmediate(() => set({ status: musicClip.status })); // TODO: This is a workaround for upstream @phifans/audio + }, + + seek(seconds: number) { + const { musicClip } = get(); + if (!musicClip) return; + musicClip.seek(seconds); + }, +})); + +export const runtimeAudioTimeStore = createStore<{ + currentTime: number, + currentBeat: number, +}>(() => ({ + currentTime: 0, + currentBeat: 0, +})); + +export const useRuntimeAudioStore = ( + selector: (state: RuntimeAudioStore) => U +) => useStore(runtimeAudioStore, selector); + +export const useRuntimeAudioTime = () => + useStore(runtimeAudioTimeStore, (s) => s.currentTime); + +export const useRuntimeAudioBeat = () => + useStore(runtimeAudioTimeStore, (s) => s.currentBeat); diff --git a/src/runtime/audio/ticker.ts b/src/runtime/audio/ticker.ts new file mode 100644 index 0000000..4d38461 --- /dev/null +++ b/src/runtime/audio/ticker.ts @@ -0,0 +1,20 @@ +import { Ticker } from 'pixi.js'; +import { getBeatByTime } from '@/core/timeline/bpm'; +import { runtimeAudioStore, runtimeAudioTimeStore } from './state'; +export const ticker = new Ticker(); + +ticker.stop(); +ticker.autoStart = false; +ticker.minFPS = ticker.maxFPS = 30; + +ticker.add(() => { + const { + musicClip + } = runtimeAudioStore.getState(); + if (!musicClip) return; + + runtimeAudioTimeStore.setState({ + currentTime: musicClip.currentTime, + currentBeat: getBeatByTime(musicClip.currentTime), + }); +}); diff --git a/src/runtime/database/adatpers/indexeddb.ts b/src/runtime/database/adatpers/indexeddb.ts new file mode 100644 index 0000000..d2d8d8f --- /dev/null +++ b/src/runtime/database/adatpers/indexeddb.ts @@ -0,0 +1,7 @@ +import indexedDBDriver from 'unstorage/drivers/indexedb'; + +export const IndexedDB = () => + indexedDBDriver({ + dbName: 'phifans-editor-database', + storeName: 'database', + }); diff --git a/src/runtime/database/adatpers/tauri.ts b/src/runtime/database/adatpers/tauri.ts new file mode 100644 index 0000000..f23c248 --- /dev/null +++ b/src/runtime/database/adatpers/tauri.ts @@ -0,0 +1,146 @@ +// Reference: https://github.com/unjs/unstorage/blob/main/src/drivers/db0.ts +import Database from '@tauri-apps/plugin-sql'; +import { defineDriver } from 'unstorage'; +import { Nullable } from '@/utils/types'; + +type RowSchema = Array<{ + key: string; + value: V; + created_at: string; + updated_at: string; +}>; + +interface TauriDBOptions { + filename?: string, + tableName?: string, +}; + +const setupTable = (db: Database, tableName: string = 'unstorage') => { + return db.execute(`CREATE TABLE IF NOT EXISTS "${tableName}" ( + key TEXT PRIMARY KEY, + value TEXT, + blob BLOB, + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP + ); + `); +}; + +const DRIVER_NAME = 'tauri'; + +const tauriDBDriver = defineDriver>((opts) => { + const tableName = opts.tableName ?? 'unstorage'; + + let db: Nullable = null; + let setupPromise: Nullable> = null; + let setupDone = false; + const ensureTable = () => { + if (setupDone) return; + + if (!setupPromise) { + setupPromise = Database.load(`sqlite:${opts.filename ?? 'database.db'}`) + .then((_db) => { + db = _db; + return setupTable(db, tableName); + }) + .then(() => { + setupPromise = null; + setupDone = true; + }); + } + + return setupPromise; + }; + + return { + name: DRIVER_NAME, + options: opts, + getInstance: () => db, + + async hasItem(key) { + await ensureTable(); + const result = await db!.select>( + `SELECT EXISTS (SELECT 1 FROM "${tableName}" WHERE key = $1) AS value`, + [ key ] + ); + return (result[0]?.value ?? 0) === 1; + }, + + async getItem(key) { + await ensureTable(); + const result = await db!.select( + `SELECT value FROM "${tableName}" WHERE key = $1`, + [ key ] + ); + return result[0]?.value ?? null; + }, + + async getItemRaw(key) { + await ensureTable(); + const result = await db!.select( + `SELECT blob AS value FROM "${tableName}" WHERE key = $1`, + [ key ] + ); + return result[0]?.value ?? null; + }, + + async setItem(key, value) { + await ensureTable(); + await db!.execute( + `INSERT INTO "${tableName}" (key, value, created_at, updated_at) VALUES ($1, $2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = $2, updated_at = CURRENT_TIMESTAMP`, + [ key, value ] + ); + }, + + async setItemRaw(key, value) { + await ensureTable(); + await db!.execute( + `INSERT INTO "${tableName}" (key, blob, created_at, updated_at) VALUES ($1, $2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET blob = $2, updated_at = CURRENT_TIMESTAMP`, + [ key, value ] + ); + }, + + async removeItem(key) { + await ensureTable(); + await db!.execute( + `DELETE FROM "${tableName}" WHERE key = $1`, + [ key ] + ); + }, + + async getMeta(key) { + await ensureTable(); + const result = await db!.select( + `SELECT created_at, updated_at FROM "${tableName}" WHERE key = $1`, + [ key ] + ); + if (result.length <= 0 || !result[0]) return null; + return { + atime: new Date(result[0].created_at), + mtime: new Date(result[0].updated_at), + }; + }, + + async getKeys(base = "") { + await ensureTable(); + const result = await db!.select( + `SELECT key FROM "${tableName}" WHERE key LIKE $1`, + [ `${base + '%'}` ] + ); + return result.map((e) => e.key); + }, + + async clear() { + await ensureTable(); + await db!.execute( + `DELETE FROM "${tableName}"` + ); + }, + }; +}); + +export const Tauri = () => + tauriDBDriver({ + filename: 'database.db', + tableName: 'database', + }); diff --git a/src/runtime/database/index.ts b/src/runtime/database/index.ts new file mode 100644 index 0000000..4d15d0d --- /dev/null +++ b/src/runtime/database/index.ts @@ -0,0 +1,20 @@ +import { isTauri } from '@tauri-apps/api/core'; +import { createStorage } from 'unstorage'; +import { Tauri } from './adatpers/tauri'; +import { IndexedDB } from './adatpers/indexeddb'; +import { SkinMeta } from '../skin/types'; + +type DatabaseStructure = { + items: { + skins: SkinMeta[], + } +}; + +const Driver = (() => { + if (isTauri()) return Tauri; + return IndexedDB; +})() + +export const db = createStorage({ + driver: Driver(), +}); diff --git a/src/runtime/init.ts b/src/runtime/init.ts new file mode 100644 index 0000000..4578e26 --- /dev/null +++ b/src/runtime/init.ts @@ -0,0 +1,25 @@ +import { db } from './database'; +import { SettingEngine } from './settings/engine'; +import { settingsStore, updateSettings } from './settings/state'; +import { skinStore } from './skin/state'; +import { importFromZip } from './skin/importer'; +import { DefaultSettings } from './settings/defaults'; + +(async () => { + // Init settings + const settings = await SettingEngine.getAll(); + if (!settings) updateSettings(DefaultSettings); + else settingsStore.setState(settings); + + // Init skins + const skins = await db.get('skins'); + const { selectSkin } = skinStore.getState(); + if (!skins || skins.length <= 0) { + await fetch('./skin-default.zip') + .then(e => e.blob()) + .then((e) => importFromZip(e)); + } else { + skinStore.setState({ skins }); + await selectSkin(skins[0].name, skins[0].author); + } +})(); diff --git a/src/runtime/resources/cache.ts b/src/runtime/resources/cache.ts new file mode 100644 index 0000000..ad7bf9b --- /dev/null +++ b/src/runtime/resources/cache.ts @@ -0,0 +1,16 @@ +import { Clip } from '@phifans/audio'; +import { Texture } from 'pixi.js'; +import { LRUCache } from './class'; + +type CacheItem = Texture | Clip; + +export const runtimeCache = new LRUCache({ + maxSize: 100, + onEviction(_key, item) { + if (item instanceof Texture) { + return item.destroy(true); + } else { + return item.destroy(); + } + } +}); diff --git a/src/runtime/resources/class.ts b/src/runtime/resources/class.ts new file mode 100644 index 0000000..5dac254 --- /dev/null +++ b/src/runtime/resources/class.ts @@ -0,0 +1,57 @@ +import QuickLRU, { Options } from 'quick-lru'; + +const SkinKeyReg = /^skin\d+:/; + +export class LRUCache extends QuickLRU { + private readonly _onEviction?: (key: KeyType, value: ValueType) => void; + + constructor(options: Options) { + super(options); + + this._onEviction = options.onEviction; + } + + /** + * Destroy an item. This will also call `onEviction` if set. + */ + destroy(key: KeyType) { + const item = this.get(key); + if (!item) return false; + + if (typeof this._onEviction === 'function') { + this._onEviction(key, item); + } + + return this.delete(key); + } + + /** + * Destroy all items. This will also call `onEviction` if set. + */ + destroyAll() { + for (const [ key, item ] of this.entriesAscending()) { + if (typeof this._onEviction === 'function') { + this._onEviction(key, item); + } + } + + return this.clear(); + } + + /** + * Destroy all items except `skin\d+:`. This will also call `onEviction` if set. + */ + destroyAllWithoutSkin() { + for (const [ key ] of this.entriesAscending()) { + if (SkinKeyReg.test(key)) return; + this.destroy(key); + } + } + + destroySkins() { + for (const [ key ] of this.entriesAscending()) { + if (!SkinKeyReg.test(key)) return; + this.destroy(key); + } + } +} diff --git a/src/runtime/resources/loader/audio.ts b/src/runtime/resources/loader/audio.ts new file mode 100644 index 0000000..d4543ff --- /dev/null +++ b/src/runtime/resources/loader/audio.ts @@ -0,0 +1,14 @@ +import { Clip } from '@phifans/audio'; +import { runtimeCache } from '../cache'; + +export const loadAudio = (file: File | Blob, filename?: string) => new Promise((res, rej) => { + Clip.from(file) + .then((clip) => { + const _filename = (file as File).name ?? filename; + + if (runtimeCache.has(_filename)) runtimeCache.destroy(_filename); + runtimeCache.set(_filename, clip); + res(clip); + }) + .catch(e => rej(e)); +}); diff --git a/src/runtime/resources/loader/image.ts b/src/runtime/resources/loader/image.ts new file mode 100644 index 0000000..e3819db --- /dev/null +++ b/src/runtime/resources/loader/image.ts @@ -0,0 +1,15 @@ +import { Texture } from 'pixi.js'; +import { runtimeCache } from '../cache'; + +export const loadImage = (file: Blob | File, filename?: string) => new Promise((res, rej) => { + window.createImageBitmap(file) + .then((bitmap) => { + const texture = Texture.from(bitmap); + const _filename = (file as File).name ?? filename; + + if (runtimeCache.has(_filename)) runtimeCache.destroy(_filename); + runtimeCache.set(_filename, texture); + res(texture); + }) + .catch(e => rej(e)); +}); diff --git a/src/runtime/resources/loader/index.ts b/src/runtime/resources/loader/index.ts new file mode 100644 index 0000000..f2a0352 --- /dev/null +++ b/src/runtime/resources/loader/index.ts @@ -0,0 +1,29 @@ +import { loadImage } from './image'; +import { loadAudio } from './audio'; + +export const loadFiles = (files: File[]) => new Promise<{ + successed: string[], failed: string[] +}>(async (res) => { + const filesSuccessed: string[] = []; + const filesFailed: string[] = []; + + for (const file of files) { + (await new Promise(() => { + throw new Error('Promice chain'); + }).catch(async () => { + await loadImage(file); + filesSuccessed.push(file.name); + }).catch(async () => { + await loadAudio(file); + filesSuccessed.push(file.name); + }).catch(() => { + console.warn(`Unsupported file: ${file.name}`); + filesFailed.push(file.name); + })); + } + + res({ + successed: filesSuccessed, + failed: filesFailed, + }); +}); diff --git a/src/runtime/settings/defaults.ts b/src/runtime/settings/defaults.ts new file mode 100644 index 0000000..4608862 --- /dev/null +++ b/src/runtime/settings/defaults.ts @@ -0,0 +1,5 @@ +import { Settings } from './types'; + +export const DefaultSettings: Readonly = Object.freeze({ + 'renderer.noteScale': 8080, +}); diff --git a/src/runtime/settings/engine/index.ts b/src/runtime/settings/engine/index.ts new file mode 100644 index 0000000..e905268 --- /dev/null +++ b/src/runtime/settings/engine/index.ts @@ -0,0 +1,9 @@ +import { isTauri } from '@tauri-apps/api/core'; +import { LocalStorageEngine } from './localstorage'; +import { TauriFSEngine } from './tauri'; +import { SettingEngine as TSettingEngine, Settings } from '../types'; + +export const SettingEngine = (() => { + if (isTauri()) return new TauriFSEngine(); + return new LocalStorageEngine(); +})() as TSettingEngine; diff --git a/src/runtime/settings/engine/localstorage.ts b/src/runtime/settings/engine/localstorage.ts new file mode 100644 index 0000000..deca778 --- /dev/null +++ b/src/runtime/settings/engine/localstorage.ts @@ -0,0 +1,65 @@ +import { SettingEngine, TSettings } from '../types'; +import { Nullable } from '@/utils/types'; + +type LocalStorageOptions = { + keyName: string, +}; + +export class LocalStorageEngine extends SettingEngine { + private readonly opts: LocalStorageOptions; + + constructor(options: Partial = {}) { + super(); + + this.opts = { + keyName: 'settings', + ...options, + }; + } + + private getStore(): Nullable { + try { + const storeRaw = window.localStorage.getItem(this.opts.keyName); + if (!storeRaw) return null; + return JSON.parse(storeRaw) as T; + } catch { + return null; + } + } + + private setStore(store: T) { + const storeRaw = JSON.stringify(store); + window.localStorage.setItem(this.opts.keyName, storeRaw); + } + + public get(key: K, defaultValue?: T[K]): Promise> { + return new Promise((res) => { + const store = this.getStore(); + if (!store) return res(null); + res(store[key] ?? defaultValue ?? null); + }); + } + + public set(key: K, value: T[K]): Promise { + return new Promise((res) => { + const store = this.getStore() ?? {} as T; + store[key] = value; + this.setStore(store); + res(); + }); + } + + public getAll(defaultValue?: T): Promise> { + return new Promise((res) => { + const store = this.getStore(); + res(store ?? defaultValue ?? null); + }); + } + + public setAll(settings: T): Promise { + return new Promise((res) => { + this.setStore(settings); + res(); + }); + } +} diff --git a/src/runtime/settings/engine/tauri.ts b/src/runtime/settings/engine/tauri.ts new file mode 100644 index 0000000..573c4ac --- /dev/null +++ b/src/runtime/settings/engine/tauri.ts @@ -0,0 +1,61 @@ +import * as fs from '@tauri-apps/plugin-fs'; +import * as path from '@tauri-apps/api/path'; +import { SettingEngine, TSettings } from '../types'; +import { Nullable } from '@/utils/types'; + +type TauriFSOptions = { + filename: string, +}; + +export class TauriFSEngine extends SettingEngine { + private readonly opts: TauriFSOptions; + + constructor(options: Partial = {}) { + super(); + + this.opts = { + filename: 'settings.json', + ...options, + }; + } + + private async path(...paths: string[]) { + return path.join(await path.appConfigDir(), ...paths); + } + + private async getStore(): Promise> { + try { + const storeRaw = await fs.readTextFile(await this.path(this.opts.filename)); + if (!storeRaw) return null; + return JSON.parse(storeRaw) as T; + } catch { + return null; + } + } + + private async setStore(store: T) { + const storeRaw = JSON.stringify(store, null, 4); + await fs.writeTextFile(await this.path(this.opts.filename), storeRaw); + } + + public async get(key: K, defaultValue?: T[K] | undefined): Promise> { + const store = await this.getStore(); + if (!store) return null; + return store[key] ?? defaultValue ?? null; + } + + public async set(key: K, value: T[K]): Promise { + const store = (await this.getStore()) ?? {} as T; + store[key] = value; + await this.setStore(store); + } + + public async getAll(defaultValue?: T): Promise> { + const store = await this.getStore(); + return store ?? defaultValue ?? null; + } + + public async setAll(settings: T): Promise { + this.setStore(settings); + } +} diff --git a/src/runtime/settings/state.ts b/src/runtime/settings/state.ts new file mode 100644 index 0000000..b7f2f45 --- /dev/null +++ b/src/runtime/settings/state.ts @@ -0,0 +1,37 @@ +import { createStore } from 'zustand/vanilla'; +import { useStore } from 'zustand'; +import { SettingEngine } from './engine'; +import { DefaultSettings } from './defaults'; +import { Settings } from './types'; + +type SettingsStore = Settings & {}; + +export const settingsStore = createStore(() => ({ + ...DefaultSettings, +})); + +export const useSettingsStore = ( + selector: (state: SettingsStore) => U +) => useStore(settingsStore, selector); + +export const updateSettings = (settings: Partial) => { + (new Promise(async (res) => { + settingsStore.setState(() => ({ + ...settings, + })); + + await SettingEngine.setAll(settingsStore.getState()); + res(void 0); + })).then(); +}; + +export const updateSettingsByKey = (key: K, value: Settings[K]) => { + (new Promise(async (res) => { + settingsStore.setState(() => ({ + [key]: value, + })); + + await SettingEngine.set(key, value); + res(void 0); + })).then(); +}; diff --git a/src/runtime/settings/types.ts b/src/runtime/settings/types.ts new file mode 100644 index 0000000..c1f0945 --- /dev/null +++ b/src/runtime/settings/types.ts @@ -0,0 +1,19 @@ +import { Nullable } from '@/utils/types'; + +export type SettingValue = Nullable; + +export type TSettings = Record; + +export abstract class SettingEngine { + public abstract get(key: K, defaultValue?: T[K]): Promise>; + + public abstract set(key: K, value: T[K]): Promise; + + public abstract getAll(defaultValue?: T): Promise>; + + public abstract setAll(settings: T): Promise; +}; + +export type Settings = { + 'renderer.noteScale': number, +}; diff --git a/src/runtime/skin/consts.ts b/src/runtime/skin/consts.ts new file mode 100644 index 0000000..cb138ab --- /dev/null +++ b/src/runtime/skin/consts.ts @@ -0,0 +1,14 @@ + +export const SkinFiles = Object.freeze([ + 'Tap.png', + 'TapHL.png', + 'Drag.png', + 'DragHL.png', + 'HoldHead.png', + 'HoldHeadHL.png', + 'HoldBody.png', + 'HoldBodyHL.png', + 'HoldEnd.png', + 'Flick.png', + 'FlickHL.png', +]); diff --git a/src/runtime/skin/importer.ts b/src/runtime/skin/importer.ts new file mode 100644 index 0000000..0265633 --- /dev/null +++ b/src/runtime/skin/importer.ts @@ -0,0 +1,57 @@ +import JSZip from 'jszip'; +import * as fs from '@zenfs/core/promises'; +import { db } from '../database'; +import { skinStore } from './state'; +import { checkMetaValidation, getSkinFolderName } from './utils'; +import { SkinFiles } from './consts'; +import { SkinMeta } from './types'; + +/** + * Import a skin from a zip file and save them to storage. + * @param zipFile {Blob | string} + * @returns {Promise} + */ +export const importFromZip = (zipFile: Blob, loadAfterImport = true): Promise => + new Promise(async (res, rej) => { + const { skins, selectSkin } = skinStore.getState(); + const zip = await JSZip.loadAsync(zipFile); + + const metaFile = zip.file('skin.json'); + if (!metaFile) return rej('Not a valid skin file'); + + const metaString = await metaFile.async('string'); + if (!checkMetaValidation(metaString)) return rej('Not a valid skin file'); + + const metaJson = JSON.parse(metaString) as SkinMeta; + const folderName = getSkinFolderName(metaJson); + if (!folderName) return rej('Invalid skin name'); + + const pendingBuffers = await Promise.all( + SkinFiles.map((filename) => { + const file = zip.file(filename); + if (!file) return null; + return file.async('uint8array').then(b => ({ filename, buffer: b })); + }).filter(e => e !== null) + ); + if (pendingBuffers.length <= 0) return rej('No skin file(s) found'); + + if (!(await fs.exists(`/skins/${folderName}`))) await fs.mkdir(`/skins/${folderName}`); + if (!(await db.has('skins'))) await db.setItem('skins', []); + + await Promise.all( + pendingBuffers.map((e) => ( + fs.writeFile(`/skins/${folderName}/${e.filename}`, e.buffer) + )) + ); + + const oldIndex = skins.findIndex(e => e.name === metaJson.name && e.author === metaJson.author); + if (oldIndex >= 0) + Object.assign(skins[oldIndex], metaJson); + else + skins.push(metaJson); + await db.set('skins', skins); + skinStore.setState({ skins }); + + if (loadAfterImport) await selectSkin(metaJson.name, metaJson.author); + res(metaJson); + }); diff --git a/src/runtime/skin/state.ts b/src/runtime/skin/state.ts new file mode 100644 index 0000000..0ffca5d --- /dev/null +++ b/src/runtime/skin/state.ts @@ -0,0 +1,69 @@ +import { createStore } from 'zustand/vanilla'; +import { useStore } from 'zustand'; +import * as fs from '@zenfs/core/promises'; +import { getSkinFolderName } from './utils'; +import { loadFiles } from '../resources/loader'; +import { SkinFiles } from './consts'; +import { SkinMeta } from './types'; +import { Nullable } from '@/utils/types'; +import { runtimeCache } from '../resources/cache'; + +type SkinStore = { + skins: SkinMeta[], + currentSkin: Nullable, + skinCount: number, + + selectSkin: (name: string, author?: string) => Promise, +}; + +export const skinStore = createStore((set, get) => ({ + skins: [], + currentSkin: null, + skinCount: -1, + + selectSkin(name: string, author?: string) { + return new Promise(async (res, rej) => { + const { skins, skinCount } = get(); + if (!skins || skins.length <= 0) return rej('No skin stored'); + + const skinIndex = skins.findIndex(e => e.name === name && (author === (void 0) || e.author === author)); + if (skinIndex === -1) return rej('No such skin found'); + + const folderName = getSkinFolderName(skins[skinIndex]); + const pendingBuffers = await Promise.all( + SkinFiles.map((filename) => + fs.readFile(`/skins/${folderName}/${filename}`) + .then((b) => ({ filename, buffer: b })) + ) + ); + + const missingFiles = SkinFiles.filter((e) => pendingBuffers.findIndex(h => h.filename === e) === -1); + if (skinCount < 0 && missingFiles.length > 0) return rej('Missing files in default skin'); + + missingFiles.forEach((filename) => { + const file = runtimeCache.peek(`skin${skinCount}:${filename}`); + if (!file) return; + + runtimeCache.set(`skin${skinCount + 1}:${filename}`, file); + runtimeCache.delete(`skin${skinCount}:${filename}`); + }); + + await loadFiles(pendingBuffers.map((e) => new File([e.buffer as unknown as ArrayBuffer], `skin${skinCount + 1}:${e.filename}`))); + + set({ + currentSkin: skins[skinIndex], + skinCount: (skinCount + 1), + }); + + for (const [ name ] of runtimeCache.entriesAscending()) { + if (name.startsWith(`skin${skinCount}:`)) runtimeCache.destroy(name); + } + + res(skins[skinIndex]); + }); + }, +})); + +export const useSkinStore = ( + selector: (state: SkinStore) => U +) => useStore(skinStore, selector); diff --git a/src/runtime/skin/types.ts b/src/runtime/skin/types.ts new file mode 100644 index 0000000..7d48968 --- /dev/null +++ b/src/runtime/skin/types.ts @@ -0,0 +1,7 @@ + +export type SkinMeta = { + name: string, + author: string, + version: string, + homepage?: string, +}; diff --git a/src/runtime/skin/utils.ts b/src/runtime/skin/utils.ts new file mode 100644 index 0000000..742f763 --- /dev/null +++ b/src/runtime/skin/utils.ts @@ -0,0 +1,20 @@ +import { SkinMeta } from './types'; + +export const checkMetaValidation = (metaString: string) => { + try { + const json = JSON.parse(metaString) as SkinMeta; + return ( + json.name !== (void 0) && + json.author !== (void 0) && + json.version !== (void 0) + ); + } catch (_) { + return false; + } +}; + +export const getSkinFolderName = (meta: SkinMeta) => + `${meta.author} - ${meta.name}` + .replace(/[\/\\?%*:|"<>]/g, '') + .replace(/[\x00-\x1F\x7F]/g, '') + .trim(); diff --git a/src/runtime/state/index.ts b/src/runtime/state/index.ts new file mode 100644 index 0000000..d23a8e2 --- /dev/null +++ b/src/runtime/state/index.ts @@ -0,0 +1,18 @@ + +import { createStore } from 'zustand/vanilla'; +import { useStore } from 'zustand'; +import { createProjectState, ProjectState } from './project'; +import { createPropertyState, PropertyState } from './property'; +import { createSelectionState, SelectionState } from './selection'; + +type RuntimeStore = ProjectState & PropertyState & SelectionState; + +export const runtimeStore = createStore((set, get, state) => ({ + ...createProjectState(set, get, state), + ...createPropertyState(set, get, state), + ...createSelectionState(set, get, state), +})); + +export const useRuntimeStore = ( + selector: (state: RuntimeStore) => U +) => useStore(runtimeStore, selector); diff --git a/src/runtime/state/project.ts b/src/runtime/state/project.ts new file mode 100644 index 0000000..d4ce26e --- /dev/null +++ b/src/runtime/state/project.ts @@ -0,0 +1,15 @@ +import { StateCreator } from 'zustand'; + +export interface ProjectState { + isEditable: boolean; + + setIsEditable: (isEditable: boolean) => void; +}; + +export const createProjectState: StateCreator = (set) => ({ + isEditable: false, + + setIsEditable: (isEditable) => set(({ + isEditable + })), +}); diff --git a/src/runtime/state/property.ts b/src/runtime/state/property.ts new file mode 100644 index 0000000..5e75e5c --- /dev/null +++ b/src/runtime/state/property.ts @@ -0,0 +1,15 @@ +import { StateCreator } from 'zustand'; + +export interface PropertyState { + tempo: number; + + setTempo: (tempo: number) => void; +}; + +export const createPropertyState: StateCreator = (set) => ({ + tempo: 4, + + setTempo: (tempo) => set(({ + tempo, + })), +}); diff --git a/src/runtime/state/selection.ts b/src/runtime/state/selection.ts new file mode 100644 index 0000000..490c460 --- /dev/null +++ b/src/runtime/state/selection.ts @@ -0,0 +1,34 @@ +import { StateCreator } from 'zustand'; +import { Nullable } from '@/utils/types'; + +export interface SelectionState { + selectedLineId: Nullable; + selectedKeyframeId: Nullable; + selectedNoteId: Nullable; + + setSelectedLine: (lineId: Nullable) => void; + setSelectedKeyframe: (keyframeId: Nullable) => void; + setSelectedNote: (noteId: Nullable) => void; +}; + +export const createSelectionState: StateCreator = (set) => ({ + selectedLineId: null, + selectedKeyframeId: null, + selectedNoteId: null, + + setSelectedLine: (lineId) => set(() => ({ + selectedLineId: lineId, + selectedKeyframeId: null, + selectedNoteId: null, + })), + + setSelectedKeyframe: (keyframeId) => set(() => ({ + selectedKeyframeId: keyframeId, + selectedNoteId: null, + })), + + setSelectedNote: (noteId) => set(() => ({ + selectedNoteId: noteId, + selectedKeyframeId: null, + })), +}); diff --git a/src/storage/adapters/indexeddb.ts b/src/storage/adapters/indexeddb.ts new file mode 100644 index 0000000..ed4de1d --- /dev/null +++ b/src/storage/adapters/indexeddb.ts @@ -0,0 +1,7 @@ +import { IndexedDB as Engine } from '@zenfs/dom'; +import { MountConfiguration } from '@zenfs/core'; + +export const IndexedDB: MountConfiguration = { + backend: Engine, + storeName: 'phifans-editor-storage', +}; diff --git a/src/storage/adapters/tauri.ts b/src/storage/adapters/tauri.ts new file mode 100644 index 0000000..cf2a02a --- /dev/null +++ b/src/storage/adapters/tauri.ts @@ -0,0 +1,151 @@ +import { isTauri } from '@tauri-apps/api/core'; +import * as $TauriFS from '@tauri-apps/plugin-fs'; +import * as TauriPath from '@tauri-apps/api/path'; +import { + CreationOptions, + Async, + FileSystem, + InodeLike, + Backend, + MountConfiguration +} from '@zenfs/core'; + +class ErrorWithCode extends Error { + readonly code: string; + + constructor(code: string, message: string) { + super(message); + + this.code = code; + this.name = code; + } +} + +class TauriFSEngine extends Async(FileSystem) { + private async path(...paths: string[]) { + return TauriPath.join(await TauriPath.appConfigDir(), ...paths); + } + + public async ready() { return; } + + public async rename(oldPath: string, newPath: string): Promise { + return $TauriFS.rename(await this.path(oldPath), await this.path(newPath)); + } + + public async stat(path: string): Promise { + if (!await ($TauriFS.exists(await this.path(path)))) { + throw new ErrorWithCode('ENOENT', `No such enrty: ${path}`); + } + + const info = await $TauriFS.stat(await this.path(path)); + return { + size: info.size, + mode: info.mode ?? 0o755, + atimeMs: (info.atime ?? new Date()).getTime(), + mtimeMs: (info.mtime ?? new Date()).getTime(), + ctimeMs: Date.now(), + birthtimeMs: (info.birthtime ?? new Date()).getTime(), + uid: info.uid ?? 1000, + gid: info.gid ?? 1000, + ino: info.ino ?? -1, + nlink: info.nlink ?? 0, + }; + } + + public async createFile(path: string): Promise { + await $TauriFS.create(await this.path(path)); + return await this.stat(path); + } + + public async unlink(path: string): Promise { + return $TauriFS.remove(await this.path(path)); + } + + public async rmdir(path: string): Promise { + return $TauriFS.remove(await this.path(path)); + } + + public async mkdir(path: string, options: CreationOptions): Promise { + const _options: $TauriFS.MkdirOptions = { recursive: true }; + if (options && options.mode) _options.mode = options.mode; + + await $TauriFS.mkdir(await this.path(path), _options); + return await this.stat(path); + } + + public async readdir(path: string): Promise { + const result = await $TauriFS.readDir(await this.path(path)); + throw result.map((e) => e.name); + } + + public async exists(path: string): Promise { + return $TauriFS.exists(await this.path(path)); + } + + public async read(path: string, buffer: Uint8Array, start: number, end: number): Promise { + const file = await $TauriFS.open(await this.path(path), { read: true }); + if ((await file.stat()).size <= 0) return; + + const _buffer = new Uint8Array(end); + await file.read(_buffer); + + const _result = _buffer.subarray(start, end); + buffer.set(_result); + } + + public async write(path: string, buffer: Uint8Array, offset: number): Promise { + const file = await $TauriFS.open(await this.path(path), { write: true, create: true }); + + const _offsetData = new Uint8Array(offset); + await file.read(_offsetData); + + const _result = new Uint8Array(buffer.length + offset); + _result.set(_offsetData, 0); + _result.set(buffer, offset); + + await file.write(_result); + } + + // These are unsupported APIs from Tauri + public async touch(): Promise { return void 0; } + + public async link() { + throw new Error('Tauri does not support symlink'); + } +} + +const _TauriFS = { + name: 'TauriFS', + + options: {}, + + async isAvailable() { + if (!(isTauri() ?? false)) return false; + + try { + const BasePath = await TauriPath.appConfigDir(); + if (!(await $TauriFS.exists(BasePath))) { + await $TauriFS.mkdir(BasePath); + } + + const file = await $TauriFS.create(await TauriPath.join(BasePath, '__zenfs-test')); + return !isNaN(file.rid); + } catch { + return false; + } finally { + await $TauriFS.remove(await TauriPath.join(await TauriPath.appConfigDir(), '__zenfs-test')); + } + }, + + create() { + const fs = new TauriFSEngine(7421, 'tauri'); + return fs; + }, +} as const satisfies Backend; +type _TauriFS = typeof _TauriFS; +interface TauriFS extends _TauriFS {}; +const TauriFS: TauriFS = _TauriFS; + +export const Tauri: MountConfiguration = { + backend: TauriFS, +}; diff --git a/src/storage/init.ts b/src/storage/init.ts new file mode 100644 index 0000000..b06115b --- /dev/null +++ b/src/storage/init.ts @@ -0,0 +1,20 @@ +import { isTauri } from '@tauri-apps/api/core'; +import { configureSingle } from '@zenfs/core'; +import * as fs from '@zenfs/core/promises'; +import { IndexedDB } from './adapters/indexeddb'; +import { Tauri } from './adapters/tauri'; + +const Engine = (() => { + if (isTauri()) return Tauri; + return IndexedDB; +})(); + +await configureSingle(Engine).then(async () => { + if (!(await fs.exists('/charts'))) { + await fs.mkdir('/charts'); + } + + if (!(await fs.exists('/skins'))) { + await fs.mkdir('/skins'); + } +}); diff --git a/src/ui/App.tsx b/src/ui/App.tsx index ac3dfcd..235915c 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -1,137 +1,150 @@ -import TempoContext from './contexts/Tempo'; -import Chart from '@/Chart/Chart'; import PanelDock from './Panel/PanelDock'; import { PopupReadFiles, ReadFileAsText } from '@/utils/file'; +import { Clip } from '@phifans/audio'; import { Nullable } from '@/utils/types'; -import { useCallback, useRef, useState } from 'react'; -import AppBar from './Bar/AppBar'; -import SettingsProvider from './contexts/Settings/Provider'; -import DockLayout from 'rc-dock'; -import SettingsPanel from './Panel/SettingsPanel/SettingsPanel'; -import { ChartExported } from '@/Chart/Chart'; -import NumberInput from './components/NumberInput'; -import TopBar from './Bar/TopBar/TopBar'; -import DialogProvider from './contexts/Dialog/Provider'; +import { useCallback, useState } from 'react'; +import { importFromZip } from '@/runtime/skin/importer'; +import { DockviewApi } from 'dockview'; +import { showCreateProjectDialog } from './dialogs/createProject'; +import { loadFiles } from '@/runtime/resources/loader'; +import { store as ChartStore } from '@/core/state/chartStore'; +import { runtimeCache } from '@/runtime/resources/cache'; +import { MenuBar } from './Bar/MenuBar/MenuBar'; +import { StatusBar } from './Bar/StatusBar/StatusBar'; +import ActivityBar from './Bar/ActivityBar'; +import '@/runtime/audio/state'; function App() { - const dockRef = useRef>(null); - const [ tempo, setTempo ] = useState(4); + const [dockviewApi, setDockviewApi] = useState(); + const [tempo, setTempo] = useState(4); let importedMusic: Nullable = null; - const onImportAudio = () => { - PopupReadFiles(false) + const onImportFiles = () => { + PopupReadFiles(true) .then((files) => { if (!files || files.length === 0) return; - importedMusic = files[0]; + return loadFiles([...files]); + }) + .then((result) => { + console.log(result); }) .catch((e) => console.error(e)); }; - const onCreateChart = () => { - if (!importedMusic) return; - if (Chart.info) return; + const onApplyTestMetadata = () => { + let audioFile: string = ''; + + for (const [name, item] of runtimeCache.entriesDescending()) { + if (!audioFile && item instanceof Clip) { + audioFile = name; + break; + } + } - Chart.create({ - name: 'test', - artist: 'test', - illustration: 'test', - level: 'test', - designer: 'test', - music: importedMusic, - background: importedMusic + ChartStore.updateMetadata({ + musicFile: audioFile, + backgroundFile: 'tHisIsATestFileNamePleaseRemove' }); }; - const onLoadChart = () => { - if (!importedMusic) return; - if (Chart.info) return; - - PopupReadFiles(false) - .then(async (files) => { + const onImportSkin = () => { + PopupReadFiles(true) + .then((files) => { if (!files || files.length === 0) return; - const chartRaw = JSON.parse(await ReadFileAsText(files[0])) as ChartExported; - Chart.load({ - ...chartRaw.info, - music: importedMusic!, - background: importedMusic!, - }, chartRaw); + return importFromZip(files[0]); + }) + .then((result) => { + console.log(result); }) .catch((e) => console.error(e)); }; - const onExportChart = () => { - if (!Chart.info) return; - - const chartJson = Chart.json!; - const chartText = JSON.stringify(chartJson, null, 4); - const chartBlob = new Blob([ chartText ], { type: 'text/json' }); - const chartUrl = URL.createObjectURL(chartBlob); - - const downloadDom = document.createElement('a'); - downloadDom.href = chartUrl; - downloadDom.download = 'exported.json'; - downloadDom.click(); - }; - const handleTempoUpdate = useCallback((tempo: number) => { setTempo(tempo); }, []); const showSettingsPanel = () => { - const dock = dockRef.current; - if (!dock) return; - if (dock.find('settings-panel')) return; - - dock.dockMove({ - id: 'settings-panel', - title: 'Settings', - cached: true, - closable: true, - content: (), - }, null, 'float'); + if (!dockviewApi) return; + const panel = dockviewApi.addPanel({ + id: 'settings', + component: 'settingsPanel', + title: 'Settings' + }); + if (panel) { + dockviewApi.addFloatingGroup(panel); + } }; return ( <> - - - -
- - - - -
- | -
- -
- -
- - - - -
- -
-
-
-
+ showCreateProjectDialog(), + }, + { + key: 'file.open', + label: 'Open', + }, + { + key: 'file.save', + label: 'Save', + }, + { + key: 'file.save_as', + label: 'Save as...', + items: [ + { + key: 'file.save_as.plain', + label: 'Plain text', + }, + { + key: 'file.save_as.csv', + label: 'CSV', + }, + { + key: 'file.save_as.test', + label: 'Click me', + onClick() { + alert('Clicked!'); + }, + } + ] + }, + { + key: 'file.settings', + label: 'Settings', + onClick: () => showSettingsPanel(), + } + ] + }, + { + key: 'edit', + label: 'Edit', + items: [ + { + key: 'edit.undo', + label: 'Undo', + }, + { + key: 'edit.redo', + label: 'Redo', + }, + ] + } + ]} + /> + + ); } diff --git a/src/ui/Bar/ActivityBar.tsx b/src/ui/Bar/ActivityBar.tsx new file mode 100644 index 0000000..1963d77 --- /dev/null +++ b/src/ui/Bar/ActivityBar.tsx @@ -0,0 +1,26 @@ +import NumberInput from '@/ui/components/NumberInput'; +import { HStack, Text } from '@chakra-ui/react'; + +type ActivityBarProps = { + handleTempoUpdate: (tempo: number) => void, +}; + +const ActivityBar = ({ handleTempoUpdate }: ActivityBarProps) => { + return ( + + Set tempo: 1/ + + + ); +}; + +export default ActivityBar; \ No newline at end of file diff --git a/src/ui/Bar/AppBar.tsx b/src/ui/Bar/AppBar.tsx deleted file mode 100644 index e60f8a3..0000000 --- a/src/ui/Bar/AppBar.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import './styles.css'; - -type AppBarProps = { - className?: string, - children?: React.ReactNode, -}; - -const AppBar = ({ - className, - children -}: AppBarProps) => { - return ( -
- {children} -
- ); -}; - -export default AppBar; diff --git a/src/ui/Bar/MenuBar/Entry.tsx b/src/ui/Bar/MenuBar/Entry.tsx new file mode 100644 index 0000000..4ce8a20 --- /dev/null +++ b/src/ui/Bar/MenuBar/Entry.tsx @@ -0,0 +1,30 @@ +import styled from 'styled-components'; +import { Trigger, Menu } from '@radix-ui/react-menubar'; +import { menuItem } from './style'; +import { MenuBarList } from './List'; +import { MenuItem } from './types'; + +const TriggerWrapper = styled(Trigger)(menuItem); + +// TODO: Hotkey +type MenuBarEntryProps = { + label: string, + items: MenuItem[], +}; + +export const MenuBarEntry: React.FC = ({ + label, + items, +}) => { + return ( + + + {label} + + + + ); +}; diff --git a/src/ui/Bar/MenuBar/Item.tsx b/src/ui/Bar/MenuBar/Item.tsx new file mode 100644 index 0000000..766a1f8 --- /dev/null +++ b/src/ui/Bar/MenuBar/Item.tsx @@ -0,0 +1,52 @@ +import styled from 'styled-components'; +import { Item, Sub, SubTrigger } from '@radix-ui/react-menubar'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCaretRight } from '@fortawesome/free-solid-svg-icons'; +import { menuItem } from './style'; +import { themeSystem } from '@/ui/theme/system'; +import { MenuBarList } from './List'; +import { MenuItemProps } from '@chakra-ui/react'; +import { MenuItem } from './types'; + +const ItemWrapper = styled(Item)(menuItem); +const SubTriggerWrapper = styled(SubTrigger)(menuItem); + +const RightSlotIcon = styled(FontAwesomeIcon)(themeSystem.css({ + marginLeft: 'auto', + paddingLeft: '2', + color: 'text', +})); + +// TODO: Hotkey +type MenuBarItemProps = MenuItemProps & { + depth: number, + items?: MenuItem[], +}; + +export const MenuBarItem: React.FC = ({ + children, + depth, + items, + ...rest +}) => { + if (items) { + return ( + + + {children} + + + + + ); + } else { + return ( + + {children} + + ); + } +}; diff --git a/src/ui/Bar/MenuBar/List.tsx b/src/ui/Bar/MenuBar/List.tsx new file mode 100644 index 0000000..89aff63 --- /dev/null +++ b/src/ui/Bar/MenuBar/List.tsx @@ -0,0 +1,60 @@ +import { Portal, Content, SubContent } from '@radix-ui/react-menubar'; +import styled from 'styled-components'; +import { themeSystem } from '@/ui/theme/system'; +import { MenuBarItem } from './Item'; +import { MenuItem } from './types'; +import { MenubarContentProps } from '@radix-ui/react-menubar'; +import { MenuSeparator } from './Separator'; + +const menuContentStyle = themeSystem.css({ + minW: '40', + layerStyle: 'fill.subtle', + p: '3', +}); + +type MenuBarListProps = { + items: MenuItem[], + depth: number, +}; + +const ContentWrapper = styled(Content)(menuContentStyle); +const SubContentWrapper = styled(SubContent)(menuContentStyle); + +export const MenuBarList: React.FC = ({ + items, + depth, +}) => { + const ContentDom = depth === 0 ? ContentWrapper : SubContentWrapper; + + const contentProps: MenubarContentProps = {}; + if (depth === 0) { + contentProps.align = 'start'; + contentProps.sideOffset = 5; + contentProps.alignOffset = -3; + } else { + contentProps.alignOffset = -5; + } + + return ( + + + {items.map((item, index) => ( + item.type && item.type === 'separator' ? ( + + ) : ( + + ) + ))} + + + ); +}; diff --git a/src/ui/Bar/MenuBar/MenuBar.tsx b/src/ui/Bar/MenuBar/MenuBar.tsx new file mode 100644 index 0000000..dddfedd --- /dev/null +++ b/src/ui/Bar/MenuBar/MenuBar.tsx @@ -0,0 +1,40 @@ +import styled from 'styled-components'; +import { Root } from '@radix-ui/react-menubar'; +import { themeSystem } from '@/ui/theme/system'; +import { MenuBarEntry } from './Entry'; +import { MenuEntry } from './types'; + +const RootWraper = styled(Root)(themeSystem.css({ + display: 'flex', + layerStyle: 'fill.subtle', +})); + +const Icon = styled.img(themeSystem.css({ + width: '0.7rem', + mx: '4', + my: 'auto', + alignSelf: 'center', + justifySelf: 'center', + borderRadius: '50%', +})); + +type MenuBarProps = { + entries: MenuEntry[], +}; + +export const MenuBar: React.FC = ({ + entries +}) => { + return ( + + + {entries.map((entry) => ( + + ))} + + ); +}; diff --git a/src/ui/Bar/MenuBar/Separator.tsx b/src/ui/Bar/MenuBar/Separator.tsx new file mode 100644 index 0000000..c04fa88 --- /dev/null +++ b/src/ui/Bar/MenuBar/Separator.tsx @@ -0,0 +1,15 @@ +import styled from 'styled-components'; +import { Separator } from '@radix-ui/react-menubar'; +import { themeSystem } from '@/ui/theme/system'; + +const SeparatorWrapper = styled(Separator)(themeSystem.css({ + height: '1px', + backgroundColor: 'border', + margin: '2', +})); + +export const MenuSeparator = () => { + return ( + + ); +}; diff --git a/src/ui/Bar/MenuBar/style.ts b/src/ui/Bar/MenuBar/style.ts new file mode 100644 index 0000000..a15b164 --- /dev/null +++ b/src/ui/Bar/MenuBar/style.ts @@ -0,0 +1,18 @@ +import { themeSystem } from '@/ui/theme/system'; + +export const menuItem = themeSystem.css({ + layerStyle: 'fill.subtle', + textStyle: 'sm', + px: '3', + py: '1', + gap: '2', + alignItems: 'center', + justifyContent: 'space-between', + display: 'flex', + lineHeight: '1.5', + outline: 'none', + userSelect: 'none', + '&[data-highlighted]': { layerStyle: 'fill.muted' }, + '&[data-state="open"]': { layerStyle: 'fill.muted' }, + '&:hover': { layerStyle: 'fill.muted' } +}); diff --git a/src/ui/Bar/MenuBar/types.ts b/src/ui/Bar/MenuBar/types.ts new file mode 100644 index 0000000..5e87b3d --- /dev/null +++ b/src/ui/Bar/MenuBar/types.ts @@ -0,0 +1,13 @@ + +export type MenuItem = { + type?: string, + key: string, + label: string, + hotkey?: string, // TODO + onClick?: () => void, // XXX + items?: MenuItem[], // XXX +} & { type?: 'separator' }; + +export type MenuEntry = Partial> & Omit & { + items: MenuItem[], +}; diff --git a/src/ui/Bar/StatusBar/StatusBar.tsx b/src/ui/Bar/StatusBar/StatusBar.tsx new file mode 100644 index 0000000..a613d08 --- /dev/null +++ b/src/ui/Bar/StatusBar/StatusBar.tsx @@ -0,0 +1,10 @@ +import { HStack } from '@chakra-ui/react'; +import { StatusBarTempo } from './Tempo'; + +export const StatusBar: React.FC = () => { + return ( + + + + ); +}; diff --git a/src/ui/Bar/StatusBar/Tempo.tsx b/src/ui/Bar/StatusBar/Tempo.tsx new file mode 100644 index 0000000..405d608 --- /dev/null +++ b/src/ui/Bar/StatusBar/Tempo.tsx @@ -0,0 +1,22 @@ +import { HStack, Text } from '@chakra-ui/react'; +import NumberInput from '@/ui/components/NumberInput'; +import { useRuntimeStore } from '@/runtime/state'; + +export const StatusBarTempo: React.FC = () => { + return ( + + {/* TODO: Better style */} + Tempo: 1/ + s.tempo)} + step={1} + dragStep={1} + onChanged={useRuntimeStore(s => s.setTempo)} + style={{ + width: 50 + }} + /> + + ); +}; diff --git a/src/ui/Bar/TopBar/Menu/File/CreateProject.tsx b/src/ui/Bar/TopBar/Menu/File/CreateProject.tsx deleted file mode 100644 index ed8edc5..0000000 --- a/src/ui/Bar/TopBar/Menu/File/CreateProject.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { Button, DialogBody, DialogFooter, FileInput, FormGroup, MenuItem } from '@blueprintjs/core'; -import { useRef } from 'react'; -import Chart from '@/Chart/Chart'; -import ProjectPanel from '@/ui/Panel/ProjectPanel/ProjectPanel'; -import { useDialog } from '@/ui/contexts/Dialog'; -import { ChartInfo } from '@/Chart/types'; -import { Nullable } from '@/utils/types'; - -type ProjectFile = 'music' | 'background'; - -type ProjectMeta = ChartInfo & { - music: File, - background: File, -}; - -const CreateProject = () => { - const { show: showDialog, close: closeDialog } = useDialog(); - const newProjectMeta = useRef>({}); - - const updateNewProjectMeta = (metaOrFile: Nullable, fileType?: ProjectFile) => { - if (!metaOrFile) return; - if (!(metaOrFile instanceof FileList)) { - newProjectMeta.current = { - ...newProjectMeta.current, - ...metaOrFile, - }; - return; - } - - if (!fileType) return; - if (metaOrFile.length !== 1) return; - const file = metaOrFile[0]; - - newProjectMeta.current[fileType] = file; - }; - - const createProject = () => { - closeDialog(); - - const projectMeta: ChartInfo & Partial = { - name: 'Untitled', - artist: 'Unknown', - illustration: 'Unknown', - level: 'SP Lv.?', - designer: 'Unknown', - ...newProjectMeta.current, - }; - - if (!projectMeta.music) { - showDialog({ - title: 'Error', - onClose: closeDialog, - children: ( - <> - No music file selected - Close - } - /> - - ) - }); - return; - } - - if (!projectMeta.background) { - showDialog({ - title: 'Error', - onClose: closeDialog, - children: ( - <> - No background file selected - Close - } - /> - - ) - }); - return; - } - - Chart.create(projectMeta as ProjectMeta); - } - - const handleOpenDialog = () => { - showDialog({ - title: 'Create Project', - onClose: closeDialog, - children: ( - <> - - - updateNewProjectMeta((e.target as HTMLInputElement).files, 'music')} - fill - /> - - - - updateNewProjectMeta((e.target as HTMLInputElement).files, 'background')} - fill - /> - - - - - - - - - )} - /> - - ) - }); - }; - - return ( - - ); -}; - -export default CreateProject; diff --git a/src/ui/Bar/TopBar/Menu/File/File.tsx b/src/ui/Bar/TopBar/Menu/File/File.tsx deleted file mode 100644 index d56206b..0000000 --- a/src/ui/Bar/TopBar/Menu/File/File.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Button, Menu, Popover } from '@blueprintjs/core'; -import CreateProject from './CreateProject'; - -const MenuFile = () => { - return ( - - - - )} - usePortal={false} - lazy={true} - minimal - > - - - + + + + + + {t('common.offset')} + + + + + + + + + + + + + + + ); -}; +}); export default BPMPanel; diff --git a/src/ui/Panel/BPMPanel/Item.tsx b/src/ui/Panel/BPMPanel/Item.tsx index 7203e26..a66930b 100644 --- a/src/ui/Panel/BPMPanel/Item.tsx +++ b/src/ui/Panel/BPMPanel/Item.tsx @@ -1,66 +1,80 @@ -import Chart from '@/Chart/Chart'; -import ChartBPM from "@/Chart/BPM"; +import { observer } from 'mobx-react-lite'; +import { commandBus } from '@/core/command/bus'; +import { store as ChartStore } from '@/core/state/chartStore'; +import { MoveBPMCommand } from '@/core/command/bpm/move'; +import { ChangeBPMCommand } from '@/core/command/bpm/change'; +import { RemoveBPMCommand } from '@/core/command/bpm/remove'; import BeatInput from '@/ui/components/BeatInput'; import NumberInput from '@/ui/components/NumberInput'; -import { useCallback } from "react"; import { useTranslation } from 'react-i18next'; import { BeatArray } from "@/utils/types"; -import { Button } from "@blueprintjs/core"; +import { Button, Grid, GridItem, Text } from "@chakra-ui/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faXmark } from "@fortawesome/free-solid-svg-icons"; +import { IBPM } from '@/core/models/BPM'; -type BPMListItemProps = { - bpm: ChartBPM, - onChanged: (beat?: BeatArray, bpm?: number) => void, -}; - -const BPMListItem = ({ +const BPMListItem = observer<{ + bpm: IBPM, +}>(({ bpm, - onChanged, -}: BPMListItemProps) => { +}) => { const { t } = useTranslation(); - const handleUpdate = useCallback((beat?: BeatArray, bpm?: number) => { - onChanged(beat, bpm); - }, [onChanged]); + const handleMove = (beat: BeatArray) => { + commandBus.dispatch( + new MoveBPMCommand().execute(ChartStore, bpm.id, beat) + ); + }; + + const handleChange = (newBPM: number) => { + commandBus.dispatch( + new ChangeBPMCommand().execute(ChartStore, bpm.id, newBPM) + ); + }; const handleDelete = () => { - Chart.removeBPM(bpm.id); + commandBus.dispatch( + new RemoveBPMCommand().execute(ChartStore, bpm.id) + ); }; return ( -
-
-
-
{t('common.time')}
-
- handleUpdate(e)} - className='bpm-input-beat' - /> -
-
-
-
BPM
-
- handleUpdate((void 0), e)} - /> -
-
-
-
- -
-
+ + + BPM + + + + + ) -}; +}); export default BPMListItem; diff --git a/src/ui/Panel/BPMPanel/List.tsx b/src/ui/Panel/BPMPanel/List.tsx index a586677..5651cc8 100644 --- a/src/ui/Panel/BPMPanel/List.tsx +++ b/src/ui/Panel/BPMPanel/List.tsx @@ -1,32 +1,28 @@ -import { useCallback } from "react"; -import Chart from '@/Chart/Chart'; -import ChartBPM from "@/Chart/BPM"; +import { observer } from "mobx-react-lite"; import BPMListItem from "./Item"; -import { BeatArray } from "@/utils/types"; +import { sortByBeat } from "@/utils/math"; +import { IBPM } from "@/core/models/BPM"; +import { Stack, For, StackSeparator } from "@chakra-ui/react"; -type BPMListProps = { - bpms: ChartBPM[], -}; - -const BPMList = ({ +const BPMList = observer<{ + bpms: IBPM[], +}>(({ bpms -}: BPMListProps) => { - const handleBPMChanged = useCallback((id: string, beat?: BeatArray, bpm?: number) => { - if (!Chart.info) return; - Chart.editBPM(id, beat, bpm); - }, []); +}) => { + const bpmList = bpms.slice().sort(sortByBeat); return ( -
- {bpms.map((bpm) => ( - handleBPMChanged(bpm.id, newBeat, newBPM)} - key={bpm.id} - /> - ))} -
+ } p="2"> + + {(bpm) => ( + + )} + + ); -}; +}); export default BPMList; diff --git a/src/ui/Panel/BPMPanel/styles.css b/src/ui/Panel/BPMPanel/styles.css deleted file mode 100644 index 44a3dc7..0000000 --- a/src/ui/Panel/BPMPanel/styles.css +++ /dev/null @@ -1,57 +0,0 @@ -.bpm-panel { - display: flex; - width: 100%; - height: 100%; - overflow: hidden; - flex-direction: column; - align-items: stretch; - flex-wrap: nowrap; -} - -.bpm-offset { - padding: 8px; - padding-bottom: 0; -} - -.bpm-list { - overflow-x: hidden; - overflow-y: auto; - flex: 1; -} - -.bpm-list-item { - display: flex; - padding: 8px; - border-bottom: 1px solid var(--border-primary-color); - box-sizing: border-box; - align-items: center; -} -.bpm-list-item:last-child { - border-bottom: unset; -} -.bpm-list-item .bpm-props { - flex: 1; -} - -.bpm-prop { - display: flex; - flex-direction: row; - align-items: center; - flex-wrap: nowrap; -} - -.bpm-prop .bpm-prop-name { - flex: 0.4; -} - -.bpm-prop .bpm-prop-input { - flex: 0.6; -} - -.bpm-offset .bpm-prop .bpm-prop-name { - flex: 0.354; -} - -.bpm-offset .bpm-prop .bpm-prop-input { - flex: 0.646; -} diff --git a/src/ui/Panel/Components.tsx b/src/ui/Panel/Components.tsx new file mode 100644 index 0000000..06f386a --- /dev/null +++ b/src/ui/Panel/Components.tsx @@ -0,0 +1,18 @@ +import { IDockviewPanelProps } from 'dockview'; +import NotePanel from './NotePanel/NotePanel'; +import PreviewPanel from './PreviewPanel/PreviewPanel'; +import Timeline from './TimelinePanel/Timeline'; +import EditPanel from './EditPanel/EditPanel'; +import BPMPanel from './BPMPanel/BPMPanel'; +import SettingsPanel from './SettingsPanel/SettingsPanel'; + +const Components = { + notePanel: (_: IDockviewPanelProps) => , + previewPanel: (_: IDockviewPanelProps) => , + timeline: (_: IDockviewPanelProps) => , + editPanel: (_: IDockviewPanelProps) => , + bpmPanel: (_: IDockviewPanelProps) => , + settingsPanel: (_: IDockviewPanelProps) => , +}; + +export default Components; \ No newline at end of file diff --git a/src/ui/Panel/DefaultLayout.tsx b/src/ui/Panel/DefaultLayout.tsx new file mode 100644 index 0000000..698024b --- /dev/null +++ b/src/ui/Panel/DefaultLayout.tsx @@ -0,0 +1,56 @@ +import { DockviewApi } from 'dockview'; + +const DefaultLayout = (api: DockviewApi) => { + console.log('Creating default layout'); + api.clear(); + + const timeline = api.addPanel({ + id: 'timeline', + component: 'timeline', + title: 'Timeline', + }); + + const notePanel = api.addPanel({ + id: 'note-panel', + component: 'notePanel', + title: 'Note panel', + initialWidth: 300, + position: { + referencePanel: timeline, + direction: 'above' + }, + }); + + const previewPanel = api.addPanel({ + id: 'live-preview', + component: 'previewPanel', + title: 'Live preview', + position: { + referencePanel: notePanel, + direction: 'right' + }, + }); + + const editPanel = api.addPanel({ + id: 'edit-panel', + component: 'editPanel', + title: 'Edit panel', + position: { + referencePanel: previewPanel, + direction: 'right' + }, + }); + + const bpmPanel = api.addPanel({ + id: 'bpm-panel', + component: 'bpmPanel', + title: 'BPM', + position: { + referencePanel: editPanel + }, + }); + + previewPanel.api.setActive(); +}; + +export default DefaultLayout; \ No newline at end of file diff --git a/src/ui/Panel/EditPanel/EditPanel.tsx b/src/ui/Panel/EditPanel/EditPanel.tsx index fd9abb2..6d8731f 100644 --- a/src/ui/Panel/EditPanel/EditPanel.tsx +++ b/src/ui/Panel/EditPanel/EditPanel.tsx @@ -1,134 +1,58 @@ -import { useCallback, useEffect, useState } from 'react'; +import { observer } from 'mobx-react-lite'; +import { resolveIdentifier } from 'mobx-state-tree'; import { useTranslation } from 'react-i18next'; -import { useSelectedItem } from '../../contexts/SelectedItem'; +import { useRuntimeStore } from '@/runtime/state'; +import { store as ChartStore } from '@/core/state/chartStore'; import List from './List'; -import { BeatArray, Nullable } from '@/utils/types'; -import ChartKeyframe, { TChartKeyframe } from '@/Chart/Keyframe'; -import ChartNote, { ChartNoteProps } from '@/Chart/Note'; import { KeyframePanelBuilderSingle, NotePanelBuilderSingle } from './builder'; -import { TChartJudgelineProps } from '@/Chart/JudgelineProps'; -import './styles.css'; +import { Box, Text, AbsoluteCenter } from '@chakra-ui/react'; +import { Note } from '@/core/models/Note'; +import { Keyframe } from '@/core/models/Keyframe'; +import { UpdatedProps, UpdatedKeyframeProps, UpdatedNoteProps } from './types'; - -type ItemKeyframeSub = { - type: keyof TChartJudgelineProps, - keyframe: ChartKeyframe, -} - -type ItemKeyframe = { - type: 'keyframe', - item: ItemKeyframeSub | ItemKeyframeSub[], -}; - -type ItemNote = { - type: 'note', - item: ChartNote | ChartNote[], -}; - -type Item = ItemKeyframe | ItemNote; - -const EditPanel: React.FC = () => { +// TODO: Multi-select +const EditPanel = observer(() => { const { t } = useTranslation(); - const [ selectedItem, ] = useSelectedItem()!; - const [ item, setItem ] = useState>(null); - const [ id, setId ] = useState>(null); - - const handleValueChanged = useCallback((newProp: Record) => { - if (!selectedItem || !item) return; - - const { line } = selectedItem; - if (item.type === 'note') { - if (item.item instanceof Array) { - // TODO: Multi note edit - } else { - line.editNote( - item.item.id, - newProp as unknown as ChartNoteProps - ); - } - } - - if (item.type === 'keyframe') { - if (item.item instanceof Array) { - // TODO: Multi keyframe edit - } else { - line.editKeyframe( - item.item.type, - item.item.keyframe.id, - newProp as unknown as TChartKeyframe - ); - } - } - }, [item, selectedItem]); - - useEffect(() => { - if ( - !selectedItem || ( - selectedItem.note === null && - selectedItem.keyframe === null - ) - ) { - setItem(null); - setId(null); - return; - } - - const { line } = selectedItem; - if (selectedItem.keyframe !== null) { - if (selectedItem.keyframe instanceof Array) { - // TODO: Multi keyframe edit - } else { - const keyframe = line.findKeyframeById(selectedItem.keyframe.type, selectedItem.keyframe.id); - if (!keyframe) return; - setItem({ - type: 'keyframe', - item: { - type: selectedItem.keyframe.type, - keyframe, - }, - }); - setId(keyframe.id); - } - } + const selectedNoteId = useRuntimeStore((s) => s.selectedNoteId); + const selectedKeyframeId = useRuntimeStore((s) => s.selectedKeyframeId); + + const selectedNote = selectedNoteId && resolveIdentifier(Note, ChartStore, selectedNoteId); + const selectedKeyframe = selectedKeyframeId && resolveIdentifier(Keyframe, ChartStore, selectedKeyframeId); - if (selectedItem.note !== null) { - if (selectedItem.note instanceof Array) { - // TODO: Multi note edit - } else { - const note = line.findNoteById(selectedItem.note.id); - if (!note) return; - setItem({ type: 'note', item: note }); - setId(note.id); - } - } - }, [selectedItem]); + const handleValueChanged = (newProp: UpdatedProps) => { + if (selectedNote) + return selectedNote.updateProps(newProp as UpdatedNoteProps); + if (selectedKeyframe) + return selectedKeyframe.updateProps(newProp as UpdatedKeyframeProps); + }; return ( -
- {item ? ( - - ): ( -
- {t('edit_panel.placeholder')} -
+ + {( + selectedNote ? ( + + ) : + selectedKeyframe ? ( + + ) : ( + + + {t('edit_panel.placeholder')} + + + ) )} -
+ ) -}; +}); export default EditPanel; diff --git a/src/ui/Panel/EditPanel/Input/Input.tsx b/src/ui/Panel/EditPanel/Input/Input.tsx index b79ce2d..3884f90 100644 --- a/src/ui/Panel/EditPanel/Input/Input.tsx +++ b/src/ui/Panel/EditPanel/Input/Input.tsx @@ -1,5 +1,6 @@ import { Nullable } from "@/utils/types"; import { useCallback, useEffect, useRef, useState } from "react"; +import { Input } from "@chakra-ui/react"; type InputProps = { placeholder?: string, @@ -43,7 +44,8 @@ const EditPanelInput = ({ }, [defaultValue]); return ( - ; @@ -12,19 +11,16 @@ const EditPanelBeat = ({ onChanged, defaultValue, }: BeatProps) => { - const [ value, setValue ] = useState(defaultValue ?? [ 0, 0, 1 ]); - - const handleInput = useCallback((newBeat: BeatArray) => { + const handleInput = (newBeat: BeatArray) => { if (isNaN(BeatArrayToNumber(newBeat))) return; - setValue(newBeat); onChanged(newBeat); - }, [onChanged]); + }; return ( - + ) diff --git a/src/ui/Panel/EditPanel/Item/Boolean.tsx b/src/ui/Panel/EditPanel/Item/Boolean.tsx index 305a2cc..f67a5e8 100644 --- a/src/ui/Panel/EditPanel/Item/Boolean.tsx +++ b/src/ui/Panel/EditPanel/Item/Boolean.tsx @@ -1,5 +1,6 @@ import { useCallback } from 'react'; import Container from './Container'; +import { Checkbox } from '@chakra-ui/react'; import { PanelItemPropsBase } from '../types'; export type BooleanProps = PanelItemPropsBase; @@ -9,18 +10,20 @@ const EditPanelBoolean = ({ onChanged, defaultValue, }: BooleanProps) => { - const handleInput = useCallback((e: React.ChangeEvent) => { - const target = e.target as HTMLInputElement; - onChanged(target.checked); + const handleInput = useCallback((e: { checked: string | boolean }) => { + onChanged(e.checked as boolean); }, [onChanged]); return ( - + onCheckedChange={handleInput} + my="2" + > + + + ); }; diff --git a/src/ui/Panel/EditPanel/Item/Combobox.tsx b/src/ui/Panel/EditPanel/Item/Combobox.tsx new file mode 100644 index 0000000..6784eeb --- /dev/null +++ b/src/ui/Panel/EditPanel/Item/Combobox.tsx @@ -0,0 +1,83 @@ +import Container from './Container'; +import { PanelItemPropsBase } from '../types'; +import { Portal, Combobox, useFilter, useListCollection } from "@chakra-ui/react" + +export type ComboboxPropsBase = PanelItemPropsBase & { + type?: 'string' | 'number', + options: { + value: string | number, + label: string, + disabled?: boolean, + }[], +}; + +export type ComboboxPropsNumber = ComboboxPropsBase & { + type: 'number', + onChanged: (newValue: number) => void, + options: { + value: number, + label: string, + disabled?: boolean, + }[], + defaultValue?: number, +}; + +export type ComboboxProps = ComboboxPropsBase | ComboboxPropsNumber; + +const EditPanelCombobox = ({ + label, + options, + type = 'string', + onChanged, + defaultValue, +}: ComboboxProps) => { + const { contains } = useFilter({ sensitivity: "base" }) + const { collection, filter } = useListCollection({ + initialItems: options, + filter: contains, + }) + + const handleValueChanged = (e: string) => { + if (type === 'number') { + const newValue = parseFloat(e); + if (isNaN(newValue)) return; + onChanged(newValue); + } else { + onChanged(e); + } + }; + + return ( + + handleValueChanged(e.value[0])} + defaultValue={[defaultValue as string]} + onInputValueChange={(e) => filter(e.inputValue)} + > + + + + + + + + + + No items found + {collection.items.map((item: any) => ( + + {item.label} + + + ))} + + + + + + ); +}; + +export default EditPanelCombobox; diff --git a/src/ui/Panel/EditPanel/Item/Container.tsx b/src/ui/Panel/EditPanel/Item/Container.tsx index a82d394..1584d11 100644 --- a/src/ui/Panel/EditPanel/Item/Container.tsx +++ b/src/ui/Panel/EditPanel/Item/Container.tsx @@ -1,3 +1,5 @@ +import { Field, Grid, GridItem, Text, Box } from '@chakra-ui/react'; + type ItemContainerProps = { title: string, children: React.ReactNode, @@ -8,22 +10,24 @@ type ItemContainerProps = { const EditPanelItemContainer = ({ title, children, - className, - useLabel, + useLabel = false }: ItemContainerProps) => { const childrenDom = ( - <> -
{title}
-
{children}
- + + + {useLabel ? {title} : {title}} + + {children} + ); return ( - <> - {useLabel ? - : -
{childrenDom}
} - + useLabel ? {childrenDom} : {childrenDom} ); }; diff --git a/src/ui/Panel/EditPanel/Item/Dropdown.tsx b/src/ui/Panel/EditPanel/Item/Dropdown.tsx index 65a0d59..00fe3b2 100644 --- a/src/ui/Panel/EditPanel/Item/Dropdown.tsx +++ b/src/ui/Panel/EditPanel/Item/Dropdown.tsx @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import Container from './Container'; import { PanelItemPropsBase } from '../types'; +import { NativeSelect } from "@chakra-ui/react" export type DropdownPropsBase = PanelItemPropsBase & { type?: 'string' | 'number', @@ -38,19 +39,22 @@ const EditPanelDropdown = ({ }, [type, onChanged]); return ( - - + + + + {options.map((option) => { + return + })} + + + ); }; diff --git a/src/ui/Panel/EditPanel/Item/Number.tsx b/src/ui/Panel/EditPanel/Item/Number.tsx index 9f421c2..5805d66 100644 --- a/src/ui/Panel/EditPanel/Item/Number.tsx +++ b/src/ui/Panel/EditPanel/Item/Number.tsx @@ -1,8 +1,7 @@ import Container from './Container'; import Input from '@/ui/components/NumberInput'; -import Slider from '../Input/Slider'; +import { Slider } from '@chakra-ui/react'; import { PanelItemPropsBase } from "../types"; -import { useCallback, useState } from 'react'; export type NumberProps = PanelItemPropsBase & { min?: number, @@ -22,35 +21,36 @@ const EditPanelNumber = ({ placeholder, useSlider, }: NumberProps) => { - const [ value, setValue ] = useState(defaultValue ?? (min ?? 0)); - - const handleValueChanged = useCallback((newVal: number) => { - setValue(newVal); - onChanged(newVal); - }, [onChanged]); - const inputDom = ( ); return ( - + {min !== (void 0) && max !== (void 0) && useSlider ? ( <> - + defaultValue={[defaultValue ?? (min ?? 0)]} + onValueChange={(e) => onChanged(e.value[0])} + > + + + + + + + {inputDom} ) : inputDom} diff --git a/src/ui/Panel/EditPanel/Item/Select.tsx b/src/ui/Panel/EditPanel/Item/Select.tsx new file mode 100644 index 0000000..8105b80 --- /dev/null +++ b/src/ui/Panel/EditPanel/Item/Select.tsx @@ -0,0 +1,80 @@ +import Container from './Container'; +import { PanelItemPropsBase } from '../types'; +import { Portal, Select, createListCollection } from "@chakra-ui/react" + +export type SelectPropsBase = PanelItemPropsBase & { + type?: 'string' | 'number', + options: { + value: string | number, + label: string, + disabled?: boolean, + }[], +}; + +export type SelectPropsNumber = SelectPropsBase & { + type: 'number', + onChanged: (newValue: number) => void, + options: { + value: number, + label: string, + disabled?: boolean, + }[], + defaultValue?: number, +}; + +export type SelectProps = SelectPropsBase | SelectPropsNumber; + +const EditPanelSelect = ({ + label, + options, + type = 'string', + onChanged, + defaultValue, +}: SelectProps) => { + const editOptions = createListCollection({items: options}) + + const handleValueChanged = (e: string) => { + if (type === 'number') { + const newValue = parseFloat(e); + if (isNaN(newValue)) return; + onChanged(newValue); + } else { + onChanged(e); + } + }; + + return ( + + handleValueChanged(e.value[0])} + defaultValue={[defaultValue as string]} + > + + + + + + + + + + + + + {editOptions.items.map((editOption: any) => ( + + {editOption.label} + + + ))} + + + + + + ); +}; + +export default EditPanelSelect; diff --git a/src/ui/Panel/EditPanel/Item/String.tsx b/src/ui/Panel/EditPanel/Item/String.tsx index e96ecd3..9c00946 100644 --- a/src/ui/Panel/EditPanel/Item/String.tsx +++ b/src/ui/Panel/EditPanel/Item/String.tsx @@ -13,7 +13,7 @@ const EditPanelString = ({ placeholder, }: StringProps) => { return ( - + , }; +type EditPanelItemSelect = EditPanelItemBase & { + type:'select', + props: Omit, +}; + +type EditPanelItemCombobox = EditPanelItemBase & { + type: 'combobox', + props: Omit, +}; + type EditPanelItemBeat = EditPanelItemBase & { type: 'beat', props: Omit, }; -export type EditPanelItem = EditPanelItemNumber | EditPanelItemString | EditPanelItemBoolean | EditPanelItemDropdown | EditPanelItemBeat; +export type EditPanelItem = EditPanelItemNumber | EditPanelItemString | EditPanelItemBoolean | EditPanelItemDropdown | EditPanelItemSelect | EditPanelItemCombobox | EditPanelItemBeat; type EditPanelListProps = { id: string, items: EditPanelItem[], - onChanged: (newProp: Record) => void, + onChanged: (newProp: UpdatedProps) => void, }; const EditPanelList = ({ @@ -56,7 +70,7 @@ const EditPanelList = ({ }: EditPanelListProps) => { const { t } = useTranslation(); - const handleValueChanged = useCallback((key: string, value: string | number | boolean | BeatArray) => { + const handleValueChanged = useCallback((key: UpdatedPropsKey, value: string | number | boolean | BeatArray) => { const newProp: Record = {}; newProp[key] = value; @@ -65,10 +79,9 @@ const EditPanelList = ({ }, [onChanged]); return ( -
+ {items.map((item) => { const itemKey = `${id}.${item.key}`; - if (item.type === 'beat') return ( handleValueChanged(item.key, e)} key={itemKey} /> ); @@ -81,9 +94,15 @@ const EditPanelList = ({ if (item.type === 'dropdown') return ( handleValueChanged(item.key, e)} key={itemKey} /> ); + if (item.type === 'select') return ( + handleValueChanged(item.key, e)} key={itemKey} /> + ); + if (item.type === 'combobox') return ( + handleValueChanged(item.key, e)} key={itemKey} /> + ); return null; })} -
+ ); }; diff --git a/src/ui/Panel/EditPanel/builder.ts b/src/ui/Panel/EditPanel/builder.ts index ceb3ca8..47b190a 100644 --- a/src/ui/Panel/EditPanel/builder.ts +++ b/src/ui/Panel/EditPanel/builder.ts @@ -1,10 +1,11 @@ -import ChartNote from "@/Chart/Note"; import { EditPanelItem } from "./List"; import { EasingNames } from "@/utils/easings"; -import { NoteType } from "@/Chart/types"; -import ChartKeyframe from "@/Chart/Keyframe"; +import { NoteType } from "@/core/types"; +import { KeyframeType } from "@/core/types"; +import { IKeyframe } from "@/core/models/Keyframe"; +import { INote } from "@/core/models/Note"; -export const KeyframePanelBuilderSingle = (keyframe: ChartKeyframe): EditPanelItem[] => { +export const KeyframePanelBuilderSingle = (keyframe: IKeyframe): EditPanelItem[] => { const valueItem: EditPanelItem = { label: 'Value', i18n: 'keyframe.value', @@ -37,16 +38,16 @@ export const KeyframePanelBuilderSingle = (keyframe: ChartKeyframe): EditPanelIt } ]; - if (keyframe.type === 'alpha') { + if (keyframe.type === KeyframeType.Alpha) { valueItem.props.min = 0; valueItem.props.max = 255; valueItem.props.step = 1; } - if (keyframe.type !== 'speed') result.push({ + if (keyframe.type !== KeyframeType.Speed) result.push({ label: 'Easing', i18n: 'keyframe.easing', - type: 'dropdown', + type: 'combobox', key: 'easing', props: { type: 'number', @@ -63,30 +64,30 @@ export const KeyframePanelBuilderSingle = (keyframe: ChartKeyframe): EditPanelIt return result; }; -export const NotePanelBuilderSingle = (note: ChartNote): EditPanelItem[] => ([ +export const NotePanelBuilderSingle = (note: INote): EditPanelItem[] => ([ { label: 'Type', i18n: 'common.type', - type: 'dropdown', + type: 'select', key: 'type', props: { - type: 'number', + type: 'string', options: [ { label: 'Tap', - value: NoteType.TAP, + value: NoteType.Tap, }, { label: 'Drag', - value: NoteType.DRAG, + value: NoteType.Drag, }, { label: 'Hold', - value: NoteType.HOLD, + value: NoteType.Hold, }, { label: 'Flick', - value: NoteType.FLICK, + value: NoteType.Flick, }, ], defaultValue: note.type, diff --git a/src/ui/Panel/EditPanel/styles.css b/src/ui/Panel/EditPanel/styles.css deleted file mode 100644 index a9c6d20..0000000 --- a/src/ui/Panel/EditPanel/styles.css +++ /dev/null @@ -1,55 +0,0 @@ -.edit-panel { - position: relative; - width: 100%; - min-height: 40px; - height: 100%; - overflow-x: hidden; - overflow-y: auto; -} - -.edit-panel-placeholder { - position: absolute; - top: 50%; - width: 100%; - text-align: center; - transform: translateY(-50%); -} - -.edit-panel-item { - display: flex; - margin: 4px 0px; - width: 100%; -} - -.edit-panel-item .edit-panel-item-label { - display: block; - flex: 0.4; -} - -.edit-panel-item .edit-panel-item-input { - display: block; - flex: 0.6; -} - -.edit-panel-slider { - --value-percent: 0; - - position: relative; - display: block; - height: 24px; - border: 1px solid var(--border-secondary-color); - border-radius: 4px; - overflow: hidden; - cursor: ew-resize; -} - -.edit-panel-slider::before { - content: ''; - position: absolute; - top: 0px; - left: calc(var(--value-percent) * 100%); - width: 2px; - height: 100%; - transform: translateX(-50%); - background: var(--background-primary-color); -} diff --git a/src/ui/Panel/EditPanel/types.ts b/src/ui/Panel/EditPanel/types.ts index 434a12a..b07aa92 100644 --- a/src/ui/Panel/EditPanel/types.ts +++ b/src/ui/Panel/EditPanel/types.ts @@ -1,6 +1,18 @@ +import { SnapshotIn } from 'mobx-state-tree'; +import { Note } from '@/core/models/Note'; +import { Keyframe } from '@/core/models/Keyframe'; export type PanelItemPropsBase = { label: string, onChanged: (newValue: T) => void, defaultValue?: T, }; + + +export type UpdatedNoteProps = Partial, 'id'>>; + +export type UpdatedKeyframeProps = Partial, 'id' | 'type'>>; + +export type UpdatedProps = Partial; + +export type UpdatedPropsKey = (keyof UpdatedNoteProps | keyof UpdatedKeyframeProps) & string; diff --git a/src/ui/Panel/NotePanel/BottomBar.tsx b/src/ui/Panel/NotePanel/BottomBar.tsx new file mode 100644 index 0000000..01c6d9f --- /dev/null +++ b/src/ui/Panel/NotePanel/BottomBar.tsx @@ -0,0 +1,55 @@ +import { Flex, Slider, HStack, Text } from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; +import NumberInput from '@/ui/components/NumberInput'; + +type BottomBarProps = { + onScaleChange: (scale: number) => void, + onAlignChange: (align: number) => void, +}; + +const BottomBar = ({ + onScaleChange, + onAlignChange, +}: BottomBarProps) => { + const { t } = useTranslation(); + + return ( + + + {t('common.scale')} + onScaleChange(e.value[0])} + > + + + + + + + + + + + + {t('common.align')} + + + + ); +}; + +export default BottomBar; \ No newline at end of file diff --git a/src/ui/Panel/NotePanel/Grid.tsx b/src/ui/Panel/NotePanel/Grid.tsx index 962940c..7e52c87 100644 --- a/src/ui/Panel/NotePanel/Grid.tsx +++ b/src/ui/Panel/NotePanel/Grid.tsx @@ -1,17 +1,22 @@ import { useTranslation } from "react-i18next"; -import { useSelectedItem } from "../../contexts/SelectedItem"; +import { resolveIdentifier } from "mobx-state-tree"; +import { useRuntimeStore } from '@/runtime/state'; +import { store as ChartStore } from '@/core/state/chartStore'; import GridAlignScale from "./AlignScale"; import NoteContainer from "./NoteContainer/NoteContainer"; +import { JudgeLine } from "@/core/models/JudgeLine"; const Grid = () => { const { t } = useTranslation(); - const [ selectedItem, ] = useSelectedItem()!; + + const selectedLineId = useRuntimeStore((state) => state.selectedLineId); + const selectedLine = selectedLineId ? resolveIdentifier(JudgeLine, ChartStore, selectedLineId) : null; return (
- - {!selectedItem && ( + + {!selectedLine && (
{t('note_panel.no_line_selected')}
diff --git a/src/ui/Panel/NotePanel/NoteContainer/BeatGraphics.tsx b/src/ui/Panel/NotePanel/NoteContainer/BeatGraphics.tsx index 19b6db1..5cb43c2 100644 --- a/src/ui/Panel/NotePanel/NoteContainer/BeatGraphics.tsx +++ b/src/ui/Panel/NotePanel/NoteContainer/BeatGraphics.tsx @@ -1,10 +1,10 @@ import React, { useMemo } from "react"; import { extend } from "@pixi/react"; import { Container, Sprite, Texture } from 'pixi.js'; -import { useClockTime } from "@/ui/contexts/Clock"; +import { useRuntimeStore } from "@/runtime/state"; +import { useRuntimeAudioBeat } from "@/runtime/audio/state"; import { parseDoublePrecist } from "@/utils/math"; import { getScaleColor } from "@/utils/tempo"; -import { useTempo } from "@/ui/contexts/Tempo"; const getStylePropertyValue = (name: string) => { return getComputedStyle(document.body).getPropertyValue(`--${name}`); @@ -76,8 +76,8 @@ const BeatGraphics = ({ }: BeatGraphicsProps) => { extend({ Container }); - const tempo = useTempo(); - const currentTime = useClockTime().beat - timeOffset; + const tempo = useRuntimeStore(s => s.tempo); + const currentTime = useRuntimeAudioBeat() - timeOffset; const timeRangeStart = Math.floor(currentTime); const _timeOffset = useMemo(() => Math.ceil(timeOffset * 2), [timeOffset]); const scaleCount = useMemo(() => Math.ceil(timeRangeEnd + _timeOffset), [timeRangeEnd, _timeOffset]); diff --git a/src/ui/Panel/NotePanel/NoteContainer/NoteContainer.tsx b/src/ui/Panel/NotePanel/NoteContainer/NoteContainer.tsx index 114b9a7..108b3b6 100644 --- a/src/ui/Panel/NotePanel/NoteContainer/NoteContainer.tsx +++ b/src/ui/Panel/NotePanel/NoteContainer/NoteContainer.tsx @@ -1,35 +1,35 @@ import React, { useCallback, useRef, useState } from 'react'; import { Application, ApplicationRef, extend } from '@pixi/react'; import { Container, Rectangle, Sprite, Texture } from 'pixi.js'; -import { useSelectedItem } from '@/ui/contexts/SelectedItem'; +import { useRuntimeStore } from '@/runtime/state'; import useResizeEffect from '@/ui/hooks/useResizeEffect'; -import ChartJudgeline from '@/Chart/Judgeline'; import useTimeRange from './TimeRange'; import BeatGraphics from './BeatGraphics'; import NoteGraphics from './NoteGraphics'; import { BeatArray, Nullable } from '@/utils/types'; +import { IJudgeLine } from '@/core/models/JudgeLine'; +import { Note } from '@/core/models/Note'; +import { SnapshotIn } from 'mobx-state-tree'; import { useProps } from '../PropsContext'; import useWrite from './useWrite'; -import { ChartNoteProps } from '@/Chart/Note'; import useWheel from './useWheel'; const NOTE_OFFSET = 50; -type NoteContainerProps = { - line: Nullable, -}; - -const NoteContainer = ({ +const NoteContainer: React.FC<{ + line?: Nullable, +}> = ({ line, -}: NoteContainerProps) => { +}) => { extend({ Container, Sprite }); + + const setSelectedNote = useRuntimeStore((state) => state.setSelectedNote); const { scale, writeMode } = useProps(); const timeOffset = useCallback(() => { return NOTE_OFFSET / scale; }, [scale])(); - const [, setSelectedItem ] = useSelectedItem()!; const containerRef = useRef>(null); const appRef = useRef>(null); const hitAreaRef = useRef>(null); @@ -61,16 +61,13 @@ const NoteContainer = ({ hitRectangle.height = height; }, []); - const handleEmptySelect = useCallback(() => { - setSelectedItem((oldItem) => { - if (oldItem !== null) return { ...oldItem, note: null }; - else return null; - }); - }, [setSelectedItem]); + const handleEmptySelect = () => { + setSelectedNote(null); + }; - const handleHoldAddStart = useCallback((props: Omit, id: string) => { + const handleHoldAddStart = useCallback((props: SnapshotIn, id: string) => { if (!line) return; - line.addNote(props, id); + line.addNote({ id, ...props }); }, [line]); const handleHoldAdding = useCallback((props: { holdEndBeat: BeatArray }, id: string) => { @@ -78,7 +75,7 @@ const NoteContainer = ({ line.editNote(id, props); }, [line]); - const handleNewNoteAdded = useCallback((props: Omit) => { + const handleNewNoteAdded = useCallback((props: SnapshotIn) => { if (!line) return; line.addNote(props); }, [line]); diff --git a/src/ui/Panel/NotePanel/NoteContainer/NoteGraphics.tsx b/src/ui/Panel/NotePanel/NoteContainer/NoteGraphics.tsx index fb48c4d..18c350a 100644 --- a/src/ui/Panel/NotePanel/NoteContainer/NoteGraphics.tsx +++ b/src/ui/Panel/NotePanel/NoteContainer/NoteGraphics.tsx @@ -1,101 +1,133 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { extend } from '@pixi/react'; import { Container, EventMode, Sprite, Texture } from 'pixi.js'; -import { useClockTime } from '@/ui/contexts/Clock'; -import ChartJudgeline from '@/Chart/Judgeline'; -import { NoteType } from '@/Chart/types'; -import { useSelectedItem } from '@/ui/contexts/SelectedItem'; +import { observer } from 'mobx-react-lite'; +import { useRuntimeStore } from '@/runtime/state'; +import { useRuntimeAudioBeat } from '@/runtime/audio/state'; +import { runtimeCache } from '@/runtime/resources/cache'; +import { useSkinStore } from '@/runtime/skin/state'; +import { NoteType } from '@/core/types'; import useDrag from '@/ui/hooks/useDrag'; -import { useTempo } from '@/ui/contexts/Tempo'; import { useProps } from '../PropsContext'; -import ChartNote from '@/Chart/Note'; -import { BeatArray, Point } from '@/utils/types'; -import { BeatNumberToArray, GridValue } from '@/utils/math'; +import { IJudgeLine } from '@/core/models/JudgeLine'; +import { Note as NoteModel, INote } from '@/core/models/Note'; +import { Point } from '@/utils/types'; +import { BeatNumberToArray, GridValue, sortByBeat } from '@/utils/math'; +import { SnapshotIn } from 'mobx-state-tree'; const NOTE_SCALE = 5000; -const getNoteTexture = (type: NoteType) => { - if (type === NoteType.DRAG) return 'note-drag'; - else if (type === NoteType.FLICK) return 'note-flick'; - else return 'note-tap'; +const getNoteTexture = (type: NoteType, skinCount = 0, withHL: boolean = false) => { + let skinName = `skin${skinCount}:Tap${withHL ? 'HL' : ''}.png`; + + if (type === NoteType.Drag) skinName = `skin${skinCount}:Drag${withHL ? 'HL' : ''}.png`; + else if (type === NoteType.Flick) skinName = `skin${skinCount}:Flick${withHL ? 'HL' : ''}.png`; + + return runtimeCache.get(skinName) as Texture | undefined; +}; + +const calculateNewTime = ( + beatNum: number, + y: number, + beatGrid: number, + tempo: number, + tempoGrid: number +) => { + return GridValue(beatNum - (y / beatGrid / tempo), tempoGrid); }; -type NoteProps = { - note: ChartNote, - type: NoteType, - beat: number, +const calculateNewPositionX = ( positionX: number, - holdLength: number, - width: number, - scale: number, - noteScale: number, - onChanged: (id: string, time: BeatArray, timeEnd: BeatArray, positionX: number) => void, - onSelected: (id: string) => void, - onRightClicked: (id: string) => void, + x: number, + widthHalf: number, + alignPercent: number, +) => { + const newValue = GridValue((100 + positionX + (x / widthHalf * 100)) / 2, alignPercent * 100); + return (newValue - 50) * 2; }; -const Note = React.memo(function Note ({ +const Note = observer<{ + note: INote, + widthHalf: number, + scale: number, + noteScale: number, + tempoGrid: number, + beatGrid: number, + alignPercent: number, + alignGrid: number, + isSelected: boolean, +}>(({ note, - type, - beat, - positionX, - holdLength, - width, + widthHalf, scale, noteScale, - onChanged, - onSelected, - onRightClicked, -}: NoteProps) { + tempoGrid, + beatGrid, + alignPercent, + alignGrid, +}) => { extend({ Container, Sprite }); - const widthHalf = useMemo(() => width / 2, [width]); - const tempo = useTempo(); - const { align, writeMode } = useProps(); - const tempoGrid = useMemo(() => 1 / tempo, [tempo]); - const beatGrid = useMemo(() => tempoGrid * scale, [tempoGrid, scale]); - const alignPercent = useMemo(() => 1 / align, [align]); - const alignGrid = useMemo(() => width * alignPercent, [width, alignPercent]); + const tempo = useRuntimeStore(s => s.tempo); + const { writeMode } = useProps(); const [isDragging, setIsDragging] = useState(false); - const [ time, setTime ] = useState(beat); - const [ posX, setPosX ] = useState(positionX); - const notePosX = (posX / 100) * widthHalf + widthHalf; - const notePosY = time * -scale; - const noteLength = holdLength * scale / noteScale; - - const calculateNewTime = useCallback((y: number) => { - return GridValue(beat - (y / beatGrid / tempo), tempoGrid); - }, [beat, beatGrid, tempo, tempoGrid]); - - const calculateNewPositionX = useCallback((x: number) => { - const newValue = GridValue((100 + positionX + (x / widthHalf * 100)) / 2, alignPercent * 100); - return (newValue - 50) * 2; - }, [positionX, widthHalf, alignPercent]); - - const handleDragging = useCallback(({ x, y }: Point) => { - setTime(calculateNewTime(y)); - setPosX(calculateNewPositionX(x)); - setIsDragging(true); - }, [calculateNewPositionX, calculateNewTime]); + const notePosX = (note.positionX / 100) * widthHalf + widthHalf; + const notePosY = note.beatNum * -scale; + const noteLength = note.holdLengthBeatNum * scale / noteScale; + + const setSelectedNote = useRuntimeStore((s) => s.setSelectedNote); + const skinCount = useSkinStore(s => s.skinCount); - const handleDragEnd = useCallback(({ x, y }: Point) => { - const newTime = calculateNewTime(y); - const newPosX = calculateNewPositionX(x); - const newHoldEnd = note.holdEndBeatNum + (newTime - beat); + const handleDragging = (_: unknown, { x, y }: Point) => { + const positionXGrid = ( + x > 0 ? alignGrid : + x < 0 ? -alignGrid : 0 + ); + const timeGrid = ( + y > 0 ? beatGrid : + y < 0 ? -beatGrid : 0 + ); + + const updatedProps: Omit>, 'id'> = {}; + + if (positionXGrid !== 0) + updatedProps.positionX = calculateNewPositionX( + note.positionX, + positionXGrid, + widthHalf, + alignPercent + ); + if (timeGrid !== 0) { + const newTime = calculateNewTime( + note.beatNum, + timeGrid, + beatGrid, + tempo, + tempoGrid + ); + const newHoldEnd = note.holdEndBeatNum + (newTime - note.beatNum); - setTime(newTime); - setPosX(newPosX); + updatedProps.beat = BeatNumberToArray(newTime, tempo); + updatedProps.holdEndBeat = BeatNumberToArray(newHoldEnd, tempo); + } + + note.updateProps(updatedProps); + setIsDragging(true); + }; + + const handleDragEnd = () => { setIsDragging(false); - onChanged(note.id, BeatNumberToArray(newTime, tempo), BeatNumberToArray(newHoldEnd, tempo), newPosX); - }, [note.holdEndBeatNum, beat, note.id, onChanged, tempo, calculateNewTime, calculateNewPositionX]); + }; - const handleClicked = useCallback(() => { - onSelected(note.id); - }, [onSelected, note.id]); + const handleClicked = () => { + setSelectedNote(note.id); + }; - const handleRightClicked = useCallback(() => { - onRightClicked(note.id); - }, [note.id, onRightClicked]); + const handleRightClicked = () => { + const line = note.line as IJudgeLine; + line.removeNote(note.id); + setSelectedNote(null); + }; const { onMouseDown } = useDrag({ grid: { @@ -107,11 +139,6 @@ const Note = React.memo(function Note ({ onClick: handleClicked, }); - useEffect(() => { - setTime(beat); - setPosX(positionX); - }, [beat, positionX]); - const noteEventProps = { x: notePosX, y: notePosY, @@ -124,27 +151,27 @@ const Note = React.memo(function Note ({ }; return (<> - {type !== NoteType.HOLD ? ( + {note.type !== NoteType.Hold ? ( ): ( @@ -153,69 +180,37 @@ const Note = React.memo(function Note ({ ); }); -type NoteGraphicsProps = { +const NoteGraphics = observer<{ timeRangeEnd: number, scale: number, width: number, - line: ChartJudgeline, + line: IJudgeLine, timeOffset?: number, -}; - -const NoteGraphics = ({ +}>(({ timeRangeEnd, scale, width, line, timeOffset = 0 -}: NoteGraphicsProps) => { +}) => { extend({ Container }); - const [ notes, setNotes ] = useState([ ...line.notes ]); - const [ , setSelectedItem ] = useSelectedItem()!; + const selectedNoteId = useRuntimeStore((s) => s.selectedNoteId); + const notes = line.notes.slice().sort(sortByBeat); + + // TODO: Move all rendering props to React.Context (create a context for note panel) const noteScale = width / NOTE_SCALE; - const currentTime = useClockTime().beat - timeOffset; - const timeRange = timeRangeEnd + currentTime; + const widthHalf = useMemo(() => width / 2, [width]); + const tempo = useRuntimeStore(s => s.tempo); + const { align } = useProps(); + const tempoGrid = useMemo(() => 1 / tempo, [tempo]); + const beatGrid = useMemo(() => tempoGrid * scale, [tempoGrid, scale]); + const alignPercent = useMemo(() => 1 / align, [align]); + const alignGrid = useMemo(() => width * alignPercent, [width, alignPercent]); - const handleNoteChanged = useCallback((id: string, time: BeatArray, timeEnd: BeatArray, positionX: number) => { - line.editNote( - id, - { - beat: time, - positionX, - holdEndBeat: timeEnd, - } - ); - }, [line]); - - const handleNoteSelected = useCallback((id: string) => { - setSelectedItem({ - line, - note: { - id, - }, - keyframe: null, - }); - }, [line, setSelectedItem]); - - const updateNote = useCallback((notes: ChartNote[]) => { - setNotes(notes); - }, []); - - const handleNoteDeleted = useCallback((id: string) => { - setSelectedItem((oldItem) => { - if (oldItem !== null) return { ...oldItem, note: null }; - else return null; - }) - line.deleteNote(id); - }, [line, setSelectedItem]); - - useEffect(() => { - line.events.on('notes.updated', updateNote); - return (() => { - line.events.off('notes.updated', updateNote); - }); - }, [line, updateNote]); + const currentTime = useRuntimeAudioBeat() - timeOffset; + const timeRange = timeRangeEnd + currentTime; const noteSprites = (() => { const result = []; @@ -227,16 +222,15 @@ const NoteGraphics = ({ result.push( ); @@ -250,6 +244,6 @@ const NoteGraphics = ({ {noteSprites} ); -}; +}); export default React.memo(NoteGraphics); diff --git a/src/ui/Panel/NotePanel/NoteContainer/useWheel.ts b/src/ui/Panel/NotePanel/NoteContainer/useWheel.ts index 7b034b2..261ffb1 100644 --- a/src/ui/Panel/NotePanel/NoteContainer/useWheel.ts +++ b/src/ui/Panel/NotePanel/NoteContainer/useWheel.ts @@ -1,20 +1,16 @@ import { useCallback, useMemo } from 'react'; -import Chart from '@/Chart/Chart'; -import { useTempo } from '@/ui/contexts/Tempo'; +import { useRuntimeStore } from '@/runtime/state'; import { GridValue } from '@/utils/math'; const useWheel = () => { - const tempo = useTempo(); + const tempo = useRuntimeStore(s => s.tempo); const tempoGrid = useMemo(() => 1 / tempo, [tempo]); const handleWheel = useCallback((e: React.WheelEvent) => { - if (!Chart.info) return; - const { deltaY, shiftKey } = e; const seekFactor = deltaY > 0 ? 1 : -1; - const newTime = GridValue(Chart.beatNum + ((shiftKey ? 1 : tempoGrid) * seekFactor), tempoGrid); - - Chart.beatNum = newTime; + + // TODO }, [tempoGrid]); return { diff --git a/src/ui/Panel/NotePanel/NoteContainer/useWrite.ts b/src/ui/Panel/NotePanel/NoteContainer/useWrite.ts index dfad372..47986d1 100644 --- a/src/ui/Panel/NotePanel/NoteContainer/useWrite.ts +++ b/src/ui/Panel/NotePanel/NoteContainer/useWrite.ts @@ -1,14 +1,16 @@ import { useCallback, useMemo, useRef } from 'react'; -import { v4 as uuid } from 'uuid'; -import Chart from '@/Chart/Chart'; +import { nanoid } from 'nanoid'; import { useProps } from '../PropsContext'; -import { useTempo } from '@/ui/contexts/Tempo'; +import { useRuntimeStore } from '@/runtime/state'; +import { runtimeAudioTimeStore } from '@/runtime/audio/state'; import { BeatArrayToNumber, BeatNumberToArray, GridValue, parseDoublePrecist } from '@/utils/math'; import { Point as PixiPoint } from 'pixi.js'; import { Point } from '@/utils/types'; -import { ChartNoteProps } from '@/Chart/Note'; -import { NoteType } from '@/Chart/types'; +import { Note } from '@/core/models/Note'; +import { NoteType } from '@/core/types'; import { BeatArray, Nullable } from '@/utils/types'; +import { SnapshotIn } from 'mobx-state-tree'; + type PixiPointEvent = MouseEvent & { screen: PixiPoint, @@ -17,9 +19,9 @@ type PixiPointEvent = MouseEvent & { type WriteProps = { width: number, height: number, - onAddStart: (props: Omit, id: string) => void, + onAddStart: (props: SnapshotIn, id: string) => void, onAdding: (props: { holdEndBeat: BeatArray }, id: string) => void, - onAdded: (props: Omit) => void, + onAdded: (props: SnapshotIn) => void, timeOffset?: number, }; @@ -31,7 +33,7 @@ const useWrite = ({ onAdded, timeOffset = 0, }: WriteProps) => { - const tempo = useTempo(); + const tempo = useRuntimeStore(s => s.tempo); const { scale, align, writeMode } = useProps(); const tempoGrid = useMemo(() => 1 / tempo, [tempo]); const beatGrid = useMemo(() => tempoGrid * scale, [tempoGrid, scale]); @@ -39,7 +41,7 @@ const useWrite = ({ const widthHalf = useMemo(() => width / 2, [width]); const posDiff = useRef>(null); - const holdPropRef = useRef>>(null); + const holdPropRef = useRef>>(null); const holdIDRef = useRef>(null); const holdLastEndRef = useRef(NaN); @@ -50,7 +52,7 @@ const useWrite = ({ const posToTime = useCallback((pos: number) => { const hitTime = GridValue(height - pos, beatGrid) / beatGrid / tempo - timeOffset; - const gridResult = GridValue(Chart.beatNum + hitTime, tempoGrid); + const gridResult = GridValue(runtimeAudioTimeStore.getState().currentBeat + hitTime, tempoGrid); return parseDoublePrecist(gridResult, 6, -1); }, [height, timeOffset, tempoGrid, beatGrid, tempo]); @@ -62,7 +64,7 @@ const useWrite = ({ const posY = e.clientY - posDiff.current.y; const beatNum = posToTime(posY); - if (BeatArrayToNumber(holdPropRef.current.beat) > beatNum) return; + if (BeatArrayToNumber(holdPropRef.current.beat as BeatArray) > beatNum) return; if (beatNum !== holdLastEndRef.current) { onAdding({ holdEndBeat: BeatNumberToArray(beatNum, tempo) }, holdIDRef.current); holdLastEndRef.current = beatNum; @@ -86,7 +88,7 @@ const useWrite = ({ const posX = posToPositionX(x); const beatNum = posToTime(y); - if (writeMode !== NoteType.HOLD) { + if (writeMode !== NoteType.Hold) { onAdded({ type: writeMode, beat: BeatNumberToArray(beatNum, tempo), @@ -97,9 +99,9 @@ const useWrite = ({ } else { if (holdPropRef.current === null) { const { clientX, clientY } = e; - const holdID = uuid(); + const holdID = nanoid(); const holdProps = { - type: NoteType.HOLD, + type: NoteType.Hold, beat: BeatNumberToArray(beatNum, tempo), positionX: posX, speed: 1, diff --git a/src/ui/Panel/NotePanel/NotePanel.tsx b/src/ui/Panel/NotePanel/NotePanel.tsx index 784687c..7306af4 100644 --- a/src/ui/Panel/NotePanel/NotePanel.tsx +++ b/src/ui/Panel/NotePanel/NotePanel.tsx @@ -1,88 +1,38 @@ -import { ChangeEvent, useCallback, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { useCallback, useState } from 'react'; +import { useRuntimeStore } from '@/runtime/state'; import Grid from './Grid'; import './styles.css'; import PropsContext from './PropsContext'; -import { useSelectedItem } from '../../contexts/SelectedItem'; import { Nullable } from '@/utils/types'; -import { NoteType } from '@/Chart/types'; -import NumberInput from '@/ui/components/NumberInput'; +import { NoteType } from '@/core/types'; +import TopBar, { EditModeType } from './TopBar'; +import BottomBar from './BottomBar'; +import { Stack } from '@chakra-ui/react'; const NotePanel = () => { - const { t } = useTranslation(); - const [, setSelectedItem ] = useSelectedItem()!; const [ writeMode, setWriteMode ] = useState>(null); const [ scale, setScale ] = useState(200); const [ alignCount, setAlighCount ] = useState(8); - const updateWriteMode = useCallback((e: ChangeEvent) => { - const newMode = parseInt(e.target.value) as (NoteType | -1); - if (newMode === -1) setWriteMode(null); + const setSelectedNote = useRuntimeStore((s) => s.setSelectedNote); + const setSelectedKeyframe = useRuntimeStore((s) => s.setSelectedKeyframe); + + const updateWriteMode = (newMode: EditModeType) => { + if (newMode === 'select') setWriteMode(null); else { setWriteMode(newMode); - setSelectedItem((oldItem) => { - if (oldItem !== null) return { ...oldItem, keyframe: null , note: null }; - else return null; - }); + setSelectedNote(null); + setSelectedKeyframe(null); } - }, [setSelectedItem]); + }; const updateScale = useCallback((scale: number) => { setScale(10 + (scale / 100) * 390); }, []); return ( -
-
-
- {t('note_panel.mode_select.title')} - - - - - -
-
+ + { }}> -
- - -
-
+ + ); }; diff --git a/src/ui/Panel/NotePanel/PropsContext.ts b/src/ui/Panel/NotePanel/PropsContext.ts index b72fd53..5f4dda8 100644 --- a/src/ui/Panel/NotePanel/PropsContext.ts +++ b/src/ui/Panel/NotePanel/PropsContext.ts @@ -1,5 +1,5 @@ import { createContext, useContext } from 'react'; -import { NoteType } from '@/Chart/types'; +import { NoteType } from '@/core/types'; import { Nullable } from '@/utils/types'; type PropsContext = { diff --git a/src/ui/Panel/NotePanel/TopBar.tsx b/src/ui/Panel/NotePanel/TopBar.tsx new file mode 100644 index 0000000..8c0adc6 --- /dev/null +++ b/src/ui/Panel/NotePanel/TopBar.tsx @@ -0,0 +1,53 @@ +import { NoteType } from '@/core/types'; +import { useTranslation } from 'react-i18next'; +import { Flex, HStack, RadioGroup, For, Text } from '@chakra-ui/react'; + +export type EditModeType = NoteType | 'select'; + +type TopBarProps = { + writeMode: EditModeType, + onChange: (newMode: EditModeType) => void, +}; + +const TopBar = ({ + writeMode, + onChange, +}: TopBarProps) => { + const { t } = useTranslation(); + const radioItems = [ + { label: t('note_panel.mode_select.select'), value: 'select' }, + { label: t('note.type.tap'), value: NoteType.Tap }, + { label: t('note.type.hold'), value: NoteType.Hold }, + { label: t('note.type.drag'), value: NoteType.Drag }, + { label: t('note.type.flick'), value: NoteType.Flick }, + ] + + return ( + + + {t('note_panel.mode_select.title')} + onChange(e.value as EditModeType)} + > + + + {item => ( + + + + {item.label} + + )} + + + + + + ); +}; + + +export default TopBar; \ No newline at end of file diff --git a/src/ui/Panel/NotePanel/styles.css b/src/ui/Panel/NotePanel/styles.css index d35aa80..8befc7b 100644 --- a/src/ui/Panel/NotePanel/styles.css +++ b/src/ui/Panel/NotePanel/styles.css @@ -25,6 +25,15 @@ overflow: hidden; } +.note-grid-placeholder { + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 2; +} + .note-grid-scale { position: absolute; top: 0; diff --git a/src/ui/Panel/PanelDock.tsx b/src/ui/Panel/PanelDock.tsx index 5215687..7eb369e 100644 --- a/src/ui/Panel/PanelDock.tsx +++ b/src/ui/Panel/PanelDock.tsx @@ -1,109 +1,104 @@ -import DockLayout, { LayoutData } from 'rc-dock'; -import NotePanel from './NotePanel/NotePanel'; -import PreviewPanel from './PreviewPanel/PreviewPanel'; -import Timeline from './TimelinePanel/Timeline'; -import EditPanel from './EditPanel/EditPanel'; -import BPMPanel from './BPMPanel/BPMPanel'; -import SelectedItemProvider from '../contexts/SelectedItem/Provider'; -import ClockTimeProvider from '../contexts/Clock/Provider'; -import { Ref } from 'react'; - -const dockLayout: LayoutData = { - dockbox: { - mode: 'vertical', - children: [ - { - mode: 'horizontal', - children: [ - { - mode: 'vertical', - children: [ - { - mode: 'horizontal', - children: [ - { - size: 60, - tabs: [ - { - id: 'note-panel', - title: 'Note panel', - cached: true, - content: (), - } - ] - }, - { - tabs: [ - { - id: 'live-preview', - title: 'Live preview', - cached: true, - content: () - } - ] - }, - ] - }, - ] - }, - { - size: 60, - tabs: [ - { - id: 'edit-panel', - title: 'Edit panel', - cached: true, - content: (), - }, - { - id: 'bpm-panel', - title: 'BPM', - cached: true, - content: () - } - ] - } - ] - }, - { - size: 80, - tabs: [ - { - id: 'timeline', - title: 'Timeline', - cached: true, - content: () - } - ] - } - ] - }, -}; +import { useEffect, useState } from 'react'; +import { + DockviewReact, + DockviewReadyEvent, + DockviewApi, +} from 'dockview'; +import 'dockview/dist/styles/dockview.css'; +import Components from './Components'; +import DefaultLayout from './DefaultLayout'; -type PanelDockProps = { - ref?: Ref, -}; +const PanelDock = (props: { api: DockviewApi | undefined, setApi: (api: DockviewApi) => void, theme?: string }) => { + const { api, setApi } = props; + + const [panels, setPanels] = useState([]); + const [groups, setGroups] = useState([]); + + const [activePanel, setActivePanel] = useState(); + const [activeGroup, setActiveGroup] = useState(); + + const onReady = (event: DockviewReadyEvent) => { + setApi(event.api); + setPanels([]); + setGroups([]); + setActivePanel(undefined); + setActiveGroup(undefined); + }; + + useEffect(() => { + if (!api) { + return; + } + + const disposables = [ + api.onDidAddPanel((event) => { + setPanels((_) => [..._, event.id]); + }), + api.onDidActivePanelChange((event) => { + setActivePanel(event?.id); + }), + api.onDidRemovePanel((event) => { + setPanels((_) => { + const next = [..._]; + next.splice( + next.findIndex((x) => x === event.id), + 1 + ); + + return next; + }); + }), + + api.onDidAddGroup((event) => { + setGroups((_) => [..._, event.id]); + }), + + api.onDidRemoveGroup((event) => { + setGroups((_) => { + const next = [..._]; + next.splice( + next.findIndex((x) => x === event.id), + 1 + ); + + return next; + }); + }), + api.onDidActiveGroupChange((event) => { + setActiveGroup(event?.id); + }), + ]; + + let success = false; + + const state = localStorage.getItem('layout-state'); + if (state) { + try { + api.fromJSON(JSON.parse(state)); + success = true; + } catch { + localStorage.removeItem('layout-state'); + } + } + + if (!success) { + DefaultLayout(api); + } + + return disposables.forEach((disposable) => disposable.dispose()); + }, [api]); -const PanelDock = ({ - ref -}: PanelDockProps) => { - return ( -
- - - - - -
- ); + return ( +
+ +
+ ); }; -export default PanelDock; +export default PanelDock; \ No newline at end of file diff --git a/src/ui/Panel/PreviewPanel/Canvas.tsx b/src/ui/Panel/PreviewPanel/Canvas.tsx index 7552ec0..ded8a14 100644 --- a/src/ui/Panel/PreviewPanel/Canvas.tsx +++ b/src/ui/Panel/PreviewPanel/Canvas.tsx @@ -1,96 +1,40 @@ -import { Application, ApplicationRef, extend } from '@pixi/react'; -import { Container, Rectangle } from 'pixi.js'; -import { useCallback, useEffect, useRef } from 'react'; -import Chart from '@/Chart/Chart'; -import { CalculateRendererSize } from '@/utils/renderer'; -import { Nullable, RendererSize } from '@/utils/types'; - -const ZeroSizeRectangle = new Rectangle(0, 0, 0, 0); +import { useEffect, useRef } from 'react'; +import useResizeEffect from '@/ui/hooks/useResizeEffect'; +import { app as previewApp } from '@/renderer/preview/app'; +import { Box } from '@chakra-ui/react'; const PreviewCanvas = () => { - extend({ Container }); - - const containerRef = useRef>(null); - const appRef = useRef>(null); - const appContainerRef = useRef>(null); - const sizeRef = useRef(CalculateRendererSize(1, 1)); - - const handleResize = useCallback(() => { - const containerDom = containerRef.current; - if (!containerDom) return; - const { clientWidth, clientHeight } = containerDom; - - if (!appRef.current) return; - const app = appRef.current.getApplication(); - if (!app) return; - - app.resize(); - const size = CalculateRendererSize(clientWidth, clientHeight); - sizeRef.current = size; - Chart.resize(size); - }, []); + const containerRef = useRef(null); - const handleChartChanged = () => { - if (!Chart.info) return; - if (!appContainerRef.current) return; + const handleAppendCanvas = () => { + if (!containerRef.current) return; + if (!previewApp.renderer) return; - const container = appContainerRef.current; - if (container.children.length !== 0) { - for (const child of container.children) { - container.removeChild(child); - } - } + if (previewApp.canvas.parentElement) + previewApp.canvas.parentElement.removeChild(previewApp.canvas); - Chart.resize(sizeRef.current); - container.addChild(Chart.container); + containerRef.current.appendChild(previewApp.canvas); + previewApp.$resize(containerRef.current.clientWidth, containerRef.current.clientHeight); }; useEffect(() => { if (!containerRef.current) return; + if (previewApp.renderer) return handleAppendCanvas(); - handleResize(); - const resize = new ResizeObserver(() => { - handleResize(); - }); - resize.observe(containerRef.current); - - return (() => { - resize.disconnect(); - }) - }, [handleResize]); - - useEffect(() => { - Chart.events.on('loaded', handleChartChanged); - return (() => { - Chart.events.off('loaded', handleChartChanged); + return previewApp.addOnInitCallback(() => { + handleAppendCanvas(); }); }, []); + useResizeEffect((size) => { + previewApp.$resize(size.width, size.height); + }, containerRef); + return ( -
- { - handleResize(); - }} - ref={appRef} - > - - - -
- ) + > + ); }; export default PreviewCanvas; diff --git a/src/ui/Panel/PreviewPanel/Controller.tsx b/src/ui/Panel/PreviewPanel/Controller.tsx index 08edaa0..8ac60ff 100644 --- a/src/ui/Panel/PreviewPanel/Controller.tsx +++ b/src/ui/Panel/PreviewPanel/Controller.tsx @@ -1,56 +1,66 @@ import { useMemo } from 'react'; -import Chart from '@/Chart/Chart'; -import { useTempo } from '../../contexts/Tempo'; +import { Slider, Button, Stack, Center, HStack } from '@chakra-ui/react'; +import { useRuntimeStore } from '@/runtime/state'; +import { useRuntimeAudioStore } from '@/runtime/audio/state'; import { GridValue } from '@/utils/math'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPlay, faPause, faStepForward, faStepBackward, faFastBackward, faFastForward } from "@fortawesome/free-solid-svg-icons"; const PreviewController = () => { - const tempo = useTempo(); + const currentStatus = useRuntimeAudioStore((s) => s.status); + const playClip = useRuntimeAudioStore((s) => s.status === 1 ? s.play : s.pause); + const tempo = useRuntimeStore(s => s.tempo); const tempoGrid = useMemo(() => 1 / tempo, [tempo]); return ( <> -
- -
-
-
- - - - - -
-
+ + + + + + + + + +
+ + + + + + + +
+
); }; diff --git a/src/ui/Panel/PreviewPanel/PreviewPanel.tsx b/src/ui/Panel/PreviewPanel/PreviewPanel.tsx index 53946c6..020f9cf 100644 --- a/src/ui/Panel/PreviewPanel/PreviewPanel.tsx +++ b/src/ui/Panel/PreviewPanel/PreviewPanel.tsx @@ -1,17 +1,17 @@ import PreviewCanvas from './Canvas'; import PreviewController from './Controller'; -import './styles.css'; +import { Box, Stack } from '@chakra-ui/react'; const PreviewPanel = () => { return ( -
-
+ + -
-
+ + -
-
+ + ); }; diff --git a/src/ui/Panel/PreviewPanel/styles.css b/src/ui/Panel/PreviewPanel/styles.css deleted file mode 100644 index 871614d..0000000 --- a/src/ui/Panel/PreviewPanel/styles.css +++ /dev/null @@ -1,48 +0,0 @@ -.live-preview { - position: relative; - display: flex; - width: 100%; - height: 100%; - overflow: hidden; - flex-direction: column; - align-items: stretch; - flex-wrap: nowrap; -} - -.live-preview .preview-canvas { - position: relative; - flex: 1; -} - -.live-preview .preview-canvas .canvas-container { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; -} - -.live-preview .preview-controller { - display: flex; - width: 100%; - flex-direction: column; - align-items: stretch; - flex-wrap: nowrap; -} - -.preview-controller .control-actions { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - justify-content: space-between; -} - -.control-actions .control-group.control-group-center { - display: flex; - flex-direction: row; - justify-content: center; - flex-wrap: nowrap; - flex: 1; -} diff --git a/src/ui/Panel/ProjectPanel/Input.tsx b/src/ui/Panel/ProjectPanel/Input.tsx index c159599..ab9d012 100644 --- a/src/ui/Panel/ProjectPanel/Input.tsx +++ b/src/ui/Panel/ProjectPanel/Input.tsx @@ -1,11 +1,14 @@ -import { FormGroup, InputGroup } from '@blueprintjs/core'; -import { ChartInfo } from '@/Chart/types'; +import { Field, Input } from "@chakra-ui/react" import { FormEvent } from 'react'; +import { SnapshotIn } from "mobx-state-tree"; +import { Metadata } from "@/core/models/Metadata"; + +type TMetadata = Omit, 'musicFile' | 'backgroundFile'>; type ProjectInputProps = { - type: keyof ChartInfo, + type: keyof TMetadata, label: string, - onInput: (key: keyof ChartInfo, value: string) => void, + onInput: (key: K, value: TMetadata[K]) => void, sublabel?: string, placeholder?: string, defaultValue?: string, @@ -27,18 +30,16 @@ const ProjectInput = ({ }; return ( - - + { label } + - + { sublabel } + ); }; diff --git a/src/ui/Panel/ProjectPanel/ProjectPanel.tsx b/src/ui/Panel/ProjectPanel/ProjectPanel.tsx index ea0db38..06f2b0a 100644 --- a/src/ui/Panel/ProjectPanel/ProjectPanel.tsx +++ b/src/ui/Panel/ProjectPanel/ProjectPanel.tsx @@ -1,17 +1,21 @@ +import { Stack } from "@chakra-ui/react" import { useState } from 'react'; -import { ChartInfo } from '@/Chart/types'; import ProjectInput from './Input'; +import { SnapshotIn } from "mobx-state-tree"; +import { Metadata } from "@/core/models/Metadata"; + +type TMetadata = Omit, 'musicFile' | 'backgroundFile'>; type ProjectPanelProps = { - project?: ChartInfo, - onChanged?: (newInfo: ChartInfo) => void, + project?: TMetadata, + onChanged?: (newMetadata: TMetadata) => void, }; const ProjectPanel = ({ project: defaultProject, onChanged, }: ProjectPanelProps) => { - const [ project, setProject ] = useState({ + const [ project, setProject ] = useState({ name: '', artist: '', illustration: '', @@ -20,11 +24,10 @@ const ProjectPanel = ({ ...(defaultProject ?? {}) }); - const handleInfoInput = (key: keyof ChartInfo, value: string) => { - setProject((prj) => { - prj[key] = value; - return prj - }); + const handleInfoUpdate: (key: K, value: TMetadata[K]) => void = (key, value) => { + setProject(() => ({ + [key]: value, + })); }; const handleInputBlur = () => { @@ -33,17 +36,12 @@ const ProjectPanel = ({ }; return ( -
+ @@ -51,7 +49,7 @@ const ProjectPanel = ({ type='artist' label='Artist' placeholder='Song artist' - onInput={handleInfoInput} + onInput={handleInfoUpdate} onBlur={handleInputBlur} /> @@ -59,7 +57,7 @@ const ProjectPanel = ({ type='illustration' label='Illustrator' placeholder='Background illustrator' - onInput={handleInfoInput} + onInput={handleInfoUpdate} onBlur={handleInputBlur} /> @@ -67,7 +65,7 @@ const ProjectPanel = ({ type='level' label='Level' placeholder='SP Lv.?' - onInput={handleInfoInput} + onInput={handleInfoUpdate} onBlur={handleInputBlur} /> @@ -75,10 +73,10 @@ const ProjectPanel = ({ type='designer' label='Designer' placeholder='Song charter' - onInput={handleInfoInput} + onInput={handleInfoUpdate} onBlur={handleInputBlur} /> -
+ ); }; diff --git a/src/ui/Panel/SettingsPanel/Item.tsx b/src/ui/Panel/SettingsPanel/Item.tsx index 51894cd..98468ce 100644 --- a/src/ui/Panel/SettingsPanel/Item.tsx +++ b/src/ui/Panel/SettingsPanel/Item.tsx @@ -1,35 +1,21 @@ -import { Card, FormGroup, Intent } from '@blueprintjs/core'; +import { Card } from '@chakra-ui/react'; type SettingsItemProps = { label: string, - children: React.ReactNode, - labelInfo?: string, - helperText?: string, - intent?: Intent, - disabled?: boolean, + children: React.ReactNode }; const SettingsItem = ({ label, - children, - labelInfo, - helperText, - intent, - disabled, + children }: SettingsItemProps) => { return ( - - + + + {label} {children} - - + + ); }; diff --git a/src/ui/Panel/SettingsPanel/Rendering.tsx b/src/ui/Panel/SettingsPanel/Rendering.tsx index b1bc7c7..98c5103 100644 --- a/src/ui/Panel/SettingsPanel/Rendering.tsx +++ b/src/ui/Panel/SettingsPanel/Rendering.tsx @@ -1,31 +1,46 @@ -import { CardList, Slider } from '@blueprintjs/core'; -import Settings from '@/Settings/Settings'; -import Item from './Item'; -import { useSettings } from '@/ui/contexts/Settings'; +import { Slider, DataList } from '@chakra-ui/react'; +import { useSettingsStore, updateSettingsByKey } from '@/runtime/settings/state'; const SettingsRendering = () => { - const settings = useSettings(); - - const updateNoteScale = (value: number) => { - const newValue = 10000 - value * 4000; - Settings.set('noteScale', newValue); - }; - return ( - - - + + Note scale + + - - + step={0.01} + value={[(10000 - useSettingsStore((s) => s['renderer.noteScale'])) / 4000]} + onValueChange={(e) => updateSettingsByKey('renderer.noteScale', 10000 - e.value[0] * 4000)} + > + + + + + + + + + + + ); }; diff --git a/src/ui/Panel/SettingsPanel/SettingsPanel.tsx b/src/ui/Panel/SettingsPanel/SettingsPanel.tsx index 32429e2..7e50212 100644 --- a/src/ui/Panel/SettingsPanel/SettingsPanel.tsx +++ b/src/ui/Panel/SettingsPanel/SettingsPanel.tsx @@ -1,19 +1,27 @@ -import { Tab, Tabs } from '@blueprintjs/core'; +import { Tabs, ScrollArea } from '@chakra-ui/react'; import SettingsRendering from './Rendering'; -import './styles.css'; const SettingsPanel = () => { return ( - - } /> - + + + Rendering + + + + + + + + + + + + + + + ); }; -export default SettingsPanel; +export default SettingsPanel; \ No newline at end of file diff --git a/src/ui/Panel/SettingsPanel/styles.css b/src/ui/Panel/SettingsPanel/styles.css deleted file mode 100644 index 34132a8..0000000 --- a/src/ui/Panel/SettingsPanel/styles.css +++ /dev/null @@ -1,11 +0,0 @@ -.settings-panel { - width: 100%; - max-height: 100%; - overflow: hidden; -} - -.settings-panel .bp5-tab-panel { - width: 100%; - overflow-y: auto; - flex: 1; -} \ No newline at end of file diff --git a/src/ui/Panel/TimelinePanel/LeftPanel/Head.tsx b/src/ui/Panel/TimelinePanel/LeftPanel/Head.tsx index 8e41b84..d9e5e05 100644 --- a/src/ui/Panel/TimelinePanel/LeftPanel/Head.tsx +++ b/src/ui/Panel/TimelinePanel/LeftPanel/Head.tsx @@ -1,20 +1,11 @@ -import { useClockTime } from "@/ui/contexts/Clock"; -import { FillZero } from "@/utils/math"; - -// XXX: Move to somewhere else? -const timeToString = (time: number) => { - const seconds = Math.floor(time) % 60; - const minutes = Math.floor(time / 60); - const milliseconds = Math.floor((time - Math.floor(time)) * 1000); - return `${FillZero(minutes)}:${FillZero(seconds)}.${FillZero(milliseconds, 3)}`; -}; +import { LeftPanelTimeLabel } from './TimeLabel'; const LeftPanelHead = () => { return
-
{timeToString(useClockTime().time)}
+
; }; diff --git a/src/ui/Panel/TimelinePanel/LeftPanel/LeftPanel.tsx b/src/ui/Panel/TimelinePanel/LeftPanel/LeftPanel.tsx index feecfa5..0dd41a8 100644 --- a/src/ui/Panel/TimelinePanel/LeftPanel/LeftPanel.tsx +++ b/src/ui/Panel/TimelinePanel/LeftPanel/LeftPanel.tsx @@ -1,18 +1,16 @@ -import React from "react"; +import { observer } from 'mobx-react-lite'; import TimelineList from "../List/List"; -import ChartJudgeline from "@/Chart/Judgeline"; import LeftPanelHead from "./Head"; import LeftPanelLine from './Line'; +import { IJudgeLine } from "@/core/models/JudgeLine"; import './styles.css'; -export type TimelineLeftPanelProps = { - lines: ChartJudgeline[], - expandedLines: number[], +const TimelineLeftPanel = observer<{ + lines: IJudgeLine[], + expandedLines: string[], listScrolled: number, - onLineExpanded: (lineIndex: number, isExpanded: boolean) => void, -}; - -const TimelineLeftPanel: React.FC = ({ + onLineExpanded: (lineIndex: string, isExpanded: boolean) => void, +}>(({ lines, expandedLines, listScrolled, @@ -33,19 +31,19 @@ const TimelineLeftPanel: React.FC = ({ top: -listScrolled }} > - {lines.map((line, index) => { // TODO: Render line props & add right click menu - return ( // TODO: Render line props & add right click menu + onLineExpanded(index, e)} - key={line.id} + isExpanded={expandedLines.includes(line.id)} + onExpandClick={onLineExpanded} /> - })} + ))}
); -}; +}); export default TimelineLeftPanel; diff --git a/src/ui/Panel/TimelinePanel/LeftPanel/Line.tsx b/src/ui/Panel/TimelinePanel/LeftPanel/Line.tsx index eb4ce81..f8129a1 100644 --- a/src/ui/Panel/TimelinePanel/LeftPanel/Line.tsx +++ b/src/ui/Panel/TimelinePanel/LeftPanel/Line.tsx @@ -1,36 +1,31 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React, { useCallback } from 'react'; +import React from 'react'; +import { useRuntimeStore } from '@/runtime/state'; import TimelineList from '../List/List'; import TimelineListItem from '../List/Item'; -import { useSelectedItem } from '@/ui/contexts/SelectedItem'; import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; -import ChartJudgeline from '@/Chart/Judgeline'; +import { IJudgeLine } from '@/core/models/JudgeLine'; -export type TimelineLeftPanelLineProps = { - line: ChartJudgeline, +const TimelineLeftPanelLine: React.FC<{ + line: IJudgeLine, name: string, isExpanded: boolean, - onExpandClick: (isExpanded: boolean) => void -}; - -const TimelineLeftPanelLine: React.FC = ({ + onExpandClick: (lineIndex: string, isExpanded: boolean) => void, +}> = ({ line, name, isExpanded, onExpandClick -}: TimelineLeftPanelLineProps) => { - const [ , setSelectedItem ] = useSelectedItem()!; +}) => { + const setSelectedLine = useRuntimeStore((s) => s.setSelectedLine); - const handleLineClicked = useCallback(() => { - setSelectedItem((oldItem) => { - if (oldItem === null) return { line, keyframe: null, note: null }; - else return { ...oldItem, line }; - }); - }, [line, setSelectedItem]); + const handleLineClicked = () => { + setSelectedLine(line.id); + }; return
-
{name}
diff --git a/src/ui/Panel/TimelinePanel/LeftPanel/TimeLabel.tsx b/src/ui/Panel/TimelinePanel/LeftPanel/TimeLabel.tsx new file mode 100644 index 0000000..a670d4b --- /dev/null +++ b/src/ui/Panel/TimelinePanel/LeftPanel/TimeLabel.tsx @@ -0,0 +1,18 @@ +import { useRuntimeAudioTime } from '@/runtime/audio/state'; +import { FillZero } from '@/utils/math'; + +// XXX: Move to somewhere else? +const timeToString = (time: number) => { + const seconds = Math.floor(time) % 60; + const minutes = Math.floor(time / 60); + const milliseconds = Math.floor((time - Math.floor(time)) * 1000); + return `${FillZero(minutes)}:${FillZero(seconds)}.${FillZero(milliseconds, 3)}`; +}; + +export const LeftPanelTimeLabel = () => { + const currentTime = useRuntimeAudioTime(); + + return ( +
{timeToString(currentTime)}
+ ) +}; diff --git a/src/ui/Panel/TimelinePanel/RightPanel/BeatScale.tsx b/src/ui/Panel/TimelinePanel/RightPanel/BeatScale.tsx index 17e6630..b37338f 100644 --- a/src/ui/Panel/TimelinePanel/RightPanel/BeatScale.tsx +++ b/src/ui/Panel/TimelinePanel/RightPanel/BeatScale.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { useTempo } from "@/ui/contexts/Tempo"; +import { useRuntimeStore } from "@/runtime/state"; import { parseDoublePrecist } from "@/utils/math"; import { getScaleColor } from "@/utils/tempo"; @@ -10,7 +10,7 @@ export type BeatScaleProps = { const BeatScale: React.FC = ({ time, }) => { - const tempo = useTempo(); + const tempo = useRuntimeStore(s => s.tempo); const beatSubscale = parseDoublePrecist(1 / tempo, 6, -1); return <> diff --git a/src/ui/Panel/TimelinePanel/RightPanel/Keyframes.tsx b/src/ui/Panel/TimelinePanel/RightPanel/Keyframes.tsx index 4dabbb4..dc92fb4 100644 --- a/src/ui/Panel/TimelinePanel/RightPanel/Keyframes.tsx +++ b/src/ui/Panel/TimelinePanel/RightPanel/Keyframes.tsx @@ -1,75 +1,71 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; +import { observer } from 'mobx-react-lite'; +import { useRuntimeStore } from '@/runtime/state'; import TimelineListItem from '../List/Item'; -import { useTempo } from '@/ui/contexts/Tempo'; import { useContext } from './Context'; -import { useSelectedItem } from '@/ui/contexts/SelectedItem'; import useDrag from '@/ui/hooks/useDrag'; import { setCSSProperties } from '@/utils/ui'; -import { BeatNumberToArray, GridValue, parseDoublePrecist } from '@/utils/math'; -import ChartKeyframe from '@/Chart/Keyframe'; -import { TChartJudgelineProps } from '@/Chart/JudgelineProps'; -import { BeatArray, Nullable } from '@/utils/types'; - -type KeyframeProps = { - keyframe: ChartKeyframe, - time: number, - nextTime: Nullable, - onSelected: (id: string) => void, - onKeyframeMove: (id: string, newBeat: BeatArray) => void, - onRightClicked: (id: string) => void, +import { BeatNumberToArray, sortByBeat, GridValue, parseDoublePrecist } from '@/utils/math'; +import { IKeyframe } from '@/core/models/Keyframe'; +import { KeyframeType } from '@/core/types'; +import { Nullable } from '@/utils/types'; + +const calculateNewBeat = ( + beatNum: number, + x: number, + beatGrid: number, + tempo: number, + tempoGrid: number +) => { + let newValue = GridValue(beatNum + (x / beatGrid / tempo), tempoGrid); + if (newValue < 0) newValue = 0; + return BeatNumberToArray(newValue, tempo); }; -const Keyframe: React.FC = ({ +const Keyframe = observer<{ + keyframe: IKeyframe, + nextTime: Nullable, + isSelected: boolean, + onRightClicked: (id: string) => void, +}>(({ keyframe, - time, nextTime, - onSelected, - onKeyframeMove, + isSelected, onRightClicked, }) => { - const tempo = useTempo(); + const tempo = useRuntimeStore(s => s.tempo); const { scale } = useContext(); const tempoGrid = useMemo(() => parseDoublePrecist(1 / tempo, 6, -1), [tempo]); const beatGrid = useMemo(() => tempoGrid * scale, [tempoGrid, scale]); - const [ selectedItem, ] = useSelectedItem()!; - const [ currentTime, setCurrentTime ] = useState(time); - - const isSelected = () => { - if (selectedItem === null) return false; - if (selectedItem.keyframe === null) return false; - if (selectedItem.keyframe instanceof Array) { - return selectedItem.keyframe.findIndex((e) => e.id === keyframe.id) !== -1; - } else { - return selectedItem.keyframe.id === keyframe.id; - }; - }; - const calculateNewTime = useCallback((x: number) => { - let newValue = GridValue(keyframe.beatNum + (x / beatGrid / tempo), tempoGrid); - if (newValue < 0) newValue = 0; - return newValue; - }, [keyframe, beatGrid, tempo, tempoGrid]); - - const handleDragging = useCallback(({ x }: { x: number }) => { - setCurrentTime(calculateNewTime(x)); - }, [calculateNewTime]); - - const handleDragEnd = useCallback(({ x }: { x: number }) => { - const newTime = calculateNewTime(x); - - setCurrentTime(newTime); - onKeyframeMove(keyframe.id, BeatNumberToArray(newTime, tempo)); - }, [calculateNewTime, keyframe.id, onKeyframeMove, tempo]); + const setSelectedKeyframe = useRuntimeStore((s) => s.setSelectedKeyframe); + + const handleDrag = (_: unknown, { x }: { x: number }) => { + const _grid = ( + x > 0 ? beatGrid : + x < 0 ? -beatGrid : 0 + ); + + keyframe.updateProps({ + beat: calculateNewBeat( + keyframe.beatNum, + _grid, + beatGrid, + tempo, + tempoGrid + ), + }); + }; - const handleSelected = useCallback(() => { - onSelected(keyframe.id); - }, [keyframe.id, onSelected]); + const handleSelected = () => { + setSelectedKeyframe(keyframe.id); + }; const { onMouseDown } = useDrag({ allowY: false, grid: beatGrid, - onDrag: handleDragging, - onDragEnd: handleDragEnd, + onDrag: handleDrag, + // onDragEnd: handleDragEnd, onClick: handleSelected, }); @@ -78,18 +74,14 @@ const Keyframe: React.FC = ({ else return onMouseDown(e); }, [keyframe.id, onRightClicked, onMouseDown]); - useEffect(() => { - setCurrentTime(time); - }, [time]); - - const className = "timeline-content-key" + (isSelected() ? " selected" : ""); + const className = "timeline-content-key" + (isSelected ? " selected" : ""); return ( <>
= ({
)} ) -}; +}); -export type TimelineRightPanelKeyframesProps = { - type: keyof TChartJudgelineProps, - keyframes: ChartKeyframe[], +const TimelineRightPanelKeyframes = observer<{ + type: KeyframeType, + keyframes: IKeyframe[], timeRange: [number, number], - onKeyframeSelected: (type: keyof TChartJudgelineProps, id: string) => void, - onDoubleClick: (type: keyof TChartJudgelineProps, clickedPosX: number) => void, - onKeyframeMove: (type: keyof TChartJudgelineProps, id: string, newBeat: BeatArray) => void, - onKeyframeDeleted: (type: keyof TChartJudgelineProps, id: string) => void, -}; - -const TimelineRightPanelKeyframes: React.FC = ({ + onDoubleClick: (type: KeyframeType, clickedPosX: number) => void, + onKeyframeDeleted: (type: KeyframeType, id: string) => void, +}>(({ type, keyframes, timeRange, - onKeyframeSelected, onDoubleClick, - onKeyframeMove, onKeyframeDeleted, -}: TimelineRightPanelKeyframesProps) => { - const handleKeyframeSelected = useCallback((id: string) => { - onKeyframeSelected(type, id); - }, [type, onKeyframeSelected]); +}) => { + const selectedKeyframeId = useRuntimeStore((s) => s.selectedKeyframeId); + const _keyframes = keyframes.slice().sort(sortByBeat); const onRowDoubleClick = useCallback((e: React.MouseEvent) => { const target = e.target as HTMLDivElement; @@ -136,10 +121,6 @@ const TimelineRightPanelKeyframes: React.FC = onDoubleClick(type, e.clientX - rect.x); }, [type, onDoubleClick]); - const handleKeyframeMove = useCallback((id: string, newBeat: BeatArray) => { - onKeyframeMove(type, id, newBeat); - }, [type, onKeyframeMove]); - const handleKeyframeRightClick = useCallback((id: string) => { onKeyframeDeleted(type, id); }, [type, onKeyframeDeleted]); @@ -147,18 +128,16 @@ const TimelineRightPanelKeyframes: React.FC = const keyframesDom = useMemo(() => { const result: React.ReactNode[] = []; - for (let i = 0; i < keyframes.length; i++) { - const keyframe = keyframes[i]; + for (let i = 0; i < _keyframes.length; i++) { + const keyframe = _keyframes[i]; if (keyframe.beatNum < timeRange[0]) continue; if (keyframe.beatNum > timeRange[1]) break; result.push( @@ -166,13 +145,13 @@ const TimelineRightPanelKeyframes: React.FC = } return result; - }, [keyframes, timeRange, handleKeyframeMove, handleKeyframeSelected, handleKeyframeRightClick]); + }, [selectedKeyframeId, _keyframes, timeRange, handleKeyframeRightClick]); return ( {keyframesDom} ); -}; +}); export default TimelineRightPanelKeyframes; diff --git a/src/ui/Panel/TimelinePanel/RightPanel/KeyframesRow.tsx b/src/ui/Panel/TimelinePanel/RightPanel/KeyframesRow.tsx index 9cf63ea..fedcce0 100644 --- a/src/ui/Panel/TimelinePanel/RightPanel/KeyframesRow.tsx +++ b/src/ui/Panel/TimelinePanel/RightPanel/KeyframesRow.tsx @@ -1,45 +1,25 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback } from 'react'; +import { useRuntimeStore } from '@/runtime/state'; import TimelineListItem from '../List/Item'; import Keyframes from './Keyframes'; -import { useTempo } from '@/ui/contexts/Tempo'; import { useContext } from './Context'; -import ChartJudgeline from '@/Chart/Judgeline'; -import { TChartJudgelineProps } from '@/Chart/JudgelineProps'; -import ChartKeyframe from '@/Chart/Keyframe'; -import { BeatArray, Nullable } from '@/utils/types'; -import { useSelectedItem } from '@/ui/contexts/SelectedItem'; -import { BeatArrayToNumber } from '@/utils/math'; +import { BeatArray } from '@/utils/types'; +import { IJudgeLine } from '@/core/models/JudgeLine'; +import { KeyframeType } from '@/core/types'; -const getLastKeyframe = (beat: BeatArray, keyframes: ChartKeyframe[]): Nullable => { - const time = BeatArrayToNumber(beat); - - for (let i = 0; i < keyframes.length; i++) { - const keyframe = keyframes[i]; - if (keyframe.beatNum < time) continue; - if (keyframe.beatNum > time) { - if (i === 0) break; - return keyframes[i - 1]; - } - } - - return null; -} - -type KeyframesRowProps = { - line: ChartJudgeline, +const KeyframesRow: React.FC<{ + line: IJudgeLine, isExpanded: boolean, -}; - -const KeyframesRow: React.FC = ({ +}> = ({ line, isExpanded, }) => { - const tempo = useTempo(); + const tempo = useRuntimeStore(s => s.tempo); const { scale, timeRange } = useContext(); - const [ lineProp, setLineProp ] = useState({ ...line.props }); - const [ , setSelectedItem ] = useSelectedItem()!; - const onAddKeyframe = useCallback((type: keyof TChartJudgelineProps, clickedPosX: number) => { + const setSelectedKeyframe = useRuntimeStore((s) => s.setSelectedKeyframe); + + const onAddKeyframe = useCallback((type: KeyframeType, clickedPosX: number) => { const beat = clickedPosX / scale; let beatFloor = Math.floor(beat); let beatSub = Math.round((beat - beatFloor) * tempo); @@ -50,69 +30,19 @@ const KeyframesRow: React.FC = ({ } const beatArr: BeatArray = [ beatFloor, beatSub, tempo ]; - const lastKeyframe = getLastKeyframe(beatArr, line.props[type]); - line.addKeyframe( - type, - beatArr, - lastKeyframe ? lastKeyframe.value : 0, - lastKeyframe ? lastKeyframe.continuous : false, - lastKeyframe ? lastKeyframe.easing : 0 + line.keyframes.add( + type, beatArr, 1, true, 1 ); }, [scale, tempo, line]); - const onKeyframeMove = useCallback((type: keyof TChartJudgelineProps, id: string, newBeat: BeatArray) => { - setSelectedItem((oldItem) => { - if (oldItem !== null) return { ...oldItem, keyframe: null }; - else return null; - }); - line.editKeyframe(type, id, { beat: newBeat }); - }, [line, setSelectedItem]); - - const onKeyframeDeleted = useCallback((type: keyof TChartJudgelineProps, id: string) => { - setSelectedItem((oldItem) => { - if (oldItem !== null) return { ...oldItem, keyframe: null }; - else return null; - }); - line.deleteKeyframe(type, id); - }, [line, setSelectedItem]); - - const handlePropsUpdate = useCallback(({ - type, - keyframes, - }: { - type: keyof TChartJudgelineProps, - keyframes: ChartKeyframe[], - }) => { - const newProp: Partial = {}; - newProp[type] = keyframes; - setLineProp({ ...lineProp, ...newProp }); - }, [lineProp]); - - const handleKeyframeSelected = useCallback((type: keyof TChartJudgelineProps, id: string) => { - const keyframe = line.findKeyframeById(type, id); - if (!keyframe) return; - - setSelectedItem({ - line, - keyframe: { - type, id - }, - note: null, - }); - }, [line, setSelectedItem]); - - useEffect(() => { - line.events.on('props.updated', handlePropsUpdate); - return (() => { - line.events.off('props.updated', handlePropsUpdate); - }); - }, [line, handlePropsUpdate]); + const onKeyframeDeleted = useCallback((type: KeyframeType, id: string) => { + setSelectedKeyframe(null); + line.keyframes.remove(type, id); + }, [line, setSelectedKeyframe]); const keyframesProps = { timeRange, - onKeyframeSelected: handleKeyframeSelected, onDoubleClick: onAddKeyframe, - onKeyframeMove, onKeyframeDeleted, }; @@ -120,28 +50,28 @@ const KeyframesRow: React.FC = ({ {isExpanded && <> } diff --git a/src/ui/Panel/TimelinePanel/RightPanel/RightPanel.tsx b/src/ui/Panel/TimelinePanel/RightPanel/RightPanel.tsx index 7b87566..5c633db 100644 --- a/src/ui/Panel/TimelinePanel/RightPanel/RightPanel.tsx +++ b/src/ui/Panel/TimelinePanel/RightPanel/RightPanel.tsx @@ -1,24 +1,26 @@ -import React, { useCallback, useState, useEffect, useMemo, useRef } from 'react'; -import Chart from '@/Chart/Chart'; -import ChartJudgeline from "@/Chart/Judgeline"; +import React, { useCallback, useRef } from 'react'; +import { observer } from 'mobx-react-lite'; +import { useRuntimeAudioStore } from '@/runtime/audio/state'; +import { getTimeByBeat } from '@/core/timeline/bpm'; import RightPanelProvider from './Context/Provider'; import ScrollBar from '@/ui/components/ScrollBar'; import TimelineList from "../List/List"; import TimelineSeeker from './Seeker'; import RightPanelHead from './Head'; import KeyframesRow from './KeyframesRow'; +import { IJudgeLine } from '@/core/models/JudgeLine'; import './styles.css'; import useResizeEffect from '@/ui/hooks/useResizeEffect'; -export type TimelineRightPanelProps = { - lines: ChartJudgeline[], - expandedLines: number[], +const TimelineRightPanel = observer<{ + lines: IJudgeLine[], + audioOffset: number, + expandedLines: string[], listScrolled: number, onListScroll: (scrolled: number) => void, -}; - -const TimelineRightPanel: React.FC = ({ +}>(({ lines, + audioOffset, expandedLines, listScrolled, onListScroll, @@ -28,24 +30,14 @@ const TimelineRightPanel: React.FC = ({ const containerHeight = useRef(0); const contentHeight = useRef(0); const listScrollRef = useRef(0); - const [ timeLength, setTimeLength ] = useState(0); - const [ timeOffset, setTimeOffset ] = useState(0); + + const durationBeat = useRuntimeAudioStore((s) => s.durationBeat); + const seekMusic = useRuntimeAudioStore((s) => s.seek); const onSeeked = useCallback((time: number) => { - if (!Chart.info) return; - Chart.beatNum = time; + seekMusic(getTimeByBeat(time)); }, []); - const keyframeRowMemoed = useMemo(() => { - return lines.map((line, index) => { // TODO: Render keyframes - return ; - }); - }, [lines, expandedLines]); - const updateListScroll = useCallback((_scroll: number) => { const scroll = _scroll / 100; const heightDiff = contentHeight.current - containerHeight.current; @@ -56,41 +48,12 @@ const TimelineRightPanel: React.FC = ({ useResizeEffect(({ height }) => { containerHeight.current = height; - updateListScroll(listScrollRef.current); - }, scrollContainerRef); - - useResizeEffect(({ height }) => { contentHeight.current = height; updateListScroll(listScrollRef.current); - }, scrollContentRef); - - useEffect(() => { - const updateTimeLength = () => { - setTimeLength(Chart.beatDuration); - }; - - Chart.events.once('music.loaded', updateTimeLength); - Chart.events.on('bpms.updated', updateTimeLength); - - return (() => { - Chart.events.off('music.loaded', updateTimeLength); - Chart.events.off('bpms.updated', updateTimeLength); - }); - }, []); - - useEffect(() => { - const updateTimeOffset = ({ offsetBeat }: { offsetBeat: number }) => { - setTimeOffset(offsetBeat); - }; - - Chart.events.on('offset.updated', updateTimeOffset); - return (() => { - Chart.events.off('offset.updated', updateTimeOffset); - }); - }); + }, scrollContainerRef); return ( - + @@ -109,13 +72,19 @@ const TimelineRightPanel: React.FC = ({ }} ref={scrollContentRef} > - {keyframeRowMemoed} + {lines.map((line) => ( + + ))}
@@ -126,11 +95,11 @@ const TimelineRightPanel: React.FC = ({ />
) -}; +}); export default TimelineRightPanel; diff --git a/src/ui/Panel/TimelinePanel/RightPanel/Seeker.tsx b/src/ui/Panel/TimelinePanel/RightPanel/Seeker.tsx index 96cfb5c..0cbb0de 100644 --- a/src/ui/Panel/TimelinePanel/RightPanel/Seeker.tsx +++ b/src/ui/Panel/TimelinePanel/RightPanel/Seeker.tsx @@ -1,5 +1,5 @@ import React, { useRef } from 'react'; -import { useClockTime } from '@/ui/contexts/Clock'; +import { useRuntimeAudioBeat } from '@/runtime/audio/state'; import { useContext } from '../RightPanel/Context'; import useDrag from '@/ui/hooks/useDrag'; import { Point } from '@/utils/types'; @@ -13,7 +13,7 @@ const TimelineSeeker: React.FC = ({ timeLength, onSeek, }: TimelineSeekerProps) => { - const { beat } = useClockTime(); + const beat = useRuntimeAudioBeat(); const { scale } = useContext(); const handleStartTime = useRef(NaN); diff --git a/src/ui/Panel/TimelinePanel/Timeline.tsx b/src/ui/Panel/TimelinePanel/Timeline.tsx index fc6f77b..9442a23 100644 --- a/src/ui/Panel/TimelinePanel/Timeline.tsx +++ b/src/ui/Panel/TimelinePanel/Timeline.tsx @@ -1,32 +1,20 @@ -import { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import SplitPane from 'react-split-pane'; +import { store as ChartStore } from '@/core/state/chartStore'; import TimelineFooter from './Footer'; -import Chart from '@/Chart/Chart'; -import ChartJudgeline from '@/Chart/Judgeline'; import TimelineLeftPanel from './LeftPanel/LeftPanel'; import TimelineRightPanel from './RightPanel/RightPanel'; import './styles.css'; const Timeline: React.FC = () => { - const [ lineList, setLineList ] = useState([]); - const [ expandedLines, setExpandedLines ] = useState([]); + const [ expandedLines, setExpandedLines ] = useState([]); const [ listScrolled, setListScrolled ] = useState(0); - const setLineExpand = (lineId: number, isExpanded: boolean) => { + const setLineExpand = (lineId: string, isExpanded: boolean) => { if (isExpanded) setExpandedLines([ ...expandedLines, lineId ]); else setExpandedLines([ ...expandedLines.filter((e) => e !== lineId) ]); }; - useEffect(() => { - const updateLineList = (newLines: ChartJudgeline[]) => { - setLineList([ ...newLines ]); - }; - Chart.events.on('lines.updated', updateLineList); - return (() => { - Chart.events.off('lines.updated', updateLineList); - }); - }, []); - return (
@@ -42,13 +30,14 @@ const Timeline: React.FC = () => { }} > setLineExpand(id, e)} + onLineExpanded={setLineExpand} /> { leftContent={ <> diff --git a/src/ui/components/BeatInput.tsx b/src/ui/components/BeatInput.tsx index fa9354c..d3dc1c0 100644 --- a/src/ui/components/BeatInput.tsx +++ b/src/ui/components/BeatInput.tsx @@ -1,52 +1,83 @@ +import { useRef, useCallback, useEffect } from 'react'; import Input from '@/ui/components/NumberInput'; +import { HStack } from '@chakra-ui/react'; +import { normalizeBeatArray } from '@/utils/math'; import { BeatArray } from '@/utils/types'; -import { useCallback } from 'react'; type BeatInputProps = { beat: BeatArray, - onInput: (beat: BeatArray) => void, + onChanged?: (beat: BeatArray) => void, + onInput?: (beat: BeatArray) => void, className?: string, }; const BeatInput = ({ beat, + onChanged, onInput, className, }: BeatInputProps) => { + const beatRef = useRef(beat); + + const handleChanged = useCallback((index: 0 | 1 | 2, value: number) => { + const result: BeatArray = [ ...beatRef.current ]; + result[index] = value; + if (index === 0 || index === 1) result[index] -= 1; + + if (onChanged) onChanged(normalizeBeatArray(result)); + }, [onChanged]); + + const handleLoop = useCallback((direction: 1 | -1) => { + const result: BeatArray = [ ...beatRef.current ]; + + result[0] += direction; + if (direction === 1) result[1] = 0; + else result[1] = result[2] - 1; + + if (onChanged) onChanged(normalizeBeatArray(result)); + }, [onChanged]); + const handleInput = useCallback((index: 0 | 1 | 2, value: number) => { - const result: BeatArray = [ ...beat ]; + const result: BeatArray = [ ...beatRef.current ]; result[index] = value; if (index === 0 || index === 1) result[index] -= 1; - onInput(result); - }, [beat, onInput]); + + if (onInput) onInput(result); + }, [onInput]); + + useEffect(() => { + beatRef.current = beat; + }, [beat]); return ( -
+ handleChanged(0, e)} onInput={(e) => handleInput(0, e)} /> - : + : handleChanged(1, e)} + onLoop={handleLoop} onInput={(e) => handleInput(1, e)} /> - / + / handleChanged(2, e)} onInput={(e) => handleInput(2, e)} /> -
+ ); }; diff --git a/src/ui/components/NumberInput.tsx b/src/ui/components/NumberInput.tsx index c7f870c..aa2a152 100644 --- a/src/ui/components/NumberInput.tsx +++ b/src/ui/components/NumberInput.tsx @@ -1,17 +1,38 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; -import { setDragStyle } from '@/utils/ui'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { NumberInput as NumInput } from '@chakra-ui/react'; +import useDrag from '../hooks/useDrag'; +import { KeyboardEventHandler } from 'react'; import { Nullable } from '@/utils/types'; -// Thanks to [lil.gui](https://github.com/georgealways/lil-gui/blob/master/src/NumberController.js#L140) -// for the drag algorithm - -const DRAG_THRESH = 5; - const getDragStep = (min?: number, max?: number) => { if (min !== (void 0) && max !== (void 0)) return (max - min) / 1000; else return 0.1; }; +const clampValue = (value: number, min?: number, max?: number) => { + if (min !== (void 0) && value < min) return min; + if (max !== (void 0) && value > max) return max; + return value; +}; + +const snapValue = (value: number, min?: number, max?: number, step?: number) => { + const _step = step ?? getDragStep(min, max); + let _value = value; + let offset = 0; + if (min !== (void 0)) offset = min; + else if (max !== (void 0)) offset = max; + + _value -= offset; + _value = Math.round(_value / _step) * _step; + _value += offset; + + _value = parseFloat(_value.toPrecision(10)); + return _value; +}; + +const clampSnapValue = (value: number, min?: number, max?: number, step?: number) => + snapValue(clampValue(value, min, max), min, max, step); + type NumberInputProps = { min?: number, max?: number, @@ -19,8 +40,10 @@ type NumberInputProps = { dragStep?: number, defaultValue?: number, placeholder?: string, + allowLoop?: boolean, onChanged?: (newValue: number) => void, onInput?: (newValue: number) => void, + onLoop?: (loopDirection: 1 | -1, afterLoopValue: number) => void, className?: string, style?: React.CSSProperties, }; @@ -32,181 +55,119 @@ const NumberInput = ({ dragStep, defaultValue, placeholder, + allowLoop, onChanged, onInput, + onLoop, className, style }: NumberInputProps) => { - const inputStyle = style ? style : {}; - const [ value, setValue ] = useState>(defaultValue ?? null); - const inputRef = useRef>(null); - const lastChanged = useRef(defaultValue ?? (min ?? 0)); - const lastInput = useRef(defaultValue ?? (min ?? 0)); - - const clampValue = useCallback((newValue: number) => { - if (min !== (void 0) && newValue < min) return min; - if (max !== (void 0) && newValue > max) return max; - return newValue; - }, [max, min]); - - const snapValue = useCallback((value: number) => { - const _step = step ?? getDragStep(min, max); - let _value = value; - let offset = 0; - if (min !== (void 0)) offset = min; - else if (max !== (void 0)) offset = max; - - _value -= offset; - _value = Math.round(_value / _step) * _step; - _value += offset; - - _value = parseFloat(_value.toPrecision(10)); - return _value; - }, [min, max, step]); - - const clampSnapValue = useCallback((value: number) => { - return snapValue(clampValue(value)); - }, [clampValue, snapValue]); - - const handleChanged = useCallback((e: React.ChangeEvent) => { - const target = e.target as HTMLInputElement; - const value = parseFloat(target.value); - - if (!isNaN(value)) { - setValue(value); - - const _value = clampSnapValue(value); - if (lastChanged.current !== _value) { - if (onChanged) onChanged(_value); - lastChanged.current = _value; + const inputStyle = style ?? {}; + const inputRef = useRef(null); + const [ value, setValue ] = useState>(defaultValue ?? min ?? null); + const valueRef = useRef>(defaultValue ?? min ?? null); + const lastInputRef = useRef(NaN); + const isDraggingStartRef = useRef(false); + const hasLoopedRef = useRef(false); + const isLoopable = useMemo(() => !!(min && max && allowLoop), [ min, max, allowLoop ]); + + const handleValueChange = (newValue: number) => { + if (isNaN(newValue)) return; + + let _newValue = snapValue(newValue, min, max, step); + let loopDirection: -1 | 0 | 1 = 0; + + if (isLoopable) { + if (_newValue > max!) { + loopDirection = 1; + _newValue = min!; + } else if (_newValue < min!) { + loopDirection = -1; + _newValue = max!; } - } else { - setValue(null); } - }, [onChanged, clampSnapValue]); + _newValue = clampValue(_newValue, min, max); - const handleChangeEnd = useCallback(() => { - if (isDragTesting.current) return; + setValue(_newValue); + valueRef.current = _newValue; - const _value = clampSnapValue(value ?? lastInput.current); - setValue(_value); - - if (lastInput.current !== _value) { - if (onInput) onInput(_value); - lastInput.current = _value; + if (loopDirection !== 0) { + hasLoopedRef.current = true; + if (onLoop) onLoop(loopDirection, _newValue); } - }, [value, onInput, clampSnapValue]); - - const handleKeydown = useCallback((e: React.KeyboardEvent) => { - if (!inputRef.current) return; - if (e.key === 'Enter') inputRef.current.blur(); - }, []); - - // Handle dragging - const isDragging = useRef(false); - const isDragTesting = useRef(false); - const dragStartValue = useRef(lastInput.current); - const dragStartPosX = useRef(NaN); - const dragStartPosY = useRef(NaN); - const dragPrevPosY = useRef(NaN); - const dragDelta = useRef(NaN); - - const handleMouseUp = useCallback(() => { - if (!isDragging.current) return; - if (!isNaN(dragDelta.current)) handleChangeEnd(); - - isDragging.current = false; - isDragTesting.current = false; - - dragStartPosX.current = NaN; - dragStartPosY.current = NaN; - dragPrevPosY.current = NaN; - - dragDelta.current = NaN; - setDragStyle(); - }, [handleChangeEnd]); - - const handleMouseMove = useCallback((e: MouseEvent) => { - if (!isDragging.current) return; - if (!inputRef.current) return; - - if (isDragTesting.current) { - const dx = e.clientX - dragStartPosX.current; - const dy = e.clientY - dragStartPosY.current; - - if (Math.abs(dy) > DRAG_THRESH) { - e.preventDefault(); - inputRef.current.blur(); - isDragTesting.current = false; - setDragStyle('vertical'); - - } else if (Math.abs(dx) > DRAG_THRESH) { - handleMouseUp(); - } - } else { - if (isNaN(dragDelta.current)) dragDelta.current = 0; - - const dy = e.clientY - dragPrevPosY.current; - dragDelta.current -= dy * (dragStep ?? step ?? getDragStep(min, max)); - - if (max !== (void 0) && dragStartValue.current + dragDelta.current > max) { - dragDelta.current = max - dragStartValue.current; - } else if (min !== (void 0) && dragStartValue.current + dragDelta.current < min) { - dragDelta.current = min - dragStartValue.current; - } - - const dragValue = clampSnapValue(parseFloat((dragDelta.current + dragStartValue.current).toPrecision(10))); - if (dragValue !== lastChanged.current) { - setValue(dragValue); - if (onChanged) onChanged(dragValue); - lastChanged.current = dragValue; - } - - dragPrevPosY.current = e.clientY; + else if (onChanged) onChanged(_newValue); + }; + + const handleValueChangeEnd = () => { + if (valueRef.current === null) return; + if (lastInputRef.current === valueRef.current && !hasLoopedRef.current) return; + + hasLoopedRef.current = false; + lastInputRef.current = valueRef.current; + if (onInput) onInput(valueRef.current); + }; + + const handleBlur = () => { + if (isDraggingStartRef.current) { + isDraggingStartRef.current = false; + return; } - }, [min, max, dragStep, step, onChanged, handleMouseUp, clampSnapValue]); - - const handleMouseDown = useCallback((e: React.MouseEvent) => { - isDragging.current = true; - isDragTesting.current = true; - dragStartValue.current = value ?? defaultValue ?? (min ?? 0); - - dragStartPosX.current = e.clientX; - dragStartPosY.current = e.clientY; - dragPrevPosY.current = e.clientY; - }, [defaultValue, min, value]); - - useEffect(() => { - window.addEventListener('mousemove', handleMouseMove); - window.addEventListener('mouseup', handleMouseUp); - - return (() => { - window.removeEventListener('mousemove', handleMouseMove); - window.removeEventListener('mouseup', handleMouseUp); - }); - }, [handleMouseMove, handleMouseUp]); + handleValueChangeEnd(); + }; + + const handleKeyDown: KeyboardEventHandler = (e) => { + if (e.key === 'Enter' && inputRef.current) inputRef.current.blur(); + }; + + const handleDragStart = () => { + isDraggingStartRef.current = true; + if (inputRef.current) inputRef.current.blur(); + }; + + const handleDragging = (_: unknown, { y }: { y: number }) => { + const direction = ( + y < 0 ? 1 : + y > 0 ? -1 : 0 + ); + handleValueChange((valueRef.current ?? 0) + (dragStep ?? step ?? getDragStep(min, max)) * direction); + }; + + const { onMouseDown } = useDrag({ + grid: 5, + allowX: false, + allowY: true, + onDragStart: handleDragStart, + onDrag: handleDragging, + onDragEnd: handleValueChangeEnd, + }); useEffect(() => { if (defaultValue === (void 0)) return; - setValue(clampSnapValue(defaultValue)); - }, [clampSnapValue, defaultValue]); + const _newValue = clampSnapValue(defaultValue, min, max, step); + setValue(_newValue); + valueRef.current = _newValue; + }, [defaultValue]); return ( - handleValueChange(e.valueAsNumber)} + onKeyDown={handleKeyDown} className={className} style={inputStyle} - ref={inputRef} - /> + > + + + ) }; diff --git a/src/ui/contexts/Clock/Provider.tsx b/src/ui/contexts/Clock/Provider.tsx deleted file mode 100644 index a540631..0000000 --- a/src/ui/contexts/Clock/Provider.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Ticker } from 'pixi.js'; -import ClockTimeContext from '.'; -import Chart from '@/Chart/Chart'; - -export type ClockTimeProviderProps = { - children: React.ReactNode, -}; - -const ClockTimeProvider: React.FC = ({ - children -}: ClockTimeProviderProps) => { - const [ time, setTime ] = useState(0); - const [ beat, setBeat ] = useState(0); - const [ beatOffset, setBeatOffset ] = useState(0); - - useEffect(() => { - const ticker = Ticker.shared; - const updateTime = () => { - setTime(Chart.time); - setBeat(Chart.beatNum); - setBeatOffset(Chart.offsetBeat); - }; - ticker.add(updateTime); - - return (() => { - ticker.remove(updateTime); - }); - }, []); - - return - {children} - -}; - -export default ClockTimeProvider; diff --git a/src/ui/contexts/Clock/index.ts b/src/ui/contexts/Clock/index.ts deleted file mode 100644 index 78d0c10..0000000 --- a/src/ui/contexts/Clock/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createContext, useContext } from 'react'; - -type ClockTime = { - time: number, - beat: number, - beatOffset:number, -}; - -const ClockTimeContext = createContext({ time: 0, beat: 0, beatOffset: 0 }); - -export const useClockTime = () => useContext(ClockTimeContext); - -export default ClockTimeContext; diff --git a/src/ui/contexts/Dialog/Provider.tsx b/src/ui/contexts/Dialog/Provider.tsx deleted file mode 100644 index 9e1293c..0000000 --- a/src/ui/contexts/Dialog/Provider.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useState } from 'react'; -import DialogContext from '.'; -import { $DialogProps } from './types'; -import { Dialog } from '@blueprintjs/core'; - -type DialogProviderProps = { - children: React.ReactNode, -}; - -const DialogProvider = ({ - children -}: DialogProviderProps) => { - const [ dialogProps, setDialogProps ] = useState<$DialogProps>({}); - const [ isOpen, setIsOpen ] = useState(false); - - const openDialog = () => setIsOpen(true); - const closeDialog = () => setIsOpen(false); - - const showDialog = (props: $DialogProps) => { - setDialogProps(props); - openDialog(); - }; - - return ( - - {children} - - - ) -}; - -export default DialogProvider; diff --git a/src/ui/contexts/Dialog/index.ts b/src/ui/contexts/Dialog/index.ts deleted file mode 100644 index d6cfe93..0000000 --- a/src/ui/contexts/Dialog/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createContext, useContext } from 'react'; -import { $DialogProps, DialogContextProps } from './types'; - -const uninitFn = (props: $DialogProps) => void props; - -const DialogContext = createContext({ - show: uninitFn, - close: () => void 0, -}); - -export const useDialog = () => useContext(DialogContext); -export default DialogContext; diff --git a/src/ui/contexts/Dialog/types.ts b/src/ui/contexts/Dialog/types.ts deleted file mode 100644 index b5bdda1..0000000 --- a/src/ui/contexts/Dialog/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { DialogProps } from '@blueprintjs/core'; - -export type $DialogProps = Omit; - -export type DialogContextProps = { - show: (props: $DialogProps) => void, - close: () => void, -}; diff --git a/src/ui/contexts/SelectedItem/Provider.tsx b/src/ui/contexts/SelectedItem/Provider.tsx deleted file mode 100644 index 006cd37..0000000 --- a/src/ui/contexts/SelectedItem/Provider.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import SelectedItemContext from '.'; -import { SelectedItem } from '.'; -import { Nullable } from '@/utils/types'; - -type SelectedItemProviderProps = { - children: React.ReactNode, -}; - -const SelectedItemProvider = ({ - children -}: SelectedItemProviderProps) => { - const [ item, setItem ] = useState(null); - - const unsetItem = useCallback((e: MouseEvent) => { - if (!item) return; - - const target = e.target as Nullable; - if (!target) return; - if ( - target.closest('.timeline-panel-head-right') || - target.closest('.edit-panel') - ) return; - - const parentDom = target.parentElement; - if (parentDom) { - if ( - parentDom.closest('.timeline-time-seeker') || - parentDom.closest('.edit-panel') || - parentDom.closest('.note-panel') - ) return; - } - - if (!target.classList.contains('timeline-content-key')) return setItem(null); - }, [item]); - - useEffect(() => { - document.addEventListener('mousedown', unsetItem); - return (() => { - document.removeEventListener('mousedown', unsetItem); - }); - }, [unsetItem]); - - return ( - - {children} - - ); -}; - -export default SelectedItemProvider; diff --git a/src/ui/contexts/SelectedItem/index.ts b/src/ui/contexts/SelectedItem/index.ts deleted file mode 100644 index 69fa085..0000000 --- a/src/ui/contexts/SelectedItem/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { createContext, useContext } from 'react'; -import { Nullable } from '@/utils/types'; -import ChartJudgeline from '@/Chart/Judgeline'; -import { TChartJudgelineProps } from '@/Chart/JudgelineProps'; - -export type SelectedKeyframe = { - type: keyof TChartJudgelineProps, - id: string, -}; - -export type SelectedNote = { - id: string, -}; - -export type SelectedItem = Nullable<{ - line: ChartJudgeline, - keyframe: Nullable, - note: Nullable, -}>; - -export type TSelectedItemContext = Nullable< - [ SelectedItem, React.Dispatch> ] ->; - -const SelectedItemContext = createContext(null); - -export const useSelectedItem = () => useContext(SelectedItemContext); - -export default SelectedItemContext; diff --git a/src/ui/contexts/Settings/Provider.tsx b/src/ui/contexts/Settings/Provider.tsx deleted file mode 100644 index c094c90..0000000 --- a/src/ui/contexts/Settings/Provider.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useEffect, useState } from 'react'; -import Settings from '@/Settings/Settings'; -import SettingsContext from '.'; -import { TSettings } from '@/Settings/types'; - -type SettingsProviderProps = { - children: React.ReactNode, -}; - -const SettingsProvider = ({ - children -}: SettingsProviderProps) => { - const [ settings, setSettings ] = useState(Settings.current); - - useEffect(() => { - const updateSettings = (newSettings: TSettings) => { - setSettings(newSettings); - }; - - Settings.event.on('settings.updated', updateSettings); - return (() => { - Settings.event.off('settings.updated', updateSettings); - }); - }, []); - - return ( - - {children} - - ) -}; - -export default SettingsProvider; diff --git a/src/ui/contexts/Settings/index.ts b/src/ui/contexts/Settings/index.ts deleted file mode 100644 index 1db28ef..0000000 --- a/src/ui/contexts/Settings/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createContext, useContext } from 'react'; -import Settings from '@/Settings/Settings'; - -const settingsContext = createContext(Settings.current); - -export const useSettings = () => useContext(settingsContext); - -export default settingsContext; diff --git a/src/ui/contexts/Tempo.ts b/src/ui/contexts/Tempo.ts deleted file mode 100644 index a76a886..0000000 --- a/src/ui/contexts/Tempo.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createContext, useContext } from 'react'; - -const TempoContext = createContext(4); - -export const useTempo = () => useContext(TempoContext); - -export default TempoContext; diff --git a/src/ui/dialogs/createProject.tsx b/src/ui/dialogs/createProject.tsx new file mode 100644 index 0000000..d18c40d --- /dev/null +++ b/src/ui/dialogs/createProject.tsx @@ -0,0 +1,189 @@ +import { Button, Dialog, Stack, Field, FileUpload, Input, InputGroup, CloseButton } from "@chakra-ui/react"; +import { dialog } from "../overlays/dialog"; +import ProjectPanel from '@/ui/Panel/ProjectPanel/ProjectPanel'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faMusic, faImage } from "@fortawesome/free-solid-svg-icons"; +import { loadFiles } from "@/runtime/resources/loader"; +import { createNewProject } from "@/core/project/create"; +import { SnapshotIn } from "mobx-state-tree"; +import { Metadata } from "@/core/models/Metadata"; + +type TMetadata = Omit, 'musicFile' | 'backgroundFile'>; +type TMetadataWithFile = TMetadata & { + music: File, + background: File, +}; + +export const showCreateProjectDialog = () => { + const metadata: Partial = { + name: 'Untitled', + artist: 'Unknown', + illustration: 'Unknown', + level: 'SP Lv.?', + designer: 'Unknown', + }; + + const handleUpdateMetadata = (newMetadata: TMetadata) => { + Object.assign(metadata, newMetadata); + }; + + const handleUpdateFile = (key: K, files: File[]) => { + const [ file ] = files; + if (!file) return; + + Object.assign(metadata, { + [key]: file, + }); + }; + + const handleProjectCreate = async () => { + dialog.close('create-project'); + await dialog.waitForExit('create-project'); + + if (!metadata.music) { + dialog.open('create-project-error', { + title: 'Error', + description: 'No music file selected', + footer: ( + + + + ) + }); + return; + } + + if (!metadata.background) { + dialog.open("create-project-error", { + title: 'Error', + description: 'No background file selected', + footer: ( + + + + ) + }); + return; + } + + dialog.open("create-project-loading", { + title: 'Loading', + description: 'Processing files...', // TODO: Progress circle + closeButton: false, + }); + + const loadFileResult = await loadFiles([ metadata.music, metadata.background ]); + if (loadFileResult.failed.includes(metadata.music.name)) { + dialog.close('create-project-loading'); + await dialog.waitForExit('create-project-loading'); + + dialog.open("create-project-error", { + title: 'Error', + description: `Failed to load audio file: ${metadata.music.name}`, + footer: ( + + + + ) + }); + } + + const _metadata: SnapshotIn & { music?: File, background?: File } = { + ...metadata, + musicFile: metadata.music.name, + backgroundFile: metadata.background.name, + }; + delete _metadata.music; + delete _metadata.background; + + createNewProject(_metadata); + + dialog.close('create-project-loading'); + }; + + dialog.open("create-project", { + title: 'Create Project', + content: ( + + + Music File + handleUpdateFile('music', e.files)} + > + + + } + endElement={ + + + + } + > + + + + + + + + + + + Background Image File + handleUpdateFile('background', e.files)} + > + + + } + endElement={ + + + + } + > + + + + + + + + + + + + ), + footer: ( + <> + + + + + + ), + closeButton: true + }); +}; diff --git a/src/ui/hooks/useDrag.ts b/src/ui/hooks/useDrag.ts index 41dbe94..bd30926 100644 --- a/src/ui/hooks/useDrag.ts +++ b/src/ui/hooks/useDrag.ts @@ -14,13 +14,19 @@ type UseDragProps = { allowX?: boolean, allowY?: boolean, onDragStart?: () => void, - onDrag?: (point: Point) => void, + onDrag?: (point: Point, direction: Point) => void, onDragEnd?: (point: Point) => void, onClick?: () => void, }; const getGrid = (grid: number | Point, type: keyof Point) => typeof grid === 'number' ? grid : grid[type]; +const getDirection = (before: number, after: number) => { + if (before < after) return 1; + if (before > after) return -1; + return 0; +}; + // Thanks to [lil.gui](https://github.com/georgealways/lil-gui/blob/master/src/NumberController.js#L140) // for the drag algorithm @@ -119,7 +125,14 @@ const useDrag = ({ lastDragPos.current.y !== dragDelta.current.y ) ) { - if (onDrag) onDrag(dragDelta.current); + if (onDrag) { + const beforePoint = lastDragPos.current || dragStartPos.current; + + onDrag(dragDelta.current, { + x: getDirection(beforePoint.x, dragDelta.current.x), + y: getDirection(beforePoint.y, dragDelta.current.y) + }); + } lastDragPos.current = { ...dragDelta.current }; } } diff --git a/src/ui/hooks/usePixi.ts b/src/ui/hooks/usePixi.ts deleted file mode 100644 index b9de253..0000000 --- a/src/ui/hooks/usePixi.ts +++ /dev/null @@ -1,64 +0,0 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; -import { Application, ApplicationOptions } from 'pixi.js'; -import { Nullable } from '@/utils/types'; - -type PixiHookOptions = Partial & { - containerRef?: React.RefObject, -}; - -type PixiHookResult = [ - Nullable, - () => void, -]; - -const usePixi = ({ - containerRef, - ...options -}: PixiHookOptions): PixiHookResult => { - const [ app, setApp ] = useState>(null); - const appRef = useRef>(null); - const isCanvasPushed = useRef(false); - - const destroy = useCallback(() => { - if (!appRef.current) return; - - appRef.current.destroy({ removeView: true }, {}); - appRef.current = null; - isCanvasPushed.current = false; - setApp(null); - }, []); - - useEffect(() => { - if (appRef.current !== null) return; - - const _options: Partial = { hello: true, ...options }; - if (containerRef && containerRef.current) { - const containerDom = containerRef.current; - _options.width = containerDom.clientWidth; - _options.height = containerDom.clientHeight; - _options.resizeTo = containerDom; - } - - const newApp = new Application(); - newApp.init(_options) - .then(() => { - appRef.current = newApp; - setApp(newApp); - - if (containerRef && containerRef.current) { - if (!isCanvasPushed.current) { - containerRef.current.appendChild(newApp.canvas); - isCanvasPushed.current = true; - } - } - }) - .catch((e) => { throw e }); - }, [containerRef, options]); - - return [ - app, - destroy, - ]; -}; - -export default usePixi; diff --git a/src/ui/index.tsx b/src/ui/index.tsx index 31bd70b..330ffde 100644 --- a/src/ui/index.tsx +++ b/src/ui/index.tsx @@ -1,11 +1,27 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import { ThemeProvider } from './theme/Provider.tsx' +import { DialogViewport } from './overlays/dialog.tsx' +import { ToasterViewport, toaster } from './overlays/toaster.tsx'; import '@/i18n' import './styles/index.css' import App from './App.tsx' createRoot(document.getElementById('root')!).render( - + + + + + , -) +); + +window.addEventListener('error', (e) => { + toaster.create({ + type: 'error', + title: 'Error', + description: e.error.message, + closable: true, + }); +}); diff --git a/src/ui/overlays/dialog.tsx b/src/ui/overlays/dialog.tsx new file mode 100644 index 0000000..ba77143 --- /dev/null +++ b/src/ui/overlays/dialog.tsx @@ -0,0 +1,51 @@ +import { Dialog, CloseButton, Portal, createOverlay } from "@chakra-ui/react"; +import { DialogRootProps } from "@chakra-ui/react"; + +type $DialogRootProps = Omit; + +type DialogProps = { + title?: string + description?: string + content?: React.ReactNode + footer?: React.ReactNode + closeButton?: boolean +}; + +export const dialog = createOverlay((props) => { + const { title, description, content, footer, closeButton, ...rest } = props; + + return ( + + + + + + {title && ( + + {title} + + )} + + {description && ( + {description} + )} + {content} + + {footer && ( + + {footer} + + )} + {closeButton && ( + + + + )} + + + + + ); +}); + +export const DialogViewport = dialog.Viewport as React.ElementType<{}>; diff --git a/src/ui/overlays/toaster.tsx b/src/ui/overlays/toaster.tsx new file mode 100644 index 0000000..5752413 --- /dev/null +++ b/src/ui/overlays/toaster.tsx @@ -0,0 +1,41 @@ +import { + Toaster as ChakraToaster, + Portal, + Spinner, + Stack, + Toast, + createToaster, +} from '@chakra-ui/react' + +export const toaster = createToaster({ + placement: 'bottom-end', + pauseOnPageIdle: true, +}); + +export const ToasterViewport = () => { + return ( + + + {(toast) => ( + + {toast.type === 'loading' ? ( + + ) : ( + + )} + + {toast.title && {toast.title}} + {toast.description && ( + {toast.description} + )} + + {toast.action && ( + {toast.action.label} + )} + {toast.closable && } + + )} + + + ); +}; diff --git a/src/ui/styles/index.css b/src/ui/styles/index.css index be7ca0c..bc81687 100644 --- a/src/ui/styles/index.css +++ b/src/ui/styles/index.css @@ -1,12 +1,8 @@ /* Default global color vars */ @import url('./colors.css'); -/* Default look of `rc-docs` */ -@import url('rc-dock/dist/rc-dock.css'); - /* Default look of BlueprintJS */ @import url('normalize.css'); -@import url('@blueprintjs/core/lib/css/blueprint.css'); @import url('./components.css'); @@ -27,13 +23,6 @@ body { margin: 0; } -input, -select, -textarea { - font-family: inherit; - font-size: 14px; -} - #root { display: flex; width: 100vw; @@ -187,213 +176,4 @@ span.hr { .Pane { overflow: hidden; -} - -/* Buttons */ -button { - background-color: var(--button-primary-background-color); - color: var(--button-primary-color); - border: 1px solid var(--button-primary-border-color); - border-radius: var(--button-primary-border-radius); - box-shadow: var(--button-primary-shadow); - margin: var(--button-primary-margin); -} - -button:hover { - background-color: var(--button-primary-hover-background-color); - box-shadow: var(--button-primary-hover-shadow); - color: var(--button-primary-hover-color); -} - -/* Scrollbars */ -::-webkit-scrollbar { - width: 10px; - height: 10px; - background-color: var(--scrollbar-background-color); -} - -::-webkit-scrollbar-thumb { - background-color: var(--scrollbar-thumb-color); - border-radius: var(--scrollbar-thumb-radius); - border: 2px solid transparent; - background-clip: content-box; -} - -::-webkit-scrollbar-thumb:hover { - background-color: var(--scrollbar-thumb-hover-color); -} - -* { - scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-background-color); - scrollbar-width: thin; -} - -/* Sliders */ -input[type="range"] { - vertical-align: middle; - display: inline-block; - -webkit-appearance: none; - appearance: none; - background: transparent; - margin: var(--slider-bar-margin) 0; -} - -input[type="range"]::-webkit-slider-runnable-track { - height: var(--slider-bar-height); - background: var(--slider-bar-color); - border-radius: var(--slider-bar-radius); - box-shadow: var(--slider-bar-shadow); -} - -input[type="range"]::-webkit-slider-thumb { - transform: scale(1); - transition: transform 0.1s ease; - -webkit-appearance: none; - width: 12px; - height: 12px; - background: var(--slider-knob-color); - border: 1px solid var(--slider-knob-border-color); - border-radius: 50%; - box-shadow: var(--slider-knob-shadow); - margin-top: -4px; - transition: all 0.2s ease; -} - -input[type="range"]:active::-webkit-slider-thumb { - transform: scale(1.2); -} - -input[type="range"]:hover::-webkit-slider-thumb { - background: var(--slider-knob-hover-color); - border-color: var(--slider-knob-hover-border-color); - box-shadow: var(--slider-knob-hover-shadow); -} - -input[type="range"]::-moz-range-track { - height: var(--slider-bar-height); - background: var(--slider-bar-color); - border-radius: var(--slider-bar-radius); -} - -input[type="range"]::-moz-range-thumb { - width: 16px; - height: 16px; - background: var(--slider-knob-color); - border: 1px solid var(--slider-knob-border-color); - border-radius: 50%; - box-shadow: var(--slider-knob-shadow); -} - -/* Radio */ -input[type="radio"] { - vertical-align: middle; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - margin: 2px; - position: relative; - cursor: pointer; - transition: all 0.2s ease; -} - -input[type="radio"]:checked { - background-color: var(--blue-primary); - border-color: var(--blue-primary); -} - -input[type="radio"]:checked::after { - content: ''; - position: absolute; - width: 6px; - height: 6px; - background: var(--background-primary-color); - border-radius: 50%; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -input[type="radio"]:focus { - outline: 2px solid var(--blue-primary); - outline-offset: 1px; -} - -/* Checkbox */ -input[type="checkbox"] { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border: 1px solid var(--border-secondary-color); - border-radius: 3px; - vertical-align: middle; - transition: all 0.2s ease; -} - -input[type="checkbox"]:checked { - background-color: var(--blue-primary); - border-color: var(--blue-primary); - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3e%3cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3e%3c/svg%3e"); -} - -/* Select */ -select { - background-color: var(--select-background-color); - color: var(--select-color); - border: 1px solid var(--select-border-color); - border-radius: var(--select-border-radius); - margin: var(--select-margin); -} - -input[type="text"], -input[type="number"], -select { - padding: 3px 2px; - line-height: 1.1; - vertical-align: middle; - transition: all 0.3s ease; -} - -/* Text Area */ -textarea { - background-color: var(--text-area-background-color); - color: var(--text-area-color); - border: 1px solid var(--text-area-border-color); - border-radius: var(--text-area-border-radius); - margin: var(--text-area-margin); -} - -input[type="text"], -input[type="number"], -input[type="password"], -input[type="email"], -input[type="search"], -input[type="tel"], -input[type="url"], -input[type="date"], -input[type="time"], -input[type="week"], -input[type="month"], -input[type="datetime-local"], -input[type="color"] { - background-color: var(--text-area-background-color); - color: var(--text-area-color); - border: 1px solid var(--text-area-border-color); - border-radius: var(--text-area-border-radius); - margin: var(--text-area-margin); -} - -/* Beat Input */ -.input-beat { - display: flex; - align-items: center; -} - -.input-beat input { - display: block; - width: 100%; - flex: 0.333333; -} +} \ No newline at end of file diff --git a/src/ui/theme/Provider.tsx b/src/ui/theme/Provider.tsx new file mode 100644 index 0000000..f5dfad9 --- /dev/null +++ b/src/ui/theme/Provider.tsx @@ -0,0 +1,15 @@ +import { ChakraProvider } from '@chakra-ui/react'; +import { themeSystem } from './system'; +import React from 'react'; + +type ThemeProviderProps = { + children: React.ReactNode; +}; + +export const ThemeProvider = ({ children }: ThemeProviderProps) => { + return ( + + {children} + + ); +}; diff --git a/src/ui/theme/config.ts b/src/ui/theme/config.ts new file mode 100644 index 0000000..4d0db16 --- /dev/null +++ b/src/ui/theme/config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from '@chakra-ui/react'; + +export const themeConfig = defineConfig({ + // TODO +}); diff --git a/src/ui/theme/system.ts b/src/ui/theme/system.ts new file mode 100644 index 0000000..3c9b58d --- /dev/null +++ b/src/ui/theme/system.ts @@ -0,0 +1,4 @@ +import { defaultConfig, createSystem } from '@chakra-ui/react'; +import { themeConfig } from './config'; + +export const themeSystem = createSystem(defaultConfig, themeConfig); \ No newline at end of file diff --git a/src/utils/chart.ts b/src/utils/chart.ts deleted file mode 100644 index a5f86d5..0000000 --- a/src/utils/chart.ts +++ /dev/null @@ -1,31 +0,0 @@ -import Easings from './easings'; -import ChartKeyframe from '@/Chart/Keyframe'; - -export const getLinePropValue = (time: number, keyframes: ChartKeyframe[], defaultValue = 0) => { - const { length } = keyframes; - let lastKeyframeValue = defaultValue; - - for (let i = 0; i < length; i++) { - const keyframe = keyframes[i]; - if (time === keyframe.time) return keyframe.value; - - const { nextKeyframe } = keyframe; - if (keyframe.time > time) { - if (i === 0) break; - return keyframes[i - 1].value; - } - if (keyframe.time < time) { - if (!nextKeyframe || nextKeyframe.time <= time) { - lastKeyframeValue = keyframe.value; - continue; - } - - const timePercentEnd = Easings[nextKeyframe.easing]( - (time - keyframe.time) / (nextKeyframe.time - keyframe.time) - ); - return keyframe.value * (1 - timePercentEnd) + nextKeyframe.value * timePercentEnd; - } - } - - return lastKeyframeValue; -}; diff --git a/src/utils/class.ts b/src/utils/class.ts deleted file mode 100644 index 0a8a2d6..0000000 --- a/src/utils/class.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import { EventEmitter } from 'eventemitter3'; - -export class UUIDMap extends Map { - push(value: T) { - const key = uuid(); - this.set(key, value); - } -} - -export class ArrayEvented extends Array { - readonly event: EventEmitter = new EventEmitter(); - - constructor(initData?: T[]) { - super(); - if (initData) this.push(...initData); - } -} diff --git a/src/utils/file.ts b/src/utils/file.ts index 89bd319..7c744a1 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -1,8 +1,4 @@ -import decodeAudio from 'audio-decode'; -import SparkMD5 from 'spark-md5'; -import AudioClip from '@/Audio/Clip'; -import { IFile, Nullable } from './types'; -import { ChartExported } from '@/Chart/Chart'; +import { Nullable } from './types'; export const PopupReadFiles = (multiple = false, accept: string | Array = ''): Promise> => new Promise((res) => { const fileDOM = document.createElement('input'); @@ -45,73 +41,3 @@ export const ReadFileAsArrayBuffer = (file: File | Blob): Promise = reader.readAsArrayBuffer(file); }); - -export const ReadFileAsAudioBuffer = (file: File | Blob): Promise => new Promise(async (res, rej) => { - try { - const arrayBuffer = await ReadFileAsArrayBuffer(file); - const audioBuffer = await decodeAudio(arrayBuffer); - res(audioBuffer); - } catch (e) { - rej(e); - } -}); - -export const GetFileMD5 = (file: Blob, chunkSize = 2097152): Promise => new Promise((res, rej) => { - const chunks = Math.ceil(file.size / chunkSize); - const spark = new SparkMD5.ArrayBuffer(); - const reader = new FileReader(); - let currentChunk = 0; - - const loadChunk = () => { - const start = currentChunk * chunkSize; - const end = (start + chunkSize) < file.size ? start + chunkSize : file.size; - reader.readAsArrayBuffer(file.slice(start, end)); - }; - - reader.onload = (e) => { - spark.append((e.target as FileReader).result as ArrayBuffer); - currentChunk++; - - if (currentChunk < chunks) loadChunk(); - else { - res(spark.end()); - spark.destroy(); - } - }; - - reader.onerror = (e) => { - rej(e); - }; - - loadChunk(); -}); - -export const DecodeFile = (file: File): Promise => new Promise((res, rej) => { - (new Promise(() => { - throw new Error('Promise chain!'); - })).catch(async () => { - // Decode as chart file - const fileText = await ReadFileAsText(file); - const chartJson = JSON.parse(fileText) as ChartExported; - res({ - type: 'chart', - data: chartJson, - }); - }).catch(async () => { - // Decode as image - const bitmap = await window.createImageBitmap(file); - res({ - type: 'image', - data: bitmap - }); - }).catch(async () => { - // Decode as audio file - const audioResult = await AudioClip.from(file); - res({ - type: 'audio', - data: audioResult, - }); - }).catch(() => { - rej(`Unsupported file type. File name: ${file.name}`); - }); -}); diff --git a/src/utils/math.ts b/src/utils/math.ts index b94c13d..7335589 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -22,6 +22,8 @@ export const BeatNumberToArray = (beat: number, tempo: number): BeatArray => { return [ beatFloor, beatSub, tempo ]; }; +export const sortByBeat = (a: { beat: BeatArray }, b: { beat: BeatArray }) => BeatArrayToNumber(a.beat) - BeatArrayToNumber(b.beat); + export const GridValue = (value: number, grid: number) => ( Math.round(value / grid) * grid ); @@ -35,3 +37,19 @@ export const parseDoublePrecist = (double: number, precision: number = 0, mode: else if (mode === -1) return Math.floor(double * (10 ** precision)) / (10 ** precision); else return Math.round(double * (10 ** precision)) / (10 ** precision); }; + +export const normalizeBeatArray = (array: BeatArray) => { + const result: BeatArray = [ ...array ]; + + if (result[0] < 0) result[0] = 0; + if (result[1] < 0) result[1] = 0; + if (result[2] < 1) result[2] = 1; + + if (Math.floor(result[1] / result[2]) > 0) { + const extraBar = Math.floor(result[1] / result[2]); + result[0] += extraBar; + result[1] = result[1] - (result[2] * extraBar); + } + + return result; +}; diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts deleted file mode 100644 index 4ccf3fd..0000000 --- a/src/utils/renderer.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { RendererSize } from './types'; - -export const CalculateRendererSize = (width: number, height: number): RendererSize => { - const result: Partial = {}; - - result.widthReal = width; - result.height = height; - - result.widthRealHalf = result.widthReal / 2; - result.heightHalf = result.height / 2; - - // TODO: Ratio settings - result.width = result.height / 9 * 16 >= result.widthReal ? result.widthReal : result.height / 9 * 16; - result.widthHalf = result.width / 2; - result.widthOffset = result.widthReal - result.widthHalf; - - result.widthHalfBorder = result.widthHalf * 1.2; - result.heightHalfBorder = result.heightHalf * 1.2; - - result.widthPercent = result.width * (9 / 160); - - // TODO: Note size settings - result.noteScale = result.width / 8080; - result.noteWidth = result.width * 0.117775; - result.noteSpeed = result.height; - - result.hitParticleScale = result.noteScale * 6; - - result.lineScale = result.width > result.height * 0.75 ? result.height / 18.75 : result.width / 14.0625; - result.heightPercent = result.height / 1080; - - return result as RendererSize; -}; diff --git a/src/utils/types.ts b/src/utils/types.ts index ecb330d..a37d8c8 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,5 +1,3 @@ -import { ChartExported } from '@/Chart/Chart'; -import AudioClip from '@/Audio/Clip'; export type Nullable = T | null; @@ -9,89 +7,3 @@ export type Point = { x: number, y: number, }; - -export type RendererSize = { - width: number, - height: number, - widthHalf: number, - heightHalf: number, - /** The real width of the screen */ - widthReal: number, - /** The real half width of the screen */ - widthRealHalf: number, - /** `widthRealHalf - widthHalf` */ - widthOffset: number, - widthPercent: number, - /** Used for culling notes */ - widthHalfBorder: number, - /** Used for culling notes */ - heightHalfBorder: number, - - noteScale: number, - noteWidth: number, - noteSpeed: number, - - hitParticleScale: number, - - lineScale: number, - heightPercent: number, -}; - -/* ==================== Files ==================== */ -export interface IFileBasic { - type: string, - data: unknown, -} - -export interface IFileChart extends IFileBasic { - type: 'chart', - data: ChartExported, -} - -export interface IFileImage extends IFileBasic { - type: 'image', - data: ImageBitmap, -} - -export interface IFileAudio extends IFileBasic { - type: 'audio', - data: AudioClip, -} - -export type IFile = IFileChart | IFileImage | IFileAudio; - -export type TChartInfo = { - name: string, - artist: string, - designer: string, - level: string, - illustrator: string, - - // Files - chart: string, - audio: string, - image?: string, - extraFiles: string[], -}; - -export type TChartInfoCSV = { - Name: string, - Designer: string, - Level: string, - Illustrator: string, - - Chart: string, - Music: string, - Image: string, -} - -export type TChartInfoTXT = { - Name: string, - Charter: string, - Level: string, - Composer: string, - - Song: string, - Picture: string, - Chart: string, -}; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 0e8e7d3..11f02fe 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,3 +1 @@ /// - -declare const __IS_TAURI__: boolean; diff --git a/tsconfig.app.json b/tsconfig.app.json index 9f5fbce..5051e55 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,7 +1,7 @@ { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2020", + "target": "ES2022", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/tsconfig.node.json b/tsconfig.node.json index 0737667..63a378c 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -21,8 +21,8 @@ "noUncheckedSideEffectImports": true }, "include": [ - "vite.config.ts", - "vite.config.web.ts", - "vite.config.tauri.ts" + "vite/config.ts", + "vite/config.web.ts", + "vite/config.tauri.ts" ] } diff --git a/vite.config.tauri.ts b/vite/config.tauri.ts similarity index 51% rename from vite.config.tauri.ts rename to vite/config.tauri.ts index e5057a3..9dd3495 100644 --- a/vite.config.tauri.ts +++ b/vite/config.tauri.ts @@ -1,9 +1,6 @@ import { defineConfig } from 'vite'; -import BaseConfig from './vite.config'; +import BaseConfig from './config'; export default defineConfig({ ...BaseConfig, - define: { - '__IS_TAURI__': true, - }, }); diff --git a/vite.config.ts b/vite/config.ts similarity index 74% rename from vite.config.ts rename to vite/config.ts index ea26b4f..554f063 100644 --- a/vite.config.ts +++ b/vite/config.ts @@ -5,9 +5,12 @@ import react from '@vitejs/plugin-react-swc'; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + build: { + target: [ 'es2022' ], + }, resolve: { alias: { - '@': resolve(__dirname, './src'), + '@': resolve(__dirname, '../src'), } }, }); diff --git a/vite.config.web.ts b/vite/config.web.ts similarity index 51% rename from vite.config.web.ts rename to vite/config.web.ts index b5f348b..9dd3495 100644 --- a/vite.config.web.ts +++ b/vite/config.web.ts @@ -1,9 +1,6 @@ import { defineConfig } from 'vite'; -import BaseConfig from './vite.config'; +import BaseConfig from './config'; export default defineConfig({ ...BaseConfig, - define: { - '__IS_TAURI__': false, - }, });