diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-15 15:45:50 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-15 15:45:50 +0200 |
| commit | c46d28891fee62ca2d3132da876af6411a41d9da (patch) | |
| tree | f856f71d04c69628f26b904088d675dd298a5080 /apps/web/src/core/managers/selection-manager.ts | |
| parent | 66e4367bc4727f1d0bcf8f869529c7518812277e (diff) | |
| download | opencut-c46d28891fee62ca2d3132da876af6411a41d9da.tar.gz | |
feat: text / custom masks, fix preview pan, refactor selection
Diffstat (limited to 'apps/web/src/core/managers/selection-manager.ts')
| -rw-r--r-- | apps/web/src/core/managers/selection-manager.ts | 268 |
1 files changed, 195 insertions, 73 deletions
diff --git a/apps/web/src/core/managers/selection-manager.ts b/apps/web/src/core/managers/selection-manager.ts index 9bafc6d7..5da4fa60 100644 --- a/apps/web/src/core/managers/selection-manager.ts +++ b/apps/web/src/core/managers/selection-manager.ts @@ -1,73 +1,195 @@ -import type { EditorCore } from "@/core";
-import type { SelectedKeyframeRef } from "@/lib/animation/types";
-import type { ElementRef } from "@/lib/timeline/types";
-
-export class SelectionManager {
- private selectedElements: ElementRef[] = [];
- private selectedKeyframes: SelectedKeyframeRef[] = [];
- private keyframeSelectionAnchor: SelectedKeyframeRef | null = null;
- private listeners = new Set<() => void>();
-
- constructor(editor: EditorCore) {
- void editor;
- }
-
- getSelectedElements(): ElementRef[] {
- return this.selectedElements;
- }
-
- getSelectedKeyframes(): SelectedKeyframeRef[] {
- return this.selectedKeyframes;
- }
-
- getKeyframeSelectionAnchor(): SelectedKeyframeRef | null {
- return this.keyframeSelectionAnchor;
- }
-
- setSelectedElements({ elements }: { elements: ElementRef[] }): void {
- this.selectedElements = elements;
- this.selectedKeyframes = [];
- this.keyframeSelectionAnchor = null;
- this.notify();
- }
-
- setSelectedKeyframes({
- keyframes,
- anchorKeyframe,
- }: {
- keyframes: SelectedKeyframeRef[];
- anchorKeyframe?: SelectedKeyframeRef | null;
- }): void {
- this.selectedKeyframes = keyframes;
- if (anchorKeyframe !== undefined) {
- this.keyframeSelectionAnchor = anchorKeyframe;
- } else if (keyframes.length === 0) {
- this.keyframeSelectionAnchor = null;
- }
- this.notify();
- }
-
- clearSelection(): void {
- this.selectedElements = [];
- this.selectedKeyframes = [];
- this.keyframeSelectionAnchor = null;
- this.notify();
- }
-
- clearKeyframeSelection(): void {
- this.selectedKeyframes = [];
- this.keyframeSelectionAnchor = null;
- this.notify();
- }
-
- subscribe(listener: () => void): () => void {
- this.listeners.add(listener);
- return () => this.listeners.delete(listener);
- }
-
- private notify(): void {
- this.listeners.forEach((fn) => {
- fn();
- });
- }
-}
+import type { EditorCore } from "@/core"; +import type { SelectedKeyframeRef } from "@/lib/animation/types"; +import type { + EditorSelectionKind, + EditorSelectionPatch, + EditorSelectionSnapshot, + SelectedMaskPointSelection, +} from "@/lib/selection/editor-selection"; +import type { ElementRef } from "@/lib/timeline/types"; + +export class SelectionManager { + private selectedElements: ElementRef[] = []; + private selectedKeyframes: SelectedKeyframeRef[] = []; + private keyframeSelectionAnchor: SelectedKeyframeRef | null = null; + private selectedMaskPoints: SelectedMaskPointSelection | null = null; + private listeners = new Set<() => void>(); + + constructor(editor: EditorCore) { + void editor; + } + + getSelectedElements(): ElementRef[] { + return this.selectedElements; + } + + getSelectedKeyframes(): SelectedKeyframeRef[] { + return this.selectedKeyframes; + } + + getKeyframeSelectionAnchor(): SelectedKeyframeRef | null { + return this.keyframeSelectionAnchor; + } + + getSelectedMaskPointSelection(): SelectedMaskPointSelection | null { + return this.selectedMaskPoints; + } + + getActiveSelectionKind(): EditorSelectionKind | null { + if ((this.selectedMaskPoints?.pointIds.length ?? 0) > 0) { + return "mask-points"; + } + if (this.selectedKeyframes.length > 0) { + return "keyframes"; + } + if (this.selectedElements.length > 0) { + return "elements"; + } + return null; + } + + getSnapshot(): EditorSelectionSnapshot { + return { + selectedElements: [...this.selectedElements], + selectedKeyframes: [...this.selectedKeyframes], + keyframeSelectionAnchor: this.keyframeSelectionAnchor, + selectedMaskPoints: this.selectedMaskPoints + ? { + ...this.selectedMaskPoints, + pointIds: [...this.selectedMaskPoints.pointIds], + } + : null, + }; + } + + setSelectedElements({ elements }: { elements: ElementRef[] }): void { + this.selectedElements = elements; + this.selectedKeyframes = []; + this.keyframeSelectionAnchor = null; + this.selectedMaskPoints = null; + this.notify(); + } + + setSelectedKeyframes({ + keyframes, + anchorKeyframe, + }: { + keyframes: SelectedKeyframeRef[]; + anchorKeyframe?: SelectedKeyframeRef | null; + }): void { + this.selectedKeyframes = keyframes; + if (anchorKeyframe !== undefined) { + this.keyframeSelectionAnchor = anchorKeyframe; + } else if (keyframes.length === 0) { + this.keyframeSelectionAnchor = null; + } + this.selectedMaskPoints = null; + this.notify(); + } + + setSelectedMaskPoints({ + selection, + }: { + selection: SelectedMaskPointSelection | null; + }): void { + this.selectedMaskPoints = + selection && selection.pointIds.length > 0 + ? { + ...selection, + pointIds: [...selection.pointIds], + } + : null; + this.selectedKeyframes = []; + this.keyframeSelectionAnchor = null; + this.notify(); + } + + clearSelection(): void { + this.selectedElements = []; + this.selectedKeyframes = []; + this.keyframeSelectionAnchor = null; + this.selectedMaskPoints = null; + this.notify(); + } + + clearKeyframeSelection(): void { + this.selectedKeyframes = []; + this.keyframeSelectionAnchor = null; + this.notify(); + } + + clearMaskPointSelection(): void { + if (!this.selectedMaskPoints) { + return; + } + this.selectedMaskPoints = null; + this.notify(); + } + + clearMostSpecificSelection(): boolean { + const activeSelectionKind = this.getActiveSelectionKind(); + if (activeSelectionKind === "mask-points") { + this.clearMaskPointSelection(); + return true; + } + if (activeSelectionKind === "keyframes") { + this.clearKeyframeSelection(); + return true; + } + if (activeSelectionKind === "elements") { + this.setSelectedElements({ elements: [] }); + return true; + } + return false; + } + + applySelectionPatch({ + patch, + }: { + patch: EditorSelectionPatch; + }): EditorSelectionSnapshot { + if (patch.selectedElements !== undefined) { + this.selectedElements = [...patch.selectedElements]; + } + if (patch.selectedKeyframes !== undefined) { + this.selectedKeyframes = [...patch.selectedKeyframes]; + } + if (patch.keyframeSelectionAnchor !== undefined) { + this.keyframeSelectionAnchor = patch.keyframeSelectionAnchor; + } + if (patch.selectedMaskPoints !== undefined) { + this.selectedMaskPoints = patch.selectedMaskPoints + ? { + ...patch.selectedMaskPoints, + pointIds: [...patch.selectedMaskPoints.pointIds], + } + : null; + } + this.notify(); + return this.getSnapshot(); + } + + restoreSnapshot({ snapshot }: { snapshot: EditorSelectionSnapshot }): void { + this.selectedElements = [...snapshot.selectedElements]; + this.selectedKeyframes = [...snapshot.selectedKeyframes]; + this.keyframeSelectionAnchor = snapshot.keyframeSelectionAnchor; + this.selectedMaskPoints = snapshot.selectedMaskPoints + ? { + ...snapshot.selectedMaskPoints, + pointIds: [...snapshot.selectedMaskPoints.pointIds], + } + : null; + this.notify(); + } + + subscribe(listener: () => void): () => void { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + private notify(): void { + this.listeners.forEach((fn) => { + fn(); + }); + } +} |
