summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/commands/timeline/element/masks/delete-custom-mask-points.ts
blob: 346eb8d9b18d0570e9fcfad50d4c6c9bf77395a7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { EditorCore } from "@/core";
import { Command, type CommandResult } from "@/lib/commands/base-command";
import {
	getCustomMaskClosedStateAfterPointRemoval,
	removeCustomMaskPoints,
} from "@/lib/masks/custom-path";
import type { CustomMask } from "@/lib/masks/types";
import { isMaskableElement, updateElementInSceneTracks } from "@/lib/timeline";
import type { MaskableElement, SceneTracks } from "@/lib/timeline";

function deletePointsFromCustomMask({
	mask,
	pointIds,
}: {
	mask: CustomMask;
	pointIds: string[];
}): CustomMask {
	const points = mask.params.path;
	const nextPoints = removeCustomMaskPoints({ points, pointIds });
	if (nextPoints.length === points.length) {
		return mask;
	}

	return {
		...mask,
		params: {
			...mask.params,
			path: nextPoints,
			closed: getCustomMaskClosedStateAfterPointRemoval({
				wasClosed: mask.params.closed,
				remainingPointCount: nextPoints.length,
			}),
		},
	};
}

function deletePointsFromElementMask({
	element,
	maskId,
	pointIds,
}: {
	element: MaskableElement;
	maskId: string;
	pointIds: string[];
}): { element: MaskableElement; didDeletePoints: boolean } {
	const currentMasks = element.masks ?? [];
	let didDeletePoints = false;
	const nextMasks = currentMasks.map((mask) => {
		if (mask.id !== maskId || mask.type !== "custom") {
			return mask;
		}

		const nextMask = deletePointsFromCustomMask({
			mask,
			pointIds,
		});
		didDeletePoints ||= nextMask !== mask;
		return nextMask;
	});

	return {
		element: didDeletePoints ? { ...element, masks: nextMasks } : element,
		didDeletePoints,
	};
}

export class DeleteCustomMaskPointsCommand extends Command {
	private savedState: SceneTracks | null = null;
	private readonly trackId: string;
	private readonly elementId: string;
	private readonly maskId: string;
	private readonly pointIds: string[];

	constructor({
		trackId,
		elementId,
		maskId,
		pointIds,
	}: {
		trackId: string;
		elementId: string;
		maskId: string;
		pointIds: string[];
	}) {
		super();
		this.trackId = trackId;
		this.elementId = elementId;
		this.maskId = maskId;
		this.pointIds = pointIds;
	}

	execute(): CommandResult | undefined {
		const editor = EditorCore.getInstance();
		this.savedState = editor.scenes.getActiveScene().tracks;

		let didDeletePoints = false;
		const updatedTracks = updateElementInSceneTracks({
			tracks: this.savedState,
			trackId: this.trackId,
			elementId: this.elementId,
			elementPredicate: isMaskableElement,
			update: (element) => {
				const result = deletePointsFromElementMask({
					element: element as MaskableElement,
					maskId: this.maskId,
					pointIds: this.pointIds,
				});
				didDeletePoints ||= result.didDeletePoints;
				return result.element;
			},
		});

		if (didDeletePoints) {
			editor.timeline.updateTracks(updatedTracks);
			return {
				selection: {
					selectedMaskPoints: null,
				},
			};
		}

		return undefined;
	}

	undo(): void {
		if (this.savedState) {
			const editor = EditorCore.getInstance();
			editor.timeline.updateTracks(this.savedState);
		}
	}
}