diff --git a/src/renderer/features/sidebar/components/mobile-sidebar.tsx b/src/renderer/features/sidebar/components/mobile-sidebar.tsx
index 4bbd5048a..2018a8eaa 100644
--- a/src/renderer/features/sidebar/components/mobile-sidebar.tsx
+++ b/src/renderer/features/sidebar/components/mobile-sidebar.tsx
@@ -7,8 +7,10 @@ import { ActionBar } from '/@/renderer/features/sidebar/components/action-bar';
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
import {
+ SidebarPlaylistAddDragContext,
SidebarPlaylistList,
SidebarSharedPlaylistList,
+ useSidebarPlaylistAddDragMonitor,
} from '/@/renderer/features/sidebar/components/sidebar-playlist-list';
import {
SidebarItemType,
@@ -20,6 +22,17 @@ import { Group } from '/@/shared/components/group/group';
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
import { Text } from '/@/shared/components/text/text';
+const MobileSidebarPlaylistSection = () => {
+ const isAddDragActive = useSidebarPlaylistAddDragMonitor();
+
+ return (
+
+
+
+
+ );
+};
+
export const MobileSidebar = () => {
const { t } = useTranslation();
const sidebarPlaylistList = useSidebarPlaylistList();
@@ -93,12 +106,7 @@ export const MobileSidebar = () => {
})}
- {sidebarPlaylistList && (
- <>
-
-
- >
- )}
+ {sidebarPlaylistList && }
diff --git a/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx b/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx
index afc17e9cd..2e229ddce 100644
--- a/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx
+++ b/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx
@@ -1,7 +1,8 @@
import { openContextModal } from '@mantine/modals';
import { useQuery } from '@tanstack/react-query';
import clsx from 'clsx';
-import { memo, MouseEvent, useCallback, useMemo, useState } from 'react';
+import { motion } from 'motion/react';
+import { createContext, memo, MouseEvent, useCallback, useContext, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath, Link } from 'react-router';
@@ -27,6 +28,7 @@ import {
usePlaylistNavigationState,
} from '/@/renderer/features/sidebar/components/playlist-folder-tree';
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
+import { useDragMonitor } from '/@/renderer/hooks/use-drag-monitor';
import { AppRoute } from '/@/renderer/router/routes';
import {
useCurrentServer,
@@ -39,6 +41,8 @@ import {
import { formatDurationString } from '/@/renderer/utils';
import { Accordion } from '/@/shared/components/accordion/accordion';
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
+import { animationProps } from '/@/shared/components/animations/animation-props';
+import { animationVariants } from '/@/shared/components/animations/animation-variants';
import { ButtonProps } from '/@/shared/components/button/button';
import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon';
@@ -52,14 +56,50 @@ import {
Song,
SortOrder,
} from '/@/shared/types/domain-types';
-import { DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
+import { DragData, DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
import { Play } from '/@/shared/types/types';
+const MotionLink = motion.create(Link);
+
+const playlistRowDimVariants = animationVariants.combine(animationVariants.fadeIn, {
+ hidden: { opacity: 0.5 },
+});
+
const getPlaylistOrderKey = (serverId: string | undefined, scope: 'owned' | 'shared') => {
const sid = serverId || 'local';
return `playlist_order:${sid}:${scope}`;
};
+export const SidebarPlaylistAddDragContext = createContext(false);
+
+const isAddToPlaylistDragSource = (source: DragData) => {
+ return (
+ source.itemType !== undefined &&
+ source.type !== DragTarget.PLAYLIST &&
+ (source.operation?.includes(DragOperation.ADD) ?? false)
+ );
+};
+
+export const useSidebarPlaylistAddDragMonitor = () => {
+ const [isAddDragActive, setIsAddDragActive] = useState(false);
+
+ const handleAddDragStart = useCallback(() => {
+ setIsAddDragActive(true);
+ }, []);
+
+ const handleAddDragDrop = useCallback(() => {
+ setIsAddDragActive(false);
+ }, []);
+
+ useDragMonitor({
+ canMonitor: isAddToPlaylistDragSource,
+ onDragStart: handleAddDragStart,
+ onDrop: handleAddDragDrop,
+ });
+
+ return isAddDragActive;
+};
+
export interface PlaylistRowButtonProps extends Omit {
item: Playlist;
name: string;
@@ -80,6 +120,8 @@ export const PlaylistRowButton = memo(
const isCompact = sidebarPlaylistMode === 'compact';
const [isHovered, setIsHovered] = useState(false);
+ const isSmartPlaylist = Boolean(item.rules);
+ const isAddDragActive = useContext(SidebarPlaylistAddDragContext);
const { isDraggedOver, isDragging, ref } = useDragDrop({
drag: {
@@ -97,6 +139,7 @@ export const PlaylistRowButton = memo(
canDrop: (args) => {
// Allow dropping items into a playlist (ADD)
const canAdd =
+ !isSmartPlaylist &&
args.source.itemType !== undefined &&
args.source.type !== DragTarget.PLAYLIST &&
(args.source.operation?.includes(DragOperation.ADD) ?? false);
@@ -118,6 +161,7 @@ export const PlaylistRowButton = memo(
};
},
onDrag: () => {
+ console.log('started drag');
return;
},
onDragLeave: () => {
@@ -150,6 +194,10 @@ export const PlaylistRowButton = memo(
return;
}
+ if (isSmartPlaylist) {
+ return;
+ }
+
const modalProps: {
albumId?: string[];
artistId?: string[];
@@ -222,13 +270,18 @@ export const PlaylistRowButton = memo(
type: 'table',
});
+ const isDimmed = isDragging || (isSmartPlaylist && isAddDragActive);
+
return (
- ) => {
e.preventDefault();
onContextMenu(e, item);
@@ -236,10 +289,8 @@ export const PlaylistRowButton = memo(
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
ref={ref}
- style={{
- opacity: isDragging ? 0.5 : 1,
- }}
to={url}
+ variants={playlistRowDimVariants}
>
{isCompact ? (
<>
@@ -297,7 +348,7 @@ export const PlaylistRowButton = memo(
{isHovered && }
>
)}
-
+
);
},
);
diff --git a/src/renderer/features/sidebar/components/sidebar.tsx b/src/renderer/features/sidebar/components/sidebar.tsx
index 27f47ca49..f5d59bf4c 100644
--- a/src/renderer/features/sidebar/components/sidebar.tsx
+++ b/src/renderer/features/sidebar/components/sidebar.tsx
@@ -16,8 +16,10 @@ import { SidebarCollectionList } from '/@/renderer/features/sidebar/components/s
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
import {
+ SidebarPlaylistAddDragContext,
SidebarPlaylistList,
SidebarSharedPlaylistList,
+ useSidebarPlaylistAddDragMonitor,
} from '/@/renderer/features/sidebar/components/sidebar-playlist-list';
import {
useAppStore,
@@ -45,6 +47,17 @@ import { Tooltip } from '/@/shared/components/tooltip/tooltip';
import { ExplicitStatus, LibraryItem } from '/@/shared/types/domain-types';
import { Platform } from '/@/shared/types/types';
+const SidebarPlaylistSection = () => {
+ const isAddDragActive = useSidebarPlaylistAddDragMonitor();
+
+ return (
+
+
+
+
+ );
+};
+
export const Sidebar = () => {
const { t } = useTranslation();
@@ -140,12 +153,7 @@ export const Sidebar = () => {
- {sidebarPlaylistList && (
- <>
-
-
- >
- )}
+ {sidebarPlaylistList && }
diff --git a/src/renderer/hooks/use-drag-monitor.tsx b/src/renderer/hooks/use-drag-monitor.tsx
new file mode 100644
index 000000000..0247abd77
--- /dev/null
+++ b/src/renderer/hooks/use-drag-monitor.tsx
@@ -0,0 +1,33 @@
+import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
+import { useEffect } from 'react';
+
+import { DragData } from '/@/shared/types/drag-and-drop';
+
+interface UseDragMonitorProps {
+ canMonitor?: (source: DragData) => boolean;
+ isEnabled?: boolean;
+ onDragStart?: (source: DragData) => void;
+ onDrop?: () => void;
+}
+
+export const useDragMonitor = ({
+ canMonitor,
+ isEnabled = true,
+ onDragStart,
+ onDrop,
+}: UseDragMonitorProps) => {
+ useEffect(() => {
+ if (!isEnabled) return;
+
+ return monitorForElements({
+ onDragStart: ({ source }) => {
+ const data = source.data as unknown as DragData;
+ if (canMonitor && !canMonitor(data)) return;
+ onDragStart?.(data);
+ },
+ onDrop: () => {
+ onDrop?.();
+ },
+ });
+ }, [canMonitor, isEnabled, onDragStart, onDrop]);
+};