From 592a479c30e9eb4b5efc86827b1032848f131e0c Mon Sep 17 00:00:00 2001 From: kveld9 Date: Sun, 12 Apr 2026 20:43:31 -0300 Subject: [PATCH] fixed some bugs --- src/components/ContextMenu.tsx | 157 ++++++++++++++++++++------------- src/locales/de.ts | 4 + src/locales/en.ts | 4 + src/locales/es.ts | 4 + src/locales/fr.ts | 4 + src/locales/nb.ts | 4 + src/locales/nl.ts | 1 + src/locales/ru.ts | 4 + src/locales/zh.ts | 1 + src/pages/PlaylistDetail.tsx | 6 +- src/store/playerStore.ts | 8 +- src/store/playlistStore.ts | 3 + 12 files changed, 136 insertions(+), 64 deletions(-) diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index 3c84b138..40f13c90 100644 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -44,6 +44,7 @@ export function AddToPlaylistSubmenu({ songIds, onDone, dropDown, triggerId }: { const [creating, setCreating] = useState(false); const [newName, setNewName] = useState(''); const [flipLeft, setFlipLeft] = useState(false); + const [flipUp, setFlipUp] = useState(false); const storePlaylists = usePlaylistStore((s) => s.playlists); const recentIds = usePlaylistStore((s) => s.recentIds); const createPlaylist = usePlaylistStore((s) => s.createPlaylist); @@ -62,10 +63,12 @@ export function AddToPlaylistSubmenu({ songIds, onDone, dropDown, triggerId }: { }, [storePlaylists, recentIds]); // Flip submenu left if it would overflow the right edge of the viewport + // Flip submenu up if it would overflow the bottom of the viewport useLayoutEffect(() => { if (subRef.current) { const rect = subRef.current.getBoundingClientRect(); if (rect.right > window.innerWidth - 8) setFlipLeft(true); + if (rect.bottom > window.innerHeight - 8) setFlipUp(true); } }, []); @@ -97,8 +100,8 @@ export function AddToPlaylistSubmenu({ songIds, onDone, dropDown, triggerId }: { const subStyle: React.CSSProperties = dropDown ? { top: 'calc(100% + 4px)', left: 0, right: 'auto' } : flipLeft - ? { right: 'calc(100% + 4px)', left: 'auto' } - : { left: 'calc(100% + 4px)', right: 'auto' }; + ? { right: 'calc(100% + 4px)', left: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' } + : { left: 'calc(100% + 4px)', right: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' }; return (
@@ -198,14 +201,18 @@ function MultiAlbumToPlaylistSubmenu({ albumIds, onDone, triggerId }: { albumIds const { t } = useTranslation(); const [resolvedIds, setResolvedIds] = useState(null); const [totalAlbums, setTotalAlbums] = useState(0); + const [showLoading, setShowLoading] = useState(false); useEffect(() => { setTotalAlbums(albumIds.length); + // Delay showing loading state to avoid flash for fast loads + const loadingTimeout = setTimeout(() => setShowLoading(true), 300); (async () => { const albumSongs = await Promise.all(albumIds.map(id => getAlbum(id).then(r => r.songs).catch(() => []))); const allSongs = albumSongs.flat(); setResolvedIds(allSongs.map(s => s.id)); })().catch(() => setResolvedIds([])); + return () => clearTimeout(loadingTimeout); }, [albumIds]); const handleAddWithToast = async (pl: SubsonicPlaylist, songIds: string[]) => { @@ -292,22 +299,26 @@ function MultiAlbumToPlaylistSubmenu({ albumIds, onDone, triggerId }: { albumIds const { t } = useTranslation(); const subRef = useRef(null); const newNameRef = useRef(null); - const [playlists, setPlaylists] = useState([]); const [adding, setAdding] = useState(null); const [creating, setCreating] = useState(false); const [newName, setNewName] = useState(''); const [flipLeft, setFlipLeft] = useState(false); + const [flipUp, setFlipUp] = useState(false); + const [visible, setVisible] = useState(false); + const storePlaylists = usePlaylistStore((s) => s.playlists); - useEffect(() => { - getPlaylists().then((all) => { - setPlaylists(all.sort((a, b) => a.name.localeCompare(b.name))); - }).catch(() => {}); - }, []); + // Sort playlists from store (no fetch needed, prevents flash) + const playlists = useMemo(() => { + return [...storePlaylists].sort((a, b) => a.name.localeCompare(b.name)); + }, [storePlaylists]); useLayoutEffect(() => { if (subRef.current) { const rect = subRef.current.getBoundingClientRect(); if (rect.right > window.innerWidth - 8) setFlipLeft(true); + if (rect.bottom > window.innerHeight - 8) setFlipUp(true); + // Show after position is calculated to prevent flash + setVisible(true); } }, []); @@ -342,11 +353,11 @@ function MultiAlbumToPlaylistSubmenu({ albumIds, onDone, triggerId }: { albumIds }; const subStyle: React.CSSProperties = flipLeft - ? { right: 'calc(100% + 4px)', left: 'auto' } - : { left: 'calc(100% + 4px)', right: 'auto' }; + ? { right: 'calc(100% + 4px)', left: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' } + : { left: 'calc(100% + 4px)', right: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' }; return ( -
+
{!creating ? (
; + } return ( -
+
{t('playlists.loadingAlbums', { count: totalAlbums })} @@ -412,9 +427,12 @@ function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId }: { artist const { t } = useTranslation(); const [resolvedIds, setResolvedIds] = useState(null); const [totalArtists, setTotalArtists] = useState(0); + const [showLoading, setShowLoading] = useState(false); useEffect(() => { setTotalArtists(artistIds.length); + // Delay showing loading state to avoid flash for fast loads + const loadingTimeout = setTimeout(() => setShowLoading(true), 300); (async () => { const allSongs: string[] = []; for (const artistId of artistIds) { @@ -428,6 +446,7 @@ function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId }: { artist } setResolvedIds(allSongs); })().catch(() => setResolvedIds([])); + return () => clearTimeout(loadingTimeout); }, [artistIds]); const handleAddWithToast = async (pl: SubsonicPlaylist, songIds: string[]) => { @@ -495,6 +514,7 @@ function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId }: { artist const [creating, setCreating] = useState(false); const [newName, setNewName] = useState(''); const [flipLeft, setFlipLeft] = useState(false); + const [flipUp, setFlipUp] = useState(false); useEffect(() => { getPlaylists().then((all) => { @@ -506,6 +526,7 @@ function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId }: { artist if (subRef.current) { const rect = subRef.current.getBoundingClientRect(); if (rect.right > window.innerWidth - 8) setFlipLeft(true); + if (rect.bottom > window.innerHeight - 8) setFlipUp(true); } }, []); @@ -540,8 +561,8 @@ function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId }: { artist }; const subStyle: React.CSSProperties = flipLeft - ? { right: 'calc(100% + 4px)', left: 'auto' } - : { left: 'calc(100% + 4px)', right: 'auto' }; + ? { right: 'calc(100% + 4px)', left: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' } + : { left: 'calc(100% + 4px)', right: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' }; return (
@@ -592,8 +613,12 @@ function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId }: { artist } if (resolvedIds === null) { + // Only show loading UI if it takes more than 300ms (avoid flash) + if (!showLoading) { + return
; + } return ( -
+
{t('playlists.loadingArtists', { count: totalArtists })} @@ -609,20 +634,25 @@ function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId }: { artist function SinglePlaylistToPlaylistSubmenu({ playlist, onDone, triggerId }: { playlist: { id: string; name: string }; onDone: () => void; triggerId?: string }) { const { t } = useTranslation(); const subRef = useRef(null); - const [loading, setLoading] = useState(true); - const [allPlaylists, setAllPlaylists] = useState<{ id: string; name: string }[]>([]); const [creating, setCreating] = useState(false); const [newName, setNewName] = useState(''); const newNameRef = useRef(null); + const [flipLeft, setFlipLeft] = useState(false); + const [flipUp, setFlipUp] = useState(false); + const storePlaylists = usePlaylistStore((s) => s.playlists); - useEffect(() => { - (async () => { - const { getPlaylists } = await import('../api/subsonic'); - const pls = await getPlaylists().catch(() => []); - setAllPlaylists(pls.filter((p: { id: string }) => p.id !== playlist.id)); - setLoading(false); - })(); - }, [playlist]); + // Filter out the current playlist from the list + const allPlaylists = useMemo(() => { + return storePlaylists.filter((p) => p.id !== playlist.id); + }, [storePlaylists, playlist.id]); + + useLayoutEffect(() => { + if (subRef.current) { + const rect = subRef.current.getBoundingClientRect(); + if (rect.right > window.innerWidth - 8) setFlipLeft(true); + if (rect.bottom > window.innerHeight - 8) setFlipUp(true); + } + }, []); useEffect(() => { if (creating && newNameRef.current) { @@ -693,17 +723,9 @@ function SinglePlaylistToPlaylistSubmenu({ playlist, onDone, triggerId }: { play } }; - if (loading) { - return ( -
-
-
-
-
- ); - } - - const subStyle: React.CSSProperties = { left: 'calc(100% + 4px)', right: 'auto' }; + const subStyle: React.CSSProperties = flipLeft + ? { right: 'calc(100% + 4px)', left: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' } + : { left: 'calc(100% + 4px)', right: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' }; return (
@@ -757,21 +779,26 @@ function SinglePlaylistToPlaylistSubmenu({ playlist, onDone, triggerId }: { play function MultiPlaylistToPlaylistSubmenu({ playlists, onDone, triggerId }: { playlists: { id: string; name: string }[]; onDone: () => void; triggerId?: string }) { const { t } = useTranslation(); const subRef = useRef(null); - const [loading, setLoading] = useState(true); - const [allPlaylists, setAllPlaylists] = useState<{ id: string; name: string }[]>([]); const [creating, setCreating] = useState(false); const [newName, setNewName] = useState(''); const newNameRef = useRef(null); + const [flipLeft, setFlipLeft] = useState(false); + const [flipUp, setFlipUp] = useState(false); + const storePlaylists = usePlaylistStore((s) => s.playlists); - useEffect(() => { - (async () => { - const { getPlaylists } = await import('../api/subsonic'); - const pls = await getPlaylists().catch(() => []); - const selectedIds = new Set(playlists.map(p => p.id)); - setAllPlaylists(pls.filter((p: { id: string }) => !selectedIds.has(p.id))); - setLoading(false); - })(); - }, [playlists]); + // Filter out the selected playlists from the list + const allPlaylists = useMemo(() => { + const selectedIds = new Set(playlists.map(p => p.id)); + return storePlaylists.filter((p) => !selectedIds.has(p.id)); + }, [storePlaylists, playlists]); + + useLayoutEffect(() => { + if (subRef.current) { + const rect = subRef.current.getBoundingClientRect(); + if (rect.right > window.innerWidth - 8) setFlipLeft(true); + if (rect.bottom > window.innerHeight - 8) setFlipUp(true); + } + }, []); useEffect(() => { if (creating && newNameRef.current) { @@ -860,17 +887,9 @@ function MultiPlaylistToPlaylistSubmenu({ playlists, onDone, triggerId }: { play } }; - if (loading) { - return ( -
-
-
-
-
- ); - } - - const subStyle: React.CSSProperties = { left: 'calc(100% + 4px)', right: 'auto' }; + const subStyle: React.CSSProperties = flipLeft + ? { right: 'calc(100% + 4px)', left: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' } + : { left: 'calc(100% + 4px)', right: 'auto', top: flipUp ? 'auto' : -4, bottom: flipUp ? 0 : 'auto' }; return (
@@ -1064,7 +1083,7 @@ export default function ContextMenu() { }; }, [pendingSubmenuKeyboardFocus, playlistSubmenuOpen, getMenuNavItems, focusMenuItemAt]); - const { type, item, queueIndex } = contextMenu; + const { type, item, queueIndex, playlistId, playlistSongIndex } = contextMenu; const isStarred = (id: string, itemStarred?: string) => id in starredOverrides ? starredOverrides[id] : !!itemStarred; @@ -1485,6 +1504,26 @@ export default function ContextMenu() {
handleAction(() => openSongInfo(song.id))}> {t('contextMenu.songInfo')}
+ {playlistId && playlistSongIndex !== undefined && ( +
handleAction(async () => { + const { getPlaylist, updatePlaylist } = await import('../api/subsonic'); + const { usePlaylistStore } = await import('../store/playlistStore'); + const { showToast } = await import('../utils/toast'); + const touchPlaylist = usePlaylistStore.getState().touchPlaylist; + try { + const { songs } = await getPlaylist(playlistId); + const prevCount = songs.length; + const updatedIds = songs.filter((_, i) => i !== playlistSongIndex).map(s => s.id); + await updatePlaylist(playlistId, updatedIds, prevCount); + touchPlaylist(playlistId); + showToast(t('playlists.removeSuccess'), 3000, 'info'); + } catch { + showToast(t('playlists.removeError'), 4000, 'error'); + } + })}> + {t('contextMenu.removeFromPlaylist')} +
+ )} ); })()} diff --git a/src/locales/de.ts b/src/locales/de.ts index 3c539f4b..6ecf447a 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -106,6 +106,7 @@ export const deTranslation = { unfavoriteArtist: 'Künstler aus Favoriten entfernen', unfavoriteAlbum: 'Album aus Favoriten entfernen', removeFromQueue: 'Diesen Song entfernen', + removeFromPlaylist: 'Aus Playlist entfernen', openAlbum: 'Album öffnen', goToArtist: 'Zum Künstler', download: 'Herunterladen (ZIP)', @@ -947,12 +948,15 @@ export const deTranslation = { select: 'Auswählen', startSelect: 'Auswahl aktivieren', cancelSelect: 'Abbrechen', + loadingAlbums: '{{count}} Alben werden aufgelöst…', loadingArtists: '{{count}} Künstler werden aufgelöst…', myPlaylists: 'Meine Playlists', noOtherPlaylists: 'Keine anderen Playlists verfügbar', addToPlaylistSuccess: '{{count}} Songs zu {{playlist}} hinzugefügt', addToPlaylistNoNew: 'Keine neuen Songs für {{playlist}}', addToPlaylistError: 'Fehler beim Hinzufügen zur Playlist', + removeSuccess: 'Song aus Playlist entfernt', + removeError: 'Fehler beim Entfernen aus der Playlist', }, mostPlayed: { title: 'Meistgehört', diff --git a/src/locales/en.ts b/src/locales/en.ts index 3153ddc0..e0d1d68d 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -107,6 +107,7 @@ export const enTranslation = { unfavoriteArtist: 'Remove Artist from Favorites', unfavoriteAlbum: 'Remove Album from Favorites', removeFromQueue: 'Remove from Queue', + removeFromPlaylist: 'Remove from Playlist', openAlbum: 'Open Album', goToArtist: 'Go to Artist', download: 'Download (ZIP)', @@ -949,12 +950,15 @@ export const enTranslation = { select: 'Select', startSelect: 'Enable selection', cancelSelect: 'Cancel', + loadingAlbums: 'Resolving {{count}} albums…', loadingArtists: 'Resolving {{count}} artists…', myPlaylists: 'My Playlists', noOtherPlaylists: 'No other playlists available', addToPlaylistSuccess: '{{count}} songs added to {{playlist}}', addToPlaylistNoNew: 'No new songs to add to {{playlist}}', addToPlaylistError: 'Error adding to playlist', + removeSuccess: 'Song removed from playlist', + removeError: 'Error removing song from playlist', }, mostPlayed: { title: 'Most Played', diff --git a/src/locales/es.ts b/src/locales/es.ts index cdba265f..e719e5b0 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -107,6 +107,7 @@ export const esTranslation = { unfavoriteArtist: 'Quitar Artista de Favoritos', unfavoriteAlbum: 'Quitar Álbum de Favoritos', removeFromQueue: 'Quitar de la Cola', + removeFromPlaylist: 'Quitar de la Playlist', openAlbum: 'Abrir Álbum', goToArtist: 'Ir al Artista', download: 'Descargar (ZIP)', @@ -950,12 +951,15 @@ export const esTranslation = { select: 'Seleccionar', startSelect: 'Activar selección', cancelSelect: 'Cancelar', + loadingAlbums: 'Resolviendo {{count}} álbumes…', loadingArtists: 'Resolviendo {{count}} artistas…', myPlaylists: 'Mis Listas', noOtherPlaylists: 'No hay otras listas disponibles', addToPlaylistSuccess: '{{count}} canciones agregadas a {{playlist}}', addToPlaylistNoNew: 'No hay canciones nuevas para agregar a {{playlist}}', addToPlaylistError: 'Error al agregar a la lista', + removeSuccess: 'Canción quitada de la playlist', + removeError: 'Error al quitar la canción de la playlist', }, mostPlayed: { title: 'Más Reproducidos', diff --git a/src/locales/fr.ts b/src/locales/fr.ts index b8e15c50..dcfac669 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -106,6 +106,7 @@ export const frTranslation = { unfavoriteArtist: 'Retirer l\'artiste des favoris', unfavoriteAlbum: 'Retirer l\'album des favoris', removeFromQueue: 'Retirer de la file', + removeFromPlaylist: 'Retirer de la playlist', openAlbum: 'Ouvrir l\'album', goToArtist: 'Aller à l\'artiste', download: 'Télécharger (ZIP)', @@ -945,12 +946,15 @@ export const frTranslation = { select: 'Sélectionner', startSelect: 'Activer la sélection', cancelSelect: 'Annuler', + loadingAlbums: 'Résolution de {{count}} albums…', loadingArtists: 'Résolution de {{count}} artistes…', myPlaylists: 'Mes Playlists', noOtherPlaylists: 'Aucune autre playlist disponible', addToPlaylistSuccess: '{{count}} morceaux ajoutés à {{playlist}}', addToPlaylistNoNew: 'Aucun nouveau morceau pour {{playlist}}', addToPlaylistError: 'Erreur lors de l\'ajout à la playlist', + removeSuccess: 'Morceau retiré de la playlist', + removeError: 'Erreur lors du retrait de la playlist', }, mostPlayed: { title: 'Les plus joués', diff --git a/src/locales/nb.ts b/src/locales/nb.ts index 2798fc3d..a76fad14 100644 --- a/src/locales/nb.ts +++ b/src/locales/nb.ts @@ -106,6 +106,7 @@ export const nbTranslation = { unfavoriteArtist: 'Fjern artist fra favoritter', unfavoriteAlbum: 'Fjern album fra favoritter', removeFromQueue: 'Fjern fra kø', + removeFromPlaylist: 'Fjern fra spilleliste', openAlbum: 'Åpne album', goToArtist: 'Gå til artist', download: 'Last ned (ZIP)', @@ -944,12 +945,15 @@ export const nbTranslation = { select: 'Velg', startSelect: 'Aktiver valg', cancelSelect: 'Avbryt', + loadingAlbums: 'Løser {{count}} album…', loadingArtists: 'Løser {{count}} artister…', myPlaylists: 'Mine spillelister', noOtherPlaylists: 'Ingen andre spillelister tilgjengelig', addToPlaylistSuccess: '{{count}} sanger lagt til i {{playlist}}', addToPlaylistNoNew: 'Ingen nye sanger for {{playlist}}', addToPlaylistError: 'Feil ved å legge til i spilleliste', + removeSuccess: 'Sang fjernet fra spilleliste', + removeError: 'Feil ved fjerning fra spilleliste', }, mostPlayed: { title: 'Mest spilt', diff --git a/src/locales/nl.ts b/src/locales/nl.ts index 16eada86..9c90db27 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -945,6 +945,7 @@ export const nlTranslation = { select: 'Selecteren', startSelect: 'Selectie inschakelen', cancelSelect: 'Annuleren', + loadingAlbums: '{{count}} albums oplossen…', loadingArtists: '{{count}} artiesten oplossen…', myPlaylists: 'Mijn playlists', noOtherPlaylists: 'Geen andere playlists beschikbaar', diff --git a/src/locales/ru.ts b/src/locales/ru.ts index ccb4482f..e1beca09 100644 --- a/src/locales/ru.ts +++ b/src/locales/ru.ts @@ -107,6 +107,7 @@ export const ruTranslation = { unfavoriteArtist: 'Убрать исполнителя из избранного', unfavoriteAlbum: 'Убрать альбом из избранного', removeFromQueue: 'Убрать из очереди', + removeFromPlaylist: 'Убрать из плейлиста', openAlbum: 'Открыть альбом', goToArtist: 'К исполнителю', download: 'Скачать (ZIP)', @@ -1004,12 +1005,15 @@ export const ruTranslation = { select: 'Выбрать', startSelect: 'Включить выбор', cancelSelect: 'Отмена', + loadingAlbums: 'Загрузка {{count}} альбомов…', loadingArtists: 'Загрузка {{count}} исполнителей…', myPlaylists: 'Мои плейлисты', noOtherPlaylists: 'Нет других доступных плейлистов', addToPlaylistSuccess: '{{count}} треков добавлено в {{playlist}}', addToPlaylistNoNew: 'Нет новых треков для {{playlist}}', addToPlaylistError: 'Ошибка при добавлении в плейлист', + removeSuccess: 'Трек убран из плейлиста', + removeError: 'Ошибка при удалении из плейлиста', }, mostPlayed: { title: 'Популярное', diff --git a/src/locales/zh.ts b/src/locales/zh.ts index 0aa80781..2c92d681 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -941,6 +941,7 @@ export const zhTranslation = { select: '选择', startSelect: '启用选择', cancelSelect: '取消', + loadingAlbums: '正在解析 {{count}} 张专辑…', loadingArtists: '正在解析 {{count}} 位艺术家…', myPlaylists: '我的播放列表', noOtherPlaylists: '没有其他可用播放列表', diff --git a/src/pages/PlaylistDetail.tsx b/src/pages/PlaylistDetail.tsx index 827fb659..6d49e0e9 100644 --- a/src/pages/PlaylistDetail.tsx +++ b/src/pages/PlaylistDetail.tsx @@ -234,6 +234,8 @@ export default function PlaylistDetail() { }, [contextMenuOpen]); // ── Load ───────────────────────────────────────────────────── + const lastModified = usePlaylistStore(s => (id ? s.lastModified[id] : undefined)); + useEffect(() => { if (!id) return; setLoading(true); @@ -253,7 +255,7 @@ export default function PlaylistDetail() { }) .catch(() => {}) .finally(() => setLoading(false)); - }, [id]); + }, [id, lastModified]); // ── Suggestions ─────────────────────────────────────────────── const loadSuggestions = useCallback(async (currentSongs: SubsonicSong[]) => { @@ -1019,7 +1021,7 @@ export default function PlaylistDetail() { onContextMenu={e => { e.preventDefault(); setContextMenuSongId(song.id); - openContextMenu(e.clientX, e.clientY, songToTrack(song), 'album-song'); + openContextMenu(e.clientX, e.clientY, songToTrack(song), 'album-song', undefined, id, realIdx); }} > {visibleCols.map(colDef => { diff --git a/src/store/playerStore.ts b/src/store/playerStore.ts index 0d41e3d3..fb1899a9 100644 --- a/src/store/playerStore.ts +++ b/src/store/playerStore.ts @@ -176,8 +176,10 @@ interface PlayerState { item: any; type: 'song' | 'album' | 'artist' | 'queue-item' | 'album-song' | 'playlist' | 'multi-album' | 'multi-artist' | 'multi-playlist' | null; queueIndex?: number; + playlistId?: string; + playlistSongIndex?: number; }; - openContextMenu: (x: number, y: number, item: any, type: 'song' | 'album' | 'artist' | 'queue-item' | 'album-song' | 'playlist' | 'multi-album' | 'multi-artist' | 'multi-playlist', queueIndex?: number) => void; + openContextMenu: (x: number, y: number, item: any, type: 'song' | 'album' | 'artist' | 'queue-item' | 'album-song' | 'playlist' | 'multi-album' | 'multi-artist' | 'multi-playlist', queueIndex?: number, playlistId?: string, playlistSongIndex?: number) => void; closeContextMenu: () => void; songInfoModal: { isOpen: boolean; songId: string | null }; @@ -729,8 +731,8 @@ export const usePlayerStore = create()( repeatMode: 'off', contextMenu: { isOpen: false, x: 0, y: 0, item: null, type: null }, - openContextMenu: (x, y, item, type, queueIndex) => set({ - contextMenu: { isOpen: true, x, y, item, type, queueIndex }, + openContextMenu: (x, y, item, type, queueIndex, playlistId, playlistSongIndex) => set({ + contextMenu: { isOpen: true, x, y, item, type, queueIndex, playlistId, playlistSongIndex }, }), closeContextMenu: () => set(state => ({ contextMenu: { ...state.contextMenu, isOpen: false }, diff --git a/src/store/playlistStore.ts b/src/store/playlistStore.ts index f772bf5c..cc6e36bf 100644 --- a/src/store/playlistStore.ts +++ b/src/store/playlistStore.ts @@ -6,6 +6,7 @@ interface PlaylistStore { recentIds: string[]; playlists: SubsonicPlaylist[]; playlistsLoading: boolean; + lastModified: Record; touchPlaylist: (id: string) => void; removeId: (id: string) => void; fetchPlaylists: () => Promise; @@ -19,9 +20,11 @@ export const usePlaylistStore = create()( recentIds: [], playlists: [], playlistsLoading: false, + lastModified: {}, touchPlaylist: (id) => set((s) => ({ recentIds: [id, ...s.recentIds.filter((x) => x !== id)].slice(0, 50), + lastModified: { ...s.lastModified, [id]: Date.now() }, })), removeId: (id) => set((s) => ({ recentIds: s.recentIds.filter((x) => x !== id) })),