feat: implement recently played albums and sidebar shortcut preferences
This commit is contained in:
@@ -1 +1 @@
|
||||
NEXT_PUBLIC_COMMIT_SHA=c1541e6
|
||||
NEXT_PUBLIC_COMMIT_SHA=31aec81
|
||||
|
||||
@@ -7,9 +7,11 @@ import { Button } from "../../components/ui/button";
|
||||
import { ScrollArea } from "../../components/ui/scroll-area";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Playlist } from "@/lib/navidrome";
|
||||
import { Playlist, Album } from "@/lib/navidrome";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { useNavidrome } from "./NavidromeContext";
|
||||
import { useRecentlyPlayedAlbums } from "@/hooks/use-recently-played-albums";
|
||||
import { useSidebarShortcuts } from "@/hooks/use-sidebar-shortcuts";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
@@ -29,6 +31,8 @@ interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export function Sidebar({ className, playlists, collapsed = false, onToggle, visible = true, favoriteAlbums = [], onRemoveFavoriteAlbum }: SidebarProps) {
|
||||
const pathname = usePathname();
|
||||
const { api } = useNavidrome();
|
||||
const { recentAlbums } = useRecentlyPlayedAlbums();
|
||||
const { showPlaylists, showAlbums } = useSidebarShortcuts();
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
@@ -337,7 +341,7 @@ export function Sidebar({ className, playlists, collapsed = false, onToggle, vis
|
||||
</Link>
|
||||
|
||||
{/* Favorite Albums Section */}
|
||||
{favoriteAlbums.length > 0 && (
|
||||
{showAlbums && favoriteAlbums.length > 0 && (
|
||||
<>
|
||||
<div className="border-t my-2"></div>
|
||||
{favoriteAlbums.slice(0, 5).map((album) => (
|
||||
@@ -393,9 +397,41 @@ export function Sidebar({ className, playlists, collapsed = false, onToggle, vis
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Recently Played Albums Section */}
|
||||
{showAlbums && recentAlbums.length > 0 && (
|
||||
<>
|
||||
<div className="border-t my-2"></div>
|
||||
{recentAlbums.slice(0, 5).map((album) => {
|
||||
const albumImageUrl = album.coverArt && api
|
||||
? api.getCoverArtUrl(album.coverArt, 32)
|
||||
: '/play.png';
|
||||
|
||||
return (
|
||||
<Link key={album.id} href={`/album/${album.id}`}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-center px-2 h-10"
|
||||
title={`${album.name} - ${album.artist} (Recently Played)`}
|
||||
>
|
||||
<div className="w-6 h-6 rounded-sm overflow-hidden">
|
||||
<Image
|
||||
src={albumImageUrl}
|
||||
alt={album.name}
|
||||
width={24}
|
||||
height={24}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Playlists Section */}
|
||||
{playlists.length > 0 && (
|
||||
{showPlaylists && playlists.length > 0 && (
|
||||
<>
|
||||
<div className="border-t my-2"></div>
|
||||
{playlists.slice(0, 5).map((playlist) => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useTheme } from '@/app/components/ThemeProvider';
|
||||
import { useNavidromeConfig } from '@/app/components/NavidromeConfigContext';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useStandaloneLastFm } from '@/hooks/use-standalone-lastfm';
|
||||
import { useSidebarShortcuts, SidebarShortcutType } from '@/hooks/use-sidebar-shortcuts';
|
||||
import { FaServer, FaUser, FaLock, FaCheck, FaTimes, FaLastfm, FaCog } from 'react-icons/fa';
|
||||
import { Settings, ExternalLink } from 'lucide-react';
|
||||
|
||||
@@ -18,6 +19,7 @@ const SettingsPage = () => {
|
||||
const { config, updateConfig, isConnected, testConnection, clearConfig } = useNavidromeConfig();
|
||||
const { toast } = useToast();
|
||||
const { isEnabled: isStandaloneLastFmEnabled, getCredentials, getAuthUrl, getSessionKey } = useStandaloneLastFm();
|
||||
const { shortcutType, updateShortcutType } = useSidebarShortcuts();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
serverUrl: '',
|
||||
@@ -568,9 +570,29 @@ const SettingsPage = () => {
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sidebar-shortcuts">Sidebar Shortcuts</Label>
|
||||
<Select
|
||||
value={shortcutType}
|
||||
onValueChange={(value: SidebarShortcutType) => updateShortcutType(value)}
|
||||
>
|
||||
<SelectTrigger id="sidebar-shortcuts">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="both">Albums & Playlists</SelectItem>
|
||||
<SelectItem value="albums">Albums Only</SelectItem>
|
||||
<SelectItem value="playlists">Playlists Only</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-muted-foreground space-y-2">
|
||||
<p><strong>Visible:</strong> Sidebar is always shown with icon navigation</p>
|
||||
<p><strong>Hidden:</strong> Sidebar is completely hidden for maximum space</p>
|
||||
<p><strong>Albums & Playlists:</strong> Show both favorite albums, recently played albums, and playlists as shortcuts</p>
|
||||
<p><strong>Albums Only:</strong> Show only favorite and recently played albums as shortcuts</p>
|
||||
<p><strong>Playlists Only:</strong> Show only playlists as shortcuts</p>
|
||||
<p className="mt-3"><strong>Note:</strong> The sidebar now shows only icons with tooltips on hover for a cleaner interface.</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
36
hooks/use-recently-played-albums.ts
Normal file
36
hooks/use-recently-played-albums.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Album } from '@/lib/navidrome';
|
||||
import { useNavidrome } from '@/app/components/NavidromeContext';
|
||||
|
||||
export function useRecentlyPlayedAlbums() {
|
||||
const [recentAlbums, setRecentAlbums] = useState<Album[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { api } = useNavidrome();
|
||||
|
||||
const fetchRecentAlbums = async () => {
|
||||
if (!api) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const albums = await api.getAlbums('recent', 5, 0);
|
||||
setRecentAlbums(albums);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch recent albums:', error);
|
||||
setRecentAlbums([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchRecentAlbums();
|
||||
}, [api]);
|
||||
|
||||
return {
|
||||
recentAlbums,
|
||||
loading,
|
||||
refetch: fetchRecentAlbums
|
||||
};
|
||||
}
|
||||
29
hooks/use-sidebar-shortcuts.ts
Normal file
29
hooks/use-sidebar-shortcuts.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export type SidebarShortcutType = 'playlists' | 'albums' | 'both';
|
||||
|
||||
export function useSidebarShortcuts() {
|
||||
const [shortcutType, setShortcutType] = useState<SidebarShortcutType>('both');
|
||||
|
||||
useEffect(() => {
|
||||
// Load preference from localStorage
|
||||
const savedType = localStorage.getItem('sidebar-shortcut-type');
|
||||
if (savedType && ['playlists', 'albums', 'both'].includes(savedType)) {
|
||||
setShortcutType(savedType as SidebarShortcutType);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const updateShortcutType = (type: SidebarShortcutType) => {
|
||||
setShortcutType(type);
|
||||
localStorage.setItem('sidebar-shortcut-type', type);
|
||||
};
|
||||
|
||||
return {
|
||||
shortcutType,
|
||||
updateShortcutType,
|
||||
showPlaylists: shortcutType === 'playlists' || shortcutType === 'both',
|
||||
showAlbums: shortcutType === 'albums' || shortcutType === 'both'
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user