[feature-107] add drag and drop to playlist maps

This commit is contained in:
MathieuG-P
2024-06-21 21:08:41 +02:00
parent 44c63a63c6
commit 33be4de2ff
7 changed files with 303 additions and 19 deletions
@@ -0,0 +1,119 @@
import { CSSProperties, useCallback, useRef } from "react";
import { DragDropContext, Draggable, DraggableProvided, DropResult, Droppable } from "react-beautiful-dnd";
import { ListChildComponentProps, VariableSizeList } from "react-window";
import { cn } from "renderer/helpers/css-class.helpers";
import { useObserveSize } from "renderer/hooks/use-observe-size.hook";
import equal from "fast-deep-equal";
import { typedMemo } from "renderer/helpers/typed-memo";
type DraggableVirtualScrollClassNames = {
mainDiv?: string;
variableList?: string;
rows?: string;
}
type Props<T = unknown> = {
className?: string;
classNames?: DraggableVirtualScrollClassNames;
itemHeight: number;
items: T[];
isDragDisabled?: boolean;
renderItem: (item: T) => JSX.Element;
onDragEnd?: (fromIndex: number, toIndex: number) => void;
}
export function DraggableVirtualScroll<T = unknown>({
className,
classNames,
items,
itemHeight,
isDragDisabled,
renderItem,
onDragEnd
}: Props<T>) {
const ref = useRef(null);
const { height } = useObserveSize({ ref, debounce: 100 });
const handleOnDragEnd = useCallback((result: DropResult) => {
if(!onDragEnd){ return; }
if (!result.destination || result.destination.index === result.source.index) { return; }
onDragEnd(result.source.index, result.destination.index);
}, [onDragEnd]);
const renderDraggedItem = useCallback((renderItem: (item: T) => JSX.Element, itemProps: T, provided: DraggableProvided) => {
return (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} className={cn("list-none outline-none", classNames?.rows)}>
{renderItem(itemProps)}
</div>
)
}, [renderItem]);
const renderRow = useCallback((props: ListChildComponentProps<T[]>) => {
return (
<DraggableRow
item={props.data[props.index]}
renderItem={renderItem}
index={props.index}
className={classNames?.rows}
style={props.style}
isDragDisabled={isDragDisabled}
/>
)
}, [renderItem, isDragDisabled]);
return (
<div ref={ref} className={cn(className, classNames?.mainDiv)}>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="idtemp" mode="virtual" renderClone={(provided, _, rubric) => {
return renderDraggedItem(renderItem, items[rubric.source.index], provided)
}}>
{provided => (
<VariableSizeList
outerRef={provided.innerRef}
className={cn("scrollbar-default", classNames?.variableList)}
width="100%" height={height}
layout="vertical"
itemCount={items.length}
itemSize={() => itemHeight}
itemData={items}
style={{ scrollbarGutter: "stable both-edges" }}
direction="vertical"
>
{renderRow}
</VariableSizeList>
)}
</Droppable>
</DragDropContext>
</div>
)
}
type DraggableRowProps<T = unknown> = {
className?: string;
style?: CSSProperties;
item: T;
index: number;
isDragDisabled?: boolean;
renderItem: (item: T) => JSX.Element;
}
function DraggableRowComponent<T>({ item, renderItem, className, style, index, isDragDisabled }: DraggableRowProps<T>) {
const innerRender = useCallback((provided: DraggableProvided) => {
return (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} className={cn("list-none outline-none", className)} style={{ ...style, ...provided.draggableProps.style }}>
{renderItem(item)}
</div>
)
}, [item]);
return (
<Draggable draggableId={index.toString()} index={index} isDragDisabled={isDragDisabled}>
{innerRender}
</Draggable>
)
}
const DraggableRow = typedMemo(DraggableRowComponent, equal);
@@ -42,8 +42,6 @@ export function VirtualScroll<T = unknown>({ className, classNames, minItemWidth
const listHeight$ = useConstant(() => new BehaviorSubject<number>(0));
const listHeight = useObservable(() => listHeight$.pipe(distinctUntilChanged(), debounceTime(100)), 0);
console.log(listHeight);
useLayoutEffect(() => {
const updateItemPerRow = (listWidth: number) => {
if (!listWidth) return;
@@ -101,7 +99,6 @@ export function VirtualScroll<T = unknown>({ className, classNames, minItemWidth
itemData={itemsToRender}
style={{ scrollbarGutter: "stable both-edges" }}
onScroll={handleScroll}
>
{renderRow}
</VariableSizeList>