@hyperframes/studio 0.8.124 → 0.8.126
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/dist/assets/{hyperframes-player-ColOZ_78.js → hyperframes-player-BbbN-wWN.js} +1 -1
- package/dist/assets/{index-BuSTrK-Z.js → index-B8kU2xXl.js} +177 -177
- package/dist/assets/{index-CVuUfURE.js → index-Cll7uTNy.js} +1 -1
- package/dist/assets/{index-jQEqFKMk.js → index-nvLF29ZH.js} +1 -1
- package/dist/index.d.ts +43 -35
- package/dist/index.html +1 -1
- package/dist/index.js +4458 -4072
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
- package/src/components/editor/DomEditSelectionChrome.test.tsx +5 -0
- package/src/components/editor/DomEditSelectionChrome.tsx +1 -0
- package/src/components/editor/MotionPathNode.tsx +56 -0
- package/src/components/editor/MotionPathOverlay.layerPress.test.tsx +140 -48
- package/src/components/editor/MotionPathOverlay.test.tsx +45 -0
- package/src/components/editor/MotionPathOverlay.tsx +30 -61
- package/src/components/editor/canvasContextMenuZOrder.ts +1 -10
- package/src/components/editor/domEditOverlayGestures.ts +2 -0
- package/src/components/editor/domEditOverlayStartGesture.ts +6 -0
- package/src/components/editor/dragUndoPaint.test.ts +170 -2
- package/src/components/editor/gestureUndoRevert.ts +104 -0
- package/src/components/editor/manualEditsSeekReapply.ts +3 -6
- package/src/components/editor/manualOffsetDrag.ts +3 -15
- package/src/components/editor/motionPathGeometry.test.ts +48 -1
- package/src/components/editor/motionPathGeometry.ts +46 -8
- package/src/components/editor/motionPathHome.ts +75 -0
- package/src/components/editor/motionPathLayerNode.test.ts +43 -35
- package/src/components/editor/motionPathLayerNode.ts +20 -16
- package/src/components/editor/reapplyPositionEditsAfterSeek.test.ts +20 -0
- package/src/components/editor/studioMotion.ts +7 -19
- package/src/components/editor/useDomEditNudge.ts +1 -1
- package/src/components/editor/useDomEditOverlayGestures.ts +9 -5
- package/src/components/editor/useMotionPathData.test.tsx +236 -0
- package/src/components/editor/useMotionPathData.ts +23 -61
- package/src/components/sidebar/CompositionsTab.thumbnails.test.tsx +1 -0
- package/src/hooks/gestureTransaction.test.ts +60 -0
- package/src/hooks/gestureTransaction.ts +5 -2
- package/src/hooks/gsapGestureOutcome.ts +9 -2
- package/src/hooks/gsapParsedTween.ts +16 -0
- package/src/hooks/gsapResizeDropPoint.test.ts +39 -1
- package/src/hooks/gsapResizeElementSize.test.tsx +27 -0
- package/src/hooks/gsapResizeIntercept.ts +55 -68
- package/src/hooks/gsapRuntimeKeyframes.realGsap.test.ts +48 -0
- package/src/hooks/gsapRuntimeKeyframes.ts +10 -3
- package/src/hooks/gsapScriptCommitTypes.ts +2 -0
- package/src/hooks/gsapShared.ts +2 -1
- package/src/hooks/gsapShared.writeTarget.test.ts +10 -0
- package/src/hooks/intersectionObserverTestUtils.ts +16 -0
- package/src/hooks/resizeObserverTestUtils.ts +9 -2
- package/src/hooks/timelineEditingHelpers.ts +2 -9
- package/src/hooks/useGsapAwareEditing.groupPlan.test.tsx +29 -1
- package/src/hooks/useGsapAwareEditing.test.tsx +1 -0
- package/src/hooks/useGsapAwareEditing.ts +21 -25
- package/src/hooks/useGsapAwareGroupMove.ts +4 -6
- package/src/hooks/useGsapScriptCommits.test.tsx +42 -1
- package/src/hooks/useGsapScriptCommits.ts +12 -3
- package/src/hooks/useThumbnailStripSize.test.tsx +236 -1
- package/src/hooks/useThumbnailStripSize.ts +206 -17
- package/src/noWindowBoundDomChecks.test.ts +31 -0
- package/src/player/components/CompositionThumbnail.tsx +10 -6
- package/src/player/components/ImageThumbnail.tsx +11 -5
- package/src/player/components/ThumbnailTiles.test.tsx +30 -0
- package/src/player/components/ThumbnailTiles.tsx +41 -0
- package/src/player/components/VideoThumbnail.test.tsx +116 -9
- package/src/player/components/VideoThumbnail.tsx +11 -5
- package/src/player/lib/timelineGroupInfo.realm.test.ts +20 -0
- package/src/player/lib/timelineGroupInfo.ts +2 -1
- package/src/utils/studioPendingEdits.test.ts +52 -0
- package/src/utils/studioPendingEdits.ts +55 -10
- package/src/webmcp/webmcpTestUtils.ts +2 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
import {
|
|
4
4
|
addStudioPendingEditFlushListener,
|
|
5
|
+
adoptingStudioPendingEdit,
|
|
5
6
|
beginStudioPendingEdit,
|
|
6
7
|
flushStudioPendingEdits,
|
|
7
8
|
hasStudioPendingEdits,
|
|
@@ -312,3 +313,54 @@ describe("a drain of only the current edits", () => {
|
|
|
312
313
|
landLater();
|
|
313
314
|
});
|
|
314
315
|
});
|
|
316
|
+
|
|
317
|
+
describe("an edit undo painted back while it saves", () => {
|
|
318
|
+
function paintedBackEdit() {
|
|
319
|
+
const box = { look: "edited" };
|
|
320
|
+
const edit = beginStudioPendingEdit(() => {
|
|
321
|
+
const shown = box.look;
|
|
322
|
+
box.look = "start";
|
|
323
|
+
return () => (box.look = shown);
|
|
324
|
+
});
|
|
325
|
+
let landSave!: () => void;
|
|
326
|
+
const inFlight = edit.adopt(() => adoptingStudioPendingEdit())!;
|
|
327
|
+
edit.settle(new Promise<void>((resolve) => (landSave = resolve)));
|
|
328
|
+
const shown = paintBackNewestStudioPendingEdit()!;
|
|
329
|
+
return { box, inFlight, shown, landSave };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
it("draws on the edit as shown, then shows it undone again in the same task", () => {
|
|
333
|
+
const { box, inFlight, shown, landSave } = paintedBackEdit();
|
|
334
|
+
let drawnOver = "";
|
|
335
|
+
inFlight.drawKeepingUndone(() => {
|
|
336
|
+
drawnOver = box.look;
|
|
337
|
+
box.look += "+size";
|
|
338
|
+
});
|
|
339
|
+
expect(drawnOver).toBe("edited");
|
|
340
|
+
expect(box.look).toBe("start");
|
|
341
|
+
shown.showAgain();
|
|
342
|
+
expect(box.look).toBe("edited+size");
|
|
343
|
+
landSave();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("keeps a redraw until the edit is shown again", () => {
|
|
347
|
+
const { inFlight, shown, landSave } = paintedBackEdit();
|
|
348
|
+
const redraw = vi.fn();
|
|
349
|
+
inFlight.drawUnlessUndone(redraw);
|
|
350
|
+
expect(redraw).not.toHaveBeenCalled();
|
|
351
|
+
shown.showAgain();
|
|
352
|
+
expect(redraw).toHaveBeenCalledTimes(1);
|
|
353
|
+
landSave();
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("counts an edit whose later write failed as saved once an earlier write landed, so undo still steps it", async () => {
|
|
358
|
+
const edit = beginStudioPendingEdit(() => () => undefined);
|
|
359
|
+
const committed = edit.adopt(async () => {
|
|
360
|
+
adoptingStudioPendingEdit()!.markSaved();
|
|
361
|
+
throw new Error("the crop save failed");
|
|
362
|
+
});
|
|
363
|
+
edit.settle(committed.catch(() => undefined));
|
|
364
|
+
const shown = paintBackNewestStudioPendingEdit()!;
|
|
365
|
+
await expect(shown.landed()).resolves.toBe(true);
|
|
366
|
+
});
|
|
@@ -14,11 +14,25 @@ export type StudioEditRevert = () => () => void;
|
|
|
14
14
|
interface PendingEdit {
|
|
15
15
|
revert: StudioEditRevert | null;
|
|
16
16
|
landed: () => Promise<boolean>;
|
|
17
|
+
redraws: Array<() => void>;
|
|
18
|
+
showAgain: (() => void) | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface StudioEditInFlight {
|
|
22
|
+
reverted: () => boolean;
|
|
23
|
+
within: <T>(run: () => T) => T;
|
|
24
|
+
drawUnlessUndone: (draw: () => void) => void;
|
|
25
|
+
drawKeepingUndone: <T>(draw: () => T) => T;
|
|
26
|
+
markSaved: () => void;
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
const pendingEdits = new Map<Promise<unknown>, PendingEdit>();
|
|
20
30
|
const NOT_SAVED = () => Promise.resolve(false);
|
|
21
|
-
let adopting =
|
|
31
|
+
let adopting: StudioEditInFlight | null = null;
|
|
32
|
+
|
|
33
|
+
export function adoptingStudioPendingEdit(): StudioEditInFlight | null {
|
|
34
|
+
return adopting;
|
|
35
|
+
}
|
|
22
36
|
|
|
23
37
|
function waitForPostBlurEffects(): Promise<void> {
|
|
24
38
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
@@ -73,7 +87,7 @@ export function trackStudioPendingEdit(
|
|
|
73
87
|
if (!result) return undefined;
|
|
74
88
|
const promise = Promise.resolve(result);
|
|
75
89
|
if (adopting) return promise;
|
|
76
|
-
pendingEdits.set(promise, { revert: null, landed: NOT_SAVED });
|
|
90
|
+
pendingEdits.set(promise, { revert: null, landed: NOT_SAVED, redraws: [], showAgain: null });
|
|
77
91
|
promise.then(
|
|
78
92
|
() => pendingEdits.delete(promise),
|
|
79
93
|
() => pendingEdits.delete(promise),
|
|
@@ -97,25 +111,49 @@ export function beginStudioPendingEdit(revert: StudioEditRevert | null) {
|
|
|
97
111
|
const entry = pendingEdits.get(promise)!;
|
|
98
112
|
entry.revert = revert;
|
|
99
113
|
let landed = Promise.resolve(false);
|
|
114
|
+
let saved = false;
|
|
100
115
|
entry.landed = () => landed;
|
|
116
|
+
const inFlight: StudioEditInFlight = {
|
|
117
|
+
reverted: () => entry.revert === null && revert !== null,
|
|
118
|
+
within(run) {
|
|
119
|
+
const outer = adopting;
|
|
120
|
+
adopting = inFlight;
|
|
121
|
+
try {
|
|
122
|
+
return run();
|
|
123
|
+
} finally {
|
|
124
|
+
adopting = outer;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
drawUnlessUndone(draw) {
|
|
128
|
+
if (inFlight.reverted()) entry.redraws.push(draw);
|
|
129
|
+
else draw();
|
|
130
|
+
},
|
|
131
|
+
drawKeepingUndone(draw) {
|
|
132
|
+
if (!inFlight.reverted()) return draw();
|
|
133
|
+
entry.showAgain?.();
|
|
134
|
+
try {
|
|
135
|
+
return draw();
|
|
136
|
+
} finally {
|
|
137
|
+
entry.showAgain = revert!();
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
markSaved: () => void (saved = true),
|
|
141
|
+
};
|
|
101
142
|
return {
|
|
102
143
|
settle,
|
|
103
|
-
reverted:
|
|
104
|
-
// Only what `start` registers synchronously is adopted; a
|
|
144
|
+
reverted: inFlight.reverted,
|
|
145
|
+
// Only what `start` registers synchronously is adopted; a later write joins only through `within`.
|
|
105
146
|
adopt<T>(start: () => T): T {
|
|
106
|
-
adopting = true;
|
|
107
147
|
try {
|
|
108
|
-
const committed = start
|
|
148
|
+
const committed = inFlight.within(start);
|
|
109
149
|
landed = Promise.resolve(committed).then(
|
|
110
150
|
() => true,
|
|
111
|
-
() =>
|
|
151
|
+
() => saved,
|
|
112
152
|
);
|
|
113
153
|
return committed;
|
|
114
154
|
} catch (error) {
|
|
115
155
|
settle();
|
|
116
156
|
throw error;
|
|
117
|
-
} finally {
|
|
118
|
-
adopting = false;
|
|
119
157
|
}
|
|
120
158
|
},
|
|
121
159
|
};
|
|
@@ -129,7 +167,14 @@ export function paintBackNewestStudioPendingEdit(): {
|
|
|
129
167
|
const revert = newest?.revert;
|
|
130
168
|
if (!newest || !revert) return null;
|
|
131
169
|
newest.revert = null;
|
|
132
|
-
|
|
170
|
+
newest.showAgain = revert();
|
|
171
|
+
return {
|
|
172
|
+
showAgain: () => {
|
|
173
|
+
newest.showAgain?.();
|
|
174
|
+
for (const redraw of newest.redraws.splice(0)) redraw();
|
|
175
|
+
},
|
|
176
|
+
landed: newest.landed,
|
|
177
|
+
};
|
|
133
178
|
}
|
|
134
179
|
|
|
135
180
|
export function revertNewestStudioPendingEdit(): (() => void) | null {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* existing `hooks/domSelectionTestHarness.ts` convention.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { isHtmlElement } from "@hyperframes/core/runtime/dom-realm";
|
|
8
9
|
import { expect } from "vitest";
|
|
9
10
|
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
10
11
|
import type { ToolFailure, ToolResult } from "./toolResult";
|
|
@@ -31,8 +32,7 @@ export function previewDoc(html: string): Document {
|
|
|
31
32
|
export function previewElement(html: string, id: string): HTMLElement {
|
|
32
33
|
const doc = previewDoc(html);
|
|
33
34
|
const element = doc.getElementById(id);
|
|
34
|
-
|
|
35
|
-
if (!HTMLElementCtor || !(element instanceof HTMLElementCtor)) {
|
|
35
|
+
if (!isHtmlElement(element)) {
|
|
36
36
|
throw new Error(`expected preview element #${id}`);
|
|
37
37
|
}
|
|
38
38
|
return element;
|