@hyperframes/studio 0.8.81 → 0.8.82
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-C68UawR3.js → hyperframes-player-zGIbZhLJ.js} +1 -1
- package/dist/assets/{index-CMzPGv9P.js → index-BiLCTunu.js} +1 -1
- package/dist/assets/{index-hNVM9Pd2.js → index-DSazIt7R.js} +189 -189
- package/dist/assets/{index-CS5SImRT.js → index-DStmfnpZ.js} +1 -1
- package/dist/index.d.ts +172 -7
- package/dist/index.html +1 -1
- package/dist/index.js +2414 -2172
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/components/TimelineHistoryButtons.dom.test.tsx +38 -1
- package/src/components/TimelineHistoryButtons.tsx +26 -14
- package/src/components/TimelineToolbar.test.tsx +9 -1
- package/src/components/TimelineToolbar.tsx +13 -5
- package/src/components/editor/BlockParamsPanel.tsx +14 -10
- package/src/components/editor/anchoredResizeReleaseShift.test.ts +2 -6
- package/src/components/editor/manualEdits.test.ts +24 -0
- package/src/components/editor/manualEdits.ts +6 -2
- package/src/components/editor/manualEditsDom.ts +0 -65
- package/src/components/editor/manualEditsSeekReapply.ts +136 -0
- package/src/components/editor/propertyPanelAudioFxGroup.test.tsx +71 -32
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +62 -0
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +4 -6
- package/src/components/editor/propertyPanelHelpers.ts +15 -0
- package/src/components/editor/propertyPanelMediaSection.tsx +3 -5
- package/src/components/editor/reapplyBoxSizeAfterSeek.test.ts +1 -1
- package/src/components/editor/reapplyPositionEditsAfterSeek.test.ts +117 -0
- package/src/components/editor/useFxLevelling.ts +35 -10
- package/src/components/nle/AudioMeterStrip.test.tsx +26 -3
- package/src/components/nle/AudioMeterStrip.tsx +23 -12
- package/src/components/pointerTargetSize.test.tsx +1 -1
- package/src/components/studioColorGradingScope.ts +12 -36
- package/src/contexts/TimelineEditContext.test.tsx +49 -0
- package/src/contexts/TimelineEditContext.tsx +27 -23
- package/src/hooks/canvasQuickSaves.test.tsx +195 -0
- package/src/hooks/gsapMutationClient.ts +116 -0
- package/src/hooks/resizeObserverTestUtils.ts +14 -0
- package/src/hooks/timelineAudioGroupCreate.ts +18 -40
- package/src/hooks/timelineEditingHelpers.test.ts +21 -0
- package/src/hooks/timelineEditingHelpers.ts +66 -61
- package/src/hooks/timelineQuickSaves.test.ts +195 -0
- package/src/hooks/timelineTimingSync.test.ts +12 -0
- package/src/hooks/timelineTimingSync.ts +31 -123
- package/src/hooks/timelineTrackVisibility.ts +15 -29
- package/src/hooks/useAskAgentModal.test.tsx +78 -0
- package/src/hooks/useAskAgentModal.ts +3 -1
- package/src/hooks/useClipboard.source.test.ts +32 -0
- package/src/hooks/useClipboard.ts +21 -20
- package/src/hooks/useDomEditCommits.test.tsx +13 -9
- package/src/hooks/useDomEditCommits.ts +88 -97
- package/src/hooks/useDomEditCommitsHelpers.ts +69 -6
- package/src/hooks/useEditorSave.ts +1 -1
- package/src/hooks/useElementLifecycleOps.ts +36 -37
- package/src/hooks/useFileManager.projectOwnership.test.tsx +51 -4
- package/src/hooks/useFileManager.ts +21 -12
- package/src/hooks/useGroupCommits.ts +23 -24
- package/src/hooks/useGsapAnimationOps.ts +26 -8
- package/src/hooks/useGsapScriptCommits.ts +1 -0
- package/src/hooks/useThumbnailStripSize.test.tsx +47 -0
- package/src/hooks/useThumbnailStripSize.ts +31 -0
- package/src/hooks/useTimelineAssetDropOps.ts +38 -35
- package/src/hooks/useTimelineDeleteOps.ts +53 -56
- package/src/hooks/useTimelineEditing.ts +3 -1
- package/src/hooks/useTimelineGroupEditing.ts +5 -1
- package/src/index.ts +8 -0
- package/src/player/components/CompositionThumbnail.test.ts +40 -6
- package/src/player/components/CompositionThumbnail.tsx +6 -21
- package/src/player/components/ImageThumbnail.test.tsx +27 -6
- package/src/player/components/ImageThumbnail.tsx +4 -18
- package/src/player/components/PlayerControls.test.tsx +52 -0
- package/src/player/components/PlayerControls.tsx +2 -1
- package/src/player/components/Timeline.test.ts +28 -3
- package/src/player/components/TimelineCanvas.tsx +5 -3
- package/src/player/components/VideoThumbnail.test.tsx +33 -11
- package/src/player/components/VideoThumbnail.tsx +7 -29
- package/src/player/components/thumbnailUtils.test.ts +16 -20
- package/src/player/components/thumbnailUtils.ts +6 -7
- package/src/player/components/timelineEditing.test.ts +55 -0
- package/src/player/components/timelineGroupEditing.ts +13 -6
- package/src/player/components/timelinePlayheadTransform.test.ts +33 -0
- package/src/player/components/timelinePlayheadTransform.ts +17 -0
- package/src/player/components/useTimelinePlayhead.ts +26 -8
- package/src/player/hooks/timelinePlayerSync.test.ts +20 -0
- package/src/player/hooks/timelinePlayerSync.ts +18 -8
- package/src/player/hooks/useShadowPreviewReload.ts +15 -4
- package/src/player/hooks/useTimelinePlayer.shadowReload.test.ts +19 -0
- package/src/player/hooks/useTimelinePlayer.test.ts +93 -1
- package/src/player/lib/playbackShortcuts.ts +18 -0
- package/src/player/lib/playbackTypes.ts +1 -0
- package/src/player/lib/timelineDOM.test.ts +92 -0
- package/src/player/lib/timelineDOM.ts +0 -2
- package/src/player/lib/timelineElementHelpers.ts +47 -10
- package/src/player/lib/timelineViewportBudgets.ts +1 -1
- package/src/player/store/timelineElement.ts +2 -1
- package/src/timelineChromeExports.test.tsx +79 -0
- package/src/utils/authoredSource.ts +15 -0
- package/src/utils/blockInstaller.ts +32 -27
- package/src/utils/studioFileHistory.test.ts +14 -14
- package/src/utils/studioFileHistory.ts +31 -2
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { ensureHfIds, isCompositionTemplate } from "@hyperframes/parsers/hf-ids";
|
|
2
|
+
import {
|
|
3
|
+
STUDIO_PREVIEW_LAZY_ATTR,
|
|
4
|
+
STUDIO_PREVIEW_UPCOMING_ATTR,
|
|
5
|
+
} from "@hyperframes/core/studio-preview-mark";
|
|
2
6
|
|
|
3
7
|
// Stamped as the preview stamps the files it serves, so a live element's hf-id finds its source.
|
|
4
8
|
export function parseSavedSource(html: string): Document {
|
|
@@ -90,3 +94,14 @@ export function authoredMarkup(authored: Element, live: Element, sourceFile: str
|
|
|
90
94
|
}
|
|
91
95
|
return copy.outerHTML;
|
|
92
96
|
}
|
|
97
|
+
|
|
98
|
+
export function liveMarkupWithoutPreviewMarks(live: Element): string {
|
|
99
|
+
const copy = live.cloneNode(true) as Element;
|
|
100
|
+
for (const el of [copy, ...Array.from(copy.querySelectorAll("*"))]) {
|
|
101
|
+
el.removeAttribute(STUDIO_PREVIEW_UPCOMING_ATTR);
|
|
102
|
+
if (!el.hasAttribute(STUDIO_PREVIEW_LAZY_ATTR)) continue;
|
|
103
|
+
el.removeAttribute("loading");
|
|
104
|
+
el.removeAttribute(STUDIO_PREVIEW_LAZY_ATTR);
|
|
105
|
+
}
|
|
106
|
+
return copy.outerHTML;
|
|
107
|
+
}
|
|
@@ -9,6 +9,7 @@ import { collectHtmlIds } from "./studioHelpers";
|
|
|
9
9
|
import { generateId } from "./generateId";
|
|
10
10
|
import { formatTimelineAttributeNumber } from "../player/components/timelineEditing";
|
|
11
11
|
import { saveProjectFilesWithHistory } from "./studioFileHistory";
|
|
12
|
+
import { serializeStudioFileMutation } from "./studioFileMutationCoordinator";
|
|
12
13
|
import { extendRootDurationInSource } from "./rootDuration";
|
|
13
14
|
import { deriveTimelineStoreKeyForDomId } from "../player/lib/timelineElementHelpers";
|
|
14
15
|
|
|
@@ -93,12 +94,14 @@ async function makeComponentBackgroundTransparent(
|
|
|
93
94
|
writeProjectFile: AddBlockOptions["writeProjectFile"],
|
|
94
95
|
): Promise<void> {
|
|
95
96
|
if (block.type !== "hyperframes:component") return;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
await serializeStudioFileMutation(writeProjectFile, compositionFile, async () => {
|
|
98
|
+
const content = await readProjectFile(compositionFile);
|
|
99
|
+
const transparentContent = content.replace(
|
|
100
|
+
/background:\s*(?:#(?:0a0a0a|000000|000|0a0805)|rgba?\([^)]*\))\s*;/g,
|
|
101
|
+
"background: transparent;",
|
|
102
|
+
);
|
|
103
|
+
if (transparentContent !== content) await writeProjectFile(compositionFile, transparentContent);
|
|
104
|
+
});
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
function resolveBlockPlacement({
|
|
@@ -201,7 +204,6 @@ export async function addBlockToProject(
|
|
|
201
204
|
);
|
|
202
205
|
|
|
203
206
|
const targetPath = activeCompPath || "index.html";
|
|
204
|
-
const originalContent = await readProjectFile(targetPath);
|
|
205
207
|
const relevantElements = timelineElements.filter(
|
|
206
208
|
(element) => (element.sourceFile || targetPath) === targetPath,
|
|
207
209
|
);
|
|
@@ -211,29 +213,32 @@ export async function addBlockToProject(
|
|
|
211
213
|
timelineElements: relevantElements,
|
|
212
214
|
currentTime: opts.currentTime ?? 0,
|
|
213
215
|
});
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
216
|
+
let hostId = "";
|
|
217
|
+
const insertHost = (originalContent: string) => {
|
|
218
|
+
const { width, height } = resolveTimelineAssetCompositionSize(originalContent);
|
|
219
|
+
hostId = buildUniqueCompositionId(block.name, collectHtmlIds(originalContent));
|
|
220
|
+
const subComposition = buildSubCompositionHtml({
|
|
221
|
+
id: hostId,
|
|
222
|
+
compositionFile,
|
|
223
|
+
start,
|
|
224
|
+
duration,
|
|
225
|
+
track,
|
|
226
|
+
width,
|
|
227
|
+
height,
|
|
228
|
+
left: visualPosition ? Math.round(visualPosition.left) : 0,
|
|
229
|
+
top: visualPosition ? Math.round(visualPosition.top) : 0,
|
|
230
|
+
zIndex: getMaxZIndexFromIframe(opts.previewIframe ?? null) + 1,
|
|
231
|
+
});
|
|
232
|
+
return extendRootDurationInSource(
|
|
233
|
+
insertTimelineAssetIntoSource(originalContent, subComposition),
|
|
234
|
+
start + duration,
|
|
235
|
+
);
|
|
236
|
+
};
|
|
232
237
|
await saveProjectFilesWithHistory({
|
|
233
238
|
projectId,
|
|
234
239
|
label: `Add ${isBlock ? "block" : "component"}: ${block.title}`,
|
|
235
|
-
files: { [targetPath]:
|
|
236
|
-
readFile:
|
|
240
|
+
files: { [targetPath]: insertHost },
|
|
241
|
+
readFile: readProjectFile,
|
|
237
242
|
writeFile: writeProjectFile,
|
|
238
243
|
recordEdit,
|
|
239
244
|
});
|
|
@@ -11,7 +11,7 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
11
11
|
await saveProjectFilesWithHistory({
|
|
12
12
|
projectId: "project-1",
|
|
13
13
|
label: "Move layer",
|
|
14
|
-
files: { "index.html": "after" },
|
|
14
|
+
files: { "index.html": () => "after" },
|
|
15
15
|
readFile: async (path) => reads[path],
|
|
16
16
|
writeFile: async (path, content) => {
|
|
17
17
|
writes[path] = content;
|
|
@@ -40,7 +40,7 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
40
40
|
await saveProjectFilesWithHistory({
|
|
41
41
|
projectId: "project-1",
|
|
42
42
|
label: "Delete timeline clip",
|
|
43
|
-
files: { "index.html": "removed+shrunk" },
|
|
43
|
+
files: { "index.html": () => "removed+shrunk" },
|
|
44
44
|
readFile: async () => "original",
|
|
45
45
|
diskContent: { "index.html": "removed" },
|
|
46
46
|
writeFile: async (path, _content, expectedContent) => {
|
|
@@ -64,7 +64,7 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
64
64
|
await saveProjectFilesWithHistory({
|
|
65
65
|
projectId: "project-1",
|
|
66
66
|
label: "Move layer",
|
|
67
|
-
files: { "index.html": "after" },
|
|
67
|
+
files: { "index.html": () => "after" },
|
|
68
68
|
readFile: async () => "before",
|
|
69
69
|
writeFile: async (path, _content, expectedContent) => {
|
|
70
70
|
expectations[path] = expectedContent;
|
|
@@ -81,7 +81,7 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
81
81
|
const changedPaths = await saveProjectFilesWithHistory({
|
|
82
82
|
projectId: "project-1",
|
|
83
83
|
label: "Edit layer",
|
|
84
|
-
files: { "index.html": "same" },
|
|
84
|
+
files: { "index.html": () => "same" },
|
|
85
85
|
readFile: async () => "same",
|
|
86
86
|
writeFile,
|
|
87
87
|
recordEdit,
|
|
@@ -105,8 +105,8 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
105
105
|
projectId: "project-1",
|
|
106
106
|
label: "Move layer",
|
|
107
107
|
files: {
|
|
108
|
-
"index.html": "index-after",
|
|
109
|
-
"scene.html": "scene-after",
|
|
108
|
+
"index.html": () => "index-after",
|
|
109
|
+
"scene.html": () => "scene-after",
|
|
110
110
|
},
|
|
111
111
|
readFile: async (path) => reads[path],
|
|
112
112
|
writeFile: async (path, content) => {
|
|
@@ -139,8 +139,8 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
139
139
|
projectId: "project-1",
|
|
140
140
|
label: "Move layer",
|
|
141
141
|
files: {
|
|
142
|
-
"index.html": "index-after",
|
|
143
|
-
"scene.html": "scene-after",
|
|
142
|
+
"index.html": () => "index-after",
|
|
143
|
+
"scene.html": () => "scene-after",
|
|
144
144
|
},
|
|
145
145
|
readFile: async (path) => reads[path],
|
|
146
146
|
writeFile: async (path, content) => {
|
|
@@ -172,8 +172,8 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
172
172
|
projectId: "project-1",
|
|
173
173
|
label: "Move layer",
|
|
174
174
|
files: {
|
|
175
|
-
"index.html": "index-after",
|
|
176
|
-
"scene.html": "scene-after",
|
|
175
|
+
"index.html": () => "index-after",
|
|
176
|
+
"scene.html": () => "scene-after",
|
|
177
177
|
},
|
|
178
178
|
readFile: async (path) => reads[path],
|
|
179
179
|
writeFile: async (path, content) => {
|
|
@@ -196,7 +196,7 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
196
196
|
]);
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
-
it("
|
|
199
|
+
it("builds from what the file holds after an earlier same-file mutation completes", async () => {
|
|
200
200
|
let disk = "before";
|
|
201
201
|
let release!: () => void;
|
|
202
202
|
const blocked = new Promise<void>((resolve) => {
|
|
@@ -215,7 +215,7 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
215
215
|
const save = saveProjectFilesWithHistory({
|
|
216
216
|
projectId: "project-1",
|
|
217
217
|
label: "Edit source",
|
|
218
|
-
files: { "index.html":
|
|
218
|
+
files: { "index.html": (current) => `${current}+editor` },
|
|
219
219
|
readFile,
|
|
220
220
|
writeFile,
|
|
221
221
|
recordEdit,
|
|
@@ -227,10 +227,10 @@ describe("saveProjectFilesWithHistory", () => {
|
|
|
227
227
|
await priorMutation;
|
|
228
228
|
await save;
|
|
229
229
|
|
|
230
|
-
expect(disk).toBe("
|
|
230
|
+
expect(disk).toBe("sdk-after+editor");
|
|
231
231
|
expect(recordEdit).toHaveBeenCalledWith(
|
|
232
232
|
expect.objectContaining({
|
|
233
|
-
files: { "index.html": { before: "sdk-after", after: "
|
|
233
|
+
files: { "index.html": { before: "sdk-after", after: "sdk-after+editor" } },
|
|
234
234
|
}),
|
|
235
235
|
);
|
|
236
236
|
});
|
|
@@ -27,7 +27,7 @@ interface SaveProjectFilesWithHistoryInput {
|
|
|
27
27
|
label: string;
|
|
28
28
|
coalesceKey?: string;
|
|
29
29
|
coalesceMs?: number;
|
|
30
|
-
files: Record<string, string>;
|
|
30
|
+
files: Record<string, (contentInsideFileQueue: string) => string>;
|
|
31
31
|
readFile: (path: string) => Promise<string>;
|
|
32
32
|
writeFile: ProjectFileWriter;
|
|
33
33
|
recordEdit: (entry: RecordEditInput) => Promise<void>;
|
|
@@ -68,6 +68,34 @@ export async function saveProjectFilesWithHistory(
|
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* A server-side rewrite and its undo entry, holding the file's queue from the read to the history write.
|
|
73
|
+
* `rewrite` returns what the server left on disk (and the edit's final content if it goes further), or null.
|
|
74
|
+
*/
|
|
75
|
+
export async function saveServerRewriteWithHistory(input: {
|
|
76
|
+
projectId: string;
|
|
77
|
+
path: string;
|
|
78
|
+
label: string;
|
|
79
|
+
coalesceKey?: string;
|
|
80
|
+
writeFile: ProjectFileWriter;
|
|
81
|
+
recordEdit: (entry: RecordEditInput) => Promise<void>;
|
|
82
|
+
rewrite: (original: string) => Promise<{ disk: string; after?: string } | null>;
|
|
83
|
+
}): Promise<boolean> {
|
|
84
|
+
const { projectId, path, writeFile } = input;
|
|
85
|
+
return serializeStudioFileMutations(writeFile, [path], async () => {
|
|
86
|
+
const original = await readProjectFileContent(projectId, path);
|
|
87
|
+
const result = await input.rewrite(original);
|
|
88
|
+
if (!result) return false;
|
|
89
|
+
await writeProjectFilesWithHistoryInQueue({
|
|
90
|
+
...input,
|
|
91
|
+
files: { [path]: () => result.after ?? result.disk },
|
|
92
|
+
readFile: async () => original,
|
|
93
|
+
diskContent: { [path]: result.disk },
|
|
94
|
+
});
|
|
95
|
+
return true;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
71
99
|
export async function writeProjectFilesWithHistoryInQueue({
|
|
72
100
|
label,
|
|
73
101
|
coalesceKey,
|
|
@@ -79,8 +107,9 @@ export async function writeProjectFilesWithHistoryInQueue({
|
|
|
79
107
|
diskContent,
|
|
80
108
|
}: SaveProjectFilesWithHistoryInput): Promise<string[]> {
|
|
81
109
|
const snapshots: Record<string, { before: string; after: string }> = {};
|
|
82
|
-
for (const [path,
|
|
110
|
+
for (const [path, build] of Object.entries(files)) {
|
|
83
111
|
const before = await readFile(path);
|
|
112
|
+
const after = build(before);
|
|
84
113
|
if (before !== after) {
|
|
85
114
|
snapshots[path] = { before, after };
|
|
86
115
|
}
|