@aura3d/editor-runtime 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +36 -5
- package/src/AnimationSceneEditor.ts +0 -368
- package/src/AssetDropZone.ts +0 -144
- package/src/CameraPathEditor.ts +0 -223
- package/src/Command.ts +0 -11
- package/src/CommandHistory.ts +0 -136
- package/src/CurveEditor.ts +0 -152
- package/src/DiagnosticsOverlayModel.ts +0 -85
- package/src/EditorPluginHost.ts +0 -106
- package/src/EditorRuntime.ts +0 -349
- package/src/EditorStateModel.ts +0 -336
- package/src/EpisodeReviewPanel.ts +0 -236
- package/src/EventTrackEditor.ts +0 -169
- package/src/Gizmo.ts +0 -101
- package/src/HierarchyModel.ts +0 -57
- package/src/InspectorModel.ts +0 -71
- package/src/KeyframeEditor.ts +0 -214
- package/src/LocalizationAccessibilityFixtures.ts +0 -368
- package/src/MaterialVariantWorkflow.ts +0 -101
- package/src/MultiUserReviewWorkflow.ts +0 -229
- package/src/NonlinearAnimationEditor.ts +0 -262
- package/src/PickingService.ts +0 -174
- package/src/PlayModeBridge.ts +0 -27
- package/src/PrefabRegistry.ts +0 -164
- package/src/ProjectSerializer.ts +0 -200
- package/src/PropertyPanel.ts +0 -82
- package/src/RenderQueuePanel.ts +0 -106
- package/src/RotateGizmo.ts +0 -35
- package/src/ScaleGizmo.ts +0 -40
- package/src/SceneOutliner.ts +0 -78
- package/src/Selection.ts +0 -63
- package/src/ShaderGraphModel.ts +0 -254
- package/src/StaticExportRuntime.ts +0 -322
- package/src/TimelineEditorController.ts +0 -525
- package/src/TimelineModel.ts +0 -466
- package/src/TimelineRuntimeBridge.ts +0 -280
- package/src/TimelineTrackTypes.ts +0 -89
- package/src/TimelineUI.ts +0 -153
- package/src/TranslateGizmo.ts +0 -39
- package/src/VisualReviewDashboard.ts +0 -87
- package/src/commands/CreateNodeCommand.ts +0 -23
- package/src/commands/DeleteNodeCommand.ts +0 -74
- package/src/commands/ReparentNodeCommand.ts +0 -55
- package/src/commands/SetPropertyCommand.ts +0 -47
- package/src/commands/TransformCommand.ts +0 -107
- package/src/index.ts +0 -140
- package/tests/editor-runtime.test.ts +0 -1087
- package/tsconfig.json +0 -7
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { TimelineModel } from "./TimelineModel";
|
|
2
|
-
import type {
|
|
3
|
-
TimelineActiveClipSnapshot,
|
|
4
|
-
TimelineClipBlendMode,
|
|
5
|
-
TimelineSignalEventSnapshot,
|
|
6
|
-
TimelineSnapshot
|
|
7
|
-
} from "./TimelineModel";
|
|
8
|
-
|
|
9
|
-
export interface TimelineRuntimeBindingConfig {
|
|
10
|
-
readonly trackId?: string;
|
|
11
|
-
readonly trackName?: string;
|
|
12
|
-
readonly targetId: string;
|
|
13
|
-
readonly assetId?: string;
|
|
14
|
-
readonly clipNameMap?: Record<string, string>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface TimelineRuntimeAnimationApplication {
|
|
18
|
-
readonly targetId: string;
|
|
19
|
-
readonly trackId: string;
|
|
20
|
-
readonly trackName: string;
|
|
21
|
-
readonly clipId: string;
|
|
22
|
-
readonly clipName: string;
|
|
23
|
-
readonly assetId?: string;
|
|
24
|
-
readonly localTime: number;
|
|
25
|
-
readonly assetTime: number;
|
|
26
|
-
readonly normalizedTime: number;
|
|
27
|
-
readonly blendWeight: number;
|
|
28
|
-
readonly blendMode: TimelineClipBlendMode;
|
|
29
|
-
readonly playback: TimelineSnapshot["playback"];
|
|
30
|
-
readonly properties: Record<string, string | number | boolean>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface TimelineRuntimeSignalDispatch {
|
|
34
|
-
readonly targetId?: string;
|
|
35
|
-
readonly trackId: string;
|
|
36
|
-
readonly trackName: string;
|
|
37
|
-
readonly clipId: string;
|
|
38
|
-
readonly clipName: string;
|
|
39
|
-
readonly event: string;
|
|
40
|
-
readonly time: number;
|
|
41
|
-
readonly localTime: number;
|
|
42
|
-
readonly properties: Record<string, string | number | boolean>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface TimelineRuntimeTarget {
|
|
46
|
-
readonly id: string;
|
|
47
|
-
applyTimelineAnimation?(application: TimelineRuntimeAnimationApplication): void;
|
|
48
|
-
applyTimelineSignal?(dispatch: TimelineRuntimeSignalDispatch): void;
|
|
49
|
-
snapshot?(): unknown;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface TimelineRuntimeBridgeConfig {
|
|
53
|
-
readonly timeline: TimelineModel;
|
|
54
|
-
readonly targets: readonly TimelineRuntimeTarget[];
|
|
55
|
-
readonly bindings?: readonly TimelineRuntimeBindingConfig[];
|
|
56
|
-
readonly strictBindings?: boolean;
|
|
57
|
-
readonly dispatchSignals?: boolean;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface TimelineRuntimeTargetSnapshot {
|
|
61
|
-
readonly id: string;
|
|
62
|
-
readonly appliedAnimationCount: number;
|
|
63
|
-
readonly dispatchedSignalCount: number;
|
|
64
|
-
readonly snapshot?: unknown;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface TimelineRuntimeBridgeSnapshot {
|
|
68
|
-
readonly kind: "aura-editor-timeline-runtime-bridge";
|
|
69
|
-
readonly timeline: TimelineSnapshot;
|
|
70
|
-
readonly appliedAnimationCount: number;
|
|
71
|
-
readonly dispatchedSignalCount: number;
|
|
72
|
-
readonly warnings: readonly string[];
|
|
73
|
-
readonly targets: readonly TimelineRuntimeTargetSnapshot[];
|
|
74
|
-
readonly lastApplications: readonly TimelineRuntimeAnimationApplication[];
|
|
75
|
-
readonly lastSignals: readonly TimelineRuntimeSignalDispatch[];
|
|
76
|
-
readonly evidence: {
|
|
77
|
-
readonly timelineToRuntimeBridge: true;
|
|
78
|
-
readonly deterministicApplyAt: true;
|
|
79
|
-
readonly animationClipBinding: boolean;
|
|
80
|
-
readonly signalDispatch: boolean;
|
|
81
|
-
readonly runtimeTargetSnapshots: boolean;
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
interface TargetCounters {
|
|
86
|
-
appliedAnimationCount: number;
|
|
87
|
-
dispatchedSignalCount: number;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export class TimelineRuntimeBridge {
|
|
91
|
-
private readonly timeline: TimelineModel;
|
|
92
|
-
private readonly targets = new Map<string, TimelineRuntimeTarget>();
|
|
93
|
-
private readonly counters = new Map<string, TargetCounters>();
|
|
94
|
-
private readonly bindings: readonly TimelineRuntimeBindingConfig[];
|
|
95
|
-
private readonly strictBindings: boolean;
|
|
96
|
-
private readonly dispatchSignals: boolean;
|
|
97
|
-
private readonly seenSignalKeys = new Set<string>();
|
|
98
|
-
private lastTimelineTime = 0;
|
|
99
|
-
private lastSnapshot: TimelineRuntimeBridgeSnapshot;
|
|
100
|
-
|
|
101
|
-
constructor(config: TimelineRuntimeBridgeConfig) {
|
|
102
|
-
this.timeline = config.timeline;
|
|
103
|
-
this.bindings = config.bindings ?? [];
|
|
104
|
-
this.strictBindings = config.strictBindings ?? false;
|
|
105
|
-
this.dispatchSignals = config.dispatchSignals ?? true;
|
|
106
|
-
for (const target of config.targets) {
|
|
107
|
-
const id = target.id.trim();
|
|
108
|
-
if (!id) throw new Error("Timeline runtime target id is required.");
|
|
109
|
-
if (this.targets.has(id)) throw new Error(`Duplicate timeline runtime target id: ${id}`);
|
|
110
|
-
this.targets.set(id, target);
|
|
111
|
-
this.counters.set(id, { appliedAnimationCount: 0, dispatchedSignalCount: 0 });
|
|
112
|
-
}
|
|
113
|
-
this.lastSnapshot = this.createBridgeSnapshot(this.timeline.snapshot(), [], [], []);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
step(deltaSeconds: number): TimelineRuntimeBridgeSnapshot {
|
|
117
|
-
this.timeline.tick(deltaSeconds);
|
|
118
|
-
return this.applyCurrent();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
applyAt(time: number, options: { readonly replaySignals?: boolean } = {}): TimelineRuntimeBridgeSnapshot {
|
|
122
|
-
this.timeline.seek(time);
|
|
123
|
-
if (options.replaySignals) this.resetSignalCursor();
|
|
124
|
-
return this.applyCurrent();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
applyCurrent(): TimelineRuntimeBridgeSnapshot {
|
|
128
|
-
const timelineSnapshot = this.timeline.snapshot();
|
|
129
|
-
if (timelineSnapshot.time < this.lastTimelineTime) {
|
|
130
|
-
this.resetSignalCursor();
|
|
131
|
-
}
|
|
132
|
-
this.lastTimelineTime = timelineSnapshot.time;
|
|
133
|
-
|
|
134
|
-
const warnings: string[] = [];
|
|
135
|
-
const applications = this.applyAnimations(timelineSnapshot, warnings);
|
|
136
|
-
const signals = this.dispatchSignals ? this.applySignals(timelineSnapshot, warnings) : [];
|
|
137
|
-
this.lastSnapshot = this.createBridgeSnapshot(timelineSnapshot, applications, signals, warnings);
|
|
138
|
-
return this.lastSnapshot;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
resetSignalCursor(): void {
|
|
142
|
-
this.seenSignalKeys.clear();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
snapshot(): TimelineRuntimeBridgeSnapshot {
|
|
146
|
-
return this.lastSnapshot;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private applyAnimations(snapshot: TimelineSnapshot, warnings: string[]): TimelineRuntimeAnimationApplication[] {
|
|
150
|
-
const applications: TimelineRuntimeAnimationApplication[] = [];
|
|
151
|
-
for (const clip of snapshot.activeClips) {
|
|
152
|
-
if (clip.trackType !== "animation") continue;
|
|
153
|
-
const binding = this.resolveBinding(clip);
|
|
154
|
-
const targetId = clip.runtimeTargetId ?? binding?.targetId;
|
|
155
|
-
if (!targetId) {
|
|
156
|
-
this.warnOrThrow(`No runtime target binding for animation track ${clip.trackId}.`, warnings);
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
const target = this.targets.get(targetId);
|
|
160
|
-
if (!target) {
|
|
161
|
-
this.warnOrThrow(`Runtime target is not registered for timeline binding: ${targetId}`, warnings);
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
const clipName = binding?.clipNameMap?.[clip.assetClipName ?? clip.clipName] ?? binding?.clipNameMap?.[clip.clipName] ?? clip.assetClipName ?? clip.clipName;
|
|
165
|
-
const application: TimelineRuntimeAnimationApplication = {
|
|
166
|
-
targetId,
|
|
167
|
-
trackId: clip.trackId,
|
|
168
|
-
trackName: clip.trackName,
|
|
169
|
-
clipId: clip.clipId,
|
|
170
|
-
clipName,
|
|
171
|
-
assetId: clip.assetId ?? binding?.assetId,
|
|
172
|
-
localTime: clip.localTime,
|
|
173
|
-
assetTime: clip.assetTime,
|
|
174
|
-
normalizedTime: clip.normalizedTime,
|
|
175
|
-
blendWeight: clip.blendWeight,
|
|
176
|
-
blendMode: clip.blendMode,
|
|
177
|
-
playback: snapshot.playback,
|
|
178
|
-
properties: { ...clip.properties }
|
|
179
|
-
};
|
|
180
|
-
target.applyTimelineAnimation?.(application);
|
|
181
|
-
this.counters.get(targetId)!.appliedAnimationCount += 1;
|
|
182
|
-
applications.push(application);
|
|
183
|
-
}
|
|
184
|
-
return applications;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private applySignals(snapshot: TimelineSnapshot, warnings: string[]): TimelineRuntimeSignalDispatch[] {
|
|
188
|
-
const signals: TimelineRuntimeSignalDispatch[] = [];
|
|
189
|
-
for (const event of snapshot.signalEventDetails) {
|
|
190
|
-
const key = `${event.trackId}:${event.clipId}:${event.event}`;
|
|
191
|
-
if (this.seenSignalKeys.has(key)) continue;
|
|
192
|
-
this.seenSignalKeys.add(key);
|
|
193
|
-
const targetId = this.resolveSignalTargetId(event);
|
|
194
|
-
const dispatch: TimelineRuntimeSignalDispatch = {
|
|
195
|
-
targetId,
|
|
196
|
-
trackId: event.trackId,
|
|
197
|
-
trackName: event.trackName,
|
|
198
|
-
clipId: event.clipId,
|
|
199
|
-
clipName: event.clipName,
|
|
200
|
-
event: event.event,
|
|
201
|
-
time: event.time,
|
|
202
|
-
localTime: event.localTime,
|
|
203
|
-
properties: { ...event.properties }
|
|
204
|
-
};
|
|
205
|
-
if (targetId) {
|
|
206
|
-
const target = this.targets.get(targetId);
|
|
207
|
-
if (!target) {
|
|
208
|
-
this.warnOrThrow(`Runtime target is not registered for timeline signal: ${targetId}`, warnings);
|
|
209
|
-
signals.push(dispatch);
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
target.applyTimelineSignal?.(dispatch);
|
|
213
|
-
this.counters.get(targetId)!.dispatchedSignalCount += 1;
|
|
214
|
-
}
|
|
215
|
-
signals.push(dispatch);
|
|
216
|
-
}
|
|
217
|
-
return signals;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private resolveBinding(clip: TimelineActiveClipSnapshot): TimelineRuntimeBindingConfig | undefined {
|
|
221
|
-
return this.bindings.find((binding) => {
|
|
222
|
-
if (binding.trackId && binding.trackId === clip.trackId) return true;
|
|
223
|
-
if (binding.trackName && binding.trackName === clip.trackName) return true;
|
|
224
|
-
return false;
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private resolveSignalTargetId(event: TimelineSignalEventSnapshot): string | undefined {
|
|
229
|
-
if (typeof event.properties.runtimeNodeId === "string" && event.properties.runtimeNodeId.trim().length > 0) return event.properties.runtimeNodeId;
|
|
230
|
-
if (typeof event.properties.targetId === "string" && event.properties.targetId.trim().length > 0) return event.properties.targetId;
|
|
231
|
-
const binding = this.bindings.find((candidate) => {
|
|
232
|
-
if (candidate.trackId && candidate.trackId === event.trackId) return true;
|
|
233
|
-
if (candidate.trackName && candidate.trackName === event.trackName) return true;
|
|
234
|
-
return false;
|
|
235
|
-
});
|
|
236
|
-
return binding?.targetId;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private createBridgeSnapshot(
|
|
240
|
-
timeline: TimelineSnapshot,
|
|
241
|
-
applications: readonly TimelineRuntimeAnimationApplication[],
|
|
242
|
-
signals: readonly TimelineRuntimeSignalDispatch[],
|
|
243
|
-
warnings: readonly string[]
|
|
244
|
-
): TimelineRuntimeBridgeSnapshot {
|
|
245
|
-
return {
|
|
246
|
-
kind: "aura-editor-timeline-runtime-bridge",
|
|
247
|
-
timeline,
|
|
248
|
-
appliedAnimationCount: [...this.counters.values()].reduce((total, counter) => total + counter.appliedAnimationCount, 0),
|
|
249
|
-
dispatchedSignalCount: [...this.counters.values()].reduce((total, counter) => total + counter.dispatchedSignalCount, 0),
|
|
250
|
-
warnings,
|
|
251
|
-
targets: [...this.targets.values()].map((target) => {
|
|
252
|
-
const counter = this.counters.get(target.id)!;
|
|
253
|
-
return {
|
|
254
|
-
id: target.id,
|
|
255
|
-
appliedAnimationCount: counter.appliedAnimationCount,
|
|
256
|
-
dispatchedSignalCount: counter.dispatchedSignalCount,
|
|
257
|
-
snapshot: target.snapshot?.()
|
|
258
|
-
};
|
|
259
|
-
}),
|
|
260
|
-
lastApplications: applications,
|
|
261
|
-
lastSignals: signals,
|
|
262
|
-
evidence: {
|
|
263
|
-
timelineToRuntimeBridge: true,
|
|
264
|
-
deterministicApplyAt: true,
|
|
265
|
-
animationClipBinding: applications.length > 0,
|
|
266
|
-
signalDispatch: signals.length > 0,
|
|
267
|
-
runtimeTargetSnapshots: [...this.targets.values()].some((target) => typeof target.snapshot === "function")
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
private warnOrThrow(message: string, warnings: string[]): void {
|
|
273
|
-
if (this.strictBindings) throw new Error(message);
|
|
274
|
-
warnings.push(message);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export function createTimelineRuntimeBridge(config: TimelineRuntimeBridgeConfig): TimelineRuntimeBridge {
|
|
279
|
-
return new TimelineRuntimeBridge(config);
|
|
280
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import type { TimelineTrackConfig } from "./TimelineModel";
|
|
2
|
-
|
|
3
|
-
export type TimelineEditorTrackKind =
|
|
4
|
-
| "shot"
|
|
5
|
-
| "animation"
|
|
6
|
-
| "audio"
|
|
7
|
-
| "viseme"
|
|
8
|
-
| "camera"
|
|
9
|
-
| "dialogue"
|
|
10
|
-
| "sfx"
|
|
11
|
-
| "caption"
|
|
12
|
-
| "selection"
|
|
13
|
-
| "signal"
|
|
14
|
-
| "generic";
|
|
15
|
-
|
|
16
|
-
export interface TimelineTrackTypeDefinition {
|
|
17
|
-
readonly kind: TimelineEditorTrackKind;
|
|
18
|
-
readonly timelineType: TimelineTrackConfig["type"];
|
|
19
|
-
readonly label: string;
|
|
20
|
-
readonly laneHeight: number;
|
|
21
|
-
readonly color: string;
|
|
22
|
-
readonly acceptsClips: boolean;
|
|
23
|
-
readonly acceptsKeyframes: boolean;
|
|
24
|
-
readonly waveform: boolean;
|
|
25
|
-
readonly lockedByDefault?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const TIMELINE_TRACK_TYPES: readonly TimelineTrackTypeDefinition[] = [
|
|
29
|
-
{ kind: "shot", timelineType: "generic", label: "Shots", laneHeight: 36, color: "#2563eb", acceptsClips: true, acceptsKeyframes: false, waveform: false },
|
|
30
|
-
{ kind: "animation", timelineType: "animation", label: "Animation", laneHeight: 44, color: "#16a34a", acceptsClips: true, acceptsKeyframes: true, waveform: false },
|
|
31
|
-
{ kind: "audio", timelineType: "audio", label: "Audio", laneHeight: 48, color: "#db2777", acceptsClips: true, acceptsKeyframes: false, waveform: true },
|
|
32
|
-
{ kind: "viseme", timelineType: "generic", label: "Visemes", laneHeight: 34, color: "#ea580c", acceptsClips: true, acceptsKeyframes: true, waveform: true },
|
|
33
|
-
{ kind: "camera", timelineType: "camera", label: "Camera", laneHeight: 40, color: "#7c3aed", acceptsClips: true, acceptsKeyframes: true, waveform: false },
|
|
34
|
-
{ kind: "dialogue", timelineType: "audio", label: "Dialogue", laneHeight: 42, color: "#0891b2", acceptsClips: true, acceptsKeyframes: false, waveform: true },
|
|
35
|
-
{ kind: "sfx", timelineType: "audio", label: "SFX", laneHeight: 36, color: "#c2410c", acceptsClips: true, acceptsKeyframes: false, waveform: true },
|
|
36
|
-
{ kind: "caption", timelineType: "generic", label: "Captions", laneHeight: 32, color: "#475569", acceptsClips: true, acceptsKeyframes: false, waveform: false },
|
|
37
|
-
{ kind: "selection", timelineType: "selection", label: "Selection", laneHeight: 32, color: "#64748b", acceptsClips: false, acceptsKeyframes: false, waveform: false },
|
|
38
|
-
{ kind: "signal", timelineType: "signal", label: "Signals", laneHeight: 32, color: "#ca8a04", acceptsClips: true, acceptsKeyframes: false, waveform: false },
|
|
39
|
-
{ kind: "generic", timelineType: "generic", label: "Generic", laneHeight: 32, color: "#334155", acceptsClips: true, acceptsKeyframes: false, waveform: false }
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
export function timelineTrackTypeDefinition(kind: TimelineEditorTrackKind): TimelineTrackTypeDefinition {
|
|
43
|
-
const definition = TIMELINE_TRACK_TYPES.find((candidate) => candidate.kind === kind);
|
|
44
|
-
if (!definition) {
|
|
45
|
-
throw new Error(`Unknown timeline editor track kind: ${kind}`);
|
|
46
|
-
}
|
|
47
|
-
return definition;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function timelineTrackKindFromConfig(track: Pick<TimelineTrackConfig, "type" | "properties">): TimelineEditorTrackKind {
|
|
51
|
-
const storedKind = track.properties?.auraTrackKind;
|
|
52
|
-
if (typeof storedKind === "string" && TIMELINE_TRACK_TYPES.some((definition) => definition.kind === storedKind)) {
|
|
53
|
-
return storedKind as TimelineEditorTrackKind;
|
|
54
|
-
}
|
|
55
|
-
if (track.type === "animation" || track.type === "audio" || track.type === "camera" || track.type === "selection" || track.type === "signal") {
|
|
56
|
-
return track.type;
|
|
57
|
-
}
|
|
58
|
-
return "generic";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface CreateTimelineTrackConfigOptions {
|
|
62
|
-
readonly id?: string;
|
|
63
|
-
readonly muted?: boolean;
|
|
64
|
-
readonly locked?: boolean;
|
|
65
|
-
readonly weight?: number;
|
|
66
|
-
readonly properties?: TimelineTrackConfig["properties"];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function createTimelineTrackConfig(
|
|
70
|
-
kind: TimelineEditorTrackKind,
|
|
71
|
-
name = timelineTrackTypeDefinition(kind).label,
|
|
72
|
-
options: CreateTimelineTrackConfigOptions = {}
|
|
73
|
-
): TimelineTrackConfig {
|
|
74
|
-
const definition = timelineTrackTypeDefinition(kind);
|
|
75
|
-
return {
|
|
76
|
-
id: options.id,
|
|
77
|
-
name,
|
|
78
|
-
type: definition.timelineType,
|
|
79
|
-
muted: options.muted,
|
|
80
|
-
locked: options.locked ?? definition.lockedByDefault,
|
|
81
|
-
weight: options.weight,
|
|
82
|
-
properties: {
|
|
83
|
-
...(options.properties ?? {}),
|
|
84
|
-
auraTrackKind: kind,
|
|
85
|
-
laneHeight: definition.laneHeight,
|
|
86
|
-
color: definition.color
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
}
|
package/src/TimelineUI.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { TimelineEditorController, type TimelineEditorSnapshot } from "./TimelineEditorController";
|
|
2
|
-
|
|
3
|
-
export interface TimelineUIOptions {
|
|
4
|
-
readonly document?: Document;
|
|
5
|
-
readonly className?: string;
|
|
6
|
-
readonly onRender?: (snapshot: TimelineEditorSnapshot) => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface TimelineUIRenderResult {
|
|
10
|
-
readonly root: HTMLElement;
|
|
11
|
-
readonly snapshot: TimelineEditorSnapshot;
|
|
12
|
-
dispose(): void;
|
|
13
|
-
render(): TimelineUIRenderResult;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class TimelineUI {
|
|
17
|
-
private readonly documentRef: Document;
|
|
18
|
-
private readonly listeners: (() => void)[] = [];
|
|
19
|
-
|
|
20
|
-
constructor(
|
|
21
|
-
private readonly controller: TimelineEditorController,
|
|
22
|
-
private readonly container: HTMLElement,
|
|
23
|
-
private readonly options: TimelineUIOptions = {}
|
|
24
|
-
) {
|
|
25
|
-
this.documentRef = options.document ?? container.ownerDocument;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
render(): TimelineUIRenderResult {
|
|
29
|
-
this.dispose();
|
|
30
|
-
const snapshot = this.controller.snapshot();
|
|
31
|
-
this.container.replaceChildren();
|
|
32
|
-
this.container.className = this.options.className ?? "aura-timeline";
|
|
33
|
-
this.container.tabIndex = this.container.tabIndex >= 0 ? this.container.tabIndex : 0;
|
|
34
|
-
|
|
35
|
-
const toolbar = this.el("div", "aura-timeline__toolbar");
|
|
36
|
-
toolbar.append(
|
|
37
|
-
this.button("Play", () => {
|
|
38
|
-
this.controller.togglePlayback();
|
|
39
|
-
this.render();
|
|
40
|
-
}),
|
|
41
|
-
this.button("Start", () => {
|
|
42
|
-
this.controller.scrubTo(0);
|
|
43
|
-
this.render();
|
|
44
|
-
}),
|
|
45
|
-
this.button("End", () => {
|
|
46
|
-
this.controller.scrubTo(this.controller.timeline.duration);
|
|
47
|
-
this.render();
|
|
48
|
-
})
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const ruler = this.el("div", "aura-timeline__ruler");
|
|
52
|
-
const durationPixels = Math.max(1, this.controller.timeline.duration * snapshot.zoomPixelsPerSecond);
|
|
53
|
-
ruler.style.width = `${durationPixels}px`;
|
|
54
|
-
ruler.append(this.marker("playhead", this.controller.timeline.currentTime));
|
|
55
|
-
for (let second = 0; second <= Math.ceil(this.controller.timeline.duration); second += 1) {
|
|
56
|
-
const tick = this.marker("tick", second);
|
|
57
|
-
tick.textContent = `${second}s`;
|
|
58
|
-
ruler.append(tick);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const tracks = this.el("div", "aura-timeline__tracks");
|
|
62
|
-
for (const track of this.controller.timeline.tracks) {
|
|
63
|
-
const trackSnapshot = snapshot.tracks.find((candidate) => candidate.id === track.id);
|
|
64
|
-
const lane = this.el("div", "aura-timeline__track");
|
|
65
|
-
lane.dataset.trackId = track.id;
|
|
66
|
-
lane.dataset.trackKind = trackSnapshot?.kind ?? "generic";
|
|
67
|
-
lane.dataset.waveform = String(trackSnapshot?.kind === "dialogue" || trackSnapshot?.kind === "audio" || trackSnapshot?.kind === "sfx" || trackSnapshot?.kind === "viseme");
|
|
68
|
-
lane.style.minHeight = `${trackSnapshot?.laneHeight ?? 32}px`;
|
|
69
|
-
const label = this.el("div", "aura-timeline__track-label");
|
|
70
|
-
label.textContent = track.name;
|
|
71
|
-
const clips = this.el("div", "aura-timeline__clips");
|
|
72
|
-
clips.style.width = `${durationPixels}px`;
|
|
73
|
-
for (const clip of track.clips) {
|
|
74
|
-
const clipEl = this.el("button", "aura-timeline__clip") as HTMLButtonElement;
|
|
75
|
-
clipEl.type = "button";
|
|
76
|
-
clipEl.dataset.clipId = clip.id;
|
|
77
|
-
clipEl.textContent = clip.name;
|
|
78
|
-
clipEl.style.left = `${clip.startTime * snapshot.zoomPixelsPerSecond}px`;
|
|
79
|
-
clipEl.style.width = `${Math.max(8, clip.duration * snapshot.zoomPixelsPerSecond)}px`;
|
|
80
|
-
clipEl.style.background = trackSnapshot?.color ?? "#334155";
|
|
81
|
-
if (snapshot.selectedIds.includes(clip.id)) clipEl.dataset.selected = "true";
|
|
82
|
-
const click = (event: MouseEvent): void => {
|
|
83
|
-
if (trackSnapshot?.kind === "shot") {
|
|
84
|
-
this.controller.jumpToShot(clip.id);
|
|
85
|
-
}
|
|
86
|
-
this.controller.selectClip(clip.id, event.shiftKey);
|
|
87
|
-
this.render();
|
|
88
|
-
};
|
|
89
|
-
clipEl.addEventListener("click", click);
|
|
90
|
-
this.listeners.push(() => clipEl.removeEventListener("click", click));
|
|
91
|
-
clips.append(clipEl);
|
|
92
|
-
}
|
|
93
|
-
lane.append(label, clips);
|
|
94
|
-
tracks.append(lane);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const minimap = this.el("div", "aura-timeline__minimap");
|
|
98
|
-
minimap.style.setProperty("--aura-timeline-duration-px", `${durationPixels}px`);
|
|
99
|
-
minimap.textContent = `${snapshot.timeline.clipCount} clips`;
|
|
100
|
-
|
|
101
|
-
const keydown = (event: KeyboardEvent): void => {
|
|
102
|
-
if (this.controller.handleKeyboardShortcut(event.key)) {
|
|
103
|
-
event.preventDefault();
|
|
104
|
-
this.render();
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
this.container.addEventListener("keydown", keydown);
|
|
108
|
-
this.listeners.push(() => this.container.removeEventListener("keydown", keydown));
|
|
109
|
-
|
|
110
|
-
this.container.append(toolbar, ruler, tracks, minimap);
|
|
111
|
-
this.options.onRender?.(snapshot);
|
|
112
|
-
return {
|
|
113
|
-
root: this.container,
|
|
114
|
-
snapshot,
|
|
115
|
-
dispose: () => this.dispose(),
|
|
116
|
-
render: () => this.render()
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
dispose(): void {
|
|
121
|
-
for (const dispose of this.listeners.splice(0)) dispose();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private button(label: string, onClick: () => void): HTMLButtonElement {
|
|
125
|
-
const button = this.el("button", "aura-timeline__button") as HTMLButtonElement;
|
|
126
|
-
button.type = "button";
|
|
127
|
-
button.textContent = label;
|
|
128
|
-
button.addEventListener("click", onClick);
|
|
129
|
-
this.listeners.push(() => button.removeEventListener("click", onClick));
|
|
130
|
-
return button;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private marker(kind: string, time: number): HTMLElement {
|
|
134
|
-
const marker = this.el("div", `aura-timeline__${kind}`);
|
|
135
|
-
marker.style.left = `${time * this.controller.zoomPixelsPerSecond}px`;
|
|
136
|
-
marker.dataset.time = String(time);
|
|
137
|
-
return marker;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private el(tagName: string, className: string): HTMLElement {
|
|
141
|
-
const element = this.documentRef.createElement(tagName);
|
|
142
|
-
element.className = className;
|
|
143
|
-
return element;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function renderTimelineUI(
|
|
148
|
-
controller: TimelineEditorController,
|
|
149
|
-
container: HTMLElement,
|
|
150
|
-
options: TimelineUIOptions = {}
|
|
151
|
-
): TimelineUIRenderResult {
|
|
152
|
-
return new TimelineUI(controller, container, options).render();
|
|
153
|
-
}
|
package/src/TranslateGizmo.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Gizmo, type GizmoDrag } from "./Gizmo";
|
|
2
|
-
import { TransformCommand, type TransformLike } from "./commands/TransformCommand";
|
|
3
|
-
|
|
4
|
-
export class TranslateGizmo extends Gizmo {
|
|
5
|
-
async drag(input: GizmoDrag): Promise<void> {
|
|
6
|
-
if (!this.target) return;
|
|
7
|
-
const current = readTransform(this.target);
|
|
8
|
-
const next = clone(current);
|
|
9
|
-
const delta = this.snapPositionDelta(input.delta);
|
|
10
|
-
if (input.axis === "x" || input.axis === "xy" || input.axis === "xz") next.position.x += delta;
|
|
11
|
-
if (input.axis === "y" || input.axis === "xy" || input.axis === "yz") next.position.y += delta;
|
|
12
|
-
if (input.axis === "z" || input.axis === "xz" || input.axis === "yz") next.position.z += delta;
|
|
13
|
-
if (input.axis === "uniform") {
|
|
14
|
-
next.position.x += delta;
|
|
15
|
-
next.position.y += delta;
|
|
16
|
-
next.position.z += delta;
|
|
17
|
-
}
|
|
18
|
-
await this.history.execute(new TransformCommand(this.target, next));
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function readTransform(target: NonNullable<TranslateGizmo["target"]>): TransformLike {
|
|
23
|
-
if ("transform" in target) {
|
|
24
|
-
return {
|
|
25
|
-
position: { x: target.transform.position[0], y: target.transform.position[1], z: target.transform.position[2] },
|
|
26
|
-
rotation: { x: target.transform.rotation[0], y: target.transform.rotation[1], z: target.transform.rotation[2], w: target.transform.rotation[3] },
|
|
27
|
-
scale: { x: target.transform.scale[0], y: target.transform.scale[1], z: target.transform.scale[2] }
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
return clone(target);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function clone(transform: TransformLike): TransformLike {
|
|
34
|
-
return {
|
|
35
|
-
position: { ...transform.position },
|
|
36
|
-
rotation: transform.rotation ? { ...transform.rotation } : undefined,
|
|
37
|
-
scale: transform.scale ? { ...transform.scale } : undefined
|
|
38
|
-
};
|
|
39
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
EpisodeReviewManualVisemeEdit,
|
|
3
|
-
EpisodeReviewNote,
|
|
4
|
-
EpisodeReviewPanelSnapshot,
|
|
5
|
-
EpisodeReviewRejectedFrame,
|
|
6
|
-
EpisodeReviewWaveformLane
|
|
7
|
-
} from "./EpisodeReviewPanel";
|
|
8
|
-
|
|
9
|
-
export interface VisualReviewDashboardPackage {
|
|
10
|
-
readonly packageId: string;
|
|
11
|
-
readonly status: EpisodeReviewPanelSnapshot["status"];
|
|
12
|
-
readonly failedFrameCount: number;
|
|
13
|
-
readonly noteCount: number;
|
|
14
|
-
readonly waveformLaneCount: number;
|
|
15
|
-
readonly manualVisemeEditCount: number;
|
|
16
|
-
readonly approvalBlocked: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface VisualReviewDashboardSnapshot {
|
|
20
|
-
readonly kind: "visual-review-dashboard";
|
|
21
|
-
readonly packageCount: number;
|
|
22
|
-
readonly packages: readonly VisualReviewDashboardPackage[];
|
|
23
|
-
readonly failedFrames: readonly (EpisodeReviewRejectedFrame & { readonly packageId: string })[];
|
|
24
|
-
readonly reviewerNotes: readonly (EpisodeReviewNote & { readonly packageId: string })[];
|
|
25
|
-
readonly waveformLanes: readonly (EpisodeReviewWaveformLane & { readonly packageId: string })[];
|
|
26
|
-
readonly manualVisemeEdits: readonly (EpisodeReviewManualVisemeEdit & { readonly packageId: string })[];
|
|
27
|
-
readonly approvalBlockedPackageIds: readonly string[];
|
|
28
|
-
readonly evidence: {
|
|
29
|
-
readonly listsFailedFrames: boolean;
|
|
30
|
-
readonly listsReviewerNotes: boolean;
|
|
31
|
-
readonly waveformReview: boolean;
|
|
32
|
-
readonly manualVisemeEdits: boolean;
|
|
33
|
-
readonly approvalBlocking: boolean;
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export class VisualReviewDashboard {
|
|
38
|
-
private reviews: readonly EpisodeReviewPanelSnapshot[];
|
|
39
|
-
|
|
40
|
-
constructor(reviews: readonly EpisodeReviewPanelSnapshot[]) {
|
|
41
|
-
this.reviews = reviews.map((review) => ({ ...review }));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
setReviews(reviews: readonly EpisodeReviewPanelSnapshot[]): VisualReviewDashboardSnapshot {
|
|
45
|
-
this.reviews = reviews.map((review) => ({ ...review }));
|
|
46
|
-
return this.snapshot();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
snapshot(): VisualReviewDashboardSnapshot {
|
|
50
|
-
const packages = this.reviews.map((review) => ({
|
|
51
|
-
packageId: review.packageId,
|
|
52
|
-
status: review.status,
|
|
53
|
-
failedFrameCount: review.rejectedFrameCount,
|
|
54
|
-
noteCount: review.noteCount,
|
|
55
|
-
waveformLaneCount: review.waveformLaneCount,
|
|
56
|
-
manualVisemeEditCount: review.manualVisemeEditCount,
|
|
57
|
-
approvalBlocked: review.rejectedFrameCount > 0
|
|
58
|
-
}));
|
|
59
|
-
const failedFrames = this.reviews.flatMap((review) => review.rejectedFrames.map((frame) => ({ ...frame, packageId: review.packageId })));
|
|
60
|
-
const reviewerNotes = this.reviews.flatMap((review) => review.notes.map((note) => ({ ...note, packageId: review.packageId })));
|
|
61
|
-
const waveformLanes = this.reviews.flatMap((review) => (review.waveformLanes ?? []).map((lane) => ({ ...lane, packageId: review.packageId })));
|
|
62
|
-
const manualVisemeEdits = this.reviews.flatMap((review) => (review.manualVisemeEdits ?? []).map((edit) => ({ ...edit, packageId: review.packageId })));
|
|
63
|
-
const approvalBlockedPackageIds = packages.filter((item) => item.approvalBlocked).map((item) => item.packageId);
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
kind: "visual-review-dashboard",
|
|
67
|
-
packageCount: packages.length,
|
|
68
|
-
packages,
|
|
69
|
-
failedFrames,
|
|
70
|
-
reviewerNotes,
|
|
71
|
-
waveformLanes,
|
|
72
|
-
manualVisemeEdits,
|
|
73
|
-
approvalBlockedPackageIds,
|
|
74
|
-
evidence: {
|
|
75
|
-
listsFailedFrames: failedFrames.length > 0,
|
|
76
|
-
listsReviewerNotes: reviewerNotes.length > 0,
|
|
77
|
-
waveformReview: waveformLanes.length > 0,
|
|
78
|
-
manualVisemeEdits: manualVisemeEdits.length > 0,
|
|
79
|
-
approvalBlocking: approvalBlockedPackageIds.length > 0
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function createVisualReviewDashboard(reviews: readonly EpisodeReviewPanelSnapshot[]): VisualReviewDashboard {
|
|
86
|
-
return new VisualReviewDashboard(reviews);
|
|
87
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { Command } from "../Command";
|
|
2
|
-
|
|
3
|
-
export interface NodeContainer<TNode> {
|
|
4
|
-
add(node: TNode): void;
|
|
5
|
-
remove(node: TNode): void;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class CreateNodeCommand<TNode> implements Command {
|
|
9
|
-
readonly name = "Create Node";
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
private readonly container: NodeContainer<TNode>,
|
|
13
|
-
private readonly node: TNode
|
|
14
|
-
) {}
|
|
15
|
-
|
|
16
|
-
execute(): void {
|
|
17
|
-
this.container.add(this.node);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
undo(): void {
|
|
21
|
-
this.container.remove(this.node);
|
|
22
|
-
}
|
|
23
|
-
}
|