mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-107] add drag and drop to playlist maps
This commit is contained in:
Generated
+120
-6
@@ -42,6 +42,7 @@
|
||||
"qrcode.react": "^3.1.0",
|
||||
"query-process": "^0.0.3",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-range": "^1.8.14",
|
||||
@@ -82,6 +83,7 @@
|
||||
"@types/node-fetch": "^2.6.3",
|
||||
"@types/pako": "^2.0.1",
|
||||
"@types/react": "^18.0.33",
|
||||
"@types/react-beautiful-dnd": "^13.1.8",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-outside-click-handler": "^1.3.1",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
@@ -6565,6 +6567,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/html-minifier-terser": {
|
||||
"version": "6.1.0",
|
||||
"dev": true,
|
||||
@@ -6737,7 +6748,6 @@
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
@@ -6752,7 +6762,6 @@
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.0.33",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -6760,6 +6769,15 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-beautiful-dnd": {
|
||||
"version": "13.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz",
|
||||
"integrity": "sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.0.11",
|
||||
"dev": true,
|
||||
@@ -6776,6 +6794,17 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-redux": {
|
||||
"version": "7.1.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz",
|
||||
"integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-test-renderer": {
|
||||
"version": "18.0.7",
|
||||
"dev": true,
|
||||
@@ -6832,7 +6861,6 @@
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
@@ -9911,6 +9939,14 @@
|
||||
"version": "4.2.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-box-model": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
||||
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
|
||||
"dependencies": {
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/css-color-names": {
|
||||
"version": "0.0.4",
|
||||
"dev": true,
|
||||
@@ -10254,7 +10290,6 @@
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.1",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
@@ -13529,6 +13564,14 @@
|
||||
"@babel/runtime": "^7.7.6"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
||||
@@ -18747,7 +18790,6 @@
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
@@ -19171,6 +19213,11 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/raf-schd": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
|
||||
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
@@ -19222,6 +19269,24 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-beautiful-dnd": {
|
||||
"version": "13.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz",
|
||||
"integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"css-box-model": "^1.2.0",
|
||||
"memoize-one": "^5.1.1",
|
||||
"raf-schd": "^4.0.2",
|
||||
"react-redux": "^7.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"use-memo-one": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.5 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-colorful": {
|
||||
"version": "5.6.1",
|
||||
"license": "MIT",
|
||||
@@ -19243,7 +19308,6 @@
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-range": {
|
||||
@@ -19254,6 +19318,35 @@
|
||||
"react-dom": "^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "7.2.9",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
||||
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.3 || ^17 || ^18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux/node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.0",
|
||||
"dev": true,
|
||||
@@ -19565,6 +19658,14 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect.getprototypeof": {
|
||||
"version": "1.0.4",
|
||||
"dev": true,
|
||||
@@ -21425,6 +21526,11 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
|
||||
},
|
||||
"node_modules/tiny-typed-emitter": {
|
||||
"version": "2.1.0",
|
||||
"license": "MIT"
|
||||
@@ -22116,6 +22222,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-memo-one": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
|
||||
"integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sidecar": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
"@types/node-fetch": "^2.6.3",
|
||||
"@types/pako": "^2.0.1",
|
||||
"@types/react": "^18.0.33",
|
||||
"@types/react-beautiful-dnd": "^13.1.8",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-outside-click-handler": "^1.3.1",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
@@ -258,6 +259,7 @@
|
||||
"qrcode.react": "^3.1.0",
|
||||
"query-process": "^0.0.3",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-range": "^1.8.14",
|
||||
|
||||
+28
-8
@@ -33,6 +33,7 @@ import { getCorrectTextColor } from "renderer/helpers/correct-text-color";
|
||||
import { BPList } from "shared/models/playlists/playlist.interface";
|
||||
import { EditPlaylistInfosModal } from "./edit-playlist-infos-modal.component";
|
||||
import { CrossIcon } from "renderer/components/svgs/icons/cross-icon.component";
|
||||
import { DraggableVirtualScroll } from "renderer/components/shared/virtual-scroll/draggable-virtual-scroll.component";
|
||||
|
||||
type Props = {
|
||||
version?: BSVersion;
|
||||
@@ -84,8 +85,6 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
|
||||
|
||||
const displayablePlaylistMaps = useMemo(() => playlistMaps ? Object.values(playlistMaps).filter(Boolean) : [], [playlistMaps]);
|
||||
|
||||
const [base64Cover, setBase64Cover] = useState<string>(undefined);
|
||||
|
||||
useOnUpdate(() => {
|
||||
const keyDown = (e: KeyboardEvent) => keyPressed$.next(e.key);
|
||||
document.addEventListener("keydown", keyDown);
|
||||
@@ -205,7 +204,7 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
|
||||
map: localMap,
|
||||
mapsSource$: localMaps$ as BehaviorSubject<(BsmLocalMap|BsvMapDetail|SongDetails)[]>,
|
||||
selectedHashs$: availabledHashsSelected$,
|
||||
noKeyPressedFallBack: () => playlistMaps$.next(Object.assign({[mapHash]: localMap}, playlistMaps$.value))
|
||||
noKeyPressedFallBack: () => playlistMaps$.next({...playlistMaps$.value, [mapHash]: localMap})
|
||||
}), isSelected$);
|
||||
}, []);
|
||||
|
||||
@@ -238,7 +237,7 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
|
||||
<VirtualScroll
|
||||
classNames={{
|
||||
mainDiv: "bg-theme-1 rounded-md size-full min-w-0",
|
||||
rows: "my-2.5 px-2.5"
|
||||
rows: "py-2.5 px-2.5"
|
||||
}}
|
||||
items={maps}
|
||||
itemHeight={110}
|
||||
@@ -323,7 +322,7 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
|
||||
}).filter(duration => !isNaN(duration));
|
||||
|
||||
const totalDuration = durations.reduce((acc, duration) => acc + duration, 0);
|
||||
return totalDuration > 3600 ? dateFormat(totalDuration * 1000, "h:MM:ss") : dateFormat(totalDuration * 1000, "MM:ss");
|
||||
return totalDuration > 3600 ? dateFormat(totalDuration * 1000, "H:MM:ss") : dateFormat(totalDuration * 1000, "MM:ss");
|
||||
}, [playlistMaps]);
|
||||
|
||||
const [playlistMinNps, playlistMaxNps] = useMemo(() => {
|
||||
@@ -377,6 +376,15 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
|
||||
resolver({ exitCode: ModalExitCode.COMPLETED, data: bpList});
|
||||
};
|
||||
|
||||
const handlePlaylistMapDragEnd = useCallback((fromIndex: number, toIndex: number) => {
|
||||
const playlistMapsArray = Object.values(playlistMaps$.value ?? {});
|
||||
const newPlaylistMaps = [...playlistMapsArray];
|
||||
const [removed] = newPlaylistMaps.splice(fromIndex, 1);
|
||||
newPlaylistMaps.splice(toIndex, 0, removed);
|
||||
|
||||
playlistMaps$.next(Object.fromEntries(newPlaylistMaps.map(map => [getHashOfMap(map), map])));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen max-h-[calc(100vh-2rem)] max-w-[55rem] lg:max-w-[66rem] xl:max-w-[77rem] 2xl:max-w-[88rem] bg-theme-3 p-4 rounded-md relative">
|
||||
<button className="absolute top-1.5 right-1.5 size-3" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })}>
|
||||
@@ -436,9 +444,21 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
|
||||
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[500px] h-fit p-2 rounded-md shadow-md shadow-black -translate-x-[40%]" filter={playlistMapsFilter} onChange={setPlaylistMapsFilter}/>
|
||||
</BsmDropdownButton>
|
||||
</div>
|
||||
{renderList(displayablePlaylistMaps.filter(map => {
|
||||
return isMapFitFilter({ map, filter: playlistMapsFilter, search: playlistMapsSearch });
|
||||
}), renderPlaylistMapItem)}
|
||||
<div className="overflow-hidden size-full">
|
||||
<DraggableVirtualScroll
|
||||
classNames={{
|
||||
mainDiv: "bg-theme-1 rounded-md size-full min-w-0",
|
||||
rows: "py-2.5 px-2.5"
|
||||
}}
|
||||
itemHeight={110}
|
||||
items={displayablePlaylistMaps.filter(map => {
|
||||
return isMapFitFilter({ map, filter: playlistMapsFilter, search: playlistMapsSearch });
|
||||
})}
|
||||
isDragDisabled={!!Object.keys(playlistMapsFilter).length || !!playlistMapsSearch}
|
||||
renderItem={renderPlaylistMapItem}
|
||||
onDragEnd={handlePlaylistMapDragEnd}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full h-4 flex justify-end gap-3 mt-px">
|
||||
<div className="h-full flex justify-center items-center gap-0.5">
|
||||
<MapIcon className='h-full aspect-square mt-0.5'/>
|
||||
|
||||
@@ -15,8 +15,6 @@ export function Modal() {
|
||||
const modals = useObservable(() => modals$);
|
||||
const currentModal = useObservable<ModalObject>(() =>modals$.pipe(map(modals => modals?.at(-1))));
|
||||
|
||||
console.log(currentModal, modals, !!currentModal);
|
||||
|
||||
useEffect(() => {
|
||||
const onEscape = (e: KeyboardEvent) => {
|
||||
if (e.key !== "Escape") {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { RefObject, useEffect, useState } from "react";
|
||||
import { useConstant } from "./use-constant.hook";
|
||||
import { BehaviorSubject, debounceTime, distinctUntilChanged } from "rxjs";
|
||||
import { useObservable } from "./use-observable.hook";
|
||||
|
||||
type Props = {
|
||||
ref: RefObject<HTMLElement>;
|
||||
deps?: unknown[];
|
||||
debounce?: number;
|
||||
}
|
||||
|
||||
export function useObserveSize(opt: Props): { width: number; height: number } {
|
||||
|
||||
const size$ = useConstant(() => new BehaviorSubject({ width: 0, height: 0 }));
|
||||
const size = useObservable(() => size$.pipe(debounceTime(opt.debounce ?? 0), distinctUntilChanged()), { width: 0, height: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (!entry) return;
|
||||
size$.next({ width: entry.contentRect.width, height: entry.contentRect.height });
|
||||
});
|
||||
|
||||
if (opt.ref?.current) {
|
||||
observer.observe(opt?.ref?.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, opt.deps ?? []);
|
||||
|
||||
return size;
|
||||
}
|
||||
Reference in New Issue
Block a user