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]); +};