support playlist sidebar drag to queue

This commit is contained in:
jeffvli
2025-11-29 13:24:58 -08:00
parent 5e12a666e3
commit 2fa6c9de94
2 changed files with 60 additions and 4 deletions
@@ -15,6 +15,11 @@ export const SidebarItem = ({ children, className, to, ...props }: SidebarItemPr
const toPath = typeof to === 'string' ? to : to.pathname || ''; const toPath = typeof to === 'string' ? to : to.pathname || '';
const isActive = location.pathname === toPath; const isActive = location.pathname === toPath;
const handleLinkDragStart = (e: React.DragEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
};
return ( return (
<Button <Button
className={clsx( className={clsx(
@@ -31,6 +36,8 @@ export const SidebarItem = ({ children, className, to, ...props }: SidebarItemPr
label: styles.label, label: styles.label,
}} }}
component={Link} component={Link}
draggable={false}
onDragStart={handleLinkDragStart}
to={to} to={to}
variant="subtle" variant="subtle"
{...props} {...props}
@@ -7,6 +7,8 @@ import { generatePath, Link } from 'react-router';
import styles from './sidebar-playlist-list.module.css'; import styles from './sidebar-playlist-list.module.css';
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api'; import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
import { CreatePlaylistForm } from '/@/renderer/features/playlists/components/create-playlist-form'; import { CreatePlaylistForm } from '/@/renderer/features/playlists/components/create-playlist-form';
@@ -30,19 +32,34 @@ import {
import { DragOperation, DragTarget } from '/@/shared/types/drag-and-drop'; import { DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
interface PlaylistRowButtonProps extends Omit<ButtonProps, 'onPlay'> { interface PlaylistRowButtonProps extends Omit<ButtonProps, 'onContextMenu' | 'onPlay'> {
item: Playlist;
name: string; name: string;
onContextMenu: (e: MouseEvent<HTMLButtonElement>, item: Playlist) => void;
onPlay: (id: string, playType: Play) => void; onPlay: (id: string, playType: Play) => void;
to: string; to: string;
} }
const PlaylistRowButton = ({ name, onPlay, to, ...props }: PlaylistRowButtonProps) => { const PlaylistRowButton = ({ item, name, onContextMenu, onPlay, to }: PlaylistRowButtonProps) => {
const url = generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: to }); const url = generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: to });
const { t } = useTranslation(); const { t } = useTranslation();
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const { isDraggedOver, ref } = useDragDrop<HTMLDivElement>({ const { isDraggedOver, isDragging, ref } = useDragDrop<HTMLDivElement>({
drag: {
getId: () => {
const draggedItems = getDraggedItems(item, undefined);
return draggedItems.map((draggedItem) => draggedItem.id);
},
getItem: () => {
const draggedItems = getDraggedItems(item, undefined);
return draggedItems;
},
itemType: LibraryItem.PLAYLIST,
operation: [DragOperation.ADD],
target: DragTarget.PLAYLIST,
},
drop: { drop: {
canDrop: (args) => { canDrop: (args) => {
return ( return (
@@ -127,14 +144,17 @@ const PlaylistRowButton = ({ name, onPlay, to, ...props }: PlaylistRowButtonProp
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
ref={ref} ref={ref}
style={{
opacity: isDragging ? 0.5 : 1,
}}
> >
<SidebarItem <SidebarItem
className={clsx({ className={clsx({
[styles.rowHover]: isHovered, [styles.rowHover]: isHovered,
})} })}
onContextMenu={(e) => onContextMenu(e, item)}
to={url} to={url}
variant="subtle" variant="subtle"
{...props}
> >
{name} {name}
</SidebarItem> </SidebarItem>
@@ -241,6 +261,17 @@ export const SidebarPlaylistList = () => {
[player, server.id], [player, server.id],
); );
const handleContextMenu = useCallback(
(e: MouseEvent<HTMLButtonElement>, playlist: Playlist) => {
e.stopPropagation();
ContextMenuController.call({
cmd: { items: [playlist], type: LibraryItem.PLAYLIST },
event: e,
});
},
[],
);
const memoizedItemData = useMemo(() => { const memoizedItemData = useMemo(() => {
const base = { handlePlay: handlePlayPlaylist }; const base = { handlePlay: handlePlayPlaylist };
@@ -317,8 +348,10 @@ export const SidebarPlaylistList = () => {
<Accordion.Panel> <Accordion.Panel>
{memoizedItemData?.items?.map((item, index) => ( {memoizedItemData?.items?.map((item, index) => (
<PlaylistRowButton <PlaylistRowButton
item={item}
key={index} key={index}
name={item.name} name={item.name}
onContextMenu={handleContextMenu}
onPlay={handlePlayPlaylist} onPlay={handlePlayPlaylist}
to={item.id} to={item.id}
/> />
@@ -352,6 +385,20 @@ export const SidebarSharedPlaylistList = () => {
[player, server.id], [player, server.id],
); );
const handleContextMenu = useCallback(
(e: MouseEvent<HTMLButtonElement>, playlist: Playlist) => {
e.stopPropagation();
ContextMenuController.call({
cmd: {
items: [playlist],
type: LibraryItem.PLAYLIST,
},
event: e,
});
},
[],
);
const memoizedItemData = useMemo(() => { const memoizedItemData = useMemo(() => {
const base = { handlePlay: handlePlayPlaylist }; const base = { handlePlay: handlePlayPlaylist };
@@ -386,8 +433,10 @@ export const SidebarSharedPlaylistList = () => {
<Accordion.Panel> <Accordion.Panel>
{memoizedItemData?.items?.map((item, index) => ( {memoizedItemData?.items?.map((item, index) => (
<PlaylistRowButton <PlaylistRowButton
item={item}
key={index} key={index}
name={item.name} name={item.name}
onContextMenu={handleContextMenu}
onPlay={handlePlayPlaylist} onPlay={handlePlayPlaylist}
to={item.id} to={item.id}
/> />