Ran linter
This commit is contained in:
parent
723f7acbc1
commit
4aaf33f19e
18 changed files with 353 additions and 298 deletions
256
package.json
256
package.json
|
|
@ -1,130 +1,130 @@
|
||||||
{
|
{
|
||||||
"name": "jedmund-svelte",
|
"name": "jedmund-svelte",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"start": "node build",
|
"start": "node build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"lint": "prettier --check . && eslint .",
|
"lint": "prettier --check . && eslint .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"db:migrate": "prisma migrate dev",
|
"db:migrate": "prisma migrate dev",
|
||||||
"db:seed": "prisma db seed",
|
"db:seed": "prisma db seed",
|
||||||
"db:studio": "prisma studio",
|
"db:studio": "prisma studio",
|
||||||
"db:init": "tsx scripts/init-db.ts",
|
"db:init": "tsx scripts/init-db.ts",
|
||||||
"db:deploy": "prisma migrate deploy",
|
"db:deploy": "prisma migrate deploy",
|
||||||
"setup:local": "./scripts/setup-local.sh",
|
"setup:local": "./scripts/setup-local.sh",
|
||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "storybook dev -p 6006",
|
||||||
"build-storybook": "storybook build"
|
"build-storybook": "storybook build"
|
||||||
},
|
},
|
||||||
"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.9",
|
"@storybook/addon-a11y": "^9.0.9",
|
||||||
"@storybook/addon-docs": "^9.0.9",
|
"@storybook/addon-docs": "^9.0.9",
|
||||||
"@storybook/addon-svelte-csf": "^5.0.3",
|
"@storybook/addon-svelte-csf": "^5.0.3",
|
||||||
"@storybook/sveltekit": "^9.0.9",
|
"@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",
|
||||||
"@types/eslint": "^8.56.7",
|
"@types/eslint": "^8.56.7",
|
||||||
"@types/node": "^22.0.2",
|
"@types/node": "^22.0.2",
|
||||||
"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.9",
|
"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.9",
|
"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",
|
||||||
"tsx": "^4.19.4",
|
"tsx": "^4.19.4",
|
||||||
"typescript": "^5.5.3",
|
"typescript": "^5.5.3",
|
||||||
"typescript-eslint": "^8.0.0-alpha.20",
|
"typescript-eslint": "^8.0.0-alpha.20",
|
||||||
"vite": "^5.0.3"
|
"vite": "^5.0.3"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aarkue/tiptap-math-extension": "^1.3.6",
|
"@aarkue/tiptap-math-extension": "^1.3.6",
|
||||||
"@prisma/client": "^6.8.2",
|
"@prisma/client": "^6.8.2",
|
||||||
"@sveltejs/adapter-node": "^5.2.0",
|
"@sveltejs/adapter-node": "^5.2.0",
|
||||||
"@tiptap/core": "^2.12.0",
|
"@tiptap/core": "^2.12.0",
|
||||||
"@tiptap/extension-bubble-menu": "^2.12.0",
|
"@tiptap/extension-bubble-menu": "^2.12.0",
|
||||||
"@tiptap/extension-character-count": "^2.12.0",
|
"@tiptap/extension-character-count": "^2.12.0",
|
||||||
"@tiptap/extension-code-block-lowlight": "^2.12.0",
|
"@tiptap/extension-code-block-lowlight": "^2.12.0",
|
||||||
"@tiptap/extension-color": "^2.12.0",
|
"@tiptap/extension-color": "^2.12.0",
|
||||||
"@tiptap/extension-floating-menu": "^2.12.0",
|
"@tiptap/extension-floating-menu": "^2.12.0",
|
||||||
"@tiptap/extension-highlight": "^2.12.0",
|
"@tiptap/extension-highlight": "^2.12.0",
|
||||||
"@tiptap/extension-image": "^2.12.0",
|
"@tiptap/extension-image": "^2.12.0",
|
||||||
"@tiptap/extension-link": "^2.12.0",
|
"@tiptap/extension-link": "^2.12.0",
|
||||||
"@tiptap/extension-placeholder": "^2.12.0",
|
"@tiptap/extension-placeholder": "^2.12.0",
|
||||||
"@tiptap/extension-subscript": "^2.12.0",
|
"@tiptap/extension-subscript": "^2.12.0",
|
||||||
"@tiptap/extension-superscript": "^2.12.0",
|
"@tiptap/extension-superscript": "^2.12.0",
|
||||||
"@tiptap/extension-table": "^2.12.0",
|
"@tiptap/extension-table": "^2.12.0",
|
||||||
"@tiptap/extension-table-header": "^2.12.0",
|
"@tiptap/extension-table-header": "^2.12.0",
|
||||||
"@tiptap/extension-table-row": "^2.12.0",
|
"@tiptap/extension-table-row": "^2.12.0",
|
||||||
"@tiptap/extension-task-item": "^2.12.0",
|
"@tiptap/extension-task-item": "^2.12.0",
|
||||||
"@tiptap/extension-task-list": "^2.12.0",
|
"@tiptap/extension-task-list": "^2.12.0",
|
||||||
"@tiptap/extension-text": "^2.12.0",
|
"@tiptap/extension-text": "^2.12.0",
|
||||||
"@tiptap/extension-text-align": "^2.12.0",
|
"@tiptap/extension-text-align": "^2.12.0",
|
||||||
"@tiptap/extension-text-style": "^2.12.0",
|
"@tiptap/extension-text-style": "^2.12.0",
|
||||||
"@tiptap/extension-typography": "^2.12.0",
|
"@tiptap/extension-typography": "^2.12.0",
|
||||||
"@tiptap/extension-underline": "^2.12.0",
|
"@tiptap/extension-underline": "^2.12.0",
|
||||||
"@tiptap/pm": "^2.12.0",
|
"@tiptap/pm": "^2.12.0",
|
||||||
"@tiptap/starter-kit": "^2.12.0",
|
"@tiptap/starter-kit": "^2.12.0",
|
||||||
"@tiptap/suggestion": "^2.12.0",
|
"@tiptap/suggestion": "^2.12.0",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/redis": "^4.0.10",
|
"@types/redis": "^4.0.10",
|
||||||
"@types/steamapi": "^2.2.5",
|
"@types/steamapi": "^2.2.5",
|
||||||
"cloudinary": "^2.6.1",
|
"cloudinary": "^2.6.1",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"giantbombing-api": "^1.0.4",
|
"giantbombing-api": "^1.0.4",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"ioredis": "^5.4.1",
|
"ioredis": "^5.4.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"katex": "^0.16.22",
|
"katex": "^0.16.22",
|
||||||
"lowlight": "^3.3.0",
|
"lowlight": "^3.3.0",
|
||||||
"lucide-svelte": "^0.511.0",
|
"lucide-svelte": "^0.511.0",
|
||||||
"marked": "^15.0.12",
|
"marked": "^15.0.12",
|
||||||
"multer": "^2.0.0",
|
"multer": "^2.0.0",
|
||||||
"node-itunes-search": "^1.2.3",
|
"node-itunes-search": "^1.2.3",
|
||||||
"prisma": "^6.8.2",
|
"prisma": "^6.8.2",
|
||||||
"psn-api": "github:jedmund/psn-api",
|
"psn-api": "github:jedmund/psn-api",
|
||||||
"redis": "^4.7.0",
|
"redis": "^4.7.0",
|
||||||
"sharp": "^0.34.2",
|
"sharp": "^0.34.2",
|
||||||
"steamapi": "^3.0.11",
|
"steamapi": "^3.0.11",
|
||||||
"svelte-bricks": "^0.3.2",
|
"svelte-bricks": "^0.3.2",
|
||||||
"svelte-infinite": "^0.5.0",
|
"svelte-infinite": "^0.5.0",
|
||||||
"svelte-medium-image-zoom": "^0.2.6",
|
"svelte-medium-image-zoom": "^0.2.6",
|
||||||
"svelte-portal": "^2.2.1",
|
"svelte-portal": "^2.2.1",
|
||||||
"svelte-tiptap": "^2.1.0",
|
"svelte-tiptap": "^2.1.0",
|
||||||
"svgo": "^3.3.2",
|
"svgo": "^3.3.2",
|
||||||
"tinyduration": "^3.3.1",
|
"tinyduration": "^3.3.1",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"tiptap-extension-auto-joiner": "^0.1.3",
|
"tiptap-extension-auto-joiner": "^0.1.3",
|
||||||
"tiptap-extension-global-drag-handle": "^0.1.18",
|
"tiptap-extension-global-drag-handle": "^0.1.18",
|
||||||
"tiptap-markdown": "^0.8.10",
|
"tiptap-markdown": "^0.8.10",
|
||||||
"zod": "^3.25.30"
|
"zod": "^3.25.30"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "tsx prisma/seed.ts"
|
"seed": "tsx prisma/seed.ts"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0",
|
"node": ">=20.0.0",
|
||||||
"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"
|
"storybook": "$storybook"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,18 +11,25 @@ const mockAlbum = {
|
||||||
url: 'https://www.last.fm/music/Radiohead/In+Rainbows',
|
url: 'https://www.last.fm/music/Radiohead/In+Rainbows',
|
||||||
rank: 1,
|
rank: 1,
|
||||||
images: {
|
images: {
|
||||||
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
small:
|
||||||
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
medium:
|
||||||
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
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'
|
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,
|
isNowPlaying: false,
|
||||||
appleMusicData: {
|
appleMusicData: {
|
||||||
appleMusicId: '1109714933',
|
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',
|
highResArtwork:
|
||||||
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',
|
'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'],
|
genres: ['Alternative', 'Music'],
|
||||||
releaseDate: '2007-10-10',
|
releaseDate: '2007-10-10',
|
||||||
trackCount: 10,
|
trackCount: 10,
|
||||||
|
|
@ -180,4 +187,4 @@ export const Interactive = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@
|
||||||
// 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
|
// Debug logging
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (album && isNowPlaying) {
|
if (album && isNowPlaying) {
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@ export default {
|
||||||
component: AlbumSimpleStory
|
component: AlbumSimpleStory
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Default = {}
|
export const Default = {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import Album from './Album.svelte'
|
import Album from './Album.svelte'
|
||||||
|
|
||||||
const album = {
|
const album = {
|
||||||
name: 'In Rainbows',
|
name: 'In Rainbows',
|
||||||
artist: {
|
artist: {
|
||||||
|
|
@ -11,18 +11,25 @@
|
||||||
url: 'https://www.last.fm/music/Radiohead/In+Rainbows',
|
url: 'https://www.last.fm/music/Radiohead/In+Rainbows',
|
||||||
rank: 1,
|
rank: 1,
|
||||||
images: {
|
images: {
|
||||||
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
small:
|
||||||
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
medium:
|
||||||
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
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'
|
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,
|
isNowPlaying: false,
|
||||||
appleMusicData: {
|
appleMusicData: {
|
||||||
appleMusicId: '1109714933',
|
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',
|
highResArtwork:
|
||||||
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',
|
'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'],
|
genres: ['Alternative', 'Music'],
|
||||||
releaseDate: '2007-10-10',
|
releaseDate: '2007-10-10',
|
||||||
trackCount: 10,
|
trackCount: 10,
|
||||||
|
|
@ -39,4 +46,4 @@
|
||||||
div {
|
div {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -47,4 +47,4 @@ export const Interactive = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
let isHovering = $state(false)
|
let isHovering = $state(false)
|
||||||
let isBlinking = $state(false)
|
let isBlinking = $state(false)
|
||||||
let isPlayingMusic = $state(forcePlayingMusic)
|
let isPlayingMusic = $state(forcePlayingMusic)
|
||||||
|
|
||||||
// Track store subscriptions for debugging
|
// Track store subscriptions for debugging
|
||||||
let nowPlayingStoreState = $state(null)
|
let nowPlayingStoreState = $state(null)
|
||||||
let albumStoreState = $state(null)
|
let albumStoreState = $state(null)
|
||||||
|
|
@ -71,35 +71,40 @@
|
||||||
nowPlayingStoreState = 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) {
|
||||||
const nowPlayingFromStream = Array.from(state.updates.values()).some((update) => update.isNowPlaying)
|
const nowPlayingFromStream = Array.from(state.updates.values()).some(
|
||||||
console.log('Avatar - nowPlayingStream update:', {
|
(update) => update.isNowPlaying
|
||||||
updatesCount: state.updates.size,
|
)
|
||||||
hasNowPlaying: nowPlayingFromStream
|
console.log('Avatar - nowPlayingStream update:', {
|
||||||
|
updatesCount: state.updates.size,
|
||||||
|
hasNowPlaying: nowPlayingFromStream
|
||||||
})
|
})
|
||||||
// Don't set to false if we haven't received album data yet
|
// Don't set to false if we haven't received album data yet
|
||||||
if (nowPlayingFromStream || albumStoreState !== null) {
|
if (nowPlayingFromStream || albumStoreState !== null) {
|
||||||
isPlayingMusic = nowPlayingFromStream || (albumStoreState?.some(album => album.isNowPlaying) ?? false)
|
isPlayingMusic =
|
||||||
|
nowPlayingFromStream || (albumStoreState?.some((album) => album.isNowPlaying) ?? false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Also check the album stream
|
// Also check the album stream
|
||||||
const unsubscribeAlbums = albumStream.subscribe((state) => {
|
const unsubscribeAlbums = albumStream.subscribe((state) => {
|
||||||
albumStoreState = state.albums
|
albumStoreState = state.albums
|
||||||
if (!forcePlayingMusic) {
|
if (!forcePlayingMusic) {
|
||||||
const hasNowPlaying = state.albums.some(album => album.isNowPlaying)
|
const hasNowPlaying = state.albums.some((album) => album.isNowPlaying)
|
||||||
|
|
||||||
// Get the current state of nowPlayingStream
|
// Get the current state of nowPlayingStream
|
||||||
const nowPlayingState = nowPlayingStoreState || get(nowPlayingStream)
|
const nowPlayingState = nowPlayingStoreState || get(nowPlayingStream)
|
||||||
const nowPlayingFromStream = Array.from(nowPlayingState.updates.values()).some((update) => update.isNowPlaying)
|
const nowPlayingFromStream = Array.from(nowPlayingState.updates.values()).some(
|
||||||
|
(update) => update.isNowPlaying
|
||||||
console.log('Avatar - albumStream update:', {
|
)
|
||||||
albumsCount: state.albums.length,
|
|
||||||
|
console.log('Avatar - albumStream update:', {
|
||||||
|
albumsCount: state.albums.length,
|
||||||
hasNowPlayingInAlbums: hasNowPlaying,
|
hasNowPlayingInAlbums: hasNowPlaying,
|
||||||
hasNowPlayingInStream: nowPlayingFromStream,
|
hasNowPlayingInStream: nowPlayingFromStream,
|
||||||
albums: state.albums.map(a => ({ name: a.name, isNowPlaying: a.isNowPlaying }))
|
albums: state.albums.map((a) => ({ name: a.name, isNowPlaying: a.isNowPlaying }))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update isPlayingMusic based on whether any album is now playing from either source
|
// Update isPlayingMusic based on whether any album is now playing from either source
|
||||||
isPlayingMusic = hasNowPlaying || nowPlayingFromStream
|
isPlayingMusic = hasNowPlaying || nowPlayingFromStream
|
||||||
}
|
}
|
||||||
|
|
@ -122,8 +127,8 @@
|
||||||
style="transform: scale({scale.current})"
|
style="transform: scale({scale.current})"
|
||||||
>
|
>
|
||||||
<AvatarSVG>
|
<AvatarSVG>
|
||||||
<!-- Face group -->
|
<!-- Face group -->
|
||||||
<g slot="face" class="face" class:hover={isHovering} class:blink={isBlinking}>
|
<g slot="face" class="face" class:hover={isHovering} class:blink={isBlinking}>
|
||||||
<!-- Normal face -->
|
<!-- Normal face -->
|
||||||
<g class="normal">
|
<g class="normal">
|
||||||
<path
|
<path
|
||||||
|
|
@ -275,4 +280,4 @@
|
||||||
.face.blink :global(.blink) {
|
.face.blink :global(.blink) {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -99,4 +99,4 @@
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
<svg
|
<svg width="497" height="497" viewBox="0 0 497 497" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
width="497"
|
|
||||||
height="497"
|
|
||||||
viewBox="0 0 497 497"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<g>
|
<g>
|
||||||
<!-- Common elements -->
|
<!-- Common elements -->
|
||||||
<!-- Skin -->
|
<!-- Skin -->
|
||||||
|
|
@ -181,4 +175,4 @@
|
||||||
<rect width="497" height="497" fill="white" style="fill:white;fill-opacity:1;" />
|
<rect width="497" height="497" fill="white" style="fill:white;fill-opacity:1;" />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
|
@ -13,13 +13,13 @@
|
||||||
|
|
||||||
// Responsive column configuration
|
// Responsive column configuration
|
||||||
// These values work well with our existing design
|
// These values work well with our existing design
|
||||||
let minColWidth = 200 // Minimum column width in px
|
let minColWidth = 200 // Minimum column width in px
|
||||||
let maxColWidth = 400 // Maximum column width in px
|
let maxColWidth = 400 // Maximum column width in px
|
||||||
let gap = 16 // Gap between items (equivalent to $unit-2x)
|
let gap = 16 // Gap between items (equivalent to $unit-2x)
|
||||||
|
|
||||||
// On tablet/phone, we want larger minimum widths
|
// On tablet/phone, we want larger minimum widths
|
||||||
let windowWidth = $state(0)
|
let windowWidth = $state(0)
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// Adjust column widths based on viewport
|
// Adjust column widths based on viewport
|
||||||
if (windowWidth < 768) {
|
if (windowWidth < 768) {
|
||||||
|
|
@ -68,4 +68,4 @@
|
||||||
:global(.photo-masonry) {
|
:global(.photo-masonry) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let { trackName }: Props = $props()
|
let { trackName }: Props = $props()
|
||||||
|
|
||||||
let textElement: HTMLSpanElement | null = $state(null)
|
let textElement: HTMLSpanElement | null = $state(null)
|
||||||
let containerElement: HTMLDivElement | null = $state(null)
|
let containerElement: HTMLDivElement | null = $state(null)
|
||||||
let shouldMarquee = $state(false)
|
let shouldMarquee = $state(false)
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (textElement && containerElement && trackName) {
|
if (textElement && containerElement && trackName) {
|
||||||
// Check if text overflows
|
// Check if text overflows
|
||||||
|
|
@ -27,11 +27,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if trackName}
|
{#if trackName}
|
||||||
<div class="track-name-container" bind:this={containerElement}>
|
<div class="track-name-container" bind:this={containerElement}>
|
||||||
<span
|
<span class="track-name" class:marquee={shouldMarquee} bind:this={textElement}>
|
||||||
class="track-name"
|
|
||||||
class:marquee={shouldMarquee}
|
|
||||||
bind:this={textElement}
|
|
||||||
>
|
|
||||||
{trackName}
|
{trackName}
|
||||||
{#if shouldMarquee}
|
{#if shouldMarquee}
|
||||||
<span class="marquee-gap"> </span>
|
<span class="marquee-gap"> </span>
|
||||||
|
|
@ -123,20 +119,20 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: $font-weight-med;
|
font-weight: $font-weight-med;
|
||||||
|
|
||||||
&.marquee {
|
&.marquee {
|
||||||
animation: marquee 8s linear infinite;
|
animation: marquee 8s linear infinite;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
animation-play-state: paused;
|
animation-play-state: paused;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.marquee-gap {
|
.marquee-gap {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes marquee {
|
@keyframes marquee {
|
||||||
0% {
|
0% {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,25 @@ const mockAlbums = [
|
||||||
url: 'https://www.last.fm/music/Radiohead/In+Rainbows',
|
url: 'https://www.last.fm/music/Radiohead/In+Rainbows',
|
||||||
rank: 1,
|
rank: 1,
|
||||||
images: {
|
images: {
|
||||||
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
small:
|
||||||
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
medium:
|
||||||
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
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'
|
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,
|
isNowPlaying: false,
|
||||||
appleMusicData: {
|
appleMusicData: {
|
||||||
appleMusicId: '1109714933',
|
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',
|
highResArtwork:
|
||||||
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',
|
'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'],
|
genres: ['Alternative', 'Music'],
|
||||||
releaseDate: '2007-10-10',
|
releaseDate: '2007-10-10',
|
||||||
trackCount: 10,
|
trackCount: 10,
|
||||||
|
|
@ -40,19 +47,26 @@ const mockAlbums = [
|
||||||
url: 'https://www.last.fm/music/Radiohead/OK+Computer',
|
url: 'https://www.last.fm/music/Radiohead/OK+Computer',
|
||||||
rank: 2,
|
rank: 2,
|
||||||
images: {
|
images: {
|
||||||
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
small:
|
||||||
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
medium:
|
||||||
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
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'
|
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,
|
isNowPlaying: true,
|
||||||
nowPlayingTrack: 'Paranoid Android',
|
nowPlayingTrack: 'Paranoid Android',
|
||||||
appleMusicData: {
|
appleMusicData: {
|
||||||
appleMusicId: '1097861387',
|
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',
|
highResArtwork:
|
||||||
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'
|
'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'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -65,17 +79,24 @@ const mockAlbums = [
|
||||||
url: 'https://www.last.fm/music/Pink+Floyd/The+Dark+Side+of+the+Moon',
|
url: 'https://www.last.fm/music/Pink+Floyd/The+Dark+Side+of+the+Moon',
|
||||||
rank: 3,
|
rank: 3,
|
||||||
images: {
|
images: {
|
||||||
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
small:
|
||||||
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
medium:
|
||||||
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
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'
|
default:
|
||||||
|
'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
},
|
},
|
||||||
appleMusicData: {
|
appleMusicData: {
|
||||||
appleMusicId: '1065973699',
|
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',
|
highResArtwork:
|
||||||
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'
|
'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'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -88,16 +109,22 @@ const mockAlbums = [
|
||||||
url: 'https://www.last.fm/music/Joy+Division/Unknown+Pleasures',
|
url: 'https://www.last.fm/music/Joy+Division/Unknown+Pleasures',
|
||||||
rank: 4,
|
rank: 4,
|
||||||
images: {
|
images: {
|
||||||
small: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
small:
|
||||||
medium: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
medium:
|
||||||
extralarge: 'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp',
|
'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',
|
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'
|
default:
|
||||||
|
'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
},
|
},
|
||||||
appleMusicData: {
|
appleMusicData: {
|
||||||
appleMusicId: '659989492',
|
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'
|
highResArtwork:
|
||||||
|
'https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/85/2e/2b/852e2b6c-93ec-806a-95b2-8f5eda2f775c/22UMGIM18886.rgb.jpg/592x592bb.webp'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -198,4 +225,4 @@ export const MobileView = {
|
||||||
defaultViewport: 'mobile1'
|
defaultViewport: 'mobile1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let { albums: initialAlbums = [] }: RecentAlbumsProps = $props()
|
let { albums: initialAlbums = [] }: RecentAlbumsProps = $props()
|
||||||
|
|
||||||
// Use SSE stream for real-time updates, fallback to initial albums
|
// Use SSE stream for real-time updates, fallback to initial albums
|
||||||
let albums = $state<AlbumType[]>(initialAlbums)
|
let albums = $state<AlbumType[]>(initialAlbums)
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const unsubscribe = albumStream.albums.subscribe((streamAlbums) => {
|
const unsubscribe = albumStream.albums.subscribe((streamAlbums) => {
|
||||||
if (streamAlbums.length > 0) {
|
if (streamAlbums.length > 0) {
|
||||||
|
|
@ -20,9 +20,9 @@
|
||||||
})
|
})
|
||||||
return unsubscribe
|
return unsubscribe
|
||||||
})
|
})
|
||||||
|
|
||||||
let hoveredAlbumId: string | null = $state(null)
|
let hoveredAlbumId: string | null = $state(null)
|
||||||
|
|
||||||
function handleAlbumHover(albumId: string | null) {
|
function handleAlbumHover(albumId: string | null) {
|
||||||
hoveredAlbumId = albumId
|
hoveredAlbumId = albumId
|
||||||
}
|
}
|
||||||
|
|
@ -33,8 +33,8 @@
|
||||||
<ul>
|
<ul>
|
||||||
{#each albums.slice(0, 4) as album, index}
|
{#each albums.slice(0, 4) as album, index}
|
||||||
<li>
|
<li>
|
||||||
<Album
|
<Album
|
||||||
{album}
|
{album}
|
||||||
albumId={`${album.artist.name}-${album.name}`}
|
albumId={`${album.artist.name}-${album.name}`}
|
||||||
{hoveredAlbumId}
|
{hoveredAlbumId}
|
||||||
onHover={handleAlbumHover}
|
onHover={handleAlbumHover}
|
||||||
|
|
|
||||||
|
|
@ -169,11 +169,17 @@ export async function findAlbum(artist: string, album: string): Promise<AppleMus
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to perform the album search and matching
|
// Helper function to perform the album search and matching
|
||||||
async function searchAndMatch(searchAlbum: string, storefront: string = DEFAULT_STOREFRONT): Promise<{album: AppleMusicAlbum, storefront: string} | null> {
|
async function searchAndMatch(
|
||||||
|
searchAlbum: string,
|
||||||
|
storefront: string = DEFAULT_STOREFRONT
|
||||||
|
): Promise<{ album: AppleMusicAlbum; storefront: string } | null> {
|
||||||
const searchQuery = `${artist} ${searchAlbum}`
|
const searchQuery = `${artist} ${searchAlbum}`
|
||||||
const response = await searchAlbums(searchQuery, 5, storefront)
|
const response = await searchAlbums(searchQuery, 5, storefront)
|
||||||
|
|
||||||
console.log(`Search results for "${searchQuery}" in ${storefront} storefront:`, JSON.stringify(response, null, 2))
|
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 ${storefront} storefront`)
|
console.log(`No albums found in ${storefront} storefront`)
|
||||||
|
|
@ -209,7 +215,7 @@ export async function findAlbum(artist: string, album: string): Promise<AppleMus
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return match ? {album: match, storefront} : null
|
return match ? { album: match, storefront } : null
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -226,9 +232,11 @@ export async function findAlbum(artist: string, album: string): Promise<AppleMus
|
||||||
if (!result) {
|
if (!result) {
|
||||||
const cleanedAlbum = removeLeadingPunctuation(album)
|
const cleanedAlbum = removeLeadingPunctuation(album)
|
||||||
if (cleanedAlbum !== album && cleanedAlbum.length > 0) {
|
if (cleanedAlbum !== album && cleanedAlbum.length > 0) {
|
||||||
console.log(`No match found for "${album}", trying without leading punctuation: "${cleanedAlbum}"`)
|
console.log(
|
||||||
|
`No match found for "${album}", trying without leading punctuation: "${cleanedAlbum}"`
|
||||||
|
)
|
||||||
result = await searchAndMatch(cleanedAlbum)
|
result = await searchAndMatch(cleanedAlbum)
|
||||||
|
|
||||||
// Also try Japanese storefront with cleaned album name
|
// Also try Japanese storefront with cleaned album name
|
||||||
if (!result) {
|
if (!result) {
|
||||||
console.log(`Still no match, trying Japanese storefront with cleaned name`)
|
console.log(`Still no match, trying Japanese storefront with cleaned name`)
|
||||||
|
|
@ -246,7 +254,7 @@ export async function findAlbum(artist: string, album: string): Promise<AppleMus
|
||||||
// Store the storefront information with the album
|
// Store the storefront information with the album
|
||||||
const matchedAlbum = result.album as any
|
const matchedAlbum = result.album as any
|
||||||
matchedAlbum._storefront = result.storefront
|
matchedAlbum._storefront = result.storefront
|
||||||
|
|
||||||
// Return the match
|
// Return the match
|
||||||
return result.album
|
return result.album
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -269,7 +277,7 @@ export async function transformAlbumData(appleMusicAlbum: AppleMusicAlbum) {
|
||||||
try {
|
try {
|
||||||
// Determine which storefront to use
|
// Determine which storefront to use
|
||||||
const storefront = (appleMusicAlbum as any)._storefront || DEFAULT_STOREFRONT
|
const storefront = (appleMusicAlbum as any)._storefront || DEFAULT_STOREFRONT
|
||||||
|
|
||||||
// Fetch album details with tracks
|
// Fetch album details with tracks
|
||||||
const endpoint = `/catalog/${storefront}/albums/${appleMusicAlbum.id}?include=tracks`
|
const endpoint = `/catalog/${storefront}/albums/${appleMusicAlbum.id}?include=tracks`
|
||||||
const response = await makeAppleMusicRequest<{
|
const response = await makeAppleMusicRequest<{
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,12 @@ 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)
|
const nowPlayingAlbum = albums.find((a) => a.isNowPlaying)
|
||||||
console.log('Album stream received albums:', {
|
console.log('Album stream received albums:', {
|
||||||
totalAlbums: albums.length,
|
totalAlbums: albums.length,
|
||||||
nowPlayingAlbum: nowPlayingAlbum ? `${nowPlayingAlbum.artist.name} - ${nowPlayingAlbum.name}` : 'none'
|
nowPlayingAlbum: nowPlayingAlbum
|
||||||
|
? `${nowPlayingAlbum.artist.name} - ${nowPlayingAlbum.name}`
|
||||||
|
: 'none'
|
||||||
})
|
})
|
||||||
update((state) => ({
|
update((state) => ({
|
||||||
...state,
|
...state,
|
||||||
|
|
@ -117,4 +119,4 @@ function createAlbumStream() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const albumStream = createAlbumStream()
|
export const albumStream = createAlbumStream()
|
||||||
|
|
|
||||||
|
|
@ -56,26 +56,29 @@ export const GET: RequestHandler = async ({ request }) => {
|
||||||
try {
|
try {
|
||||||
// Fetch full album data
|
// Fetch full album data
|
||||||
const albums = await getRecentAlbums(client)
|
const albums = await getRecentAlbums(client)
|
||||||
|
|
||||||
// Update recentTracks for duration-based now playing detection
|
// Update recentTracks for duration-based now playing detection
|
||||||
await getNowPlayingAlbums(client) // This populates recentTracks
|
await getNowPlayingAlbums(client) // This populates recentTracks
|
||||||
|
|
||||||
// Enrich albums with additional info and check now playing status
|
// 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)
|
||||||
const withAppleMusic = await searchAppleMusicForAlbum(enriched)
|
const withAppleMusic = await searchAppleMusicForAlbum(enriched)
|
||||||
|
|
||||||
// Check if this album is currently playing using duration-based detection
|
// Check if this album is currently playing using duration-based detection
|
||||||
if (withAppleMusic.appleMusicData?.tracks && !withAppleMusic.isNowPlaying) {
|
if (withAppleMusic.appleMusicData?.tracks && !withAppleMusic.isNowPlaying) {
|
||||||
const nowPlayingCheck = checkWithTracks(withAppleMusic.name, withAppleMusic.appleMusicData.tracks)
|
const nowPlayingCheck = checkWithTracks(
|
||||||
|
withAppleMusic.name,
|
||||||
|
withAppleMusic.appleMusicData.tracks
|
||||||
|
)
|
||||||
if (nowPlayingCheck?.isNowPlaying) {
|
if (nowPlayingCheck?.isNowPlaying) {
|
||||||
withAppleMusic.isNowPlaying = true
|
withAppleMusic.isNowPlaying = true
|
||||||
withAppleMusic.nowPlayingTrack = nowPlayingCheck.nowPlayingTrack
|
withAppleMusic.nowPlayingTrack = nowPlayingCheck.nowPlayingTrack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return withAppleMusic
|
return withAppleMusic
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error enriching album ${album.name}:`, error)
|
console.error(`Error enriching album ${album.name}:`, error)
|
||||||
|
|
@ -83,12 +86,14 @@ export const GET: RequestHandler = async ({ request }) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure only one album is marked as now playing in the enriched albums
|
// Ensure only one album is marked as now playing in the enriched albums
|
||||||
const nowPlayingCount = enrichedAlbums.filter(a => a.isNowPlaying).length
|
const nowPlayingCount = enrichedAlbums.filter((a) => a.isNowPlaying).length
|
||||||
if (nowPlayingCount > 1) {
|
if (nowPlayingCount > 1) {
|
||||||
console.log(`Multiple enriched albums marked as now playing (${nowPlayingCount}), keeping only the most recent one`)
|
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
|
// 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
|
// So we keep the first now playing album and mark others as not playing
|
||||||
let foundFirst = false
|
let foundFirst = false
|
||||||
|
|
@ -107,24 +112,27 @@ export const GET: RequestHandler = async ({ request }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if album order has changed or now playing status changed
|
// 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)
|
||||||
|
|
||||||
// Also check if any now playing status changed
|
// Also check if any now playing status changed
|
||||||
let nowPlayingChanged = false
|
let nowPlayingChanged = false
|
||||||
for (const album of enrichedAlbums) {
|
for (const album of enrichedAlbums) {
|
||||||
const key = `${album.artist.name}:${album.name}`
|
const key = `${album.artist.name}:${album.name}`
|
||||||
const lastState = lastNowPlayingState.get(key)
|
const lastState = lastNowPlayingState.get(key)
|
||||||
if (album.isNowPlaying !== (lastState?.isPlaying || false) ||
|
if (
|
||||||
(album.isNowPlaying && album.nowPlayingTrack !== lastState?.track)) {
|
album.isNowPlaying !== (lastState?.isPlaying || false) ||
|
||||||
|
(album.isNowPlaying && album.nowPlayingTrack !== lastState?.track)
|
||||||
|
) {
|
||||||
nowPlayingChanged = true
|
nowPlayingChanged = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (albumOrderChanged || nowPlayingChanged) {
|
if (albumOrderChanged || nowPlayingChanged) {
|
||||||
lastAlbumOrder = currentAlbumOrder
|
lastAlbumOrder = currentAlbumOrder
|
||||||
|
|
||||||
// Update now playing state
|
// Update now playing state
|
||||||
for (const album of enrichedAlbums) {
|
for (const album of enrichedAlbums) {
|
||||||
const key = `${album.artist.name}:${album.name}`
|
const key = `${album.artist.name}:${album.name}`
|
||||||
|
|
@ -133,16 +141,18 @@ export const GET: RequestHandler = async ({ request }) => {
|
||||||
track: album.nowPlayingTrack
|
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)
|
const nowPlayingAlbum = enrichedAlbums.find((a) => a.isNowPlaying)
|
||||||
console.log('Sent album update with now playing status:', {
|
console.log('Sent album update with now playing status:', {
|
||||||
totalAlbums: enrichedAlbums.length,
|
totalAlbums: enrichedAlbums.length,
|
||||||
nowPlayingAlbum: nowPlayingAlbum ? `${nowPlayingAlbum.artist.name} - ${nowPlayingAlbum.name}` : 'none'
|
nowPlayingAlbum: nowPlayingAlbum
|
||||||
|
? `${nowPlayingAlbum.artist.name} - ${nowPlayingAlbum.name}`
|
||||||
|
: 'none'
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
isClosed = true
|
isClosed = true
|
||||||
|
|
@ -158,9 +168,9 @@ export const GET: RequestHandler = async ({ request }) => {
|
||||||
// (Recent albums already have their now playing status included)
|
// (Recent albums already have their now playing status included)
|
||||||
for (const album of nowPlayingAlbums) {
|
for (const album of nowPlayingAlbums) {
|
||||||
const isInRecentAlbums = enrichedAlbums.some(
|
const isInRecentAlbums = enrichedAlbums.some(
|
||||||
a => a.artist.name === album.artistName && a.name === album.albumName
|
(a) => a.artist.name === album.artistName && a.name === album.albumName
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isInRecentAlbums) {
|
if (!isInRecentAlbums) {
|
||||||
const key = `${album.artistName}:${album.albumName}`
|
const key = `${album.artistName}:${album.albumName}`
|
||||||
const lastState = lastNowPlayingState.get(key)
|
const lastState = lastNowPlayingState.get(key)
|
||||||
|
|
@ -321,19 +331,21 @@ async function getNowPlayingAlbums(client: LastClient): Promise<NowPlayingUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure only one album is marked as now playing - keep the most recent one
|
// Ensure only one album is marked as now playing - keep the most recent one
|
||||||
const nowPlayingAlbums = Array.from(albums.values()).filter(a => a.isNowPlaying)
|
const nowPlayingAlbums = Array.from(albums.values()).filter((a) => a.isNowPlaying)
|
||||||
if (nowPlayingAlbums.length > 1) {
|
if (nowPlayingAlbums.length > 1) {
|
||||||
console.log(`Multiple albums marked as now playing (${nowPlayingAlbums.length}), keeping only the most recent one`)
|
console.log(
|
||||||
|
`Multiple albums marked as now playing (${nowPlayingAlbums.length}), keeping only the most recent one`
|
||||||
|
)
|
||||||
|
|
||||||
// Find the most recent track
|
// Find the most recent track
|
||||||
let mostRecentTime = new Date(0)
|
let mostRecentTime = new Date(0)
|
||||||
let mostRecentAlbum = nowPlayingAlbums[0]
|
let mostRecentAlbum = nowPlayingAlbums[0]
|
||||||
|
|
||||||
for (const album of nowPlayingAlbums) {
|
for (const album of nowPlayingAlbums) {
|
||||||
// Find the most recent track for this album
|
// Find the most recent track for this album
|
||||||
const albumTracks = recentTracks.filter(t => t.albumName === album.albumName)
|
const albumTracks = recentTracks.filter((t) => t.albumName === album.albumName)
|
||||||
if (albumTracks.length > 0) {
|
if (albumTracks.length > 0) {
|
||||||
const latestTrack = albumTracks.reduce((latest, track) =>
|
const latestTrack = albumTracks.reduce((latest, track) =>
|
||||||
track.scrobbleTime > latest.scrobbleTime ? track : latest
|
track.scrobbleTime > latest.scrobbleTime ? track : latest
|
||||||
)
|
)
|
||||||
if (latestTrack.scrobbleTime > mostRecentTime) {
|
if (latestTrack.scrobbleTime > mostRecentTime) {
|
||||||
|
|
@ -342,9 +354,9 @@ async function getNowPlayingAlbums(client: LastClient): Promise<NowPlayingUpdate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark all others as not playing
|
// Mark all others as not playing
|
||||||
nowPlayingAlbums.forEach(album => {
|
nowPlayingAlbums.forEach((album) => {
|
||||||
if (album !== mostRecentAlbum) {
|
if (album !== mostRecentAlbum) {
|
||||||
const key = `${album.artistName}:${album.albumName}`
|
const key = `${album.artistName}:${album.albumName}`
|
||||||
albums.set(key, {
|
albums.set(key, {
|
||||||
|
|
@ -533,10 +545,7 @@ async function searchAppleMusicForAlbum(album: Album): Promise<Album> {
|
||||||
return album
|
return album
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRecentAlbums(
|
async function getRecentAlbums(client: LastClient, limit: number = 4): Promise<Album[]> {
|
||||||
client: LastClient,
|
|
||||||
limit: number = 4
|
|
||||||
): Promise<Album[]> {
|
|
||||||
// Check cache for recent tracks
|
// Check cache for recent tracks
|
||||||
const cacheKey = `lastfm:recent:${USERNAME}`
|
const cacheKey = `lastfm:recent:${USERNAME}`
|
||||||
const cached = await redis.get(cacheKey)
|
const cached = await redis.get(cacheKey)
|
||||||
|
|
|
||||||
|
|
@ -80,12 +80,12 @@ export const GET: RequestHandler = async (event) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to photoPublishedAt
|
// Fallback to photoPublishedAt
|
||||||
if (media.photoPublishedAt) {
|
if (media.photoPublishedAt) {
|
||||||
return new Date(media.photoPublishedAt)
|
return new Date(media.photoPublishedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final fallback to createdAt
|
// Final fallback to createdAt
|
||||||
return new Date(media.createdAt)
|
return new Date(media.createdAt)
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ export const GET: RequestHandler = async (event) => {
|
||||||
.filter((album) => album.media.length > 0) // Only include albums with media
|
.filter((album) => album.media.length > 0) // Only include albums with media
|
||||||
.map((album) => {
|
.map((album) => {
|
||||||
const firstMedia = album.media[0].media
|
const firstMedia = album.media[0].media
|
||||||
|
|
||||||
// Find the most recent EXIF date from all photos in the album
|
// Find the most recent EXIF date from all photos in the album
|
||||||
let albumDate = new Date(album.createdAt)
|
let albumDate = new Date(album.createdAt)
|
||||||
for (const albumMedia of album.media) {
|
for (const albumMedia of album.media) {
|
||||||
|
|
@ -104,7 +104,7 @@ export const GET: RequestHandler = async (event) => {
|
||||||
albumDate = mediaDate
|
albumDate = mediaDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `album-${album.id}`,
|
id: `album-${album.id}`,
|
||||||
slug: album.slug,
|
slug: album.slug,
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@
|
||||||
// Initialize state with server-side data
|
// Initialize state with server-side data
|
||||||
let allPhotoItems = $state<PhotoItem[]>(data.photoItems || [])
|
let allPhotoItems = $state<PhotoItem[]>(data.photoItems || [])
|
||||||
let currentOffset = $state(data.pagination?.limit || 20)
|
let currentOffset = $state(data.pagination?.limit || 20)
|
||||||
|
|
||||||
// Track loaded photo IDs to prevent duplicates
|
// Track loaded photo IDs to prevent duplicates
|
||||||
let loadedPhotoIds = $state(new Set(data.photoItems?.map(item => item.id) || []))
|
let loadedPhotoIds = $state(new Set(data.photoItems?.map((item) => item.id) || []))
|
||||||
|
|
||||||
const error = $derived(data.error)
|
const error = $derived(data.error)
|
||||||
const pageUrl = $derived($page.url.href)
|
const pageUrl = $derived($page.url.href)
|
||||||
|
|
||||||
// Error message for retry display
|
// Error message for retry display
|
||||||
let lastError = $state<string>('')
|
let lastError = $state<string>('')
|
||||||
|
|
||||||
|
|
@ -32,30 +32,30 @@
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch photos: ${response.statusText}`)
|
throw new Error(`Failed to fetch photos: ${response.statusText}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
// Filter out duplicates
|
// Filter out duplicates
|
||||||
const newItems = (data.photoItems || []).filter(
|
const newItems = (data.photoItems || []).filter(
|
||||||
(item: PhotoItem) => !loadedPhotoIds.has(item.id)
|
(item: PhotoItem) => !loadedPhotoIds.has(item.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add new photo IDs to the set
|
// Add new photo IDs to the set
|
||||||
newItems.forEach((item: PhotoItem) => loadedPhotoIds.add(item.id))
|
newItems.forEach((item: PhotoItem) => loadedPhotoIds.add(item.id))
|
||||||
|
|
||||||
// Append new photos to existing list
|
// Append new photos to existing list
|
||||||
allPhotoItems = [...allPhotoItems, ...newItems]
|
allPhotoItems = [...allPhotoItems, ...newItems]
|
||||||
|
|
||||||
// Update pagination state
|
// Update pagination state
|
||||||
currentOffset += data.pagination?.limit || 20
|
currentOffset += data.pagination?.limit || 20
|
||||||
|
|
||||||
// Update loader state
|
// Update loader state
|
||||||
if (!data.pagination?.hasMore || newItems.length === 0) {
|
if (!data.pagination?.hasMore || newItems.length === 0) {
|
||||||
loaderState.complete()
|
loaderState.complete()
|
||||||
} else {
|
} else {
|
||||||
loaderState.loaded()
|
loaderState.loaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear any previous error
|
// Clear any previous error
|
||||||
lastError = ''
|
lastError = ''
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -117,37 +117,37 @@
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<MasonryPhotoGrid photoItems={allPhotoItems} />
|
<MasonryPhotoGrid photoItems={allPhotoItems} />
|
||||||
|
|
||||||
<InfiniteLoader
|
<InfiniteLoader
|
||||||
{loaderState}
|
{loaderState}
|
||||||
triggerLoad={loadMore}
|
triggerLoad={loadMore}
|
||||||
intersectionOptions={{ rootMargin: "0px 0px 200px 0px" }}
|
intersectionOptions={{ rootMargin: '0px 0px 200px 0px' }}
|
||||||
>
|
>
|
||||||
<!-- Empty content since we're rendering the grid above -->
|
<!-- Empty content since we're rendering the grid above -->
|
||||||
<div style="height: 1px;"></div>
|
<div style="height: 1px;"></div>
|
||||||
|
|
||||||
{#snippet loading()}
|
{#snippet loading()}
|
||||||
<div class="loading-container">
|
<div class="loading-container">
|
||||||
<LoadingSpinner size="medium" text="Loading more photos..." />
|
<LoadingSpinner size="medium" text="Loading more photos..." />
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet error()}
|
{#snippet error()}
|
||||||
<div class="error-retry">
|
<div class="error-retry">
|
||||||
<p class="error-text">{lastError || 'Failed to load photos'}</p>
|
<p class="error-text">{lastError || 'Failed to load photos'}</p>
|
||||||
<button
|
<button
|
||||||
class="retry-button"
|
class="retry-button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
lastError = '';
|
lastError = ''
|
||||||
loaderState.reset();
|
loaderState.reset()
|
||||||
loadMore();
|
loadMore()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Try again
|
Try again
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet noData()}
|
{#snippet noData()}
|
||||||
<div class="end-message">
|
<div class="end-message">
|
||||||
<p>You've reached the end</p>
|
<p>You've reached the end</p>
|
||||||
|
|
@ -214,7 +214,7 @@
|
||||||
.end-message {
|
.end-message {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: $unit-6x 0;
|
padding: $unit-6x 0;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: $grey-50;
|
color: $grey-50;
|
||||||
|
|
@ -249,11 +249,11 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: darken($primary-color, 10%);
|
background-color: darken($primary-color, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue