@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
package/src/EditorStateModel.ts
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import { normalizeGizmoSettings, type GizmoPivotMode, type GizmoSettings, type GizmoSpaceMode } from "./Gizmo";
|
|
2
|
-
|
|
3
|
-
export interface EditorStateStorage {
|
|
4
|
-
getItem(key: string): string | null;
|
|
5
|
-
setItem(key: string, value: string): void;
|
|
6
|
-
removeItem?(key: string): void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface EditorViewportSettings {
|
|
10
|
-
readonly showGrid: boolean;
|
|
11
|
-
readonly gridSize: number;
|
|
12
|
-
readonly gridSubdivisions: number;
|
|
13
|
-
readonly showAxisGizmo: boolean;
|
|
14
|
-
readonly showStats: boolean;
|
|
15
|
-
readonly showWireframe: boolean;
|
|
16
|
-
readonly showBounds: boolean;
|
|
17
|
-
readonly showLights: boolean;
|
|
18
|
-
readonly showCameras: boolean;
|
|
19
|
-
readonly fov: number;
|
|
20
|
-
readonly near: number;
|
|
21
|
-
readonly far: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface EditorGridSnapSettings {
|
|
25
|
-
readonly snapToGrid: boolean;
|
|
26
|
-
readonly positionSnap: number;
|
|
27
|
-
readonly rotationSnapEnabled: boolean;
|
|
28
|
-
readonly rotationSnapDegrees: number;
|
|
29
|
-
readonly scaleSnapEnabled: boolean;
|
|
30
|
-
readonly scaleSnap: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface EditorStateSnapshot {
|
|
34
|
-
readonly activeTool: string;
|
|
35
|
-
readonly transformSpace: GizmoSpaceMode;
|
|
36
|
-
readonly pivotMode: GizmoPivotMode;
|
|
37
|
-
readonly viewport: EditorViewportSettings;
|
|
38
|
-
readonly gridSnap: EditorGridSnapSettings;
|
|
39
|
-
readonly persisted: boolean;
|
|
40
|
-
readonly storageKey: string;
|
|
41
|
-
readonly changeCount: number;
|
|
42
|
-
readonly evidence: {
|
|
43
|
-
readonly oldCodebasePort: true;
|
|
44
|
-
readonly persistentEditorState: boolean;
|
|
45
|
-
readonly viewportSettings: boolean;
|
|
46
|
-
readonly gridSnapSettings: boolean;
|
|
47
|
-
readonly transformSpacePivotMode: boolean;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface EditorStateChange {
|
|
52
|
-
readonly property: string;
|
|
53
|
-
readonly oldValue: unknown;
|
|
54
|
-
readonly newValue: unknown;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface EditorStateModelOptions {
|
|
58
|
-
readonly storage?: EditorStateStorage;
|
|
59
|
-
readonly storageKey?: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
type Listener = (change: EditorStateChange) => void;
|
|
63
|
-
|
|
64
|
-
const defaultViewportSettings: EditorViewportSettings = {
|
|
65
|
-
showGrid: true,
|
|
66
|
-
gridSize: 10,
|
|
67
|
-
gridSubdivisions: 10,
|
|
68
|
-
showAxisGizmo: true,
|
|
69
|
-
showStats: true,
|
|
70
|
-
showWireframe: false,
|
|
71
|
-
showBounds: false,
|
|
72
|
-
showLights: true,
|
|
73
|
-
showCameras: true,
|
|
74
|
-
fov: 60,
|
|
75
|
-
near: 0.1,
|
|
76
|
-
far: 1000
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const defaultGridSnapSettings: EditorGridSnapSettings = {
|
|
80
|
-
snapToGrid: true,
|
|
81
|
-
positionSnap: 1,
|
|
82
|
-
rotationSnapEnabled: false,
|
|
83
|
-
rotationSnapDegrees: 15,
|
|
84
|
-
scaleSnapEnabled: false,
|
|
85
|
-
scaleSnap: 0.1
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
export class EditorStateModel {
|
|
89
|
-
private activeToolRef = "select";
|
|
90
|
-
private transformSpaceRef: GizmoSpaceMode = "world";
|
|
91
|
-
private pivotModeRef: GizmoPivotMode = "center";
|
|
92
|
-
private viewportRef: EditorViewportSettings = defaultViewportSettings;
|
|
93
|
-
private gridSnapRef: EditorGridSnapSettings = defaultGridSnapSettings;
|
|
94
|
-
private readonly listeners = new Set<Listener>();
|
|
95
|
-
private readonly storageKey: string;
|
|
96
|
-
private readonly storage: EditorStateStorage | undefined;
|
|
97
|
-
private persisted = false;
|
|
98
|
-
private changes = 0;
|
|
99
|
-
|
|
100
|
-
constructor(options: EditorStateModelOptions = {}) {
|
|
101
|
-
this.storage = options.storage;
|
|
102
|
-
this.storageKey = options.storageKey ?? "aura3d:editor-state:externalParity";
|
|
103
|
-
this.load();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
get activeTool(): string {
|
|
107
|
-
return this.activeToolRef;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
get transformSpace(): GizmoSpaceMode {
|
|
111
|
-
return this.transformSpaceRef;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
get pivotMode(): GizmoPivotMode {
|
|
115
|
-
return this.pivotModeRef;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
setActiveTool(tool: string): void {
|
|
119
|
-
const normalized = tool.trim();
|
|
120
|
-
if (normalized.length === 0) throw new Error("Editor state active tool cannot be empty.");
|
|
121
|
-
this.setValue("activeTool", this.activeToolRef, normalized, (value) => {
|
|
122
|
-
this.activeToolRef = value;
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
setTransformSpace(space: GizmoSpaceMode): void {
|
|
127
|
-
this.setValue("transformSpace", this.transformSpaceRef, space, (value) => {
|
|
128
|
-
this.transformSpaceRef = value;
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
setPivotMode(mode: GizmoPivotMode): void {
|
|
133
|
-
this.setValue("pivotMode", this.pivotModeRef, mode, (value) => {
|
|
134
|
-
this.pivotModeRef = value;
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
configureViewport(settings: Partial<EditorViewportSettings>): EditorViewportSettings {
|
|
139
|
-
const next = normalizeViewportSettings({ ...this.viewportRef, ...settings });
|
|
140
|
-
this.setValue("viewport", this.viewportRef, next, (value) => {
|
|
141
|
-
this.viewportRef = value;
|
|
142
|
-
});
|
|
143
|
-
return this.viewportRef;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
configureGridSnap(settings: Partial<EditorGridSnapSettings>): EditorGridSnapSettings {
|
|
147
|
-
const next = normalizeGridSnapSettings({ ...this.gridSnapRef, ...settings });
|
|
148
|
-
this.setValue("gridSnap", this.gridSnapRef, next, (value) => {
|
|
149
|
-
this.gridSnapRef = value;
|
|
150
|
-
});
|
|
151
|
-
return this.gridSnapRef;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
configureFromGizmoSettings(settings: GizmoSettings): void {
|
|
155
|
-
this.setTransformSpace(settings.spaceMode);
|
|
156
|
-
this.setPivotMode(settings.pivotMode);
|
|
157
|
-
this.configureGridSnap({
|
|
158
|
-
snapToGrid: settings.snapEnabled,
|
|
159
|
-
positionSnap: settings.positionSnap,
|
|
160
|
-
rotationSnapEnabled: settings.snapEnabled,
|
|
161
|
-
rotationSnapDegrees: settings.rotationSnapDegrees,
|
|
162
|
-
scaleSnapEnabled: settings.snapEnabled,
|
|
163
|
-
scaleSnap: settings.scaleSnap
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
gizmoSettings(): GizmoSettings {
|
|
168
|
-
return normalizeGizmoSettings({
|
|
169
|
-
snapEnabled: this.gridSnapRef.snapToGrid,
|
|
170
|
-
positionSnap: this.gridSnapRef.positionSnap,
|
|
171
|
-
rotationSnapDegrees: this.gridSnapRef.rotationSnapDegrees,
|
|
172
|
-
scaleSnap: this.gridSnapRef.scaleSnap,
|
|
173
|
-
spaceMode: this.transformSpaceRef,
|
|
174
|
-
pivotMode: this.pivotModeRef
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
snapPosition(value: number): number {
|
|
179
|
-
return this.gridSnapRef.snapToGrid ? snap(value, this.gridSnapRef.positionSnap) : value;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
snapRotation(radians: number): number {
|
|
183
|
-
return this.gridSnapRef.rotationSnapEnabled ? snap(radians, this.gridSnapRef.rotationSnapDegrees * Math.PI / 180) : radians;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
snapScale(value: number): number {
|
|
187
|
-
return this.gridSnapRef.scaleSnapEnabled ? snap(value, this.gridSnapRef.scaleSnap) : value;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
subscribe(listener: Listener): () => void {
|
|
191
|
-
this.listeners.add(listener);
|
|
192
|
-
return () => this.listeners.delete(listener);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
snapshot(): EditorStateSnapshot {
|
|
196
|
-
return {
|
|
197
|
-
activeTool: this.activeToolRef,
|
|
198
|
-
transformSpace: this.transformSpaceRef,
|
|
199
|
-
pivotMode: this.pivotModeRef,
|
|
200
|
-
viewport: this.viewportRef,
|
|
201
|
-
gridSnap: this.gridSnapRef,
|
|
202
|
-
persisted: this.persisted,
|
|
203
|
-
storageKey: this.storageKey,
|
|
204
|
-
changeCount: this.changes,
|
|
205
|
-
evidence: {
|
|
206
|
-
oldCodebasePort: true,
|
|
207
|
-
persistentEditorState: this.persisted,
|
|
208
|
-
viewportSettings: this.viewportRef.gridSize > 0 && this.viewportRef.fov > 0 && this.viewportRef.far > this.viewportRef.near,
|
|
209
|
-
gridSnapSettings: this.gridSnapRef.positionSnap > 0 && this.gridSnapRef.rotationSnapDegrees > 0 && this.gridSnapRef.scaleSnap > 0,
|
|
210
|
-
transformSpacePivotMode: Boolean(this.transformSpaceRef) && Boolean(this.pivotModeRef)
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
reset(): void {
|
|
216
|
-
this.activeToolRef = "select";
|
|
217
|
-
this.transformSpaceRef = "world";
|
|
218
|
-
this.pivotModeRef = "center";
|
|
219
|
-
this.viewportRef = defaultViewportSettings;
|
|
220
|
-
this.gridSnapRef = defaultGridSnapSettings;
|
|
221
|
-
this.changes += 1;
|
|
222
|
-
this.persist();
|
|
223
|
-
this.emit({ property: "reset", oldValue: null, newValue: this.snapshot() });
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private setValue<T>(property: string, current: T, next: T, apply: (value: T) => void): void {
|
|
227
|
-
if (deepEqual(current, next)) return;
|
|
228
|
-
apply(next);
|
|
229
|
-
this.changes += 1;
|
|
230
|
-
this.persist();
|
|
231
|
-
this.emit({ property, oldValue: current, newValue: next });
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
private emit(change: EditorStateChange): void {
|
|
235
|
-
for (const listener of this.listeners) listener(change);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private persist(): void {
|
|
239
|
-
if (!this.storage) return;
|
|
240
|
-
this.storage.setItem(this.storageKey, JSON.stringify({
|
|
241
|
-
activeTool: this.activeToolRef,
|
|
242
|
-
transformSpace: this.transformSpaceRef,
|
|
243
|
-
pivotMode: this.pivotModeRef,
|
|
244
|
-
viewport: this.viewportRef,
|
|
245
|
-
gridSnap: this.gridSnapRef
|
|
246
|
-
}));
|
|
247
|
-
this.persisted = true;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
private load(): void {
|
|
251
|
-
if (!this.storage) return;
|
|
252
|
-
const text = this.storage.getItem(this.storageKey);
|
|
253
|
-
if (!text) return;
|
|
254
|
-
try {
|
|
255
|
-
const parsed = JSON.parse(text) as unknown;
|
|
256
|
-
if (!isRecord(parsed)) return;
|
|
257
|
-
this.activeToolRef = typeof parsed.activeTool === "string" && parsed.activeTool.trim().length > 0 ? parsed.activeTool : this.activeToolRef;
|
|
258
|
-
this.transformSpaceRef = parsed.transformSpace === "local" ? "local" : "world";
|
|
259
|
-
this.pivotModeRef = parsed.pivotMode === "active" || parsed.pivotMode === "individual" ? parsed.pivotMode : "center";
|
|
260
|
-
this.viewportRef = normalizeViewportSettings({ ...this.viewportRef, ...(isRecord(parsed.viewport) ? parsed.viewport : {}) });
|
|
261
|
-
this.gridSnapRef = normalizeGridSnapSettings({ ...this.gridSnapRef, ...(isRecord(parsed.gridSnap) ? parsed.gridSnap : {}) });
|
|
262
|
-
this.persisted = true;
|
|
263
|
-
} catch {
|
|
264
|
-
this.persisted = false;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export function createMemoryEditorStateStorage(initial: Record<string, string> = {}): EditorStateStorage & { readonly values: Record<string, string> } {
|
|
270
|
-
const values = { ...initial };
|
|
271
|
-
return {
|
|
272
|
-
values,
|
|
273
|
-
getItem(key) {
|
|
274
|
-
return values[key] ?? null;
|
|
275
|
-
},
|
|
276
|
-
setItem(key, value) {
|
|
277
|
-
values[key] = value;
|
|
278
|
-
},
|
|
279
|
-
removeItem(key) {
|
|
280
|
-
delete values[key];
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function normalizeViewportSettings(settings: Partial<EditorViewportSettings>): EditorViewportSettings {
|
|
286
|
-
return {
|
|
287
|
-
showGrid: booleanOr(settings.showGrid, defaultViewportSettings.showGrid),
|
|
288
|
-
gridSize: positive(settings.gridSize, defaultViewportSettings.gridSize),
|
|
289
|
-
gridSubdivisions: Math.max(1, Math.round(positive(settings.gridSubdivisions, defaultViewportSettings.gridSubdivisions))),
|
|
290
|
-
showAxisGizmo: booleanOr(settings.showAxisGizmo, defaultViewportSettings.showAxisGizmo),
|
|
291
|
-
showStats: booleanOr(settings.showStats, defaultViewportSettings.showStats),
|
|
292
|
-
showWireframe: booleanOr(settings.showWireframe, defaultViewportSettings.showWireframe),
|
|
293
|
-
showBounds: booleanOr(settings.showBounds, defaultViewportSettings.showBounds),
|
|
294
|
-
showLights: booleanOr(settings.showLights, defaultViewportSettings.showLights),
|
|
295
|
-
showCameras: booleanOr(settings.showCameras, defaultViewportSettings.showCameras),
|
|
296
|
-
fov: clamp(positive(settings.fov, defaultViewportSettings.fov), 10, 140),
|
|
297
|
-
near: positive(settings.near, defaultViewportSettings.near),
|
|
298
|
-
far: Math.max(positive(settings.far, defaultViewportSettings.far), positive(settings.near, defaultViewportSettings.near) + 1)
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function normalizeGridSnapSettings(settings: Partial<EditorGridSnapSettings>): EditorGridSnapSettings {
|
|
303
|
-
return {
|
|
304
|
-
snapToGrid: booleanOr(settings.snapToGrid, defaultGridSnapSettings.snapToGrid),
|
|
305
|
-
positionSnap: positive(settings.positionSnap, defaultGridSnapSettings.positionSnap),
|
|
306
|
-
rotationSnapEnabled: booleanOr(settings.rotationSnapEnabled, defaultGridSnapSettings.rotationSnapEnabled),
|
|
307
|
-
rotationSnapDegrees: positive(settings.rotationSnapDegrees, defaultGridSnapSettings.rotationSnapDegrees),
|
|
308
|
-
scaleSnapEnabled: booleanOr(settings.scaleSnapEnabled, defaultGridSnapSettings.scaleSnapEnabled),
|
|
309
|
-
scaleSnap: positive(settings.scaleSnap, defaultGridSnapSettings.scaleSnap)
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function booleanOr(value: unknown, fallback: boolean): boolean {
|
|
314
|
-
return typeof value === "boolean" ? value : fallback;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function positive(value: unknown, fallback: number): number {
|
|
318
|
-
const number = Number(value);
|
|
319
|
-
return Number.isFinite(number) && number > 0 ? number : fallback;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function clamp(value: number, min: number, max: number): number {
|
|
323
|
-
return Math.max(min, Math.min(max, value));
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function snap(value: number, increment: number): number {
|
|
327
|
-
return Math.round(value / increment) * increment;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function deepEqual(left: unknown, right: unknown): boolean {
|
|
331
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
335
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
336
|
-
}
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
export type EpisodeReviewStatus = "draft" | "needs-review" | "approved" | "rejected";
|
|
2
|
-
|
|
3
|
-
export interface EpisodeReviewRejectedFrame {
|
|
4
|
-
readonly id: string;
|
|
5
|
-
readonly time: number;
|
|
6
|
-
readonly reason: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface EpisodeReviewNote {
|
|
10
|
-
readonly id: string;
|
|
11
|
-
readonly author: string;
|
|
12
|
-
readonly text: string;
|
|
13
|
-
readonly time?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface EpisodeReviewWaveformLane {
|
|
17
|
-
readonly id: string;
|
|
18
|
-
readonly label: string;
|
|
19
|
-
readonly startTime: number;
|
|
20
|
-
readonly duration: number;
|
|
21
|
-
readonly peakCount: number;
|
|
22
|
-
readonly pathPointCount: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface EpisodeReviewManualVisemeEdit {
|
|
26
|
-
readonly id: string;
|
|
27
|
-
readonly characterId: string;
|
|
28
|
-
readonly visemeId: string;
|
|
29
|
-
readonly startTime: number;
|
|
30
|
-
readonly endTime: number;
|
|
31
|
-
readonly reason?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface EpisodeReviewPanelState {
|
|
35
|
-
readonly packageId: string;
|
|
36
|
-
readonly packageHash?: string;
|
|
37
|
-
readonly status: EpisodeReviewStatus;
|
|
38
|
-
readonly reviewer?: string;
|
|
39
|
-
readonly approvedAt?: string;
|
|
40
|
-
readonly notes: readonly EpisodeReviewNote[];
|
|
41
|
-
readonly rejectedFrames: readonly EpisodeReviewRejectedFrame[];
|
|
42
|
-
readonly waveformLanes?: readonly EpisodeReviewWaveformLane[];
|
|
43
|
-
readonly manualVisemeEdits?: readonly EpisodeReviewManualVisemeEdit[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface EpisodeReviewPanelSnapshot extends EpisodeReviewPanelState {
|
|
47
|
-
readonly kind: "episode-review-panel";
|
|
48
|
-
readonly approvalRecorded: boolean;
|
|
49
|
-
readonly rejectedFrameCount: number;
|
|
50
|
-
readonly noteCount: number;
|
|
51
|
-
readonly waveformLaneCount: number;
|
|
52
|
-
readonly manualVisemeEditCount: number;
|
|
53
|
-
readonly reviewUiEvidence: {
|
|
54
|
-
readonly waveformReview: boolean;
|
|
55
|
-
readonly manualVisemeEdits: boolean;
|
|
56
|
-
readonly visualFrameReview: boolean;
|
|
57
|
-
readonly reviewerNotes: boolean;
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export class EpisodeReviewPanel {
|
|
62
|
-
private state: EpisodeReviewPanelState;
|
|
63
|
-
|
|
64
|
-
constructor(state: EpisodeReviewPanelState) {
|
|
65
|
-
this.state = sanitizeReviewState(state);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
addNote(note: EpisodeReviewNote): EpisodeReviewPanelSnapshot {
|
|
69
|
-
this.state = sanitizeReviewState({
|
|
70
|
-
...this.state,
|
|
71
|
-
notes: [...this.state.notes, sanitizeReviewNote(note)]
|
|
72
|
-
});
|
|
73
|
-
return this.snapshot();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
rejectFrame(frame: EpisodeReviewRejectedFrame): EpisodeReviewPanelSnapshot {
|
|
77
|
-
this.state = sanitizeReviewState({
|
|
78
|
-
...this.state,
|
|
79
|
-
status: "rejected",
|
|
80
|
-
rejectedFrames: [...this.state.rejectedFrames, sanitizeRejectedFrame(frame)]
|
|
81
|
-
});
|
|
82
|
-
return this.snapshot();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
setWaveformLanes(waveformLanes: readonly EpisodeReviewWaveformLane[]): EpisodeReviewPanelSnapshot {
|
|
86
|
-
this.state = sanitizeReviewState({
|
|
87
|
-
...this.state,
|
|
88
|
-
waveformLanes: waveformLanes.map(sanitizeWaveformLane)
|
|
89
|
-
});
|
|
90
|
-
return this.snapshot();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
applyManualVisemeEdit(edit: EpisodeReviewManualVisemeEdit): EpisodeReviewPanelSnapshot {
|
|
94
|
-
const sanitized = sanitizeManualVisemeEdit(edit);
|
|
95
|
-
this.state = sanitizeReviewState({
|
|
96
|
-
...this.state,
|
|
97
|
-
manualVisemeEdits: [
|
|
98
|
-
...((this.state.manualVisemeEdits ?? []).filter((candidate) => candidate.id !== sanitized.id)),
|
|
99
|
-
sanitized
|
|
100
|
-
]
|
|
101
|
-
});
|
|
102
|
-
return this.snapshot();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
approve(reviewer: string, approvedAt = new Date(0).toISOString()): EpisodeReviewPanelSnapshot {
|
|
106
|
-
const name = nonEmpty(reviewer, "Episode review reviewer");
|
|
107
|
-
if (this.state.rejectedFrames.length > 0) {
|
|
108
|
-
throw new Error("Episode review cannot be approved while rejected frames are present.");
|
|
109
|
-
}
|
|
110
|
-
this.state = sanitizeReviewState({
|
|
111
|
-
...this.state,
|
|
112
|
-
status: "approved",
|
|
113
|
-
reviewer: name,
|
|
114
|
-
approvedAt
|
|
115
|
-
});
|
|
116
|
-
return this.snapshot();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
markNeedsReview(): EpisodeReviewPanelSnapshot {
|
|
120
|
-
this.state = sanitizeReviewState({
|
|
121
|
-
...this.state,
|
|
122
|
-
status: "needs-review",
|
|
123
|
-
reviewer: undefined,
|
|
124
|
-
approvedAt: undefined
|
|
125
|
-
});
|
|
126
|
-
return this.snapshot();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
serialize(): EpisodeReviewPanelState {
|
|
130
|
-
return sanitizeReviewState(this.state);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
snapshot(): EpisodeReviewPanelSnapshot {
|
|
134
|
-
const waveformLanes = this.state.waveformLanes ?? [];
|
|
135
|
-
const manualVisemeEdits = this.state.manualVisemeEdits ?? [];
|
|
136
|
-
return {
|
|
137
|
-
kind: "episode-review-panel",
|
|
138
|
-
...this.serialize(),
|
|
139
|
-
approvalRecorded: this.state.status === "approved" && Boolean(this.state.reviewer),
|
|
140
|
-
rejectedFrameCount: this.state.rejectedFrames.length,
|
|
141
|
-
noteCount: this.state.notes.length,
|
|
142
|
-
waveformLaneCount: waveformLanes.length,
|
|
143
|
-
manualVisemeEditCount: manualVisemeEdits.length,
|
|
144
|
-
reviewUiEvidence: {
|
|
145
|
-
waveformReview: waveformLanes.length > 0,
|
|
146
|
-
manualVisemeEdits: manualVisemeEdits.length > 0,
|
|
147
|
-
visualFrameReview: this.state.rejectedFrames.length > 0,
|
|
148
|
-
reviewerNotes: this.state.notes.length > 0
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function createEpisodeReviewPanel(state: EpisodeReviewPanelState): EpisodeReviewPanel {
|
|
155
|
-
return new EpisodeReviewPanel(state);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function sanitizeReviewState(state: EpisodeReviewPanelState): EpisodeReviewPanelState {
|
|
159
|
-
return {
|
|
160
|
-
packageId: nonEmpty(state.packageId, "Episode review packageId"),
|
|
161
|
-
packageHash: state.packageHash,
|
|
162
|
-
status: state.status,
|
|
163
|
-
reviewer: state.reviewer,
|
|
164
|
-
approvedAt: state.approvedAt,
|
|
165
|
-
notes: state.notes.map(sanitizeReviewNote),
|
|
166
|
-
rejectedFrames: state.rejectedFrames.map(sanitizeRejectedFrame),
|
|
167
|
-
waveformLanes: (state.waveformLanes ?? []).map(sanitizeWaveformLane),
|
|
168
|
-
manualVisemeEdits: (state.manualVisemeEdits ?? []).map(sanitizeManualVisemeEdit)
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function sanitizeReviewNote(note: EpisodeReviewNote): EpisodeReviewNote {
|
|
173
|
-
return {
|
|
174
|
-
id: nonEmpty(note.id, "Episode review note id"),
|
|
175
|
-
author: nonEmpty(note.author, "Episode review note author"),
|
|
176
|
-
text: nonEmpty(note.text, "Episode review note text"),
|
|
177
|
-
time: note.time
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function sanitizeRejectedFrame(frame: EpisodeReviewRejectedFrame): EpisodeReviewRejectedFrame {
|
|
182
|
-
if (!Number.isFinite(frame.time) || frame.time < 0) {
|
|
183
|
-
throw new Error("Episode review rejected frame time must be a non-negative finite number.");
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
id: nonEmpty(frame.id, "Episode review rejected frame id"),
|
|
187
|
-
time: frame.time,
|
|
188
|
-
reason: nonEmpty(frame.reason, "Episode review rejected frame reason")
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function sanitizeWaveformLane(lane: EpisodeReviewWaveformLane): EpisodeReviewWaveformLane {
|
|
193
|
-
if (!Number.isFinite(lane.startTime) || lane.startTime < 0) {
|
|
194
|
-
throw new Error("Episode review waveform lane startTime must be a non-negative finite number.");
|
|
195
|
-
}
|
|
196
|
-
if (!Number.isFinite(lane.duration) || lane.duration < 0) {
|
|
197
|
-
throw new Error("Episode review waveform lane duration must be a non-negative finite number.");
|
|
198
|
-
}
|
|
199
|
-
if (!Number.isInteger(lane.peakCount) || lane.peakCount < 0) {
|
|
200
|
-
throw new Error("Episode review waveform lane peakCount must be a non-negative integer.");
|
|
201
|
-
}
|
|
202
|
-
if (!Number.isInteger(lane.pathPointCount) || lane.pathPointCount < 0) {
|
|
203
|
-
throw new Error("Episode review waveform lane pathPointCount must be a non-negative integer.");
|
|
204
|
-
}
|
|
205
|
-
return {
|
|
206
|
-
id: nonEmpty(lane.id, "Episode review waveform lane id"),
|
|
207
|
-
label: nonEmpty(lane.label, "Episode review waveform lane label"),
|
|
208
|
-
startTime: lane.startTime,
|
|
209
|
-
duration: lane.duration,
|
|
210
|
-
peakCount: lane.peakCount,
|
|
211
|
-
pathPointCount: lane.pathPointCount
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function sanitizeManualVisemeEdit(edit: EpisodeReviewManualVisemeEdit): EpisodeReviewManualVisemeEdit {
|
|
216
|
-
if (!Number.isFinite(edit.startTime) || edit.startTime < 0) {
|
|
217
|
-
throw new Error("Episode review manual viseme startTime must be a non-negative finite number.");
|
|
218
|
-
}
|
|
219
|
-
if (!Number.isFinite(edit.endTime) || edit.endTime <= edit.startTime) {
|
|
220
|
-
throw new Error("Episode review manual viseme endTime must be greater than startTime.");
|
|
221
|
-
}
|
|
222
|
-
return {
|
|
223
|
-
id: nonEmpty(edit.id, "Episode review manual viseme id"),
|
|
224
|
-
characterId: nonEmpty(edit.characterId, "Episode review manual viseme characterId"),
|
|
225
|
-
visemeId: nonEmpty(edit.visemeId, "Episode review manual viseme visemeId"),
|
|
226
|
-
startTime: edit.startTime,
|
|
227
|
-
endTime: edit.endTime,
|
|
228
|
-
reason: edit.reason
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function nonEmpty(value: string, label: string): string {
|
|
233
|
-
const trimmed = value.trim();
|
|
234
|
-
if (!trimmed) throw new Error(`${label} must be a non-empty string.`);
|
|
235
|
-
return trimmed;
|
|
236
|
-
}
|