mirror of
https://github.com/Jellify-Music/App.git
synced 2026-03-18 11:10:59 -05:00
Merge branch 'main' into ritesh/callsuperConfig
This commit is contained in:
15
README.md
15
README.md
@@ -188,16 +188,15 @@ Install via [Altstore](https://altstore.io) or your favorite sideloading utility
|
||||
### Roadmap
|
||||
|
||||
#### 1.1.0 (Socket To Me Baby) - March '26
|
||||
- Android Auto/CarPlay Support
|
||||
- Websocket Support (Server online status)
|
||||
- Home Screen Updates
|
||||
- Discover Screen Updates
|
||||
- Artist Screen Redesign
|
||||
- Library Redesign
|
||||
- Gapless Playback
|
||||
- WebSocket Support (Server online status)
|
||||
- Library Enhancements
|
||||
- Quick Connect Support
|
||||
- Allow Self-Signed Certificates
|
||||
|
||||
#### 1.2.0 (We Made a Language For Us Two...) - June '26
|
||||
- Android Auto/CarPlay Support
|
||||
- EQ Controls
|
||||
- Collaborative Playlists
|
||||
- App Customization Options
|
||||
- Desktop Support (Experimental)
|
||||
@@ -207,12 +206,10 @@ Install via [Altstore](https://altstore.io) or your favorite sideloading utility
|
||||
- Tablet Support
|
||||
|
||||
#### 2.0.0 - December '26
|
||||
- Gapless Playback
|
||||
- Seerr (formerly Jellyseerr) Integration
|
||||
- JellyJam
|
||||
- EQ Controls
|
||||
|
||||
#### 3.0.0 - TBD
|
||||
#### 3.0.0 - December '27
|
||||
- Watch Support
|
||||
- tvOS (Apple and Android)
|
||||
|
||||
|
||||
204
bun.lock
204
bun.lock
@@ -11,12 +11,12 @@
|
||||
"@react-native-community/netinfo": "11.5.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-native-vector-icons/material-design-icons": "12.4.0",
|
||||
"@react-navigation/bottom-tabs": "7.10.1",
|
||||
"@react-navigation/bottom-tabs": "7.12.0",
|
||||
"@react-navigation/material-top-tabs": "7.4.13",
|
||||
"@react-navigation/native": "7.1.28",
|
||||
"@react-navigation/native-stack": "7.11.0",
|
||||
"@react-navigation/native-stack": "7.12.0",
|
||||
"@sentry/react-native": "7.8.0",
|
||||
"@shopify/flash-list": "2.2.0",
|
||||
"@shopify/flash-list": "2.2.1",
|
||||
"@tamagui/config": "1.144.3",
|
||||
"@tanstack/query-async-storage-persister": "5.90.12",
|
||||
"@tanstack/react-query": "5.90.12",
|
||||
@@ -62,8 +62,8 @@
|
||||
"zustand": "5.0.10",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.6",
|
||||
"@babel/preset-env": "7.28.6",
|
||||
"@babel/core": "7.29.0",
|
||||
"@babel/preset-env": "7.29.0",
|
||||
"@babel/runtime": "7.28.6",
|
||||
"@eslint/eslintrc": "3.3.3",
|
||||
"@eslint/js": "9.39.2",
|
||||
@@ -75,7 +75,7 @@
|
||||
"@react-native/typescript-config": "0.83.1",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/lodash": "^4.17.21",
|
||||
"@types/node": "25.0.9",
|
||||
"@types/node": "25.2.0",
|
||||
"@types/react": "19.2.0",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/react-test-renderer": "19.1.0",
|
||||
@@ -87,7 +87,7 @@
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"globals": "17.0.0",
|
||||
"globals": "17.3.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "30.2.0",
|
||||
"jscodeshift": "^17.3.0",
|
||||
@@ -107,15 +107,15 @@
|
||||
"react-native-nitro-modules",
|
||||
],
|
||||
"packages": {
|
||||
"@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="],
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="],
|
||||
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
|
||||
"@babel/eslint-parser": ["@babel/eslint-parser@7.28.5", "", { "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.11.0", "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, "sha512-fcdRcWahONYo+JRnJg1/AekOacGvKx12Gu0qXJXFi2WBqQA1i7+O5PaxRB7kxE/Op94dExnCiiar6T09pvdHpA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
|
||||
"@babel/generator": ["@babel/generator@7.29.0", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
|
||||
"@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="],
|
||||
|
||||
"@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="],
|
||||
"@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="],
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
|
||||
"@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="],
|
||||
|
||||
"@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA=="],
|
||||
"@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w=="],
|
||||
|
||||
"@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g=="],
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
|
||||
"@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q=="],
|
||||
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA=="],
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw=="],
|
||||
|
||||
"@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A=="],
|
||||
|
||||
@@ -267,11 +267,11 @@
|
||||
|
||||
"@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.28.5", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew=="],
|
||||
"@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.0", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ=="],
|
||||
|
||||
"@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="],
|
||||
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="],
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ=="],
|
||||
|
||||
"@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ=="],
|
||||
|
||||
@@ -303,7 +303,7 @@
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||
|
||||
"@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw=="],
|
||||
"@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog=="],
|
||||
|
||||
"@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg=="],
|
||||
|
||||
@@ -331,7 +331,7 @@
|
||||
|
||||
"@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q=="],
|
||||
|
||||
"@babel/preset-env": ["@babel/preset-env@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.28.6", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.28.6", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw=="],
|
||||
"@babel/preset-env": ["@babel/preset-env@7.29.0", "", { "dependencies": { "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w=="],
|
||||
|
||||
"@babel/preset-flow": ["@babel/preset-flow@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-flow-strip-types": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg=="],
|
||||
|
||||
@@ -345,11 +345,11 @@
|
||||
|
||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
|
||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||
|
||||
"@babel/traverse--for-generate-function-map": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
|
||||
|
||||
@@ -565,7 +565,7 @@
|
||||
|
||||
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.83.1", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-MdmoAbQUTOdicCocm5XAFDJWsswxk7hxa6ALnm6Y88p01HFML0W593hAn6qOt9q6IM1KbAcebtH6oOd4gcQy8w=="],
|
||||
|
||||
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.10.1", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-MirOzKEe/rRwPSE9HMrS4niIo0LyUhewlvd01TpzQ1ipuXjH2wJbzAM9gS/r62zriB6HMHz2OY6oIRduwQJtTw=="],
|
||||
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.12.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-/GtOfVWRligHG0mvX39I1FGdUWeWl0GVF2okEziQSQj0bOTrLIt7y44C3r/aCLkEpTVltCPGM3swqGTH3UfRCw=="],
|
||||
|
||||
"@react-navigation/core": ["@react-navigation/core@7.14.0", "", { "dependencies": { "@react-navigation/routers": "^7.5.3", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g=="],
|
||||
|
||||
@@ -575,7 +575,7 @@
|
||||
|
||||
"@react-navigation/native": ["@react-navigation/native@7.1.28", "", { "dependencies": { "@react-navigation/core": "^7.14.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ=="],
|
||||
|
||||
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.11.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-yNx9Wr4dfpOHpqjf2sGog4eH6KCYwTAEPlUPrKbvWlQbCRm5bglwPmaTXw9hTovX9v3HIa42yo7bXpbYfq4jzg=="],
|
||||
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.12.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-XmNJsPshjkNsahgbxNgGWQUq4s1l6HqH/Fei4QsjBNn/0mTvVrRVZwJ1XrY9YhWYvyiYkAN6/OmarWQaQJ0otQ=="],
|
||||
|
||||
"@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="],
|
||||
|
||||
@@ -619,7 +619,7 @@
|
||||
|
||||
"@sentry/types": ["@sentry/types@10.30.0", "", { "dependencies": { "@sentry/core": "10.30.0" } }, "sha512-tSyzG/JunWjbuQDDwP3DKgt8KP23ZSuNUEudMSv2jCF/956o8ksamPeidCTSVMXoEyTt5tvimWNeNvUFIFq3EA=="],
|
||||
|
||||
"@shopify/flash-list": ["@shopify/flash-list@2.2.0", "", { "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-mL61IofcfBNRZ/qazIf+pghGULkcZUQ7EZNldH1JBbIjtDb25ADSiQrt62ZTnRz0H5+bPFEZUmN9+WChHzX8pw=="],
|
||||
"@shopify/flash-list": ["@shopify/flash-list@2.2.1", "", { "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-Squs4SneVNXl8WoCZ7zuer7SidbjXiSSafF5nAYQTtSzylchl5sc/P1swYY7snxWBZwYH9aHy7kCvuMPjiCh1g=="],
|
||||
|
||||
"@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="],
|
||||
|
||||
@@ -875,7 +875,7 @@
|
||||
|
||||
"@types/lodash": ["@types/lodash@4.17.23", "", {}, "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
||||
"@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="],
|
||||
|
||||
@@ -1025,11 +1025,11 @@
|
||||
|
||||
"babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.14", "", { "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg=="],
|
||||
"babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.15", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="],
|
||||
"babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ=="],
|
||||
|
||||
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="],
|
||||
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.6", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A=="],
|
||||
|
||||
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
|
||||
|
||||
@@ -1139,7 +1139,7 @@
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="],
|
||||
"core-js-compat": ["core-js-compat@3.48.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q=="],
|
||||
|
||||
"cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="],
|
||||
|
||||
@@ -1377,7 +1377,7 @@
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@17.0.0", "", {}, "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw=="],
|
||||
"globals": ["globals@17.3.0", "", {}, "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw=="],
|
||||
|
||||
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
||||
|
||||
@@ -2271,18 +2271,26 @@
|
||||
|
||||
"@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
"@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="],
|
||||
|
||||
"@babel/helper-define-polyfill-provider/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
|
||||
|
||||
"@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
|
||||
"@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/helper-remap-async-to-generator/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
@@ -2293,6 +2301,8 @@
|
||||
|
||||
"@babel/helper-wrap-function/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/helpers/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
@@ -2305,6 +2315,8 @@
|
||||
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ=="],
|
||||
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
|
||||
|
||||
"@babel/plugin-proposal-export-default-from/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-syntax-async-generators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
@@ -2351,6 +2363,8 @@
|
||||
|
||||
"@babel/plugin-transform-block-scoped-functions/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-classes/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
|
||||
|
||||
"@babel/plugin-transform-destructuring/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-destructuring/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
@@ -2379,20 +2393,14 @@
|
||||
|
||||
"@babel/plugin-transform-modules-amd/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
|
||||
|
||||
"@babel/plugin-transform-modules-umd/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-new-target/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
|
||||
|
||||
"@babel/plugin-transform-object-super/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-object-super/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="],
|
||||
@@ -2419,6 +2427,12 @@
|
||||
|
||||
"@babel/plugin-transform-runtime/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.14", "", { "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="],
|
||||
|
||||
"@babel/plugin-transform-shorthand-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/plugin-transform-sticky-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
@@ -2445,6 +2459,12 @@
|
||||
|
||||
"@babel/preset-typescript/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="],
|
||||
|
||||
"@babel/template/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||
|
||||
"@babel/template/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||
|
||||
"@babel/template/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
|
||||
"@babel/traverse--for-generate-function-map/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/traverse--for-generate-function-map/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||
@@ -2563,6 +2583,8 @@
|
||||
|
||||
"@react-native/babel-preset/@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="],
|
||||
|
||||
"@react-native/babel-preset/@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="],
|
||||
|
||||
"@react-native/babel-preset/@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA=="],
|
||||
|
||||
"@react-native/babel-preset/@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw=="],
|
||||
@@ -2649,8 +2671,6 @@
|
||||
|
||||
"babel-plugin-jest-hoist/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs2/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||
|
||||
"babel-plugin-react-compiler/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"babel-preset-current-node-syntax/@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww=="],
|
||||
@@ -2671,6 +2691,8 @@
|
||||
|
||||
"connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"core-js-compat/browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||
|
||||
"error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
|
||||
"eslint/@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
|
||||
@@ -2977,7 +2999,13 @@
|
||||
|
||||
"write-file-atomic/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
@@ -2987,6 +3015,20 @@
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/helper-module-imports/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||
|
||||
"@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
|
||||
|
||||
"@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||
|
||||
"@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||
|
||||
"@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
|
||||
|
||||
"@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||
|
||||
"@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
|
||||
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||
@@ -2997,6 +3039,14 @@
|
||||
|
||||
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||
@@ -3025,6 +3075,22 @@
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
|
||||
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
|
||||
"@babel/plugin-transform-classes/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||
|
||||
"@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
|
||||
|
||||
"@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||
|
||||
"@babel/plugin-transform-classes/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
|
||||
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||
@@ -3051,22 +3117,18 @@
|
||||
|
||||
"@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
||||
|
||||
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
|
||||
|
||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
|
||||
|
||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||
|
||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||
|
||||
"@babel/plugin-transform-object-super/@babel/helper-replace-supers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx/@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
@@ -3075,6 +3137,16 @@
|
||||
|
||||
"@babel/plugin-transform-runtime/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="],
|
||||
|
||||
"@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="],
|
||||
|
||||
"@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
@@ -3211,6 +3283,8 @@
|
||||
|
||||
"@react-native/babel-preset/@babel/plugin-transform-modules-commonjs/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@react-native/babel-preset/@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@react-native/babel-preset/@babel/plugin-transform-nullish-coalescing-operator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@react-native/babel-preset/@babel/plugin-transform-numeric-separator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
@@ -3335,6 +3409,14 @@
|
||||
|
||||
"connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"core-js-compat/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
||||
|
||||
"core-js-compat/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001767", "", {}, "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ=="],
|
||||
|
||||
"core-js-compat/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.283", "", {}, "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w=="],
|
||||
|
||||
"core-js-compat/browserslist/update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||
@@ -3647,8 +3729,6 @@
|
||||
|
||||
"@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
@@ -3687,6 +3767,12 @@
|
||||
|
||||
"@babel/plugin-transform-runtime/@babel/helper-module-imports/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs2/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
|
||||
"@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||
@@ -3955,6 +4041,10 @@
|
||||
|
||||
"slice-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-corejs3/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||
|
||||
"@babel/preset-typescript/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@babel/preset-typescript/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
14
package.json
14
package.json
@@ -45,12 +45,12 @@
|
||||
"@react-native-community/netinfo": "11.5.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-native-vector-icons/material-design-icons": "12.4.0",
|
||||
"@react-navigation/bottom-tabs": "7.10.1",
|
||||
"@react-navigation/bottom-tabs": "7.12.0",
|
||||
"@react-navigation/material-top-tabs": "7.4.13",
|
||||
"@react-navigation/native": "7.1.28",
|
||||
"@react-navigation/native-stack": "7.11.0",
|
||||
"@react-navigation/native-stack": "7.12.0",
|
||||
"@sentry/react-native": "7.8.0",
|
||||
"@shopify/flash-list": "2.2.0",
|
||||
"@shopify/flash-list": "2.2.1",
|
||||
"@tamagui/config": "1.144.3",
|
||||
"@tanstack/query-async-storage-persister": "5.90.12",
|
||||
"@tanstack/react-query": "5.90.12",
|
||||
@@ -96,8 +96,8 @@
|
||||
"zustand": "5.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.6",
|
||||
"@babel/preset-env": "7.28.6",
|
||||
"@babel/core": "7.29.0",
|
||||
"@babel/preset-env": "7.29.0",
|
||||
"@babel/runtime": "7.28.6",
|
||||
"@eslint/eslintrc": "3.3.3",
|
||||
"@eslint/js": "9.39.2",
|
||||
@@ -109,7 +109,7 @@
|
||||
"@react-native/typescript-config": "0.83.1",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/lodash": "^4.17.21",
|
||||
"@types/node": "25.0.9",
|
||||
"@types/node": "25.2.0",
|
||||
"@types/react": "19.2.0",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/react-test-renderer": "19.1.0",
|
||||
@@ -121,7 +121,7 @@
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"globals": "17.0.0",
|
||||
"globals": "17.3.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "30.2.0",
|
||||
"jscodeshift": "^17.3.0",
|
||||
|
||||
@@ -28,16 +28,55 @@ const useAlbums: () => [
|
||||
const user = getUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const isFavorites = useLibraryStore((state) => state.filters.albums.isFavorites)
|
||||
const {
|
||||
filters,
|
||||
sortBy: librarySortByState,
|
||||
sortDescending: librarySortDescendingState,
|
||||
} = useLibraryStore()
|
||||
const rawAlbumSortBy = librarySortByState.albums ?? ItemSortBy.SortName
|
||||
const albumSortByOptions = [
|
||||
ItemSortBy.Name,
|
||||
ItemSortBy.SortName,
|
||||
ItemSortBy.Album,
|
||||
ItemSortBy.Artist,
|
||||
ItemSortBy.PlayCount,
|
||||
ItemSortBy.DateCreated,
|
||||
ItemSortBy.PremiereDate,
|
||||
] as ItemSortBy[]
|
||||
const librarySortBy = albumSortByOptions.includes(rawAlbumSortBy as ItemSortBy)
|
||||
? (rawAlbumSortBy as ItemSortBy)
|
||||
: ItemSortBy.Album
|
||||
const sortDescending = librarySortDescendingState.albums ?? false
|
||||
const isFavorites = filters.albums.isFavorites
|
||||
const yearMin = filters.albums.yearMin
|
||||
const yearMax = filters.albums.yearMax
|
||||
|
||||
const albumPageParams = useRef<Set<string>>(new Set<string>())
|
||||
|
||||
// Memize the expensive albums select function
|
||||
const selectAlbums = (data: InfiniteData<BaseItemDto[], unknown>) =>
|
||||
flattenInfiniteQueryPages(data, albumPageParams)
|
||||
// Add letter sections when sorting by name/album/artist (for A-Z selector)
|
||||
const isSortByLetter =
|
||||
librarySortBy === ItemSortBy.Name ||
|
||||
librarySortBy === ItemSortBy.SortName ||
|
||||
librarySortBy === ItemSortBy.Album ||
|
||||
librarySortBy === ItemSortBy.Artist
|
||||
|
||||
const selectAlbums = (data: InfiniteData<BaseItemDto[], unknown>) => {
|
||||
if (!isSortByLetter) return data.pages.flatMap((page) => page)
|
||||
return flattenInfiniteQueryPages(data, albumPageParams, {
|
||||
sortBy: librarySortBy === ItemSortBy.Artist ? ItemSortBy.Artist : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const albumsInfiniteQuery = useInfiniteQuery({
|
||||
queryKey: [QueryKeys.InfiniteAlbums, isFavorites, library?.musicLibraryId],
|
||||
queryKey: [
|
||||
QueryKeys.InfiniteAlbums,
|
||||
isFavorites,
|
||||
library?.musicLibraryId,
|
||||
librarySortBy,
|
||||
sortDescending,
|
||||
yearMin,
|
||||
yearMax,
|
||||
],
|
||||
queryFn: ({ pageParam }) =>
|
||||
fetchAlbums(
|
||||
api,
|
||||
@@ -45,8 +84,10 @@ const useAlbums: () => [
|
||||
library,
|
||||
pageParam,
|
||||
isFavorites,
|
||||
[ItemSortBy.SortName],
|
||||
[SortOrder.Ascending],
|
||||
[librarySortBy ?? ItemSortBy.SortName],
|
||||
[sortDescending ? SortOrder.Descending : SortOrder.Ascending],
|
||||
yearMin,
|
||||
yearMax,
|
||||
),
|
||||
initialPageParam: 0,
|
||||
select: selectAlbums,
|
||||
|
||||
@@ -9,9 +9,9 @@ import { JellifyLibrary } from '../../../../types/JellifyLibrary'
|
||||
import { Api } from '@jellyfin/sdk'
|
||||
import { fetchItem, fetchItems } from '../../item'
|
||||
import { JellifyUser } from '../../../../types/JellifyUser'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { ApiLimits } from '../../../../configs/query.config'
|
||||
import { nitroFetch } from '../../../utils/nitro'
|
||||
import buildYearsParam from '../../../../utils/mapping/build-years-param'
|
||||
|
||||
export function fetchAlbums(
|
||||
api: Api | undefined,
|
||||
@@ -21,12 +21,16 @@ export function fetchAlbums(
|
||||
isFavorite: boolean | undefined,
|
||||
sortBy: ItemSortBy[] = [ItemSortBy.SortName],
|
||||
sortOrder: SortOrder[] = [SortOrder.Ascending],
|
||||
yearMin?: number,
|
||||
yearMax?: number,
|
||||
): Promise<BaseItemDto[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!api) return reject('No API instance provided')
|
||||
if (!user) return reject('No user provided')
|
||||
if (!library) return reject('Library has not been set')
|
||||
|
||||
const yearsParam = buildYearsParam(yearMin, yearMax)
|
||||
|
||||
nitroFetch<{ Items: BaseItemDto[] }>(api, '/Items', {
|
||||
ParentId: library.musicLibraryId,
|
||||
IncludeItemTypes: [BaseItemKind.MusicAlbum],
|
||||
@@ -38,9 +42,15 @@ export function fetchAlbums(
|
||||
IsFavorite: isFavorite,
|
||||
Fields: [ItemFields.SortName],
|
||||
Recursive: true,
|
||||
}).then((data) => {
|
||||
return data.Items ? resolve(data.Items) : resolve([])
|
||||
Years: yearsParam,
|
||||
})
|
||||
.then((data) => {
|
||||
return data.Items ? resolve(data.Items) : resolve([])
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
return reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -44,17 +44,41 @@ export const useAlbumArtists: () => [
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const { filters, sortDescending } = useLibraryStore()
|
||||
const {
|
||||
filters,
|
||||
sortBy: librarySortByState,
|
||||
sortDescending: librarySortDescendingState,
|
||||
} = useLibraryStore()
|
||||
const rawArtistSortBy = librarySortByState.artists ?? ItemSortBy.SortName
|
||||
// Artists tab only supports sort by name
|
||||
const librarySortBy =
|
||||
rawArtistSortBy === ItemSortBy.SortName || rawArtistSortBy === ItemSortBy.Name
|
||||
? rawArtistSortBy
|
||||
: ItemSortBy.SortName
|
||||
const sortDescending = librarySortDescendingState.artists ?? false
|
||||
const isFavorites = filters.artists.isFavorites
|
||||
|
||||
const artistPageParams = useRef<Set<string>>(new Set<string>())
|
||||
|
||||
// Memoize the expensive artists select function
|
||||
const selectArtists = (data: InfiniteData<BaseItemDto[], unknown>) =>
|
||||
flattenInfiniteQueryPages(data, artistPageParams)
|
||||
const isSortByName =
|
||||
librarySortBy === ItemSortBy.Name ||
|
||||
librarySortBy === ItemSortBy.SortName ||
|
||||
librarySortBy === ItemSortBy.Artist
|
||||
|
||||
// Only add letter sections when sorting by name (for A-Z selector)
|
||||
const selectArtists = (data: InfiniteData<BaseItemDto[], unknown>) => {
|
||||
if (!isSortByName) return data.pages.flatMap((page) => page)
|
||||
return flattenInfiniteQueryPages(data, artistPageParams)
|
||||
}
|
||||
|
||||
const artistsInfiniteQuery = useInfiniteQuery({
|
||||
queryKey: [QueryKeys.InfiniteArtists, isFavorites, sortDescending, library?.musicLibraryId],
|
||||
queryKey: [
|
||||
QueryKeys.InfiniteArtists,
|
||||
isFavorites,
|
||||
sortDescending,
|
||||
library?.musicLibraryId,
|
||||
librarySortBy,
|
||||
],
|
||||
queryFn: ({ pageParam }: { pageParam: number }) =>
|
||||
fetchArtists(
|
||||
api,
|
||||
@@ -62,7 +86,7 @@ export const useAlbumArtists: () => [
|
||||
library,
|
||||
pageParam,
|
||||
isFavorites,
|
||||
[ItemSortBy.SortName],
|
||||
[librarySortBy ?? ItemSortBy.SortName],
|
||||
[sortDescending ? SortOrder.Descending : SortOrder.Ascending],
|
||||
),
|
||||
select: selectArtists,
|
||||
|
||||
@@ -33,11 +33,19 @@ const useTracks: (
|
||||
const api = getApi()
|
||||
const user = getUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
const { filters, sortDescending: isLibrarySortDescending } = useLibraryStore()
|
||||
const {
|
||||
filters,
|
||||
sortBy: librarySortByState,
|
||||
sortDescending: librarySortDescendingState,
|
||||
} = useLibraryStore()
|
||||
const librarySortBy = librarySortByState.tracks ?? undefined
|
||||
const isLibrarySortDescending = librarySortDescendingState.tracks ?? false
|
||||
const isLibraryFavorites = filters.tracks.isFavorites
|
||||
const isDownloaded = filters.tracks.isDownloaded ?? false
|
||||
const isLibraryUnplayed = filters.tracks.isUnplayed ?? false
|
||||
const libraryGenreIds = filters.tracks.genreIds
|
||||
const libraryYearMin = filters.tracks.yearMin
|
||||
const libraryYearMax = filters.tracks.yearMax
|
||||
|
||||
// Use provided values or fallback to library context
|
||||
// If artistId is present, we use isFavoritesParam if provided, otherwise false (default to showing all artist tracks)
|
||||
@@ -50,7 +58,7 @@ const useTracks: (
|
||||
: isLibraryFavorites
|
||||
const isUnplayed =
|
||||
isUnplayedParam !== undefined ? isUnplayedParam : artistId ? undefined : isLibraryUnplayed
|
||||
const finalSortBy = sortBy ?? ItemSortBy.Name
|
||||
const finalSortBy = librarySortBy ?? sortBy ?? ItemSortBy.Name
|
||||
const finalSortOrder =
|
||||
sortOrder ?? (isLibrarySortDescending ? SortOrder.Descending : SortOrder.Ascending)
|
||||
|
||||
@@ -59,11 +67,22 @@ const useTracks: (
|
||||
const trackPageParams = useRef<Set<string>>(new Set<string>())
|
||||
|
||||
const selectTracks = (data: InfiniteData<BaseItemDto[], unknown>) => {
|
||||
if (finalSortBy === ItemSortBy.SortName || finalSortBy === ItemSortBy.Name) {
|
||||
return flattenInfiniteQueryPages(data, trackPageParams)
|
||||
} else {
|
||||
return data.pages.flatMap((page) => page)
|
||||
if (
|
||||
finalSortBy === ItemSortBy.SortName ||
|
||||
finalSortBy === ItemSortBy.Name ||
|
||||
finalSortBy === ItemSortBy.Album ||
|
||||
finalSortBy === ItemSortBy.Artist
|
||||
) {
|
||||
return flattenInfiniteQueryPages(data, trackPageParams, {
|
||||
sortBy:
|
||||
finalSortBy === ItemSortBy.Artist
|
||||
? ItemSortBy.Artist
|
||||
: finalSortBy === ItemSortBy.Album
|
||||
? ItemSortBy.Album
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
return data.pages.flatMap((page) => page)
|
||||
}
|
||||
|
||||
const tracksInfiniteQuery = useInfiniteQuery({
|
||||
@@ -78,6 +97,8 @@ const useTracks: (
|
||||
finalSortBy,
|
||||
finalSortOrder,
|
||||
isDownloaded ? undefined : libraryGenreIds,
|
||||
libraryYearMin,
|
||||
libraryYearMax,
|
||||
),
|
||||
queryFn: ({ pageParam }) => {
|
||||
if (!isDownloaded) {
|
||||
@@ -92,21 +113,33 @@ const useTracks: (
|
||||
finalSortOrder,
|
||||
artistId,
|
||||
libraryGenreIds,
|
||||
libraryYearMin,
|
||||
libraryYearMax,
|
||||
)
|
||||
} else
|
||||
return (downloadedTracks ?? [])
|
||||
.map(({ item }) => item)
|
||||
.sort((a, b) => {
|
||||
const aName = a.Name ?? ''
|
||||
const bName = b.Name ?? ''
|
||||
if (aName < bName) return -1
|
||||
else if (aName === bName) return 0
|
||||
else return 1
|
||||
})
|
||||
.filter((track) => {
|
||||
if (!isFavorites) return true
|
||||
else return isDownloadedTrackAlsoFavorite(user, track)
|
||||
} else {
|
||||
let items = (downloadedTracks ?? []).map(({ item }) => item)
|
||||
if (libraryYearMin != null || libraryYearMax != null) {
|
||||
const min = libraryYearMin ?? 0
|
||||
const max = libraryYearMax ?? new Date().getFullYear()
|
||||
items = items.filter((track) => {
|
||||
const y =
|
||||
'ProductionYear' in track
|
||||
? (track as BaseItemDto).ProductionYear
|
||||
: undefined
|
||||
if (y == null) return false
|
||||
return y >= min && y <= max
|
||||
})
|
||||
}
|
||||
const sortByForCompare =
|
||||
finalSortBy === ItemSortBy.SortName ? ItemSortBy.Name : finalSortBy
|
||||
items = items.sort((a, b) =>
|
||||
compareDownloadedTracks(a, b, sortByForCompare, finalSortOrder),
|
||||
)
|
||||
return items.filter((track) => {
|
||||
if (!isFavorites) return true
|
||||
else return isDownloadedTrackAlsoFavorite(user, track)
|
||||
})
|
||||
}
|
||||
},
|
||||
initialPageParam: 0,
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => {
|
||||
@@ -130,3 +163,45 @@ function isDownloadedTrackAlsoFavorite(user: JellifyUser | undefined, track: Bas
|
||||
|
||||
return userData?.IsFavorite ?? false
|
||||
}
|
||||
|
||||
function getSortValue(item: BaseItemDto, sortBy: ItemSortBy): string | number {
|
||||
switch (sortBy) {
|
||||
case ItemSortBy.Name:
|
||||
case ItemSortBy.SortName:
|
||||
return item.Name ?? item.SortName ?? ''
|
||||
case ItemSortBy.Album:
|
||||
return item.Album ?? ''
|
||||
case ItemSortBy.Artist:
|
||||
return item.AlbumArtist ?? item.Artists?.[0] ?? ''
|
||||
case ItemSortBy.DateCreated:
|
||||
return item.DateCreated ? new Date(item.DateCreated).getTime() : 0
|
||||
case ItemSortBy.PlayCount:
|
||||
return item.UserData?.PlayCount ?? 0
|
||||
case ItemSortBy.PremiereDate:
|
||||
return item.PremiereDate ? new Date(item.PremiereDate).getTime() : 0
|
||||
case ItemSortBy.Runtime:
|
||||
return item.RunTimeTicks ?? 0
|
||||
default:
|
||||
return item.Name ?? item.SortName ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
function compareDownloadedTracks(
|
||||
a: BaseItemDto,
|
||||
b: BaseItemDto,
|
||||
sortBy: ItemSortBy,
|
||||
sortOrder: SortOrder,
|
||||
): number {
|
||||
const aVal = getSortValue(a, sortBy)
|
||||
const bVal = getSortValue(b, sortBy)
|
||||
const isDesc = sortOrder === SortOrder.Descending
|
||||
let cmp: number
|
||||
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
||||
cmp = aVal - bVal
|
||||
} else {
|
||||
const aStr = String(aVal)
|
||||
const bStr = String(bVal)
|
||||
cmp = aStr.localeCompare(bStr, undefined, { sensitivity: 'base' })
|
||||
}
|
||||
return isDesc ? -cmp : cmp
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ export const TracksQueryKey = (
|
||||
sortBy?: string,
|
||||
sortOrder?: string,
|
||||
genreIds?: string[],
|
||||
yearMin?: number,
|
||||
yearMax?: number,
|
||||
) => [
|
||||
TrackQueryKeys.AllTracks,
|
||||
library?.musicLibraryId,
|
||||
@@ -27,4 +29,6 @@ export const TracksQueryKey = (
|
||||
sortBy,
|
||||
sortOrder,
|
||||
genreIds && genreIds.length > 0 ? `genres:${genreIds.sort().join(',')}` : undefined,
|
||||
yearMin,
|
||||
yearMax,
|
||||
]
|
||||
|
||||
@@ -12,6 +12,7 @@ import { nitroFetch } from '../../../utils/nitro'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { ApiLimits } from '../../../../configs/query.config'
|
||||
import { JellifyUser } from '../../../../types/JellifyUser'
|
||||
import buildYearsParam from '../../../../utils/mapping/build-years-param'
|
||||
|
||||
export default function fetchTracks(
|
||||
api: Api | undefined,
|
||||
@@ -24,6 +25,8 @@ export default function fetchTracks(
|
||||
sortOrder: SortOrder = SortOrder.Ascending,
|
||||
artistId?: string,
|
||||
genreIds?: string[],
|
||||
yearMin?: number,
|
||||
yearMax?: number,
|
||||
) {
|
||||
return new Promise<BaseItemDto[]>((resolve, reject) => {
|
||||
if (isUndefined(api)) return reject('Client instance not set')
|
||||
@@ -43,6 +46,8 @@ export default function fetchTracks(
|
||||
filters.push(ItemFilter.IsUnplayed)
|
||||
}
|
||||
|
||||
const yearsParam = buildYearsParam(yearMin, yearMax)
|
||||
|
||||
nitroFetch<{ Items: BaseItemDto[] }>(api, '/Items', {
|
||||
IncludeItemTypes: [BaseItemKind.Audio],
|
||||
ParentId: library.musicLibraryId,
|
||||
@@ -56,6 +61,7 @@ export default function fetchTracks(
|
||||
Fields: [ItemFields.SortName],
|
||||
ArtistIds: artistId ? [artistId] : undefined,
|
||||
GenreIds: genreIds && genreIds.length > 0 ? genreIds : undefined,
|
||||
Years: yearsParam,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.Items) return resolve(data.Items)
|
||||
|
||||
27
src/api/queries/years/index.ts
Normal file
27
src/api/queries/years/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchLibraryYears } from './utils'
|
||||
import { LibraryYearsQueryKey } from './keys'
|
||||
import { getApi, getUser, useJellifyLibrary } from '../../../stores'
|
||||
|
||||
export function useLibraryYears(): {
|
||||
years: number[]
|
||||
isPending: boolean
|
||||
isError: boolean
|
||||
} {
|
||||
const api = getApi()
|
||||
const user = getUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const {
|
||||
data: years = [],
|
||||
isPending,
|
||||
isError,
|
||||
} = useQuery({
|
||||
queryKey: LibraryYearsQueryKey(library?.musicLibraryId, user?.id),
|
||||
queryFn: () => fetchLibraryYears(api, library, user?.id),
|
||||
enabled: Boolean(api && library && user?.id),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
|
||||
return { years, isPending, isError }
|
||||
}
|
||||
5
src/api/queries/years/keys.ts
Normal file
5
src/api/queries/years/keys.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const LibraryYearsQueryKey = (libraryId: string | undefined, userId: string | undefined) => [
|
||||
'LibraryYears',
|
||||
libraryId,
|
||||
userId,
|
||||
]
|
||||
36
src/api/queries/years/utils/index.ts
Normal file
36
src/api/queries/years/utils/index.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Api } from '@jellyfin/sdk'
|
||||
import { JellifyLibrary } from '../../../../types/JellifyLibrary'
|
||||
import { nitroFetch } from '../../../utils/nitro'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
|
||||
export type ItemsFiltersResponse = {
|
||||
Genres?: string[] | null
|
||||
Years?: number[] | null
|
||||
Tags?: string[] | null
|
||||
OfficialRatings?: string[] | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches available filter values (genres, years) for the music library via /Items/Filters.
|
||||
* Uses MusicAlbum so years reflect album release dates in the library.
|
||||
* Returns sorted ascending list of year numbers.
|
||||
*/
|
||||
export async function fetchLibraryYears(
|
||||
api: Api | undefined,
|
||||
library: JellifyLibrary | undefined,
|
||||
userId: string | undefined,
|
||||
): Promise<number[]> {
|
||||
if (isUndefined(api)) throw new Error('Client instance not set')
|
||||
if (isUndefined(library)) throw new Error('Library instance not set')
|
||||
if (isUndefined(userId)) throw new Error('User id required')
|
||||
|
||||
const data = await nitroFetch<ItemsFiltersResponse>(api, '/Items/Filters', {
|
||||
UserId: userId,
|
||||
ParentId: library.musicLibraryId,
|
||||
IncludeItemTypes: [BaseItemKind.MusicAlbum],
|
||||
})
|
||||
|
||||
const years = data?.Years ?? []
|
||||
return [...years].filter((y) => typeof y === 'number' && !Number.isNaN(y)).sort((a, b) => a - b)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Text } from '../Global/helpers/text'
|
||||
import { FlashList, FlashListRef } from '@shopify/flash-list'
|
||||
import { UseInfiniteQueryResult } from '@tanstack/react-query'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import LibraryStackParamList from '../../screens/Library/types'
|
||||
@@ -19,6 +20,8 @@ import MAX_ITEMS_IN_RECYCLE_POOL from '../../configs/library.config'
|
||||
interface AlbumsProps {
|
||||
albumsInfiniteQuery: UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error>
|
||||
showAlphabeticalSelector: boolean
|
||||
sortBy?: ItemSortBy
|
||||
sortDescending?: boolean
|
||||
albumPageParams?: RefObject<Set<string>>
|
||||
}
|
||||
|
||||
@@ -26,6 +29,8 @@ export default function Albums({
|
||||
albumsInfiniteQuery,
|
||||
albumPageParams,
|
||||
showAlphabeticalSelector,
|
||||
sortBy,
|
||||
sortDescending,
|
||||
}: AlbumsProps): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
|
||||
@@ -40,11 +45,11 @@ export default function Albums({
|
||||
const pendingLetterRef = useRef<string | null>(null)
|
||||
|
||||
const stickyHeaderIndices =
|
||||
!showAlphabeticalSelector || !albumsInfiniteQuery.data
|
||||
!showAlphabeticalSelector || !albumsInfiniteQuery.data || sortBy === ItemSortBy.Artist
|
||||
? []
|
||||
: albumsInfiniteQuery.data
|
||||
.map((album, index) => (typeof album === 'string' ? index : 0))
|
||||
.filter((value, index, indices) => indices.indexOf(value) === index)
|
||||
.map((album, index) => (typeof album === 'string' ? index : null))
|
||||
.filter((v): v is number => v !== null)
|
||||
|
||||
const { mutateAsync: alphabetSelectorMutate, isPending: isAlphabetSelectorPending } =
|
||||
useAlphabetSelector((letter) => (pendingLetterRef.current = letter.toUpperCase()))
|
||||
@@ -68,9 +73,15 @@ export default function Albums({
|
||||
item: BaseItemDto | string | number
|
||||
}) =>
|
||||
typeof album === 'string' ? (
|
||||
<FlashListStickyHeader text={album.toUpperCase()} />
|
||||
sortBy === ItemSortBy.Artist ? null : (
|
||||
<FlashListStickyHeader text={album.toUpperCase()} />
|
||||
)
|
||||
) : typeof album === 'number' ? null : typeof album === 'object' ? (
|
||||
<ItemRow item={album} navigation={navigation} />
|
||||
<ItemRow
|
||||
item={album}
|
||||
navigation={navigation}
|
||||
sortingByReleasedDate={sortBy === ItemSortBy.PremiereDate}
|
||||
/>
|
||||
) : null
|
||||
|
||||
const onEndReached = () => {
|
||||
@@ -143,6 +154,7 @@ export default function Albums({
|
||||
|
||||
{showAlphabeticalSelector && albumPageParams && (
|
||||
<AZScroller
|
||||
reverseOrder={sortDescending}
|
||||
onLetterSelect={(letter) =>
|
||||
alphabetSelectorMutate({
|
||||
letter,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { RefObject, useEffect, useRef } from 'react'
|
||||
import React, { RefObject, useEffect, useRef, useState } from 'react'
|
||||
import { Separator, useTheme, XStack, YStack } from 'tamagui'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
@@ -22,6 +22,7 @@ export interface ArtistsProps {
|
||||
Error
|
||||
>
|
||||
showAlphabeticalSelector: boolean
|
||||
sortDescending?: boolean
|
||||
artistPageParams?: RefObject<Set<string>>
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ export interface ArtistsProps {
|
||||
export default function Artists({
|
||||
artistsInfiniteQuery,
|
||||
showAlphabeticalSelector,
|
||||
sortDescending,
|
||||
artistPageParams,
|
||||
}: ArtistsProps): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
@@ -158,6 +160,7 @@ export default function Artists({
|
||||
|
||||
{showAlphabeticalSelector && artistPageParams && (
|
||||
<AZScroller
|
||||
reverseOrder={sortDescending}
|
||||
onLetterSelect={(letter) =>
|
||||
alphabetSelectorMutate({
|
||||
letter,
|
||||
|
||||
@@ -8,7 +8,6 @@ import { FiltersProps } from './types'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
|
||||
export default function Filters({
|
||||
currentTab,
|
||||
@@ -27,6 +26,11 @@ export default function Filters({
|
||||
const isUnplayed = currentFilters.isUnplayed ?? false
|
||||
const selectedGenreIds = currentFilters.genreIds ?? []
|
||||
const hasGenresSelected = selectedGenreIds.length > 0
|
||||
const yearMin = currentFilters.yearMin
|
||||
const yearMax = currentFilters.yearMax
|
||||
const hasYearRange = yearMin != null || yearMax != null
|
||||
const yearRangeLabel =
|
||||
yearMin != null || yearMax != null ? `${yearMin ?? '…'} – ${yearMax ?? '…'}` : null
|
||||
|
||||
const handleFavoritesToggle = (checked: boolean | 'indeterminate') => {
|
||||
triggerHaptic('impactLight')
|
||||
@@ -60,6 +64,13 @@ export default function Filters({
|
||||
navigation?.navigate('GenreSelection')
|
||||
}
|
||||
|
||||
const handleYearRangeSelect = () => {
|
||||
triggerHaptic('impactLight')
|
||||
navigation?.navigate('YearSelection', {
|
||||
tab: currentTab === 'Tracks' || currentTab === 'Albums' ? currentTab : 'Tracks',
|
||||
})
|
||||
}
|
||||
|
||||
const handleUnplayedToggle = (checked: boolean | 'indeterminate') => {
|
||||
triggerHaptic('impactLight')
|
||||
if (currentTab === 'Tracks') {
|
||||
@@ -144,6 +155,37 @@ export default function Filters({
|
||||
</Button>
|
||||
</XStack>
|
||||
)}
|
||||
|
||||
{(isTracksTab || currentTab === 'Albums') && (
|
||||
<XStack alignItems='center' justifyContent='space-between' marginTop='$4'>
|
||||
<Button
|
||||
variant='outlined'
|
||||
size='$4'
|
||||
onPress={handleYearRangeSelect}
|
||||
pressStyle={{ opacity: 0.6 }}
|
||||
animation='quick'
|
||||
flex={1}
|
||||
justifyContent='space-between'
|
||||
disabled={isTracksTab && isDownloaded}
|
||||
>
|
||||
<Text
|
||||
color={
|
||||
isTracksTab && isDownloaded
|
||||
? '$borderColor'
|
||||
: hasYearRange
|
||||
? '$primary'
|
||||
: '$neutral'
|
||||
}
|
||||
>
|
||||
{hasYearRange ? `Year range ${yearRangeLabel}` : 'Year range'}
|
||||
</Text>
|
||||
<Icon
|
||||
name={hasYearRange ? 'filter-variant' : 'filter'}
|
||||
color={hasYearRange ? '$primary' : '$borderColor'}
|
||||
/>
|
||||
</Button>
|
||||
</XStack>
|
||||
)}
|
||||
</YStack>
|
||||
</YStack>
|
||||
)
|
||||
|
||||
@@ -142,15 +142,26 @@ export default function TrackRowContent({
|
||||
|
||||
<SlidingTextArea leftGapWidth={artworkAreaWidth} hasArtwork={!!showArtwork}>
|
||||
<YStack alignItems='flex-start' justifyContent='center'>
|
||||
<Text
|
||||
key={`${track.Id}-name`}
|
||||
bold
|
||||
color={textColor}
|
||||
lineBreakStrategyIOS='standard'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{trackName}
|
||||
</Text>
|
||||
<XStack alignItems='center'>
|
||||
<Text
|
||||
key={`${track.Id}-name`}
|
||||
bold
|
||||
color={textColor}
|
||||
lineBreakStrategyIOS='standard'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{trackName}
|
||||
</Text>
|
||||
{!shouldShowArtists && isExplicit(track as JellifyTrack) && (
|
||||
<XStack alignSelf='center' paddingLeft='$2'>
|
||||
<Icon
|
||||
name='alpha-e-box-outline'
|
||||
color={'$borderColor'}
|
||||
xxsmall
|
||||
/>
|
||||
</XStack>
|
||||
)}
|
||||
</XStack>
|
||||
|
||||
{shouldShowArtists && (
|
||||
<XStack alignItems='center'>
|
||||
|
||||
@@ -35,6 +35,9 @@ export interface TrackProps {
|
||||
invertedColors?: boolean | undefined
|
||||
testID?: string | undefined
|
||||
editing?: boolean | undefined
|
||||
sortingByAlbum?: boolean | undefined
|
||||
sortingByReleasedDate?: boolean | undefined
|
||||
sortingByPlayCount?: boolean | undefined
|
||||
}
|
||||
|
||||
export default function Track({
|
||||
@@ -50,6 +53,9 @@ export default function Track({
|
||||
isNested,
|
||||
invertedColors,
|
||||
editing,
|
||||
sortingByAlbum,
|
||||
sortingByReleasedDate,
|
||||
sortingByPlayCount,
|
||||
}: TrackProps): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
const [artworkAreaWidth, setArtworkAreaWidth] = useState(0)
|
||||
@@ -132,7 +138,14 @@ export default function Track({
|
||||
: undefined
|
||||
|
||||
// Memoize artists text
|
||||
const artistsText = track.Artists?.join(' • ') ?? ''
|
||||
const artistsText =
|
||||
(sortingByAlbum
|
||||
? track.Album
|
||||
: sortingByReleasedDate
|
||||
? `${track.ProductionYear?.toString()} • ${track.Artists?.join(' • ')}`
|
||||
: sortingByPlayCount
|
||||
? `${track.UserData?.PlayCount?.toString()} • ${track.Artists?.join(' • ')}`
|
||||
: track.Artists?.join(' • ')) ?? ''
|
||||
|
||||
// Memoize track name
|
||||
const trackName = track.Name ?? 'Untitled Track'
|
||||
|
||||
@@ -9,7 +9,8 @@ import { UseInfiniteQueryResult, useMutation } from '@tanstack/react-query'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { triggerHaptic } from '../../../hooks/use-haptic-feedback'
|
||||
|
||||
const alphabet = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
|
||||
const alphabetAtoZ = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
|
||||
const alphabetZtoA = '#ZYXWVUTSRQPONMLKJIHGFEDCBA'.split('')
|
||||
/**
|
||||
* A component that displays a list of hardcoded alphabet letters and a selected letter overlay
|
||||
* When a letter is selected, the overlay will be shown and the callback function will be called
|
||||
@@ -18,16 +19,19 @@ const alphabet = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
|
||||
* The overlay will be hidden after 200ms
|
||||
*
|
||||
* @param onLetterSelect - Callback function to be called when a letter is selected
|
||||
* @param reverseOrder - When true, display #, Z-A (for descending sort) instead of #, A-Z
|
||||
* @returns A component that displays a list of letters and a selected letter overlay
|
||||
*/
|
||||
export default function AZScroller({
|
||||
onLetterSelect,
|
||||
alphabet: customAlphabet,
|
||||
reverseOrder,
|
||||
}: {
|
||||
onLetterSelect: (letter: string) => Promise<void>
|
||||
alphabet?: string[]
|
||||
reverseOrder?: boolean
|
||||
}) {
|
||||
const alphabetToUse = customAlphabet ?? alphabet
|
||||
const alphabetToUse = customAlphabet ?? (reverseOrder ? alphabetZtoA : alphabetAtoZ)
|
||||
const theme = useTheme()
|
||||
|
||||
const [operationPending, setOperationPending] = useState<boolean>(false)
|
||||
|
||||
@@ -38,6 +38,7 @@ interface ItemRowProps {
|
||||
onLongPress?: () => void
|
||||
navigation?: Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
|
||||
queueName?: Queue
|
||||
sortingByReleasedDate?: boolean | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +59,7 @@ function ItemRow({
|
||||
onPress,
|
||||
onLongPress,
|
||||
queueName,
|
||||
sortingByReleasedDate,
|
||||
}: ItemRowProps): React.JSX.Element {
|
||||
const artworkAreaWidth = useSharedValue(0)
|
||||
|
||||
@@ -170,7 +172,7 @@ function ItemRow({
|
||||
>
|
||||
<HideableArtwork item={item} circular={circular} onLayout={handleArtworkLayout} />
|
||||
<SlidingTextArea leftGapWidth={artworkAreaWidth}>
|
||||
<ItemRowDetails item={item} />
|
||||
<ItemRowDetails item={item} sortingByReleasedDate={sortingByReleasedDate} />
|
||||
</SlidingTextArea>
|
||||
|
||||
<XStack justifyContent='flex-end' alignItems='center' flexShrink={1}>
|
||||
@@ -235,7 +237,13 @@ function ItemRow({
|
||||
)
|
||||
}
|
||||
|
||||
function ItemRowDetails({ item }: { item: BaseItemDto }): React.JSX.Element {
|
||||
function ItemRowDetails({
|
||||
item,
|
||||
sortingByReleasedDate,
|
||||
}: {
|
||||
item: BaseItemDto
|
||||
sortingByReleasedDate?: boolean | undefined
|
||||
}): React.JSX.Element {
|
||||
const route = useRoute<RouteProp<BaseStackParamList>>()
|
||||
|
||||
const shouldRenderArtistName =
|
||||
@@ -253,7 +261,10 @@ function ItemRowDetails({ item }: { item: BaseItemDto }): React.JSX.Element {
|
||||
|
||||
{shouldRenderArtistName && (
|
||||
<Text color={'$borderColor'} lineBreakStrategyIOS='standard' numberOfLines={1}>
|
||||
{formatArtistName(item.AlbumArtist)}
|
||||
{formatArtistName(
|
||||
item.AlbumArtist,
|
||||
sortingByReleasedDate ? item.ProductionYear?.toString() : undefined,
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,13 +1,36 @@
|
||||
import useAlbums from '../../../api/queries/album'
|
||||
import Albums from '../../Albums/component'
|
||||
import useLibraryStore from '../../../stores/library'
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'
|
||||
|
||||
function AlbumsTab(): React.JSX.Element {
|
||||
const [albumPageParams, albumsInfiniteQuery] = useAlbums()
|
||||
|
||||
const sortBy = useLibraryStore((state) => {
|
||||
const sb = state.sortBy as Record<string, string> | string
|
||||
if (typeof sb === 'string') return sb
|
||||
return sb?.albums ?? ItemSortBy.Album
|
||||
})
|
||||
const sortDescending = useLibraryStore((state) => {
|
||||
const sd = state.sortDescending as Record<string, boolean> | boolean
|
||||
if (typeof sd === 'boolean') return sd
|
||||
return sd?.albums ?? false
|
||||
})
|
||||
const hasLetterSections =
|
||||
albumsInfiniteQuery.data?.some((item) => typeof item === 'string') ?? false
|
||||
const showAlphabeticalSelector =
|
||||
hasLetterSections ||
|
||||
sortBy === ItemSortBy.Name ||
|
||||
sortBy === ItemSortBy.SortName ||
|
||||
sortBy === ItemSortBy.Album ||
|
||||
sortBy === ItemSortBy.Artist
|
||||
|
||||
return (
|
||||
<Albums
|
||||
albumsInfiniteQuery={albumsInfiniteQuery}
|
||||
showAlphabeticalSelector={true}
|
||||
showAlphabeticalSelector={showAlphabeticalSelector}
|
||||
sortBy={sortBy as ItemSortBy}
|
||||
sortDescending={sortDescending}
|
||||
albumPageParams={albumPageParams}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
import { useAlbumArtists } from '../../../api/queries/artist'
|
||||
import Artists from '../../Artists/component'
|
||||
import useLibraryStore from '../../../stores/library'
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'
|
||||
|
||||
function ArtistsTab(): React.JSX.Element {
|
||||
const [artistPageParams, artistsInfiniteQuery] = useAlbumArtists()
|
||||
|
||||
const sortBy = useLibraryStore((state) => {
|
||||
const sb = state.sortBy as Record<string, string> | string
|
||||
if (typeof sb === 'string') return sb
|
||||
return sb?.artists ?? ItemSortBy.SortName
|
||||
})
|
||||
const sortDescending = useLibraryStore((state) => {
|
||||
const sd = state.sortDescending as Record<string, boolean> | boolean
|
||||
if (typeof sd === 'boolean') return sd
|
||||
return sd?.artists ?? false
|
||||
})
|
||||
const hasLetterSections =
|
||||
artistsInfiniteQuery.data?.some((item) => typeof item === 'string') ?? false
|
||||
// Artists tab only sorts by name, so always show A-Z when we have letter sections
|
||||
const showAlphabeticalSelector =
|
||||
hasLetterSections ||
|
||||
sortBy === ItemSortBy.Name ||
|
||||
sortBy === ItemSortBy.SortName ||
|
||||
sortBy === ItemSortBy.Artist
|
||||
|
||||
return (
|
||||
<Artists
|
||||
artistsInfiniteQuery={artistsInfiniteQuery}
|
||||
showAlphabeticalSelector={true}
|
||||
showAlphabeticalSelector={showAlphabeticalSelector}
|
||||
sortDescending={sortDescending}
|
||||
artistPageParams={artistPageParams}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -6,12 +6,32 @@ import LibraryStackParamList from '@/src/screens/Library/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import useTracks from '../../../api/queries/track'
|
||||
import useLibraryStore from '../../../stores/library'
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'
|
||||
|
||||
function TracksTab(): React.JSX.Element {
|
||||
const [trackPageParams, tracksInfiniteQuery] = useTracks()
|
||||
|
||||
const { filters } = useLibraryStore()
|
||||
const filters = useLibraryStore((state) => state.filters)
|
||||
const sortBy = useLibraryStore((state) => {
|
||||
const sb = state.sortBy as Record<string, string> | string
|
||||
if (typeof sb === 'string') return sb
|
||||
return sb?.tracks ?? ItemSortBy.Name
|
||||
})
|
||||
const sortDescending = useLibraryStore((state) => {
|
||||
const sd = state.sortDescending as Record<string, boolean> | boolean
|
||||
if (typeof sd === 'boolean') return sd
|
||||
return sd?.tracks ?? false
|
||||
})
|
||||
const { isFavorites, isDownloaded } = filters.tracks
|
||||
// Show A-Z when sort is by name OR when data already has letter sections (e.g. after sort change)
|
||||
const hasLetterSections =
|
||||
tracksInfiniteQuery.data?.some((item) => typeof item === 'string') ?? false
|
||||
const showAlphabeticalSelector =
|
||||
hasLetterSections ||
|
||||
sortBy === ItemSortBy.Name ||
|
||||
sortBy === ItemSortBy.SortName ||
|
||||
sortBy === ItemSortBy.Album ||
|
||||
sortBy === ItemSortBy.Artist
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<LibraryStackParamList>>()
|
||||
|
||||
@@ -20,7 +40,9 @@ function TracksTab(): React.JSX.Element {
|
||||
navigation={navigation}
|
||||
tracksInfiniteQuery={tracksInfiniteQuery}
|
||||
queue={isFavorites ? 'Favorite Tracks' : isDownloaded ? 'Downloaded Tracks' : 'Library'}
|
||||
showAlphabeticalSelector={true}
|
||||
showAlphabeticalSelector={showAlphabeticalSelector}
|
||||
sortBy={sortBy as ItemSortBy}
|
||||
sortDescending={sortDescending}
|
||||
trackPageParams={trackPageParams}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -32,7 +32,9 @@ function LibraryTabBar(props: MaterialTopTabBarProps) {
|
||||
(currentFilters.isFavorites === true ||
|
||||
currentFilters.isDownloaded === true ||
|
||||
currentFilters.isUnplayed === true ||
|
||||
(currentFilters.genreIds && currentFilters.genreIds.length > 0))
|
||||
(currentFilters.genreIds && currentFilters.genreIds.length > 0) ||
|
||||
currentFilters.yearMin != null ||
|
||||
currentFilters.yearMax != null)
|
||||
|
||||
const handleShufflePress = async () => {
|
||||
triggerHaptic('impactLight')
|
||||
@@ -99,29 +101,56 @@ function LibraryTabBar(props: MaterialTopTabBarProps) {
|
||||
)}
|
||||
|
||||
{props.state.routes[props.state.index].name !== 'Playlists' && (
|
||||
<XStack
|
||||
onPress={() => {
|
||||
triggerHaptic('impactLight')
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('Filters', {
|
||||
currentTab: currentTab as 'Tracks' | 'Albums' | 'Artists',
|
||||
})
|
||||
}
|
||||
}}
|
||||
pressStyle={{ opacity: 0.6 }}
|
||||
animation='quick'
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
<Icon
|
||||
name={hasActiveFilters ? 'filter-variant' : 'filter'}
|
||||
color={hasActiveFilters ? '$primary' : '$borderColor'}
|
||||
/>
|
||||
<>
|
||||
<XStack
|
||||
onPress={() => {
|
||||
triggerHaptic('impactLight')
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('SortOptions', {
|
||||
currentTab: currentTab as
|
||||
| 'Tracks'
|
||||
| 'Albums'
|
||||
| 'Artists',
|
||||
})
|
||||
}
|
||||
}}
|
||||
pressStyle={{ opacity: 0.6 }}
|
||||
animation='quick'
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
<Icon name={'sort'} color={'$borderColor'} />
|
||||
|
||||
<Text color={hasActiveFilters ? '$primary' : '$borderColor'}>
|
||||
Filter
|
||||
</Text>
|
||||
</XStack>
|
||||
<Text color={'$borderColor'}>Sort</Text>
|
||||
</XStack>
|
||||
|
||||
<XStack
|
||||
onPress={() => {
|
||||
triggerHaptic('impactLight')
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('Filters', {
|
||||
currentTab: currentTab as
|
||||
| 'Tracks'
|
||||
| 'Albums'
|
||||
| 'Artists',
|
||||
})
|
||||
}
|
||||
}}
|
||||
pressStyle={{ opacity: 0.6 }}
|
||||
animation='quick'
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
<Icon
|
||||
name={hasActiveFilters ? 'filter-variant' : 'filter'}
|
||||
color={hasActiveFilters ? '$primary' : '$borderColor'}
|
||||
/>
|
||||
|
||||
<Text color={hasActiveFilters ? '$primary' : '$borderColor'}>
|
||||
Filter
|
||||
</Text>
|
||||
</XStack>
|
||||
</>
|
||||
)}
|
||||
|
||||
{props.state.routes[props.state.index].name !== 'Playlists' &&
|
||||
@@ -136,11 +165,15 @@ function LibraryTabBar(props: MaterialTopTabBarProps) {
|
||||
isDownloaded: false,
|
||||
isUnplayed: false,
|
||||
genreIds: undefined,
|
||||
yearMin: undefined,
|
||||
yearMax: undefined,
|
||||
})
|
||||
} else if (currentTab === 'Albums') {
|
||||
useLibraryStore
|
||||
.getState()
|
||||
.setAlbumsFilters({ isFavorites: undefined })
|
||||
useLibraryStore.getState().setAlbumsFilters({
|
||||
isFavorites: undefined,
|
||||
yearMin: undefined,
|
||||
yearMax: undefined,
|
||||
})
|
||||
} else if (currentTab === 'Artists') {
|
||||
useLibraryStore
|
||||
.getState()
|
||||
|
||||
136
src/components/SortOptions/index.tsx
Normal file
136
src/components/SortOptions/index.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { YStack } from 'tamagui'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import { RadioGroup } from 'tamagui'
|
||||
import { RadioGroupItemWithLabel } from '../Global/helpers/radio-group-item-with-label'
|
||||
import useLibraryStore, { LibraryTab } from '../../stores/library'
|
||||
import { triggerHaptic } from '../../hooks/use-haptic-feedback'
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'
|
||||
|
||||
const TRACK_SORT_OPTIONS: { value: ItemSortBy; label: string }[] = [
|
||||
{ value: ItemSortBy.Name, label: 'Track' },
|
||||
{ value: ItemSortBy.Album, label: 'Album' },
|
||||
{ value: ItemSortBy.Artist, label: 'Artist' },
|
||||
{ value: ItemSortBy.DateCreated, label: 'Date Added' },
|
||||
{ value: ItemSortBy.PlayCount, label: 'Play Count' },
|
||||
{ value: ItemSortBy.PremiereDate, label: 'Release Date' },
|
||||
{ value: ItemSortBy.Runtime, label: 'Runtime' },
|
||||
]
|
||||
|
||||
const ALBUM_SORT_OPTIONS: { value: ItemSortBy; label: string }[] = [
|
||||
{ value: ItemSortBy.SortName, label: 'Album' },
|
||||
{ value: ItemSortBy.Artist, label: 'Artist' },
|
||||
{ value: ItemSortBy.DateCreated, label: 'Date Added' },
|
||||
{ value: ItemSortBy.PremiereDate, label: 'Release Date' },
|
||||
]
|
||||
|
||||
const ARTIST_SORT_OPTIONS: { value: ItemSortBy; label: string }[] = [
|
||||
{ value: ItemSortBy.SortName, label: 'Artist' },
|
||||
]
|
||||
|
||||
function toLibraryTab(tab: string | undefined): LibraryTab {
|
||||
const lower = tab?.toLowerCase()
|
||||
return lower === 'albums' || lower === 'artists' ? lower : 'tracks'
|
||||
}
|
||||
|
||||
function getSortByOptionsForTab(tab: LibraryTab): { value: ItemSortBy; label: string }[] {
|
||||
switch (tab) {
|
||||
case 'albums':
|
||||
return ALBUM_SORT_OPTIONS
|
||||
case 'artists':
|
||||
return ARTIST_SORT_OPTIONS
|
||||
default:
|
||||
return TRACK_SORT_OPTIONS
|
||||
}
|
||||
}
|
||||
|
||||
const DATE_SORT_BY: ItemSortBy[] = [ItemSortBy.DateCreated, ItemSortBy.PremiereDate]
|
||||
const NUMERIC_SORT_BY: ItemSortBy[] = [ItemSortBy.PlayCount, ItemSortBy.Runtime]
|
||||
|
||||
function getSortOrderLabels(sortBy: ItemSortBy): { ascending: string; descending: string } {
|
||||
if (DATE_SORT_BY.includes(sortBy)) {
|
||||
return { ascending: 'Oldest', descending: 'Newest' }
|
||||
}
|
||||
if (NUMERIC_SORT_BY.includes(sortBy)) {
|
||||
return { ascending: 'Lowest', descending: 'Highest' }
|
||||
}
|
||||
return { ascending: 'Ascending', descending: 'Descending' }
|
||||
}
|
||||
|
||||
export default function SortOptions({
|
||||
currentTab,
|
||||
}: {
|
||||
currentTab?: 'Tracks' | 'Albums' | 'Artists'
|
||||
}): React.JSX.Element {
|
||||
const tab = toLibraryTab(currentTab)
|
||||
const { getSortBy, getSortDescending, setSortBy, setSortDescending } = useLibraryStore()
|
||||
const sortByOptions = getSortByOptionsForTab(tab)
|
||||
const currentSortBy = getSortBy(tab)
|
||||
const effectiveSortBy = sortByOptions.some((o) => o.value === currentSortBy)
|
||||
? currentSortBy
|
||||
: sortByOptions[0]!.value
|
||||
const sortDescending = getSortDescending(tab)
|
||||
const sortOrderLabels = getSortOrderLabels(effectiveSortBy)
|
||||
|
||||
const handleSortByChange = (value: string) => {
|
||||
triggerHaptic('impactLight')
|
||||
setSortBy(tab, value as ItemSortBy)
|
||||
}
|
||||
|
||||
const handleSortOrderChange = (value: string) => {
|
||||
triggerHaptic('impactLight')
|
||||
setSortDescending(tab, value === 'descending')
|
||||
}
|
||||
|
||||
// When opening the sheet, if stored sort is not in allowed options (e.g. after tab-specific change), persist the fallback
|
||||
useEffect(() => {
|
||||
if (effectiveSortBy !== currentSortBy) {
|
||||
setSortBy(tab, effectiveSortBy)
|
||||
}
|
||||
}, [tab, effectiveSortBy, currentSortBy, setSortBy])
|
||||
|
||||
return (
|
||||
<YStack flex={1} padding={'$4'} gap={'$4'}>
|
||||
<YStack gap={'$2'}>
|
||||
<Text bold fontSize={'$6'} marginBottom={'$2'}>
|
||||
Sort By
|
||||
</Text>
|
||||
<RadioGroup value={effectiveSortBy} onValueChange={handleSortByChange}>
|
||||
<YStack gap={'$2'}>
|
||||
{sortByOptions.map((option) => (
|
||||
<RadioGroupItemWithLabel
|
||||
key={option.value}
|
||||
size={'$4'}
|
||||
value={option.value}
|
||||
label={option.label}
|
||||
/>
|
||||
))}
|
||||
</YStack>
|
||||
</RadioGroup>
|
||||
</YStack>
|
||||
|
||||
<YStack gap={'$2'}>
|
||||
<Text bold fontSize={'$6'} marginBottom={'$2'}>
|
||||
Sort Order
|
||||
</Text>
|
||||
<RadioGroup
|
||||
value={sortDescending ? 'descending' : 'ascending'}
|
||||
onValueChange={handleSortOrderChange}
|
||||
>
|
||||
<YStack gap={'$2'}>
|
||||
<RadioGroupItemWithLabel
|
||||
size={'$4'}
|
||||
value='ascending'
|
||||
label={sortOrderLabels.ascending}
|
||||
/>
|
||||
<RadioGroupItemWithLabel
|
||||
size={'$4'}
|
||||
value='descending'
|
||||
label={sortOrderLabels.descending}
|
||||
/>
|
||||
</YStack>
|
||||
</RadioGroup>
|
||||
</YStack>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import React, { RefObject, useRef, useEffect } from 'react'
|
||||
import Track from '../Global/components/Track'
|
||||
import { useTheme, XStack, YStack } from 'tamagui'
|
||||
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'
|
||||
import { Queue } from '../../player/types/queue-item'
|
||||
import { FlashList, FlashListRef } from '@shopify/flash-list'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
@@ -20,6 +21,8 @@ interface TracksProps {
|
||||
tracksInfiniteQuery: UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error>
|
||||
trackPageParams?: RefObject<Set<string>>
|
||||
showAlphabeticalSelector?: boolean
|
||||
sortBy?: ItemSortBy
|
||||
sortDescending?: boolean
|
||||
navigation: Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
|
||||
queue: Queue
|
||||
}
|
||||
@@ -28,6 +31,8 @@ export default function Tracks({
|
||||
tracksInfiniteQuery,
|
||||
trackPageParams,
|
||||
showAlphabeticalSelector,
|
||||
sortBy,
|
||||
sortDescending,
|
||||
navigation,
|
||||
queue,
|
||||
}: TracksProps): React.JSX.Element {
|
||||
@@ -38,11 +43,16 @@ export default function Tracks({
|
||||
const pendingLetterRef = useRef<string | null>(null)
|
||||
|
||||
const stickyHeaderIndicies = (() => {
|
||||
if (!showAlphabeticalSelector || !tracksInfiniteQuery.data) return []
|
||||
|
||||
if (
|
||||
!showAlphabeticalSelector ||
|
||||
!tracksInfiniteQuery.data ||
|
||||
sortBy === ItemSortBy.Artist ||
|
||||
sortBy === ItemSortBy.Album
|
||||
)
|
||||
return []
|
||||
return tracksInfiniteQuery.data
|
||||
.map((track, index) => (typeof track === 'string' ? index : 0))
|
||||
.filter((value, index, indices) => indices.indexOf(value) === index)
|
||||
.map((track, index) => (typeof track === 'string' ? index : null))
|
||||
.filter((v): v is number => v !== null)
|
||||
})()
|
||||
|
||||
const { mutateAsync: alphabetSelectorMutate, isPending: isAlphabetSelectorPending } =
|
||||
@@ -72,6 +82,7 @@ export default function Tracks({
|
||||
}) => {
|
||||
switch (typeof track) {
|
||||
case 'string':
|
||||
if (sortBy === ItemSortBy.Artist || sortBy === ItemSortBy.Album) return null
|
||||
return <FlashListStickyHeader text={track.toUpperCase()} />
|
||||
case 'object':
|
||||
return track.Type === BaseItemKind.Audio ? (
|
||||
@@ -83,6 +94,9 @@ export default function Tracks({
|
||||
testID={`track-item-${index}`}
|
||||
tracklist={tracks.slice(tracks.indexOf(track), tracks.indexOf(track) + 50)}
|
||||
queue={queue}
|
||||
sortingByAlbum={sortBy === ItemSortBy.Album}
|
||||
sortingByReleasedDate={sortBy === ItemSortBy.PremiereDate}
|
||||
sortingByPlayCount={sortBy === ItemSortBy.PlayCount}
|
||||
/>
|
||||
) : (
|
||||
<ItemRow navigation={navigation} item={track} />
|
||||
@@ -138,6 +152,7 @@ export default function Tracks({
|
||||
return (
|
||||
<XStack flex={1}>
|
||||
<FlashList
|
||||
key={`tracks-${sortBy ?? 'default'}`}
|
||||
ref={sectionListRef}
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
numColumns={1}
|
||||
@@ -173,6 +188,7 @@ export default function Tracks({
|
||||
|
||||
{showAlphabeticalSelector && trackPageParams && (
|
||||
<AZScroller
|
||||
reverseOrder={sortDescending}
|
||||
onLetterSelect={(letter) =>
|
||||
alphabetSelectorMutate({
|
||||
letter,
|
||||
|
||||
@@ -67,6 +67,8 @@ export async function handleShuffle(keepCurrentTrack: boolean = true): Promise<J
|
||||
const isDownloaded = filters.isDownloaded === true
|
||||
const isUnplayed = filters.isUnplayed === true
|
||||
const genreIds = filters.genreIds
|
||||
const yearMin = filters.yearMin
|
||||
const yearMax = filters.yearMax
|
||||
|
||||
let randomTracks: JellifyTrack[] = []
|
||||
|
||||
@@ -87,6 +89,16 @@ export async function handleShuffle(keepCurrentTrack: boolean = true): Promise<J
|
||||
// Filter downloaded tracks
|
||||
let filteredDownloads = downloadedTracks
|
||||
|
||||
// Filter by year range
|
||||
if (yearMin != null || yearMax != null) {
|
||||
const min = yearMin ?? 0
|
||||
const max = yearMax ?? new Date().getFullYear()
|
||||
filteredDownloads = filteredDownloads.filter((download) => {
|
||||
const y = download.item.ProductionYear
|
||||
return y != null && y >= min && y <= max
|
||||
})
|
||||
}
|
||||
|
||||
// Filter by favorites
|
||||
if (isFavorites) {
|
||||
filteredDownloads = filteredDownloads.filter((download) => {
|
||||
@@ -118,6 +130,19 @@ export async function handleShuffle(keepCurrentTrack: boolean = true): Promise<J
|
||||
apiFilters.push(ItemFilter.IsUnplayed)
|
||||
}
|
||||
|
||||
// Build years param for year range filter
|
||||
const yearsParam =
|
||||
yearMin != null || yearMax != null
|
||||
? (() => {
|
||||
const min = yearMin ?? 0
|
||||
const max = yearMax ?? new Date().getFullYear()
|
||||
if (min > max) return undefined
|
||||
const years: string[] = []
|
||||
for (let y = min; y <= max; y++) years.push(String(y))
|
||||
return years.length > 0 ? years : undefined
|
||||
})()
|
||||
: undefined
|
||||
|
||||
// Fetch random tracks from Jellyfin with filters
|
||||
const data = await nitroFetch<{ Items: BaseItemDto[] }>(api, '/Items', {
|
||||
ParentId: library.musicLibraryId,
|
||||
@@ -127,6 +152,7 @@ export async function handleShuffle(keepCurrentTrack: boolean = true): Promise<J
|
||||
SortBy: [ItemSortBy.Random],
|
||||
Filters: apiFilters.length > 0 ? apiFilters : undefined,
|
||||
GenreIds: genreIds && genreIds.length > 0 ? genreIds : undefined,
|
||||
Years: yearsParam,
|
||||
Limit: ApiLimits.LibraryShuffle,
|
||||
Fields: [
|
||||
ItemFields.MediaSources,
|
||||
|
||||
@@ -94,6 +94,44 @@ export default function GenreSelectionScreen({
|
||||
})
|
||||
}, [triggerHaptic])
|
||||
|
||||
const allLoadedGenreIds = useMemo(
|
||||
() => genres?.map((g) => g.Id!).filter(Boolean) ?? [],
|
||||
[genres],
|
||||
)
|
||||
const allSelected =
|
||||
allLoadedGenreIds.length > 0 && selectedGenreIds.length === allLoadedGenreIds.length
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
triggerHaptic('impactLight')
|
||||
setSelectedGenreIds([...allLoadedGenreIds])
|
||||
}, [allLoadedGenreIds, triggerHaptic])
|
||||
|
||||
const renderListHeader = useCallback(
|
||||
() => (
|
||||
<XStack
|
||||
alignItems='center'
|
||||
padding='$3'
|
||||
gap='$3'
|
||||
pressStyle={{ opacity: 0.6 }}
|
||||
animation='quick'
|
||||
onPress={handleSelectAll}
|
||||
backgroundColor='$backgroundHover'
|
||||
>
|
||||
<YStack flex={1}>
|
||||
<Text bold>Select all</Text>
|
||||
{genres != null && (
|
||||
<Text color='$borderColor'>{`${allLoadedGenreIds.length} genres`}</Text>
|
||||
)}
|
||||
</YStack>
|
||||
<Icon
|
||||
name={allSelected ? 'check-circle-outline' : 'circle-outline'}
|
||||
color={allSelected ? '$primary' : '$borderColor'}
|
||||
/>
|
||||
</XStack>
|
||||
),
|
||||
[handleSelectAll, allSelected, allLoadedGenreIds.length, genres],
|
||||
)
|
||||
|
||||
const renderItem: ListRenderItem<BaseItemDto | string> = ({ item }) => {
|
||||
if (typeof item === 'string') {
|
||||
// Section header
|
||||
@@ -181,6 +219,7 @@ export default function GenreSelectionScreen({
|
||||
data={flattenedGenres}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
ListHeaderComponent={renderListHeader}
|
||||
// @ts-expect-error - estimatedItemSize is required by FlashList but types are incorrect
|
||||
estimatedItemSize={70}
|
||||
onEndReached={() => {
|
||||
|
||||
6
src/screens/SortOptions/index.tsx
Normal file
6
src/screens/SortOptions/index.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import SortOptionsComponent from '../../components/SortOptions/index'
|
||||
import { SortOptionsProps } from '../types'
|
||||
|
||||
export default function SortOptionsSheet({ route }: SortOptionsProps): React.JSX.Element {
|
||||
return <SortOptionsComponent currentTab={route.params?.currentTab} />
|
||||
}
|
||||
295
src/screens/YearSelection/index.tsx
Normal file
295
src/screens/YearSelection/index.tsx
Normal file
@@ -0,0 +1,295 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { YStack, XStack, Button, Spinner } from 'tamagui'
|
||||
import { Modal, ScrollView, Pressable } from 'react-native'
|
||||
import { Text } from '../../components/Global/helpers/text'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import { triggerHaptic } from '../../hooks/use-haptic-feedback'
|
||||
import { YearSelectionProps } from '../types'
|
||||
import useLibraryStore from '../../stores/library'
|
||||
import { useLibraryYears } from '../../api/queries/years'
|
||||
|
||||
const ANY = 'any'
|
||||
type Picking = 'min' | 'max' | null
|
||||
|
||||
export default function YearSelectionScreen({
|
||||
navigation,
|
||||
route,
|
||||
}: YearSelectionProps): React.JSX.Element {
|
||||
const tab = route.params?.tab ?? 'Tracks'
|
||||
const { years: availableYears, isPending, isError } = useLibraryYears()
|
||||
const storeFilters = useLibraryStore.getState().filters[tab === 'Albums' ? 'albums' : 'tracks']
|
||||
const [minYear, setMinYear] = useState<number | typeof ANY>(storeFilters.yearMin ?? ANY)
|
||||
const [maxYear, setMaxYear] = useState<number | typeof ANY>(storeFilters.yearMax ?? ANY)
|
||||
const [picking, setPicking] = useState<Picking>(null)
|
||||
|
||||
// Min year options: if maxYear is set, only years <= maxYear
|
||||
const minYearOptions = useMemo(() => {
|
||||
if (availableYears.length === 0) return []
|
||||
const max = typeof maxYear === 'number' ? maxYear : Math.max(...availableYears)
|
||||
return availableYears.filter((y) => y <= max)
|
||||
}, [availableYears, maxYear])
|
||||
|
||||
// Max year options: if minYear is set, only years >= minYear
|
||||
const maxYearOptions = useMemo(() => {
|
||||
if (availableYears.length === 0) return []
|
||||
const min = typeof minYear === 'number' ? minYear : Math.min(...availableYears)
|
||||
return availableYears.filter((y) => y >= min)
|
||||
}, [availableYears, minYear])
|
||||
|
||||
const handleOpenMin = useCallback(() => {
|
||||
triggerHaptic('impactLight')
|
||||
setPicking('min')
|
||||
}, [])
|
||||
|
||||
const handleOpenMax = useCallback(() => {
|
||||
triggerHaptic('impactLight')
|
||||
setPicking('max')
|
||||
}, [])
|
||||
|
||||
const handleSelectMin = useCallback(
|
||||
(year: number | typeof ANY) => {
|
||||
triggerHaptic('impactLight')
|
||||
setMinYear(year)
|
||||
setPicking(null)
|
||||
if (year !== ANY && typeof maxYear === 'number' && year > maxYear) {
|
||||
setMaxYear(year)
|
||||
}
|
||||
},
|
||||
[maxYear],
|
||||
)
|
||||
|
||||
const handleSelectMax = useCallback(
|
||||
(year: number | typeof ANY) => {
|
||||
triggerHaptic('impactLight')
|
||||
setMaxYear(year)
|
||||
setPicking(null)
|
||||
if (year !== ANY && typeof minYear === 'number' && year < minYear) {
|
||||
setMinYear(year)
|
||||
}
|
||||
},
|
||||
[minYear],
|
||||
)
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
triggerHaptic('impactLight')
|
||||
const payload = {
|
||||
yearMin: minYear === ANY ? undefined : minYear,
|
||||
yearMax: maxYear === ANY ? undefined : maxYear,
|
||||
}
|
||||
if (tab === 'Albums') {
|
||||
useLibraryStore.getState().setAlbumsFilters(payload)
|
||||
} else {
|
||||
useLibraryStore.getState().setTracksFilters(payload)
|
||||
}
|
||||
navigation.goBack()
|
||||
}, [minYear, maxYear, navigation, tab])
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
triggerHaptic('impactLight')
|
||||
setMinYear(ANY)
|
||||
setMaxYear(ANY)
|
||||
const payload = { yearMin: undefined, yearMax: undefined }
|
||||
if (tab === 'Albums') {
|
||||
useLibraryStore.getState().setAlbumsFilters(payload)
|
||||
} else {
|
||||
useLibraryStore.getState().setTracksFilters(payload)
|
||||
}
|
||||
}, [tab])
|
||||
|
||||
const hasSelection = minYear !== ANY || maxYear !== ANY
|
||||
const rangeLabel =
|
||||
minYear !== ANY || maxYear !== ANY
|
||||
? `${minYear === ANY ? '…' : minYear} – ${maxYear === ANY ? '…' : maxYear}`
|
||||
: null
|
||||
|
||||
const minLabel = minYear === ANY ? 'Any' : String(minYear)
|
||||
const maxLabel = maxYear === ANY ? 'Any' : String(maxYear)
|
||||
|
||||
if (isPending && availableYears.length === 0) {
|
||||
return (
|
||||
<YStack flex={1} alignItems='center' justifyContent='center'>
|
||||
<Spinner size='large' />
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<YStack flex={1} alignItems='center' justifyContent='center' padding='$4'>
|
||||
<Text color='$borderColor'>Could not load years</Text>
|
||||
<Button marginTop='$4' onPress={() => navigation.goBack()}>
|
||||
Go back
|
||||
</Button>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
const pickerOptions = picking === 'min' ? minYearOptions : maxYearOptions
|
||||
const onSelectOption = picking === 'min' ? handleSelectMin : handleSelectMax
|
||||
const currentValue = picking === 'min' ? minYear : maxYear
|
||||
|
||||
return (
|
||||
<YStack flex={1} backgroundColor='$background'>
|
||||
<XStack
|
||||
justifyContent='space-between'
|
||||
alignItems='center'
|
||||
padding='$4'
|
||||
borderBottomWidth={1}
|
||||
borderBottomColor='$borderColor'
|
||||
>
|
||||
<Button variant='outlined' size='$3' onPress={() => navigation.goBack()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Text bold fontSize='$6'>
|
||||
Year range
|
||||
</Text>
|
||||
<Button variant='outlined' size='$3' onPress={handleClear} disabled={!hasSelection}>
|
||||
Clear
|
||||
</Button>
|
||||
</XStack>
|
||||
|
||||
<YStack flex={1} padding='$4' gap='$2'>
|
||||
<Text bold fontSize='$5' marginBottom='$2'>
|
||||
Min year
|
||||
</Text>
|
||||
<Pressable onPress={handleOpenMin}>
|
||||
<XStack
|
||||
alignItems='center'
|
||||
justifyContent='space-between'
|
||||
padding='$3'
|
||||
backgroundColor='$backgroundHover'
|
||||
borderRadius='$2'
|
||||
borderWidth={1}
|
||||
borderColor='$borderColor'
|
||||
>
|
||||
<Text>{minLabel}</Text>
|
||||
<Icon name='chevron-down' color='$borderColor' />
|
||||
</XStack>
|
||||
</Pressable>
|
||||
|
||||
<Text bold fontSize='$5' marginBottom='$2' marginTop='$3'>
|
||||
Max year
|
||||
</Text>
|
||||
<Pressable onPress={handleOpenMax}>
|
||||
<XStack
|
||||
alignItems='center'
|
||||
justifyContent='space-between'
|
||||
padding='$3'
|
||||
backgroundColor='$backgroundHover'
|
||||
borderRadius='$2'
|
||||
borderWidth={1}
|
||||
borderColor='$borderColor'
|
||||
>
|
||||
<Text>{maxLabel}</Text>
|
||||
<Icon name='chevron-down' color='$borderColor' />
|
||||
</XStack>
|
||||
</Pressable>
|
||||
</YStack>
|
||||
|
||||
{/* Dropdown picker modal */}
|
||||
<Modal
|
||||
visible={picking !== null}
|
||||
transparent
|
||||
animationType='fade'
|
||||
onRequestClose={() => setPicking(null)}
|
||||
>
|
||||
<Pressable
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
}}
|
||||
onPress={() => setPicking(null)}
|
||||
>
|
||||
<Pressable style={{ maxHeight: '100%' }} onPress={(e) => e.stopPropagation()}>
|
||||
<YStack
|
||||
backgroundColor='$background'
|
||||
borderTopLeftRadius='$4'
|
||||
borderTopRightRadius='$4'
|
||||
padding='$4'
|
||||
maxHeight='100%'
|
||||
>
|
||||
<Text bold fontSize='$5' marginBottom='$3'>
|
||||
{picking === 'min' ? 'Select min year' : 'Select max year'}
|
||||
</Text>
|
||||
<ScrollView
|
||||
style={{ maxHeight: 330 }}
|
||||
showsVerticalScrollIndicator
|
||||
keyboardShouldPersistTaps='handled'
|
||||
>
|
||||
<Pressable
|
||||
onPress={() => onSelectOption(ANY)}
|
||||
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
|
||||
>
|
||||
<XStack
|
||||
padding='$3'
|
||||
alignItems='center'
|
||||
backgroundColor={
|
||||
currentValue === ANY
|
||||
? '$backgroundHover'
|
||||
: 'transparent'
|
||||
}
|
||||
borderRadius='$2'
|
||||
>
|
||||
<Text
|
||||
fontWeight={currentValue === ANY ? 'bold' : undefined}
|
||||
>
|
||||
Any
|
||||
</Text>
|
||||
</XStack>
|
||||
</Pressable>
|
||||
{pickerOptions.map((y) => (
|
||||
<Pressable
|
||||
key={y}
|
||||
onPress={() => onSelectOption(y)}
|
||||
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
|
||||
>
|
||||
<XStack
|
||||
padding='$3'
|
||||
alignItems='center'
|
||||
backgroundColor={
|
||||
currentValue === y
|
||||
? '$backgroundHover'
|
||||
: 'transparent'
|
||||
}
|
||||
borderRadius='$2'
|
||||
>
|
||||
<Text
|
||||
fontWeight={currentValue === y ? 'bold' : undefined}
|
||||
>
|
||||
{String(y)}
|
||||
</Text>
|
||||
</XStack>
|
||||
</Pressable>
|
||||
))}
|
||||
</ScrollView>
|
||||
</YStack>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
|
||||
{hasSelection && (
|
||||
<XStack
|
||||
justifyContent='space-evenly'
|
||||
alignItems='center'
|
||||
padding='$4'
|
||||
borderTopWidth={1}
|
||||
borderTopColor='$borderColor'
|
||||
>
|
||||
<Text fontSize='$3' bold color='$primary'>
|
||||
{rangeLabel ?? ''}
|
||||
</Text>
|
||||
<Button
|
||||
variant='outlined'
|
||||
borderColor='$primary'
|
||||
color='$primary'
|
||||
size='$3'
|
||||
onPress={handleSave}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</XStack>
|
||||
)}
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
@@ -17,7 +17,9 @@ import DeletePlaylist from './Library/delete-playlist'
|
||||
import { Platform } from 'react-native'
|
||||
import { formatArtistNames } from '../utils/formatting/artist-names'
|
||||
import FiltersSheet from './Filters'
|
||||
import SortOptionsSheet from './SortOptions'
|
||||
import GenreSelectionScreen from './GenreSelection'
|
||||
import YearSelectionScreen from './YearSelection'
|
||||
|
||||
const RootStack = createNativeStackNavigator<RootStackParamList>()
|
||||
|
||||
@@ -88,6 +90,17 @@ export default function Root(): React.JSX.Element {
|
||||
}}
|
||||
/>
|
||||
|
||||
<RootStack.Screen
|
||||
name='SortOptions'
|
||||
component={SortOptionsSheet}
|
||||
options={{
|
||||
headerTitle: 'Sort',
|
||||
presentation: 'formSheet',
|
||||
sheetAllowedDetents: 'fitToContents',
|
||||
sheetGrabberVisible: true,
|
||||
}}
|
||||
/>
|
||||
|
||||
<RootStack.Screen
|
||||
name='AudioSpecs'
|
||||
component={AudioSpecsSheet}
|
||||
@@ -120,6 +133,16 @@ export default function Root(): React.JSX.Element {
|
||||
sheetGrabberVisible: true,
|
||||
}}
|
||||
/>
|
||||
|
||||
<RootStack.Screen
|
||||
name='YearSelection'
|
||||
component={YearSelectionScreen}
|
||||
options={{
|
||||
headerTitle: 'Year range',
|
||||
presentation: 'modal',
|
||||
sheetGrabberVisible: true,
|
||||
}}
|
||||
/>
|
||||
</RootStack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
9
src/screens/types.d.ts
vendored
9
src/screens/types.d.ts
vendored
@@ -66,10 +66,15 @@ export type RootStackParamList = {
|
||||
}
|
||||
|
||||
Filters: {
|
||||
currentTab?: 'Tracks' | 'Albums' | 'Artists' | 'Playlists'
|
||||
currentTab?: 'Tracks' | 'Albums' | 'Artists'
|
||||
}
|
||||
|
||||
SortOptions: {
|
||||
currentTab?: 'Tracks' | 'Albums' | 'Artists'
|
||||
}
|
||||
|
||||
GenreSelection: undefined
|
||||
YearSelection: { tab?: 'Tracks' | 'Albums' }
|
||||
|
||||
AudioSpecs: {
|
||||
item: BaseItemDto
|
||||
@@ -93,7 +98,9 @@ export type AudioSpecsProps = NativeStackScreenProps<RootStackParamList, 'AudioS
|
||||
export type DeletePlaylistProps = NativeStackScreenProps<RootStackParamList, 'DeletePlaylist'>
|
||||
|
||||
export type FiltersProps = NativeStackScreenProps<RootStackParamList, 'Filters'>
|
||||
export type SortOptionsProps = NativeStackScreenProps<RootStackParamList, 'SortOptions'>
|
||||
export type GenreSelectionProps = NativeStackScreenProps<RootStackParamList, 'GenreSelection'>
|
||||
export type YearSelectionProps = NativeStackScreenProps<RootStackParamList, 'YearSelection'>
|
||||
|
||||
export type GenresProps = {
|
||||
genres: InfiniteData<BaseItemDto[], unknown> | undefined
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
import { createJSONStorage, devtools, persist } from 'zustand/middleware'
|
||||
import { mmkvStateStorage } from '../constants/storage'
|
||||
import { create } from 'zustand'
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'
|
||||
|
||||
export type LibraryTab = 'tracks' | 'albums' | 'artists'
|
||||
|
||||
type TabFilterState = {
|
||||
isFavorites: boolean | undefined
|
||||
isDownloaded?: boolean // Only for Tracks tab
|
||||
isUnplayed?: boolean // Only for Tracks tab
|
||||
genreIds?: string[] // Only for Tracks tab
|
||||
yearMin?: number // Tracks and Albums
|
||||
yearMax?: number // Tracks and Albums
|
||||
}
|
||||
|
||||
type SortState = Record<LibraryTab, ItemSortBy>
|
||||
type SortOrderState = Record<LibraryTab, boolean>
|
||||
|
||||
type LibraryStore = {
|
||||
sortDescending: boolean
|
||||
setSortDescending: (sortDescending: boolean) => void
|
||||
sortBy: SortState
|
||||
sortDescending: SortOrderState
|
||||
setSortBy: (tab: LibraryTab, sortBy: ItemSortBy) => void
|
||||
setSortDescending: (tab: LibraryTab, sortDescending: boolean) => void
|
||||
getSortBy: (tab: LibraryTab) => ItemSortBy
|
||||
getSortDescending: (tab: LibraryTab) => boolean
|
||||
filters: {
|
||||
tracks: TabFilterState
|
||||
albums: TabFilterState
|
||||
@@ -28,8 +40,54 @@ const useLibraryStore = create<LibraryStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
sortDescending: false,
|
||||
setSortDescending: (sortDescending: boolean) => set({ sortDescending }),
|
||||
sortBy: {
|
||||
tracks: ItemSortBy.Name,
|
||||
albums: ItemSortBy.Name,
|
||||
artists: ItemSortBy.SortName,
|
||||
},
|
||||
sortDescending: {
|
||||
tracks: false,
|
||||
albums: false,
|
||||
artists: false,
|
||||
},
|
||||
setSortBy: (tab: LibraryTab, sortBy: ItemSortBy) =>
|
||||
set((state) => {
|
||||
const current = state.sortBy as SortState | string
|
||||
const next: SortState =
|
||||
typeof current === 'object' && current !== null && 'tracks' in current
|
||||
? { ...current, [tab]: sortBy }
|
||||
: {
|
||||
tracks: ItemSortBy.Name,
|
||||
albums: ItemSortBy.Name,
|
||||
artists: ItemSortBy.SortName,
|
||||
[tab]: sortBy,
|
||||
}
|
||||
return { sortBy: next }
|
||||
}),
|
||||
setSortDescending: (tab: LibraryTab, sortDescending: boolean) =>
|
||||
set((state) => {
|
||||
const current = state.sortDescending as SortOrderState | boolean
|
||||
const next: SortOrderState =
|
||||
typeof current === 'object' && current !== null && 'tracks' in current
|
||||
? { ...current, [tab]: sortDescending }
|
||||
: {
|
||||
tracks: false,
|
||||
albums: false,
|
||||
artists: false,
|
||||
[tab]: sortDescending,
|
||||
}
|
||||
return { sortDescending: next }
|
||||
}),
|
||||
getSortBy: (tab: LibraryTab) => {
|
||||
const sortBy = get().sortBy as SortState | string
|
||||
if (typeof sortBy === 'string') return sortBy as ItemSortBy
|
||||
return sortBy[tab] ?? ItemSortBy.Name
|
||||
},
|
||||
getSortDescending: (tab: LibraryTab) => {
|
||||
const sortDescending = get().sortDescending as SortOrderState | boolean
|
||||
if (typeof sortDescending === 'boolean') return sortDescending
|
||||
return sortDescending[tab] ?? false
|
||||
},
|
||||
|
||||
filters: {
|
||||
tracks: {
|
||||
@@ -37,9 +95,13 @@ const useLibraryStore = create<LibraryStore>()(
|
||||
isDownloaded: false,
|
||||
isUnplayed: undefined,
|
||||
genreIds: undefined,
|
||||
yearMin: undefined,
|
||||
yearMax: undefined,
|
||||
},
|
||||
albums: {
|
||||
isFavorites: undefined,
|
||||
yearMin: undefined,
|
||||
yearMax: undefined,
|
||||
},
|
||||
artists: {
|
||||
isFavorites: undefined,
|
||||
|
||||
@@ -16,6 +16,7 @@ export type BaseItemDtoSlimified = Pick<
|
||||
| 'RunTimeTicks'
|
||||
| 'OfficialRating'
|
||||
| 'CustomRating'
|
||||
| 'ProductionYear'
|
||||
>
|
||||
|
||||
interface JellifyTrack extends Track {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
export function formatArtistName(artistName: string | null | undefined): string {
|
||||
if (!artistName) return 'Unknown Artist'
|
||||
return artistName
|
||||
export function formatArtistName(
|
||||
artistName: string | null | undefined,
|
||||
releaseDate?: string | null | undefined,
|
||||
): string {
|
||||
const unknownArtist = 'Unknown Artist'
|
||||
if (!artistName) return releaseDate ? `${releaseDate} • ${unknownArtist}` : unknownArtist
|
||||
return releaseDate ? `${releaseDate} • ${artistName}` : artistName
|
||||
}
|
||||
|
||||
export function formatArtistNames(artistNames: string[] | null | undefined): string {
|
||||
if (!artistNames || artistNames.length === 0) return 'Unknown Artist'
|
||||
return artistNames.map(formatArtistName).join(' • ')
|
||||
return artistNames.map((artistName) => formatArtistName(artistName)).join(' • ')
|
||||
}
|
||||
|
||||
9
src/utils/mapping/build-years-param.ts
Normal file
9
src/utils/mapping/build-years-param.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export default function buildYearsParam(yearMin?: number, yearMax?: number): string[] | undefined {
|
||||
if (yearMin == null && yearMax == null) return undefined
|
||||
const min = yearMin ?? 0
|
||||
const max = yearMax ?? new Date().getFullYear()
|
||||
if (min > max) return undefined
|
||||
const years: string[] = []
|
||||
for (let y = min; y <= max; y++) years.push(String(y))
|
||||
return years.length > 0 ? years : undefined
|
||||
}
|
||||
@@ -1,15 +1,26 @@
|
||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'
|
||||
import { InfiniteData } from '@tanstack/react-query'
|
||||
import { isString } from 'lodash'
|
||||
import { RefObject } from 'react'
|
||||
|
||||
export type FlattenInfiniteQueryPagesOptions = {
|
||||
/**
|
||||
* When ItemSortBy.Artist, section letters are derived from the item's artist (AlbumArtist/Artists).
|
||||
* When ItemSortBy.Album, section letters are derived from the item's album name.
|
||||
* Otherwise (Name, SortName, etc.) letters are derived from the item's name/SortName.
|
||||
*/
|
||||
sortBy?: ItemSortBy
|
||||
}
|
||||
|
||||
export default function flattenInfiniteQueryPages(
|
||||
data: InfiniteData<BaseItemDto[], unknown>,
|
||||
pageParams: RefObject<Set<string>>,
|
||||
options?: FlattenInfiniteQueryPagesOptions,
|
||||
) {
|
||||
/**
|
||||
* A flattened array of all artists derived from the infinite query
|
||||
* A flattened array of all items derived from the infinite query
|
||||
*/
|
||||
const flattenedItemPages = data.pages.flatMap((page) => page)
|
||||
|
||||
@@ -19,15 +30,23 @@ export default function flattenInfiniteQueryPages(
|
||||
const seenLetters = new Set<string>()
|
||||
|
||||
/**
|
||||
* The final array that will be provided to and rendered by the {@link Artists} component
|
||||
* The final array that will be provided to and rendered by the list component
|
||||
*/
|
||||
const flashListItems: (string | number | BaseItemDto)[] = []
|
||||
|
||||
// Letter source: Artist → artist; Album → album name; otherwise → item name (track name, etc.)
|
||||
const extractLetter =
|
||||
options?.sortBy === ItemSortBy.Artist
|
||||
? extractFirstLetterByArtist
|
||||
: options?.sortBy === ItemSortBy.Album
|
||||
? extractFirstLetterByAlbum
|
||||
: extractFirstLetter
|
||||
|
||||
flattenedItemPages.forEach((item: BaseItemDto) => {
|
||||
const rawLetter = extractFirstLetter(item)
|
||||
const rawLetter = extractLetter(item)
|
||||
|
||||
/**
|
||||
* An alpha character or a hash if the artist's name doesn't start with a letter
|
||||
* An alpha character or a hash if the name doesn't start with a letter
|
||||
*/
|
||||
const letter = rawLetter.match(/[A-Z]/) ? rawLetter : '#'
|
||||
|
||||
@@ -53,3 +72,17 @@ function extractFirstLetter({ Type, SortName, Name }: BaseItemDto): string {
|
||||
|
||||
return letter
|
||||
}
|
||||
|
||||
function extractFirstLetterByArtist(item: BaseItemDto): string {
|
||||
const raw =
|
||||
(isString(item.AlbumArtist) && item.AlbumArtist.trim()) ||
|
||||
(item.Artists?.[0] && isString(item.Artists[0]) && item.Artists[0].trim())
|
||||
if (!raw) return '#'
|
||||
return raw.charAt(0).toUpperCase()
|
||||
}
|
||||
|
||||
function extractFirstLetterByAlbum(item: BaseItemDto): string {
|
||||
const raw = isString(item.Album) && item.Album.trim()
|
||||
if (!raw) return '#'
|
||||
return raw.charAt(0).toUpperCase()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user