mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-16 00:14:23 +02:00
prevent item drop on smart playlists
This commit is contained in:
@@ -7,8 +7,10 @@ import { ActionBar } from '/@/renderer/features/sidebar/components/action-bar';
|
|||||||
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
|
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
|
||||||
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
||||||
import {
|
import {
|
||||||
|
SidebarPlaylistAddDragContext,
|
||||||
SidebarPlaylistList,
|
SidebarPlaylistList,
|
||||||
SidebarSharedPlaylistList,
|
SidebarSharedPlaylistList,
|
||||||
|
useSidebarPlaylistAddDragMonitor,
|
||||||
} from '/@/renderer/features/sidebar/components/sidebar-playlist-list';
|
} from '/@/renderer/features/sidebar/components/sidebar-playlist-list';
|
||||||
import {
|
import {
|
||||||
SidebarItemType,
|
SidebarItemType,
|
||||||
@@ -20,6 +22,17 @@ import { Group } from '/@/shared/components/group/group';
|
|||||||
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
|
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
|
|
||||||
|
const MobileSidebarPlaylistSection = () => {
|
||||||
|
const isAddDragActive = useSidebarPlaylistAddDragMonitor();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarPlaylistAddDragContext.Provider value={isAddDragActive}>
|
||||||
|
<SidebarPlaylistList />
|
||||||
|
<SidebarSharedPlaylistList />
|
||||||
|
</SidebarPlaylistAddDragContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const MobileSidebar = () => {
|
export const MobileSidebar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const sidebarPlaylistList = useSidebarPlaylistList();
|
const sidebarPlaylistList = useSidebarPlaylistList();
|
||||||
@@ -93,12 +106,7 @@ export const MobileSidebar = () => {
|
|||||||
})}
|
})}
|
||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
{sidebarPlaylistList && (
|
{sidebarPlaylistList && <MobileSidebarPlaylistSection />}
|
||||||
<>
|
|
||||||
<SidebarPlaylistList />
|
|
||||||
<SidebarSharedPlaylistList />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { openContextModal } from '@mantine/modals';
|
import { openContextModal } from '@mantine/modals';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import clsx from 'clsx';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, Link } from 'react-router';
|
import { generatePath, Link } from 'react-router';
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
usePlaylistNavigationState,
|
usePlaylistNavigationState,
|
||||||
} from '/@/renderer/features/sidebar/components/playlist-folder-tree';
|
} from '/@/renderer/features/sidebar/components/playlist-folder-tree';
|
||||||
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||||
|
import { useDragMonitor } from '/@/renderer/hooks/use-drag-monitor';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import {
|
import {
|
||||||
useCurrentServer,
|
useCurrentServer,
|
||||||
@@ -39,6 +41,8 @@ import {
|
|||||||
import { formatDurationString } from '/@/renderer/utils';
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
import { Accordion } from '/@/shared/components/accordion/accordion';
|
import { Accordion } from '/@/shared/components/accordion/accordion';
|
||||||
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
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 { ButtonProps } from '/@/shared/components/button/button';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { Icon } from '/@/shared/components/icon/icon';
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
@@ -52,14 +56,50 @@ import {
|
|||||||
Song,
|
Song,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from '/@/shared/types/domain-types';
|
} 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';
|
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 getPlaylistOrderKey = (serverId: string | undefined, scope: 'owned' | 'shared') => {
|
||||||
const sid = serverId || 'local';
|
const sid = serverId || 'local';
|
||||||
return `playlist_order:${sid}:${scope}`;
|
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<ButtonProps, 'onContextMenu' | 'onPlay'> {
|
export interface PlaylistRowButtonProps extends Omit<ButtonProps, 'onContextMenu' | 'onPlay'> {
|
||||||
item: Playlist;
|
item: Playlist;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -80,6 +120,8 @@ export const PlaylistRowButton = memo(
|
|||||||
const isCompact = sidebarPlaylistMode === 'compact';
|
const isCompact = sidebarPlaylistMode === 'compact';
|
||||||
|
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const isSmartPlaylist = Boolean(item.rules);
|
||||||
|
const isAddDragActive = useContext(SidebarPlaylistAddDragContext);
|
||||||
|
|
||||||
const { isDraggedOver, isDragging, ref } = useDragDrop<HTMLAnchorElement>({
|
const { isDraggedOver, isDragging, ref } = useDragDrop<HTMLAnchorElement>({
|
||||||
drag: {
|
drag: {
|
||||||
@@ -97,6 +139,7 @@ export const PlaylistRowButton = memo(
|
|||||||
canDrop: (args) => {
|
canDrop: (args) => {
|
||||||
// Allow dropping items into a playlist (ADD)
|
// Allow dropping items into a playlist (ADD)
|
||||||
const canAdd =
|
const canAdd =
|
||||||
|
!isSmartPlaylist &&
|
||||||
args.source.itemType !== undefined &&
|
args.source.itemType !== undefined &&
|
||||||
args.source.type !== DragTarget.PLAYLIST &&
|
args.source.type !== DragTarget.PLAYLIST &&
|
||||||
(args.source.operation?.includes(DragOperation.ADD) ?? false);
|
(args.source.operation?.includes(DragOperation.ADD) ?? false);
|
||||||
@@ -118,6 +161,7 @@ export const PlaylistRowButton = memo(
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
onDrag: () => {
|
onDrag: () => {
|
||||||
|
console.log('started drag');
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
onDragLeave: () => {
|
onDragLeave: () => {
|
||||||
@@ -150,6 +194,10 @@ export const PlaylistRowButton = memo(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSmartPlaylist) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const modalProps: {
|
const modalProps: {
|
||||||
albumId?: string[];
|
albumId?: string[];
|
||||||
artistId?: string[];
|
artistId?: string[];
|
||||||
@@ -222,13 +270,18 @@ export const PlaylistRowButton = memo(
|
|||||||
type: 'table',
|
type: 'table',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isDimmed = isDragging || (isSmartPlaylist && isAddDragActive);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<MotionLink
|
||||||
|
{...animationProps.fadeIn}
|
||||||
|
animate={isDimmed ? 'hidden' : 'show'}
|
||||||
className={clsx(styles.row, {
|
className={clsx(styles.row, {
|
||||||
[styles.rowCompact]: isCompact,
|
[styles.rowCompact]: isCompact,
|
||||||
[styles.rowDraggedOver]: isDraggedOver,
|
[styles.rowDraggedOver]: isDraggedOver && !isSmartPlaylist,
|
||||||
[styles.rowHover]: isHovered,
|
[styles.rowHover]: isHovered,
|
||||||
})}
|
})}
|
||||||
|
initial={false}
|
||||||
onContextMenu={(e: MouseEvent<HTMLAnchorElement>) => {
|
onContextMenu={(e: MouseEvent<HTMLAnchorElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onContextMenu(e, item);
|
onContextMenu(e, item);
|
||||||
@@ -236,10 +289,8 @@ export const PlaylistRowButton = memo(
|
|||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={{
|
|
||||||
opacity: isDragging ? 0.5 : 1,
|
|
||||||
}}
|
|
||||||
to={url}
|
to={url}
|
||||||
|
variants={playlistRowDimVariants}
|
||||||
>
|
>
|
||||||
{isCompact ? (
|
{isCompact ? (
|
||||||
<>
|
<>
|
||||||
@@ -297,7 +348,7 @@ export const PlaylistRowButton = memo(
|
|||||||
{isHovered && <RowControls id={to} onPlay={handlePlay} />}
|
{isHovered && <RowControls id={to} onPlay={handlePlay} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</MotionLink>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ import { SidebarCollectionList } from '/@/renderer/features/sidebar/components/s
|
|||||||
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
|
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
|
||||||
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
||||||
import {
|
import {
|
||||||
|
SidebarPlaylistAddDragContext,
|
||||||
SidebarPlaylistList,
|
SidebarPlaylistList,
|
||||||
SidebarSharedPlaylistList,
|
SidebarSharedPlaylistList,
|
||||||
|
useSidebarPlaylistAddDragMonitor,
|
||||||
} from '/@/renderer/features/sidebar/components/sidebar-playlist-list';
|
} from '/@/renderer/features/sidebar/components/sidebar-playlist-list';
|
||||||
import {
|
import {
|
||||||
useAppStore,
|
useAppStore,
|
||||||
@@ -45,6 +47,17 @@ import { Tooltip } from '/@/shared/components/tooltip/tooltip';
|
|||||||
import { ExplicitStatus, LibraryItem } from '/@/shared/types/domain-types';
|
import { ExplicitStatus, LibraryItem } from '/@/shared/types/domain-types';
|
||||||
import { Platform } from '/@/shared/types/types';
|
import { Platform } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
const SidebarPlaylistSection = () => {
|
||||||
|
const isAddDragActive = useSidebarPlaylistAddDragMonitor();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarPlaylistAddDragContext.Provider value={isAddDragActive}>
|
||||||
|
<SidebarPlaylistList />
|
||||||
|
<SidebarSharedPlaylistList />
|
||||||
|
</SidebarPlaylistAddDragContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const Sidebar = () => {
|
export const Sidebar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -140,12 +153,7 @@ export const Sidebar = () => {
|
|||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
<SidebarCollectionList />
|
<SidebarCollectionList />
|
||||||
{sidebarPlaylistList && (
|
{sidebarPlaylistList && <SidebarPlaylistSection />}
|
||||||
<>
|
|
||||||
<SidebarPlaylistList />
|
|
||||||
<SidebarSharedPlaylistList />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<AnimatePresence initial={false} mode="popLayout">
|
<AnimatePresence initial={false} mode="popLayout">
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user