feat: implement responsive album layout for mobile and desktop views, add debugging configurations for Next.js
This commit is contained in:
@@ -1 +1 @@
|
|||||||
NEXT_PUBLIC_COMMIT_SHA=fccf3c5
|
NEXT_PUBLIC_COMMIT_SHA=3a3c065
|
||||||
|
|||||||
38
.vscode/launch.json
vendored
Normal file
38
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug: Next.js Development",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/next",
|
||||||
|
"args": ["dev"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "development"
|
||||||
|
},
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["run", "dev"],
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"resolveSourceMapLocations": [
|
||||||
|
"${workspaceFolder}/**",
|
||||||
|
"!**/node_modules/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug: Next.js Production",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/next",
|
||||||
|
"args": ["start"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "production"
|
||||||
|
},
|
||||||
|
"preLaunchTask": "Build: Production Build Only",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["run", "start"],
|
||||||
|
"skipFiles": ["<node_internals>/**"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
114
.vscode/tasks.json
vendored
Normal file
114
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Dev: Start Development Server",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "pnpm",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"dev"
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": [
|
||||||
|
"$tsc-watch"
|
||||||
|
],
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "new",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": false
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Prod: Build and Start Production",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "bash",
|
||||||
|
"args": [
|
||||||
|
"-c",
|
||||||
|
"pnpm run build && pnpm run start"
|
||||||
|
],
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": true,
|
||||||
|
"panel": "new",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": true
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "production"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"problemMatcher": ["$tsc"],
|
||||||
|
"dependsOrder": "sequence"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Debug: Development with Debug Info",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "pnpm",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"dev"
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "test",
|
||||||
|
"isDefault": false
|
||||||
|
},
|
||||||
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "new",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": false
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "development",
|
||||||
|
"DEBUG": "*",
|
||||||
|
"NEXT_TELEMETRY_DISABLED": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"problemMatcher": ["$tsc-watch"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build: Production Build Only",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "pnpm",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"build"
|
||||||
|
],
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": true,
|
||||||
|
"panel": "new",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": true
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "production"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"problemMatcher": ["$tsc"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import { Separator } from '@/components/ui/separator';
|
|||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { getNavidromeAPI } from '@/lib/navidrome';
|
import { getNavidromeAPI } from '@/lib/navidrome';
|
||||||
import { useFavoriteAlbums } from '@/hooks/use-favorite-albums';
|
import { useFavoriteAlbums } from '@/hooks/use-favorite-albums';
|
||||||
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
|
|
||||||
export default function AlbumPage() {
|
export default function AlbumPage() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -24,6 +25,7 @@ export default function AlbumPage() {
|
|||||||
const { getAlbum, starItem, unstarItem } = useNavidrome();
|
const { getAlbum, starItem, unstarItem } = useNavidrome();
|
||||||
const { playTrack, addAlbumToQueue, playAlbum, playAlbumFromTrack, currentTrack } = useAudioPlayer();
|
const { playTrack, addAlbumToQueue, playAlbum, playAlbumFromTrack, currentTrack } = useAudioPlayer();
|
||||||
const { isFavoriteAlbum, toggleFavoriteAlbum } = useFavoriteAlbums();
|
const { isFavoriteAlbum, toggleFavoriteAlbum } = useFavoriteAlbums();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
const api = getNavidromeAPI();
|
const api = getNavidromeAPI();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -128,34 +130,82 @@ export default function AlbumPage() {
|
|||||||
<>
|
<>
|
||||||
<div className="h-full px-4 py-6 lg:px-8">
|
<div className="h-full px-4 py-6 lg:px-8">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-start gap-6">
|
{isMobile ? (
|
||||||
<Image
|
/* Mobile Layout */
|
||||||
src={coverArtUrl}
|
<div className="space-y-6">
|
||||||
alt={album.name}
|
{/* Album Cover - Centered */}
|
||||||
width={300}
|
<div className="flex justify-center">
|
||||||
height={300}
|
<Image
|
||||||
className="rounded-md"
|
src={coverArtUrl}
|
||||||
/>
|
alt={album.name}
|
||||||
<div className="space-y-2">
|
width={280}
|
||||||
<div className="flex items-center space-x-4">
|
height={280}
|
||||||
<p className="text-3xl font-semibold tracking-tight">{album.name}</p>
|
className="rounded-md shadow-lg"
|
||||||
<Button onClick={handleStar} variant="ghost" title={isStarred ? "Unstar album" : "Star album"}>
|
/>
|
||||||
<Heart className={isStarred ? 'text-primary' : 'text-gray-500'} fill={isStarred ? 'var(--primary)' : ""}/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<Link href={`/artist/${album.artistId}`}>
|
|
||||||
<p className="text-xl text-primary mt-0 mb-4 underline">{album.artist}</p>
|
|
||||||
</Link>
|
|
||||||
<Button className="px-5" onClick={() => playAlbum(album.id)}>
|
|
||||||
Play
|
|
||||||
</Button>
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
<p>{album.genre} • {album.year}</p>
|
|
||||||
<p>{album.songCount} songs, {formatDuration(album.duration)}</p>
|
|
||||||
|
|
||||||
|
{/* Album Info and Controls */}
|
||||||
|
<div className="flex justify-between items-start gap-4">
|
||||||
|
{/* Left side - Album Info */}
|
||||||
|
<div className="flex-1 space-y-1">
|
||||||
|
<h1 className="text-2xl font-bold text-left">{album.name}</h1>
|
||||||
|
<Link href={`/artist/${album.artistId}`}>
|
||||||
|
<p className="text-lg text-primary underline text-left">{album.artist}</p>
|
||||||
|
</Link>
|
||||||
|
<p className="text-sm text-muted-foreground text-left">{album.genre} • {album.year}</p>
|
||||||
|
<p className="text-sm text-muted-foreground text-left">{album.songCount} songs, {formatDuration(album.duration)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Right side - Controls */}
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<Button
|
||||||
|
className="w-12 h-12 rounded-full p-0"
|
||||||
|
onClick={() => playAlbum(album.id)}
|
||||||
|
title="Play Album"
|
||||||
|
>
|
||||||
|
<Play className="w-6 h-6" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleStar}
|
||||||
|
variant="ghost"
|
||||||
|
className="w-12 h-12 rounded-full p-0"
|
||||||
|
title={isStarred ? "Unstar album" : "Star album"}
|
||||||
|
>
|
||||||
|
<Heart className={`w-6 h-6 ${isStarred ? 'text-primary' : 'text-gray-500'}`} fill={isStarred ? 'var(--primary)' : ""}/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
/* Desktop Layout */
|
||||||
|
<div className="flex items-start gap-6">
|
||||||
|
<Image
|
||||||
|
src={coverArtUrl}
|
||||||
|
alt={album.name}
|
||||||
|
width={300}
|
||||||
|
height={300}
|
||||||
|
className="rounded-md"
|
||||||
|
/>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<p className="text-3xl font-semibold tracking-tight">{album.name}</p>
|
||||||
|
<Button onClick={handleStar} variant="ghost" title={isStarred ? "Unstar album" : "Star album"}>
|
||||||
|
<Heart className={isStarred ? 'text-primary' : 'text-gray-500'} fill={isStarred ? 'var(--primary)' : ""}/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Link href={`/artist/${album.artistId}`}>
|
||||||
|
<p className="text-xl text-primary mt-0 mb-4 underline">{album.artist}</p>
|
||||||
|
</Link>
|
||||||
|
<Button className="px-5" onClick={() => playAlbum(album.id)}>
|
||||||
|
Play
|
||||||
|
</Button>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
<p>{album.genre} • {album.year}</p>
|
||||||
|
<p>{album.songCount} songs, {formatDuration(album.duration)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user