@hyperframes/studio-server 0.8.114 → 0.8.116
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 +102 -57
- 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,
|
|
@@ -588,6 +591,7 @@ import {
|
|
|
588
591
|
addMotionPathToScript,
|
|
589
592
|
removeArcPathFromScript,
|
|
590
593
|
addAnimationWithKeyframesToScript,
|
|
594
|
+
replaceTweenWithKeyframesInScript,
|
|
591
595
|
splitAnimationsInScript,
|
|
592
596
|
splitIntoPropertyGroupsFromScript,
|
|
593
597
|
shiftPositionsInScript,
|
|
@@ -1459,6 +1463,21 @@ function bakeVisibilityOnDelete(document, anim) {
|
|
|
1459
1463
|
} catch {
|
|
1460
1464
|
}
|
|
1461
1465
|
}
|
|
1466
|
+
function replaceInPlaceUnlessSetOrUnknown(scriptText, body) {
|
|
1467
|
+
const edit = { ...body, easeEach: resolveReplacementEaseEach(scriptText, body) };
|
|
1468
|
+
const inPlace = replaceTweenWithKeyframesInScript(scriptText, body.animationId, edit);
|
|
1469
|
+
if (inPlace !== null) return inPlace;
|
|
1470
|
+
const script = removeAnimationFromScript(scriptText, body.animationId);
|
|
1471
|
+
return addAnimationWithKeyframesToScript(
|
|
1472
|
+
script,
|
|
1473
|
+
body.targetSelector,
|
|
1474
|
+
body.position,
|
|
1475
|
+
body.duration,
|
|
1476
|
+
body.keyframes,
|
|
1477
|
+
body.ease,
|
|
1478
|
+
edit.easeEach
|
|
1479
|
+
).script;
|
|
1480
|
+
}
|
|
1462
1481
|
function resolveReplacementEaseEach(scriptText, request) {
|
|
1463
1482
|
if (request.easeEach !== void 0) return request.easeEach;
|
|
1464
1483
|
const original = parseGsapScriptAcorn(scriptText).animations.find(
|
|
@@ -1578,7 +1597,9 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
1578
1597
|
} catch (error) {
|
|
1579
1598
|
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
1580
1599
|
}
|
|
1600
|
+
const mutationChanges = [];
|
|
1581
1601
|
for (const mutation of mutations) {
|
|
1602
|
+
const previousScript = block.scriptText;
|
|
1582
1603
|
const result = await executeGsapMutation(mutation, block, respond, writer);
|
|
1583
1604
|
if (result instanceof Response) return result;
|
|
1584
1605
|
let newScript = typeof result === "string" ? result : result.script;
|
|
@@ -1588,6 +1609,7 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
1588
1609
|
if (HOLD_SYNC_MUTATION_TYPES.has(mutation.type)) {
|
|
1589
1610
|
newScript = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(newScript) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(newScript);
|
|
1590
1611
|
}
|
|
1612
|
+
mutationChanges.push(newScript !== previousScript);
|
|
1591
1613
|
block.scriptText = newScript;
|
|
1592
1614
|
}
|
|
1593
1615
|
const changed = block.scriptText !== initialScript;
|
|
@@ -1612,6 +1634,7 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
1612
1634
|
ok: true,
|
|
1613
1635
|
changed,
|
|
1614
1636
|
mutated: changed,
|
|
1637
|
+
mutationChanges,
|
|
1615
1638
|
parsed: parseGsapScriptAcorn(block.scriptText),
|
|
1616
1639
|
before: beforeHtml,
|
|
1617
1640
|
after: newHtml,
|
|
@@ -1861,17 +1884,7 @@ function executeGsapMutationAcorn(body, block, respond) {
|
|
|
1861
1884
|
if (keyframesWritePosition(body.keyframes) || keyframesWriteRotation(body.keyframes)) {
|
|
1862
1885
|
stripStudioEditsFromTarget(block.document, body.targetSelector);
|
|
1863
1886
|
}
|
|
1864
|
-
|
|
1865
|
-
const added = addAnimationWithKeyframesToScript(
|
|
1866
|
-
script,
|
|
1867
|
-
body.targetSelector,
|
|
1868
|
-
body.position,
|
|
1869
|
-
body.duration,
|
|
1870
|
-
body.keyframes,
|
|
1871
|
-
body.ease,
|
|
1872
|
-
resolveReplacementEaseEach(block.scriptText, body)
|
|
1873
|
-
);
|
|
1874
|
-
return added.script;
|
|
1887
|
+
return replaceInPlaceUnlessSetOrUnknown(block.scriptText, body);
|
|
1875
1888
|
}
|
|
1876
1889
|
case "split-animations": {
|
|
1877
1890
|
if (typeof body.originalId !== "string" || !body.originalId || typeof body.newId !== "string" || !body.newId || typeof body.splitTime !== "number" || !Number.isFinite(body.splitTime) || typeof body.elementStart !== "number" || !Number.isFinite(body.elementStart) || typeof body.elementDuration !== "number" || !Number.isFinite(body.elementDuration) || body.elementDuration <= 0) {
|
|
@@ -2193,17 +2206,7 @@ async function executeGsapMutationRecast(body, block, respond) {
|
|
|
2193
2206
|
if (keyframesWritePosition(body.keyframes) || keyframesWriteRotation(body.keyframes)) {
|
|
2194
2207
|
stripStudioEditsFromTarget(block.document, body.targetSelector);
|
|
2195
2208
|
}
|
|
2196
|
-
|
|
2197
|
-
const added = addAnimationWithKeyframesToScript2(
|
|
2198
|
-
script,
|
|
2199
|
-
body.targetSelector,
|
|
2200
|
-
body.position,
|
|
2201
|
-
body.duration,
|
|
2202
|
-
body.keyframes,
|
|
2203
|
-
body.ease,
|
|
2204
|
-
resolveReplacementEaseEach(block.scriptText, body)
|
|
2205
|
-
);
|
|
2206
|
-
return added.script;
|
|
2209
|
+
return replaceInPlaceUnlessSetOrUnknown(block.scriptText, body);
|
|
2207
2210
|
}
|
|
2208
2211
|
case "split-animations": {
|
|
2209
2212
|
if (typeof body.originalId !== "string" || !body.originalId || typeof body.newId !== "string" || !body.newId || typeof body.splitTime !== "number" || !Number.isFinite(body.splitTime) || typeof body.elementStart !== "number" || !Number.isFinite(body.elementStart) || typeof body.elementDuration !== "number" || !Number.isFinite(body.elementDuration) || body.elementDuration <= 0) {
|
|
@@ -2671,11 +2674,14 @@ function registerFileRoutes(api, adapter) {
|
|
|
2671
2674
|
return c.json({ error: "targets required" }, 400);
|
|
2672
2675
|
}
|
|
2673
2676
|
const originalContent = readFileSync3(ctx.absPath, "utf-8");
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2677
|
+
return writeIfChanged(
|
|
2678
|
+
c,
|
|
2679
|
+
ctx.project.dir,
|
|
2680
|
+
ctx.filePath,
|
|
2681
|
+
ctx.absPath,
|
|
2682
|
+
originalContent,
|
|
2683
|
+
removeElementsFromHtml(originalContent, targets)
|
|
2684
|
+
);
|
|
2679
2685
|
});
|
|
2680
2686
|
api.post("/projects/:id/file-mutations/split-batch", async (c) => {
|
|
2681
2687
|
const body = await c.req.json().catch(() => null);
|
|
@@ -3079,6 +3085,19 @@ function registerFileRoutes(api, adapter) {
|
|
|
3079
3085
|
const exists = probeElementInSource(content, parsed.target);
|
|
3080
3086
|
return c.json({ exists });
|
|
3081
3087
|
});
|
|
3088
|
+
api.post("/projects/:id/file-mutations/probe-elements/*", async (c) => {
|
|
3089
|
+
const ctx = await resolveFileMutationContext(c, adapter, "probe-elements");
|
|
3090
|
+
if ("error" in ctx) return ctx.error;
|
|
3091
|
+
const body = await c.req.json().catch(() => null);
|
|
3092
|
+
if (!Array.isArray(body?.targets)) return c.json({ error: "targets required" }, 400);
|
|
3093
|
+
let content;
|
|
3094
|
+
try {
|
|
3095
|
+
content = readFileSync3(ctx.absPath, "utf-8");
|
|
3096
|
+
} catch {
|
|
3097
|
+
return c.json({ exists: body.targets.map(() => false) });
|
|
3098
|
+
}
|
|
3099
|
+
return c.json({ exists: probeElementsInSource(content, body.targets) });
|
|
3100
|
+
});
|
|
3082
3101
|
api.patch("/projects/:id/files/*", async (c) => {
|
|
3083
3102
|
const res = await resolveProjectFile(c, adapter, { mustExist: true });
|
|
3084
3103
|
if ("error" in res) return res.error;
|
|
@@ -3162,17 +3181,18 @@ function registerFileRoutes(api, adapter) {
|
|
|
3162
3181
|
});
|
|
3163
3182
|
if ("error" in res) return res.error;
|
|
3164
3183
|
const html = readFileSync3(res.absPath, "utf-8");
|
|
3184
|
+
const etag = `${fileContentVersion(html).slice(0, -1)}:gsap-animations:v1"`;
|
|
3185
|
+
const headers = { ETag: etag, "Cache-Control": "no-cache" };
|
|
3186
|
+
if (c.req.header("If-None-Match") === etag) return new Response(null, { status: 304, headers });
|
|
3165
3187
|
const block = extractGsapScriptBlock(html);
|
|
3166
3188
|
if (!block) {
|
|
3167
|
-
return c.json(
|
|
3168
|
-
animations: [],
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
});
|
|
3189
|
+
return c.json(
|
|
3190
|
+
{ animations: [], timelineVar: "tl", preamble: "", postamble: "" },
|
|
3191
|
+
200,
|
|
3192
|
+
headers
|
|
3193
|
+
);
|
|
3173
3194
|
}
|
|
3174
|
-
|
|
3175
|
-
return c.json(parsed);
|
|
3195
|
+
return c.json(parseGsapScriptAcorn(block.scriptText), 200, headers);
|
|
3176
3196
|
});
|
|
3177
3197
|
api.get("/projects/:id/gsap-mutation-capabilities", async (c) => {
|
|
3178
3198
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
@@ -3349,6 +3369,9 @@ function tagRootCompositionFile(bodyHtml, compPath) {
|
|
|
3349
3369
|
if (bodyHtml.slice(match.index, tagEnd).includes("data-composition-file")) return bodyHtml;
|
|
3350
3370
|
return bodyHtml.slice(0, tagEnd) + ` data-composition-file="${compPath}"` + bodyHtml.slice(tagEnd);
|
|
3351
3371
|
}
|
|
3372
|
+
function rootHeadContent(rootHtml) {
|
|
3373
|
+
return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
|
|
3374
|
+
}
|
|
3352
3375
|
function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
|
|
3353
3376
|
const compFile = join6(projectDir, compPath);
|
|
3354
3377
|
if (!existsSync4(compFile)) return null;
|
|
@@ -3384,11 +3407,7 @@ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, raw
|
|
|
3384
3407
|
rewrittenContent = tagRootCompositionFile(rewrittenContent, compPath);
|
|
3385
3408
|
const indexPath = join6(projectDir, "index.html");
|
|
3386
3409
|
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
|
-
}
|
|
3410
|
+
if (existsSync4(indexPath)) headContent = rootHeadContent(readFileSync4(indexPath, "utf-8"));
|
|
3392
3411
|
if (baseHref && !hasBaseElement(headContent)) {
|
|
3393
3412
|
headContent = `<base href="${baseHref}">
|
|
3394
3413
|
${headContent}`;
|
|
@@ -4544,10 +4563,11 @@ import {
|
|
|
4544
4563
|
writeFileSync as writeFileSync4
|
|
4545
4564
|
} from "fs";
|
|
4546
4565
|
import { join as join11 } from "path";
|
|
4547
|
-
import { createHash as
|
|
4566
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
4548
4567
|
|
|
4549
4568
|
// src/helpers/compositionInputs.ts
|
|
4550
4569
|
import { readFileSync as readFileSync8 } from "fs";
|
|
4570
|
+
import { createHash as createHash3 } from "crypto";
|
|
4551
4571
|
var ROOT_COMPOSITION = "index.html";
|
|
4552
4572
|
var COMPOSITION_SRC = /\bdata-composition-src\s*=\s*(["'])(.*?)\1/g;
|
|
4553
4573
|
function projectReader(projectDir) {
|
|
@@ -4584,27 +4604,51 @@ function closureOf(read, compPath) {
|
|
|
4584
4604
|
return closure;
|
|
4585
4605
|
}
|
|
4586
4606
|
var inputSignatures = /* @__PURE__ */ new Map();
|
|
4607
|
+
var rootMemos = /* @__PURE__ */ new Map();
|
|
4608
|
+
var sha1 = (text2) => createHash3("sha1").update(text2).digest("hex");
|
|
4609
|
+
function rootHashes(read) {
|
|
4610
|
+
const source = read(ROOT_COMPOSITION) ?? "";
|
|
4611
|
+
return { root: sha1(source), head: sha1(rootHeadContent(source)) };
|
|
4612
|
+
}
|
|
4587
4613
|
function compositionInputSignature(projectDir, compPath, projectSignature) {
|
|
4588
4614
|
const key = `${projectDir}\0${compPath}`;
|
|
4589
4615
|
const known = inputSignatures.get(key);
|
|
4590
4616
|
if (known?.projectSignature === projectSignature) return known.inputSignature;
|
|
4591
4617
|
const read = projectReader(projectDir);
|
|
4592
|
-
const
|
|
4593
|
-
const
|
|
4594
|
-
const
|
|
4618
|
+
const comp = normalizeSource(compPath) ?? compPath;
|
|
4619
|
+
const inputs = closureOf(read, comp).add(ROOT_COMPOSITION);
|
|
4620
|
+
const excluded = new Set(
|
|
4621
|
+
[...closureOf(read, ROOT_COMPOSITION)].filter((path) => !inputs.has(path))
|
|
4622
|
+
);
|
|
4623
|
+
let inputSignature;
|
|
4624
|
+
if (comp === ROOT_COMPOSITION) {
|
|
4625
|
+
inputSignature = createProjectSignature(projectDir, excluded);
|
|
4626
|
+
} else {
|
|
4627
|
+
const hashes = rootHashes(read);
|
|
4628
|
+
if (!rootMemos.has(projectDir)) rootMemos.set(projectDir, { ...hashes, affected: null });
|
|
4629
|
+
inputSignature = `${createProjectSignature(projectDir, excluded.add(ROOT_COMPOSITION))}:${hashes.head}`;
|
|
4630
|
+
}
|
|
4595
4631
|
inputSignatures.set(key, { projectSignature, inputSignature });
|
|
4596
4632
|
return inputSignature;
|
|
4597
4633
|
}
|
|
4598
4634
|
function compositionsAffectedBy(projectDir, changedPath) {
|
|
4599
4635
|
const changed = changedPath.replace(/\\/g, "/");
|
|
4600
4636
|
const read = projectReader(projectDir);
|
|
4637
|
+
if (changed === ROOT_COMPOSITION) {
|
|
4638
|
+
const now = rootHashes(read);
|
|
4639
|
+
const memo = rootMemos.get(projectDir);
|
|
4640
|
+
if (memo?.root === now.root) return memo.affected;
|
|
4641
|
+
const affected = memo?.head === now.head ? [ROOT_COMPOSITION] : null;
|
|
4642
|
+
rootMemos.set(projectDir, { ...now, affected });
|
|
4643
|
+
return affected;
|
|
4644
|
+
}
|
|
4601
4645
|
const mounted = closureOf(read, ROOT_COMPOSITION);
|
|
4602
|
-
if (
|
|
4646
|
+
if (!mounted.has(changed)) return null;
|
|
4603
4647
|
return [...mounted].filter((path) => closureOf(read, path).has(changed));
|
|
4604
4648
|
}
|
|
4605
4649
|
|
|
4606
4650
|
// src/routes/thumbnail.ts
|
|
4607
|
-
var THUMBNAIL_CACHE_VERSION = "
|
|
4651
|
+
var THUMBNAIL_CACHE_VERSION = "v5";
|
|
4608
4652
|
var THUMBNAIL_MAX_OUTPUT_WIDTH = 240;
|
|
4609
4653
|
var THUMBNAIL_MAX_OUTPUT_HEIGHT = 135;
|
|
4610
4654
|
var THUMBNAIL_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
@@ -4677,7 +4721,7 @@ function readFileOnce(file) {
|
|
|
4677
4721
|
function manifestKey(file) {
|
|
4678
4722
|
const manifest = readFileOnce(file);
|
|
4679
4723
|
if (typeof manifest === "string") return { key: "", mtimeMs: -Infinity };
|
|
4680
|
-
const hash =
|
|
4724
|
+
const hash = createHash4("sha1").update(manifest.data).digest("hex").slice(0, 16);
|
|
4681
4725
|
return { key: `_${hash}`, mtimeMs: Math.round(manifest.mtimeMs) };
|
|
4682
4726
|
}
|
|
4683
4727
|
function registerThumbnailRoutes(api, adapter) {
|
|
@@ -4714,7 +4758,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4714
4758
|
let sourceKey = "";
|
|
4715
4759
|
if (source !== "missing") {
|
|
4716
4760
|
const html = source.data.toString("utf-8");
|
|
4717
|
-
sourceKey = `_${
|
|
4761
|
+
sourceKey = `_${createHash4("sha1").update(html).digest("hex").slice(0, 16)}`;
|
|
4718
4762
|
sourceMtime = Math.round(source.mtimeMs);
|
|
4719
4763
|
if (!vpWidth) {
|
|
4720
4764
|
const wMatch = html.match(/data-width=["'](\d+)["']/);
|
|
@@ -4730,9 +4774,9 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4730
4774
|
const previewPath = compPath === "index.html" ? `${projectUrl}/preview` : `${projectUrl}/preview/comp/${compPath.split("/").map(encodeURIComponent).join("/")}`;
|
|
4731
4775
|
const previewUrl = `${previewPath}?${PREVIEW_CAPTURE_PARAM}=1`;
|
|
4732
4776
|
const cacheDir = join11(project.dir, ".thumbnails");
|
|
4733
|
-
const selectorKey = selector ? `_${selector.
|
|
4777
|
+
const selectorKey = selector ? `_${createHash4("sha1").update(selector).digest("hex").slice(0, 16)}_${selectorIndex ?? 0}` : "";
|
|
4734
4778
|
const urlVersionKey = urlVersion ? `_${urlVersion.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32)}` : "";
|
|
4735
|
-
const inputSignatureKey = `_${
|
|
4779
|
+
const inputSignatureKey = `_${createHash4("sha1").update(inputSignature).digest("hex").slice(0, 16)}`;
|
|
4736
4780
|
const outputScale = outputMode === "source" ? 1 : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH);
|
|
4737
4781
|
const outputWidth = Math.max(1, Math.round(compW * outputScale));
|
|
4738
4782
|
const outputHeight = Math.max(1, Math.round(compH * outputScale));
|
|
@@ -4852,7 +4896,7 @@ import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries"
|
|
|
4852
4896
|
import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
|
|
4853
4897
|
|
|
4854
4898
|
// src/helpers/freezeFrame.ts
|
|
4855
|
-
import { createHash as
|
|
4899
|
+
import { createHash as createHash5, randomBytes } from "crypto";
|
|
4856
4900
|
import { ensureHfIds as ensureHfIds3 } from "@hyperframes/parsers/hf-ids";
|
|
4857
4901
|
import { readMediaOffsetSeconds } from "@hyperframes/parsers/media-duration";
|
|
4858
4902
|
import { resolveRateSpec, sourceTimeAt } from "@hyperframes/core/speed-ramp";
|
|
@@ -4879,7 +4923,7 @@ function freezeExtractArgs(src, mediaTime, output) {
|
|
|
4879
4923
|
var randomStillToken = () => randomBytes(4).toString("hex");
|
|
4880
4924
|
function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
|
|
4881
4925
|
const stem = clipId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 48) || "clip";
|
|
4882
|
-
const idHash =
|
|
4926
|
+
const idHash = createHash5("sha256").update(clipId).digest("hex").slice(0, 10);
|
|
4883
4927
|
return `${stem}-${idHash}-${Math.round(playhead * 1e3)}-${token}.png`;
|
|
4884
4928
|
}
|
|
4885
4929
|
function readFreezeSource(html, target, playhead) {
|
|
@@ -6087,13 +6131,13 @@ function numericAttribute(element, name) {
|
|
|
6087
6131
|
}
|
|
6088
6132
|
|
|
6089
6133
|
// src/helpers/previewDocumentStore.ts
|
|
6090
|
-
import { createHash as
|
|
6134
|
+
import { createHash as createHash6 } from "crypto";
|
|
6091
6135
|
import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
|
|
6092
6136
|
import { join as join17 } from "path";
|
|
6093
6137
|
import { replaceFileAtomically as replaceFileAtomically4 } from "@hyperframes/core/atomic-file";
|
|
6094
6138
|
function createPreviewDocumentStore(projectDir, salt) {
|
|
6095
6139
|
const dir = join17(projectDir, ".hyperframes", "preview");
|
|
6096
|
-
const fileFor = (key) => join17(dir, `${
|
|
6140
|
+
const fileFor = (key) => join17(dir, `${createHash6("sha256").update(`${salt}
|
|
6097
6141
|
${key}`).digest("hex").slice(0, 32)}.html`);
|
|
6098
6142
|
return {
|
|
6099
6143
|
read(key) {
|
|
@@ -6203,6 +6247,7 @@ export {
|
|
|
6203
6247
|
applyFileMutations,
|
|
6204
6248
|
buildSubCompositionHtml,
|
|
6205
6249
|
cleanupProxyCache,
|
|
6250
|
+
clearElementScreenshotIsolation,
|
|
6206
6251
|
compositionsAffectedBy,
|
|
6207
6252
|
consumeFileWriteReceipt,
|
|
6208
6253
|
createBackgroundRemovalJob,
|