@hyperframes/studio-server 0.8.114 → 0.8.115
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/{chunk-EPL4YKGI.js → chunk-E3HLZKPI.js} +16 -7
- package/dist/chunk-E3HLZKPI.js.map +1 -0
- package/dist/{chunk-Z4DIWF3Z.js → chunk-G4YB7Q2Q.js} +24 -9
- package/dist/chunk-G4YB7Q2Q.js.map +1 -0
- package/dist/{chunk-W2SBTCO2.js → chunk-IMG2GSY6.js} +21 -2
- package/dist/chunk-IMG2GSY6.js.map +1 -0
- package/dist/helpers/screenshotClip.d.ts +2 -1
- package/dist/helpers/screenshotClip.js +3 -1
- package/dist/helpers/sourceMutation.d.ts +8 -1
- package/dist/helpers/sourceMutation.js +5 -1
- package/dist/history/index.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +84 -35
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-EPL4YKGI.js.map +0 -1
- package/dist/chunk-W2SBTCO2.js.map +0 -1
- package/dist/chunk-Z4DIWF3Z.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/helpers/screenshotClip.ts"],"sourcesContent":["export interface ScreenshotClip {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n// Serialised by page.evaluate, so self-contained; fades everything but the element's own branch.\nexport function getElementScreenshotClip(\n selector: string,\n selectorIndex?: number,\n): ScreenshotClip | undefined {\n // Guard against invalid CSS selectors (e.g. `#0` — a digit-leading id from\n // user HTML that upstream producers forgot to CSS.escape). querySelectorAll\n // throws SyntaxError on those, which bubbles out of page.evaluate and fails\n // the whole thumbnail. Returning undefined here falls back to a full-page\n // screenshot, so the user still sees a thumbnail instead of a broken image.\n let matches: HTMLElement[];\n try {\n matches = Array.from(document.querySelectorAll(selector)).filter(\n (el): el is HTMLElement => el instanceof HTMLElement,\n );\n } catch {\n return undefined;\n }\n const safeIndex = Math.max(0, Math.min(matches.length - 1, Math.floor(selectorIndex ?? 0)));\n const el = matches[safeIndex] ?? null;\n if (!(el instanceof HTMLElement)) return undefined;\n // Opacity, not visibility; each prior inline opacity is kept for clearElementScreenshotIsolation.\n const page = window as Window & { __hfThumbnailFaded?: [CSSStyleDeclaration, string, string][] };\n const faded = (page.__hfThumbnailFaded ??= []);\n for (let node: Element = el; node.parentElement; node = node.parentElement) {\n for (const sibling of Array.from(node.parentElement.children)) {\n const style = (sibling as HTMLElement).style;\n if (sibling === node || !style) continue;\n faded.push([style, style.getPropertyValue(\"opacity\"), style.getPropertyPriority(\"opacity\")]);\n style.setProperty(\"opacity\", \"0\", \"important\");\n }\n }\n const rect = el.getBoundingClientRect();\n if (rect.width < 4 || rect.height < 4) return undefined;\n const pad = 8;\n const x = Math.max(0, rect.left - pad);\n const y = Math.max(0, rect.top - pad);\n const maxWidth = window.innerWidth - x;\n const maxHeight = window.innerHeight - y;\n return {\n x,\n y,\n width: Math.max(1, Math.min(rect.width + pad * 2, maxWidth)),\n height: Math.max(1, Math.min(rect.height + pad * 2, maxHeight)),\n };\n}\n\nexport function clearElementScreenshotIsolation(): void {\n const page = window as Window & { __hfThumbnailFaded?: [CSSStyleDeclaration, string, string][] };\n for (const [style, value, priority] of (page.__hfThumbnailFaded ?? []).reverse()) {\n if (value) style.setProperty(\"opacity\", value, priority);\n else style.removeProperty(\"opacity\");\n }\n page.__hfThumbnailFaded = [];\n}\n"],"mappings":";AAQO,SAAS,yBACd,UACA,eAC4B;AAM5B,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,KAAK,SAAS,iBAAiB,QAAQ,CAAC,EAAE;AAAA,MACxD,CAACA,QAA0BA,eAAc;AAAA,IAC3C;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,SAAS,GAAG,KAAK,MAAM,iBAAiB,CAAC,CAAC,CAAC;AAC1F,QAAM,KAAK,QAAQ,SAAS,KAAK;AACjC,MAAI,EAAE,cAAc,aAAc,QAAO;AAEzC,QAAM,OAAO;AACb,QAAM,QAAS,KAAK,uBAAuB,CAAC;AAC5C,WAAS,OAAgB,IAAI,KAAK,eAAe,OAAO,KAAK,eAAe;AAC1E,eAAW,WAAW,MAAM,KAAK,KAAK,cAAc,QAAQ,GAAG;AAC7D,YAAM,QAAS,QAAwB;AACvC,UAAI,YAAY,QAAQ,CAAC,MAAO;AAChC,YAAM,KAAK,CAAC,OAAO,MAAM,iBAAiB,SAAS,GAAG,MAAM,oBAAoB,SAAS,CAAC,CAAC;AAC3F,YAAM,YAAY,WAAW,KAAK,WAAW;AAAA,IAC/C;AAAA,EACF;AACA,QAAM,OAAO,GAAG,sBAAsB;AACtC,MAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,EAAG,QAAO;AAC9C,QAAM,MAAM;AACZ,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,OAAO,GAAG;AACrC,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,GAAG;AACpC,QAAM,WAAW,OAAO,aAAa;AACrC,QAAM,YAAY,OAAO,cAAc;AACvC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,QAAQ,MAAM,GAAG,QAAQ,CAAC;AAAA,IAC3D,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,SAAS,MAAM,GAAG,SAAS,CAAC;AAAA,EAChE;AACF;AAEO,SAAS,kCAAwC;AACtD,QAAM,OAAO;AACb,aAAW,CAAC,OAAO,OAAO,QAAQ,MAAM,KAAK,sBAAsB,CAAC,GAAG,QAAQ,GAAG;AAChF,QAAI,MAAO,OAAM,YAAY,WAAW,OAAO,QAAQ;AAAA,QAClD,OAAM,eAAe,SAAS;AAAA,EACrC;AACA,OAAK,qBAAqB,CAAC;AAC7B;","names":["el"]}
|
|
@@ -5,5 +5,6 @@ interface ScreenshotClip {
|
|
|
5
5
|
height: number;
|
|
6
6
|
}
|
|
7
7
|
declare function getElementScreenshotClip(selector: string, selectorIndex?: number): ScreenshotClip | undefined;
|
|
8
|
+
declare function clearElementScreenshotIsolation(): void;
|
|
8
9
|
|
|
9
|
-
export { type ScreenshotClip, getElementScreenshotClip };
|
|
10
|
+
export { type ScreenshotClip, clearElementScreenshotIsolation, getElementScreenshotClip };
|
|
@@ -9,6 +9,11 @@ declare function parseSourceDocument(raw: string): {
|
|
|
9
9
|
wrappedFragment: boolean;
|
|
10
10
|
};
|
|
11
11
|
declare function findTargetElement(document: Document, target: SourceMutationTarget): Element | null;
|
|
12
|
+
/**
|
|
13
|
+
* Removes every target in one parse and one serialization. A target nested inside one already
|
|
14
|
+
* removed no longer matches, which is a normal outcome rather than a failure.
|
|
15
|
+
*/
|
|
16
|
+
declare function removeElementsFromHtml(source: string, targets: readonly SourceMutationTarget[]): string;
|
|
12
17
|
declare function removeElementFromHtml(source: string, target: SourceMutationTarget): string;
|
|
13
18
|
declare function isHTMLElement(el: Node): el is HTMLElement;
|
|
14
19
|
declare function dedupeClonedCompositionId(document: Document, clone: Element): void;
|
|
@@ -23,6 +28,8 @@ declare function patchElementInHtml(source: string, target: SourceMutationTarget
|
|
|
23
28
|
html: string;
|
|
24
29
|
matched: boolean;
|
|
25
30
|
};
|
|
31
|
+
/** Whether each target exists in `source`; the document is parsed once however many targets ask. */
|
|
32
|
+
declare function probeElementsInSource(source: string, targets: readonly SourceMutationTarget[]): boolean[];
|
|
26
33
|
declare function probeElementInSource(source: string, target: SourceMutationTarget): boolean;
|
|
27
34
|
interface SplitElementResult {
|
|
28
35
|
html: string;
|
|
@@ -87,4 +94,4 @@ declare function unwrapElementsFromHtml(source: string, groupTarget: SourceMutat
|
|
|
87
94
|
/** After a cut, give each linked group's right halves their own `data-link` and `data-sync-origin`. */
|
|
88
95
|
declare function relinkSplitHalvesInHtml(source: string, rightHalfIds: readonly string[]): string;
|
|
89
96
|
|
|
90
|
-
export { type ElementRebase, type PatchOperation, type SourceMutationTarget, type SplitElementResult, type UnwrapChildTrack, type UnwrapElementsResult, type WrapElementsResult, dedupeClonedCompositionId, findTargetElement, isHTMLElement, parseSourceDocument, patchElementInHtml, probeElementInSource, relinkSplitHalvesInHtml, removeElementFromHtml, splitElementInHtml, unwrapElementsFromHtml, wrapElementsInHtml };
|
|
97
|
+
export { type ElementRebase, type PatchOperation, type SourceMutationTarget, type SplitElementResult, type UnwrapChildTrack, type UnwrapElementsResult, type WrapElementsResult, dedupeClonedCompositionId, findTargetElement, isHTMLElement, parseSourceDocument, patchElementInHtml, probeElementInSource, probeElementsInSource, relinkSplitHalvesInHtml, removeElementFromHtml, removeElementsFromHtml, splitElementInHtml, unwrapElementsFromHtml, wrapElementsInHtml };
|
|
@@ -5,12 +5,14 @@ import {
|
|
|
5
5
|
parseSourceDocument,
|
|
6
6
|
patchElementInHtml,
|
|
7
7
|
probeElementInSource,
|
|
8
|
+
probeElementsInSource,
|
|
8
9
|
relinkSplitHalvesInHtml,
|
|
9
10
|
removeElementFromHtml,
|
|
11
|
+
removeElementsFromHtml,
|
|
10
12
|
splitElementInHtml,
|
|
11
13
|
unwrapElementsFromHtml,
|
|
12
14
|
wrapElementsInHtml
|
|
13
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-G4YB7Q2Q.js";
|
|
14
16
|
export {
|
|
15
17
|
dedupeClonedCompositionId,
|
|
16
18
|
findTargetElement,
|
|
@@ -18,8 +20,10 @@ export {
|
|
|
18
20
|
parseSourceDocument,
|
|
19
21
|
patchElementInHtml,
|
|
20
22
|
probeElementInSource,
|
|
23
|
+
probeElementsInSource,
|
|
21
24
|
relinkSplitHalvesInHtml,
|
|
22
25
|
removeElementFromHtml,
|
|
26
|
+
removeElementsFromHtml,
|
|
23
27
|
splitElementInHtml,
|
|
24
28
|
unwrapElementsFromHtml,
|
|
25
29
|
wrapElementsInHtml
|
package/dist/history/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { walkDir } from './helpers/safePath.js';
|
|
|
8
8
|
export { stampProjectHfIds } from './helpers/hfIdPersist.js';
|
|
9
9
|
import { SourceMutationTarget } from './helpers/sourceMutation.js';
|
|
10
10
|
export { PatchOperation, dedupeClonedCompositionId, findTargetElement, parseSourceDocument, patchElementInHtml, removeElementFromHtml, splitElementInHtml } from './helpers/sourceMutation.js';
|
|
11
|
-
export { ScreenshotClip, getElementScreenshotClip } from './helpers/screenshotClip.js';
|
|
11
|
+
export { ScreenshotClip, clearElementScreenshotIsolation, getElementScreenshotClip } from './helpers/screenshotClip.js';
|
|
12
12
|
export { STUDIO_MANUAL_EDITS_PATH, StudioManualEditsRenderScriptOptions, createStudioManualEditsRenderBodyScript, createStudioPositionSeekReapplyScript } from './helpers/manualEditsRenderScript.js';
|
|
13
13
|
export { STUDIO_MOTION_PATH, StudioMotionRenderScriptOptions, createStudioMotionRenderBodyScript } from './helpers/studioMotionRenderScript.js';
|
|
14
14
|
export { isSafePath } from '@hyperframes/core/safe-path';
|
|
@@ -54,7 +54,8 @@ declare function createProjectSignature(projectDir: string, excluding?: Readonly
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Compositions whose rendered frames a write at `changedPath` can change, or `null` for all
|
|
57
|
-
* of them:
|
|
57
|
+
* of them: assets, a root head edit, and any file the root does not mount reach every
|
|
58
|
+
* composition. A head-preserving root write changes only the root's own frames.
|
|
58
59
|
*/
|
|
59
60
|
declare function compositionsAffectedBy(projectDir: string, changedPath: string): string[] | null;
|
|
60
61
|
|
package/dist/index.js
CHANGED
|
@@ -9,12 +9,14 @@ import {
|
|
|
9
9
|
parseSourceDocument,
|
|
10
10
|
patchElementInHtml,
|
|
11
11
|
probeElementInSource,
|
|
12
|
+
probeElementsInSource,
|
|
12
13
|
relinkSplitHalvesInHtml,
|
|
13
14
|
removeElementFromHtml,
|
|
15
|
+
removeElementsFromHtml,
|
|
14
16
|
splitElementInHtml,
|
|
15
17
|
unwrapElementsFromHtml,
|
|
16
18
|
wrapElementsInHtml
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-G4YB7Q2Q.js";
|
|
18
20
|
import {
|
|
19
21
|
isCompositionSource,
|
|
20
22
|
stampProjectHfIds
|
|
@@ -44,10 +46,11 @@ import {
|
|
|
44
46
|
resolveProjectAndSignature,
|
|
45
47
|
resolveProjectSignature,
|
|
46
48
|
settledFileTag
|
|
47
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-E3HLZKPI.js";
|
|
48
50
|
import {
|
|
51
|
+
clearElementScreenshotIsolation,
|
|
49
52
|
getElementScreenshotClip
|
|
50
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-IMG2GSY6.js";
|
|
51
54
|
import {
|
|
52
55
|
DEFAULT_TARGET_LUFS,
|
|
53
56
|
absoluteLoudnessPlan,
|
|
@@ -1578,7 +1581,9 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
1578
1581
|
} catch (error) {
|
|
1579
1582
|
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
1580
1583
|
}
|
|
1584
|
+
const mutationChanges = [];
|
|
1581
1585
|
for (const mutation of mutations) {
|
|
1586
|
+
const previousScript = block.scriptText;
|
|
1582
1587
|
const result = await executeGsapMutation(mutation, block, respond, writer);
|
|
1583
1588
|
if (result instanceof Response) return result;
|
|
1584
1589
|
let newScript = typeof result === "string" ? result : result.script;
|
|
@@ -1588,6 +1593,7 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
1588
1593
|
if (HOLD_SYNC_MUTATION_TYPES.has(mutation.type)) {
|
|
1589
1594
|
newScript = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(newScript) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(newScript);
|
|
1590
1595
|
}
|
|
1596
|
+
mutationChanges.push(newScript !== previousScript);
|
|
1591
1597
|
block.scriptText = newScript;
|
|
1592
1598
|
}
|
|
1593
1599
|
const changed = block.scriptText !== initialScript;
|
|
@@ -1612,6 +1618,7 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
1612
1618
|
ok: true,
|
|
1613
1619
|
changed,
|
|
1614
1620
|
mutated: changed,
|
|
1621
|
+
mutationChanges,
|
|
1615
1622
|
parsed: parseGsapScriptAcorn(block.scriptText),
|
|
1616
1623
|
before: beforeHtml,
|
|
1617
1624
|
after: newHtml,
|
|
@@ -2671,11 +2678,14 @@ function registerFileRoutes(api, adapter) {
|
|
|
2671
2678
|
return c.json({ error: "targets required" }, 400);
|
|
2672
2679
|
}
|
|
2673
2680
|
const originalContent = readFileSync3(ctx.absPath, "utf-8");
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2681
|
+
return writeIfChanged(
|
|
2682
|
+
c,
|
|
2683
|
+
ctx.project.dir,
|
|
2684
|
+
ctx.filePath,
|
|
2685
|
+
ctx.absPath,
|
|
2686
|
+
originalContent,
|
|
2687
|
+
removeElementsFromHtml(originalContent, targets)
|
|
2688
|
+
);
|
|
2679
2689
|
});
|
|
2680
2690
|
api.post("/projects/:id/file-mutations/split-batch", async (c) => {
|
|
2681
2691
|
const body = await c.req.json().catch(() => null);
|
|
@@ -3079,6 +3089,19 @@ function registerFileRoutes(api, adapter) {
|
|
|
3079
3089
|
const exists = probeElementInSource(content, parsed.target);
|
|
3080
3090
|
return c.json({ exists });
|
|
3081
3091
|
});
|
|
3092
|
+
api.post("/projects/:id/file-mutations/probe-elements/*", async (c) => {
|
|
3093
|
+
const ctx = await resolveFileMutationContext(c, adapter, "probe-elements");
|
|
3094
|
+
if ("error" in ctx) return ctx.error;
|
|
3095
|
+
const body = await c.req.json().catch(() => null);
|
|
3096
|
+
if (!Array.isArray(body?.targets)) return c.json({ error: "targets required" }, 400);
|
|
3097
|
+
let content;
|
|
3098
|
+
try {
|
|
3099
|
+
content = readFileSync3(ctx.absPath, "utf-8");
|
|
3100
|
+
} catch {
|
|
3101
|
+
return c.json({ exists: body.targets.map(() => false) });
|
|
3102
|
+
}
|
|
3103
|
+
return c.json({ exists: probeElementsInSource(content, body.targets) });
|
|
3104
|
+
});
|
|
3082
3105
|
api.patch("/projects/:id/files/*", async (c) => {
|
|
3083
3106
|
const res = await resolveProjectFile(c, adapter, { mustExist: true });
|
|
3084
3107
|
if ("error" in res) return res.error;
|
|
@@ -3162,17 +3185,18 @@ function registerFileRoutes(api, adapter) {
|
|
|
3162
3185
|
});
|
|
3163
3186
|
if ("error" in res) return res.error;
|
|
3164
3187
|
const html = readFileSync3(res.absPath, "utf-8");
|
|
3188
|
+
const etag = `${fileContentVersion(html).slice(0, -1)}:gsap-animations:v1"`;
|
|
3189
|
+
const headers = { ETag: etag, "Cache-Control": "no-cache" };
|
|
3190
|
+
if (c.req.header("If-None-Match") === etag) return new Response(null, { status: 304, headers });
|
|
3165
3191
|
const block = extractGsapScriptBlock(html);
|
|
3166
3192
|
if (!block) {
|
|
3167
|
-
return c.json(
|
|
3168
|
-
animations: [],
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
});
|
|
3193
|
+
return c.json(
|
|
3194
|
+
{ animations: [], timelineVar: "tl", preamble: "", postamble: "" },
|
|
3195
|
+
200,
|
|
3196
|
+
headers
|
|
3197
|
+
);
|
|
3173
3198
|
}
|
|
3174
|
-
|
|
3175
|
-
return c.json(parsed);
|
|
3199
|
+
return c.json(parseGsapScriptAcorn(block.scriptText), 200, headers);
|
|
3176
3200
|
});
|
|
3177
3201
|
api.get("/projects/:id/gsap-mutation-capabilities", async (c) => {
|
|
3178
3202
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
@@ -3349,6 +3373,9 @@ function tagRootCompositionFile(bodyHtml, compPath) {
|
|
|
3349
3373
|
if (bodyHtml.slice(match.index, tagEnd).includes("data-composition-file")) return bodyHtml;
|
|
3350
3374
|
return bodyHtml.slice(0, tagEnd) + ` data-composition-file="${compPath}"` + bodyHtml.slice(tagEnd);
|
|
3351
3375
|
}
|
|
3376
|
+
function rootHeadContent(rootHtml) {
|
|
3377
|
+
return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
|
|
3378
|
+
}
|
|
3352
3379
|
function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
|
|
3353
3380
|
const compFile = join6(projectDir, compPath);
|
|
3354
3381
|
if (!existsSync4(compFile)) return null;
|
|
@@ -3384,11 +3411,7 @@ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, raw
|
|
|
3384
3411
|
rewrittenContent = tagRootCompositionFile(rewrittenContent, compPath);
|
|
3385
3412
|
const indexPath = join6(projectDir, "index.html");
|
|
3386
3413
|
let headContent = "";
|
|
3387
|
-
if (existsSync4(indexPath))
|
|
3388
|
-
const indexHtml = readFileSync4(indexPath, "utf-8");
|
|
3389
|
-
const headMatch = indexHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
|
3390
|
-
headContent = headMatch?.[1] ?? "";
|
|
3391
|
-
}
|
|
3414
|
+
if (existsSync4(indexPath)) headContent = rootHeadContent(readFileSync4(indexPath, "utf-8"));
|
|
3392
3415
|
if (baseHref && !hasBaseElement(headContent)) {
|
|
3393
3416
|
headContent = `<base href="${baseHref}">
|
|
3394
3417
|
${headContent}`;
|
|
@@ -4544,10 +4567,11 @@ import {
|
|
|
4544
4567
|
writeFileSync as writeFileSync4
|
|
4545
4568
|
} from "fs";
|
|
4546
4569
|
import { join as join11 } from "path";
|
|
4547
|
-
import { createHash as
|
|
4570
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
4548
4571
|
|
|
4549
4572
|
// src/helpers/compositionInputs.ts
|
|
4550
4573
|
import { readFileSync as readFileSync8 } from "fs";
|
|
4574
|
+
import { createHash as createHash3 } from "crypto";
|
|
4551
4575
|
var ROOT_COMPOSITION = "index.html";
|
|
4552
4576
|
var COMPOSITION_SRC = /\bdata-composition-src\s*=\s*(["'])(.*?)\1/g;
|
|
4553
4577
|
function projectReader(projectDir) {
|
|
@@ -4584,27 +4608,51 @@ function closureOf(read, compPath) {
|
|
|
4584
4608
|
return closure;
|
|
4585
4609
|
}
|
|
4586
4610
|
var inputSignatures = /* @__PURE__ */ new Map();
|
|
4611
|
+
var rootMemos = /* @__PURE__ */ new Map();
|
|
4612
|
+
var sha1 = (text2) => createHash3("sha1").update(text2).digest("hex");
|
|
4613
|
+
function rootHashes(read) {
|
|
4614
|
+
const source = read(ROOT_COMPOSITION) ?? "";
|
|
4615
|
+
return { root: sha1(source), head: sha1(rootHeadContent(source)) };
|
|
4616
|
+
}
|
|
4587
4617
|
function compositionInputSignature(projectDir, compPath, projectSignature) {
|
|
4588
4618
|
const key = `${projectDir}\0${compPath}`;
|
|
4589
4619
|
const known = inputSignatures.get(key);
|
|
4590
4620
|
if (known?.projectSignature === projectSignature) return known.inputSignature;
|
|
4591
4621
|
const read = projectReader(projectDir);
|
|
4592
|
-
const
|
|
4593
|
-
const
|
|
4594
|
-
const
|
|
4622
|
+
const comp = normalizeSource(compPath) ?? compPath;
|
|
4623
|
+
const inputs = closureOf(read, comp).add(ROOT_COMPOSITION);
|
|
4624
|
+
const excluded = new Set(
|
|
4625
|
+
[...closureOf(read, ROOT_COMPOSITION)].filter((path) => !inputs.has(path))
|
|
4626
|
+
);
|
|
4627
|
+
let inputSignature;
|
|
4628
|
+
if (comp === ROOT_COMPOSITION) {
|
|
4629
|
+
inputSignature = createProjectSignature(projectDir, excluded);
|
|
4630
|
+
} else {
|
|
4631
|
+
const hashes = rootHashes(read);
|
|
4632
|
+
if (!rootMemos.has(projectDir)) rootMemos.set(projectDir, { ...hashes, affected: null });
|
|
4633
|
+
inputSignature = `${createProjectSignature(projectDir, excluded.add(ROOT_COMPOSITION))}:${hashes.head}`;
|
|
4634
|
+
}
|
|
4595
4635
|
inputSignatures.set(key, { projectSignature, inputSignature });
|
|
4596
4636
|
return inputSignature;
|
|
4597
4637
|
}
|
|
4598
4638
|
function compositionsAffectedBy(projectDir, changedPath) {
|
|
4599
4639
|
const changed = changedPath.replace(/\\/g, "/");
|
|
4600
4640
|
const read = projectReader(projectDir);
|
|
4641
|
+
if (changed === ROOT_COMPOSITION) {
|
|
4642
|
+
const now = rootHashes(read);
|
|
4643
|
+
const memo = rootMemos.get(projectDir);
|
|
4644
|
+
if (memo?.root === now.root) return memo.affected;
|
|
4645
|
+
const affected = memo?.head === now.head ? [ROOT_COMPOSITION] : null;
|
|
4646
|
+
rootMemos.set(projectDir, { ...now, affected });
|
|
4647
|
+
return affected;
|
|
4648
|
+
}
|
|
4601
4649
|
const mounted = closureOf(read, ROOT_COMPOSITION);
|
|
4602
|
-
if (
|
|
4650
|
+
if (!mounted.has(changed)) return null;
|
|
4603
4651
|
return [...mounted].filter((path) => closureOf(read, path).has(changed));
|
|
4604
4652
|
}
|
|
4605
4653
|
|
|
4606
4654
|
// src/routes/thumbnail.ts
|
|
4607
|
-
var THUMBNAIL_CACHE_VERSION = "
|
|
4655
|
+
var THUMBNAIL_CACHE_VERSION = "v5";
|
|
4608
4656
|
var THUMBNAIL_MAX_OUTPUT_WIDTH = 240;
|
|
4609
4657
|
var THUMBNAIL_MAX_OUTPUT_HEIGHT = 135;
|
|
4610
4658
|
var THUMBNAIL_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
@@ -4677,7 +4725,7 @@ function readFileOnce(file) {
|
|
|
4677
4725
|
function manifestKey(file) {
|
|
4678
4726
|
const manifest = readFileOnce(file);
|
|
4679
4727
|
if (typeof manifest === "string") return { key: "", mtimeMs: -Infinity };
|
|
4680
|
-
const hash =
|
|
4728
|
+
const hash = createHash4("sha1").update(manifest.data).digest("hex").slice(0, 16);
|
|
4681
4729
|
return { key: `_${hash}`, mtimeMs: Math.round(manifest.mtimeMs) };
|
|
4682
4730
|
}
|
|
4683
4731
|
function registerThumbnailRoutes(api, adapter) {
|
|
@@ -4714,7 +4762,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4714
4762
|
let sourceKey = "";
|
|
4715
4763
|
if (source !== "missing") {
|
|
4716
4764
|
const html = source.data.toString("utf-8");
|
|
4717
|
-
sourceKey = `_${
|
|
4765
|
+
sourceKey = `_${createHash4("sha1").update(html).digest("hex").slice(0, 16)}`;
|
|
4718
4766
|
sourceMtime = Math.round(source.mtimeMs);
|
|
4719
4767
|
if (!vpWidth) {
|
|
4720
4768
|
const wMatch = html.match(/data-width=["'](\d+)["']/);
|
|
@@ -4730,9 +4778,9 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4730
4778
|
const previewPath = compPath === "index.html" ? `${projectUrl}/preview` : `${projectUrl}/preview/comp/${compPath.split("/").map(encodeURIComponent).join("/")}`;
|
|
4731
4779
|
const previewUrl = `${previewPath}?${PREVIEW_CAPTURE_PARAM}=1`;
|
|
4732
4780
|
const cacheDir = join11(project.dir, ".thumbnails");
|
|
4733
|
-
const selectorKey = selector ? `_${selector.
|
|
4781
|
+
const selectorKey = selector ? `_${createHash4("sha1").update(selector).digest("hex").slice(0, 16)}_${selectorIndex ?? 0}` : "";
|
|
4734
4782
|
const urlVersionKey = urlVersion ? `_${urlVersion.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32)}` : "";
|
|
4735
|
-
const inputSignatureKey = `_${
|
|
4783
|
+
const inputSignatureKey = `_${createHash4("sha1").update(inputSignature).digest("hex").slice(0, 16)}`;
|
|
4736
4784
|
const outputScale = outputMode === "source" ? 1 : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH);
|
|
4737
4785
|
const outputWidth = Math.max(1, Math.round(compW * outputScale));
|
|
4738
4786
|
const outputHeight = Math.max(1, Math.round(compH * outputScale));
|
|
@@ -4852,7 +4900,7 @@ import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries"
|
|
|
4852
4900
|
import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
|
|
4853
4901
|
|
|
4854
4902
|
// src/helpers/freezeFrame.ts
|
|
4855
|
-
import { createHash as
|
|
4903
|
+
import { createHash as createHash5, randomBytes } from "crypto";
|
|
4856
4904
|
import { ensureHfIds as ensureHfIds3 } from "@hyperframes/parsers/hf-ids";
|
|
4857
4905
|
import { readMediaOffsetSeconds } from "@hyperframes/parsers/media-duration";
|
|
4858
4906
|
import { resolveRateSpec, sourceTimeAt } from "@hyperframes/core/speed-ramp";
|
|
@@ -4879,7 +4927,7 @@ function freezeExtractArgs(src, mediaTime, output) {
|
|
|
4879
4927
|
var randomStillToken = () => randomBytes(4).toString("hex");
|
|
4880
4928
|
function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
|
|
4881
4929
|
const stem = clipId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 48) || "clip";
|
|
4882
|
-
const idHash =
|
|
4930
|
+
const idHash = createHash5("sha256").update(clipId).digest("hex").slice(0, 10);
|
|
4883
4931
|
return `${stem}-${idHash}-${Math.round(playhead * 1e3)}-${token}.png`;
|
|
4884
4932
|
}
|
|
4885
4933
|
function readFreezeSource(html, target, playhead) {
|
|
@@ -6087,13 +6135,13 @@ function numericAttribute(element, name) {
|
|
|
6087
6135
|
}
|
|
6088
6136
|
|
|
6089
6137
|
// src/helpers/previewDocumentStore.ts
|
|
6090
|
-
import { createHash as
|
|
6138
|
+
import { createHash as createHash6 } from "crypto";
|
|
6091
6139
|
import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
|
|
6092
6140
|
import { join as join17 } from "path";
|
|
6093
6141
|
import { replaceFileAtomically as replaceFileAtomically4 } from "@hyperframes/core/atomic-file";
|
|
6094
6142
|
function createPreviewDocumentStore(projectDir, salt) {
|
|
6095
6143
|
const dir = join17(projectDir, ".hyperframes", "preview");
|
|
6096
|
-
const fileFor = (key) => join17(dir, `${
|
|
6144
|
+
const fileFor = (key) => join17(dir, `${createHash6("sha256").update(`${salt}
|
|
6097
6145
|
${key}`).digest("hex").slice(0, 32)}.html`);
|
|
6098
6146
|
return {
|
|
6099
6147
|
read(key) {
|
|
@@ -6203,6 +6251,7 @@ export {
|
|
|
6203
6251
|
applyFileMutations,
|
|
6204
6252
|
buildSubCompositionHtml,
|
|
6205
6253
|
cleanupProxyCache,
|
|
6254
|
+
clearElementScreenshotIsolation,
|
|
6206
6255
|
compositionsAffectedBy,
|
|
6207
6256
|
consumeFileWriteReceipt,
|
|
6208
6257
|
createBackgroundRemovalJob,
|