Updates for now playing
This commit is contained in:
parent
090b2b95bd
commit
4ffcb25a67
13 changed files with 10069 additions and 9395 deletions
250
package-lock.json
generated
250
package-lock.json
generated
|
|
@ -72,10 +72,10 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@musicorum/lastfm": "github:jedmund/lastfm",
|
"@musicorum/lastfm": "github:jedmund/lastfm",
|
||||||
"@poppanator/sveltekit-svg": "^5.0.0-svelte5.4",
|
"@poppanator/sveltekit-svg": "^5.0.0-svelte5.4",
|
||||||
"@storybook/addon-a11y": "^9.0.1",
|
"@storybook/addon-a11y": "^9.0.9",
|
||||||
"@storybook/addon-docs": "^9.0.1",
|
"@storybook/addon-docs": "^9.0.9",
|
||||||
"@storybook/addon-svelte-csf": "^5.0.3",
|
"@storybook/addon-svelte-csf": "^5.0.3",
|
||||||
"@storybook/sveltekit": "^9.0.1",
|
"@storybook/sveltekit": "^9.0.9",
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
||||||
|
|
@ -84,14 +84,14 @@
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-storybook": "^9.0.1",
|
"eslint-plugin-storybook": "^9.0.9",
|
||||||
"eslint-plugin-svelte": "^2.36.0",
|
"eslint-plugin-svelte": "^2.36.0",
|
||||||
"globals": "^15.0.0",
|
"globals": "^15.0.0",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.39",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.1.1",
|
||||||
"prettier-plugin-svelte": "^3.1.2",
|
"prettier-plugin-svelte": "^3.1.2",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"storybook": "^9.0.1",
|
"storybook": "^9.0.9",
|
||||||
"svelte": "^5.0.0-next.1",
|
"svelte": "^5.0.0-next.1",
|
||||||
"svelte-check": "^3.6.0",
|
"svelte-check": "^3.6.0",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.4.1",
|
||||||
|
|
@ -1190,7 +1190,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz",
|
||||||
"integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==",
|
"integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdx": "^2.0.0"
|
"@types/mdx": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -1756,11 +1755,10 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/addon-a11y": {
|
"node_modules/@storybook/addon-a11y": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-9.0.9.tgz",
|
||||||
"integrity": "sha512-YlJtsGtsungRnTI50AIIxJ0vCo0AoVzkBh3iQSi8nMs+jo2OBzXTXeAVs/8R97s0Pa1rnUgq98rVHdG9y7KUrg==",
|
"integrity": "sha512-0oNSkjdmvJadSORAlCFia4YfxWjrYNNMmuK64Jrl/kahWj3tFX1JMAI1uNWuHc+SRwGzC5rEhw4oq+7AmVUVag==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/global": "^5.0.0",
|
"@storybook/global": "^5.0.0",
|
||||||
"axe-core": "^4.2.0"
|
"axe-core": "^4.2.0"
|
||||||
|
|
@ -1770,20 +1768,19 @@
|
||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"storybook": "^9.0.1"
|
"storybook": "^9.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/addon-docs": {
|
"node_modules/@storybook/addon-docs": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-9.0.9.tgz",
|
||||||
"integrity": "sha512-sRU18E4auHY0F7JEvyCxO+yt3C3L+7k3l9YIIW3oy1SEQNAZzPkOgQz6Tl74L1UGBk5+Q505PGaQbXAaIHGlzw==",
|
"integrity": "sha512-jioo7RXTEzuCvZ1mD+7e2n6sGvlXH0mm8ENEsercM35vPKZzrmsmwQ+SPJk5GNjrcNy2qXR7bXJA14hwc+shuA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"@storybook/csf-plugin": "9.0.1",
|
"@storybook/csf-plugin": "9.0.9",
|
||||||
"@storybook/icons": "^1.2.12",
|
"@storybook/icons": "^1.2.12",
|
||||||
"@storybook/react-dom-shim": "9.0.1",
|
"@storybook/react-dom-shim": "9.0.9",
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"ts-dedent": "^2.0.0"
|
"ts-dedent": "^2.0.0"
|
||||||
|
|
@ -1793,7 +1790,7 @@
|
||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"storybook": "^9.0.1"
|
"storybook": "^9.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/addon-svelte-csf": {
|
"node_modules/@storybook/addon-svelte-csf": {
|
||||||
|
|
@ -1801,7 +1798,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/addon-svelte-csf/-/addon-svelte-csf-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/addon-svelte-csf/-/addon-svelte-csf-5.0.3.tgz",
|
||||||
"integrity": "sha512-P6f/fCqL0Wq79LTe1EjSjKppKE5h8Dz5+x9R/NK7+5PyQI4NS1RNwMnppUJeJhF4CqV7BLjsFGr88DT3AAdytw==",
|
"integrity": "sha512-P6f/fCqL0Wq79LTe1EjSjKppKE5h8Dz5+x9R/NK7+5PyQI4NS1RNwMnppUJeJhF4CqV7BLjsFGr88DT3AAdytw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/csf": "^0.1.13",
|
"@storybook/csf": "^0.1.13",
|
||||||
"dedent": "^1.5.3",
|
"dedent": "^1.5.3",
|
||||||
|
|
@ -1820,13 +1816,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/builder-vite": {
|
"node_modules/@storybook/builder-vite": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-9.0.9.tgz",
|
||||||
"integrity": "sha512-Kt1Ze/LrK9en4BxG6VNmLy/RuxvexOHE2Rf7R6e9WoQb7zGy7G0EbGRRSm1zqF6jF9L6e6S129nWkiwt2oPhPw==",
|
"integrity": "sha512-qRjXNKc2V5OzGAJ+O82eYlXX7kE9PVGiTAuwaysMSEFiWBEkHVm6SeSwAJFDcKlD4kS60fRj3xWaa724OCqMbw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/csf-plugin": "9.0.1",
|
"@storybook/csf-plugin": "9.0.9",
|
||||||
"ts-dedent": "^2.0.0"
|
"ts-dedent": "^2.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
|
|
@ -1834,7 +1829,7 @@
|
||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"storybook": "^9.0.1",
|
"storybook": "^9.0.9",
|
||||||
"vite": "^5.0.0 || ^6.0.0"
|
"vite": "^5.0.0 || ^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1843,17 +1838,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.13.tgz",
|
||||||
"integrity": "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==",
|
"integrity": "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"type-fest": "^2.19.0"
|
"type-fest": "^2.19.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/csf-plugin": {
|
"node_modules/@storybook/csf-plugin": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-9.0.9.tgz",
|
||||||
"integrity": "sha512-DRAyU1CKMDJpEOEn70gQtPQielI0gxu55unM4CpK6qWAAPAPVmz3+e+VHbh0AnvBZVgwsaB+g7EI8DD4l2d4PQ==",
|
"integrity": "sha512-MvY2EOy/syxFykzgNJhpR+bkJFwyJ4lvUaZnU74/SIJSNc2gdbLDnx3J/AqXyNm0+VRHrBYvteiXLczE/1EtHg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"unplugin": "^1.3.1"
|
"unplugin": "^1.3.1"
|
||||||
},
|
},
|
||||||
|
|
@ -1862,7 +1855,7 @@
|
||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"storybook": "^9.0.1"
|
"storybook": "^9.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/global": {
|
"node_modules/@storybook/global": {
|
||||||
|
|
@ -1877,7 +1870,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.4.0.tgz",
|
||||||
"integrity": "sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA==",
|
"integrity": "sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -1887,11 +1879,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/react-dom-shim": {
|
"node_modules/@storybook/react-dom-shim": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-9.0.9.tgz",
|
||||||
"integrity": "sha512-MtowY/NS+v/UsHJDZDvsAaXRZERw3diQgWSldEtdlwQxypZQ9CWwvgIuIzRuP4i+/m58TyCEeMEG9gZB1b5pBQ==",
|
"integrity": "sha512-c2jvzpHW0EcYKhb7fvl3gh2waAnrNooZJasodxJXNhOIJWa6JkslxQXvhJsBkm24/nsvPvUthUP4hg7rA20a1A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
|
|
@ -1899,15 +1890,14 @@
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
|
||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
|
||||||
"storybook": "^9.0.1"
|
"storybook": "^9.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/svelte": {
|
"node_modules/@storybook/svelte": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-9.0.9.tgz",
|
||||||
"integrity": "sha512-lkhwTBzx+4f5gxOgNTh9yQlSlIBTwLcEU+zy90akL7D5hBcrA8NUPyKOcQMKJjaR9JSjCIYxjOMz36ps5sdlvw==",
|
"integrity": "sha512-q9wwFUHYqS3s5bgSbPlUEz+PpoOgNhe82U02cuF4mHt0mTETWQ4yQQzfZZBHzPkQ12Uqu+ZfXXualVuLZaXYng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ts-dedent": "^2.0.0",
|
"ts-dedent": "^2.0.0",
|
||||||
"type-fest": "~2.19"
|
"type-fest": "~2.19"
|
||||||
|
|
@ -1920,19 +1910,18 @@
|
||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"storybook": "^9.0.1",
|
"storybook": "^9.0.9",
|
||||||
"svelte": "^5.0.0"
|
"svelte": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/svelte-vite": {
|
"node_modules/@storybook/svelte-vite": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-9.0.9.tgz",
|
||||||
"integrity": "sha512-z7LLDkZL74IDb6UgUrb8HDQuwXh3NTKpMXLYCmLIq73uHwO/V2kb6kYnv0UIEuzAsqtP8yG0CdMFqJRsuFyLJw==",
|
"integrity": "sha512-JGQ2+cxCjdj1Kkvr/1wBRsN9Rx650cPUDiQXro8ixsJsJW/M25GeVtGVgUAdiyVvojUpQDfEkvlElSMP9+bYkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/builder-vite": "9.0.1",
|
"@storybook/builder-vite": "9.0.9",
|
||||||
"@storybook/svelte": "9.0.1",
|
"@storybook/svelte": "9.0.9",
|
||||||
"magic-string": "^0.30.0",
|
"magic-string": "^0.30.0",
|
||||||
"svelte2tsx": "^0.7.35",
|
"svelte2tsx": "^0.7.35",
|
||||||
"typescript": "^4.9.4 || ^5.0.0"
|
"typescript": "^4.9.4 || ^5.0.0"
|
||||||
|
|
@ -1946,21 +1935,20 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
|
||||||
"storybook": "^9.0.1",
|
"storybook": "^9.0.9",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
"vite": "^5.0.0 || ^6.0.0"
|
"vite": "^5.0.0 || ^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/sveltekit": {
|
"node_modules/@storybook/sveltekit": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-9.0.9.tgz",
|
||||||
"integrity": "sha512-aPr8g1rNdUEYyAc/wXCeEUySzY+6ad1yi0vrhIEbeTLziCAAsAfcxdHcVxSFE1CO05ES6OsUeAPSylOytu/UBw==",
|
"integrity": "sha512-HYf2KUvKLrqfXRwU5LS0Z/TemSLW48tmJIERP6q14d+GKIjQ08KEVi85YuKcNmuDKWHPNuweGAhz6fm/f5Ys1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/builder-vite": "9.0.1",
|
"@storybook/builder-vite": "9.0.9",
|
||||||
"@storybook/svelte": "9.0.1",
|
"@storybook/svelte": "9.0.9",
|
||||||
"@storybook/svelte-vite": "9.0.1"
|
"@storybook/svelte-vite": "9.0.9"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
|
|
@ -1970,7 +1958,7 @@
|
||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"storybook": "^9.0.1",
|
"storybook": "^9.0.9",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
"vite": "^5.0.0 || ^6.0.0"
|
"vite": "^5.0.0 || ^6.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -2912,8 +2900,7 @@
|
||||||
"version": "2.0.13",
|
"version": "2.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz",
|
||||||
"integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==",
|
"integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
|
@ -2963,11 +2950,10 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.6",
|
"version": "19.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
|
||||||
"integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==",
|
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
|
|
@ -3082,14 +3068,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.33.0",
|
"version": "8.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz",
|
||||||
"integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==",
|
"integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.33.0",
|
"@typescript-eslint/tsconfig-utils": "^8.34.0",
|
||||||
"@typescript-eslint/types": "^8.33.0",
|
"@typescript-eslint/types": "^8.34.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -3098,14 +3083,16 @@
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.33.0",
|
"version": "8.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz",
|
||||||
"integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==",
|
"integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
|
|
@ -3132,11 +3119,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.33.0",
|
"version": "8.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz",
|
||||||
"integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==",
|
"integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
|
|
@ -3549,7 +3535,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
|
||||||
"integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==",
|
"integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MPL-2.0",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
|
|
@ -4065,7 +4050,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/dashdash": {
|
"node_modules/dashdash": {
|
||||||
|
|
@ -4111,7 +4095,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz",
|
||||||
"integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==",
|
"integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"babel-plugin-macros": "^3.1.0"
|
"babel-plugin-macros": "^3.1.0"
|
||||||
},
|
},
|
||||||
|
|
@ -4125,8 +4108,7 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz",
|
||||||
"integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==",
|
"integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/deep-eql": {
|
"node_modules/deep-eql": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
|
|
@ -4357,11 +4339,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-toolkit": {
|
"node_modules/es-toolkit": {
|
||||||
"version": "1.38.0",
|
"version": "1.39.3",
|
||||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.38.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.3.tgz",
|
||||||
"integrity": "sha512-OT3AxczYYd3W50bCj4V0hKoOAfqIy9tof0leNQYekEDxVKir3RTVTJOLij7VAe6fsCNsGhC0JqIkURpMXTCSEA==",
|
"integrity": "sha512-Qb/TCFCldgOy8lZ5uC7nLGdqJwSabkQiYQShmw4jyiPk1pZzaYWTwaYKYP7EgLccWYgZocMrtItrwh683voaww==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"docs",
|
"docs",
|
||||||
"benchmarks"
|
"benchmarks"
|
||||||
|
|
@ -4522,11 +4503,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-storybook": {
|
"node_modules/eslint-plugin-storybook": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-9.0.9.tgz",
|
||||||
"integrity": "sha512-8WHL0K9c2SY3Ks4jEELQGu1jOJT311NqCN+8ppiKxNogzUUrEe0duaeSKCj6QA+IPtzXgHgbW7vVVk/mu3Nkqg==",
|
"integrity": "sha512-IPxl6OfZPQq9R/aAac5F/gKdu/wW83z1HYkEdoLf0MDUgFDt+QMmMUi1jmW7oVt7c8tMaYKCcAVCZ+EC7ZXBug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/utils": "^8.8.1"
|
"@typescript-eslint/utils": "^8.8.1"
|
||||||
},
|
},
|
||||||
|
|
@ -4535,18 +4515,17 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": ">=8",
|
"eslint": ">=8",
|
||||||
"storybook": "^9.0.1"
|
"storybook": "^9.0.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.33.0",
|
"version": "8.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz",
|
||||||
"integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==",
|
"integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.33.0",
|
"@typescript-eslint/types": "8.34.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.33.0"
|
"@typescript-eslint/visitor-keys": "8.34.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|
@ -4557,11 +4536,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/types": {
|
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.33.0",
|
"version": "8.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz",
|
||||||
"integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==",
|
"integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
|
|
@ -4571,16 +4549,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.33.0",
|
"version": "8.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz",
|
||||||
"integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==",
|
"integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.33.0",
|
"@typescript-eslint/project-service": "8.34.0",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.33.0",
|
"@typescript-eslint/tsconfig-utils": "8.34.0",
|
||||||
"@typescript-eslint/types": "8.33.0",
|
"@typescript-eslint/types": "8.34.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.33.0",
|
"@typescript-eslint/visitor-keys": "8.34.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
|
|
@ -4600,16 +4577,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/utils": {
|
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.33.0",
|
"version": "8.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz",
|
||||||
"integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==",
|
"integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.7.0",
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
"@typescript-eslint/scope-manager": "8.33.0",
|
"@typescript-eslint/scope-manager": "8.34.0",
|
||||||
"@typescript-eslint/types": "8.33.0",
|
"@typescript-eslint/types": "8.34.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.33.0"
|
"@typescript-eslint/typescript-estree": "8.34.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|
@ -4624,13 +4600,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.33.0",
|
"version": "8.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz",
|
||||||
"integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==",
|
"integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.33.0",
|
"@typescript-eslint/types": "8.34.0",
|
||||||
"eslint-visitor-keys": "^4.2.0"
|
"eslint-visitor-keys": "^4.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -4642,11 +4617,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-storybook/node_modules/brace-expansion": {
|
"node_modules/eslint-plugin-storybook/node_modules/brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -4656,7 +4630,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"brace-expansion": "^2.0.1"
|
||||||
},
|
},
|
||||||
|
|
@ -4672,7 +4645,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
||||||
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
|
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.12"
|
"node": ">=18.12"
|
||||||
},
|
},
|
||||||
|
|
@ -5951,7 +5923,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
|
||||||
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
|
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
}
|
}
|
||||||
|
|
@ -6219,7 +6190,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||||
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
|
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lower-case": "^2.0.2",
|
"lower-case": "^2.0.2",
|
||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
|
|
@ -6427,7 +6397,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
||||||
"integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
|
"integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"no-case": "^3.0.4",
|
"no-case": "^3.0.4",
|
||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
|
|
@ -7011,7 +6980,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -7021,7 +6989,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
|
|
@ -7362,8 +7329,7 @@
|
||||||
"version": "0.26.0",
|
"version": "0.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/section-matter": {
|
"node_modules/section-matter": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|
@ -7587,11 +7553,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/storybook": {
|
"node_modules/storybook": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/storybook/-/storybook-9.0.9.tgz",
|
||||||
"integrity": "sha512-WE6tTNUl0Xwa/vT4dv2PcNUeujE9pKY4ruGVLsNV7r7lvQla5Jsqi/eWePjU3kuTmi2BcQqLXfapMy0kM3dabw==",
|
"integrity": "sha512-RYDKWD6X4ksYA+ASI1TRt2uB6681vhXGll5ofK9YUA5nrLd4hsp0yanNE2owMtaEhDATutpLKS+/+iFzPU8M2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/global": "^5.0.0",
|
"@storybook/global": "^5.0.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
|
@ -7823,7 +7788,6 @@
|
||||||
"url": "https://opencollective.com/xeho91"
|
"url": "https://opencollective.com/xeho91"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esrap": "1.2.2",
|
"esrap": "1.2.2",
|
||||||
"zimmerframe": "1.1.2"
|
"zimmerframe": "1.1.2"
|
||||||
|
|
@ -7840,7 +7804,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz",
|
||||||
"integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==",
|
"integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15",
|
"@jridgewell/sourcemap-codec": "^1.4.15",
|
||||||
"@types/estree": "^1.0.1"
|
"@types/estree": "^1.0.1"
|
||||||
|
|
@ -8037,7 +8000,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.39.tgz",
|
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.39.tgz",
|
||||||
"integrity": "sha512-NX8a7eSqF1hr6WKArvXr7TV7DeE+y0kDFD7L5JP7TWqlwFidzGKaG415p992MHREiiEWOv2xIWXJ+mlONofs0A==",
|
"integrity": "sha512-NX8a7eSqF1hr6WKArvXr7TV7DeE+y0kDFD7L5JP7TWqlwFidzGKaG415p992MHREiiEWOv2xIWXJ+mlONofs0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dedent-js": "^1.0.1",
|
"dedent-js": "^1.0.1",
|
||||||
"pascal-case": "^3.1.1"
|
"pascal-case": "^3.1.1"
|
||||||
|
|
@ -8237,7 +8199,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
|
||||||
"integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
|
"integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.10"
|
"node": ">=6.10"
|
||||||
}
|
}
|
||||||
|
|
@ -8736,7 +8697,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
|
||||||
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
|
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "(MIT OR CC0-1.0)",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.20"
|
"node": ">=12.20"
|
||||||
},
|
},
|
||||||
|
|
@ -8822,7 +8782,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz",
|
||||||
"integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
|
"integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^8.14.0",
|
"acorn": "^8.14.0",
|
||||||
"webpack-virtual-modules": "^0.6.2"
|
"webpack-virtual-modules": "^0.6.2"
|
||||||
|
|
@ -8995,8 +8954,7 @@
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/whatwg-url": {
|
"node_modules/whatwg-url": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
|
|
|
||||||
13
package.json
13
package.json
|
|
@ -23,10 +23,10 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@musicorum/lastfm": "github:jedmund/lastfm",
|
"@musicorum/lastfm": "github:jedmund/lastfm",
|
||||||
"@poppanator/sveltekit-svg": "^5.0.0-svelte5.4",
|
"@poppanator/sveltekit-svg": "^5.0.0-svelte5.4",
|
||||||
"@storybook/addon-a11y": "^9.0.1",
|
"@storybook/addon-a11y": "^9.0.9",
|
||||||
"@storybook/addon-docs": "^9.0.1",
|
"@storybook/addon-docs": "^9.0.9",
|
||||||
"@storybook/addon-svelte-csf": "^5.0.3",
|
"@storybook/addon-svelte-csf": "^5.0.3",
|
||||||
"@storybook/sveltekit": "^9.0.1",
|
"@storybook/sveltekit": "^9.0.9",
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
||||||
|
|
@ -35,14 +35,14 @@
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-storybook": "^9.0.1",
|
"eslint-plugin-storybook": "^9.0.9",
|
||||||
"eslint-plugin-svelte": "^2.36.0",
|
"eslint-plugin-svelte": "^2.36.0",
|
||||||
"globals": "^15.0.0",
|
"globals": "^15.0.0",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.39",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.1.1",
|
||||||
"prettier-plugin-svelte": "^3.1.2",
|
"prettier-plugin-svelte": "^3.1.2",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"storybook": "^9.0.1",
|
"storybook": "^9.0.9",
|
||||||
"svelte": "^5.0.0-next.1",
|
"svelte": "^5.0.0-next.1",
|
||||||
"svelte-check": "^3.6.0",
|
"svelte-check": "^3.6.0",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.4.1",
|
||||||
|
|
@ -122,6 +122,7 @@
|
||||||
"npm": ">=10.0.0"
|
"npm": ">=10.0.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6"
|
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
||||||
|
"storybook": "$storybook"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
183
src/lib/components/Album.stories.js
Normal file
183
src/lib/components/Album.stories.js
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
import Album from './Album.svelte'
|
||||||
|
|
||||||
|
// Mock album data
|
||||||
|
const mockAlbum = {
|
||||||
|
name: 'In Rainbows',
|
||||||
|
artist: {
|
||||||
|
name: 'Radiohead',
|
||||||
|
mbid: 'a74b1b7f-71a5-4011-9441-d0b5e4122711'
|
||||||
|
},
|
||||||
|
playCount: 156,
|
||||||
|
url: 'https://www.last.fm/music/Radiohead/In+Rainbows',
|
||||||
|
rank: 1,
|
||||||
|
images: {
|
||||||
|
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
large: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
mega: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
default: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
|
},
|
||||||
|
isNowPlaying: false,
|
||||||
|
appleMusicData: {
|
||||||
|
appleMusicId: '1109714933',
|
||||||
|
highResArtwork: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
previewUrl: 'https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125/v4/5c/e8/e3/5ce8e347-3bea-3bb0-0664-a6e1c9125d3a/mzaf_7638610958907470670.plus.aac.p.m4a',
|
||||||
|
genres: ['Alternative', 'Music'],
|
||||||
|
releaseDate: '2007-10-10',
|
||||||
|
trackCount: 10,
|
||||||
|
recordLabel: 'XL Recordings'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockAlbumWithLongName = {
|
||||||
|
...mockAlbum,
|
||||||
|
name: 'The Rise and Fall of Ziggy Stardust and the Spiders from Mars',
|
||||||
|
artist: {
|
||||||
|
name: 'David Bowie',
|
||||||
|
mbid: '5441c29d-3602-4898-b1a1-b77fa23b8e50'
|
||||||
|
},
|
||||||
|
nowPlayingTrack: 'Starman (2012 Remaster)',
|
||||||
|
isNowPlaying: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockAlbumNoPreview = {
|
||||||
|
...mockAlbum,
|
||||||
|
name: 'Unknown Pleasures',
|
||||||
|
artist: {
|
||||||
|
name: 'Joy Division',
|
||||||
|
mbid: '9e1ff9b2-25fc-4c59-b8e8-8b9d9e3d3a2a'
|
||||||
|
},
|
||||||
|
appleMusicData: {
|
||||||
|
...mockAlbum.appleMusicData,
|
||||||
|
previewUrl: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockAlbumNoArtwork = {
|
||||||
|
name: 'Demo Album',
|
||||||
|
artist: {
|
||||||
|
name: 'Unknown Artist',
|
||||||
|
mbid: ''
|
||||||
|
},
|
||||||
|
playCount: 10,
|
||||||
|
url: '#',
|
||||||
|
rank: 1,
|
||||||
|
images: {
|
||||||
|
small: '',
|
||||||
|
medium: '',
|
||||||
|
large: '',
|
||||||
|
extralarge: '',
|
||||||
|
mega: '',
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Album',
|
||||||
|
component: Album,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: 'Album component displaying album artwork, artist info, and playback controls.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: 'centered'
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
album: {
|
||||||
|
control: 'object',
|
||||||
|
description: 'Album data object'
|
||||||
|
},
|
||||||
|
albumId: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Unique identifier for the album'
|
||||||
|
},
|
||||||
|
hoveredAlbumId: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'ID of currently hovered album (for shrink effect)'
|
||||||
|
},
|
||||||
|
onHover: {
|
||||||
|
action: 'hovered',
|
||||||
|
description: 'Callback when album is hovered'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template function to wrap Album in a container
|
||||||
|
const Template = (args) => ({
|
||||||
|
Component: Album,
|
||||||
|
props: args
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Default = {
|
||||||
|
args: {
|
||||||
|
album: mockAlbum
|
||||||
|
},
|
||||||
|
render: Template,
|
||||||
|
decorators: [
|
||||||
|
(story) => ({
|
||||||
|
Component: story,
|
||||||
|
target: document.createElement('div'),
|
||||||
|
props: {
|
||||||
|
style: 'width: 200px; display: block;'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NowPlaying = {
|
||||||
|
args: {
|
||||||
|
album: {
|
||||||
|
...mockAlbum,
|
||||||
|
isNowPlaying: true,
|
||||||
|
nowPlayingTrack: '15 Step'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: Template
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NowPlayingLongTrackName = {
|
||||||
|
args: {
|
||||||
|
album: mockAlbumWithLongName
|
||||||
|
},
|
||||||
|
render: Template
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithoutPreview = {
|
||||||
|
args: {
|
||||||
|
album: mockAlbumNoPreview
|
||||||
|
},
|
||||||
|
render: Template
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithoutArtwork = {
|
||||||
|
args: {
|
||||||
|
album: mockAlbumNoArtwork
|
||||||
|
},
|
||||||
|
render: Template
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Shrunk = {
|
||||||
|
args: {
|
||||||
|
album: mockAlbum,
|
||||||
|
albumId: 'radiohead-in-rainbows',
|
||||||
|
hoveredAlbumId: 'some-other-album'
|
||||||
|
},
|
||||||
|
render: Template
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Interactive = {
|
||||||
|
args: {
|
||||||
|
album: mockAlbum
|
||||||
|
},
|
||||||
|
render: Template,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
story: {
|
||||||
|
inline: false,
|
||||||
|
height: '300px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -87,7 +87,14 @@
|
||||||
|
|
||||||
// Use high-res artwork if available
|
// Use high-res artwork if available
|
||||||
const artworkUrl = $derived(
|
const artworkUrl = $derived(
|
||||||
album?.appleMusicData?.highResArtwork || album?.images.itunes || album?.images.mega || ''
|
album?.appleMusicData?.highResArtwork ||
|
||||||
|
album?.images?.itunes ||
|
||||||
|
album?.images?.mega ||
|
||||||
|
album?.images?.extralarge ||
|
||||||
|
album?.images?.large ||
|
||||||
|
album?.images?.medium ||
|
||||||
|
album?.images?.small ||
|
||||||
|
''
|
||||||
)
|
)
|
||||||
|
|
||||||
const hasPreview = $derived(!!album?.appleMusicData?.previewUrl)
|
const hasPreview = $derived(!!album?.appleMusicData?.previewUrl)
|
||||||
|
|
@ -110,6 +117,17 @@
|
||||||
// Combine initial state with real-time updates
|
// Combine initial state with real-time updates
|
||||||
const isNowPlaying = $derived(realtimeNowPlaying?.isNowPlaying ?? album?.isNowPlaying ?? false)
|
const isNowPlaying = $derived(realtimeNowPlaying?.isNowPlaying ?? album?.isNowPlaying ?? false)
|
||||||
const nowPlayingTrack = $derived(realtimeNowPlaying?.nowPlayingTrack ?? album?.nowPlayingTrack)
|
const nowPlayingTrack = $derived(realtimeNowPlaying?.nowPlayingTrack ?? album?.nowPlayingTrack)
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
$effect(() => {
|
||||||
|
if (album && isNowPlaying) {
|
||||||
|
console.log(`Album "${album.name}" is now playing:`, {
|
||||||
|
fromRealtime: realtimeNowPlaying?.isNowPlaying,
|
||||||
|
fromAlbum: album?.isNowPlaying,
|
||||||
|
track: nowPlayingTrack
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="album">
|
<div class="album">
|
||||||
|
|
@ -136,7 +154,7 @@
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
{#if isNowPlaying}
|
{#if isNowPlaying}
|
||||||
<NowPlaying trackName={nowPlayingTrack !== album.name ? nowPlayingTrack : undefined} />
|
<NowPlaying trackName={nowPlayingTrack} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if hasPreview && (isHovering || isPlaying)}
|
{#if hasPreview && (isHovering || isPlaying)}
|
||||||
<button
|
<button
|
||||||
|
|
@ -194,6 +212,9 @@
|
||||||
.artwork-container {
|
.artwork-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background-color: $grey-5;
|
||||||
|
border-radius: $unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
|
@ -201,8 +222,9 @@
|
||||||
border-radius: $unit;
|
border-radius: $unit;
|
||||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-button {
|
.preview-button {
|
||||||
|
|
|
||||||
8
src/lib/components/AlbumSimple.stories.js
Normal file
8
src/lib/components/AlbumSimple.stories.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import AlbumSimpleStory from './AlbumSimple.stories.svelte'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Album Simple',
|
||||||
|
component: AlbumSimpleStory
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Default = {}
|
||||||
42
src/lib/components/AlbumSimple.stories.svelte
Normal file
42
src/lib/components/AlbumSimple.stories.svelte
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script>
|
||||||
|
import Album from './Album.svelte'
|
||||||
|
|
||||||
|
const album = {
|
||||||
|
name: 'In Rainbows',
|
||||||
|
artist: {
|
||||||
|
name: 'Radiohead',
|
||||||
|
mbid: 'a74b1b7f-71a5-4011-9441-d0b5e4122711'
|
||||||
|
},
|
||||||
|
playCount: 156,
|
||||||
|
url: 'https://www.last.fm/music/Radiohead/In+Rainbows',
|
||||||
|
rank: 1,
|
||||||
|
images: {
|
||||||
|
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
large: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
mega: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
default: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
|
},
|
||||||
|
isNowPlaying: false,
|
||||||
|
appleMusicData: {
|
||||||
|
appleMusicId: '1109714933',
|
||||||
|
highResArtwork: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
previewUrl: 'https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125/v4/5c/e8/e3/5ce8e347-3bea-3bb0-0664-a6e1c9125d3a/mzaf_7638610958907470670.plus.aac.p.m4a',
|
||||||
|
genres: ['Alternative', 'Music'],
|
||||||
|
releaseDate: '2007-10-10',
|
||||||
|
trackCount: 10,
|
||||||
|
recordLabel: 'XL Recordings'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="width: 200px; padding: 20px; background: #f5f5f5;">
|
||||||
|
<Album {album} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from 'svelte'
|
import { onMount, onDestroy } from 'svelte'
|
||||||
import { spring } from 'svelte/motion'
|
import { Spring } from 'svelte/motion'
|
||||||
import { nowPlayingStream } from '$lib/stores/now-playing-stream'
|
import { nowPlayingStream } from '$lib/stores/now-playing-stream'
|
||||||
|
import { albumStream } from '$lib/stores/album-stream'
|
||||||
import AvatarSVG from './AvatarSVG.svelte'
|
import AvatarSVG from './AvatarSVG.svelte'
|
||||||
import AvatarHeadphones from './AvatarHeadphones.svelte'
|
import AvatarHeadphones from './AvatarHeadphones.svelte'
|
||||||
|
import { get } from 'svelte/store'
|
||||||
|
|
||||||
// Props for testing/forcing states
|
// Props for testing/forcing states
|
||||||
let { forcePlayingMusic = false } = $props()
|
let { forcePlayingMusic = false } = $props()
|
||||||
|
|
@ -12,19 +14,23 @@
|
||||||
let isBlinking = $state(false)
|
let isBlinking = $state(false)
|
||||||
let isPlayingMusic = $state(forcePlayingMusic)
|
let isPlayingMusic = $state(forcePlayingMusic)
|
||||||
|
|
||||||
const scale = spring(1, {
|
// Track store subscriptions for debugging
|
||||||
|
let nowPlayingStoreState = $state(null)
|
||||||
|
let albumStoreState = $state(null)
|
||||||
|
|
||||||
|
const scale = new Spring(1, {
|
||||||
stiffness: 0.1,
|
stiffness: 0.1,
|
||||||
damping: 0.125
|
damping: 0.125
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleMouseEnter() {
|
function handleMouseEnter() {
|
||||||
isHovering = true
|
isHovering = true
|
||||||
scale.set(1.25)
|
scale.target = 1.25
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseLeave() {
|
function handleMouseLeave() {
|
||||||
isHovering = false
|
isHovering = false
|
||||||
scale.set(1)
|
scale.target = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
|
|
@ -60,11 +66,42 @@
|
||||||
}
|
}
|
||||||
}, 4000)
|
}, 4000)
|
||||||
|
|
||||||
// Subscribe to now playing updates
|
// Subscribe to now playing updates from both sources
|
||||||
const unsubscribe = nowPlayingStream.subscribe((state) => {
|
const unsubscribeNowPlaying = nowPlayingStream.subscribe((state) => {
|
||||||
|
nowPlayingStoreState = state
|
||||||
// Check if any album is currently playing, unless forced
|
// Check if any album is currently playing, unless forced
|
||||||
if (!forcePlayingMusic) {
|
if (!forcePlayingMusic) {
|
||||||
isPlayingMusic = Array.from(state.updates.values()).some((update) => update.isNowPlaying)
|
const nowPlayingFromStream = Array.from(state.updates.values()).some((update) => update.isNowPlaying)
|
||||||
|
console.log('Avatar - nowPlayingStream update:', {
|
||||||
|
updatesCount: state.updates.size,
|
||||||
|
hasNowPlaying: nowPlayingFromStream
|
||||||
|
})
|
||||||
|
// Don't set to false if we haven't received album data yet
|
||||||
|
if (nowPlayingFromStream || albumStoreState !== null) {
|
||||||
|
isPlayingMusic = nowPlayingFromStream || (albumStoreState?.some(album => album.isNowPlaying) ?? false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Also check the album stream
|
||||||
|
const unsubscribeAlbums = albumStream.subscribe((state) => {
|
||||||
|
albumStoreState = state.albums
|
||||||
|
if (!forcePlayingMusic) {
|
||||||
|
const hasNowPlaying = state.albums.some(album => album.isNowPlaying)
|
||||||
|
|
||||||
|
// Get the current state of nowPlayingStream
|
||||||
|
const nowPlayingState = nowPlayingStoreState || get(nowPlayingStream)
|
||||||
|
const nowPlayingFromStream = Array.from(nowPlayingState.updates.values()).some((update) => update.isNowPlaying)
|
||||||
|
|
||||||
|
console.log('Avatar - albumStream update:', {
|
||||||
|
albumsCount: state.albums.length,
|
||||||
|
hasNowPlayingInAlbums: hasNowPlaying,
|
||||||
|
hasNowPlayingInStream: nowPlayingFromStream,
|
||||||
|
albums: state.albums.map(a => ({ name: a.name, isNowPlaying: a.isNowPlaying }))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update isPlayingMusic based on whether any album is now playing from either source
|
||||||
|
isPlayingMusic = hasNowPlaying || nowPlayingFromStream
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -72,7 +109,8 @@
|
||||||
if (blinkInterval) {
|
if (blinkInterval) {
|
||||||
clearInterval(blinkInterval)
|
clearInterval(blinkInterval)
|
||||||
}
|
}
|
||||||
unsubscribe()
|
unsubscribeNowPlaying()
|
||||||
|
unsubscribeAlbums()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -81,7 +119,7 @@
|
||||||
class="face-container"
|
class="face-container"
|
||||||
onmouseenter={handleMouseEnter}
|
onmouseenter={handleMouseEnter}
|
||||||
onmouseleave={handleMouseLeave}
|
onmouseleave={handleMouseLeave}
|
||||||
style="transform: scale({$scale})"
|
style="transform: scale({scale.current})"
|
||||||
>
|
>
|
||||||
<AvatarSVG>
|
<AvatarSVG>
|
||||||
<!-- Face group -->
|
<!-- Face group -->
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let { trackName }: Props = $props()
|
let { trackName }: Props = $props()
|
||||||
|
|
||||||
|
let textElement: HTMLSpanElement | null = $state(null)
|
||||||
|
let containerElement: HTMLDivElement | null = $state(null)
|
||||||
|
let shouldMarquee = $state(false)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (textElement && containerElement && trackName) {
|
||||||
|
// Check if text overflows
|
||||||
|
const textWidth = textElement.scrollWidth
|
||||||
|
const containerWidth = containerElement.clientWidth
|
||||||
|
shouldMarquee = textWidth > containerWidth
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="now-playing">
|
<div class="now-playing">
|
||||||
|
|
@ -13,7 +26,19 @@
|
||||||
<span class="bar"></span>
|
<span class="bar"></span>
|
||||||
</div>
|
</div>
|
||||||
{#if trackName}
|
{#if trackName}
|
||||||
<span class="track-name">{trackName}</span>
|
<div class="track-name-container" bind:this={containerElement}>
|
||||||
|
<span
|
||||||
|
class="track-name"
|
||||||
|
class:marquee={shouldMarquee}
|
||||||
|
bind:this={textElement}
|
||||||
|
>
|
||||||
|
{trackName}
|
||||||
|
{#if shouldMarquee}
|
||||||
|
<span class="marquee-gap"> </span>
|
||||||
|
{trackName}
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -22,6 +47,8 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: $unit;
|
top: $unit;
|
||||||
left: $unit;
|
left: $unit;
|
||||||
|
right: $unit;
|
||||||
|
max-width: calc(100% - #{$unit * 2});
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: $unit-half;
|
gap: $unit-half;
|
||||||
|
|
@ -33,6 +60,7 @@
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
animation: fadeIn 0.3s ease-out;
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
|
|
@ -85,12 +113,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-name {
|
.track-name-container {
|
||||||
max-width: 150px;
|
max-width: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-name {
|
||||||
|
display: inline-block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: $font-weight-med;
|
font-weight: $font-weight-med;
|
||||||
|
|
||||||
|
&.marquee {
|
||||||
|
animation: marquee 8s linear infinite;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.marquee-gap {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes marquee {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include breakpoint('phone') {
|
@include breakpoint('phone') {
|
||||||
|
|
@ -99,7 +152,7 @@
|
||||||
padding: $unit-fourth $unit-half;
|
padding: $unit-fourth $unit-half;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-name {
|
.track-name-container {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
201
src/lib/components/RecentAlbums.stories.js
Normal file
201
src/lib/components/RecentAlbums.stories.js
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
import RecentAlbums from './RecentAlbums.svelte'
|
||||||
|
|
||||||
|
// Mock albums data
|
||||||
|
const mockAlbums = [
|
||||||
|
{
|
||||||
|
name: 'In Rainbows',
|
||||||
|
artist: {
|
||||||
|
name: 'Radiohead',
|
||||||
|
mbid: 'a74b1b7f-71a5-4011-9441-d0b5e4122711'
|
||||||
|
},
|
||||||
|
playCount: 156,
|
||||||
|
url: 'https://www.last.fm/music/Radiohead/In+Rainbows',
|
||||||
|
rank: 1,
|
||||||
|
images: {
|
||||||
|
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
large: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
mega: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
default: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
|
},
|
||||||
|
isNowPlaying: false,
|
||||||
|
appleMusicData: {
|
||||||
|
appleMusicId: '1109714933',
|
||||||
|
highResArtwork: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
previewUrl: 'https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125/v4/5c/e8/e3/5ce8e347-3bea-3bb0-0664-a6e1c9125d3a/mzaf_7638610958907470670.plus.aac.p.m4a',
|
||||||
|
genres: ['Alternative', 'Music'],
|
||||||
|
releaseDate: '2007-10-10',
|
||||||
|
trackCount: 10,
|
||||||
|
recordLabel: 'XL Recordings'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'OK Computer',
|
||||||
|
artist: {
|
||||||
|
name: 'Radiohead',
|
||||||
|
mbid: 'a74b1b7f-71a5-4011-9441-d0b5e4122711'
|
||||||
|
},
|
||||||
|
playCount: 234,
|
||||||
|
url: 'https://www.last.fm/music/Radiohead/OK+Computer',
|
||||||
|
rank: 2,
|
||||||
|
images: {
|
||||||
|
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
large: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
mega: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
default: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
|
},
|
||||||
|
isNowPlaying: true,
|
||||||
|
nowPlayingTrack: 'Paranoid Android',
|
||||||
|
appleMusicData: {
|
||||||
|
appleMusicId: '1097861387',
|
||||||
|
highResArtwork: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
previewUrl: 'https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125/v4/65/f2/85/65f285d2-5a99-f502-89f8-ca2c4da24d19/mzaf_1760708625972666865.plus.aac.p.m4a'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'The Dark Side of the Moon',
|
||||||
|
artist: {
|
||||||
|
name: 'Pink Floyd',
|
||||||
|
mbid: '83d91898-7763-47d7-b03b-b92132375c47'
|
||||||
|
},
|
||||||
|
playCount: 189,
|
||||||
|
url: 'https://www.last.fm/music/Pink+Floyd/The+Dark+Side+of+the+Moon',
|
||||||
|
rank: 3,
|
||||||
|
images: {
|
||||||
|
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
large: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
mega: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
default: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
|
},
|
||||||
|
appleMusicData: {
|
||||||
|
appleMusicId: '1065973699',
|
||||||
|
highResArtwork: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
previewUrl: 'https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview115/v4/57/15/fb/5715fb67-0424-8e6e-a1ff-2c0cf09e4bdc/mzaf_3641989451682986919.plus.aac.p.m4a'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Unknown Pleasures',
|
||||||
|
artist: {
|
||||||
|
name: 'Joy Division',
|
||||||
|
mbid: '9e1ff9b2-25fc-4c59-b8e8-8b9d9e3d3a2a'
|
||||||
|
},
|
||||||
|
playCount: 112,
|
||||||
|
url: 'https://www.last.fm/music/Joy+Division/Unknown+Pleasures',
|
||||||
|
rank: 4,
|
||||||
|
images: {
|
||||||
|
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
large: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
mega: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
||||||
|
default: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
|
},
|
||||||
|
appleMusicData: {
|
||||||
|
appleMusicId: '659989492',
|
||||||
|
highResArtwork: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const mockAlbumsWithLongNames = [
|
||||||
|
{
|
||||||
|
...mockAlbums[0],
|
||||||
|
name: 'The Rise and Fall of Ziggy Stardust and the Spiders from Mars',
|
||||||
|
artist: {
|
||||||
|
name: 'David Bowie',
|
||||||
|
mbid: '5441c29d-3602-4898-b1a1-b77fa23b8e50'
|
||||||
|
},
|
||||||
|
isNowPlaying: true,
|
||||||
|
nowPlayingTrack: 'Starman (2012 Remaster) - Extended Version with Additional Notes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...mockAlbums[1],
|
||||||
|
name: '!Despertate!',
|
||||||
|
artist: {
|
||||||
|
name: '!deladap',
|
||||||
|
mbid: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...mockAlbums[2],
|
||||||
|
name: ';PEBFG',
|
||||||
|
artist: {
|
||||||
|
name: 'Various Artists',
|
||||||
|
mbid: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mockAlbums[3]
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/RecentAlbums',
|
||||||
|
component: RecentAlbums,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: 'Displays a grid of recent albums with hover effects and now playing indicators.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: 'fullscreen'
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
albums: {
|
||||||
|
control: 'object',
|
||||||
|
description: 'Array of album objects to display'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => ({
|
||||||
|
Component: Story,
|
||||||
|
props: {
|
||||||
|
style: 'padding: 40px; background: #f9f9f9; min-height: 400px;'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Default = {
|
||||||
|
args: {
|
||||||
|
albums: mockAlbums
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithNowPlaying = {
|
||||||
|
args: {
|
||||||
|
albums: mockAlbums
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithLongTitles = {
|
||||||
|
args: {
|
||||||
|
albums: mockAlbumsWithLongNames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Empty = {
|
||||||
|
args: {
|
||||||
|
albums: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SingleAlbum = {
|
||||||
|
args: {
|
||||||
|
albums: [mockAlbums[0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MobileView = {
|
||||||
|
args: {
|
||||||
|
albums: mockAlbums
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
viewport: {
|
||||||
|
defaultViewport: 'mobile1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import { ApiRateLimiter } from './rate-limiter'
|
||||||
|
|
||||||
const APPLE_MUSIC_API_BASE = 'https://api.music.apple.com/v1'
|
const APPLE_MUSIC_API_BASE = 'https://api.music.apple.com/v1'
|
||||||
const DEFAULT_STOREFRONT = 'us' // Default to US storefront
|
const DEFAULT_STOREFRONT = 'us' // Default to US storefront
|
||||||
|
const JAPANESE_STOREFRONT = 'jp' // Japanese storefront
|
||||||
const RATE_LIMIT_DELAY = 200 // 200ms between requests to stay well under 3000/hour
|
const RATE_LIMIT_DELAY = 200 // 200ms between requests to stay well under 3000/hour
|
||||||
|
|
||||||
let lastRequestTime = 0
|
let lastRequestTime = 0
|
||||||
|
|
@ -88,10 +89,11 @@ async function makeAppleMusicRequest<T>(endpoint: string, identifier?: string):
|
||||||
|
|
||||||
export async function searchAlbums(
|
export async function searchAlbums(
|
||||||
query: string,
|
query: string,
|
||||||
limit: number = 10
|
limit: number = 10,
|
||||||
|
storefront: string = DEFAULT_STOREFRONT
|
||||||
): Promise<AppleMusicSearchResponse> {
|
): Promise<AppleMusicSearchResponse> {
|
||||||
const encodedQuery = encodeURIComponent(query)
|
const encodedQuery = encodeURIComponent(query)
|
||||||
const endpoint = `/catalog/${DEFAULT_STOREFRONT}/search?types=albums&term=${encodedQuery}&limit=${limit}`
|
const endpoint = `/catalog/${storefront}/search?types=albums&term=${encodedQuery}&limit=${limit}`
|
||||||
|
|
||||||
return makeAppleMusicRequest<AppleMusicSearchResponse>(endpoint, query)
|
return makeAppleMusicRequest<AppleMusicSearchResponse>(endpoint, query)
|
||||||
}
|
}
|
||||||
|
|
@ -160,16 +162,21 @@ export async function findAlbum(artist: string, album: string): Promise<AppleMus
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Helper function to remove leading punctuation
|
||||||
const searchQuery = `${artist} ${album}`
|
function removeLeadingPunctuation(str: string): string {
|
||||||
const response = await searchAlbums(searchQuery, 5)
|
// Remove leading punctuation marks like ; ! ? . , : ' " etc.
|
||||||
|
return str.replace(/^[^\w\s]+/, '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Search results for "${searchQuery}":`, JSON.stringify(response, null, 2))
|
// Helper function to perform the album search and matching
|
||||||
|
async function searchAndMatch(searchAlbum: string, storefront: string = DEFAULT_STOREFRONT): Promise<{album: AppleMusicAlbum, storefront: string} | null> {
|
||||||
|
const searchQuery = `${artist} ${searchAlbum}`
|
||||||
|
const response = await searchAlbums(searchQuery, 5, storefront)
|
||||||
|
|
||||||
|
console.log(`Search results for "${searchQuery}" in ${storefront} storefront:`, JSON.stringify(response, null, 2))
|
||||||
|
|
||||||
if (!response.results?.albums?.data?.length) {
|
if (!response.results?.albums?.data?.length) {
|
||||||
console.log('No albums found in search results')
|
console.log(`No albums found in ${storefront} storefront`)
|
||||||
// Cache this as not found for 1 hour
|
|
||||||
await rateLimiter.cacheNotFound(identifier, 3600)
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,30 +184,71 @@ export async function findAlbum(artist: string, album: string): Promise<AppleMus
|
||||||
const albums = response.results.albums.data
|
const albums = response.results.albums.data
|
||||||
console.log(`Found ${albums.length} albums`)
|
console.log(`Found ${albums.length} albums`)
|
||||||
|
|
||||||
// First try exact match
|
// First try exact match with original album name
|
||||||
let match = albums.find(
|
let match = albums.find(
|
||||||
(a) =>
|
(a) =>
|
||||||
a.attributes?.name?.toLowerCase() === album.toLowerCase() &&
|
a.attributes?.name?.toLowerCase() === album.toLowerCase() &&
|
||||||
a.attributes?.artistName?.toLowerCase() === artist.toLowerCase()
|
a.attributes?.artistName?.toLowerCase() === artist.toLowerCase()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// If no exact match, try matching with the search term we used
|
||||||
|
if (!match && searchAlbum !== album) {
|
||||||
|
match = albums.find(
|
||||||
|
(a) =>
|
||||||
|
a.attributes?.name?.toLowerCase() === searchAlbum.toLowerCase() &&
|
||||||
|
a.attributes?.artistName?.toLowerCase() === artist.toLowerCase()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// If no exact match, try partial match
|
// If no exact match, try partial match
|
||||||
if (!match) {
|
if (!match) {
|
||||||
match = albums.find(
|
match = albums.find(
|
||||||
(a) =>
|
(a) =>
|
||||||
a.attributes?.name?.toLowerCase().includes(album.toLowerCase()) &&
|
a.attributes?.name?.toLowerCase().includes(searchAlbum.toLowerCase()) &&
|
||||||
a.attributes?.artistName?.toLowerCase().includes(artist.toLowerCase())
|
a.attributes?.artistName?.toLowerCase().includes(artist.toLowerCase())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return match ? {album: match, storefront} : null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// First try with the original album name in US storefront
|
||||||
|
let result = await searchAndMatch(album)
|
||||||
|
|
||||||
|
// If no match, try Japanese storefront
|
||||||
|
if (!result) {
|
||||||
|
console.log(`No match found in US storefront, trying Japanese storefront`)
|
||||||
|
result = await searchAndMatch(album, JAPANESE_STOREFRONT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no match and album starts with punctuation, try without it in both storefronts
|
||||||
|
if (!result) {
|
||||||
|
const cleanedAlbum = removeLeadingPunctuation(album)
|
||||||
|
if (cleanedAlbum !== album && cleanedAlbum.length > 0) {
|
||||||
|
console.log(`No match found for "${album}", trying without leading punctuation: "${cleanedAlbum}"`)
|
||||||
|
result = await searchAndMatch(cleanedAlbum)
|
||||||
|
|
||||||
|
// Also try Japanese storefront with cleaned album name
|
||||||
|
if (!result) {
|
||||||
|
console.log(`Still no match, trying Japanese storefront with cleaned name`)
|
||||||
|
result = await searchAndMatch(cleanedAlbum, JAPANESE_STOREFRONT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If still no match, cache as not found
|
// If still no match, cache as not found
|
||||||
if (!match) {
|
if (!result) {
|
||||||
await rateLimiter.cacheNotFound(identifier, 3600)
|
await rateLimiter.cacheNotFound(identifier, 3600)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the storefront information with the album
|
||||||
|
const matchedAlbum = result.album as any
|
||||||
|
matchedAlbum._storefront = result.storefront
|
||||||
|
|
||||||
// Return the match
|
// Return the match
|
||||||
return match
|
return result.album
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to find album "${album}" by "${artist}":`, error)
|
console.error(`Failed to find album "${album}" by "${artist}":`, error)
|
||||||
// Don't cache as not found on error - might be temporary
|
// Don't cache as not found on error - might be temporary
|
||||||
|
|
@ -219,8 +267,11 @@ export async function transformAlbumData(appleMusicAlbum: AppleMusicAlbum) {
|
||||||
// Always fetch tracks to get preview URLs
|
// Always fetch tracks to get preview URLs
|
||||||
if (appleMusicAlbum.id) {
|
if (appleMusicAlbum.id) {
|
||||||
try {
|
try {
|
||||||
|
// Determine which storefront to use
|
||||||
|
const storefront = (appleMusicAlbum as any)._storefront || DEFAULT_STOREFRONT
|
||||||
|
|
||||||
// Fetch album details with tracks
|
// Fetch album details with tracks
|
||||||
const endpoint = `/catalog/${DEFAULT_STOREFRONT}/albums/${appleMusicAlbum.id}?include=tracks`
|
const endpoint = `/catalog/${storefront}/albums/${appleMusicAlbum.id}?include=tracks`
|
||||||
const response = await makeAppleMusicRequest<{
|
const response = await makeAppleMusicRequest<{
|
||||||
data: AppleMusicAlbum[]
|
data: AppleMusicAlbum[]
|
||||||
included?: AppleMusicTrack[]
|
included?: AppleMusicTrack[]
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,13 @@ function createAlbumStream() {
|
||||||
function connect() {
|
function connect() {
|
||||||
if (!browser || eventSource?.readyState === EventSource.OPEN) return
|
if (!browser || eventSource?.readyState === EventSource.OPEN) return
|
||||||
|
|
||||||
|
// Don't connect in Storybook
|
||||||
|
if (typeof window !== 'undefined' && window.parent !== window) {
|
||||||
|
// We're in an iframe, likely Storybook
|
||||||
|
console.log('Album stream disabled in Storybook')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up existing connection
|
// Clean up existing connection
|
||||||
disconnect()
|
disconnect()
|
||||||
|
|
||||||
|
|
@ -36,6 +43,11 @@ function createAlbumStream() {
|
||||||
eventSource.addEventListener('albums', (event) => {
|
eventSource.addEventListener('albums', (event) => {
|
||||||
try {
|
try {
|
||||||
const albums: Album[] = JSON.parse(event.data)
|
const albums: Album[] = JSON.parse(event.data)
|
||||||
|
const nowPlayingAlbum = albums.find(a => a.isNowPlaying)
|
||||||
|
console.log('Album stream received albums:', {
|
||||||
|
totalAlbums: albums.length,
|
||||||
|
nowPlayingAlbum: nowPlayingAlbum ? `${nowPlayingAlbum.artist.name} - ${nowPlayingAlbum.name}` : 'none'
|
||||||
|
})
|
||||||
update((state) => ({
|
update((state) => ({
|
||||||
...state,
|
...state,
|
||||||
albums,
|
albums,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,13 @@ function createNowPlayingStream() {
|
||||||
function connect() {
|
function connect() {
|
||||||
if (!browser || eventSource?.readyState === EventSource.OPEN) return
|
if (!browser || eventSource?.readyState === EventSource.OPEN) return
|
||||||
|
|
||||||
|
// Don't connect in Storybook
|
||||||
|
if (typeof window !== 'undefined' && window.parent !== window) {
|
||||||
|
// We're in an iframe, likely Storybook
|
||||||
|
console.log('Now Playing stream disabled in Storybook')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up existing connection
|
// Clean up existing connection
|
||||||
disconnect()
|
disconnect()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,12 +57,26 @@ export const GET: RequestHandler = async ({ request }) => {
|
||||||
// Fetch full album data
|
// Fetch full album data
|
||||||
const albums = await getRecentAlbums(client)
|
const albums = await getRecentAlbums(client)
|
||||||
|
|
||||||
// Enrich albums with additional info
|
// Update recentTracks for duration-based now playing detection
|
||||||
|
await getNowPlayingAlbums(client) // This populates recentTracks
|
||||||
|
|
||||||
|
// Enrich albums with additional info and check now playing status
|
||||||
const enrichedAlbums = await Promise.all(
|
const enrichedAlbums = await Promise.all(
|
||||||
albums.map(async (album) => {
|
albums.map(async (album) => {
|
||||||
try {
|
try {
|
||||||
const enriched = await enrichAlbumWithInfo(client, album)
|
const enriched = await enrichAlbumWithInfo(client, album)
|
||||||
return await searchAppleMusicForAlbum(enriched)
|
const withAppleMusic = await searchAppleMusicForAlbum(enriched)
|
||||||
|
|
||||||
|
// Check if this album is currently playing using duration-based detection
|
||||||
|
if (withAppleMusic.appleMusicData?.tracks && !withAppleMusic.isNowPlaying) {
|
||||||
|
const nowPlayingCheck = checkWithTracks(withAppleMusic.name, withAppleMusic.appleMusicData.tracks)
|
||||||
|
if (nowPlayingCheck?.isNowPlaying) {
|
||||||
|
withAppleMusic.isNowPlaying = true
|
||||||
|
withAppleMusic.nowPlayingTrack = nowPlayingCheck.nowPlayingTrack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return withAppleMusic
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error enriching album ${album.name}:`, error)
|
console.error(`Error enriching album ${album.name}:`, error)
|
||||||
return album
|
return album
|
||||||
|
|
@ -70,33 +84,85 @@ export const GET: RequestHandler = async ({ request }) => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if album order has changed
|
// Ensure only one album is marked as now playing in the enriched albums
|
||||||
|
const nowPlayingCount = enrichedAlbums.filter(a => a.isNowPlaying).length
|
||||||
|
if (nowPlayingCount > 1) {
|
||||||
|
console.log(`Multiple enriched albums marked as now playing (${nowPlayingCount}), keeping only the most recent one`)
|
||||||
|
|
||||||
|
// The albums are already in order from most recent to oldest
|
||||||
|
// So we keep the first now playing album and mark others as not playing
|
||||||
|
let foundFirst = false
|
||||||
|
enrichedAlbums.forEach((album, index) => {
|
||||||
|
if (album.isNowPlaying) {
|
||||||
|
if (foundFirst) {
|
||||||
|
console.log(`Marking album "${album.name}" at position ${index} as not playing`)
|
||||||
|
album.isNowPlaying = false
|
||||||
|
album.nowPlayingTrack = undefined
|
||||||
|
} else {
|
||||||
|
console.log(`Keeping album "${album.name}" at position ${index} as now playing`)
|
||||||
|
foundFirst = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if album order has changed or now playing status changed
|
||||||
const currentAlbumOrder = enrichedAlbums.map(a => `${a.artist.name}:${a.name}`)
|
const currentAlbumOrder = enrichedAlbums.map(a => `${a.artist.name}:${a.name}`)
|
||||||
const albumOrderChanged = JSON.stringify(currentAlbumOrder) !== JSON.stringify(lastAlbumOrder)
|
const albumOrderChanged = JSON.stringify(currentAlbumOrder) !== JSON.stringify(lastAlbumOrder)
|
||||||
|
|
||||||
if (albumOrderChanged) {
|
// Also check if any now playing status changed
|
||||||
|
let nowPlayingChanged = false
|
||||||
|
for (const album of enrichedAlbums) {
|
||||||
|
const key = `${album.artist.name}:${album.name}`
|
||||||
|
const lastState = lastNowPlayingState.get(key)
|
||||||
|
if (album.isNowPlaying !== (lastState?.isPlaying || false) ||
|
||||||
|
(album.isNowPlaying && album.nowPlayingTrack !== lastState?.track)) {
|
||||||
|
nowPlayingChanged = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (albumOrderChanged || nowPlayingChanged) {
|
||||||
lastAlbumOrder = currentAlbumOrder
|
lastAlbumOrder = currentAlbumOrder
|
||||||
|
|
||||||
|
// Update now playing state
|
||||||
|
for (const album of enrichedAlbums) {
|
||||||
|
const key = `${album.artist.name}:${album.name}`
|
||||||
|
lastNowPlayingState.set(key, {
|
||||||
|
isPlaying: album.isNowPlaying || false,
|
||||||
|
track: album.nowPlayingTrack
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Send full album update
|
// Send full album update
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.stringify(enrichedAlbums)
|
const data = JSON.stringify(enrichedAlbums)
|
||||||
controller.enqueue(encoder.encode(`event: albums\ndata: ${data}\n\n`))
|
controller.enqueue(encoder.encode(`event: albums\ndata: ${data}\n\n`))
|
||||||
|
const nowPlayingAlbum = enrichedAlbums.find(a => a.isNowPlaying)
|
||||||
|
console.log('Sent album update with now playing status:', {
|
||||||
|
totalAlbums: enrichedAlbums.length,
|
||||||
|
nowPlayingAlbum: nowPlayingAlbum ? `${nowPlayingAlbum.artist.name} - ${nowPlayingAlbum.name}` : 'none'
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
isClosed = true
|
isClosed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now playing updates
|
// Send now playing updates for albums not in the recent list
|
||||||
const nowPlayingAlbums = await getNowPlayingAlbums(client)
|
const nowPlayingAlbums = await getNowPlayingAlbums(client)
|
||||||
const updates: NowPlayingUpdate[] = []
|
const updates: NowPlayingUpdate[] = []
|
||||||
const currentAlbums = new Set<string>()
|
|
||||||
|
|
||||||
// Check for changes
|
// Only send now playing updates for albums that aren't in the recent albums list
|
||||||
|
// (Recent albums already have their now playing status included)
|
||||||
for (const album of nowPlayingAlbums) {
|
for (const album of nowPlayingAlbums) {
|
||||||
const key = `${album.artistName}:${album.albumName}`
|
const isInRecentAlbums = enrichedAlbums.some(
|
||||||
currentAlbums.add(key)
|
a => a.artist.name === album.artistName && a.name === album.albumName
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isInRecentAlbums) {
|
||||||
|
const key = `${album.artistName}:${album.albumName}`
|
||||||
const lastState = lastNowPlayingState.get(key)
|
const lastState = lastNowPlayingState.get(key)
|
||||||
const wasPlaying = lastState?.isPlaying || false
|
const wasPlaying = lastState?.isPlaying || false
|
||||||
const lastTrack = lastState?.track
|
const lastTrack = lastState?.track
|
||||||
|
|
@ -108,7 +174,7 @@ export const GET: RequestHandler = async ({ request }) => {
|
||||||
) {
|
) {
|
||||||
updates.push(album)
|
updates.push(album)
|
||||||
console.log(
|
console.log(
|
||||||
`Update for ${album.albumName}: playing=${album.isNowPlaying}, track=${album.nowPlayingTrack}`
|
`Now playing update for non-recent album ${album.albumName}: playing=${album.isNowPlaying}, track=${album.nowPlayingTrack}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,19 +183,6 @@ export const GET: RequestHandler = async ({ request }) => {
|
||||||
track: album.nowPlayingTrack
|
track: album.nowPlayingTrack
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for albums that were in the list but aren't anymore (stopped playing)
|
|
||||||
for (const [key, state] of lastNowPlayingState.entries()) {
|
|
||||||
if (!currentAlbums.has(key) && state.isPlaying) {
|
|
||||||
const [artistName, albumName] = key.split(':')
|
|
||||||
updates.push({
|
|
||||||
albumName,
|
|
||||||
artistName,
|
|
||||||
isNowPlaying: false
|
|
||||||
})
|
|
||||||
console.log(`Album no longer in recent: ${albumName}`)
|
|
||||||
lastNowPlayingState.delete(key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if controller is still open before sending
|
// Check if controller is still open before sending
|
||||||
|
|
@ -221,10 +274,19 @@ async function getNowPlayingAlbums(client: LastClient): Promise<NowPlayingUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
const albums: Map<string, NowPlayingUpdate> = new Map()
|
const albums: Map<string, NowPlayingUpdate> = new Map()
|
||||||
|
let hasOfficialNowPlaying = false
|
||||||
|
|
||||||
// Clear old tracks and collect new track play information
|
// Clear old tracks and collect new track play information
|
||||||
recentTracks = []
|
recentTracks = []
|
||||||
|
|
||||||
|
// First pass: check if Last.fm reports any track as now playing
|
||||||
|
for (const track of recentTracksResponse.tracks) {
|
||||||
|
if (track.nowPlaying) {
|
||||||
|
hasOfficialNowPlaying = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const track of recentTracksResponse.tracks) {
|
for (const track of recentTracksResponse.tracks) {
|
||||||
// Store track play information
|
// Store track play information
|
||||||
if (track.date) {
|
if (track.date) {
|
||||||
|
|
@ -245,8 +307,8 @@ async function getNowPlayingAlbums(client: LastClient): Promise<NowPlayingUpdate
|
||||||
nowPlayingTrack: track.nowPlaying ? track.name : undefined
|
nowPlayingTrack: track.nowPlaying ? track.name : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not marked as now playing by Last.fm, check with duration-based detection
|
// Only use duration-based detection if Last.fm doesn't report any now playing
|
||||||
if (!album.isNowPlaying) {
|
if (!album.isNowPlaying && !hasOfficialNowPlaying) {
|
||||||
const updatedStatus = await checkNowPlayingWithDuration(album.albumName, album.artistName)
|
const updatedStatus = await checkNowPlayingWithDuration(album.albumName, album.artistName)
|
||||||
if (updatedStatus) {
|
if (updatedStatus) {
|
||||||
album.isNowPlaying = updatedStatus.isNowPlaying
|
album.isNowPlaying = updatedStatus.isNowPlaying
|
||||||
|
|
@ -258,6 +320,42 @@ async function getNowPlayingAlbums(client: LastClient): Promise<NowPlayingUpdate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure only one album is marked as now playing - keep the most recent one
|
||||||
|
const nowPlayingAlbums = Array.from(albums.values()).filter(a => a.isNowPlaying)
|
||||||
|
if (nowPlayingAlbums.length > 1) {
|
||||||
|
console.log(`Multiple albums marked as now playing (${nowPlayingAlbums.length}), keeping only the most recent one`)
|
||||||
|
|
||||||
|
// Find the most recent track
|
||||||
|
let mostRecentTime = new Date(0)
|
||||||
|
let mostRecentAlbum = nowPlayingAlbums[0]
|
||||||
|
|
||||||
|
for (const album of nowPlayingAlbums) {
|
||||||
|
// Find the most recent track for this album
|
||||||
|
const albumTracks = recentTracks.filter(t => t.albumName === album.albumName)
|
||||||
|
if (albumTracks.length > 0) {
|
||||||
|
const latestTrack = albumTracks.reduce((latest, track) =>
|
||||||
|
track.scrobbleTime > latest.scrobbleTime ? track : latest
|
||||||
|
)
|
||||||
|
if (latestTrack.scrobbleTime > mostRecentTime) {
|
||||||
|
mostRecentTime = latestTrack.scrobbleTime
|
||||||
|
mostRecentAlbum = album
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all others as not playing
|
||||||
|
nowPlayingAlbums.forEach(album => {
|
||||||
|
if (album !== mostRecentAlbum) {
|
||||||
|
const key = `${album.artistName}:${album.albumName}`
|
||||||
|
albums.set(key, {
|
||||||
|
...album,
|
||||||
|
isNowPlaying: false,
|
||||||
|
nowPlayingTrack: undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return Array.from(albums.values())
|
return Array.from(albums.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue