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
|
import { Command } from "@/lib/commands/base-command";
import type { TimelineElement, TimelineTrack } from "@/lib/timeline";
import { generateUUID } from "@/utils/id";
import { EditorCore } from "@/core";
import {
buildEmptyTrack,
getHighestInsertIndexForTrack,
} from "@/lib/timeline/track-utils";
import { cloneAnimations } from "@/lib/animation";
interface DuplicateElementsParams {
elements: { trackId: string; elementId: string }[];
}
export class DuplicateElementsCommand extends Command {
private duplicatedElements: { trackId: string; elementId: string }[] = [];
private savedState: TimelineTrack[] | null = null;
private previousSelection: { trackId: string; elementId: string }[] = [];
private elements: DuplicateElementsParams["elements"];
constructor({ elements }: DuplicateElementsParams) {
super();
this.elements = elements;
}
execute(): void {
const editor = EditorCore.getInstance();
this.savedState = editor.timeline.getTracks();
this.previousSelection = editor.selection.getSelectedElements();
this.duplicatedElements = [];
const updatedTracks = [...this.savedState];
for (const track of this.savedState) {
const elementsToDuplicate = this.elements.filter(
(elementEntry) => elementEntry.trackId === track.id,
);
if (elementsToDuplicate.length === 0) {
continue;
}
const elementIdsToDuplicate = new Set(
elementsToDuplicate.map((element) => element.elementId),
);
const newTrackElements: TimelineElement[] = [];
const newTrackId = generateUUID();
const newTrackBase = buildEmptyTrack({
id: newTrackId,
type: track.type,
});
for (const element of track.elements) {
if (!elementIdsToDuplicate.has(element.id)) {
continue;
}
const newId = generateUUID();
this.duplicatedElements.push({
trackId: newTrackId,
elementId: newId,
});
newTrackElements.push(
buildDuplicateElement({
element,
id: newId,
startTime: element.startTime,
}),
);
}
const newTrack = {
...newTrackBase,
elements: newTrackElements,
} as TimelineTrack;
const insertIndex = getHighestInsertIndexForTrack({
tracks: updatedTracks,
trackType: track.type,
});
updatedTracks.splice(insertIndex, 0, newTrack);
}
editor.timeline.updateTracks(updatedTracks);
if (this.duplicatedElements.length > 0) {
editor.selection.setSelectedElements({
elements: this.duplicatedElements,
});
}
}
undo(): void {
if (this.savedState) {
const editor = EditorCore.getInstance();
editor.timeline.updateTracks(this.savedState);
editor.selection.setSelectedElements({
elements: this.previousSelection,
});
}
}
getDuplicatedElements(): { trackId: string; elementId: string }[] {
return this.duplicatedElements;
}
}
function buildDuplicateElement({
element,
id,
startTime,
}: {
element: TimelineElement;
id: string;
startTime: number;
}): TimelineElement {
return {
...element,
id,
name: `${element.name} (copy)`,
startTime,
animations: cloneAnimations({
animations: element.animations,
shouldRegenerateKeyframeIds: true,
}),
};
}
|