mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
add drag state to item grid
This commit is contained in:
@@ -10,17 +10,15 @@
|
|||||||
border-radius: var(--theme-radius-md);
|
border-radius: var(--theme-radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container.previewed {
|
|
||||||
outline: 2px dashed var(--theme-colors-primary);
|
|
||||||
outline-offset: 2px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container.selected {
|
.container.selected {
|
||||||
outline: 2px solid var(--theme-colors-primary);
|
outline: 2px solid var(--theme-colors-primary);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.image-container {
|
.image-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import { generatePath, Link } from 'react-router';
|
|||||||
import styles from './item-card.module.css';
|
import styles from './item-card.module.css';
|
||||||
|
|
||||||
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
||||||
|
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
||||||
import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
|
import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
import { ItemControls } from '/@/renderer/components/item-list/types';
|
||||||
|
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { Image } from '/@/shared/components/image/image';
|
import { Image } from '/@/shared/components/image/image';
|
||||||
import { Separator } from '/@/shared/components/separator/separator';
|
import { Separator } from '/@/shared/components/separator/separator';
|
||||||
@@ -21,10 +23,12 @@ import {
|
|||||||
Playlist,
|
Playlist,
|
||||||
Song,
|
Song,
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
import { DragTarget } from '/@/shared/types/drag-and-drop';
|
||||||
|
|
||||||
export interface ItemCardProps {
|
export interface ItemCardProps {
|
||||||
controls?: ItemControls;
|
controls?: ItemControls;
|
||||||
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
||||||
|
enableDrag?: boolean;
|
||||||
internalState?: ItemListStateActions;
|
internalState?: ItemListStateActions;
|
||||||
isRound?: boolean;
|
isRound?: boolean;
|
||||||
itemType: LibraryItem;
|
itemType: LibraryItem;
|
||||||
@@ -41,6 +45,7 @@ type DataRow = {
|
|||||||
export const ItemCard = ({
|
export const ItemCard = ({
|
||||||
controls,
|
controls,
|
||||||
data,
|
data,
|
||||||
|
enableDrag,
|
||||||
internalState,
|
internalState,
|
||||||
isRound,
|
isRound,
|
||||||
itemType,
|
itemType,
|
||||||
@@ -56,6 +61,7 @@ export const ItemCard = ({
|
|||||||
<CompactItemCard
|
<CompactItemCard
|
||||||
controls={controls}
|
controls={controls}
|
||||||
data={data}
|
data={data}
|
||||||
|
enableDrag={enableDrag}
|
||||||
imageUrl={imageUrl}
|
imageUrl={imageUrl}
|
||||||
internalState={internalState}
|
internalState={internalState}
|
||||||
isRound={isRound}
|
isRound={isRound}
|
||||||
@@ -69,6 +75,7 @@ export const ItemCard = ({
|
|||||||
<PosterItemCard
|
<PosterItemCard
|
||||||
controls={controls}
|
controls={controls}
|
||||||
data={data}
|
data={data}
|
||||||
|
enableDrag={enableDrag}
|
||||||
imageUrl={imageUrl}
|
imageUrl={imageUrl}
|
||||||
internalState={internalState}
|
internalState={internalState}
|
||||||
isRound={isRound}
|
isRound={isRound}
|
||||||
@@ -83,6 +90,7 @@ export const ItemCard = ({
|
|||||||
<DefaultItemCard
|
<DefaultItemCard
|
||||||
controls={controls}
|
controls={controls}
|
||||||
data={data}
|
data={data}
|
||||||
|
enableDrag={enableDrag}
|
||||||
imageUrl={imageUrl}
|
imageUrl={imageUrl}
|
||||||
internalState={internalState}
|
internalState={internalState}
|
||||||
isRound={isRound}
|
isRound={isRound}
|
||||||
@@ -312,6 +320,7 @@ const DefaultItemCard = ({
|
|||||||
const PosterItemCard = ({
|
const PosterItemCard = ({
|
||||||
controls,
|
controls,
|
||||||
data,
|
data,
|
||||||
|
enableDrag,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
internalState,
|
internalState,
|
||||||
isRound,
|
isRound,
|
||||||
@@ -325,6 +334,43 @@ const PosterItemCard = ({
|
|||||||
? internalState.isSelected((data as any).id)
|
? internalState.isSelected((data as any).id)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
|
const { isDragging: isDraggingLocal, ref } = useDragDrop<HTMLDivElement>({
|
||||||
|
drag: {
|
||||||
|
getId: () => {
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const draggedItems = getDraggedItems(data, itemType, internalState);
|
||||||
|
return draggedItems.map((item) => item.id);
|
||||||
|
},
|
||||||
|
getItem: () => {
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [data];
|
||||||
|
},
|
||||||
|
onDragStart: () => {
|
||||||
|
if (!data || !internalState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const draggedItems = getDraggedItems(data, itemType, internalState);
|
||||||
|
internalState.setDragging(draggedItems);
|
||||||
|
},
|
||||||
|
onDrop: () => {
|
||||||
|
if (internalState) {
|
||||||
|
internalState.setDragging([]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
target: DragTarget.ALBUM,
|
||||||
|
},
|
||||||
|
isEnabled: !!enableDrag && !!data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDragging = data && internalState ? internalState.isDragging(data.id) : isDraggingLocal;
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
if (withControls) {
|
if (withControls) {
|
||||||
@@ -364,8 +410,10 @@ const PosterItemCard = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, styles.poster, {
|
className={clsx(styles.container, styles.poster, {
|
||||||
|
[styles.dragging]: isDragging,
|
||||||
[styles.selected]: isSelected,
|
[styles.selected]: isSelected,
|
||||||
})}
|
})}
|
||||||
|
ref={ref}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
|
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Suspense } from 'react';
|
|||||||
import styles from './expanded-list-item.module.css';
|
import styles from './expanded-list-item.module.css';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ItemListItem,
|
ItemListStateItem,
|
||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { ExpandedAlbumListItem } from '/@/renderer/features/albums/components/expanded-album-list-item';
|
import { ExpandedAlbumListItem } from '/@/renderer/features/albums/components/expanded-album-list-item';
|
||||||
@@ -35,7 +35,7 @@ export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemPr
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface SelectedItemProps {
|
interface SelectedItemProps {
|
||||||
item: ItemListItem;
|
item: ItemListStateItem;
|
||||||
itemType: LibraryItem;
|
itemType: LibraryItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
ItemListStateActions,
|
||||||
|
ItemListStateItem,
|
||||||
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
|
import {
|
||||||
|
Album,
|
||||||
|
AlbumArtist,
|
||||||
|
Artist,
|
||||||
|
LibraryItem,
|
||||||
|
Playlist,
|
||||||
|
Song,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts domain data to ItemListStateItem format
|
||||||
|
*/
|
||||||
|
const convertToItemListItem = (
|
||||||
|
data: Album | AlbumArtist | Artist | Playlist | Song,
|
||||||
|
itemType: LibraryItem,
|
||||||
|
): ItemListStateItem => {
|
||||||
|
return {
|
||||||
|
_serverId: data._serverId,
|
||||||
|
id: data.id,
|
||||||
|
itemType,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the items that should be dragged based on the current data and selection state.
|
||||||
|
* If the current item is already selected, drag all selected items.
|
||||||
|
* Otherwise, select and drag only the current item.
|
||||||
|
*
|
||||||
|
* @param data - The item data to drag (Album, AlbumArtist, Artist, Playlist, or Song)
|
||||||
|
* @param itemType - The type of library item
|
||||||
|
* @param internalState - The item list state actions (optional)
|
||||||
|
* @param updateSelection - Whether to update the selection state (default: true)
|
||||||
|
* @returns Array of ItemListItem objects that should be dragged
|
||||||
|
*/
|
||||||
|
export const getDraggedItems = (
|
||||||
|
data: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
||||||
|
itemType: LibraryItem,
|
||||||
|
internalState?: ItemListStateActions,
|
||||||
|
updateSelection: boolean = true,
|
||||||
|
): ItemListStateItem[] => {
|
||||||
|
if (!data || !internalState) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert data to ItemListStateItem format
|
||||||
|
const draggedItem = convertToItemListItem(data, itemType);
|
||||||
|
|
||||||
|
const previouslySelected = internalState.getSelected();
|
||||||
|
const isDraggingSelectedItem = previouslySelected.some((selected) => selected.id === data.id);
|
||||||
|
|
||||||
|
const draggedItems: ItemListStateItem[] = [];
|
||||||
|
|
||||||
|
if (isDraggingSelectedItem) {
|
||||||
|
// If dragging a selected item, drag all selected items
|
||||||
|
draggedItems.push(...previouslySelected);
|
||||||
|
} else {
|
||||||
|
// If dragging an unselected item, select it and drag only it
|
||||||
|
if (updateSelection) {
|
||||||
|
internalState.setSelected([draggedItem]);
|
||||||
|
}
|
||||||
|
draggedItems.push(draggedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return draggedItems;
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { ItemListItem } from '/@/renderer/components/item-list/helpers/item-list-state';
|
import { ItemListStateItem } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { DefaultItemControlProps, ItemControls } from '/@/renderer/components/item-list/types';
|
import { DefaultItemControlProps, ItemControls } from '/@/renderer/components/item-list/types';
|
||||||
import { usePlayerContext } from '/@/renderer/features/player/context/player-context';
|
import { usePlayerContext } from '/@/renderer/features/player/context/player-context';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
@@ -15,7 +15,7 @@ export const useDefaultItemListControls = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemListItem: ItemListItem = {
|
const itemListItem: ItemListStateItem = {
|
||||||
_serverId: item._serverId,
|
_serverId: item._serverId,
|
||||||
id: item.id,
|
id: item.id,
|
||||||
itemType,
|
itemType,
|
||||||
@@ -61,7 +61,7 @@ export const useDefaultItemListControls = () => {
|
|||||||
const startIndex = Math.min(lastIndex, currentIndex);
|
const startIndex = Math.min(lastIndex, currentIndex);
|
||||||
const stopIndex = Math.max(lastIndex, currentIndex);
|
const stopIndex = Math.max(lastIndex, currentIndex);
|
||||||
|
|
||||||
const rangeItems: ItemListItem[] = [];
|
const rangeItems: ItemListStateItem[] = [];
|
||||||
for (let i = startIndex; i <= stopIndex; i++) {
|
for (let i = startIndex; i <= stopIndex; i++) {
|
||||||
const rangeItem = validData[i];
|
const rangeItem = validData[i];
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ItemListAction, ItemListItem, ItemListState } from './item-list-state';
|
import { ItemListAction, ItemListStateItem, ItemListState } from './item-list-state';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action creators for item grid state management
|
* Action creators for item grid state management
|
||||||
@@ -17,22 +17,27 @@ export const itemGridActions = {
|
|||||||
type: 'CLEAR_SELECTED',
|
type: 'CLEAR_SELECTED',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setExpanded: (items: ItemListItem[]): ItemListAction => ({
|
setDragging: (items: ItemListStateItem[]): ItemListAction => ({
|
||||||
|
payload: items,
|
||||||
|
type: 'SET_DRAGGING',
|
||||||
|
}),
|
||||||
|
|
||||||
|
setExpanded: (items: ItemListStateItem[]): ItemListAction => ({
|
||||||
payload: items,
|
payload: items,
|
||||||
type: 'SET_EXPANDED',
|
type: 'SET_EXPANDED',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setSelected: (items: ItemListItem[]): ItemListAction => ({
|
setSelected: (items: ItemListStateItem[]): ItemListAction => ({
|
||||||
payload: items,
|
payload: items,
|
||||||
type: 'SET_SELECTED',
|
type: 'SET_SELECTED',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
toggleExpanded: (item: ItemListItem): ItemListAction => ({
|
toggleExpanded: (item: ItemListStateItem): ItemListAction => ({
|
||||||
payload: item,
|
payload: item,
|
||||||
type: 'TOGGLE_EXPANDED',
|
type: 'TOGGLE_EXPANDED',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
toggleSelected: (item: ItemListItem): ItemListAction => ({
|
toggleSelected: (item: ItemListStateItem): ItemListAction => ({
|
||||||
payload: item,
|
payload: item,
|
||||||
type: 'TOGGLE_SELECTED',
|
type: 'TOGGLE_SELECTED',
|
||||||
}),
|
}),
|
||||||
@@ -43,7 +48,19 @@ export const itemGridActions = {
|
|||||||
* These can be reused to extract specific data from state
|
* These can be reused to extract specific data from state
|
||||||
*/
|
*/
|
||||||
export const itemGridSelectors = {
|
export const itemGridSelectors = {
|
||||||
getExpanded: (state: ItemListState): ItemListItem[] => {
|
getDragging: (state: ItemListState): ItemListStateItem[] => {
|
||||||
|
return Array.from(state.draggingItems.values());
|
||||||
|
},
|
||||||
|
|
||||||
|
getDraggingCount: (state: ItemListState): number => {
|
||||||
|
return state.dragging.size;
|
||||||
|
},
|
||||||
|
|
||||||
|
getDraggingIds: (state: ItemListState): string[] => {
|
||||||
|
return Array.from(state.dragging);
|
||||||
|
},
|
||||||
|
|
||||||
|
getExpanded: (state: ItemListState): ItemListStateItem[] => {
|
||||||
return Array.from(state.expandedItems.values());
|
return Array.from(state.expandedItems.values());
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -55,7 +72,7 @@ export const itemGridSelectors = {
|
|||||||
return Array.from(state.expanded);
|
return Array.from(state.expanded);
|
||||||
},
|
},
|
||||||
|
|
||||||
getSelected: (state: ItemListState): ItemListItem[] => {
|
getSelected: (state: ItemListState): ItemListStateItem[] => {
|
||||||
return Array.from(state.selectedItems.values());
|
return Array.from(state.selectedItems.values());
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -71,6 +88,10 @@ export const itemGridSelectors = {
|
|||||||
return state.version;
|
return state.version;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasAnyDragging: (state: ItemListState): boolean => {
|
||||||
|
return state.dragging.size > 0;
|
||||||
|
},
|
||||||
|
|
||||||
hasAnyExpanded: (state: ItemListState): boolean => {
|
hasAnyExpanded: (state: ItemListState): boolean => {
|
||||||
return state.expanded.size > 0;
|
return state.expanded.size > 0;
|
||||||
},
|
},
|
||||||
@@ -79,6 +100,10 @@ export const itemGridSelectors = {
|
|||||||
return state.selected.size > 0;
|
return state.selected.size > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isDragging: (state: ItemListState, itemId: string): boolean => {
|
||||||
|
return state.dragging.has(itemId);
|
||||||
|
},
|
||||||
|
|
||||||
isExpanded: (state: ItemListState, itemId: string): boolean => {
|
isExpanded: (state: ItemListState, itemId: string): boolean => {
|
||||||
return state.expanded.has(itemId);
|
return state.expanded.has(itemId);
|
||||||
},
|
},
|
||||||
@@ -120,7 +145,10 @@ export const itemListUtils = {
|
|||||||
/**
|
/**
|
||||||
* Toggle expansion of all items in a list
|
* Toggle expansion of all items in a list
|
||||||
*/
|
*/
|
||||||
toggleAllExpanded: (items: ItemListItem[], currentState: ItemListState): ItemListAction => {
|
toggleAllExpanded: (
|
||||||
|
items: ItemListStateItem[],
|
||||||
|
currentState: ItemListState,
|
||||||
|
): ItemListAction => {
|
||||||
const allExpanded = items.every((item) => currentState.expanded.has(item.id));
|
const allExpanded = items.every((item) => currentState.expanded.has(item.id));
|
||||||
return allExpanded ? itemGridActions.clearExpanded() : itemGridActions.setExpanded(items);
|
return allExpanded ? itemGridActions.clearExpanded() : itemGridActions.setExpanded(items);
|
||||||
},
|
},
|
||||||
@@ -128,7 +156,10 @@ export const itemListUtils = {
|
|||||||
/**
|
/**
|
||||||
* Toggle selection of all items in a list
|
* Toggle selection of all items in a list
|
||||||
*/
|
*/
|
||||||
toggleAllSelected: (items: ItemListItem[], currentState: ItemListState): ItemListAction => {
|
toggleAllSelected: (
|
||||||
|
items: ItemListStateItem[],
|
||||||
|
currentState: ItemListState,
|
||||||
|
): ItemListAction => {
|
||||||
const allSelected = items.every((item) => currentState.selected.has(item.id));
|
const allSelected = items.every((item) => currentState.selected.has(item.id));
|
||||||
return allSelected ? itemGridActions.clearSelected() : itemGridActions.setSelected(items);
|
return allSelected ? itemGridActions.clearSelected() : itemGridActions.setSelected(items);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,47 +4,57 @@ import { itemGridSelectors } from '/@/renderer/components/item-list/helpers/item
|
|||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export type ItemListAction =
|
export type ItemListAction =
|
||||||
| { payload: ItemListItem; type: 'TOGGLE_EXPANDED' }
|
| { payload: ItemListStateItem; type: 'TOGGLE_EXPANDED' }
|
||||||
| { payload: ItemListItem; type: 'TOGGLE_SELECTED' }
|
| { payload: ItemListStateItem; type: 'TOGGLE_SELECTED' }
|
||||||
| { payload: ItemListItem[]; type: 'SET_EXPANDED' }
|
| { payload: ItemListStateItem[]; type: 'SET_DRAGGING' }
|
||||||
| { payload: ItemListItem[]; type: 'SET_SELECTED' }
|
| { payload: ItemListStateItem[]; type: 'SET_EXPANDED' }
|
||||||
|
| { payload: ItemListStateItem[]; type: 'SET_SELECTED' }
|
||||||
| { type: 'CLEAR_ALL' }
|
| { type: 'CLEAR_ALL' }
|
||||||
|
| { type: 'CLEAR_DRAGGING' }
|
||||||
| { type: 'CLEAR_EXPANDED' }
|
| { type: 'CLEAR_EXPANDED' }
|
||||||
| { type: 'CLEAR_SELECTED' };
|
| { type: 'CLEAR_SELECTED' };
|
||||||
|
|
||||||
export interface ItemListItem {
|
|
||||||
_serverId: string;
|
|
||||||
id: string;
|
|
||||||
itemType: LibraryItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ItemListState {
|
export interface ItemListState {
|
||||||
|
dragging: Set<string>;
|
||||||
|
draggingItems: Map<string, ItemListStateItem>;
|
||||||
expanded: Set<string>;
|
expanded: Set<string>;
|
||||||
expandedItems: Map<string, ItemListItem>;
|
expandedItems: Map<string, ItemListStateItem>;
|
||||||
selected: Set<string>;
|
selected: Set<string>;
|
||||||
selectedItems: Map<string, ItemListItem>;
|
selectedItems: Map<string, ItemListStateItem>;
|
||||||
version: number;
|
version: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemListStateActions {
|
export interface ItemListStateActions {
|
||||||
clearAll: () => void;
|
clearAll: () => void;
|
||||||
|
clearDragging: () => void;
|
||||||
clearExpanded: () => void;
|
clearExpanded: () => void;
|
||||||
clearSelected: () => void;
|
clearSelected: () => void;
|
||||||
findItemIndex: (itemId: string) => number;
|
findItemIndex: (itemId: string) => number;
|
||||||
getData: () => unknown[];
|
getData: () => unknown[];
|
||||||
getExpanded: () => ItemListItem[];
|
getDragging: () => ItemListStateItem[];
|
||||||
|
getDraggingIds: () => string[];
|
||||||
|
getExpanded: () => ItemListStateItem[];
|
||||||
getExpandedIds: () => string[];
|
getExpandedIds: () => string[];
|
||||||
getSelected: () => ItemListItem[];
|
getSelected: () => ItemListStateItem[];
|
||||||
getSelectedIds: () => string[];
|
getSelectedIds: () => string[];
|
||||||
getVersion: () => number;
|
getVersion: () => number;
|
||||||
|
hasDragging: () => boolean;
|
||||||
hasExpanded: () => boolean;
|
hasExpanded: () => boolean;
|
||||||
hasSelected: () => boolean;
|
hasSelected: () => boolean;
|
||||||
|
isDragging: (itemId: string) => boolean;
|
||||||
isExpanded: (itemId: string) => boolean;
|
isExpanded: (itemId: string) => boolean;
|
||||||
isSelected: (itemId: string) => boolean;
|
isSelected: (itemId: string) => boolean;
|
||||||
setExpanded: (items: ItemListItem[]) => void;
|
setDragging: (items: ItemListStateItem[]) => void;
|
||||||
setSelected: (items: ItemListItem[]) => void;
|
setExpanded: (items: ItemListStateItem[]) => void;
|
||||||
toggleExpanded: (item: ItemListItem) => void;
|
setSelected: (items: ItemListStateItem[]) => void;
|
||||||
toggleSelected: (item: ItemListItem) => void;
|
toggleExpanded: (item: ItemListStateItem) => void;
|
||||||
|
toggleSelected: (item: ItemListStateItem) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ItemListStateItem {
|
||||||
|
_serverId: string;
|
||||||
|
id: string;
|
||||||
|
itemType: LibraryItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,6 +66,8 @@ export const itemListReducer = (state: ItemListState, action: ItemListAction): I
|
|||||||
case 'CLEAR_ALL':
|
case 'CLEAR_ALL':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
dragging: new Set(),
|
||||||
|
draggingItems: new Map(),
|
||||||
expanded: new Set(),
|
expanded: new Set(),
|
||||||
expandedItems: new Map(),
|
expandedItems: new Map(),
|
||||||
selected: new Set(),
|
selected: new Set(),
|
||||||
@@ -63,6 +75,14 @@ export const itemListReducer = (state: ItemListState, action: ItemListAction): I
|
|||||||
version: state.version + 1,
|
version: state.version + 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case 'CLEAR_DRAGGING':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
dragging: new Set(),
|
||||||
|
draggingItems: new Map(),
|
||||||
|
version: state.version + 1,
|
||||||
|
};
|
||||||
|
|
||||||
case 'CLEAR_EXPANDED':
|
case 'CLEAR_EXPANDED':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -79,9 +99,26 @@ export const itemListReducer = (state: ItemListState, action: ItemListAction): I
|
|||||||
version: state.version + 1,
|
version: state.version + 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case 'SET_DRAGGING': {
|
||||||
|
const newDragging = new Set<string>();
|
||||||
|
const newDraggingItems = new Map<string, ItemListStateItem>();
|
||||||
|
|
||||||
|
action.payload.forEach((item) => {
|
||||||
|
newDragging.add(item.id);
|
||||||
|
newDraggingItems.set(item.id, item);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
dragging: newDragging,
|
||||||
|
draggingItems: newDraggingItems,
|
||||||
|
version: state.version + 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_EXPANDED': {
|
case 'SET_EXPANDED': {
|
||||||
const newExpanded = new Set<string>();
|
const newExpanded = new Set<string>();
|
||||||
const newExpandedItems = new Map<string, ItemListItem>();
|
const newExpandedItems = new Map<string, ItemListStateItem>();
|
||||||
|
|
||||||
console.log('SET_EXPANDED', action.payload);
|
console.log('SET_EXPANDED', action.payload);
|
||||||
|
|
||||||
@@ -101,7 +138,7 @@ export const itemListReducer = (state: ItemListState, action: ItemListAction): I
|
|||||||
|
|
||||||
case 'SET_SELECTED': {
|
case 'SET_SELECTED': {
|
||||||
const newSelected = new Set<string>();
|
const newSelected = new Set<string>();
|
||||||
const newSelectedItems = new Map<string, ItemListItem>();
|
const newSelectedItems = new Map<string, ItemListStateItem>();
|
||||||
|
|
||||||
action.payload.forEach((item) => {
|
action.payload.forEach((item) => {
|
||||||
newSelected.add(item.id);
|
newSelected.add(item.id);
|
||||||
@@ -118,7 +155,7 @@ export const itemListReducer = (state: ItemListState, action: ItemListAction): I
|
|||||||
|
|
||||||
case 'TOGGLE_EXPANDED': {
|
case 'TOGGLE_EXPANDED': {
|
||||||
const newExpanded = new Set<string>();
|
const newExpanded = new Set<string>();
|
||||||
const newExpandedItems = new Map<string, ItemListItem>();
|
const newExpandedItems = new Map<string, ItemListStateItem>();
|
||||||
|
|
||||||
// If the item is already expanded, collapse it
|
// If the item is already expanded, collapse it
|
||||||
if (state.expanded.has(action.payload.id)) {
|
if (state.expanded.has(action.payload.id)) {
|
||||||
@@ -163,6 +200,8 @@ export const itemListReducer = (state: ItemListState, action: ItemListAction): I
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const initialItemListState: ItemListState = {
|
export const initialItemListState: ItemListState = {
|
||||||
|
dragging: new Set(),
|
||||||
|
draggingItems: new Map(),
|
||||||
expanded: new Set(),
|
expanded: new Set(),
|
||||||
expandedItems: new Map(),
|
expandedItems: new Map(),
|
||||||
selected: new Set(),
|
selected: new Set(),
|
||||||
@@ -173,19 +212,23 @@ export const initialItemListState: ItemListState = {
|
|||||||
export const useItemListState = (getDataFn?: () => unknown[]): ItemListStateActions => {
|
export const useItemListState = (getDataFn?: () => unknown[]): ItemListStateActions => {
|
||||||
const [state, dispatch] = useReducer(itemListReducer, initialItemListState);
|
const [state, dispatch] = useReducer(itemListReducer, initialItemListState);
|
||||||
|
|
||||||
const setExpanded = useCallback((items: ItemListItem[]) => {
|
const setExpanded = useCallback((items: ItemListStateItem[]) => {
|
||||||
dispatch({ payload: items, type: 'SET_EXPANDED' });
|
dispatch({ payload: items, type: 'SET_EXPANDED' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setSelected = useCallback((items: ItemListItem[]) => {
|
const setDragging = useCallback((items: ItemListStateItem[]) => {
|
||||||
|
dispatch({ payload: items, type: 'SET_DRAGGING' });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setSelected = useCallback((items: ItemListStateItem[]) => {
|
||||||
dispatch({ payload: items, type: 'SET_SELECTED' });
|
dispatch({ payload: items, type: 'SET_SELECTED' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleExpanded = useCallback((item: ItemListItem) => {
|
const toggleExpanded = useCallback((item: ItemListStateItem) => {
|
||||||
dispatch({ payload: item, type: 'TOGGLE_EXPANDED' });
|
dispatch({ payload: item, type: 'TOGGLE_EXPANDED' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleSelected = useCallback((item: ItemListItem) => {
|
const toggleSelected = useCallback((item: ItemListStateItem) => {
|
||||||
dispatch({ payload: item, type: 'TOGGLE_SELECTED' });
|
dispatch({ payload: item, type: 'TOGGLE_SELECTED' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -207,10 +250,18 @@ export const useItemListState = (getDataFn?: () => unknown[]): ItemListStateActi
|
|||||||
return itemGridSelectors.getExpanded(state);
|
return itemGridSelectors.getExpanded(state);
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
|
const getDragging = useCallback(() => {
|
||||||
|
return itemGridSelectors.getDragging(state);
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
const getSelected = useCallback(() => {
|
const getSelected = useCallback(() => {
|
||||||
return itemGridSelectors.getSelected(state);
|
return itemGridSelectors.getSelected(state);
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
|
const getDraggingIds = useCallback(() => {
|
||||||
|
return Array.from(state.dragging);
|
||||||
|
}, [state.dragging]);
|
||||||
|
|
||||||
const getExpandedIds = useCallback(() => {
|
const getExpandedIds = useCallback(() => {
|
||||||
return Array.from(state.expanded);
|
return Array.from(state.expanded);
|
||||||
}, [state.expanded]);
|
}, [state.expanded]);
|
||||||
@@ -223,6 +274,10 @@ export const useItemListState = (getDataFn?: () => unknown[]): ItemListStateActi
|
|||||||
dispatch({ type: 'CLEAR_EXPANDED' });
|
dispatch({ type: 'CLEAR_EXPANDED' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const clearDragging = useCallback(() => {
|
||||||
|
dispatch({ type: 'CLEAR_DRAGGING' });
|
||||||
|
}, []);
|
||||||
|
|
||||||
const clearSelected = useCallback(() => {
|
const clearSelected = useCallback(() => {
|
||||||
dispatch({ type: 'CLEAR_SELECTED' });
|
dispatch({ type: 'CLEAR_SELECTED' });
|
||||||
}, []);
|
}, []);
|
||||||
@@ -239,10 +294,21 @@ export const useItemListState = (getDataFn?: () => unknown[]): ItemListStateActi
|
|||||||
return itemGridSelectors.hasAnyExpanded(state);
|
return itemGridSelectors.hasAnyExpanded(state);
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
|
const hasDragging = useCallback(() => {
|
||||||
|
return itemGridSelectors.hasAnyDragging(state);
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
const hasSelected = useCallback(() => {
|
const hasSelected = useCallback(() => {
|
||||||
return itemGridSelectors.hasAnySelected(state);
|
return itemGridSelectors.hasAnySelected(state);
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
|
const isDragging = useCallback(
|
||||||
|
(itemId: string) => {
|
||||||
|
return itemGridSelectors.isDragging(state, itemId);
|
||||||
|
},
|
||||||
|
[state],
|
||||||
|
);
|
||||||
|
|
||||||
const getData = useCallback(() => {
|
const getData = useCallback(() => {
|
||||||
return getDataFn ? getDataFn() : [];
|
return getDataFn ? getDataFn() : [];
|
||||||
}, [getDataFn]);
|
}, [getDataFn]);
|
||||||
@@ -260,19 +326,25 @@ export const useItemListState = (getDataFn?: () => unknown[]): ItemListStateActi
|
|||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
clearAll,
|
clearAll,
|
||||||
|
clearDragging,
|
||||||
clearExpanded,
|
clearExpanded,
|
||||||
clearSelected,
|
clearSelected,
|
||||||
findItemIndex,
|
findItemIndex,
|
||||||
getData,
|
getData,
|
||||||
|
getDragging,
|
||||||
|
getDraggingIds,
|
||||||
getExpanded,
|
getExpanded,
|
||||||
getExpandedIds,
|
getExpandedIds,
|
||||||
getSelected,
|
getSelected,
|
||||||
getSelectedIds,
|
getSelectedIds,
|
||||||
getVersion,
|
getVersion,
|
||||||
|
hasDragging,
|
||||||
hasExpanded,
|
hasExpanded,
|
||||||
hasSelected,
|
hasSelected,
|
||||||
|
isDragging,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
setDragging,
|
||||||
setExpanded,
|
setExpanded,
|
||||||
setSelected,
|
setSelected,
|
||||||
toggleExpanded,
|
toggleExpanded,
|
||||||
@@ -280,19 +352,25 @@ export const useItemListState = (getDataFn?: () => unknown[]): ItemListStateActi
|
|||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
clearAll,
|
clearAll,
|
||||||
|
clearDragging,
|
||||||
clearExpanded,
|
clearExpanded,
|
||||||
clearSelected,
|
clearSelected,
|
||||||
findItemIndex,
|
findItemIndex,
|
||||||
getData,
|
getData,
|
||||||
|
getDragging,
|
||||||
|
getDraggingIds,
|
||||||
getExpanded,
|
getExpanded,
|
||||||
getExpandedIds,
|
getExpandedIds,
|
||||||
getSelected,
|
getSelected,
|
||||||
getSelectedIds,
|
getSelectedIds,
|
||||||
getVersion,
|
getVersion,
|
||||||
|
hasDragging,
|
||||||
hasExpanded,
|
hasExpanded,
|
||||||
hasSelected,
|
hasSelected,
|
||||||
|
isDragging,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
setDragging,
|
||||||
setExpanded,
|
setExpanded,
|
||||||
setSelected,
|
setSelected,
|
||||||
toggleExpanded,
|
toggleExpanded,
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ import { ExpandedListContainer } from '/@/renderer/components/item-list/expanded
|
|||||||
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
||||||
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
||||||
import {
|
import {
|
||||||
ItemListItem,
|
|
||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
|
ItemListStateItem,
|
||||||
useItemListState,
|
useItemListState,
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types';
|
import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types';
|
||||||
@@ -46,6 +46,7 @@ import { LibraryItem } from '/@/shared/types/domain-types';
|
|||||||
interface VirtualizedGridListProps {
|
interface VirtualizedGridListProps {
|
||||||
controls: ItemControls;
|
controls: ItemControls;
|
||||||
data: unknown[];
|
data: unknown[];
|
||||||
|
enableDrag?: boolean;
|
||||||
enableExpansion: boolean;
|
enableExpansion: boolean;
|
||||||
enableSelection: boolean;
|
enableSelection: boolean;
|
||||||
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||||
@@ -68,6 +69,7 @@ const VirtualizedGridList = React.memo(
|
|||||||
({
|
({
|
||||||
controls,
|
controls,
|
||||||
data,
|
data,
|
||||||
|
enableDrag,
|
||||||
enableExpansion,
|
enableExpansion,
|
||||||
enableSelection,
|
enableSelection,
|
||||||
gap,
|
gap,
|
||||||
@@ -86,6 +88,7 @@ const VirtualizedGridList = React.memo(
|
|||||||
columns: tableMeta?.columnCount || 0,
|
columns: tableMeta?.columnCount || 0,
|
||||||
controls,
|
controls,
|
||||||
data,
|
data,
|
||||||
|
enableDrag,
|
||||||
enableExpansion,
|
enableExpansion,
|
||||||
enableSelection,
|
enableSelection,
|
||||||
gap,
|
gap,
|
||||||
@@ -97,6 +100,7 @@ const VirtualizedGridList = React.memo(
|
|||||||
tableMeta,
|
tableMeta,
|
||||||
controls,
|
controls,
|
||||||
data,
|
data,
|
||||||
|
enableDrag,
|
||||||
enableExpansion,
|
enableExpansion,
|
||||||
enableSelection,
|
enableSelection,
|
||||||
gap,
|
gap,
|
||||||
@@ -229,6 +233,7 @@ export interface GridItemProps {
|
|||||||
columns: number;
|
columns: number;
|
||||||
controls: ItemCardProps['controls'];
|
controls: ItemCardProps['controls'];
|
||||||
data: any[];
|
data: any[];
|
||||||
|
enableDrag?: boolean;
|
||||||
enableExpansion?: boolean;
|
enableExpansion?: boolean;
|
||||||
enableSelection?: boolean;
|
enableSelection?: boolean;
|
||||||
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||||
@@ -244,6 +249,7 @@ export interface GridItemProps {
|
|||||||
export interface ItemGridListProps {
|
export interface ItemGridListProps {
|
||||||
currentPage?: number;
|
currentPage?: number;
|
||||||
data: unknown[];
|
data: unknown[];
|
||||||
|
enableDrag?: boolean;
|
||||||
enableExpansion?: boolean;
|
enableExpansion?: boolean;
|
||||||
enableSelection?: boolean;
|
enableSelection?: boolean;
|
||||||
gap?: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
gap?: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||||
@@ -258,6 +264,7 @@ export interface ItemGridListProps {
|
|||||||
|
|
||||||
export const ItemGridList = ({
|
export const ItemGridList = ({
|
||||||
data,
|
data,
|
||||||
|
enableDrag = true,
|
||||||
enableExpansion = true,
|
enableExpansion = true,
|
||||||
enableSelection = true,
|
enableSelection = true,
|
||||||
gap = 'sm',
|
gap = 'sm',
|
||||||
@@ -336,7 +343,6 @@ export const ItemGridList = ({
|
|||||||
|
|
||||||
const controls = useDefaultItemListControls();
|
const controls = useDefaultItemListControls();
|
||||||
|
|
||||||
// Scroll to a specific index
|
|
||||||
const scrollToIndex = useCallback(
|
const scrollToIndex = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
if (!listRef.current || !tableMeta) return;
|
if (!listRef.current || !tableMeta) return;
|
||||||
@@ -346,7 +352,6 @@ export const ItemGridList = ({
|
|||||||
[tableMeta],
|
[tableMeta],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Scroll to a specific offset
|
|
||||||
const scrollToOffset = useCallback((offset: number) => {
|
const scrollToOffset = useCallback((offset: number) => {
|
||||||
if (!listRef.current) return;
|
if (!listRef.current) return;
|
||||||
listRef.current.scrollTo(offset);
|
listRef.current.scrollTo(offset);
|
||||||
@@ -355,7 +360,6 @@ export const ItemGridList = ({
|
|||||||
// Handle keyboard navigation
|
// Handle keyboard navigation
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
console.log('handleKeyDown', e.key);
|
|
||||||
if (!enableSelection || !tableMeta) return;
|
if (!enableSelection || !tableMeta) return;
|
||||||
if (
|
if (
|
||||||
e.key !== 'ArrowDown' &&
|
e.key !== 'ArrowDown' &&
|
||||||
@@ -480,7 +484,7 @@ export const ItemGridList = ({
|
|||||||
const startIndex = Math.min(lastIndex, newIndex);
|
const startIndex = Math.min(lastIndex, newIndex);
|
||||||
const stopIndex = Math.max(lastIndex, newIndex);
|
const stopIndex = Math.max(lastIndex, newIndex);
|
||||||
|
|
||||||
const rangeItems: ItemListItem[] = [];
|
const rangeItems: ItemListStateItem[] = [];
|
||||||
for (let i = startIndex; i <= stopIndex; i++) {
|
for (let i = startIndex; i <= stopIndex; i++) {
|
||||||
const rangeItem = data[i];
|
const rangeItem = data[i];
|
||||||
if (
|
if (
|
||||||
@@ -507,7 +511,7 @@ export const ItemGridList = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Ensure the last item in selection is the item at newIndex for incremental extension
|
// Ensure the last item in selection is the item at newIndex for incremental extension
|
||||||
const newItemListItem: ItemListItem = {
|
const newItemListItem: ItemListStateItem = {
|
||||||
_serverId: newItem.serverId,
|
_serverId: newItem.serverId,
|
||||||
id: newItem.id,
|
id: newItem.id,
|
||||||
itemType,
|
itemType,
|
||||||
@@ -546,7 +550,6 @@ export const ItemGridList = ({
|
|||||||
[data, enableSelection, internalState, itemType, tableMeta, scrollToIndex],
|
[data, enableSelection, internalState, itemType, tableMeta, scrollToIndex],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create imperative handle
|
|
||||||
const imperativeHandle: ItemListHandle = useMemo(() => {
|
const imperativeHandle: ItemListHandle = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
clearExpanded: () => {
|
clearExpanded: () => {
|
||||||
@@ -568,12 +571,10 @@ export const ItemGridList = ({
|
|||||||
};
|
};
|
||||||
}, [data, internalState, scrollToIndex, scrollToOffset]);
|
}, [data, internalState, scrollToIndex, scrollToOffset]);
|
||||||
|
|
||||||
// Expose handle via ref
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleRef.current = imperativeHandle;
|
handleRef.current = imperativeHandle;
|
||||||
}, [imperativeHandle]);
|
}, [imperativeHandle]);
|
||||||
|
|
||||||
// Expose handle via forwardRef
|
|
||||||
useImperativeHandle(ref, () => imperativeHandle, [imperativeHandle]);
|
useImperativeHandle(ref, () => imperativeHandle, [imperativeHandle]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -588,6 +589,7 @@ export const ItemGridList = ({
|
|||||||
<VirtualizedGridList
|
<VirtualizedGridList
|
||||||
controls={controls}
|
controls={controls}
|
||||||
data={data}
|
data={data}
|
||||||
|
enableDrag={enableDrag}
|
||||||
enableExpansion={enableExpansion}
|
enableExpansion={enableExpansion}
|
||||||
enableSelection={enableSelection}
|
enableSelection={enableSelection}
|
||||||
gap={gap}
|
gap={gap}
|
||||||
@@ -614,7 +616,7 @@ export const ItemGridList = ({
|
|||||||
|
|
||||||
const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
|
const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
|
||||||
const { index, style } = props;
|
const { index, style } = props;
|
||||||
const { columns, controls, data, gap, itemType } = props.data;
|
const { columns, controls, data, enableDrag, gap, itemType } = props.data;
|
||||||
|
|
||||||
const items: ReactNode[] = [];
|
const items: ReactNode[] = [];
|
||||||
const itemCount = data.length;
|
const itemCount = data.length;
|
||||||
@@ -640,6 +642,7 @@ const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
|
|||||||
<ItemCard
|
<ItemCard
|
||||||
controls={controls}
|
controls={controls}
|
||||||
data={data[i]}
|
data={data[i]}
|
||||||
|
enableDrag={enableDrag}
|
||||||
internalState={props.data.internalState}
|
internalState={props.data.internalState}
|
||||||
itemType={itemType}
|
itemType={itemType}
|
||||||
withControls
|
withControls
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { ExpandedListContainer } from '/@/renderer/components/item-list/expanded
|
|||||||
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
||||||
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
||||||
import {
|
import {
|
||||||
ItemListItem,
|
ItemListStateItem,
|
||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
useItemListState,
|
useItemListState,
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
@@ -976,7 +976,7 @@ export const ItemTableList = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemListItem: ItemListItem = {
|
const itemListItem: ItemListStateItem = {
|
||||||
_serverId: item.serverId,
|
_serverId: item.serverId,
|
||||||
id: item.id,
|
id: item.id,
|
||||||
itemType,
|
itemType,
|
||||||
@@ -1020,7 +1020,7 @@ export const ItemTableList = ({
|
|||||||
const startIndex = Math.min(lastIndex, currentIndex);
|
const startIndex = Math.min(lastIndex, currentIndex);
|
||||||
const stopIndex = Math.max(lastIndex, currentIndex);
|
const stopIndex = Math.max(lastIndex, currentIndex);
|
||||||
|
|
||||||
const rangeItems: ItemListItem[] = [];
|
const rangeItems: ItemListStateItem[] = [];
|
||||||
for (let i = startIndex; i <= stopIndex; i++) {
|
for (let i = startIndex; i <= stopIndex; i++) {
|
||||||
const rangeItem = data[i];
|
const rangeItem = data[i];
|
||||||
if (
|
if (
|
||||||
@@ -1128,7 +1128,7 @@ export const ItemTableList = ({
|
|||||||
const startIndex = Math.min(lastIndex, newIndex);
|
const startIndex = Math.min(lastIndex, newIndex);
|
||||||
const stopIndex = Math.max(lastIndex, newIndex);
|
const stopIndex = Math.max(lastIndex, newIndex);
|
||||||
|
|
||||||
const rangeItems: ItemListItem[] = [];
|
const rangeItems: ItemListStateItem[] = [];
|
||||||
for (let i = startIndex; i <= stopIndex; i++) {
|
for (let i = startIndex; i <= stopIndex; i++) {
|
||||||
const rangeItem = data[i];
|
const rangeItem = data[i];
|
||||||
if (
|
if (
|
||||||
@@ -1155,7 +1155,7 @@ export const ItemTableList = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Ensure the last item in selection is the item at newIndex for incremental extension
|
// Ensure the last item in selection is the item at newIndex for incremental extension
|
||||||
const newItemListItem: ItemListItem = {
|
const newItemListItem: ItemListStateItem = {
|
||||||
_serverId: newItem.serverId,
|
_serverId: newItem.serverId,
|
||||||
id: newItem.id,
|
id: newItem.id,
|
||||||
itemType,
|
itemType,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Fragment, Suspense } from 'react';
|
|||||||
|
|
||||||
import styles from './expanded-album-list-item.module.css';
|
import styles from './expanded-album-list-item.module.css';
|
||||||
|
|
||||||
import { ItemListItem } from '/@/renderer/components/item-list/helpers/item-list-state';
|
import { ItemListStateItem } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
@@ -18,7 +18,7 @@ import { TextTitle } from '/@/shared/components/text-title/text-title';
|
|||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
|
|
||||||
interface ExpandedAlbumListItemProps {
|
interface ExpandedAlbumListItemProps {
|
||||||
item: ItemListItem;
|
item: ItemListStateItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExpandedAlbumListItem = ({ item }: ExpandedAlbumListItemProps) => {
|
export const ExpandedAlbumListItem = ({ item }: ExpandedAlbumListItemProps) => {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { useMemo, useRef, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, useNavigate, useParams } from 'react-router';
|
import { generatePath, useNavigate, useParams } from 'react-router';
|
||||||
|
|
||||||
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
|
||||||
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import { PlaylistDetailSongListHeader } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header';
|
import { PlaylistDetailSongListHeader } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header';
|
||||||
import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder';
|
import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder';
|
||||||
@@ -32,7 +31,6 @@ const PlaylistDetailSongListRoute = () => {
|
|||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
const tableRef = useRef<AgGridReactType | null>(null);
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
|
||||||
|
|
||||||
const detailQuery = useQuery(
|
const detailQuery = useQuery(
|
||||||
playlistsQueries.detail({ query: { id: playlistId }, serverId: server?.id }),
|
playlistsQueries.detail({ query: { id: playlistId }, serverId: server?.id }),
|
||||||
@@ -159,13 +157,13 @@ const PlaylistDetailSongListRoute = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const filterSortedSongs = useMemo(() => {
|
const filterSortedSongs = useMemo(() => {
|
||||||
let items = playlistSongs.data?.items;
|
const items = playlistSongs.data?.items;
|
||||||
|
|
||||||
if (items) {
|
if (items) {
|
||||||
const searchTerm = page?.table.id[playlistId]?.filter?.searchTerm;
|
const searchTerm = page?.table.id[playlistId]?.filter?.searchTerm;
|
||||||
|
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
items = searchSongs(items, searchTerm);
|
// items = searchSongs(items, searchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID;
|
const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID;
|
||||||
@@ -182,10 +180,10 @@ const PlaylistDetailSongListRoute = () => {
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const handlePlay = (play: Play) => {
|
const handlePlay = (play: Play) => {
|
||||||
handlePlayQueueAdd?.({
|
// handlePlayQueueAdd?.({
|
||||||
byData: filterSortedSongs,
|
// byData: filterSortedSongs,
|
||||||
playType: play,
|
// playType: play,
|
||||||
});
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import {
|
||||||
|
attachClosestEdge,
|
||||||
|
type Edge,
|
||||||
|
extractClosestEdge,
|
||||||
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
||||||
|
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
||||||
|
import {
|
||||||
|
BaseEventPayload,
|
||||||
|
CleanupFn,
|
||||||
|
ElementDragType,
|
||||||
|
Input,
|
||||||
|
} from '@atlaskit/pragmatic-drag-and-drop/dist/types/internal-types';
|
||||||
|
import {
|
||||||
|
draggable,
|
||||||
|
dropTargetForElements,
|
||||||
|
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||||
|
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
|
||||||
|
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
||||||
|
import { values } from 'lodash';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
|
import { dndUtils, DragData, DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
|
||||||
|
|
||||||
|
interface UseDraggableProps {
|
||||||
|
drag?: {
|
||||||
|
getId: () => string[];
|
||||||
|
getItem: () => unknown[];
|
||||||
|
onDragStart?: () => void;
|
||||||
|
onDrop?: () => void;
|
||||||
|
onGenerateDragPreview?: (data: BaseEventPayload<ElementDragType>) => void;
|
||||||
|
target: DragTarget | string;
|
||||||
|
};
|
||||||
|
drop?: {
|
||||||
|
canDrop: (args: { source: DragData }) => boolean;
|
||||||
|
getData: (args: { element: HTMLElement; input: Input }) => DragData;
|
||||||
|
onDrag: (args: { self: DragData }) => void;
|
||||||
|
onDragLeave: () => void;
|
||||||
|
onDrop: (args: { self: DragData }) => void;
|
||||||
|
};
|
||||||
|
isEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDragDrop = <TElement extends HTMLElement>({
|
||||||
|
drag,
|
||||||
|
drop,
|
||||||
|
isEnabled,
|
||||||
|
}: UseDraggableProps) => {
|
||||||
|
const ref = useRef<null | TElement>(null);
|
||||||
|
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current || !isEnabled) return;
|
||||||
|
|
||||||
|
const functions: CleanupFn[] = [];
|
||||||
|
|
||||||
|
if (drag) {
|
||||||
|
functions.push(
|
||||||
|
draggable({
|
||||||
|
element: ref.current,
|
||||||
|
getInitialData: () => {
|
||||||
|
const id = drag.getId();
|
||||||
|
const item = drag.getItem();
|
||||||
|
|
||||||
|
const data = dndUtils.generateDragData({
|
||||||
|
id,
|
||||||
|
item,
|
||||||
|
type: drag.target,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onDragStart: () => {
|
||||||
|
setIsDragging(true);
|
||||||
|
drag.onDragStart?.();
|
||||||
|
},
|
||||||
|
onDrop: () => {
|
||||||
|
setIsDragging(false);
|
||||||
|
drag.onDrop?.();
|
||||||
|
},
|
||||||
|
onGenerateDragPreview: (data) => {
|
||||||
|
if (drag.onGenerateDragPreview) {
|
||||||
|
return drag.onGenerateDragPreview(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
disableNativeDragPreview({ nativeSetDragImage: data.nativeSetDragImage });
|
||||||
|
// setCustomNativeDragPreview({
|
||||||
|
// nativeSetDragImage: data.nativeSetDragImage,
|
||||||
|
// // render: ({ container }) => {
|
||||||
|
// // const root = createRoot(container);
|
||||||
|
// // root.render(<DragPreview itemCount={1} />);
|
||||||
|
// // },
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (drop) {
|
||||||
|
// functions.push(
|
||||||
|
// dropTargetForElements({
|
||||||
|
// canDrop: (args) => {
|
||||||
|
// const data = args.source.data as unknown as DragData;
|
||||||
|
// const isSelf = (args.source.data.id as string[])[0] === option.value;
|
||||||
|
// return dndUtils.isDropTarget(data.type, [DragTarget.GENERIC]) && !isSelf;
|
||||||
|
// },
|
||||||
|
// element: ref.current,
|
||||||
|
// getData: ({ element, input }) => {
|
||||||
|
// const data = dndUtils.generateDragData({
|
||||||
|
// id: [option.value],
|
||||||
|
// operation: [DragOperation.REORDER],
|
||||||
|
// type: DragTarget.GENERIC,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return attachClosestEdge(data, {
|
||||||
|
// allowedEdges: ['top', 'bottom'],
|
||||||
|
// element,
|
||||||
|
// input,
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// onDrag: (args) => {
|
||||||
|
// const closestEdgeOfTarget: Edge | null = extractClosestEdge(args.self.data);
|
||||||
|
// setIsDraggedOver(closestEdgeOfTarget);
|
||||||
|
// },
|
||||||
|
// onDragLeave: () => {
|
||||||
|
// setIsDraggedOver(null);
|
||||||
|
// },
|
||||||
|
// onDrop: (args) => {
|
||||||
|
// const closestEdgeOfTarget: Edge | null = extractClosestEdge(args.self.data);
|
||||||
|
|
||||||
|
// const from = args.source.data.id as string[];
|
||||||
|
// const to = args.self.data.id as string[];
|
||||||
|
|
||||||
|
// const newOrder = dndUtils.reorderById({
|
||||||
|
// edge: closestEdgeOfTarget,
|
||||||
|
// idFrom: from[0],
|
||||||
|
// idTo: to[0],
|
||||||
|
// list: values,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// onChange(newOrder);
|
||||||
|
// setIsDraggedOver(null);
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
return combine(...functions);
|
||||||
|
}, [drag, drop, isDragging]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDragging,
|
||||||
|
ref,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -53,7 +53,7 @@ export const dndUtils = {
|
|||||||
id: string[];
|
id: string[];
|
||||||
item?: TDataType[];
|
item?: TDataType[];
|
||||||
operation?: DragOperation[];
|
operation?: DragOperation[];
|
||||||
type: DragTarget;
|
type: DragTarget | string;
|
||||||
},
|
},
|
||||||
metadata?: T,
|
metadata?: T,
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user