OpenCut/legacy/use-assets-selection.ts

210 lines
4.7 KiB
TypeScript

import { useCallback, useEffect, useMemo } from "react";
import { useAssetsPanelStore } from "@/stores/assets-panel-store";
function mergeUniqueIds({ ids }: { ids: string[] }) {
return [...new Set(ids)];
}
function getRangeIds({
orderedIds,
anchorId,
targetId,
}: {
orderedIds: string[];
anchorId: string;
targetId: string;
}) {
const anchorIndex = orderedIds.indexOf(anchorId);
const targetIndex = orderedIds.indexOf(targetId);
if (anchorIndex === -1 || targetIndex === -1) {
return [targetId];
}
const rangeStart = Math.min(anchorIndex, targetIndex);
const rangeEnd = Math.max(anchorIndex, targetIndex);
return orderedIds.slice(rangeStart, rangeEnd + 1);
}
export function useAssetsSelection({
orderedIds,
}: {
orderedIds: string[];
}) {
const {
selectedMediaIds,
selectionAnchorId,
setMediaSelection,
clearMediaSelection,
} = useAssetsPanelStore();
const selectedIdSet = useMemo(() => {
return new Set(selectedMediaIds);
}, [selectedMediaIds]);
useEffect(() => {
const validIds = new Set(orderedIds);
const nextSelectedIds = selectedMediaIds.filter((id) => validIds.has(id));
const nextAnchorId =
selectionAnchorId && validIds.has(selectionAnchorId)
? selectionAnchorId
: nextSelectedIds[nextSelectedIds.length - 1] ?? null;
const isSelectionUnchanged =
nextSelectedIds.length === selectedMediaIds.length &&
nextAnchorId === selectionAnchorId;
if (isSelectionUnchanged) {
return;
}
setMediaSelection({
ids: nextSelectedIds,
anchorId: nextAnchorId,
});
}, [orderedIds, selectedMediaIds, selectionAnchorId, setMediaSelection]);
const replaceSelection = useCallback(
(ids: string[], anchorId?: string | null) => {
setMediaSelection({
ids: mergeUniqueIds({ ids }),
anchorId,
});
},
[setMediaSelection],
);
const isSelected = useCallback(
(id: string) => {
return selectedIdSet.has(id);
},
[selectedIdSet],
);
const toggleSelection = useCallback(
(id: string) => {
if (selectedIdSet.has(id)) {
const nextSelectedIds = selectedMediaIds.filter(
(selectedId) => selectedId !== id,
);
const nextAnchorId =
selectionAnchorId === id
? nextSelectedIds[nextSelectedIds.length - 1] ?? null
: selectionAnchorId;
replaceSelection(nextSelectedIds, nextAnchorId);
return;
}
replaceSelection([...selectedMediaIds, id], id);
},
[
selectedIdSet,
selectedMediaIds,
selectionAnchorId,
replaceSelection,
],
);
const selectRange = useCallback(
({
targetId,
isAdditive,
}: {
targetId: string;
isAdditive: boolean;
}) => {
const anchorId =
selectionAnchorId ??
selectedMediaIds[selectedMediaIds.length - 1] ??
targetId;
const rangeIds = getRangeIds({
orderedIds,
anchorId,
targetId,
});
const nextSelectedIds = isAdditive
? mergeUniqueIds({
ids: [...selectedMediaIds, ...rangeIds],
})
: rangeIds;
replaceSelection(nextSelectedIds, anchorId);
},
[orderedIds, replaceSelection, selectedMediaIds, selectionAnchorId],
);
const handleItemClick = useCallback(
({
event,
id,
}: {
event: React.MouseEvent;
id: string;
}) => {
const isToggleSelection = event.ctrlKey || event.metaKey;
if (event.shiftKey) {
selectRange({
targetId: id,
isAdditive: isToggleSelection,
});
return;
}
if (isToggleSelection) {
toggleSelection(id);
return;
}
replaceSelection([id], id);
},
[replaceSelection, selectRange, toggleSelection],
);
const handleItemContextMenu = useCallback(
(id: string) => {
if (selectedIdSet.has(id)) {
return;
}
replaceSelection([id], id);
},
[selectedIdSet, replaceSelection],
);
const applyBoxSelection = useCallback(
({
intersectedIds,
initialSelectedIds,
initialAnchorId,
isAdditive,
}: {
intersectedIds: string[];
initialSelectedIds: string[];
initialAnchorId: string | null;
isAdditive: boolean;
}) => {
const nextSelectedIds = isAdditive
? mergeUniqueIds({
ids: [...initialSelectedIds, ...intersectedIds],
})
: intersectedIds;
const nextAnchorId = isAdditive
? initialAnchorId ?? intersectedIds[intersectedIds.length - 1] ?? null
: intersectedIds[intersectedIds.length - 1] ?? null;
replaceSelection(nextSelectedIds, nextAnchorId);
},
[replaceSelection],
);
return {
selectedMediaIds,
selectionAnchorId,
isSelected,
replaceSelection,
clearSelection: clearMediaSelection,
handleItemClick,
handleItemContextMenu,
applyBoxSelection,
};
}