@aura3d/editor-runtime 1.0.6 → 1.1.0

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.
Files changed (82) hide show
  1. package/dist/AssetDropZone.d.ts +52 -0
  2. package/dist/AssetDropZone.d.ts.map +1 -0
  3. package/dist/AssetDropZone.js +115 -0
  4. package/dist/AssetDropZone.js.map +1 -0
  5. package/dist/CameraPathEditor.d.ts +48 -0
  6. package/dist/CameraPathEditor.d.ts.map +1 -0
  7. package/dist/CameraPathEditor.js +181 -0
  8. package/dist/CameraPathEditor.js.map +1 -0
  9. package/dist/CartoonSceneEditor.d.ts +111 -0
  10. package/dist/CartoonSceneEditor.d.ts.map +1 -0
  11. package/dist/CartoonSceneEditor.js +285 -0
  12. package/dist/CartoonSceneEditor.js.map +1 -0
  13. package/dist/CurveEditor.d.ts +38 -0
  14. package/dist/CurveEditor.d.ts.map +1 -0
  15. package/dist/CurveEditor.js +112 -0
  16. package/dist/CurveEditor.js.map +1 -0
  17. package/dist/EpisodeReviewPanel.d.ts +67 -0
  18. package/dist/EpisodeReviewPanel.d.ts.map +1 -0
  19. package/dist/EpisodeReviewPanel.js +162 -0
  20. package/dist/EpisodeReviewPanel.js.map +1 -0
  21. package/dist/KeyframeEditor.d.ts +49 -0
  22. package/dist/KeyframeEditor.d.ts.map +1 -0
  23. package/dist/KeyframeEditor.js +161 -0
  24. package/dist/KeyframeEditor.js.map +1 -0
  25. package/dist/MultiUserReviewWorkflow.d.ts +77 -0
  26. package/dist/MultiUserReviewWorkflow.d.ts.map +1 -0
  27. package/dist/MultiUserReviewWorkflow.js +162 -0
  28. package/dist/MultiUserReviewWorkflow.js.map +1 -0
  29. package/dist/NonlinearAnimationEditor.d.ts +81 -0
  30. package/dist/NonlinearAnimationEditor.d.ts.map +1 -0
  31. package/dist/NonlinearAnimationEditor.js +180 -0
  32. package/dist/NonlinearAnimationEditor.js.map +1 -0
  33. package/dist/PropertyPanel.d.ts +21 -0
  34. package/dist/PropertyPanel.d.ts.map +1 -0
  35. package/dist/PropertyPanel.js +72 -0
  36. package/dist/PropertyPanel.js.map +1 -0
  37. package/dist/RenderQueuePanel.d.ts +38 -0
  38. package/dist/RenderQueuePanel.d.ts.map +1 -0
  39. package/dist/RenderQueuePanel.js +72 -0
  40. package/dist/RenderQueuePanel.js.map +1 -0
  41. package/dist/SceneOutliner.d.ts +19 -0
  42. package/dist/SceneOutliner.d.ts.map +1 -0
  43. package/dist/SceneOutliner.js +64 -0
  44. package/dist/SceneOutliner.js.map +1 -0
  45. package/dist/TimelineEditorController.d.ts +102 -0
  46. package/dist/TimelineEditorController.d.ts.map +1 -0
  47. package/dist/TimelineEditorController.js +449 -0
  48. package/dist/TimelineEditorController.js.map +1 -0
  49. package/dist/TimelineTrackTypes.d.ts +25 -0
  50. package/dist/TimelineTrackTypes.d.ts.map +1 -0
  51. package/dist/TimelineTrackTypes.js +48 -0
  52. package/dist/TimelineTrackTypes.js.map +1 -0
  53. package/dist/TimelineUI.d.ts +27 -0
  54. package/dist/TimelineUI.d.ts.map +1 -0
  55. package/dist/TimelineUI.js +123 -0
  56. package/dist/TimelineUI.js.map +1 -0
  57. package/dist/VisualReviewDashboard.d.ts +43 -0
  58. package/dist/VisualReviewDashboard.d.ts.map +1 -0
  59. package/dist/VisualReviewDashboard.js +47 -0
  60. package/dist/VisualReviewDashboard.js.map +1 -0
  61. package/dist/index.d.ts +30 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +15 -0
  64. package/dist/index.js.map +1 -1
  65. package/package.json +3 -3
  66. package/src/AssetDropZone.ts +144 -0
  67. package/src/CameraPathEditor.ts +223 -0
  68. package/src/CartoonSceneEditor.ts +368 -0
  69. package/src/CurveEditor.ts +152 -0
  70. package/src/EpisodeReviewPanel.ts +236 -0
  71. package/src/KeyframeEditor.ts +214 -0
  72. package/src/MultiUserReviewWorkflow.ts +229 -0
  73. package/src/NonlinearAnimationEditor.ts +262 -0
  74. package/src/PropertyPanel.ts +82 -0
  75. package/src/RenderQueuePanel.ts +106 -0
  76. package/src/SceneOutliner.ts +78 -0
  77. package/src/TimelineEditorController.ts +525 -0
  78. package/src/TimelineTrackTypes.ts +89 -0
  79. package/src/TimelineUI.ts +153 -0
  80. package/src/VisualReviewDashboard.ts +87 -0
  81. package/src/index.ts +53 -0
  82. package/tests/editor-runtime.test.ts +490 -0
@@ -0,0 +1,162 @@
1
+ export class EpisodeReviewPanel {
2
+ state;
3
+ constructor(state) {
4
+ this.state = sanitizeReviewState(state);
5
+ }
6
+ addNote(note) {
7
+ this.state = sanitizeReviewState({
8
+ ...this.state,
9
+ notes: [...this.state.notes, sanitizeReviewNote(note)]
10
+ });
11
+ return this.snapshot();
12
+ }
13
+ rejectFrame(frame) {
14
+ this.state = sanitizeReviewState({
15
+ ...this.state,
16
+ status: "rejected",
17
+ rejectedFrames: [...this.state.rejectedFrames, sanitizeRejectedFrame(frame)]
18
+ });
19
+ return this.snapshot();
20
+ }
21
+ setWaveformLanes(waveformLanes) {
22
+ this.state = sanitizeReviewState({
23
+ ...this.state,
24
+ waveformLanes: waveformLanes.map(sanitizeWaveformLane)
25
+ });
26
+ return this.snapshot();
27
+ }
28
+ applyManualVisemeEdit(edit) {
29
+ const sanitized = sanitizeManualVisemeEdit(edit);
30
+ this.state = sanitizeReviewState({
31
+ ...this.state,
32
+ manualVisemeEdits: [
33
+ ...((this.state.manualVisemeEdits ?? []).filter((candidate) => candidate.id !== sanitized.id)),
34
+ sanitized
35
+ ]
36
+ });
37
+ return this.snapshot();
38
+ }
39
+ approve(reviewer, approvedAt = new Date(0).toISOString()) {
40
+ const name = nonEmpty(reviewer, "Episode review reviewer");
41
+ if (this.state.rejectedFrames.length > 0) {
42
+ throw new Error("Episode review cannot be approved while rejected frames are present.");
43
+ }
44
+ this.state = sanitizeReviewState({
45
+ ...this.state,
46
+ status: "approved",
47
+ reviewer: name,
48
+ approvedAt
49
+ });
50
+ return this.snapshot();
51
+ }
52
+ markNeedsReview() {
53
+ this.state = sanitizeReviewState({
54
+ ...this.state,
55
+ status: "needs-review",
56
+ reviewer: undefined,
57
+ approvedAt: undefined
58
+ });
59
+ return this.snapshot();
60
+ }
61
+ serialize() {
62
+ return sanitizeReviewState(this.state);
63
+ }
64
+ snapshot() {
65
+ const waveformLanes = this.state.waveformLanes ?? [];
66
+ const manualVisemeEdits = this.state.manualVisemeEdits ?? [];
67
+ return {
68
+ kind: "episode-review-panel",
69
+ ...this.serialize(),
70
+ approvalRecorded: this.state.status === "approved" && Boolean(this.state.reviewer),
71
+ rejectedFrameCount: this.state.rejectedFrames.length,
72
+ noteCount: this.state.notes.length,
73
+ waveformLaneCount: waveformLanes.length,
74
+ manualVisemeEditCount: manualVisemeEdits.length,
75
+ reviewUiEvidence: {
76
+ waveformReview: waveformLanes.length > 0,
77
+ manualVisemeEdits: manualVisemeEdits.length > 0,
78
+ visualFrameReview: this.state.rejectedFrames.length > 0,
79
+ reviewerNotes: this.state.notes.length > 0
80
+ }
81
+ };
82
+ }
83
+ }
84
+ export function createEpisodeReviewPanel(state) {
85
+ return new EpisodeReviewPanel(state);
86
+ }
87
+ function sanitizeReviewState(state) {
88
+ return {
89
+ packageId: nonEmpty(state.packageId, "Episode review packageId"),
90
+ packageHash: state.packageHash,
91
+ status: state.status,
92
+ reviewer: state.reviewer,
93
+ approvedAt: state.approvedAt,
94
+ notes: state.notes.map(sanitizeReviewNote),
95
+ rejectedFrames: state.rejectedFrames.map(sanitizeRejectedFrame),
96
+ waveformLanes: (state.waveformLanes ?? []).map(sanitizeWaveformLane),
97
+ manualVisemeEdits: (state.manualVisemeEdits ?? []).map(sanitizeManualVisemeEdit)
98
+ };
99
+ }
100
+ function sanitizeReviewNote(note) {
101
+ return {
102
+ id: nonEmpty(note.id, "Episode review note id"),
103
+ author: nonEmpty(note.author, "Episode review note author"),
104
+ text: nonEmpty(note.text, "Episode review note text"),
105
+ time: note.time
106
+ };
107
+ }
108
+ function sanitizeRejectedFrame(frame) {
109
+ if (!Number.isFinite(frame.time) || frame.time < 0) {
110
+ throw new Error("Episode review rejected frame time must be a non-negative finite number.");
111
+ }
112
+ return {
113
+ id: nonEmpty(frame.id, "Episode review rejected frame id"),
114
+ time: frame.time,
115
+ reason: nonEmpty(frame.reason, "Episode review rejected frame reason")
116
+ };
117
+ }
118
+ function sanitizeWaveformLane(lane) {
119
+ if (!Number.isFinite(lane.startTime) || lane.startTime < 0) {
120
+ throw new Error("Episode review waveform lane startTime must be a non-negative finite number.");
121
+ }
122
+ if (!Number.isFinite(lane.duration) || lane.duration < 0) {
123
+ throw new Error("Episode review waveform lane duration must be a non-negative finite number.");
124
+ }
125
+ if (!Number.isInteger(lane.peakCount) || lane.peakCount < 0) {
126
+ throw new Error("Episode review waveform lane peakCount must be a non-negative integer.");
127
+ }
128
+ if (!Number.isInteger(lane.pathPointCount) || lane.pathPointCount < 0) {
129
+ throw new Error("Episode review waveform lane pathPointCount must be a non-negative integer.");
130
+ }
131
+ return {
132
+ id: nonEmpty(lane.id, "Episode review waveform lane id"),
133
+ label: nonEmpty(lane.label, "Episode review waveform lane label"),
134
+ startTime: lane.startTime,
135
+ duration: lane.duration,
136
+ peakCount: lane.peakCount,
137
+ pathPointCount: lane.pathPointCount
138
+ };
139
+ }
140
+ function sanitizeManualVisemeEdit(edit) {
141
+ if (!Number.isFinite(edit.startTime) || edit.startTime < 0) {
142
+ throw new Error("Episode review manual viseme startTime must be a non-negative finite number.");
143
+ }
144
+ if (!Number.isFinite(edit.endTime) || edit.endTime <= edit.startTime) {
145
+ throw new Error("Episode review manual viseme endTime must be greater than startTime.");
146
+ }
147
+ return {
148
+ id: nonEmpty(edit.id, "Episode review manual viseme id"),
149
+ characterId: nonEmpty(edit.characterId, "Episode review manual viseme characterId"),
150
+ visemeId: nonEmpty(edit.visemeId, "Episode review manual viseme visemeId"),
151
+ startTime: edit.startTime,
152
+ endTime: edit.endTime,
153
+ reason: edit.reason
154
+ };
155
+ }
156
+ function nonEmpty(value, label) {
157
+ const trimmed = value.trim();
158
+ if (!trimmed)
159
+ throw new Error(`${label} must be a non-empty string.`);
160
+ return trimmed;
161
+ }
162
+ //# sourceMappingURL=EpisodeReviewPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EpisodeReviewPanel.js","sourceRoot":"","sources":["../../../../packages/editor-runtime/src/EpisodeReviewPanel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { CommandHistory } from "./CommandHistory";
2
+ import type { TimelineClip } from "./TimelineModel";
3
+ export type TimelineKeyframeValue = number | string | boolean;
4
+ export type TimelineKeyframeInterpolation = "hold" | "linear" | "bezier";
5
+ export interface TimelineBezierHandle {
6
+ readonly time: number;
7
+ readonly value: number;
8
+ }
9
+ export interface TimelineKeyframe {
10
+ readonly id: string;
11
+ readonly propertyPath: string;
12
+ readonly time: number;
13
+ readonly value: TimelineKeyframeValue;
14
+ readonly interpolation: TimelineKeyframeInterpolation;
15
+ readonly inHandle?: TimelineBezierHandle;
16
+ readonly outHandle?: TimelineBezierHandle;
17
+ }
18
+ export interface CreateTimelineKeyframeOptions {
19
+ readonly id?: string;
20
+ readonly propertyPath: string;
21
+ readonly time: number;
22
+ readonly value: TimelineKeyframeValue;
23
+ readonly interpolation?: TimelineKeyframeInterpolation;
24
+ readonly inHandle?: TimelineBezierHandle;
25
+ readonly outHandle?: TimelineBezierHandle;
26
+ }
27
+ export interface PasteTimelineKeyframesOptions {
28
+ readonly timeOffset?: number;
29
+ readonly idPrefix?: string;
30
+ readonly propertyPath?: string;
31
+ }
32
+ export declare function readTimelineKeyframes(clip: TimelineClip): readonly TimelineKeyframe[];
33
+ export declare function encodeTimelineKeyframes(keyframes: readonly TimelineKeyframe[]): string;
34
+ export declare function timelineKeyframesByProperty(clip: TimelineClip, propertyPath: string): readonly TimelineKeyframe[];
35
+ export declare class KeyframeEditor {
36
+ private readonly history;
37
+ constructor(history?: CommandHistory);
38
+ get commandHistory(): CommandHistory;
39
+ keyframes(clip: TimelineClip, propertyPath?: string): readonly TimelineKeyframe[];
40
+ addKeyframe(clip: TimelineClip, options: CreateTimelineKeyframeOptions): Promise<TimelineKeyframe>;
41
+ deleteKeyframes(clip: TimelineClip, ids: readonly string[]): Promise<void>;
42
+ moveKeyframes(clip: TimelineClip, ids: readonly string[], deltaTime: number): Promise<readonly TimelineKeyframe[]>;
43
+ scaleKeyframes(clip: TimelineClip, ids: readonly string[], pivotTime: number, scale: number): Promise<readonly TimelineKeyframe[]>;
44
+ copyKeyframes(clip: TimelineClip, ids: readonly string[]): readonly TimelineKeyframe[];
45
+ pasteKeyframes(clip: TimelineClip, keyframes: readonly TimelineKeyframe[], options?: PasteTimelineKeyframesOptions): Promise<readonly TimelineKeyframe[]>;
46
+ replaceKeyframes(clip: TimelineClip, keyframes: readonly TimelineKeyframe[], label?: string): Promise<void>;
47
+ private applyKeyframes;
48
+ }
49
+ //# sourceMappingURL=KeyframeEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyframeEditor.d.ts","sourceRoot":"","sources":["../../../../packages/editor-runtime/src/KeyframeEditor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,161 @@
1
+ import { CommandHistory } from "./CommandHistory.js";
2
+ const KEYFRAME_PROPERTY = "auraKeyframes";
3
+ export function readTimelineKeyframes(clip) {
4
+ const encoded = clip.properties[KEYFRAME_PROPERTY];
5
+ if (typeof encoded !== "string" || encoded.trim().length === 0)
6
+ return [];
7
+ const parsed = JSON.parse(encoded);
8
+ if (!Array.isArray(parsed)) {
9
+ throw new Error(`Timeline clip keyframes must decode to an array: ${clip.id}`);
10
+ }
11
+ return parsed.map(normalizeKeyframe).sort(compareKeyframes);
12
+ }
13
+ export function encodeTimelineKeyframes(keyframes) {
14
+ return JSON.stringify([...keyframes].map(normalizeKeyframe).sort(compareKeyframes));
15
+ }
16
+ export function timelineKeyframesByProperty(clip, propertyPath) {
17
+ return readTimelineKeyframes(clip).filter((keyframe) => keyframe.propertyPath === propertyPath);
18
+ }
19
+ export class KeyframeEditor {
20
+ history;
21
+ constructor(history = new CommandHistory()) {
22
+ this.history = history;
23
+ }
24
+ get commandHistory() {
25
+ return this.history;
26
+ }
27
+ keyframes(clip, propertyPath) {
28
+ const keyframes = readTimelineKeyframes(clip);
29
+ return propertyPath ? keyframes.filter((keyframe) => keyframe.propertyPath === propertyPath) : keyframes;
30
+ }
31
+ async addKeyframe(clip, options) {
32
+ const keyframe = normalizeKeyframe({
33
+ id: options.id ?? stableKeyframeId(options.propertyPath, options.time),
34
+ propertyPath: options.propertyPath,
35
+ time: options.time,
36
+ value: options.value,
37
+ interpolation: options.interpolation ?? "linear",
38
+ inHandle: options.inHandle,
39
+ outHandle: options.outHandle
40
+ });
41
+ const next = [...readTimelineKeyframes(clip).filter((candidate) => candidate.id !== keyframe.id), keyframe];
42
+ await this.applyKeyframes(clip, next, `Add keyframe ${keyframe.id}`);
43
+ return keyframe;
44
+ }
45
+ async deleteKeyframes(clip, ids) {
46
+ const remove = new Set(ids);
47
+ await this.applyKeyframes(clip, readTimelineKeyframes(clip).filter((keyframe) => !remove.has(keyframe.id)), `Delete ${ids.length} keyframes`);
48
+ }
49
+ async moveKeyframes(clip, ids, deltaTime) {
50
+ assertFinite(deltaTime, "Keyframe deltaTime");
51
+ const move = new Set(ids);
52
+ const next = readTimelineKeyframes(clip).map((keyframe) => move.has(keyframe.id)
53
+ ? normalizeKeyframe({ ...keyframe, time: Math.max(0, keyframe.time + deltaTime) })
54
+ : keyframe);
55
+ await this.applyKeyframes(clip, next, `Move ${ids.length} keyframes`);
56
+ return next.filter((keyframe) => move.has(keyframe.id));
57
+ }
58
+ async scaleKeyframes(clip, ids, pivotTime, scale) {
59
+ assertFinite(pivotTime, "Keyframe pivotTime");
60
+ assertFinite(scale, "Keyframe scale");
61
+ if (scale <= 0)
62
+ throw new RangeError("Keyframe scale must be positive.");
63
+ const scaleIds = new Set(ids);
64
+ const next = readTimelineKeyframes(clip).map((keyframe) => scaleIds.has(keyframe.id)
65
+ ? normalizeKeyframe({ ...keyframe, time: Math.max(0, pivotTime + (keyframe.time - pivotTime) * scale) })
66
+ : keyframe);
67
+ await this.applyKeyframes(clip, next, `Scale ${ids.length} keyframes`);
68
+ return next.filter((keyframe) => scaleIds.has(keyframe.id));
69
+ }
70
+ copyKeyframes(clip, ids) {
71
+ const copy = new Set(ids);
72
+ return readTimelineKeyframes(clip).filter((keyframe) => copy.has(keyframe.id)).map((keyframe) => ({ ...keyframe }));
73
+ }
74
+ async pasteKeyframes(clip, keyframes, options = {}) {
75
+ const timeOffset = options.timeOffset ?? 0;
76
+ assertFinite(timeOffset, "Paste keyframe timeOffset");
77
+ const prefix = options.idPrefix ?? "pasted";
78
+ const pasted = keyframes.map((keyframe, index) => normalizeKeyframe({
79
+ ...keyframe,
80
+ id: `${prefix}-${keyframe.id}-${index}`,
81
+ propertyPath: options.propertyPath ?? keyframe.propertyPath,
82
+ time: Math.max(0, keyframe.time + timeOffset)
83
+ }));
84
+ await this.applyKeyframes(clip, [...readTimelineKeyframes(clip), ...pasted], `Paste ${pasted.length} keyframes`);
85
+ return pasted;
86
+ }
87
+ async replaceKeyframes(clip, keyframes, label = "Replace keyframes") {
88
+ await this.applyKeyframes(clip, keyframes, label);
89
+ }
90
+ async applyKeyframes(clip, keyframes, name) {
91
+ await this.history.execute(new SetClipKeyframesCommand(clip, keyframes, name));
92
+ }
93
+ }
94
+ class SetClipKeyframesCommand {
95
+ clip;
96
+ name;
97
+ before;
98
+ after;
99
+ constructor(clip, keyframes, name) {
100
+ this.clip = clip;
101
+ this.name = name;
102
+ this.before = readTimelineKeyframes(clip);
103
+ this.after = [...keyframes].map(normalizeKeyframe).sort(compareKeyframes);
104
+ }
105
+ execute() {
106
+ this.write(this.after);
107
+ }
108
+ undo() {
109
+ this.write(this.before);
110
+ }
111
+ write(keyframes) {
112
+ if (keyframes.length === 0) {
113
+ delete this.clip.properties[KEYFRAME_PROPERTY];
114
+ return;
115
+ }
116
+ this.clip.properties[KEYFRAME_PROPERTY] = encodeTimelineKeyframes(keyframes);
117
+ }
118
+ }
119
+ function normalizeKeyframe(keyframe) {
120
+ if (!keyframe.id.trim())
121
+ throw new Error("Timeline keyframe id is required.");
122
+ if (!keyframe.propertyPath.trim())
123
+ throw new Error("Timeline keyframe propertyPath is required.");
124
+ assertFinite(keyframe.time, "Timeline keyframe time");
125
+ if (keyframe.time < 0)
126
+ throw new RangeError("Timeline keyframe time must be non-negative.");
127
+ validateKeyframeValue(keyframe.value);
128
+ validateHandle(keyframe.inHandle, "inHandle");
129
+ validateHandle(keyframe.outHandle, "outHandle");
130
+ return {
131
+ id: keyframe.id,
132
+ propertyPath: keyframe.propertyPath,
133
+ time: Number(keyframe.time.toFixed(4)),
134
+ value: keyframe.value,
135
+ interpolation: keyframe.interpolation,
136
+ inHandle: keyframe.inHandle,
137
+ outHandle: keyframe.outHandle
138
+ };
139
+ }
140
+ function validateKeyframeValue(value) {
141
+ if (typeof value === "number" && !Number.isFinite(value)) {
142
+ throw new Error("Timeline keyframe numeric values must be finite.");
143
+ }
144
+ }
145
+ function validateHandle(handle, label) {
146
+ if (!handle)
147
+ return;
148
+ assertFinite(handle.time, `Timeline keyframe ${label}.time`);
149
+ assertFinite(handle.value, `Timeline keyframe ${label}.value`);
150
+ }
151
+ function assertFinite(value, label) {
152
+ if (!Number.isFinite(value))
153
+ throw new RangeError(`${label} must be finite.`);
154
+ }
155
+ function compareKeyframes(left, right) {
156
+ return left.time - right.time || left.propertyPath.localeCompare(right.propertyPath) || left.id.localeCompare(right.id);
157
+ }
158
+ function stableKeyframeId(propertyPath, time) {
159
+ return `key-${propertyPath.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "value"}-${Number(time.toFixed(4)).toString().replace(".", "-")}`;
160
+ }
161
+ //# sourceMappingURL=KeyframeEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyframeEditor.js","sourceRoot":"","sources":["../../../../packages/editor-runtime/src/KeyframeEditor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ export type ReviewParticipantRole = "owner" | "reviewer" | "animator" | "audio" | "viewer";
2
+ export type ReviewDecisionStatus = "pending" | "approved" | "changes-requested";
3
+ export type ReviewWorkflowStatus = "draft" | "needs-review" | "changes-requested" | "approved";
4
+ export interface ReviewParticipant {
5
+ readonly id: string;
6
+ readonly name: string;
7
+ readonly role: ReviewParticipantRole;
8
+ readonly active?: boolean | undefined;
9
+ }
10
+ export interface ReviewThread {
11
+ readonly id: string;
12
+ readonly authorId: string;
13
+ readonly text: string;
14
+ readonly time?: number | undefined;
15
+ readonly frame?: number | undefined;
16
+ readonly assignedTo?: string | undefined;
17
+ readonly resolved: boolean;
18
+ }
19
+ export interface ReviewDecision {
20
+ readonly reviewerId: string;
21
+ readonly status: ReviewDecisionStatus;
22
+ readonly at: string;
23
+ readonly note?: string | undefined;
24
+ }
25
+ export interface MultiUserReviewWorkflowState {
26
+ readonly packageId: string;
27
+ readonly status?: ReviewWorkflowStatus | undefined;
28
+ readonly requiredReviewerCount?: number | undefined;
29
+ readonly participants: readonly ReviewParticipant[];
30
+ readonly threads: readonly ReviewThread[];
31
+ readonly decisions: readonly ReviewDecision[];
32
+ }
33
+ export interface MultiUserReviewWorkflowSnapshot {
34
+ readonly kind: "multi-user-review-workflow";
35
+ readonly packageId: string;
36
+ readonly status: ReviewWorkflowStatus;
37
+ readonly participantCount: number;
38
+ readonly activeParticipantCount: number;
39
+ readonly reviewerCount: number;
40
+ readonly requiredReviewerCount: number;
41
+ readonly approvedReviewerCount: number;
42
+ readonly changeRequestCount: number;
43
+ readonly threadCount: number;
44
+ readonly unresolvedThreadCount: number;
45
+ readonly assignedThreadCount: number;
46
+ readonly canPublish: boolean;
47
+ readonly participants: readonly ReviewParticipant[];
48
+ readonly threads: readonly ReviewThread[];
49
+ readonly decisions: readonly ReviewDecision[];
50
+ readonly evidence: {
51
+ readonly multiUserReview: boolean;
52
+ readonly assignmentWorkflow: boolean;
53
+ readonly reviewerQuorum: boolean;
54
+ readonly publishBlockedByOpenThreads: boolean;
55
+ };
56
+ }
57
+ export declare class MultiUserReviewWorkflow {
58
+ private packageId;
59
+ private status;
60
+ private requiredReviewerCount;
61
+ private participants;
62
+ private threads;
63
+ private decisions;
64
+ constructor(state: MultiUserReviewWorkflowState);
65
+ addParticipant(participant: ReviewParticipant): MultiUserReviewWorkflowSnapshot;
66
+ addThread(thread: ReviewThread): MultiUserReviewWorkflowSnapshot;
67
+ assignThread(threadId: string, participantId: string): MultiUserReviewWorkflowSnapshot;
68
+ resolveThread(threadId: string): MultiUserReviewWorkflowSnapshot;
69
+ recordDecision(decision: ReviewDecision): MultiUserReviewWorkflowSnapshot;
70
+ markNeedsReview(): MultiUserReviewWorkflowSnapshot;
71
+ serialize(): MultiUserReviewWorkflowState;
72
+ snapshot(): MultiUserReviewWorkflowSnapshot;
73
+ private requireParticipant;
74
+ private requireThread;
75
+ }
76
+ export declare function createMultiUserReviewWorkflow(state: MultiUserReviewWorkflowState): MultiUserReviewWorkflow;
77
+ //# sourceMappingURL=MultiUserReviewWorkflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiUserReviewWorkflow.d.ts","sourceRoot":"","sources":["../../../../packages/editor-runtime/src/MultiUserReviewWorkflow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,162 @@
1
+ export class MultiUserReviewWorkflow {
2
+ packageId;
3
+ status;
4
+ requiredReviewerCount;
5
+ participants = new Map();
6
+ threads = new Map();
7
+ decisions = new Map();
8
+ constructor(state) {
9
+ this.packageId = nonEmpty(state.packageId, "Review package id");
10
+ this.status = state.status ?? "needs-review";
11
+ this.requiredReviewerCount = Math.max(1, Math.floor(state.requiredReviewerCount ?? 1));
12
+ for (const participant of state.participants)
13
+ this.addParticipant(participant);
14
+ for (const thread of state.threads)
15
+ this.addThread(thread);
16
+ for (const decision of state.decisions)
17
+ this.recordDecision(decision);
18
+ }
19
+ addParticipant(participant) {
20
+ const clean = sanitizeParticipant(participant);
21
+ this.participants.set(clean.id, clean);
22
+ return this.snapshot();
23
+ }
24
+ addThread(thread) {
25
+ const clean = sanitizeThread(thread);
26
+ this.requireParticipant(clean.authorId);
27
+ if (clean.assignedTo)
28
+ this.requireParticipant(clean.assignedTo);
29
+ this.threads.set(clean.id, clean);
30
+ if (!clean.resolved && this.status === "approved")
31
+ this.status = "changes-requested";
32
+ return this.snapshot();
33
+ }
34
+ assignThread(threadId, participantId) {
35
+ this.requireParticipant(participantId);
36
+ const thread = this.requireThread(threadId);
37
+ this.threads.set(threadId, { ...thread, assignedTo: participantId });
38
+ return this.snapshot();
39
+ }
40
+ resolveThread(threadId) {
41
+ const thread = this.requireThread(threadId);
42
+ this.threads.set(threadId, { ...thread, resolved: true });
43
+ return this.snapshot();
44
+ }
45
+ recordDecision(decision) {
46
+ const clean = sanitizeDecision(decision);
47
+ const participant = this.requireParticipant(clean.reviewerId);
48
+ if (participant.role !== "reviewer" && participant.role !== "owner") {
49
+ throw new Error(`Review participant "${clean.reviewerId}" is not allowed to approve or request changes.`);
50
+ }
51
+ this.decisions.set(clean.reviewerId, clean);
52
+ if (clean.status === "changes-requested")
53
+ this.status = "changes-requested";
54
+ else if (this.snapshot().canPublish)
55
+ this.status = "approved";
56
+ return this.snapshot();
57
+ }
58
+ markNeedsReview() {
59
+ this.status = "needs-review";
60
+ return this.snapshot();
61
+ }
62
+ serialize() {
63
+ return {
64
+ packageId: this.packageId,
65
+ status: this.status,
66
+ requiredReviewerCount: this.requiredReviewerCount,
67
+ participants: [...this.participants.values()],
68
+ threads: [...this.threads.values()],
69
+ decisions: [...this.decisions.values()]
70
+ };
71
+ }
72
+ snapshot() {
73
+ const participants = [...this.participants.values()];
74
+ const threads = [...this.threads.values()];
75
+ const decisions = [...this.decisions.values()];
76
+ const reviewerCount = participants.filter((participant) => participant.role === "owner" || participant.role === "reviewer").length;
77
+ const approvedReviewerCount = decisions.filter((decision) => decision.status === "approved").length;
78
+ const changeRequestCount = decisions.filter((decision) => decision.status === "changes-requested").length;
79
+ const unresolvedThreadCount = threads.filter((thread) => !thread.resolved).length;
80
+ const canPublish = approvedReviewerCount >= this.requiredReviewerCount && changeRequestCount === 0 && unresolvedThreadCount === 0;
81
+ const status = canPublish ? "approved" : changeRequestCount > 0 ? "changes-requested" : this.status === "draft" ? "draft" : "needs-review";
82
+ return {
83
+ kind: "multi-user-review-workflow",
84
+ packageId: this.packageId,
85
+ status,
86
+ participantCount: participants.length,
87
+ activeParticipantCount: participants.filter((participant) => participant.active !== false).length,
88
+ reviewerCount,
89
+ requiredReviewerCount: this.requiredReviewerCount,
90
+ approvedReviewerCount,
91
+ changeRequestCount,
92
+ threadCount: threads.length,
93
+ unresolvedThreadCount,
94
+ assignedThreadCount: threads.filter((thread) => Boolean(thread.assignedTo)).length,
95
+ canPublish,
96
+ participants,
97
+ threads,
98
+ decisions,
99
+ evidence: {
100
+ multiUserReview: participants.length > 1,
101
+ assignmentWorkflow: threads.some((thread) => Boolean(thread.assignedTo)),
102
+ reviewerQuorum: approvedReviewerCount >= this.requiredReviewerCount,
103
+ publishBlockedByOpenThreads: unresolvedThreadCount > 0
104
+ }
105
+ };
106
+ }
107
+ requireParticipant(id) {
108
+ const participant = this.participants.get(id);
109
+ if (!participant)
110
+ throw new Error(`Unknown review participant: ${id}`);
111
+ return participant;
112
+ }
113
+ requireThread(id) {
114
+ const thread = this.threads.get(id);
115
+ if (!thread)
116
+ throw new Error(`Unknown review thread: ${id}`);
117
+ return thread;
118
+ }
119
+ }
120
+ export function createMultiUserReviewWorkflow(state) {
121
+ return new MultiUserReviewWorkflow(state);
122
+ }
123
+ function sanitizeParticipant(participant) {
124
+ return {
125
+ id: nonEmpty(participant.id, "Review participant id"),
126
+ name: nonEmpty(participant.name, "Review participant name"),
127
+ role: participant.role,
128
+ active: participant.active
129
+ };
130
+ }
131
+ function sanitizeThread(thread) {
132
+ if (thread.time !== undefined && (!Number.isFinite(thread.time) || thread.time < 0)) {
133
+ throw new Error("Review thread time must be a non-negative finite number.");
134
+ }
135
+ if (thread.frame !== undefined && (!Number.isInteger(thread.frame) || thread.frame < 0)) {
136
+ throw new Error("Review thread frame must be a non-negative integer.");
137
+ }
138
+ return {
139
+ id: nonEmpty(thread.id, "Review thread id"),
140
+ authorId: nonEmpty(thread.authorId, "Review thread author"),
141
+ text: nonEmpty(thread.text, "Review thread text"),
142
+ time: thread.time,
143
+ frame: thread.frame,
144
+ assignedTo: thread.assignedTo,
145
+ resolved: thread.resolved
146
+ };
147
+ }
148
+ function sanitizeDecision(decision) {
149
+ return {
150
+ reviewerId: nonEmpty(decision.reviewerId, "Review decision reviewer"),
151
+ status: decision.status,
152
+ at: nonEmpty(decision.at, "Review decision timestamp"),
153
+ note: decision.note
154
+ };
155
+ }
156
+ function nonEmpty(value, label) {
157
+ const trimmed = value.trim();
158
+ if (!trimmed)
159
+ throw new Error(`${label} must be a non-empty string.`);
160
+ return trimmed;
161
+ }
162
+ //# sourceMappingURL=MultiUserReviewWorkflow.js.map