@hyperframes/studio 0.8.77 → 0.8.79
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-Bx_Kw4Ey.js +464 -0
- package/dist/assets/{index-DcxWQ7Za.js → index-CJP92F4-.js} +1 -1
- package/dist/assets/{index-2GgHjQq-.js → index-DBKySIo7.js} +187 -187
- package/dist/assets/{index-CsFCRdMo.js → index-ORZXYB0k.js} +1 -1
- package/dist/{chunk-5VANA2TG.js → chunk-I7XNAIOF.js} +2 -1
- package/dist/{domEditingLayers-IGPLTI5E.js → domEditingLayers-MHFDKAQ7.js} +2 -2
- package/dist/index.d.ts +23 -15
- package/dist/index.html +1 -1
- package/dist/index.js +459 -257
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/App.tsx +1 -0
- package/src/components/nle/useTimelineEditCallbacks.ts +8 -2
- package/src/hooks/timelineAudioGroupVolume.ts +21 -11
- package/src/hooks/timelineEditPermission.ts +47 -13
- package/src/hooks/timelineElementFxAttribute.ts +19 -13
- package/src/hooks/useClipboard.source.test.ts +340 -0
- package/src/hooks/useClipboard.ts +141 -62
- package/src/hooks/useElementPicker.test.ts +172 -0
- package/src/hooks/useElementPicker.ts +141 -93
- package/src/hooks/useFrameCapture.test.tsx +67 -0
- package/src/hooks/useFrameCapture.ts +18 -9
- package/src/hooks/usePersistentEditHistory.test.ts +2 -2
- package/src/hooks/usePersistentEditHistory.ts +2 -2
- package/src/hooks/useTimelineEditing.test.tsx +59 -0
- package/src/hooks/useTimelineEditing.ts +42 -33
- package/src/index.ts +8 -0
- package/src/player/components/Player.test.ts +5 -0
- package/src/player/components/Player.tsx +1 -0
- package/src/player/components/VideoThumbnail.test.tsx +13 -0
- package/src/player/components/VideoThumbnail.tsx +1 -1
- package/src/player/components/timelineCallbacks.ts +3 -2
- package/src/player/hooks/useTimelinePlayer.hiddenPause.test.ts +75 -0
- package/src/player/hooks/useTimelinePlayer.play.test.ts +36 -0
- package/src/player/hooks/useTimelinePlayer.ts +25 -28
- package/src/player/hooks/useTimelineSyncCallbacks.ts +12 -4
- package/src/player/lib/thumbnailVideoDecoder.test.ts +102 -6
- package/src/player/lib/thumbnailVideoDecoder.ts +20 -5
- package/src/telemetry/client.test.ts +26 -0
- package/src/telemetry/policy.test.ts +10 -0
- package/src/telemetry/policy.ts +7 -0
- package/src/utils/authoredSource.ts +92 -0
- package/src/utils/gsapSoftReload.test.ts +29 -0
- package/src/utils/gsapSoftReload.ts +3 -7
- package/dist/assets/hyperframes-player-BSDTJ9EF.js +0 -464
- /package/dist/{chunk-5VANA2TG.js.map → chunk-I7XNAIOF.js.map} +0 -0
- /package/dist/{domEditingLayers-IGPLTI5E.js.map → domEditingLayers-MHFDKAQ7.js.map} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ensureHfIds, isCompositionTemplate } from "@hyperframes/parsers/hf-ids";
|
|
2
|
+
|
|
3
|
+
// Stamped as the preview stamps the files it serves, so a live element's hf-id finds its source.
|
|
4
|
+
export function parseSavedSource(html: string): Document {
|
|
5
|
+
return new DOMParser().parseFromString(ensureHfIds(html), "text/html");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const rootsByDoc = new WeakMap<Document, ParentNode[]>();
|
|
9
|
+
|
|
10
|
+
// A plain template is a runtime clone source, so ids inside it repeat across clones.
|
|
11
|
+
function searchRoots(root: ParentNode): ParentNode[] {
|
|
12
|
+
const templates = Array.from(root.querySelectorAll("template")).filter(isCompositionTemplate);
|
|
13
|
+
return [root, ...templates.flatMap((template) => searchRoots(template.content))];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function findByAttribute(doc: Document, name: string, value: string): Element | null {
|
|
17
|
+
let roots = rootsByDoc.get(doc);
|
|
18
|
+
if (!roots) rootsByDoc.set(doc, (roots = searchRoots(doc)));
|
|
19
|
+
const selector = `[${name}="${value.replace(/["\\]/g, "\\$&")}"]`;
|
|
20
|
+
for (const root of roots) {
|
|
21
|
+
const match = root.querySelector(selector);
|
|
22
|
+
if (match) return match;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function findAuthoredElement(doc: Document, live: Element): Element | null {
|
|
28
|
+
const hfId = live.getAttribute("data-hf-id");
|
|
29
|
+
if (hfId) return findByAttribute(doc, "data-hf-id", hfId);
|
|
30
|
+
return live.id ? findByAttribute(doc, "id", live.id) : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function findAuthoredElementById(doc: Document, live: Element): Element | null {
|
|
34
|
+
return live.id ? findByAttribute(doc, "id", live.id) : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const PATH_ATTRS = ["src", "href"];
|
|
38
|
+
const CSS_URL = /\burl\(\s*(["']?)([^)"'\s](?:[^)"']*[^)"'\s])?)\1\s*\)/g;
|
|
39
|
+
|
|
40
|
+
function isRelative(path: string): boolean {
|
|
41
|
+
return !!path && !/^(?:[a-z][a-z\d+.-]*:|\/|#)/i.test(path);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function toProjectPath(sourceFile: string, path: string): string {
|
|
45
|
+
const parts = sourceFile.split("/").slice(0, -1);
|
|
46
|
+
for (const part of path.split("/")) {
|
|
47
|
+
if (part === "..") parts.pop();
|
|
48
|
+
else if (part !== "." && part !== "") parts.push(part);
|
|
49
|
+
}
|
|
50
|
+
return parts.join("/");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function livePathsOf(live: Element): Set<string> {
|
|
54
|
+
const paths = new Set<string>();
|
|
55
|
+
for (const el of [live, ...Array.from(live.querySelectorAll("*"))]) {
|
|
56
|
+
for (const attr of PATH_ATTRS) paths.add(el.getAttribute(attr) ?? "");
|
|
57
|
+
for (const match of (el.getAttribute("style") ?? "").matchAll(CSS_URL)) paths.add(match[2]);
|
|
58
|
+
}
|
|
59
|
+
return paths;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The preview inlines a sub-composition with its relative asset paths rebased to the project
|
|
63
|
+
// root (rewriteAssetPath in @hyperframes/parsers, which is not browser-safe); copy does the same.
|
|
64
|
+
export function authoredMarkup(authored: Element, live: Element, sourceFile: string): string {
|
|
65
|
+
if (!sourceFile.includes("/")) return authored.outerHTML;
|
|
66
|
+
const livePaths = livePathsOf(live);
|
|
67
|
+
const rebase = (path: string): string => {
|
|
68
|
+
if (!isRelative(path)) return path;
|
|
69
|
+
const cut = path.search(/[?#]/);
|
|
70
|
+
const suffix = cut < 0 ? "" : path.slice(cut);
|
|
71
|
+
const rebased = toProjectPath(sourceFile, cut < 0 ? path : path.slice(0, cut)) + suffix;
|
|
72
|
+
return path.startsWith("../") || livePaths.has(rebased) ? rebased : path;
|
|
73
|
+
};
|
|
74
|
+
const copy = authored.cloneNode(true) as Element;
|
|
75
|
+
for (const el of [copy, ...Array.from(copy.querySelectorAll("*"))]) {
|
|
76
|
+
for (const attr of PATH_ATTRS) {
|
|
77
|
+
const value = el.getAttribute(attr);
|
|
78
|
+
if (value) el.setAttribute(attr, rebase(value.trim()));
|
|
79
|
+
}
|
|
80
|
+
const style = el.getAttribute("style");
|
|
81
|
+
if (style) {
|
|
82
|
+
el.setAttribute(
|
|
83
|
+
"style",
|
|
84
|
+
style.replace(
|
|
85
|
+
CSS_URL,
|
|
86
|
+
(_, quote: string, url: string) => `url(${quote}${rebase(url)}${quote})`,
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return copy.outerHTML;
|
|
92
|
+
}
|
|
@@ -488,6 +488,35 @@ describe("applySoftReload authored-opacity restore", () => {
|
|
|
488
488
|
expect(el.style.getPropertyPriority("opacity")).toBe("");
|
|
489
489
|
});
|
|
490
490
|
|
|
491
|
+
it("keeps the stamp for an element whose hf-id the written file lacks, even if it shares an id", () => {
|
|
492
|
+
const el = document.createElement("img");
|
|
493
|
+
el.id = "title-card";
|
|
494
|
+
el.setAttribute("data-hf-id", "hf-from-another-file");
|
|
495
|
+
el.setAttribute("data-hf-authored-opacity", "0.5");
|
|
496
|
+
el.style.opacity = "0";
|
|
497
|
+
|
|
498
|
+
const opacity = restoreOpacity(
|
|
499
|
+
el,
|
|
500
|
+
'<html><body><img id="title-card" style="opacity: 0.9"></body></html>',
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
expect(opacity).toBe("0.5");
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it("does not take a runtime clone's opacity from its plain template", () => {
|
|
507
|
+
const el = document.createElement("li");
|
|
508
|
+
el.id = "item";
|
|
509
|
+
el.setAttribute("data-hf-authored-opacity", "0.5");
|
|
510
|
+
el.style.opacity = "0";
|
|
511
|
+
|
|
512
|
+
const opacity = restoreOpacity(
|
|
513
|
+
el,
|
|
514
|
+
'<html><body><template><li id="item" style="opacity: 0.3"></li></template></body></html>',
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
expect(opacity).toBe("0.5");
|
|
518
|
+
});
|
|
519
|
+
|
|
491
520
|
it("falls back to the parse-time stamp when no after-write HTML is given", () => {
|
|
492
521
|
const el = document.createElement("img");
|
|
493
522
|
el.setAttribute("data-hf-authored-opacity", "0.75");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { COLOR_GRADING_SOURCE_HIDDEN_ATTR } from "@hyperframes/core/color-grading";
|
|
2
|
+
import { findAuthoredElement, parseSavedSource } from "./authoredSource";
|
|
2
3
|
import { applyAuthoredInlineOpacity, readStampedAuthoredOpacity } from "./authoredOpacity";
|
|
3
4
|
|
|
4
5
|
type IframeWindow = Window & {
|
|
@@ -301,17 +302,12 @@ export function applySoftReload(
|
|
|
301
302
|
const findAuthoredSource = (el: HTMLElement): Element | null => {
|
|
302
303
|
if (authoredDoc === undefined) {
|
|
303
304
|
try {
|
|
304
|
-
authoredDoc = authoredHtml
|
|
305
|
-
? new DOMParser().parseFromString(authoredHtml, "text/html")
|
|
306
|
-
: null;
|
|
305
|
+
authoredDoc = authoredHtml ? parseSavedSource(authoredHtml) : null;
|
|
307
306
|
} catch {
|
|
308
307
|
authoredDoc = null;
|
|
309
308
|
}
|
|
310
309
|
}
|
|
311
|
-
|
|
312
|
-
const hfId = el.getAttribute("data-hf-id");
|
|
313
|
-
if (hfId) return authoredDoc.querySelector(`[data-hf-id="${hfId}"]`);
|
|
314
|
-
return el.id ? authoredDoc.getElementById(el.id) : null;
|
|
310
|
+
return authoredDoc ? findAuthoredElement(authoredDoc, el) : null;
|
|
315
311
|
};
|
|
316
312
|
const readAuthoredOpacity = (el: HTMLElement): string | null => {
|
|
317
313
|
const source = findAuthoredSource(el);
|