diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
| commit | 3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch) | |
| tree | 57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/selection/state.ts | |
| parent | 729d10592f4e802f91c555c309ff3ba4b119f843 (diff) | |
| download | opencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz | |
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/selection/state.ts')
| -rw-r--r-- | apps/web/src/selection/state.ts | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/apps/web/src/selection/state.ts b/apps/web/src/selection/state.ts new file mode 100644 index 00000000..b44578a1 --- /dev/null +++ b/apps/web/src/selection/state.ts @@ -0,0 +1,165 @@ +import type {
+ BoxSelectionChange,
+ SelectionState,
+} from "@/selection/types";
+
+function dedupeIds({ 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 replaceSelection({
+ ids,
+ anchorId,
+}: {
+ ids: string[];
+ anchorId?: string | null;
+}): SelectionState {
+ const selectedIds = dedupeIds({ ids });
+ return {
+ selectedIds,
+ anchorId: anchorId ?? selectedIds[selectedIds.length - 1] ?? null,
+ };
+}
+
+export function clearSelection(): SelectionState {
+ return {
+ selectedIds: [],
+ anchorId: null,
+ };
+}
+
+export function pruneSelection({
+ state,
+ orderedIds,
+}: {
+ state: SelectionState;
+ orderedIds: string[];
+}): SelectionState {
+ const validIds = new Set(orderedIds);
+ const selectedIds = state.selectedIds.filter((id) => validIds.has(id));
+ const anchorId =
+ state.anchorId && validIds.has(state.anchorId)
+ ? state.anchorId
+ : selectedIds[selectedIds.length - 1] ?? null;
+ const isUnchanged =
+ selectedIds.length === state.selectedIds.length &&
+ anchorId === state.anchorId;
+
+ if (isUnchanged) {
+ return state;
+ }
+
+ return {
+ selectedIds,
+ anchorId,
+ };
+}
+
+export function isSelected({
+ state,
+ id,
+}: {
+ state: SelectionState;
+ id: string;
+}) {
+ return state.selectedIds.includes(id);
+}
+
+export function toggleSelection({
+ state,
+ id,
+}: {
+ state: SelectionState;
+ id: string;
+}): SelectionState {
+ if (isSelected({ state, id })) {
+ const selectedIds = state.selectedIds.filter((selectedId) => selectedId !== id);
+ const anchorId =
+ state.anchorId === id
+ ? selectedIds[selectedIds.length - 1] ?? null
+ : state.anchorId;
+ return replaceSelection({
+ ids: selectedIds,
+ anchorId,
+ });
+ }
+
+ return replaceSelection({
+ ids: [...state.selectedIds, id],
+ anchorId: id,
+ });
+}
+
+export function selectRange({
+ state,
+ orderedIds,
+ targetId,
+ isAdditive,
+}: {
+ state: SelectionState;
+ orderedIds: string[];
+ targetId: string;
+ isAdditive: boolean;
+}): SelectionState {
+ const anchorId =
+ state.anchorId ??
+ state.selectedIds[state.selectedIds.length - 1] ??
+ targetId;
+ const rangeIds = getRangeIds({
+ orderedIds,
+ anchorId,
+ targetId,
+ });
+ const selectedIds = isAdditive
+ ? dedupeIds({
+ ids: [...state.selectedIds, ...rangeIds],
+ })
+ : rangeIds;
+
+ return replaceSelection({
+ ids: selectedIds,
+ anchorId,
+ });
+}
+
+export function applyBoxSelection({
+ intersectedIds,
+ initialSelectedIds,
+ initialAnchorId,
+ isAdditive,
+}: BoxSelectionChange): SelectionState {
+ const selectedIds = isAdditive
+ ? dedupeIds({
+ ids: [...initialSelectedIds, ...intersectedIds],
+ })
+ : intersectedIds;
+ const anchorId = isAdditive
+ ? initialAnchorId ?? intersectedIds[intersectedIds.length - 1] ?? null
+ : intersectedIds[intersectedIds.length - 1] ?? null;
+
+ return replaceSelection({
+ ids: selectedIds,
+ anchorId,
+ });
+}
|
