summaryrefslogtreecommitdiff
path: root/legacy/use-assets-selection.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-03-25 16:47:22 +0100
committerMaze Winther <mazewinther@gmail.com>2026-03-29 15:48:22 +0200
commit8db3bead13441a5882d8689dfc6a9f09c82375d9 (patch)
treee9d0cafa93904ffcbec3188c37961bcc16c6422d /legacy/use-assets-selection.ts
parent39ea298a9ccba3104c549c0ca079c231981811f5 (diff)
downloadopencut-8db3bead13441a5882d8689dfc6a9f09c82375d9.tar.gz
feat: masks, properties refactor, shaders, storage migrations, and more
Diffstat (limited to 'legacy/use-assets-selection.ts')
-rw-r--r--legacy/use-assets-selection.ts209
1 files changed, 209 insertions, 0 deletions
diff --git a/legacy/use-assets-selection.ts b/legacy/use-assets-selection.ts
new file mode 100644
index 00000000..89d245e6
--- /dev/null
+++ b/legacy/use-assets-selection.ts
@@ -0,0 +1,209 @@
+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,
+ };
+}