mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-26 13:57:36 +02:00
Highlight the playlist in the left panel on play (#2025)
* Fixed bad smart playlist field s * first try to add playlist highlight * Simplified calls * Now works for grids too. * Derive the playlist highlight from the currently-playing track's origin instead of a stale global field. * addressed comments
This commit is contained in:
+10
-4
@@ -64,6 +64,7 @@ export const useItemDragDropState = <TElement extends HTMLElement = HTMLDivEleme
|
||||
return draggedItems;
|
||||
},
|
||||
itemType,
|
||||
metadata: { playlistId },
|
||||
onDragStart: () => {
|
||||
if (!item || !isDataRow) {
|
||||
return;
|
||||
@@ -248,10 +249,15 @@ export const useItemDragDropState = <TElement extends HTMLElement = HTMLDivEleme
|
||||
case DragTarget.SONG: {
|
||||
const sourceItems = (args.source.item || []) as Song[];
|
||||
if (sourceItems.length > 0) {
|
||||
playerContext.addToQueueByData(sourceItems, {
|
||||
edge: args.edge,
|
||||
uniqueId: droppedOnUniqueId,
|
||||
});
|
||||
const sourcePlaylistId = args.source.metadata?.playlistId as
|
||||
| string
|
||||
| undefined;
|
||||
playerContext.addToQueueByData(
|
||||
sourceItems,
|
||||
{ edge: args.edge, uniqueId: droppedOnUniqueId },
|
||||
undefined,
|
||||
sourcePlaylistId ?? null,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,12 @@ import {
|
||||
import { Play, PlayerRepeat, PlayerShuffle } from '/@/shared/types/types';
|
||||
|
||||
export interface PlayerContext {
|
||||
addToQueueByData: (data: Song[], type: AddToQueueType, playSongId?: string) => void;
|
||||
addToQueueByData: (
|
||||
data: Song[],
|
||||
type: AddToQueueType,
|
||||
playSongId?: string,
|
||||
contextPlaylistId?: null | string,
|
||||
) => void;
|
||||
addToQueueByFetch: (
|
||||
serverId: string,
|
||||
id: string[],
|
||||
@@ -137,6 +142,23 @@ const getRootQueryKey = (itemType: LibraryItem, serverId: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const isReplaceQueueType = (type: AddToQueueType): boolean => {
|
||||
if (typeof type === 'object') return false;
|
||||
return type === Play.NOW || type === Play.SHUFFLE;
|
||||
};
|
||||
|
||||
// HashRouter puts the route in location.hash, not pathname.
|
||||
const inferPlaylistContextFromUrl = (): null | string => {
|
||||
const route = window.location.hash.replace(/^#/, '');
|
||||
const match = route.match(/^\/playlists\/([^/]+)/);
|
||||
return match ? match[1] : null;
|
||||
};
|
||||
|
||||
// Stamps each song with the playlist it was queued from, so the sidebar highlight
|
||||
// can be derived from whichever song is currently playing (see useCurrentPlaylistContextId).
|
||||
const tagPlaylistContext = (songs: Song[], contextPlaylistId: string): Song[] =>
|
||||
songs.map((song) => ({ ...song, _contextPlaylistId: contextPlaylistId }));
|
||||
|
||||
export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -187,9 +209,20 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
}, [doNotShowAgain, setDoNotShowAgain, t]);
|
||||
|
||||
const addToQueueByData = useCallback(
|
||||
(data: Song[], type: AddToQueueType, playSongId?: string) => {
|
||||
(
|
||||
data: Song[],
|
||||
type: AddToQueueType,
|
||||
playSongId?: string,
|
||||
contextPlaylistId?: null | string,
|
||||
) => {
|
||||
const filters = useSettingsStore.getState().playback.filters;
|
||||
const filteredData = filterSongsByPlayerFilters(data, filters);
|
||||
let filteredData = filterSongsByPlayerFilters(data, filters);
|
||||
const resolvedContextId =
|
||||
contextPlaylistId ??
|
||||
(isReplaceQueueType(type) ? inferPlaylistContextFromUrl() : null);
|
||||
if (resolvedContextId) {
|
||||
filteredData = tagPlaylistContext(filteredData, resolvedContextId);
|
||||
}
|
||||
|
||||
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
||||
const edge = type.edge === 'top' ? 'top' : 'bottom';
|
||||
@@ -279,7 +312,21 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
}
|
||||
|
||||
const filters = useSettingsStore.getState().playback.filters;
|
||||
const filteredSongs = filterSongsByPlayerFilters(sortedSongs, filters);
|
||||
let filteredSongs = filterSongsByPlayerFilters(sortedSongs, filters);
|
||||
|
||||
// Songs from multiple playlists are merged together, so there is no single
|
||||
// playlist to attribute them to: skip tagging (and URL inference) entirely.
|
||||
const isMultiPlaylist = itemType === LibraryItem.PLAYLIST && id.length > 1;
|
||||
const explicitId =
|
||||
itemType === LibraryItem.PLAYLIST && id.length === 1 ? id[0] : null;
|
||||
const resolvedContextId =
|
||||
explicitId ??
|
||||
(!isMultiPlaylist && isReplaceQueueType(type)
|
||||
? inferPlaylistContextFromUrl()
|
||||
: null);
|
||||
if (resolvedContextId) {
|
||||
filteredSongs = tagPlaylistContext(filteredSongs, resolvedContextId);
|
||||
}
|
||||
|
||||
if (typeof type === 'object' && 'edge' in type && type.edge !== null) {
|
||||
const edge = type.edge === 'top' ? 'top' : 'bottom';
|
||||
|
||||
@@ -136,6 +136,10 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.name-active {
|
||||
color: var(--theme-colors-primary);
|
||||
}
|
||||
|
||||
.image-container {
|
||||
flex-shrink: 0;
|
||||
width: 3rem;
|
||||
|
||||
@@ -28,6 +28,7 @@ import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||
import { useDragMonitor } from '/@/renderer/hooks/use-drag-monitor';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import {
|
||||
useCurrentPlaylistContextId,
|
||||
useCurrentServer,
|
||||
useCurrentServerId,
|
||||
usePermissions,
|
||||
@@ -116,6 +117,8 @@ export const PlaylistRowButton = memo(
|
||||
const sidebarPlaylistSorting = useSidebarPlaylistSorting();
|
||||
const sidebarPlaylistMode = useSidebarPlaylistMode();
|
||||
const isCompact = sidebarPlaylistMode === 'compact';
|
||||
const activePlaylistId = useCurrentPlaylistContextId();
|
||||
const isActive = activePlaylistId === item.id;
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const isSmartPlaylist = Boolean(item.rules);
|
||||
@@ -292,7 +295,13 @@ export const PlaylistRowButton = memo(
|
||||
>
|
||||
{isCompact ? (
|
||||
<>
|
||||
<Text className={styles.compactName} fw={500} size="md">
|
||||
<Text
|
||||
className={clsx(styles.compactName, {
|
||||
[styles.nameActive]: isActive,
|
||||
})}
|
||||
fw={500}
|
||||
size="md"
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
{isHovered && (
|
||||
@@ -307,7 +316,13 @@ export const PlaylistRowButton = memo(
|
||||
<div className={styles.rowGroup}>
|
||||
<Image containerClassName={styles.imageContainer} src={imageUrl} />
|
||||
<div className={styles.metadata}>
|
||||
<Text className={styles.name} fw={500} size="md">
|
||||
<Text
|
||||
className={clsx(styles.name, {
|
||||
[styles.nameActive]: isActive,
|
||||
})}
|
||||
fw={500}
|
||||
size="md"
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
<div className={styles.metadataGroup}>
|
||||
|
||||
@@ -1640,6 +1640,7 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
||||
const excludedPlayerKeys = ['playerNum', 'seekToTimestamp', 'status'];
|
||||
|
||||
// If we're not restoring the play queue, we don't need the index property
|
||||
// (it is meaningless without the queue)
|
||||
if (!shouldRestorePlayQueue) {
|
||||
excludedPlayerKeys.push('index');
|
||||
}
|
||||
@@ -2076,6 +2077,7 @@ export const updateQueueSong = (songId: string, updatedSong: Song) => {
|
||||
const uniqueId = song._uniqueId;
|
||||
state.queue.songs[song._uniqueId] = {
|
||||
...updatedSong,
|
||||
_contextPlaylistId: song._contextPlaylistId,
|
||||
_uniqueId: uniqueId,
|
||||
};
|
||||
}
|
||||
@@ -2083,6 +2085,10 @@ export const updateQueueSong = (songId: string, updatedSong: Song) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useCurrentPlaylistContextId = () => {
|
||||
return usePlayerStoreBase((state) => state.getCurrentSong()?._contextPlaylistId ?? null);
|
||||
};
|
||||
|
||||
export const usePlayerMuted = () => {
|
||||
return usePlayerStoreBase((state) => state.player.muted);
|
||||
};
|
||||
|
||||
@@ -73,6 +73,7 @@ export interface QueueData {
|
||||
}
|
||||
|
||||
export type QueueSong = Song & {
|
||||
_contextPlaylistId?: null | string;
|
||||
_uniqueId: string;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user