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 { getNavidromeAPI } from '@/lib/navidrome';
|
||||
import { useFavoriteAlbums } from '@/hooks/use-favorite-albums';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
|
||||
export default function AlbumPage() {
|
||||
const { id } = useParams();
|
||||
@@ -24,6 +25,7 @@ export default function AlbumPage() {
|
||||
const { getAlbum, starItem, unstarItem } = useNavidrome();
|
||||
const { playTrack, addAlbumToQueue, playAlbum, playAlbumFromTrack, currentTrack } = useAudioPlayer();
|
||||
const { isFavoriteAlbum, toggleFavoriteAlbum } = useFavoriteAlbums();
|
||||
const isMobile = useIsMobile();
|
||||
const api = getNavidromeAPI();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -128,34 +130,82 @@ export default function AlbumPage() {
|
||||
<>
|
||||
<div className="h-full px-4 py-6 lg:px-8">
|
||||
<div className="space-y-4">
|
||||
<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>
|
||||
{isMobile ? (
|
||||
/* Mobile Layout */
|
||||
<div className="space-y-6">
|
||||
{/* Album Cover - Centered */}
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src={coverArtUrl}
|
||||
alt={album.name}
|
||||
width={280}
|
||||
height={280}
|
||||
className="rounded-md shadow-lg"
|
||||
/>
|
||||
</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>
|
||||
|
||||
{/* 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>
|
||||
) : (
|
||||
/* 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">
|
||||
<Separator />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user