@hyperframes/studio 0.8.19 → 0.8.21
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-CIXNA_bj.js +460 -0
- package/dist/assets/{index-Dd0fsIIL.js → index-44gfDvkh.js} +1 -1
- package/dist/assets/{index-DxccOrkZ.js → index-9VfE7bZe.js} +1 -1
- package/dist/assets/{index-Bjd2hioj.js → index-CgGY8dAv.js} +164 -164
- package/dist/index.d.ts +28 -0
- package/dist/index.html +1 -1
- package/dist/index.js +1206 -234
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +2 -0
- package/src/contexts/StudioContext.tsx +10 -0
- package/src/hooks/useEditorSave.test.tsx +81 -3
- package/src/hooks/useEditorSave.ts +31 -6
- package/src/hooks/useGsapInteractionFailureTelemetry.test.tsx +48 -16
- package/src/hooks/useGsapInteractionFailureTelemetry.ts +5 -2
- package/src/hooks/useStudioContextValue.ts +9 -0
- package/src/player/hooks/timelineSyncHydration.test.ts +96 -0
- package/src/player/hooks/timelineSyncHydration.ts +47 -1
- package/src/player/hooks/useExpandedTimelineElements.test.ts +43 -0
- package/src/player/hooks/useExpandedTimelineElements.ts +52 -29
- package/src/player/hooks/useTimelineSyncCallbacks.ts +4 -0
- package/src/player/store/playerStore.ts +17 -2
- package/src/player/store/timelineElement.ts +21 -0
- package/src/utils/studioFileVersion.test.ts +37 -1
- package/src/utils/studioFileVersion.ts +12 -2
- package/src/utils/studioSaveDiagnostics.test.ts +29 -0
- package/src/utils/studioSaveDiagnostics.ts +4 -0
- package/src/webmcp/StudioAgentTools.tsx +105 -10
- package/src/webmcp/toolResult.ts +1 -1
- package/src/webmcp/tools/animationTools.test.ts +244 -0
- package/src/webmcp/tools/animationTools.ts +276 -0
- package/src/webmcp/tools/contentTools.test.ts +275 -0
- package/src/webmcp/tools/contentTools.ts +217 -0
- package/src/webmcp/tools/frameTools.test.ts +136 -0
- package/src/webmcp/tools/frameTools.ts +133 -0
- package/src/webmcp/tools/inspectTools.test.ts +197 -0
- package/src/webmcp/tools/inspectTools.ts +208 -0
- package/src/webmcp/tools/selectionTools.test.ts +164 -0
- package/src/webmcp/tools/selectionTools.ts +154 -0
- package/src/webmcp/tools/transformTools.test.ts +179 -0
- package/src/webmcp/tools/transformTools.ts +205 -0
- package/src/webmcp/useStudioAgentTools.test.tsx +71 -22
- package/src/webmcp/useStudioAgentTools.ts +195 -1
- package/src/webmcp/webmcpTestUtils.ts +91 -0
- package/dist/assets/hyperframes-player-BA-QO9CQ.js +0 -459
package/dist/index.js
CHANGED
|
@@ -1618,7 +1618,8 @@ function createTimelineResetState() {
|
|
|
1618
1618
|
beatPersist: null,
|
|
1619
1619
|
clipManifest: null,
|
|
1620
1620
|
clipParentMap: /* @__PURE__ */ new Map(),
|
|
1621
|
-
domClipChildren: []
|
|
1621
|
+
domClipChildren: [],
|
|
1622
|
+
subCompositionHostState: /* @__PURE__ */ new Map()
|
|
1622
1623
|
};
|
|
1623
1624
|
}
|
|
1624
1625
|
var usePlayerStore = create2((set, get) => ({
|
|
@@ -1744,6 +1745,8 @@ var usePlayerStore = create2((set, get) => ({
|
|
|
1744
1745
|
setClipParentMap: (map) => set({ clipParentMap: map }),
|
|
1745
1746
|
domClipChildren: [],
|
|
1746
1747
|
setDomClipChildren: (children) => set({ domClipChildren: children }),
|
|
1748
|
+
subCompositionHostState: /* @__PURE__ */ new Map(),
|
|
1749
|
+
setSubCompositionHostState: (state) => set({ subCompositionHostState: state }),
|
|
1747
1750
|
setIsPlaying: (playing) => {
|
|
1748
1751
|
if (get().isPlaying === playing) return;
|
|
1749
1752
|
set({ isPlaying: playing });
|
|
@@ -4352,22 +4355,12 @@ function childGroupState(flat, domChild) {
|
|
|
4352
4355
|
audioGroupAutomation: source.audioGroupAutomation
|
|
4353
4356
|
};
|
|
4354
4357
|
}
|
|
4355
|
-
function hostElementState(flat) {
|
|
4356
|
-
if (!flat) return {};
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
// Same reason as the three above: these are read off the host element, which
|
|
4362
|
-
// an expanded child is built without. Missing them, an audio child inside a
|
|
4363
|
-
// sub-composition reserved no automation height and drew no lanes, while the
|
|
4364
|
-
// property panel — reading the live DOM selection rather than this row —
|
|
4365
|
-
// still showed the chain and its toggles.
|
|
4366
|
-
fxChain: flat.fxChain,
|
|
4367
|
-
automation: flat.automation
|
|
4368
|
-
};
|
|
4369
|
-
}
|
|
4370
|
-
function buildChildElements(siblings, display2, editBasis, expandedHostKey, elements, domChildrenById) {
|
|
4358
|
+
function hostElementState(flat, live) {
|
|
4359
|
+
if (!flat) return { ...live };
|
|
4360
|
+
const { hidden, timelineLocked, timelineRole, fxChain, automation } = flat;
|
|
4361
|
+
return { hidden, timelineLocked, timelineRole, fxChain, automation, ...live };
|
|
4362
|
+
}
|
|
4363
|
+
function buildChildElements(siblings, display2, editBasis, expandedHostKey, elements, domChildrenById, hostStateById) {
|
|
4371
4364
|
const result = [];
|
|
4372
4365
|
for (const child of siblings) {
|
|
4373
4366
|
const clamped = clampChildToParent(child, display2.start, display2.end);
|
|
@@ -4388,7 +4381,10 @@ function buildChildElements(siblings, display2, editBasis, expandedHostKey, elem
|
|
|
4388
4381
|
});
|
|
4389
4382
|
result.push({
|
|
4390
4383
|
...base2,
|
|
4391
|
-
...hostElementState(
|
|
4384
|
+
...hostElementState(
|
|
4385
|
+
elements.find((element) => element.key === key2),
|
|
4386
|
+
domId ? hostStateById.get(domId) : void 0
|
|
4387
|
+
),
|
|
4392
4388
|
...childGroupState(
|
|
4393
4389
|
elements.find((element) => element.key === key2),
|
|
4394
4390
|
domId ? domChildrenById.get(domId) : void 0
|
|
@@ -4448,7 +4444,7 @@ function domSiblingClips(domClipChildren, siblingParentId, host) {
|
|
|
4448
4444
|
})
|
|
4449
4445
|
);
|
|
4450
4446
|
}
|
|
4451
|
-
function buildExpandedElements(elements, manifest, parentMap, topLevelId, siblingParentId, domClipChildren = []) {
|
|
4447
|
+
function buildExpandedElements(elements, manifest, parentMap, topLevelId, siblingParentId, domClipChildren = [], subCompositionHostState = /* @__PURE__ */ new Map()) {
|
|
4452
4448
|
const topLevelElement = elements.find((el) => el.id === topLevelId || el.domId === topLevelId);
|
|
4453
4449
|
if (!topLevelElement) return filterToTopLevel(elements, parentMap);
|
|
4454
4450
|
const parentHost = manifest.find((c) => c.id === siblingParentId);
|
|
@@ -4476,7 +4472,8 @@ function buildExpandedElements(elements, manifest, parentMap, topLevelId, siblin
|
|
|
4476
4472
|
editBasis,
|
|
4477
4473
|
parentKey,
|
|
4478
4474
|
elements,
|
|
4479
|
-
domChildrenById
|
|
4475
|
+
domChildrenById,
|
|
4476
|
+
subCompositionHostState
|
|
4480
4477
|
);
|
|
4481
4478
|
if (expanded.length === 0) return filterToTopLevel(elements, parentMap);
|
|
4482
4479
|
const drillPath = /* @__PURE__ */ new Set();
|
|
@@ -4496,6 +4493,7 @@ function useExpandedTimelineElements() {
|
|
|
4496
4493
|
const clipManifest = usePlayerStore((s) => s.clipManifest);
|
|
4497
4494
|
const clipParentMap = usePlayerStore((s) => s.clipParentMap);
|
|
4498
4495
|
const domClipChildren = usePlayerStore((s) => s.domClipChildren);
|
|
4496
|
+
const subCompositionHostState = usePlayerStore((s) => s.subCompositionHostState);
|
|
4499
4497
|
const selectedElementId = usePlayerStore((s) => s.selectedElementId);
|
|
4500
4498
|
const currentTime = usePlayerStore((s) => s.currentTime);
|
|
4501
4499
|
const { rawId, selectedRawId } = useMemo3(() => {
|
|
@@ -4526,9 +4524,18 @@ function useExpandedTimelineElements() {
|
|
|
4526
4524
|
clipParentMap,
|
|
4527
4525
|
topLevel,
|
|
4528
4526
|
immediateParent,
|
|
4529
|
-
domClipChildren
|
|
4527
|
+
domClipChildren,
|
|
4528
|
+
subCompositionHostState
|
|
4530
4529
|
);
|
|
4531
|
-
}, [
|
|
4530
|
+
}, [
|
|
4531
|
+
elements,
|
|
4532
|
+
clipManifest,
|
|
4533
|
+
clipParentMap,
|
|
4534
|
+
domClipChildren,
|
|
4535
|
+
subCompositionHostState,
|
|
4536
|
+
rawId,
|
|
4537
|
+
selectedRawId
|
|
4538
|
+
]);
|
|
4532
4539
|
}
|
|
4533
4540
|
|
|
4534
4541
|
// src/player/components/timelineTheme.ts
|
|
@@ -16306,6 +16313,9 @@ function buildStudioSaveFailureProperties(input) {
|
|
|
16306
16313
|
function trackStudioSaveFailure(input) {
|
|
16307
16314
|
trackStudioEvent("save_failure", buildStudioSaveFailureProperties(input));
|
|
16308
16315
|
}
|
|
16316
|
+
function trackStudioEditBlocked(input) {
|
|
16317
|
+
trackStudioEvent("edit_blocked", buildStudioSaveFailureProperties(input));
|
|
16318
|
+
}
|
|
16309
16319
|
async function createStudioSaveHttpError(response, fallbackMessage, options = {}) {
|
|
16310
16320
|
let body = "";
|
|
16311
16321
|
try {
|
|
@@ -16693,7 +16703,16 @@ async function studioExpectedFileVersion(versions, path, expectedContent) {
|
|
|
16693
16703
|
return versions.get(path);
|
|
16694
16704
|
}
|
|
16695
16705
|
function createStudioWriteToken() {
|
|
16696
|
-
|
|
16706
|
+
const webCrypto = globalThis.crypto;
|
|
16707
|
+
if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
|
|
16708
|
+
if (typeof webCrypto?.getRandomValues !== "function") {
|
|
16709
|
+
throw new Error("Web Crypto getRandomValues is required for Studio write identity");
|
|
16710
|
+
}
|
|
16711
|
+
const bytes = webCrypto.getRandomValues(new Uint8Array(16));
|
|
16712
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
16713
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
16714
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
16715
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
16697
16716
|
}
|
|
16698
16717
|
function studioWriteHeaders() {
|
|
16699
16718
|
const token = createStudioWriteToken();
|
|
@@ -21192,6 +21211,7 @@ function StudioShellProvider({
|
|
|
21192
21211
|
showToast,
|
|
21193
21212
|
previewIframeRef,
|
|
21194
21213
|
editHistory,
|
|
21214
|
+
writeBlockedReason,
|
|
21195
21215
|
handleUndo,
|
|
21196
21216
|
handleRedo,
|
|
21197
21217
|
renderQueue,
|
|
@@ -21207,6 +21227,7 @@ function StudioShellProvider({
|
|
|
21207
21227
|
showToast,
|
|
21208
21228
|
previewIframeRef,
|
|
21209
21229
|
editHistory,
|
|
21230
|
+
writeBlockedReason,
|
|
21210
21231
|
handleUndo,
|
|
21211
21232
|
handleRedo,
|
|
21212
21233
|
renderQueue,
|
|
@@ -21223,6 +21244,7 @@ function StudioShellProvider({
|
|
|
21223
21244
|
setActiveCompPath,
|
|
21224
21245
|
showToast,
|
|
21225
21246
|
previewIframeRef,
|
|
21247
|
+
writeBlockedReason,
|
|
21226
21248
|
handleUndo,
|
|
21227
21249
|
handleRedo,
|
|
21228
21250
|
waitForPendingDomEditSaves,
|
|
@@ -24831,6 +24853,32 @@ function collectSubCompositionDomChildren(iframeDoc, clips, parentMap) {
|
|
|
24831
24853
|
}
|
|
24832
24854
|
return out;
|
|
24833
24855
|
}
|
|
24856
|
+
function readSubCompositionHostState(el) {
|
|
24857
|
+
const state = {};
|
|
24858
|
+
if (el.hasAttribute("data-hidden")) state.hidden = true;
|
|
24859
|
+
if (el.hasAttribute("data-timeline-locked")) state.timelineLocked = true;
|
|
24860
|
+
const timelineRole = el.getAttribute("data-timeline-role");
|
|
24861
|
+
if (timelineRole) state.timelineRole = timelineRole;
|
|
24862
|
+
const fxChain = el.getAttribute("data-fx-chain");
|
|
24863
|
+
if (fxChain) state.fxChain = fxChain;
|
|
24864
|
+
const automation = el.getAttribute("data-automation");
|
|
24865
|
+
if (automation) state.automation = automation;
|
|
24866
|
+
return Object.keys(state).length > 0 ? state : null;
|
|
24867
|
+
}
|
|
24868
|
+
function collectSubCompositionHostState(iframeDoc, clips) {
|
|
24869
|
+
const out = /* @__PURE__ */ new Map();
|
|
24870
|
+
if (!iframeDoc) return out;
|
|
24871
|
+
for (const clip of clips) {
|
|
24872
|
+
if (clip.kind !== "composition" || !clip.id) continue;
|
|
24873
|
+
const hostEl = iframeDoc.getElementById(clip.id);
|
|
24874
|
+
if (!hostEl) continue;
|
|
24875
|
+
for (const el of Array.from(hostEl.querySelectorAll("[id]"))) {
|
|
24876
|
+
const state = readSubCompositionHostState(el);
|
|
24877
|
+
if (state) out.set(el.id, state);
|
|
24878
|
+
}
|
|
24879
|
+
}
|
|
24880
|
+
return out;
|
|
24881
|
+
}
|
|
24834
24882
|
function safeContentDocument(iframe) {
|
|
24835
24883
|
try {
|
|
24836
24884
|
return iframe?.contentDocument ?? null;
|
|
@@ -24988,6 +25036,7 @@ function useTimelineSyncCallbacks({
|
|
|
24988
25036
|
const domClipChildren = collectSubCompositionDomChildren(iframeDoc, data.clips, parentMap);
|
|
24989
25037
|
usePlayerStore.getState().setClipParentMap(parentMap);
|
|
24990
25038
|
usePlayerStore.getState().setDomClipChildren(domClipChildren);
|
|
25039
|
+
usePlayerStore.getState().setSubCompositionHostState(collectSubCompositionHostState(iframeDoc, data.clips));
|
|
24991
25040
|
} catch {
|
|
24992
25041
|
}
|
|
24993
25042
|
const els = buildTimelineElementsFromClips(filtered, iframeDoc);
|
|
@@ -40572,7 +40621,7 @@ function useTimelineSelectionPreviewSync({
|
|
|
40572
40621
|
}
|
|
40573
40622
|
|
|
40574
40623
|
// src/webmcp/StudioAgentTools.tsx
|
|
40575
|
-
import { useCallback as useCallback60 } from "react";
|
|
40624
|
+
import { useCallback as useCallback60, useMemo as useMemo37 } from "react";
|
|
40576
40625
|
|
|
40577
40626
|
// src/webmcp/useStudioAgentTools.ts
|
|
40578
40627
|
import { useEffect as useEffect49, useRef as useRef68 } from "react";
|
|
@@ -40719,6 +40768,43 @@ function patchTargetAddress(target) {
|
|
|
40719
40768
|
selectorIndex: target.selectorIndex
|
|
40720
40769
|
};
|
|
40721
40770
|
}
|
|
40771
|
+
function parseElementHandle(handle) {
|
|
40772
|
+
const separatorAt = handle.indexOf(SEPARATOR);
|
|
40773
|
+
if (separatorAt <= 0) return null;
|
|
40774
|
+
const scheme = handle.slice(0, separatorAt);
|
|
40775
|
+
const rest = handle.slice(separatorAt + 1);
|
|
40776
|
+
if (!rest) return null;
|
|
40777
|
+
if (scheme === "hf" || scheme === "dom") return { scheme, value: rest, index: 0 };
|
|
40778
|
+
if (scheme !== "sel") return null;
|
|
40779
|
+
const indexAt = rest.lastIndexOf(INDEX_SEPARATOR);
|
|
40780
|
+
if (indexAt <= 0) return { scheme, value: rest, index: 0 };
|
|
40781
|
+
const index = Number(rest.slice(indexAt + 1));
|
|
40782
|
+
if (!Number.isInteger(index) || index < 0) return { scheme, value: rest, index: 0 };
|
|
40783
|
+
return { scheme, value: rest.slice(0, indexAt), index };
|
|
40784
|
+
}
|
|
40785
|
+
function resolveElementHandle(doc, handle) {
|
|
40786
|
+
const parsed = parseElementHandle(handle);
|
|
40787
|
+
if (!parsed) return null;
|
|
40788
|
+
if (parsed.scheme === "dom") return asHtmlElement(doc, doc.getElementById(parsed.value));
|
|
40789
|
+
if (parsed.scheme === "hf") {
|
|
40790
|
+
return asHtmlElement(doc, doc.querySelector(`[data-hf-id="${cssEscape(parsed.value)}"]`));
|
|
40791
|
+
}
|
|
40792
|
+
let matches;
|
|
40793
|
+
try {
|
|
40794
|
+
matches = doc.querySelectorAll(parsed.value);
|
|
40795
|
+
} catch {
|
|
40796
|
+
return null;
|
|
40797
|
+
}
|
|
40798
|
+
return asHtmlElement(doc, matches.item(parsed.index));
|
|
40799
|
+
}
|
|
40800
|
+
function cssEscape(value) {
|
|
40801
|
+
return typeof CSS?.escape === "function" ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
|
|
40802
|
+
}
|
|
40803
|
+
function asHtmlElement(doc, node) {
|
|
40804
|
+
if (!node) return null;
|
|
40805
|
+
const ctor = doc.defaultView?.HTMLElement;
|
|
40806
|
+
return ctor && node instanceof ctor ? node : null;
|
|
40807
|
+
}
|
|
40722
40808
|
|
|
40723
40809
|
// src/webmcp/tools/lookTools.ts
|
|
40724
40810
|
var DEFAULT_LIMIT = 200;
|
|
@@ -40804,6 +40890,744 @@ var STUDIO_LOOK_DESCRIPTION = [
|
|
|
40804
40890
|
"you did not do, a human pressed undo and your earlier edits are gone."
|
|
40805
40891
|
].join(" ");
|
|
40806
40892
|
|
|
40893
|
+
// src/webmcp/tools/selectionTools.ts
|
|
40894
|
+
async function studioSelect(deps, handle) {
|
|
40895
|
+
if (typeof handle !== "string" || !handle.trim()) {
|
|
40896
|
+
return toolFailure("invalid", "handle must be a non-empty string", "Call studio_look first.");
|
|
40897
|
+
}
|
|
40898
|
+
const doc = deps.getPreviewDocument();
|
|
40899
|
+
if (!doc) {
|
|
40900
|
+
return toolFailure(
|
|
40901
|
+
"blocked",
|
|
40902
|
+
"the preview is not mounted yet",
|
|
40903
|
+
"Wait for the composition to load, then retry."
|
|
40904
|
+
);
|
|
40905
|
+
}
|
|
40906
|
+
const element = resolveElementHandle(doc, handle);
|
|
40907
|
+
if (!element) {
|
|
40908
|
+
return toolFailure(
|
|
40909
|
+
"invalid",
|
|
40910
|
+
`no element matches handle ${handle}`,
|
|
40911
|
+
"The composition may have changed. Call studio_look for current handles."
|
|
40912
|
+
);
|
|
40913
|
+
}
|
|
40914
|
+
const selection = await deps.buildSelection(element);
|
|
40915
|
+
if (!selection) {
|
|
40916
|
+
return toolFailure(
|
|
40917
|
+
"blocked",
|
|
40918
|
+
`${handle} resolved to an element Studio cannot select`,
|
|
40919
|
+
"Try a parent or child element from studio_look."
|
|
40920
|
+
);
|
|
40921
|
+
}
|
|
40922
|
+
deps.applySelection(selection);
|
|
40923
|
+
return toolOk({
|
|
40924
|
+
handle: mintElementHandle(patchTargetAddress(selection)),
|
|
40925
|
+
label: selection.label,
|
|
40926
|
+
tagName: selection.tagName,
|
|
40927
|
+
box: selection.boundingBox
|
|
40928
|
+
});
|
|
40929
|
+
}
|
|
40930
|
+
function studioSeek(deps, time) {
|
|
40931
|
+
if (typeof time !== "number" || !Number.isFinite(time)) {
|
|
40932
|
+
return toolFailure("invalid", "time must be a finite number of seconds");
|
|
40933
|
+
}
|
|
40934
|
+
const before = deps.readPlayhead();
|
|
40935
|
+
deps.requestSeek(time);
|
|
40936
|
+
const after = deps.readPlayhead();
|
|
40937
|
+
const moved = after.currentTime !== before.currentTime;
|
|
40938
|
+
if (!moved && before.currentTime !== time) {
|
|
40939
|
+
return toolFailure(
|
|
40940
|
+
"blocked",
|
|
40941
|
+
`the playhead did not move; it is still at ${after.currentTime}`,
|
|
40942
|
+
"The preview may not be ready. Check studio_look, then retry."
|
|
40943
|
+
);
|
|
40944
|
+
}
|
|
40945
|
+
return toolOk({
|
|
40946
|
+
playhead: after.currentTime,
|
|
40947
|
+
duration: after.duration,
|
|
40948
|
+
isPlaying: after.isPlaying,
|
|
40949
|
+
moved
|
|
40950
|
+
});
|
|
40951
|
+
}
|
|
40952
|
+
var STUDIO_SELECT_INPUT_SCHEMA = {
|
|
40953
|
+
type: "object",
|
|
40954
|
+
properties: {
|
|
40955
|
+
handle: { type: "string", description: "An element handle from studio_look." }
|
|
40956
|
+
},
|
|
40957
|
+
required: ["handle"],
|
|
40958
|
+
additionalProperties: false
|
|
40959
|
+
};
|
|
40960
|
+
var STUDIO_SELECT_DESCRIPTION = [
|
|
40961
|
+
"Select an element in HyperFrames Studio, exactly as clicking it would:",
|
|
40962
|
+
"the human sees the same selection box and inspector.",
|
|
40963
|
+
"Takes a handle from studio_look. Most editing tools act on the CURRENT selection,",
|
|
40964
|
+
"so call this first, then the edit.",
|
|
40965
|
+
"Returns `ok: true` with the resulting selection, or `ok: false` with `kind`, `reason` and a `hint`."
|
|
40966
|
+
].join(" ");
|
|
40967
|
+
var STUDIO_SEEK_INPUT_SCHEMA = {
|
|
40968
|
+
type: "object",
|
|
40969
|
+
properties: {
|
|
40970
|
+
time: { type: "number", minimum: 0, description: "Playhead position in seconds." }
|
|
40971
|
+
},
|
|
40972
|
+
required: ["time"],
|
|
40973
|
+
additionalProperties: false
|
|
40974
|
+
};
|
|
40975
|
+
var STUDIO_SEEK_DESCRIPTION = [
|
|
40976
|
+
"Move the playhead to a time in seconds. Pauses playback.",
|
|
40977
|
+
"Out-of-range times are clamped by the player, so check the returned `playhead`",
|
|
40978
|
+
"for where it actually landed rather than assuming it matched your request.",
|
|
40979
|
+
"Returns `ok: true`, or `ok: false` with `kind`, `reason` and a `hint`."
|
|
40980
|
+
].join(" ");
|
|
40981
|
+
|
|
40982
|
+
// src/utils/projectRouting.ts
|
|
40983
|
+
var PROJECT_HASH_PREFIX = "#project/";
|
|
40984
|
+
function decodeHashProjectId(value) {
|
|
40985
|
+
try {
|
|
40986
|
+
return decodeURIComponent(value);
|
|
40987
|
+
} catch {
|
|
40988
|
+
return value;
|
|
40989
|
+
}
|
|
40990
|
+
}
|
|
40991
|
+
function normalizeHashParams(params) {
|
|
40992
|
+
if (!params) return new URLSearchParams();
|
|
40993
|
+
if (params instanceof URLSearchParams) return params;
|
|
40994
|
+
const next = new URLSearchParams();
|
|
40995
|
+
for (const [key2, value] of Object.entries(params)) {
|
|
40996
|
+
if (!key2 || value == null || value === "") continue;
|
|
40997
|
+
next.set(key2, value);
|
|
40998
|
+
}
|
|
40999
|
+
return next;
|
|
41000
|
+
}
|
|
41001
|
+
function encodeProjectId(projectId) {
|
|
41002
|
+
return encodeURIComponent(projectId);
|
|
41003
|
+
}
|
|
41004
|
+
function buildProjectHash(projectId, params) {
|
|
41005
|
+
const search = normalizeHashParams(params).toString();
|
|
41006
|
+
return `${PROJECT_HASH_PREFIX}${encodeProjectId(projectId)}${search ? `?${search}` : ""}`;
|
|
41007
|
+
}
|
|
41008
|
+
function parseProjectHashRoute(hash) {
|
|
41009
|
+
if (!hash.startsWith(PROJECT_HASH_PREFIX)) return null;
|
|
41010
|
+
const route = hash.slice(PROJECT_HASH_PREFIX.length);
|
|
41011
|
+
const queryIndex = route.indexOf("?");
|
|
41012
|
+
const encodedProjectId = queryIndex >= 0 ? route.slice(0, queryIndex) : route;
|
|
41013
|
+
if (!encodedProjectId || encodedProjectId.includes("/")) return null;
|
|
41014
|
+
const rawParams = queryIndex >= 0 ? route.slice(queryIndex + 1) : "";
|
|
41015
|
+
return {
|
|
41016
|
+
projectId: decodeHashProjectId(encodedProjectId),
|
|
41017
|
+
params: new URLSearchParams(rawParams)
|
|
41018
|
+
};
|
|
41019
|
+
}
|
|
41020
|
+
function parseProjectIdFromHash(hash) {
|
|
41021
|
+
return parseProjectHashRoute(hash)?.projectId ?? null;
|
|
41022
|
+
}
|
|
41023
|
+
function buildProjectApiPath(projectId, suffix = "") {
|
|
41024
|
+
const normalizedSuffix = suffix && !suffix.startsWith("/") ? `/${suffix}` : suffix;
|
|
41025
|
+
return `/api/projects/${encodeProjectId(projectId)}${normalizedSuffix}`;
|
|
41026
|
+
}
|
|
41027
|
+
|
|
41028
|
+
// src/utils/frameCapture.ts
|
|
41029
|
+
function normalizeCompositionPath(compositionPath) {
|
|
41030
|
+
return compositionPath && compositionPath !== "master" ? compositionPath : "index.html";
|
|
41031
|
+
}
|
|
41032
|
+
function buildFrameCaptureUrl({
|
|
41033
|
+
projectId,
|
|
41034
|
+
compositionPath,
|
|
41035
|
+
currentTime,
|
|
41036
|
+
origin = window.location.origin
|
|
41037
|
+
}) {
|
|
41038
|
+
const compPath = normalizeCompositionPath(compositionPath);
|
|
41039
|
+
const url = new URL(
|
|
41040
|
+
buildProjectApiPath(projectId, `/thumbnail/${encodeURIComponent(compPath)}`),
|
|
41041
|
+
origin
|
|
41042
|
+
);
|
|
41043
|
+
url.searchParams.set("t", Math.max(0, currentTime).toFixed(3));
|
|
41044
|
+
url.searchParams.set("format", "png");
|
|
41045
|
+
url.searchParams.set("output", "source");
|
|
41046
|
+
url.searchParams.set("v", String(Date.now()));
|
|
41047
|
+
return url.toString();
|
|
41048
|
+
}
|
|
41049
|
+
function buildFrameCaptureFilename(compositionPath, currentTime) {
|
|
41050
|
+
const compPath = normalizeCompositionPath(compositionPath);
|
|
41051
|
+
const base2 = compPath.split("/").pop()?.replace(/\.html$/i, "") || "frame";
|
|
41052
|
+
const frameTime = Math.max(0, currentTime).toFixed(3).replace(".", "-");
|
|
41053
|
+
return `${base2}-${frameTime}s.png`;
|
|
41054
|
+
}
|
|
41055
|
+
|
|
41056
|
+
// src/webmcp/tools/frameTools.ts
|
|
41057
|
+
var DEFAULT_SETTLE_MS = 150;
|
|
41058
|
+
var MAX_SETTLE_MS = 5e3;
|
|
41059
|
+
async function studioFrame(deps, input = {}) {
|
|
41060
|
+
const projectId = deps.getProjectId();
|
|
41061
|
+
if (!projectId) {
|
|
41062
|
+
return toolFailure("blocked", "no project is open");
|
|
41063
|
+
}
|
|
41064
|
+
if (input.time !== void 0) {
|
|
41065
|
+
if (typeof input.time !== "number" || !Number.isFinite(input.time) || input.time < 0) {
|
|
41066
|
+
return toolFailure("invalid", "time must be a non-negative, finite number of seconds");
|
|
41067
|
+
}
|
|
41068
|
+
deps.requestSeek(input.time);
|
|
41069
|
+
}
|
|
41070
|
+
const settledMs = clampSettle(input.settleMs);
|
|
41071
|
+
if (settledMs > 0) await deps.wait(settledMs);
|
|
41072
|
+
const { currentTime } = deps.readPlayhead();
|
|
41073
|
+
const compositionPath = deps.getCompositionPath();
|
|
41074
|
+
const url = buildFrameCaptureUrl({ projectId, compositionPath, currentTime });
|
|
41075
|
+
const probe2 = await deps.probeFrame(url);
|
|
41076
|
+
if (!probe2.ok) {
|
|
41077
|
+
return toolFailure(
|
|
41078
|
+
"failed",
|
|
41079
|
+
`the renderer returned ${probe2.status} for this frame`,
|
|
41080
|
+
"The composition may not build. Try `hyperframes check`."
|
|
41081
|
+
);
|
|
41082
|
+
}
|
|
41083
|
+
return toolOk({
|
|
41084
|
+
url,
|
|
41085
|
+
time: currentTime,
|
|
41086
|
+
compositionPath: compositionPath ?? "index.html",
|
|
41087
|
+
settledMs
|
|
41088
|
+
});
|
|
41089
|
+
}
|
|
41090
|
+
function clampSettle(requested) {
|
|
41091
|
+
if (requested === void 0) return DEFAULT_SETTLE_MS;
|
|
41092
|
+
if (typeof requested !== "number" || !Number.isFinite(requested) || requested < 0) {
|
|
41093
|
+
return DEFAULT_SETTLE_MS;
|
|
41094
|
+
}
|
|
41095
|
+
return Math.min(requested, MAX_SETTLE_MS);
|
|
41096
|
+
}
|
|
41097
|
+
var STUDIO_FRAME_INPUT_SCHEMA = {
|
|
41098
|
+
type: "object",
|
|
41099
|
+
properties: {
|
|
41100
|
+
time: {
|
|
41101
|
+
type: "number",
|
|
41102
|
+
minimum: 0,
|
|
41103
|
+
description: "Seconds. Omit to capture wherever the playhead already is."
|
|
41104
|
+
},
|
|
41105
|
+
settleMs: {
|
|
41106
|
+
type: "integer",
|
|
41107
|
+
minimum: 0,
|
|
41108
|
+
maximum: MAX_SETTLE_MS,
|
|
41109
|
+
description: `Wait this long before capturing so a just-made edit is included. Default ${DEFAULT_SETTLE_MS}.`
|
|
41110
|
+
}
|
|
41111
|
+
},
|
|
41112
|
+
additionalProperties: false
|
|
41113
|
+
};
|
|
41114
|
+
var STUDIO_FRAME_DESCRIPTION = [
|
|
41115
|
+
"Render the composition to a PNG at a given time and return its URL, so you can",
|
|
41116
|
+
"SEE the result instead of inferring it from source. Use this to judge a change:",
|
|
41117
|
+
"edit, capture the instant it affects, look, adjust.",
|
|
41118
|
+
"The frame is rendered from the file on disk, not the live preview.",
|
|
41119
|
+
"A capture taken immediately after an edit can therefore predate that edit, because",
|
|
41120
|
+
"the render cache is cleared by a file watcher. The tool waits briefly to cover that;",
|
|
41121
|
+
"raise `settleMs` if a frame still looks stale, rather than concluding the edit failed.",
|
|
41122
|
+
"Returns `ok: true` with `url` and the `time` actually captured, or `ok: false`."
|
|
41123
|
+
].join(" ");
|
|
41124
|
+
|
|
41125
|
+
// src/webmcp/tools/inspectTools.ts
|
|
41126
|
+
function describeAnimation(animation) {
|
|
41127
|
+
return {
|
|
41128
|
+
animationId: animation.id,
|
|
41129
|
+
method: animation.method,
|
|
41130
|
+
target: animation.targetSelector,
|
|
41131
|
+
position: animation.position,
|
|
41132
|
+
duration: animation.duration ?? null,
|
|
41133
|
+
ease: animation.ease ?? null,
|
|
41134
|
+
properties: animation.properties,
|
|
41135
|
+
hasKeyframes: animation.keyframes !== void 0,
|
|
41136
|
+
hasArcPath: animation.arcPath !== void 0
|
|
41137
|
+
};
|
|
41138
|
+
}
|
|
41139
|
+
function describe(selection, deps, isCurrentSelection) {
|
|
41140
|
+
const { capabilities } = selection;
|
|
41141
|
+
const gsap3 = deps.getGsapDiagnostics();
|
|
41142
|
+
const animations = isCurrentSelection ? gsap3.animations.map(describeAnimation) : [];
|
|
41143
|
+
let animationEditingBlocked = null;
|
|
41144
|
+
if (!isCurrentSelection) {
|
|
41145
|
+
animationEditingBlocked = "animations are only readable for the current selection";
|
|
41146
|
+
} else if (gsap3.multipleTimelines) {
|
|
41147
|
+
animationEditingBlocked = "this composition has multiple GSAP timelines";
|
|
41148
|
+
} else if (gsap3.unsupportedTimelinePattern) {
|
|
41149
|
+
animationEditingBlocked = "this composition's timeline pattern is not editable by Studio";
|
|
41150
|
+
}
|
|
41151
|
+
return toolOk({
|
|
41152
|
+
handle: mintElementHandle(patchTargetAddress(selection)),
|
|
41153
|
+
label: selection.label,
|
|
41154
|
+
tagName: selection.tagName,
|
|
41155
|
+
sourceFile: selection.sourceFile,
|
|
41156
|
+
box: selection.boundingBox,
|
|
41157
|
+
text: selection.textContent,
|
|
41158
|
+
textFields: selection.textFields.map((field) => ({
|
|
41159
|
+
key: field.key,
|
|
41160
|
+
label: field.label,
|
|
41161
|
+
value: field.value,
|
|
41162
|
+
tagName: field.tagName
|
|
41163
|
+
})),
|
|
41164
|
+
styles: selection.computedStyles,
|
|
41165
|
+
inlineStyles: selection.inlineStyles,
|
|
41166
|
+
dataAttributes: selection.dataAttributes,
|
|
41167
|
+
can: {
|
|
41168
|
+
editStyles: capabilities.canEditStyles,
|
|
41169
|
+
move: capabilities.canMove || capabilities.canApplyManualOffset,
|
|
41170
|
+
resize: capabilities.canResize || capabilities.canApplyManualSize,
|
|
41171
|
+
rotate: capabilities.canApplyManualRotation,
|
|
41172
|
+
crop: capabilities.canCrop,
|
|
41173
|
+
editText: selection.textFields.length > 0,
|
|
41174
|
+
reasonIfDisabled: capabilities.reasonIfDisabled ?? null
|
|
41175
|
+
},
|
|
41176
|
+
animations,
|
|
41177
|
+
animationEditingBlocked,
|
|
41178
|
+
isCurrentSelection
|
|
41179
|
+
});
|
|
41180
|
+
}
|
|
41181
|
+
async function studioInspect(deps, input = {}) {
|
|
41182
|
+
const current = deps.getCurrentSelection();
|
|
41183
|
+
if (!input.handle) {
|
|
41184
|
+
if (!current) {
|
|
41185
|
+
return toolFailure(
|
|
41186
|
+
"invalid",
|
|
41187
|
+
"nothing is selected and no handle was given",
|
|
41188
|
+
"Pass a handle from studio_look, or call studio_select first."
|
|
41189
|
+
);
|
|
41190
|
+
}
|
|
41191
|
+
return describe(current, deps, true);
|
|
41192
|
+
}
|
|
41193
|
+
const doc = deps.getPreviewDocument();
|
|
41194
|
+
if (!doc) return toolFailure("blocked", "the preview is not mounted yet");
|
|
41195
|
+
const element = resolveElementHandle(doc, input.handle);
|
|
41196
|
+
if (!element) {
|
|
41197
|
+
return toolFailure(
|
|
41198
|
+
"invalid",
|
|
41199
|
+
`no element matches handle ${input.handle}`,
|
|
41200
|
+
"Call studio_look for current handles."
|
|
41201
|
+
);
|
|
41202
|
+
}
|
|
41203
|
+
const selection = await deps.buildSelection(element);
|
|
41204
|
+
if (!selection) {
|
|
41205
|
+
return toolFailure("blocked", `${input.handle} resolved to an element Studio cannot inspect`);
|
|
41206
|
+
}
|
|
41207
|
+
return describe(selection, deps, current?.element === element);
|
|
41208
|
+
}
|
|
41209
|
+
var STUDIO_INSPECT_INPUT_SCHEMA = {
|
|
41210
|
+
type: "object",
|
|
41211
|
+
properties: {
|
|
41212
|
+
handle: {
|
|
41213
|
+
type: "string",
|
|
41214
|
+
description: "An element handle from studio_look. Omit to inspect the current selection."
|
|
41215
|
+
}
|
|
41216
|
+
},
|
|
41217
|
+
additionalProperties: false
|
|
41218
|
+
};
|
|
41219
|
+
var STUDIO_INSPECT_DESCRIPTION = [
|
|
41220
|
+
"Everything about one element: its resolved styles, its text fields, its box,",
|
|
41221
|
+
"its GSAP animations, and crucially what it will and will not accept.",
|
|
41222
|
+
"Read this BEFORE editing. `can` tells you which edits are possible and",
|
|
41223
|
+
"`can.reasonIfDisabled` says why one is not, so you can avoid a write that would be refused.",
|
|
41224
|
+
"Animations are only readable for the CURRENT selection; `animationEditingBlocked` says when",
|
|
41225
|
+
"and why animation editing is unavailable.",
|
|
41226
|
+
"Returns `ok: true`, or `ok: false` with `kind`, `reason` and a `hint`."
|
|
41227
|
+
].join(" ");
|
|
41228
|
+
|
|
41229
|
+
// src/webmcp/tools/contentTools.ts
|
|
41230
|
+
var DECLINE_HINTS = {
|
|
41231
|
+
"no-selection": { kind: "invalid", hint: "Call studio_select first." },
|
|
41232
|
+
"no-project": { kind: "blocked" },
|
|
41233
|
+
"geometry-property": {
|
|
41234
|
+
kind: "blocked",
|
|
41235
|
+
hint: "Position and size are not editable as styles. Use the transform tools."
|
|
41236
|
+
},
|
|
41237
|
+
"styles-not-editable": {
|
|
41238
|
+
kind: "blocked",
|
|
41239
|
+
hint: "studio_inspect reports why, in can.reasonIfDisabled."
|
|
41240
|
+
},
|
|
41241
|
+
"not-text-editable": {
|
|
41242
|
+
kind: "blocked",
|
|
41243
|
+
hint: "This element has no editable text. studio_inspect lists its textFields."
|
|
41244
|
+
},
|
|
41245
|
+
"persist-failed": { kind: "failed", hint: "The write did not reach the file. Check Studio." }
|
|
41246
|
+
};
|
|
41247
|
+
function fromOutcome(outcome, what) {
|
|
41248
|
+
if (outcome.ok) return null;
|
|
41249
|
+
const mapped = DECLINE_HINTS[outcome.reason] ?? { kind: "failed" };
|
|
41250
|
+
return toolFailure(mapped.kind, `${what} was not applied: ${outcome.reason}`, mapped.hint);
|
|
41251
|
+
}
|
|
41252
|
+
function guardWrite(deps) {
|
|
41253
|
+
const blocked = deps.getWriteBlockedReason();
|
|
41254
|
+
if (blocked) {
|
|
41255
|
+
return toolFailure("blocked", blocked, "Resolve it in Studio, then retry.");
|
|
41256
|
+
}
|
|
41257
|
+
if (!deps.getCurrentSelection()) {
|
|
41258
|
+
return toolFailure("invalid", "nothing is selected", "Call studio_select first.");
|
|
41259
|
+
}
|
|
41260
|
+
return null;
|
|
41261
|
+
}
|
|
41262
|
+
async function studioSetText(deps, input) {
|
|
41263
|
+
if (typeof input.text !== "string") {
|
|
41264
|
+
return toolFailure("invalid", "text must be a string");
|
|
41265
|
+
}
|
|
41266
|
+
const blocked = guardWrite(deps);
|
|
41267
|
+
if (blocked) return blocked;
|
|
41268
|
+
const selection = deps.getCurrentSelection();
|
|
41269
|
+
if (!selection) return toolFailure("invalid", "nothing is selected");
|
|
41270
|
+
const fields = selection.textFields;
|
|
41271
|
+
const requested = typeof input.field === "string" && input.field ? input.field : void 0;
|
|
41272
|
+
if (requested && !fields.some((candidate) => candidate.key === requested)) {
|
|
41273
|
+
return toolFailure(
|
|
41274
|
+
"invalid",
|
|
41275
|
+
`this element has no text field "${requested}"`,
|
|
41276
|
+
`Its fields are: ${fields.map((candidate) => candidate.key).join(", ") || "none"}.`
|
|
41277
|
+
);
|
|
41278
|
+
}
|
|
41279
|
+
const field = requested ?? (fields.length === 1 ? fields[0]?.key : void 0);
|
|
41280
|
+
if (!field) {
|
|
41281
|
+
if (fields.length === 0) {
|
|
41282
|
+
return toolFailure(
|
|
41283
|
+
"blocked",
|
|
41284
|
+
"this element has no editable text field",
|
|
41285
|
+
"studio_inspect lists an element's textFields."
|
|
41286
|
+
);
|
|
41287
|
+
}
|
|
41288
|
+
return toolFailure(
|
|
41289
|
+
"invalid",
|
|
41290
|
+
`this element has ${fields.length} text fields, so one must be named`,
|
|
41291
|
+
`Pass field as one of: ${fields.map((candidate) => candidate.key).join(", ")}.`
|
|
41292
|
+
);
|
|
41293
|
+
}
|
|
41294
|
+
const before = selection.textContent ?? null;
|
|
41295
|
+
const outcome = await deps.setText(input.text, field);
|
|
41296
|
+
const failure = fromOutcome(outcome, "the text");
|
|
41297
|
+
if (failure) return failure;
|
|
41298
|
+
return toolOk({ text: input.text, changed: before !== input.text });
|
|
41299
|
+
}
|
|
41300
|
+
async function studioSetStyle(deps, input) {
|
|
41301
|
+
const styles = input.styles;
|
|
41302
|
+
if (typeof styles !== "object" || styles === null || Array.isArray(styles)) {
|
|
41303
|
+
return toolFailure("invalid", "styles must be an object of CSS property to value");
|
|
41304
|
+
}
|
|
41305
|
+
const entries = Object.entries(styles).filter(
|
|
41306
|
+
(entry2) => typeof entry2[1] === "string"
|
|
41307
|
+
);
|
|
41308
|
+
if (entries.length === 0) {
|
|
41309
|
+
return toolFailure("invalid", "styles must contain at least one string value");
|
|
41310
|
+
}
|
|
41311
|
+
const blocked = guardWrite(deps);
|
|
41312
|
+
if (blocked) return blocked;
|
|
41313
|
+
const applied = {};
|
|
41314
|
+
const rejected = {};
|
|
41315
|
+
for (const [property, value] of entries) {
|
|
41316
|
+
const outcome = await deps.setStyle(property, value);
|
|
41317
|
+
if (outcome.ok) applied[property] = value;
|
|
41318
|
+
else rejected[property] = outcome.reason;
|
|
41319
|
+
}
|
|
41320
|
+
if (Object.keys(applied).length === 0) {
|
|
41321
|
+
const reasons = Object.entries(rejected).map(([property, reason]) => `${property}: ${reason}`).join(", ");
|
|
41322
|
+
return toolFailure("blocked", `no style was applied (${reasons})`);
|
|
41323
|
+
}
|
|
41324
|
+
return toolOk({ applied, rejected });
|
|
41325
|
+
}
|
|
41326
|
+
var STUDIO_SET_TEXT_INPUT_SCHEMA = {
|
|
41327
|
+
type: "object",
|
|
41328
|
+
properties: {
|
|
41329
|
+
text: { type: "string", description: "The new text content." },
|
|
41330
|
+
field: {
|
|
41331
|
+
type: "string",
|
|
41332
|
+
description: "Which text field to write, from studio_inspect. Omit for the element's own text."
|
|
41333
|
+
}
|
|
41334
|
+
},
|
|
41335
|
+
required: ["text"],
|
|
41336
|
+
additionalProperties: false
|
|
41337
|
+
};
|
|
41338
|
+
var STUDIO_SET_TEXT_DESCRIPTION = [
|
|
41339
|
+
"Set the text of the CURRENTLY SELECTED element. Call studio_select first.",
|
|
41340
|
+
"This is the edit a synthetic double-click cannot reach, because Studio's canvas",
|
|
41341
|
+
"takes pointer capture and recognises the double press itself.",
|
|
41342
|
+
"Returns `ok: true` with the resulting text and whether it changed, or `ok: false`",
|
|
41343
|
+
"with `kind`, `reason` and usually a `hint` naming what to do instead."
|
|
41344
|
+
].join(" ");
|
|
41345
|
+
var STUDIO_SET_STYLE_INPUT_SCHEMA = {
|
|
41346
|
+
type: "object",
|
|
41347
|
+
properties: {
|
|
41348
|
+
styles: {
|
|
41349
|
+
type: "object",
|
|
41350
|
+
description: 'CSS property to value, for example {"color": "red", "font-size": "48px"}.',
|
|
41351
|
+
additionalProperties: { type: "string" }
|
|
41352
|
+
}
|
|
41353
|
+
},
|
|
41354
|
+
required: ["styles"],
|
|
41355
|
+
additionalProperties: false
|
|
41356
|
+
};
|
|
41357
|
+
var STUDIO_SET_STYLE_DESCRIPTION = [
|
|
41358
|
+
"Set inline styles on the CURRENTLY SELECTED element. Call studio_select first.",
|
|
41359
|
+
"Each property is a separate commit, so N properties produce N undo entries.",
|
|
41360
|
+
"Position and size properties (left, top, width, height) are refused here on purpose;",
|
|
41361
|
+
"they belong to the transform tools.",
|
|
41362
|
+
"Returns `ok: true` with `applied` and `rejected` maps, so a partial success is visible",
|
|
41363
|
+
"as a partial success rather than reported as a whole one."
|
|
41364
|
+
].join(" ");
|
|
41365
|
+
|
|
41366
|
+
// src/webmcp/tools/transformTools.ts
|
|
41367
|
+
var NO_OP_HINT = "Move and rotate are written as GSAP code; a composition with no GSAP timeline has nothing to write to. studio_inspect reports the element's animations.";
|
|
41368
|
+
function readNumber(value) {
|
|
41369
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
41370
|
+
}
|
|
41371
|
+
function guard(deps) {
|
|
41372
|
+
const blocked = deps.getWriteBlockedReason();
|
|
41373
|
+
if (blocked) return toolFailure("blocked", blocked, "Resolve it in Studio, then retry.");
|
|
41374
|
+
if (!deps.getCurrentSelection()) {
|
|
41375
|
+
return toolFailure("invalid", "nothing is selected", "Call studio_select first.");
|
|
41376
|
+
}
|
|
41377
|
+
return null;
|
|
41378
|
+
}
|
|
41379
|
+
function parsePair(a, b, names, min = Number.NEGATIVE_INFINITY) {
|
|
41380
|
+
const first = readNumber(a);
|
|
41381
|
+
const second = readNumber(b);
|
|
41382
|
+
if (first === null && second === null) return { pair: null };
|
|
41383
|
+
if (first === null || second === null) {
|
|
41384
|
+
return toolFailure("invalid", `${names[0]} and ${names[1]} must be given together`);
|
|
41385
|
+
}
|
|
41386
|
+
if (first < min || second < min) {
|
|
41387
|
+
return toolFailure("invalid", `${names[0]} and ${names[1]} must be at least ${min}`);
|
|
41388
|
+
}
|
|
41389
|
+
return { pair: [first, second] };
|
|
41390
|
+
}
|
|
41391
|
+
function isFailure(value) {
|
|
41392
|
+
return "ok" in value;
|
|
41393
|
+
}
|
|
41394
|
+
function parseRequest(input) {
|
|
41395
|
+
const move = parsePair(input.x, input.y, ["x", "y"]);
|
|
41396
|
+
if (isFailure(move)) return move;
|
|
41397
|
+
const size = parsePair(input.width, input.height, ["width", "height"], 0);
|
|
41398
|
+
if (isFailure(size)) return size;
|
|
41399
|
+
const rotate2 = readNumber(input.rotate);
|
|
41400
|
+
if (!move.pair && !size.pair && rotate2 === null) {
|
|
41401
|
+
return toolFailure(
|
|
41402
|
+
"invalid",
|
|
41403
|
+
"give at least one of x, y, width, height, rotate as a finite number"
|
|
41404
|
+
);
|
|
41405
|
+
}
|
|
41406
|
+
return {
|
|
41407
|
+
move: move.pair ? { x: move.pair[0], y: move.pair[1] } : null,
|
|
41408
|
+
size: size.pair ? { width: size.pair[0], height: size.pair[1] } : null,
|
|
41409
|
+
rotate: rotate2
|
|
41410
|
+
};
|
|
41411
|
+
}
|
|
41412
|
+
async function studioTransform(deps, input) {
|
|
41413
|
+
const request = parseRequest(input);
|
|
41414
|
+
if (isFailure(request)) return request;
|
|
41415
|
+
const blocked = guard(deps);
|
|
41416
|
+
if (blocked) return blocked;
|
|
41417
|
+
const selection = deps.getCurrentSelection();
|
|
41418
|
+
if (!selection) return toolFailure("invalid", "nothing is selected");
|
|
41419
|
+
const applied = [];
|
|
41420
|
+
const unchanged = {};
|
|
41421
|
+
if (request.size) {
|
|
41422
|
+
const before = deps.readBox(selection);
|
|
41423
|
+
await deps.resizeTo(selection, request.size);
|
|
41424
|
+
const after = deps.readBox(selection);
|
|
41425
|
+
if (after.width !== before.width || after.height !== before.height) applied.push("resize");
|
|
41426
|
+
else unchanged.resize = "the element's size did not change";
|
|
41427
|
+
}
|
|
41428
|
+
if (request.move) {
|
|
41429
|
+
const before = deps.readBox(selection);
|
|
41430
|
+
await deps.moveTo(selection, request.move);
|
|
41431
|
+
const after = deps.readBox(selection);
|
|
41432
|
+
if (after.x !== before.x || after.y !== before.y) applied.push("move");
|
|
41433
|
+
else unchanged.move = `the element did not move. ${NO_OP_HINT}`;
|
|
41434
|
+
}
|
|
41435
|
+
if (request.rotate !== null) {
|
|
41436
|
+
await deps.rotateTo(selection, { angle: request.rotate });
|
|
41437
|
+
applied.push("rotate");
|
|
41438
|
+
}
|
|
41439
|
+
if (applied.length === 0) {
|
|
41440
|
+
return toolFailure(
|
|
41441
|
+
"blocked",
|
|
41442
|
+
`nothing changed: ${Object.values(unchanged).join("; ")}`,
|
|
41443
|
+
NO_OP_HINT
|
|
41444
|
+
);
|
|
41445
|
+
}
|
|
41446
|
+
return toolOk({ box: deps.readBox(selection), applied, unchanged });
|
|
41447
|
+
}
|
|
41448
|
+
var STUDIO_TRANSFORM_INPUT_SCHEMA = {
|
|
41449
|
+
type: "object",
|
|
41450
|
+
properties: {
|
|
41451
|
+
x: { type: "number", description: "New x offset in pixels. Must be paired with y." },
|
|
41452
|
+
y: { type: "number", description: "New y offset in pixels. Must be paired with x." },
|
|
41453
|
+
width: { type: "number", minimum: 0, description: "New width. Must be paired with height." },
|
|
41454
|
+
height: { type: "number", minimum: 0, description: "New height. Must be paired with width." },
|
|
41455
|
+
rotate: { type: "number", description: "Rotation in degrees." }
|
|
41456
|
+
},
|
|
41457
|
+
additionalProperties: false
|
|
41458
|
+
};
|
|
41459
|
+
var STUDIO_TRANSFORM_DESCRIPTION = [
|
|
41460
|
+
"Move, resize or rotate the CURRENTLY SELECTED element, the way a drag would.",
|
|
41461
|
+
"Call studio_select first. Give x with y, and width with height.",
|
|
41462
|
+
"The result's `box` is READ BACK after the write, not echoed from your request, and",
|
|
41463
|
+
"`applied` lists what actually took effect. Check it.",
|
|
41464
|
+
"Move and rotate are written as GSAP code, so in a composition with no GSAP timeline they",
|
|
41465
|
+
"do nothing; that shows up in `unchanged` rather than as a false success.",
|
|
41466
|
+
"Rotation is reported as dispatched rather than verified, because the CSS `rotate` property",
|
|
41467
|
+
"does not appear in the element's computed transform.",
|
|
41468
|
+
"Returns `ok: true`, or `ok: false` with `kind`, `reason` and a `hint`."
|
|
41469
|
+
].join(" ");
|
|
41470
|
+
|
|
41471
|
+
// src/webmcp/tools/animationTools.ts
|
|
41472
|
+
var METHODS = ["to", "from", "set", "fromTo"];
|
|
41473
|
+
var INSPECT_HINT = "Call studio_inspect to see the result.";
|
|
41474
|
+
function guard2(deps) {
|
|
41475
|
+
const blocked = deps.getWriteBlockedReason();
|
|
41476
|
+
if (blocked) return toolFailure("blocked", blocked, "Resolve it in Studio, then retry.");
|
|
41477
|
+
if (!deps.getCurrentSelection()) {
|
|
41478
|
+
return toolFailure("invalid", "nothing is selected", "Call studio_select first.");
|
|
41479
|
+
}
|
|
41480
|
+
return null;
|
|
41481
|
+
}
|
|
41482
|
+
function readAnimationId(value) {
|
|
41483
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
41484
|
+
}
|
|
41485
|
+
async function studioAddAnimation(deps, input) {
|
|
41486
|
+
const method = METHODS.find((candidate) => candidate === input.method);
|
|
41487
|
+
if (!method) {
|
|
41488
|
+
return toolFailure("invalid", `method must be one of ${METHODS.join(", ")}`);
|
|
41489
|
+
}
|
|
41490
|
+
const blocked = guard2(deps);
|
|
41491
|
+
if (blocked) return blocked;
|
|
41492
|
+
const { currentTime } = deps.readPlayhead();
|
|
41493
|
+
deps.addAnimation(method);
|
|
41494
|
+
return toolOk({
|
|
41495
|
+
method,
|
|
41496
|
+
insertedAtSeconds: currentTime,
|
|
41497
|
+
dispatched: true
|
|
41498
|
+
});
|
|
41499
|
+
}
|
|
41500
|
+
async function studioUpdateAnimation(deps, input) {
|
|
41501
|
+
const animationId = readAnimationId(input.animationId);
|
|
41502
|
+
if (!animationId) {
|
|
41503
|
+
return toolFailure("invalid", "animationId must be a non-empty string", INSPECT_HINT);
|
|
41504
|
+
}
|
|
41505
|
+
const updates = {};
|
|
41506
|
+
if (typeof input.duration === "number" && Number.isFinite(input.duration)) {
|
|
41507
|
+
if (input.duration < 0) return toolFailure("invalid", "duration must not be negative");
|
|
41508
|
+
updates.duration = input.duration;
|
|
41509
|
+
}
|
|
41510
|
+
if (typeof input.ease === "string" && input.ease.trim()) updates.ease = input.ease;
|
|
41511
|
+
if (typeof input.position === "number" && Number.isFinite(input.position)) {
|
|
41512
|
+
updates.position = input.position;
|
|
41513
|
+
}
|
|
41514
|
+
if (Object.keys(updates).length === 0) {
|
|
41515
|
+
return toolFailure("invalid", "give at least one of duration, ease, position");
|
|
41516
|
+
}
|
|
41517
|
+
const blocked = guard2(deps);
|
|
41518
|
+
if (blocked) return blocked;
|
|
41519
|
+
const landed = await deps.updateAnimation(animationId, updates);
|
|
41520
|
+
if (!landed) {
|
|
41521
|
+
return toolFailure(
|
|
41522
|
+
"failed",
|
|
41523
|
+
`the update to ${animationId} did not land`,
|
|
41524
|
+
"The animation id may be stale. studio_inspect lists the current ones."
|
|
41525
|
+
);
|
|
41526
|
+
}
|
|
41527
|
+
return toolOk({ animationId, updated: updates });
|
|
41528
|
+
}
|
|
41529
|
+
async function studioAddKeyframe(deps, input) {
|
|
41530
|
+
const animationId = readAnimationId(input.animationId);
|
|
41531
|
+
if (!animationId) {
|
|
41532
|
+
return toolFailure("invalid", "animationId must be a non-empty string", INSPECT_HINT);
|
|
41533
|
+
}
|
|
41534
|
+
const percent3 = input.percent;
|
|
41535
|
+
if (typeof percent3 !== "number" || !Number.isFinite(percent3) || percent3 < 0 || percent3 > 100) {
|
|
41536
|
+
return toolFailure("invalid", "percent must be a number between 0 and 100");
|
|
41537
|
+
}
|
|
41538
|
+
const raw = input.properties;
|
|
41539
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
41540
|
+
return toolFailure("invalid", "properties must be an object of GSAP property to value");
|
|
41541
|
+
}
|
|
41542
|
+
const properties = {};
|
|
41543
|
+
for (const [key2, value] of Object.entries(raw)) {
|
|
41544
|
+
if (typeof value === "number" || typeof value === "string") properties[key2] = value;
|
|
41545
|
+
}
|
|
41546
|
+
if (Object.keys(properties).length === 0) {
|
|
41547
|
+
return toolFailure("invalid", "properties must contain at least one number or string value");
|
|
41548
|
+
}
|
|
41549
|
+
const blocked = guard2(deps);
|
|
41550
|
+
if (blocked) return blocked;
|
|
41551
|
+
await deps.addKeyframe(animationId, percent3, properties);
|
|
41552
|
+
return toolOk({ animationId, percent: percent3, properties, dispatched: true });
|
|
41553
|
+
}
|
|
41554
|
+
async function studioDeleteAnimation(deps, input) {
|
|
41555
|
+
const animationId = readAnimationId(input.animationId);
|
|
41556
|
+
if (!animationId) {
|
|
41557
|
+
return toolFailure("invalid", "animationId must be a non-empty string", INSPECT_HINT);
|
|
41558
|
+
}
|
|
41559
|
+
const blocked = guard2(deps);
|
|
41560
|
+
if (blocked) return blocked;
|
|
41561
|
+
deps.deleteAnimation(animationId);
|
|
41562
|
+
return toolOk({ animationId, dispatched: true });
|
|
41563
|
+
}
|
|
41564
|
+
var DISPATCH_CAVEAT = `Reports what was dispatched, not what landed: the handler underneath does not report back. ${INSPECT_HINT}`;
|
|
41565
|
+
var STUDIO_ADD_ANIMATION_INPUT_SCHEMA = {
|
|
41566
|
+
type: "object",
|
|
41567
|
+
properties: {
|
|
41568
|
+
method: { type: "string", enum: METHODS, description: "The GSAP method to add." }
|
|
41569
|
+
},
|
|
41570
|
+
required: ["method"],
|
|
41571
|
+
additionalProperties: false
|
|
41572
|
+
};
|
|
41573
|
+
var STUDIO_ADD_ANIMATION_DESCRIPTION = [
|
|
41574
|
+
"Add a GSAP animation to the CURRENTLY SELECTED element. Call studio_select first.",
|
|
41575
|
+
"It is inserted AT THE PLAYHEAD, which this tool does not control: call studio_seek first",
|
|
41576
|
+
"to choose when it starts. The result reports where the playhead actually was.",
|
|
41577
|
+
DISPATCH_CAVEAT
|
|
41578
|
+
].join(" ");
|
|
41579
|
+
var STUDIO_UPDATE_ANIMATION_INPUT_SCHEMA = {
|
|
41580
|
+
type: "object",
|
|
41581
|
+
properties: {
|
|
41582
|
+
animationId: { type: "string", description: "An animation id from studio_inspect." },
|
|
41583
|
+
duration: { type: "number", minimum: 0, description: "Duration in seconds." },
|
|
41584
|
+
ease: { type: "string", description: "A GSAP ease, for example power2.out." },
|
|
41585
|
+
position: { type: "number", description: "Start position in seconds." }
|
|
41586
|
+
},
|
|
41587
|
+
required: ["animationId"],
|
|
41588
|
+
additionalProperties: false
|
|
41589
|
+
};
|
|
41590
|
+
var STUDIO_UPDATE_ANIMATION_DESCRIPTION = [
|
|
41591
|
+
"Change an existing animation's duration, ease or position.",
|
|
41592
|
+
"This is the one animation tool that CONFIRMS its write, so a failure here is real",
|
|
41593
|
+
"and usually means a stale animationId. Get current ids from studio_inspect."
|
|
41594
|
+
].join(" ");
|
|
41595
|
+
var STUDIO_ADD_KEYFRAME_INPUT_SCHEMA = {
|
|
41596
|
+
type: "object",
|
|
41597
|
+
properties: {
|
|
41598
|
+
animationId: { type: "string", description: "An animation id from studio_inspect." },
|
|
41599
|
+
percent: {
|
|
41600
|
+
type: "number",
|
|
41601
|
+
minimum: 0,
|
|
41602
|
+
maximum: 100,
|
|
41603
|
+
description: "Where in the tween, 0 to 100."
|
|
41604
|
+
},
|
|
41605
|
+
properties: {
|
|
41606
|
+
type: "object",
|
|
41607
|
+
description: 'GSAP property to value, for example {"y": -50, "opacity": 0}.'
|
|
41608
|
+
}
|
|
41609
|
+
},
|
|
41610
|
+
required: ["animationId", "percent", "properties"],
|
|
41611
|
+
additionalProperties: false
|
|
41612
|
+
};
|
|
41613
|
+
var STUDIO_ADD_KEYFRAME_DESCRIPTION = [
|
|
41614
|
+
"Add a keyframe to an existing animation at a percentage through it.",
|
|
41615
|
+
"All the properties land in one commit, so they are one undo entry.",
|
|
41616
|
+
DISPATCH_CAVEAT
|
|
41617
|
+
].join(" ");
|
|
41618
|
+
var STUDIO_DELETE_ANIMATION_INPUT_SCHEMA = {
|
|
41619
|
+
type: "object",
|
|
41620
|
+
properties: {
|
|
41621
|
+
animationId: { type: "string", description: "An animation id from studio_inspect." }
|
|
41622
|
+
},
|
|
41623
|
+
required: ["animationId"],
|
|
41624
|
+
additionalProperties: false
|
|
41625
|
+
};
|
|
41626
|
+
var STUDIO_DELETE_ANIMATION_DESCRIPTION = [
|
|
41627
|
+
"Remove an animation from the currently selected element. Undo reverses it.",
|
|
41628
|
+
DISPATCH_CAVEAT
|
|
41629
|
+
].join(" ");
|
|
41630
|
+
|
|
40807
41631
|
// src/webmcp/useStudioAgentTools.ts
|
|
40808
41632
|
var log2 = makeStudioDebugLogger("webmcp");
|
|
40809
41633
|
function reportRegistration(report, native) {
|
|
@@ -40832,9 +41656,117 @@ function buildStudioTools(depsRef) {
|
|
|
40832
41656
|
"studio_look",
|
|
40833
41657
|
async () => buildStudioLook(depsRef.current.getSnapshot(), input)
|
|
40834
41658
|
)
|
|
41659
|
+
},
|
|
41660
|
+
{
|
|
41661
|
+
name: "studio_select",
|
|
41662
|
+
title: "Select an element",
|
|
41663
|
+
description: STUDIO_SELECT_DESCRIPTION,
|
|
41664
|
+
inputSchema: STUDIO_SELECT_INPUT_SCHEMA,
|
|
41665
|
+
annotations: { readOnlyHint: false, untrustedContentHint: true },
|
|
41666
|
+
execute: (input) => runToolBody(
|
|
41667
|
+
"studio_select",
|
|
41668
|
+
() => studioSelect(depsRef.current, readStringInput(input, "handle"))
|
|
41669
|
+
)
|
|
41670
|
+
},
|
|
41671
|
+
{
|
|
41672
|
+
name: "studio_seek",
|
|
41673
|
+
title: "Move the playhead",
|
|
41674
|
+
description: STUDIO_SEEK_DESCRIPTION,
|
|
41675
|
+
inputSchema: STUDIO_SEEK_INPUT_SCHEMA,
|
|
41676
|
+
annotations: { readOnlyHint: false },
|
|
41677
|
+
execute: (input) => runToolBody(
|
|
41678
|
+
"studio_seek",
|
|
41679
|
+
async () => studioSeek(depsRef.current, readNumberInput(input, "time"))
|
|
41680
|
+
)
|
|
41681
|
+
},
|
|
41682
|
+
{
|
|
41683
|
+
name: "studio_frame",
|
|
41684
|
+
title: "See the composition",
|
|
41685
|
+
description: STUDIO_FRAME_DESCRIPTION,
|
|
41686
|
+
inputSchema: STUDIO_FRAME_INPUT_SCHEMA,
|
|
41687
|
+
annotations: { readOnlyHint: true, untrustedContentHint: true },
|
|
41688
|
+
execute: (input) => runToolBody("studio_frame", () => studioFrame(depsRef.current, input))
|
|
41689
|
+
},
|
|
41690
|
+
{
|
|
41691
|
+
name: "studio_inspect",
|
|
41692
|
+
title: "Inspect one element",
|
|
41693
|
+
description: STUDIO_INSPECT_DESCRIPTION,
|
|
41694
|
+
inputSchema: STUDIO_INSPECT_INPUT_SCHEMA,
|
|
41695
|
+
annotations: { readOnlyHint: true, untrustedContentHint: true },
|
|
41696
|
+
execute: (input) => runToolBody(
|
|
41697
|
+
"studio_inspect",
|
|
41698
|
+
() => studioInspect(depsRef.current, input)
|
|
41699
|
+
)
|
|
41700
|
+
},
|
|
41701
|
+
{
|
|
41702
|
+
name: "studio_set_text",
|
|
41703
|
+
title: "Set an element's text",
|
|
41704
|
+
description: STUDIO_SET_TEXT_DESCRIPTION,
|
|
41705
|
+
inputSchema: STUDIO_SET_TEXT_INPUT_SCHEMA,
|
|
41706
|
+
annotations: { readOnlyHint: false, untrustedContentHint: true },
|
|
41707
|
+
execute: (input) => runToolBody("studio_set_text", () => studioSetText(depsRef.current, input))
|
|
41708
|
+
},
|
|
41709
|
+
{
|
|
41710
|
+
name: "studio_set_style",
|
|
41711
|
+
title: "Set an element's styles",
|
|
41712
|
+
description: STUDIO_SET_STYLE_DESCRIPTION,
|
|
41713
|
+
inputSchema: STUDIO_SET_STYLE_INPUT_SCHEMA,
|
|
41714
|
+
annotations: { readOnlyHint: false },
|
|
41715
|
+
execute: (input) => runToolBody("studio_set_style", () => studioSetStyle(depsRef.current, input))
|
|
41716
|
+
},
|
|
41717
|
+
{
|
|
41718
|
+
name: "studio_transform",
|
|
41719
|
+
title: "Move, resize or rotate",
|
|
41720
|
+
description: STUDIO_TRANSFORM_DESCRIPTION,
|
|
41721
|
+
inputSchema: STUDIO_TRANSFORM_INPUT_SCHEMA,
|
|
41722
|
+
annotations: { readOnlyHint: false },
|
|
41723
|
+
execute: (input) => runToolBody(
|
|
41724
|
+
"studio_transform",
|
|
41725
|
+
() => studioTransform(depsRef.current, input)
|
|
41726
|
+
)
|
|
41727
|
+
},
|
|
41728
|
+
{
|
|
41729
|
+
name: "studio_add_animation",
|
|
41730
|
+
title: "Add an animation",
|
|
41731
|
+
description: STUDIO_ADD_ANIMATION_DESCRIPTION,
|
|
41732
|
+
inputSchema: STUDIO_ADD_ANIMATION_INPUT_SCHEMA,
|
|
41733
|
+
annotations: { readOnlyHint: false },
|
|
41734
|
+
execute: (input) => runToolBody("studio_add_animation", () => studioAddAnimation(depsRef.current, input))
|
|
41735
|
+
},
|
|
41736
|
+
{
|
|
41737
|
+
name: "studio_update_animation",
|
|
41738
|
+
title: "Change an animation",
|
|
41739
|
+
description: STUDIO_UPDATE_ANIMATION_DESCRIPTION,
|
|
41740
|
+
inputSchema: STUDIO_UPDATE_ANIMATION_INPUT_SCHEMA,
|
|
41741
|
+
annotations: { readOnlyHint: false },
|
|
41742
|
+
execute: (input) => runToolBody("studio_update_animation", () => studioUpdateAnimation(depsRef.current, input))
|
|
41743
|
+
},
|
|
41744
|
+
{
|
|
41745
|
+
name: "studio_add_keyframe",
|
|
41746
|
+
title: "Add a keyframe",
|
|
41747
|
+
description: STUDIO_ADD_KEYFRAME_DESCRIPTION,
|
|
41748
|
+
inputSchema: STUDIO_ADD_KEYFRAME_INPUT_SCHEMA,
|
|
41749
|
+
annotations: { readOnlyHint: false },
|
|
41750
|
+
execute: (input) => runToolBody("studio_add_keyframe", () => studioAddKeyframe(depsRef.current, input))
|
|
41751
|
+
},
|
|
41752
|
+
{
|
|
41753
|
+
name: "studio_delete_animation",
|
|
41754
|
+
title: "Remove an animation",
|
|
41755
|
+
description: STUDIO_DELETE_ANIMATION_DESCRIPTION,
|
|
41756
|
+
inputSchema: STUDIO_DELETE_ANIMATION_INPUT_SCHEMA,
|
|
41757
|
+
annotations: { readOnlyHint: false },
|
|
41758
|
+
execute: (input) => runToolBody("studio_delete_animation", () => studioDeleteAnimation(depsRef.current, input))
|
|
40835
41759
|
}
|
|
40836
41760
|
];
|
|
40837
41761
|
}
|
|
41762
|
+
function readStringInput(input, key2) {
|
|
41763
|
+
const value = Reflect.get(input, key2);
|
|
41764
|
+
return typeof value === "string" ? value : "";
|
|
41765
|
+
}
|
|
41766
|
+
function readNumberInput(input, key2) {
|
|
41767
|
+
const value = Reflect.get(input, key2);
|
|
41768
|
+
return typeof value === "number" ? value : Number.NaN;
|
|
41769
|
+
}
|
|
40838
41770
|
function useStudioAgentTools(deps) {
|
|
40839
41771
|
const depsRef = useRef68(deps);
|
|
40840
41772
|
depsRef.current = deps;
|
|
@@ -40866,8 +41798,27 @@ function useStudioAgentTools(deps) {
|
|
|
40866
41798
|
|
|
40867
41799
|
// src/webmcp/StudioAgentTools.tsx
|
|
40868
41800
|
function StudioAgentTools() {
|
|
40869
|
-
const { projectId, activeCompPath, editHistory } = useStudioShellContext();
|
|
40870
|
-
const {
|
|
41801
|
+
const { projectId, activeCompPath, editHistory, writeBlockedReason } = useStudioShellContext();
|
|
41802
|
+
const {
|
|
41803
|
+
domEditSelection,
|
|
41804
|
+
selectedGsapAnimations,
|
|
41805
|
+
gsapMultipleTimelines,
|
|
41806
|
+
gsapUnsupportedTimelinePattern
|
|
41807
|
+
} = useDomEditSelectionContext();
|
|
41808
|
+
const {
|
|
41809
|
+
previewIframeRef,
|
|
41810
|
+
buildDomSelectionFromTarget,
|
|
41811
|
+
applyDomSelection,
|
|
41812
|
+
handleDomTextCommit,
|
|
41813
|
+
handleDomStyleCommit,
|
|
41814
|
+
handleDomPathOffsetCommit,
|
|
41815
|
+
handleDomBoxSizeCommit,
|
|
41816
|
+
handleDomRotationCommit,
|
|
41817
|
+
handleGsapAddAnimation,
|
|
41818
|
+
handleGsapUpdateMeta,
|
|
41819
|
+
handleGsapAddKeyframeBatch,
|
|
41820
|
+
handleGsapDeleteAnimation
|
|
41821
|
+
} = useDomEditActionsContext();
|
|
40871
41822
|
const getSnapshot = useCallback60(() => {
|
|
40872
41823
|
const player = usePlayerStore.getState();
|
|
40873
41824
|
return {
|
|
@@ -40887,7 +41838,82 @@ function StudioAgentTools() {
|
|
|
40887
41838
|
}
|
|
40888
41839
|
};
|
|
40889
41840
|
}, [projectId, activeCompPath, domEditSelection, selectedGsapAnimations, editHistory]);
|
|
40890
|
-
|
|
41841
|
+
const deps = useMemo37(
|
|
41842
|
+
() => ({
|
|
41843
|
+
getSnapshot,
|
|
41844
|
+
getPreviewDocument: () => previewIframeRef.current?.contentDocument ?? null,
|
|
41845
|
+
buildSelection: (element) => buildDomSelectionFromTarget(element),
|
|
41846
|
+
applySelection: (selection) => applyDomSelection(selection, { revealPanel: true }),
|
|
41847
|
+
requestSeek: (time) => usePlayerStore.getState().requestSeek(time),
|
|
41848
|
+
readPlayhead: () => {
|
|
41849
|
+
const player = usePlayerStore.getState();
|
|
41850
|
+
return {
|
|
41851
|
+
currentTime: player.currentTime,
|
|
41852
|
+
duration: player.duration,
|
|
41853
|
+
isPlaying: player.isPlaying
|
|
41854
|
+
};
|
|
41855
|
+
},
|
|
41856
|
+
getProjectId: () => projectId,
|
|
41857
|
+
getCompositionPath: () => activeCompPath,
|
|
41858
|
+
// HEAD, not GET: the tool only needs to know the frame renders. Pulling
|
|
41859
|
+
// the PNG here would download it once for nothing, since the agent
|
|
41860
|
+
// fetches the URL itself.
|
|
41861
|
+
probeFrame: async (url) => {
|
|
41862
|
+
try {
|
|
41863
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
41864
|
+
return { ok: response.ok, status: response.status };
|
|
41865
|
+
} catch {
|
|
41866
|
+
return { ok: false, status: 0 };
|
|
41867
|
+
}
|
|
41868
|
+
},
|
|
41869
|
+
wait: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
41870
|
+
getCurrentSelection: () => domEditSelection,
|
|
41871
|
+
getWriteBlockedReason: () => writeBlockedReason,
|
|
41872
|
+
setText: (value, fieldKey) => handleDomTextCommit(value, fieldKey),
|
|
41873
|
+
setStyle: (property, value) => handleDomStyleCommit(property, value),
|
|
41874
|
+
// Measured, not authored: the tool compares this before and after to
|
|
41875
|
+
// tell a real change from a handler that did nothing and resolved.
|
|
41876
|
+
readBox: (selection) => {
|
|
41877
|
+
const rect = selection.element.getBoundingClientRect();
|
|
41878
|
+
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
41879
|
+
},
|
|
41880
|
+
moveTo: (selection, next) => handleDomPathOffsetCommit(selection, next),
|
|
41881
|
+
resizeTo: (selection, next) => handleDomBoxSizeCommit(selection, next),
|
|
41882
|
+
rotateTo: (selection, next) => handleDomRotationCommit(selection, next),
|
|
41883
|
+
addAnimation: (method) => handleGsapAddAnimation(method),
|
|
41884
|
+
updateAnimation: (animationId, updates) => handleGsapUpdateMeta(animationId, updates),
|
|
41885
|
+
addKeyframe: (animationId, percent3, properties) => handleGsapAddKeyframeBatch(animationId, percent3, properties),
|
|
41886
|
+
deleteAnimation: (animationId) => handleGsapDeleteAnimation(animationId),
|
|
41887
|
+
getGsapDiagnostics: () => ({
|
|
41888
|
+
animations: selectedGsapAnimations,
|
|
41889
|
+
multipleTimelines: gsapMultipleTimelines,
|
|
41890
|
+
unsupportedTimelinePattern: gsapUnsupportedTimelinePattern
|
|
41891
|
+
})
|
|
41892
|
+
}),
|
|
41893
|
+
[
|
|
41894
|
+
getSnapshot,
|
|
41895
|
+
previewIframeRef,
|
|
41896
|
+
buildDomSelectionFromTarget,
|
|
41897
|
+
applyDomSelection,
|
|
41898
|
+
projectId,
|
|
41899
|
+
activeCompPath,
|
|
41900
|
+
writeBlockedReason,
|
|
41901
|
+
handleDomTextCommit,
|
|
41902
|
+
handleDomStyleCommit,
|
|
41903
|
+
handleDomPathOffsetCommit,
|
|
41904
|
+
handleDomBoxSizeCommit,
|
|
41905
|
+
handleDomRotationCommit,
|
|
41906
|
+
handleGsapAddAnimation,
|
|
41907
|
+
handleGsapUpdateMeta,
|
|
41908
|
+
handleGsapAddKeyframeBatch,
|
|
41909
|
+
handleGsapDeleteAnimation,
|
|
41910
|
+
domEditSelection,
|
|
41911
|
+
selectedGsapAnimations,
|
|
41912
|
+
gsapMultipleTimelines,
|
|
41913
|
+
gsapUnsupportedTimelinePattern
|
|
41914
|
+
]
|
|
41915
|
+
);
|
|
41916
|
+
useStudioAgentTools(deps);
|
|
40891
41917
|
return null;
|
|
40892
41918
|
}
|
|
40893
41919
|
|
|
@@ -41215,13 +42241,13 @@ var SourceEditor = memo28(function SourceEditor2({
|
|
|
41215
42241
|
});
|
|
41216
42242
|
|
|
41217
42243
|
// src/components/editor/PropertyPanel.tsx
|
|
41218
|
-
import { memo as memo32, useMemo as
|
|
42244
|
+
import { memo as memo32, useMemo as useMemo53, useRef as useRef99, useState as useState90 } from "react";
|
|
41219
42245
|
|
|
41220
42246
|
// src/components/editor/InspectorHeaderActions.tsx
|
|
41221
42247
|
import { Eye as Eye3, EyeSlash as EyeSlash2 } from "@phosphor-icons/react";
|
|
41222
42248
|
|
|
41223
42249
|
// src/contexts/DesignPanelInputContext.tsx
|
|
41224
|
-
import { useCallback as useCallback63, useContext as useContext6, useMemo as
|
|
42250
|
+
import { useCallback as useCallback63, useContext as useContext6, useMemo as useMemo38 } from "react";
|
|
41225
42251
|
|
|
41226
42252
|
// src/utils/designInputTracking.ts
|
|
41227
42253
|
var COALESCE_WINDOW_MS = 600;
|
|
@@ -41264,7 +42290,7 @@ function DesignPanelInputProvider({
|
|
|
41264
42290
|
children
|
|
41265
42291
|
}) {
|
|
41266
42292
|
const parent = useContext6(DesignPanelInputContext);
|
|
41267
|
-
const value =
|
|
42293
|
+
const value = useMemo38(
|
|
41268
42294
|
() => ({ ui: ui ?? parent.ui, section: section ?? parent.section }),
|
|
41269
42295
|
[ui, section, parent.ui, parent.section]
|
|
41270
42296
|
);
|
|
@@ -42879,7 +43905,7 @@ function MediaSection({
|
|
|
42879
43905
|
import { isHfColorGradingActive as isHfColorGradingActive2 } from "@hyperframes/core/color-grading";
|
|
42880
43906
|
|
|
42881
43907
|
// src/components/editor/propertyPanelColorGradingControls.tsx
|
|
42882
|
-
import { useMemo as
|
|
43908
|
+
import { useMemo as useMemo39, useRef as useRef74, useState as useState53 } from "react";
|
|
42883
43909
|
import {
|
|
42884
43910
|
HF_COLOR_GRADING_PRESETS,
|
|
42885
43911
|
normalizeHfColorGrading
|
|
@@ -43323,7 +44349,7 @@ function ColorGradingControls({
|
|
|
43323
44349
|
const [detailSettings, setDetailSettings] = useState53(null);
|
|
43324
44350
|
const [lutImporting, setLutImporting] = useState53(false);
|
|
43325
44351
|
const [lutImportError, setLutImportError] = useState53(null);
|
|
43326
|
-
const lutAssets =
|
|
44352
|
+
const lutAssets = useMemo39(
|
|
43327
44353
|
() => assets.filter((asset) => LUT_EXT.test(asset)).sort((a, b) => a.localeCompare(b)),
|
|
43328
44354
|
[assets]
|
|
43329
44355
|
);
|
|
@@ -43603,7 +44629,7 @@ function ColorGradingControls({
|
|
|
43603
44629
|
}
|
|
43604
44630
|
|
|
43605
44631
|
// src/components/editor/useColorGradingController.ts
|
|
43606
|
-
import { useCallback as useCallback68, useEffect as useEffect57, useMemo as
|
|
44632
|
+
import { useCallback as useCallback68, useEffect as useEffect57, useMemo as useMemo40, useRef as useRef76, useState as useState55 } from "react";
|
|
43607
44633
|
import {
|
|
43608
44634
|
HF_COLOR_GRADING_ATTR,
|
|
43609
44635
|
hasHfColorGradingAuthoredValues,
|
|
@@ -44015,7 +45041,7 @@ function useColorGradingController({
|
|
|
44015
45041
|
state: "pending",
|
|
44016
45042
|
message: "Waiting for runtime"
|
|
44017
45043
|
}));
|
|
44018
|
-
const selectedAssetPath =
|
|
45044
|
+
const selectedAssetPath = useMemo40(
|
|
44019
45045
|
() => selectedMediaAssetPath(element, projectId),
|
|
44020
45046
|
[element, projectId]
|
|
44021
45047
|
);
|
|
@@ -44062,7 +45088,7 @@ function useColorGradingController({
|
|
|
44062
45088
|
trackStudioPendingEdit(onSetAttributeLive(COLOR_GRADING_DATA_KEY, value));
|
|
44063
45089
|
};
|
|
44064
45090
|
}, [identityKey]);
|
|
44065
|
-
const target =
|
|
45091
|
+
const target = useMemo40(
|
|
44066
45092
|
() => ({
|
|
44067
45093
|
id: element.id ?? null,
|
|
44068
45094
|
hfId: element.hfId ?? null,
|
|
@@ -45040,7 +46066,7 @@ function ColorField({
|
|
|
45040
46066
|
}
|
|
45041
46067
|
|
|
45042
46068
|
// src/components/editor/propertyPanelFont.tsx
|
|
45043
|
-
import { useEffect as useEffect59, useMemo as
|
|
46069
|
+
import { useEffect as useEffect59, useMemo as useMemo41, useRef as useRef78, useState as useState57 } from "react";
|
|
45044
46070
|
|
|
45045
46071
|
// src/components/editor/fontAssets.ts
|
|
45046
46072
|
var FONT_EXT_RE = /\.(eot|otf|ttc|ttf|woff2?)$/i;
|
|
@@ -45262,13 +46288,13 @@ function FontFamilyField({
|
|
|
45262
46288
|
setImportingFonts(false);
|
|
45263
46289
|
}
|
|
45264
46290
|
};
|
|
45265
|
-
const projectFontAssets =
|
|
46291
|
+
const projectFontAssets = useMemo41(
|
|
45266
46292
|
() => uniqueFontOptions(
|
|
45267
46293
|
importedFonts.map((f) => ({ family: f.family, source: "Imported" }))
|
|
45268
46294
|
),
|
|
45269
46295
|
[importedFonts]
|
|
45270
46296
|
);
|
|
45271
|
-
const options =
|
|
46297
|
+
const options = useMemo41(() => {
|
|
45272
46298
|
const documentFonts = collectDocumentFontFamilies();
|
|
45273
46299
|
const googleSet = new Set(googleFonts.map((f) => f.toLowerCase()));
|
|
45274
46300
|
const taggedLocal = localFonts.map(
|
|
@@ -45288,7 +46314,7 @@ function FontFamilyField({
|
|
|
45288
46314
|
])
|
|
45289
46315
|
);
|
|
45290
46316
|
}, [currentFamily, googleFonts, localFonts, projectFontAssets]);
|
|
45291
|
-
const filteredOptions =
|
|
46317
|
+
const filteredOptions = useMemo41(() => {
|
|
45292
46318
|
const matches = options.filter((o) => fontMatchesQuery(o.family, query));
|
|
45293
46319
|
if (query.trim()) return matches.slice(0, 200);
|
|
45294
46320
|
const bySource = /* @__PURE__ */ new Map();
|
|
@@ -45524,7 +46550,7 @@ function FontFamilyField({
|
|
|
45524
46550
|
import { useEffect as useEffect61 } from "react";
|
|
45525
46551
|
|
|
45526
46552
|
// src/components/panels/VariablesBindElement.tsx
|
|
45527
|
-
import { useMemo as
|
|
46553
|
+
import { useMemo as useMemo42, useState as useState59 } from "react";
|
|
45528
46554
|
|
|
45529
46555
|
// src/components/panels/VariablesValueControls.tsx
|
|
45530
46556
|
import { useState as useState58 } from "react";
|
|
@@ -45806,7 +46832,7 @@ function VariablesBindElement({
|
|
|
45806
46832
|
}) {
|
|
45807
46833
|
const [activeKey, setActiveKey] = useState59(null);
|
|
45808
46834
|
const [idDraft, setIdDraft] = useState59("");
|
|
45809
|
-
const actions =
|
|
46835
|
+
const actions = useMemo42(() => buildBindActions(selection, sdkSession), [selection, sdkSession]);
|
|
45810
46836
|
if (actions.length === 0) return null;
|
|
45811
46837
|
const active = actions.find((a) => a.key === activeKey) ?? null;
|
|
45812
46838
|
const trimmedId = sanitizeId(idDraft);
|
|
@@ -45917,7 +46943,7 @@ function uniqueId(base2, existing) {
|
|
|
45917
46943
|
}
|
|
45918
46944
|
|
|
45919
46945
|
// src/contexts/VariablePromoteContext.tsx
|
|
45920
|
-
import { useContext as useContext7, useEffect as useEffect60, useMemo as
|
|
46946
|
+
import { useContext as useContext7, useEffect as useEffect60, useMemo as useMemo43, useState as useState60 } from "react";
|
|
45921
46947
|
import { jsx as jsx100 } from "react/jsx-runtime";
|
|
45922
46948
|
var VariablePromoteContext = createStableContext(
|
|
45923
46949
|
"VariablePromoteContext",
|
|
@@ -45940,15 +46966,15 @@ function VariablePromoteProvider({
|
|
|
45940
46966
|
if (!session) return;
|
|
45941
46967
|
return session.on("change", () => setRevision((r) => r + 1));
|
|
45942
46968
|
}, [session]);
|
|
45943
|
-
const actions =
|
|
46969
|
+
const actions = useMemo43(() => {
|
|
45944
46970
|
if (!session || !selection) return [];
|
|
45945
46971
|
return buildBindActions(selection, session);
|
|
45946
46972
|
}, [session, selection, revision]);
|
|
45947
|
-
const declarations =
|
|
46973
|
+
const declarations = useMemo43(() => {
|
|
45948
46974
|
if (!session) return [];
|
|
45949
46975
|
return session.getVariableDeclarations();
|
|
45950
46976
|
}, [session, revision]);
|
|
45951
|
-
const value =
|
|
46977
|
+
const value = useMemo43(
|
|
45952
46978
|
() => ({ session, selection, actions, declarations, persist: persist2, onPersistError }),
|
|
45953
46979
|
[session, selection, actions, declarations, persist2, onPersistError]
|
|
45954
46980
|
);
|
|
@@ -45957,7 +46983,7 @@ function VariablePromoteProvider({
|
|
|
45957
46983
|
function useVariablePromoteChannel(channel) {
|
|
45958
46984
|
const ctx3 = useContext7(VariablePromoteContext);
|
|
45959
46985
|
const key2 = channel.kind === "style" ? `style:${channel.prop}` : channel.kind;
|
|
45960
|
-
return
|
|
46986
|
+
return useMemo43(() => {
|
|
45961
46987
|
if (!ctx3 || !ctx3.session || !ctx3.selection?.hfId) return null;
|
|
45962
46988
|
const { session, selection, actions, declarations, persist: persist2, onPersistError } = ctx3;
|
|
45963
46989
|
const hfId = selection.hfId;
|
|
@@ -46392,7 +47418,7 @@ function parseColorString(value) {
|
|
|
46392
47418
|
}
|
|
46393
47419
|
|
|
46394
47420
|
// src/components/editor/propertyPanelFill.tsx
|
|
46395
|
-
import { useMemo as
|
|
47421
|
+
import { useMemo as useMemo44, useRef as useRef79, useState as useState61 } from "react";
|
|
46396
47422
|
import { jsx as jsx102, jsxs as jsxs79 } from "react/jsx-runtime";
|
|
46397
47423
|
function normalizeProjectPath(value) {
|
|
46398
47424
|
const trimmed = value.trim();
|
|
@@ -46437,8 +47463,8 @@ function ImageFillField({
|
|
|
46437
47463
|
const fileInputRef = useRef79(null);
|
|
46438
47464
|
const [uploading, setUploading] = useState61(false);
|
|
46439
47465
|
const [uploadError, setUploadError] = useState61(null);
|
|
46440
|
-
const imageAssets =
|
|
46441
|
-
const selectedAsset =
|
|
47466
|
+
const imageAssets = useMemo44(() => assets.filter((a) => IMAGE_EXT.test(a)), [assets]);
|
|
47467
|
+
const selectedAsset = useMemo44(
|
|
46442
47468
|
() => resolveSelectedAsset(value, sourceFile, imageAssets),
|
|
46443
47469
|
[imageAssets, sourceFile, value]
|
|
46444
47470
|
);
|
|
@@ -47920,7 +48946,7 @@ import { useCallback as useCallback73, useState as useState66 } from "react";
|
|
|
47920
48946
|
import { useShallow } from "zustand/react/shallow";
|
|
47921
48947
|
|
|
47922
48948
|
// src/components/editor/AnimationCard.tsx
|
|
47923
|
-
import { memo as memo30, useCallback as useCallback72, useEffect as useEffect64, useMemo as
|
|
48949
|
+
import { memo as memo30, useCallback as useCallback72, useEffect as useEffect64, useMemo as useMemo45, useRef as useRef82, useState as useState65 } from "react";
|
|
47924
48950
|
import { SUPPORTED_EASES, SUPPORTED_PROPS } from "@hyperframes/core/gsap-constants";
|
|
47925
48951
|
|
|
47926
48952
|
// src/components/editor/gsapAnimationHelpers.ts
|
|
@@ -48461,21 +49487,21 @@ var AnimationCard = memo30(function AnimationCard2({
|
|
|
48461
49487
|
segment?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
48462
49488
|
pendingAutoScrollRef.current = false;
|
|
48463
49489
|
}, [expandedKfPct]);
|
|
48464
|
-
const usedProps =
|
|
49490
|
+
const usedProps = useMemo45(
|
|
48465
49491
|
() => new Set(Object.keys(animation.properties)),
|
|
48466
49492
|
[animation.properties]
|
|
48467
49493
|
);
|
|
48468
|
-
const availableProps =
|
|
49494
|
+
const availableProps = useMemo45(
|
|
48469
49495
|
() => SUPPORTED_PROPS.filter(
|
|
48470
49496
|
(p) => !usedProps.has(p) && (animation.method === "set" || !BOOLEAN_PROPS.has(p))
|
|
48471
49497
|
),
|
|
48472
49498
|
[usedProps, animation.method]
|
|
48473
49499
|
);
|
|
48474
|
-
const usedFromProps =
|
|
49500
|
+
const usedFromProps = useMemo45(
|
|
48475
49501
|
() => new Set(Object.keys(animation.fromProperties ?? {})),
|
|
48476
49502
|
[animation.fromProperties]
|
|
48477
49503
|
);
|
|
48478
|
-
const availableFromProps =
|
|
49504
|
+
const availableFromProps = useMemo45(
|
|
48479
49505
|
() => SUPPORTED_PROPS.filter((p) => !usedFromProps.has(p) && !BOOLEAN_PROPS.has(p)),
|
|
48480
49506
|
[usedFromProps]
|
|
48481
49507
|
);
|
|
@@ -48522,7 +49548,7 @@ var AnimationCard = memo30(function AnimationCard2({
|
|
|
48522
49548
|
const easeName = (animation.keyframes ? animation.keyframes.easeEach : void 0) ?? animation.ease ?? "none";
|
|
48523
49549
|
const easeLabel = easeName.startsWith("custom(") ? "Custom curve" : EASE_LABELS[easeName] ?? easeName;
|
|
48524
49550
|
const endTime = typeof animation.position === "number" ? animation.position + (animation.duration ?? 0) : animation.position;
|
|
48525
|
-
const summary =
|
|
49551
|
+
const summary = useMemo45(() => buildTweenSummary(animation), [animation]);
|
|
48526
49552
|
const setKeys = Object.keys(animation.properties);
|
|
48527
49553
|
if (animation.method === "set" && // `every` is vacuously true on an empty bag — require at least one key so a
|
|
48528
49554
|
// property-less set doesn't masquerade as a position row.
|
|
@@ -49670,7 +50696,7 @@ function PropertyPanel3dTransform({
|
|
|
49670
50696
|
}
|
|
49671
50697
|
|
|
49672
50698
|
// src/components/editor/PropertyPanelFlat.tsx
|
|
49673
|
-
import { useEffect as useEffect84, useMemo as
|
|
50699
|
+
import { useEffect as useEffect84, useMemo as useMemo52, useRef as useRef98, useState as useState89 } from "react";
|
|
49674
50700
|
import { useShallow as useShallow2 } from "zustand/react/shallow";
|
|
49675
50701
|
|
|
49676
50702
|
// src/components/editor/audioFxSummary.ts
|
|
@@ -51740,7 +52766,7 @@ function motionSectionLabel(args) {
|
|
|
51740
52766
|
}
|
|
51741
52767
|
|
|
51742
52768
|
// src/components/editor/propertyPanelAudioFxGroup.tsx
|
|
51743
|
-
import { useMemo as
|
|
52769
|
+
import { useMemo as useMemo48, useState as useState77 } from "react";
|
|
51744
52770
|
import {
|
|
51745
52771
|
HF_AUDIO_FX_ATTR as HF_AUDIO_FX_ATTR6,
|
|
51746
52772
|
HF_AUDIO_FX_DATA_KEY as HF_AUDIO_FX_DATA_KEY2,
|
|
@@ -51799,7 +52825,7 @@ function automationAttrValue(automation) {
|
|
|
51799
52825
|
}
|
|
51800
52826
|
|
|
51801
52827
|
// src/components/editor/propertyPanelFxSection.tsx
|
|
51802
|
-
import { useCallback as useCallback76, useEffect as useEffect71, useMemo as
|
|
52828
|
+
import { useCallback as useCallback76, useEffect as useEffect71, useMemo as useMemo47, useRef as useRef86, useState as useState75 } from "react";
|
|
51803
52829
|
|
|
51804
52830
|
// src/components/editor/audioFxRevealTarget.ts
|
|
51805
52831
|
import { parseAutomationTarget as parseAutomationTarget2 } from "@hyperframes/core/audio-automation";
|
|
@@ -52745,7 +53771,7 @@ function FxEqModule({
|
|
|
52745
53771
|
import { getAudioFxPreset as getAudioFxPreset3 } from "@hyperframes/core/audio-fx-presets";
|
|
52746
53772
|
|
|
52747
53773
|
// src/components/editor/propertyPanelFxNodeRow.tsx
|
|
52748
|
-
import { useMemo as
|
|
53774
|
+
import { useMemo as useMemo46, useState as useState74 } from "react";
|
|
52749
53775
|
import {
|
|
52750
53776
|
defaultAudioFxParams as defaultAudioFxParams3,
|
|
52751
53777
|
getAudioFxDef as getAudioFxDef3
|
|
@@ -53171,11 +54197,11 @@ function FxNodeRow({
|
|
|
53171
54197
|
trackKind
|
|
53172
54198
|
}) {
|
|
53173
54199
|
const registryDef = getAudioFxDef3(node.type);
|
|
53174
|
-
const def =
|
|
54200
|
+
const def = useMemo46(() => registryDef ? plainDef(registryDef) : null, [registryDef]);
|
|
53175
54201
|
const primary = registryDef ? primaryParamOf(registryDef) : null;
|
|
53176
54202
|
const profile = getAudioFxProfile2(node.type);
|
|
53177
|
-
const derived =
|
|
53178
|
-
const onlyPrimary =
|
|
54203
|
+
const derived = useMemo46(() => profileParam(node.type), [node.type]);
|
|
54204
|
+
const onlyPrimary = useMemo46(
|
|
53179
54205
|
() => def && primary ? { ...def, params: def.params.filter((p) => p.key === primary) } : def,
|
|
53180
54206
|
[def, primary]
|
|
53181
54207
|
);
|
|
@@ -53821,12 +54847,12 @@ function FxSection({
|
|
|
53821
54847
|
[chain.nodes, mutate, onRemoveNodeAutomation, trackKind]
|
|
53822
54848
|
);
|
|
53823
54849
|
const [carveOpen, setCarveOpen] = useState75(true);
|
|
53824
|
-
const carveNodes =
|
|
53825
|
-
const handBuilt =
|
|
54850
|
+
const carveNodes = useMemo47(() => chain.nodes.filter((n) => n.fromCarve), [chain.nodes]);
|
|
54851
|
+
const handBuilt = useMemo47(
|
|
53826
54852
|
() => chain.nodes.map((node, i) => ({ node, i })).filter(({ node }) => !node.fromCarve && !node.fromEq),
|
|
53827
54853
|
[chain.nodes]
|
|
53828
54854
|
);
|
|
53829
|
-
const runs =
|
|
54855
|
+
const runs = useMemo47(() => {
|
|
53830
54856
|
const out = [];
|
|
53831
54857
|
for (const item of handBuilt) {
|
|
53832
54858
|
const preset = item.node.fromPreset;
|
|
@@ -53837,8 +54863,8 @@ function FxSection({
|
|
|
53837
54863
|
return out;
|
|
53838
54864
|
}, [handBuilt]);
|
|
53839
54865
|
const [collapsedRuns, setCollapsedRuns] = useState75(/* @__PURE__ */ new Set());
|
|
53840
|
-
const eqIds =
|
|
53841
|
-
const positions =
|
|
54866
|
+
const eqIds = useMemo47(() => audioEqIds(chain), [chain]);
|
|
54867
|
+
const positions = useMemo47(() => {
|
|
53842
54868
|
const map = /* @__PURE__ */ new Map();
|
|
53843
54869
|
let at = (showCarve ? 1 : 0) + eqIds.length;
|
|
53844
54870
|
for (const { i } of handBuilt) map.set(i, ++at);
|
|
@@ -54574,7 +55600,7 @@ function AudioFxGroup({
|
|
|
54574
55600
|
const removeNodeAutomation = (nodeId) => removeNodesAutomation([nodeId]);
|
|
54575
55601
|
const [analysing, setAnalysing] = useState77(false);
|
|
54576
55602
|
const storeElements = usePlayerStore((s) => s.elements);
|
|
54577
|
-
const signalPath =
|
|
55603
|
+
const signalPath = useMemo48(() => {
|
|
54578
55604
|
const doc = element.element?.ownerDocument;
|
|
54579
55605
|
return audioFxSignalPath(
|
|
54580
55606
|
element.tagName?.toLowerCase(),
|
|
@@ -55051,7 +56077,7 @@ function createGsapLivePreview(iframeRef) {
|
|
|
55051
56077
|
}
|
|
55052
56078
|
|
|
55053
56079
|
// src/components/editor/propertyPanelFlatColorGradingSection.tsx
|
|
55054
|
-
import { useEffect as useEffect81, useMemo as
|
|
56080
|
+
import { useEffect as useEffect81, useMemo as useMemo50, useRef as useRef97, useState as useState85 } from "react";
|
|
55055
56081
|
import {
|
|
55056
56082
|
HF_COLOR_GRADING_GRADE_PRESETS,
|
|
55057
56083
|
normalizeHfColorGrading as normalizeHfColorGrading5,
|
|
@@ -55086,7 +56112,7 @@ function presetPreviewHandlers({
|
|
|
55086
56112
|
import { useState as useState81 } from "react";
|
|
55087
56113
|
|
|
55088
56114
|
// src/components/editor/propertyPanelColorCurveGraph.tsx
|
|
55089
|
-
import { useMemo as
|
|
56115
|
+
import { useMemo as useMemo49, useRef as useRef90 } from "react";
|
|
55090
56116
|
import {
|
|
55091
56117
|
compileHfColorCurve,
|
|
55092
56118
|
compileHfHueCurve,
|
|
@@ -55274,8 +56300,8 @@ function CurveGraph({
|
|
|
55274
56300
|
onCancel
|
|
55275
56301
|
}) {
|
|
55276
56302
|
const pointerRef = useRef90(null);
|
|
55277
|
-
const samples =
|
|
55278
|
-
const path =
|
|
56303
|
+
const samples = useMemo49(() => samplesFor(points, tab), [points, tab]);
|
|
56304
|
+
const path = useMemo49(() => curvePath(samples, tab), [samples, tab]);
|
|
55279
56305
|
const selectRelativePoint = (offset) => {
|
|
55280
56306
|
if (points.length === 0) return;
|
|
55281
56307
|
const current = selectedIndex ?? (offset > 0 ? -1 : 0);
|
|
@@ -57072,14 +58098,14 @@ function FlatColorGradingSection({
|
|
|
57072
58098
|
const lutInputRef = useRef97(null);
|
|
57073
58099
|
const [lutOpen, setLutOpen] = useState85(false);
|
|
57074
58100
|
const [detailSettingsOpen, setDetailSettingsOpen] = useState85(null);
|
|
57075
|
-
const lutAssets =
|
|
58101
|
+
const lutAssets = useMemo50(
|
|
57076
58102
|
() => assets.filter((asset) => LUT_EXT.test(asset)).sort((a, b) => a.localeCompare(b)),
|
|
57077
58103
|
[assets]
|
|
57078
58104
|
);
|
|
57079
58105
|
const lut = grading.lut;
|
|
57080
58106
|
const selectedLutName = lut?.src ? lut.src.split("/").pop() ?? lut.src : null;
|
|
57081
|
-
const resolvedGrading =
|
|
57082
|
-
const secondaryInputGrading =
|
|
58107
|
+
const resolvedGrading = useMemo50(() => resolveColorGrading(grading), [grading]);
|
|
58108
|
+
const secondaryInputGrading = useMemo50(
|
|
57083
58109
|
() => resolveColorGrading({
|
|
57084
58110
|
intensity: 1,
|
|
57085
58111
|
adjust: resolvedGrading.adjust,
|
|
@@ -57097,7 +58123,7 @@ function FlatColorGradingSection({
|
|
|
57097
58123
|
]
|
|
57098
58124
|
);
|
|
57099
58125
|
const actions = createColorGradingActions(grading, onCommitColorGrading);
|
|
57100
|
-
const scopesRefreshKey =
|
|
58126
|
+
const scopesRefreshKey = useMemo50(() => serializeHfColorGrading2(grading), [grading]);
|
|
57101
58127
|
useEffect81(() => {
|
|
57102
58128
|
if (presetPreviews.status === "idle") onRequestPresetPreviews();
|
|
57103
58129
|
}, [onRequestPresetPreviews, presetPreviews.status]);
|
|
@@ -58227,7 +59253,7 @@ function FlatEffectsSection({
|
|
|
58227
59253
|
import { useState as useState88 } from "react";
|
|
58228
59254
|
|
|
58229
59255
|
// src/hooks/useBlockCatalog.ts
|
|
58230
|
-
import { useState as useState87, useEffect as useEffect83, useMemo as
|
|
59256
|
+
import { useState as useState87, useEffect as useEffect83, useMemo as useMemo51 } from "react";
|
|
58231
59257
|
|
|
58232
59258
|
// src/utils/blockCategories.ts
|
|
58233
59259
|
import {
|
|
@@ -58294,7 +59320,7 @@ function useBlockCatalog() {
|
|
|
58294
59320
|
cancelled = true;
|
|
58295
59321
|
};
|
|
58296
59322
|
}, []);
|
|
58297
|
-
const filteredBlocks =
|
|
59323
|
+
const filteredBlocks = useMemo51(() => {
|
|
58298
59324
|
let result = blocks;
|
|
58299
59325
|
if (category) {
|
|
58300
59326
|
result = result.filter((b) => b.category === category);
|
|
@@ -58606,7 +59632,7 @@ function PropertyPanelFlat({
|
|
|
58606
59632
|
const showMotionEffects = gsapEffectHandlers !== null && !audioSelection;
|
|
58607
59633
|
const showMotionGroup = showMotionTiming || showMotionEffects;
|
|
58608
59634
|
const volumeAutomation = useVolumeAutomation(element, onSetAttributeQuiet ?? onSetAttributeLive);
|
|
58609
|
-
const audioGroupLabel =
|
|
59635
|
+
const audioGroupLabel = useMemo52(() => {
|
|
58610
59636
|
const doc = element.element?.ownerDocument;
|
|
58611
59637
|
const id = element.id;
|
|
58612
59638
|
if (!doc || !id) return void 0;
|
|
@@ -59167,12 +60193,12 @@ var PropertyPanel = memo32(function PropertyPanel2(props) {
|
|
|
59167
60193
|
const cacheEntry = usePlayerStore((s) => s.keyframeCache.get(cacheElementKey));
|
|
59168
60194
|
const iframeRef = previewIframeRef ?? { current: null };
|
|
59169
60195
|
const gsapAnimIdForMemo = element ? gsapAnimations?.find((a) => a.keyframes)?.id ?? gsapAnimations?.[0]?.id ?? null : null;
|
|
59170
|
-
const gsapRuntimeValues =
|
|
60196
|
+
const gsapRuntimeValues = useMemo53(
|
|
59171
60197
|
() => element ? readGsapRuntimeValuesForPanel(gsapAnimIdForMemo, gsapAnimations, element, iframeRef) : null,
|
|
59172
60198
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- iframeRef is stable; currentTime drives re-reads during playback
|
|
59173
60199
|
[gsapAnimIdForMemo, gsapAnimations, element, currentTime]
|
|
59174
60200
|
);
|
|
59175
|
-
const gsapBorderRadius =
|
|
60201
|
+
const gsapBorderRadius = useMemo53(
|
|
59176
60202
|
() => element ? readGsapBorderRadiusForPanel(gsapRuntimeValues, gsapAnimations, element, iframeRef) : null,
|
|
59177
60203
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59178
60204
|
[gsapRuntimeValues, gsapAnimations, element, currentTime]
|
|
@@ -59562,11 +60588,11 @@ var PropertyPanel = memo32(function PropertyPanel2(props) {
|
|
|
59562
60588
|
});
|
|
59563
60589
|
|
|
59564
60590
|
// src/components/editor/FileTree.tsx
|
|
59565
|
-
import { memo as memo34, useState as useState92, useCallback as useCallback78, useMemo as
|
|
60591
|
+
import { memo as memo34, useState as useState92, useCallback as useCallback78, useMemo as useMemo55, useRef as useRef101 } from "react";
|
|
59566
60592
|
import { Plus as Plus2, FolderSimplePlus as FolderSimplePlus2 } from "@phosphor-icons/react";
|
|
59567
60593
|
|
|
59568
60594
|
// src/components/editor/FileTreeNodes.tsx
|
|
59569
|
-
import { memo as memo33, useState as useState91, useCallback as useCallback77, useMemo as
|
|
60595
|
+
import { memo as memo33, useState as useState91, useCallback as useCallback77, useMemo as useMemo54, useRef as useRef100, useEffect as useEffect85 } from "react";
|
|
59570
60596
|
import {
|
|
59571
60597
|
PencilSimple,
|
|
59572
60598
|
Copy,
|
|
@@ -60049,7 +61075,7 @@ var TreeFolder = memo33(function TreeFolder2({
|
|
|
60049
61075
|
}) {
|
|
60050
61076
|
const [isOpen, setIsOpen] = useState91(defaultOpen);
|
|
60051
61077
|
const toggle = useCallback77(() => setIsOpen((v) => !v), []);
|
|
60052
|
-
const children =
|
|
61078
|
+
const children = useMemo54(() => sortChildren(node.children), [node.children]);
|
|
60053
61079
|
const Chevron = isOpen ? ChevronDown : ChevronRight;
|
|
60054
61080
|
const isDragOver = dragOverFolder === node.fullPath;
|
|
60055
61081
|
const isRenaming = inlineInput?.mode === "rename" && inlineInput.originalPath === node.fullPath;
|
|
@@ -60180,8 +61206,8 @@ var FileTree = memo34(function FileTree2({
|
|
|
60180
61206
|
onImportFiles,
|
|
60181
61207
|
lintFindingsByFile
|
|
60182
61208
|
}) {
|
|
60183
|
-
const tree =
|
|
60184
|
-
const children =
|
|
61209
|
+
const tree = useMemo55(() => buildTree(files), [files]);
|
|
61210
|
+
const children = useMemo55(() => sortChildren(tree.children), [tree]);
|
|
60185
61211
|
const [contextMenu, setContextMenu] = useState92(null);
|
|
60186
61212
|
const [inlineInput, setInlineInput] = useState92(null);
|
|
60187
61213
|
const [deleteTarget, setDeleteTarget] = useState92(null);
|
|
@@ -60448,10 +61474,10 @@ var FileTree = memo34(function FileTree2({
|
|
|
60448
61474
|
});
|
|
60449
61475
|
|
|
60450
61476
|
// src/App.tsx
|
|
60451
|
-
import { useState as useState155, useCallback as useCallback176, useRef as useRef170, useMemo as
|
|
61477
|
+
import { useState as useState155, useCallback as useCallback176, useRef as useRef170, useMemo as useMemo77, useLayoutEffect as useLayoutEffect10 } from "react";
|
|
60452
61478
|
|
|
60453
61479
|
// src/components/renders/useRenderQueue.ts
|
|
60454
|
-
import { useState as useState94, useEffect as useEffect87, useCallback as useCallback80, useRef as useRef103, useMemo as
|
|
61480
|
+
import { useState as useState94, useEffect as useEffect87, useCallback as useCallback80, useRef as useRef103, useMemo as useMemo56 } from "react";
|
|
60455
61481
|
|
|
60456
61482
|
// src/components/renders/serverError.ts
|
|
60457
61483
|
async function readServerError(res) {
|
|
@@ -60975,7 +62001,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
|
|
|
60975
62001
|
};
|
|
60976
62002
|
}, [projectId]);
|
|
60977
62003
|
const isRendering = jobs.some((j) => j.status === "rendering");
|
|
60978
|
-
return
|
|
62004
|
+
return useMemo56(
|
|
60979
62005
|
() => ({
|
|
60980
62006
|
jobs,
|
|
60981
62007
|
isRendering,
|
|
@@ -61032,8 +62058,8 @@ function useDialogBehavior({
|
|
|
61032
62058
|
const onCloseRef = useRef104(onClose);
|
|
61033
62059
|
onCloseRef.current = onClose;
|
|
61034
62060
|
const requestClose = useCallback81(() => {
|
|
61035
|
-
const
|
|
61036
|
-
if (
|
|
62061
|
+
const guard3 = canCloseRef.current;
|
|
62062
|
+
if (guard3 && !guard3()) return;
|
|
61037
62063
|
onCloseRef.current();
|
|
61038
62064
|
}, []);
|
|
61039
62065
|
useEffect88(() => {
|
|
@@ -62242,7 +63268,7 @@ function useCaptionSync(projectId) {
|
|
|
62242
63268
|
}
|
|
62243
63269
|
|
|
62244
63270
|
// src/hooks/usePersistentEditHistory.ts
|
|
62245
|
-
import { useCallback as useCallback84, useEffect as useEffect90, useMemo as
|
|
63271
|
+
import { useCallback as useCallback84, useEffect as useEffect90, useMemo as useMemo57, useRef as useRef110, useState as useState99 } from "react";
|
|
62246
63272
|
|
|
62247
63273
|
// src/utils/editHistory.ts
|
|
62248
63274
|
var DEFAULT_MAX_ENTRIES = 100;
|
|
@@ -62594,7 +63620,7 @@ function createPersistentEditHistoryStore({
|
|
|
62594
63620
|
};
|
|
62595
63621
|
}
|
|
62596
63622
|
function usePersistentEditHistory(options) {
|
|
62597
|
-
const storage =
|
|
63623
|
+
const storage = useMemo57(
|
|
62598
63624
|
() => options.storage ?? createIndexedDbEditHistoryStorage(),
|
|
62599
63625
|
[options.storage]
|
|
62600
63626
|
);
|
|
@@ -62863,10 +63889,10 @@ function usePanelLayout(initialState2) {
|
|
|
62863
63889
|
}
|
|
62864
63890
|
|
|
62865
63891
|
// src/hooks/useFileManager.ts
|
|
62866
|
-
import { useState as useState102, useCallback as useCallback88, useMemo as
|
|
63892
|
+
import { useState as useState102, useCallback as useCallback88, useMemo as useMemo59, useRef as useRef113 } from "react";
|
|
62867
63893
|
|
|
62868
63894
|
// src/hooks/useFileTree.ts
|
|
62869
|
-
import { useState as useState101, useCallback as useCallback86, useEffect as useEffect92, useMemo as
|
|
63895
|
+
import { useState as useState101, useCallback as useCallback86, useEffect as useEffect92, useMemo as useMemo58 } from "react";
|
|
62870
63896
|
function useFileTree({ projectId, projectIdRef }) {
|
|
62871
63897
|
const [projectDir, setProjectDir] = useState101(null);
|
|
62872
63898
|
const [fileTree, setFileTree] = useState101([]);
|
|
@@ -62902,11 +63928,11 @@ function useFileTree({ projectId, projectIdRef }) {
|
|
|
62902
63928
|
if (data.files) setFileTree(data.files);
|
|
62903
63929
|
}, [projectIdRef]);
|
|
62904
63930
|
const compositions = compositionPaths;
|
|
62905
|
-
const assets =
|
|
63931
|
+
const assets = useMemo58(
|
|
62906
63932
|
() => fileTree.filter((f) => !f.endsWith(".html") && !f.endsWith(".md") && !f.endsWith(".json")),
|
|
62907
63933
|
[fileTree]
|
|
62908
63934
|
);
|
|
62909
|
-
const fontAssets =
|
|
63935
|
+
const fontAssets = useMemo58(
|
|
62910
63936
|
() => assets.filter((asset) => FONT_EXT.test(asset)).map((asset) => ({
|
|
62911
63937
|
family: fontFamilyFromAssetPath(asset),
|
|
62912
63938
|
path: asset,
|
|
@@ -62928,6 +63954,7 @@ function useFileTree({ projectId, projectIdRef }) {
|
|
|
62928
63954
|
|
|
62929
63955
|
// src/hooks/useEditorSave.ts
|
|
62930
63956
|
import { useCallback as useCallback87, useRef as useRef112 } from "react";
|
|
63957
|
+
var FAILURE_BURST_MS = 5e3;
|
|
62931
63958
|
function useEditorSave({
|
|
62932
63959
|
editingPathRef,
|
|
62933
63960
|
projectIdRef,
|
|
@@ -62940,18 +63967,32 @@ function useEditorSave({
|
|
|
62940
63967
|
}) {
|
|
62941
63968
|
const saveRafRef = useRef112(null);
|
|
62942
63969
|
const refreshRafRef = useRef112(null);
|
|
62943
|
-
const lastFailureToastAtRef = useRef112(
|
|
63970
|
+
const lastFailureToastAtRef = useRef112(null);
|
|
63971
|
+
const lastFailureReportRef = useRef112(null);
|
|
62944
63972
|
const pendingCandidateRef = useRef112(null);
|
|
62945
63973
|
const inFlightRef = useRef112(null);
|
|
62946
63974
|
const inFlightCandidateRef = useRef112(null);
|
|
62947
63975
|
const reportFailure = useCallback87(
|
|
62948
63976
|
(path, error) => {
|
|
62949
|
-
|
|
63977
|
+
const now2 = Date.now();
|
|
63978
|
+
const properties = buildStudioSaveFailureProperties({
|
|
62950
63979
|
source: "code_editor",
|
|
62951
|
-
|
|
63980
|
+
error,
|
|
63981
|
+
filePath: path
|
|
62952
63982
|
});
|
|
62953
|
-
const
|
|
62954
|
-
|
|
63983
|
+
const errorName = error instanceof Error ? error.name : typeof error;
|
|
63984
|
+
const fingerprint = JSON.stringify([
|
|
63985
|
+
path,
|
|
63986
|
+
errorName,
|
|
63987
|
+
properties.error_message,
|
|
63988
|
+
properties.status_code
|
|
63989
|
+
]);
|
|
63990
|
+
const previous = lastFailureReportRef.current;
|
|
63991
|
+
if (previous === null || previous.fingerprint !== fingerprint || now2 - previous.emittedAt >= FAILURE_BURST_MS) {
|
|
63992
|
+
trackStudioSaveFailure({ source: "code_editor", error, filePath: path });
|
|
63993
|
+
lastFailureReportRef.current = { fingerprint, emittedAt: now2 };
|
|
63994
|
+
}
|
|
63995
|
+
if (lastFailureToastAtRef.current === null || now2 - lastFailureToastAtRef.current >= FAILURE_BURST_MS) {
|
|
62955
63996
|
lastFailureToastAtRef.current = now2;
|
|
62956
63997
|
showToast(
|
|
62957
63998
|
`Couldn't save ${path} \u2014 your latest edits are NOT persisted. Check the preview server; editing again retries the save.`,
|
|
@@ -62974,6 +64015,7 @@ function useEditorSave({
|
|
|
62974
64015
|
recordEdit
|
|
62975
64016
|
}).then(() => {
|
|
62976
64017
|
if (pendingCandidateRef.current === candidate) pendingCandidateRef.current = null;
|
|
64018
|
+
lastFailureReportRef.current = null;
|
|
62977
64019
|
if (refreshRafRef.current != null) cancelAnimationFrame(refreshRafRef.current);
|
|
62978
64020
|
refreshRafRef.current = requestAnimationFrame(() => setRefreshKey((k) => k + 1));
|
|
62979
64021
|
return { status: "clean" };
|
|
@@ -63053,7 +64095,7 @@ function useFileManager({
|
|
|
63053
64095
|
const projectIdRef = useRef113(projectId);
|
|
63054
64096
|
projectIdRef.current = projectId;
|
|
63055
64097
|
const importedFontAssetsRef = useRef113([]);
|
|
63056
|
-
const fileVersionScope =
|
|
64098
|
+
const fileVersionScope = useMemo59(
|
|
63057
64099
|
() => ({ projectId, versions: /* @__PURE__ */ new Map() }),
|
|
63058
64100
|
[projectId]
|
|
63059
64101
|
);
|
|
@@ -63853,52 +64895,6 @@ import { useCallback as useCallback100, useRef as useRef118 } from "react";
|
|
|
63853
64895
|
// src/hooks/useRazorSplit.ts
|
|
63854
64896
|
import { useCallback as useCallback91, useRef as useRef116 } from "react";
|
|
63855
64897
|
|
|
63856
|
-
// src/utils/projectRouting.ts
|
|
63857
|
-
var PROJECT_HASH_PREFIX = "#project/";
|
|
63858
|
-
function decodeHashProjectId(value) {
|
|
63859
|
-
try {
|
|
63860
|
-
return decodeURIComponent(value);
|
|
63861
|
-
} catch {
|
|
63862
|
-
return value;
|
|
63863
|
-
}
|
|
63864
|
-
}
|
|
63865
|
-
function normalizeHashParams(params) {
|
|
63866
|
-
if (!params) return new URLSearchParams();
|
|
63867
|
-
if (params instanceof URLSearchParams) return params;
|
|
63868
|
-
const next = new URLSearchParams();
|
|
63869
|
-
for (const [key2, value] of Object.entries(params)) {
|
|
63870
|
-
if (!key2 || value == null || value === "") continue;
|
|
63871
|
-
next.set(key2, value);
|
|
63872
|
-
}
|
|
63873
|
-
return next;
|
|
63874
|
-
}
|
|
63875
|
-
function encodeProjectId(projectId) {
|
|
63876
|
-
return encodeURIComponent(projectId);
|
|
63877
|
-
}
|
|
63878
|
-
function buildProjectHash(projectId, params) {
|
|
63879
|
-
const search = normalizeHashParams(params).toString();
|
|
63880
|
-
return `${PROJECT_HASH_PREFIX}${encodeProjectId(projectId)}${search ? `?${search}` : ""}`;
|
|
63881
|
-
}
|
|
63882
|
-
function parseProjectHashRoute(hash) {
|
|
63883
|
-
if (!hash.startsWith(PROJECT_HASH_PREFIX)) return null;
|
|
63884
|
-
const route = hash.slice(PROJECT_HASH_PREFIX.length);
|
|
63885
|
-
const queryIndex = route.indexOf("?");
|
|
63886
|
-
const encodedProjectId = queryIndex >= 0 ? route.slice(0, queryIndex) : route;
|
|
63887
|
-
if (!encodedProjectId || encodedProjectId.includes("/")) return null;
|
|
63888
|
-
const rawParams = queryIndex >= 0 ? route.slice(queryIndex + 1) : "";
|
|
63889
|
-
return {
|
|
63890
|
-
projectId: decodeHashProjectId(encodedProjectId),
|
|
63891
|
-
params: new URLSearchParams(rawParams)
|
|
63892
|
-
};
|
|
63893
|
-
}
|
|
63894
|
-
function parseProjectIdFromHash(hash) {
|
|
63895
|
-
return parseProjectHashRoute(hash)?.projectId ?? null;
|
|
63896
|
-
}
|
|
63897
|
-
function buildProjectApiPath(projectId, suffix = "") {
|
|
63898
|
-
const normalizedSuffix = suffix && !suffix.startsWith("/") ? `/${suffix}` : suffix;
|
|
63899
|
-
return `/api/projects/${encodeProjectId(projectId)}${normalizedSuffix}`;
|
|
63900
|
-
}
|
|
63901
|
-
|
|
63902
64898
|
// src/utils/razorSplitTransaction.ts
|
|
63903
64899
|
function targetIdentity(path, target) {
|
|
63904
64900
|
if (target.hfId) return `${path}|hf:${target.hfId}`;
|
|
@@ -68603,7 +69599,7 @@ function useGroupCommits(params) {
|
|
|
68603
69599
|
}
|
|
68604
69600
|
|
|
68605
69601
|
// src/hooks/useGsapScriptCommits.ts
|
|
68606
|
-
import { useCallback as useCallback115, useMemo as
|
|
69602
|
+
import { useCallback as useCallback115, useMemo as useMemo60, useRef as useRef126 } from "react";
|
|
68607
69603
|
import { findUnsafeMutationValues } from "@hyperframes/core/studio-api/finite-mutation";
|
|
68608
69604
|
|
|
68609
69605
|
// src/hooks/gsapRuntimePatch.ts
|
|
@@ -69918,7 +70914,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
|
|
|
69918
70914
|
const { instantPatch: _instantPatch, ...batchOptions } = options;
|
|
69919
70915
|
await finalizeSuccessfulMutation(pid, compositionPath, last.selection, last.mutation, targetPath, result, instantPatches.length > 0 ? { ...batchOptions, instantPatches } : batchOptions);
|
|
69920
70916
|
}, [showToast, finalizeSuccessfulMutation]);
|
|
69921
|
-
const commitMutation =
|
|
70917
|
+
const commitMutation = useMemo60(() => {
|
|
69922
70918
|
const serializeFile = (file, task) => {
|
|
69923
70919
|
if (writeProjectFile) {
|
|
69924
70920
|
return serializeStudioFileMutation(writeProjectFile, file, task);
|
|
@@ -69983,7 +70979,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
|
|
|
69983
70979
|
},
|
|
69984
70980
|
[activeProjectId]
|
|
69985
70981
|
);
|
|
69986
|
-
const sdkDeps =
|
|
70982
|
+
const sdkDeps = useMemo60(
|
|
69987
70983
|
() => writeProjectFile ? {
|
|
69988
70984
|
editHistory: { recordEdit: editHistory.recordEdit },
|
|
69989
70985
|
writeProjectFile,
|
|
@@ -70189,7 +71185,8 @@ import { useCallback as useCallback117 } from "react";
|
|
|
70189
71185
|
function useGsapInteractionFailureTelemetry(activeCompPath, showToast) {
|
|
70190
71186
|
return useCallback117(
|
|
70191
71187
|
(error, selection, mutationType, label) => {
|
|
70192
|
-
trackStudioSaveFailure
|
|
71188
|
+
const report = isGsapEditBlockedError(error) ? trackStudioEditBlocked : trackStudioSaveFailure;
|
|
71189
|
+
report({
|
|
70193
71190
|
source: "gsap_commit",
|
|
70194
71191
|
error,
|
|
70195
71192
|
filePath: selection?.sourceFile ?? activeCompPath ?? "index.html",
|
|
@@ -72977,7 +73974,7 @@ function useStudioExternalFileChanges({
|
|
|
72977
73974
|
}
|
|
72978
73975
|
|
|
72979
73976
|
// src/hooks/useBlockHandlers.ts
|
|
72980
|
-
import { useCallback as useCallback127, useMemo as
|
|
73977
|
+
import { useCallback as useCallback127, useMemo as useMemo61, useRef as useRef133, useState as useState109 } from "react";
|
|
72981
73978
|
|
|
72982
73979
|
// src/utils/blockInstaller.ts
|
|
72983
73980
|
function getMaxZIndexFromIframe(iframe) {
|
|
@@ -73163,7 +74160,7 @@ function useBlockHandlers({
|
|
|
73163
74160
|
setRightPanelTab
|
|
73164
74161
|
}) {
|
|
73165
74162
|
const [activeBlockParams, setActiveBlockParams] = useState109(null);
|
|
73166
|
-
const blockCtx =
|
|
74163
|
+
const blockCtx = useMemo61(
|
|
73167
74164
|
() => ({
|
|
73168
74165
|
activeCompPath: blockCtxDeps.activeCompPath,
|
|
73169
74166
|
timelineElements: blockCtxDeps.timelineElements,
|
|
@@ -74248,7 +75245,7 @@ import { useCallback as useCallback132 } from "react";
|
|
|
74248
75245
|
import { createElement as createElement3 } from "react";
|
|
74249
75246
|
|
|
74250
75247
|
// src/player/components/AudioWaveform.tsx
|
|
74251
|
-
import { memo as memo36, useCallback as useCallback130, useMemo as
|
|
75248
|
+
import { memo as memo36, useCallback as useCallback130, useMemo as useMemo62, useRef as useRef137 } from "react";
|
|
74252
75249
|
import { jsx as jsx167, jsxs as jsxs139 } from "react/jsx-runtime";
|
|
74253
75250
|
var BAR_WIDTH = 2;
|
|
74254
75251
|
var BAR_STEP = 3;
|
|
@@ -74308,7 +75305,7 @@ var AudioWaveform = memo36(function AudioWaveform2({
|
|
|
74308
75305
|
const canvasRef = useRef137(null);
|
|
74309
75306
|
const observerRef = useRef137(null);
|
|
74310
75307
|
const cacheKey = waveformUrl ?? audioUrl;
|
|
74311
|
-
const request =
|
|
75308
|
+
const request = useMemo62(
|
|
74312
75309
|
() => ({
|
|
74313
75310
|
key: createThumbnailKey({ kind: "waveform", source: cacheKey }),
|
|
74314
75311
|
projectId,
|
|
@@ -74417,7 +75414,7 @@ var AudioWaveform = memo36(function AudioWaveform2({
|
|
|
74417
75414
|
});
|
|
74418
75415
|
|
|
74419
75416
|
// src/player/components/ImageThumbnail.tsx
|
|
74420
|
-
import { memo as memo37, useCallback as useCallback131, useMemo as
|
|
75417
|
+
import { memo as memo37, useCallback as useCallback131, useMemo as useMemo63, useRef as useRef138, useState as useState110 } from "react";
|
|
74421
75418
|
import { jsx as jsx168, jsxs as jsxs140 } from "react/jsx-runtime";
|
|
74422
75419
|
var ImageThumbnail = memo37(function ImageThumbnail2({
|
|
74423
75420
|
imageSrc,
|
|
@@ -74430,7 +75427,7 @@ var ImageThumbnail = memo37(function ImageThumbnail2({
|
|
|
74430
75427
|
}) {
|
|
74431
75428
|
const [containerWidth, setContainerWidth] = useState110(0);
|
|
74432
75429
|
const observerRef = useRef138(null);
|
|
74433
|
-
const request =
|
|
75430
|
+
const request = useMemo63(
|
|
74434
75431
|
() => ({
|
|
74435
75432
|
key: createThumbnailKey({ kind: "image", source: imageSrc, rich: Number(rich) }),
|
|
74436
75433
|
projectId,
|
|
@@ -74747,36 +75744,6 @@ function useConsoleErrorCapture(previewIframe) {
|
|
|
74747
75744
|
|
|
74748
75745
|
// src/hooks/useFrameCapture.ts
|
|
74749
75746
|
import { useState as useState112, useCallback as useCallback134, useRef as useRef140 } from "react";
|
|
74750
|
-
|
|
74751
|
-
// src/utils/frameCapture.ts
|
|
74752
|
-
function normalizeCompositionPath(compositionPath) {
|
|
74753
|
-
return compositionPath && compositionPath !== "master" ? compositionPath : "index.html";
|
|
74754
|
-
}
|
|
74755
|
-
function buildFrameCaptureUrl({
|
|
74756
|
-
projectId,
|
|
74757
|
-
compositionPath,
|
|
74758
|
-
currentTime,
|
|
74759
|
-
origin = window.location.origin
|
|
74760
|
-
}) {
|
|
74761
|
-
const compPath = normalizeCompositionPath(compositionPath);
|
|
74762
|
-
const url = new URL(
|
|
74763
|
-
buildProjectApiPath(projectId, `/thumbnail/${encodeURIComponent(compPath)}`),
|
|
74764
|
-
origin
|
|
74765
|
-
);
|
|
74766
|
-
url.searchParams.set("t", Math.max(0, currentTime).toFixed(3));
|
|
74767
|
-
url.searchParams.set("format", "png");
|
|
74768
|
-
url.searchParams.set("output", "source");
|
|
74769
|
-
url.searchParams.set("v", String(Date.now()));
|
|
74770
|
-
return url.toString();
|
|
74771
|
-
}
|
|
74772
|
-
function buildFrameCaptureFilename(compositionPath, currentTime) {
|
|
74773
|
-
const compPath = normalizeCompositionPath(compositionPath);
|
|
74774
|
-
const base2 = compPath.split("/").pop()?.replace(/\.html$/i, "") || "frame";
|
|
74775
|
-
const frameTime = Math.max(0, currentTime).toFixed(3).replace(".", "-");
|
|
74776
|
-
return `${base2}-${frameTime}s.png`;
|
|
74777
|
-
}
|
|
74778
|
-
|
|
74779
|
-
// src/hooks/useFrameCapture.ts
|
|
74780
75747
|
function useFrameCapture({
|
|
74781
75748
|
projectId,
|
|
74782
75749
|
activeCompPath,
|
|
@@ -74870,7 +75837,7 @@ function useFrameCapture({
|
|
|
74870
75837
|
}
|
|
74871
75838
|
|
|
74872
75839
|
// src/hooks/useLintModal.ts
|
|
74873
|
-
import { useState as useState113, useCallback as useCallback135, useEffect as useEffect108, useRef as useRef141, useMemo as
|
|
75840
|
+
import { useState as useState113, useCallback as useCallback135, useEffect as useEffect108, useRef as useRef141, useMemo as useMemo64 } from "react";
|
|
74874
75841
|
function parseFinding(f) {
|
|
74875
75842
|
return {
|
|
74876
75843
|
severity: f.severity === "error" ? "error" : "warning",
|
|
@@ -74942,8 +75909,8 @@ function useLintModal(projectId, refreshKey) {
|
|
|
74942
75909
|
},
|
|
74943
75910
|
[backgroundFindings]
|
|
74944
75911
|
);
|
|
74945
|
-
const findingsByElement =
|
|
74946
|
-
const findingsByFile =
|
|
75912
|
+
const findingsByElement = useMemo64(() => groupFindings((f) => f.elementId), [groupFindings]);
|
|
75913
|
+
const findingsByFile = useMemo64(() => groupFindings((f) => f.file), [groupFindings]);
|
|
74947
75914
|
useEffect108(() => {
|
|
74948
75915
|
usePlayerStore.getState().setLintFindingsByElement(findingsByElement);
|
|
74949
75916
|
}, [findingsByElement]);
|
|
@@ -75425,16 +76392,16 @@ function useStudioUrlState({
|
|
|
75425
76392
|
}
|
|
75426
76393
|
|
|
75427
76394
|
// src/hooks/useEffectiveTimelineDuration.ts
|
|
75428
|
-
import { useMemo as
|
|
76395
|
+
import { useMemo as useMemo65 } from "react";
|
|
75429
76396
|
function useEffectiveTimelineDuration(timelineDuration2, timelineElements) {
|
|
75430
|
-
return
|
|
76397
|
+
return useMemo65(
|
|
75431
76398
|
() => getEffectiveTimelineDuration(timelineDuration2, timelineElements),
|
|
75432
76399
|
[timelineDuration2, timelineElements]
|
|
75433
76400
|
);
|
|
75434
76401
|
}
|
|
75435
76402
|
|
|
75436
76403
|
// src/hooks/useStudioContextValue.ts
|
|
75437
|
-
import { useCallback as useCallback139, useMemo as
|
|
76404
|
+
import { useCallback as useCallback139, useMemo as useMemo66, useRef as useRef144, useState as useState116 } from "react";
|
|
75438
76405
|
function buildStudioContextValue(input) {
|
|
75439
76406
|
return {
|
|
75440
76407
|
projectId: input.projectId,
|
|
@@ -75449,6 +76416,9 @@ function buildStudioContextValue(input) {
|
|
|
75449
76416
|
timelineElements: input.timelineElements,
|
|
75450
76417
|
isPlaying: input.isPlaying,
|
|
75451
76418
|
editHistory: input.editHistory,
|
|
76419
|
+
// Conflict first: when both are true the conflict is the one the user has
|
|
76420
|
+
// been asked to decide, and resolving it is what unblocks the queue.
|
|
76421
|
+
writeBlockedReason: input.externalFileConflict ? "an external change to this file is waiting to be resolved" : input.domEditSaveQueuePaused,
|
|
75452
76422
|
handleUndo: input.handleUndo,
|
|
75453
76423
|
handleRedo: input.handleRedo,
|
|
75454
76424
|
renderQueue: input.renderQueue,
|
|
@@ -75459,7 +76429,7 @@ function buildStudioContextValue(input) {
|
|
|
75459
76429
|
};
|
|
75460
76430
|
}
|
|
75461
76431
|
function useInspectorState(rightPanelTab, rightInspectorPanes, rightCollapsed, isPlaying, domEditSelection, isGestureRecording) {
|
|
75462
|
-
return
|
|
76432
|
+
return useMemo66(() => {
|
|
75463
76433
|
const inspectorTabActive = rightPanelTab === "design" || rightPanelTab === "layers";
|
|
75464
76434
|
const layersPanelActive = inspectorTabActive && rightInspectorPanes.layers;
|
|
75465
76435
|
const designPanelActive = inspectorTabActive && rightInspectorPanes.design;
|
|
@@ -75532,7 +76502,7 @@ function useGlobalFileDrop(handleTimelineFileDrop) {
|
|
|
75532
76502
|
import { useRef as useRef146 } from "react";
|
|
75533
76503
|
|
|
75534
76504
|
// src/contexts/PanelLayoutContext.tsx
|
|
75535
|
-
import { useContext as useContext8, useMemo as
|
|
76505
|
+
import { useContext as useContext8, useMemo as useMemo67 } from "react";
|
|
75536
76506
|
import { jsx as jsx169 } from "react/jsx-runtime";
|
|
75537
76507
|
var PanelLayoutContext = createStableContext("PanelLayoutContext", null);
|
|
75538
76508
|
function usePanelLayoutContext() {
|
|
@@ -75562,7 +76532,7 @@ function PanelLayoutProvider({
|
|
|
75562
76532
|
},
|
|
75563
76533
|
children
|
|
75564
76534
|
}) {
|
|
75565
|
-
const stable =
|
|
76535
|
+
const stable = useMemo67(
|
|
75566
76536
|
() => ({
|
|
75567
76537
|
leftWidth,
|
|
75568
76538
|
rightWidth,
|
|
@@ -75610,7 +76580,7 @@ import {
|
|
|
75610
76580
|
useCallback as useCallback140,
|
|
75611
76581
|
useContext as useContext9,
|
|
75612
76582
|
useEffect as useEffect110,
|
|
75613
|
-
useMemo as
|
|
76583
|
+
useMemo as useMemo68,
|
|
75614
76584
|
useRef as useRef145,
|
|
75615
76585
|
useState as useState117
|
|
75616
76586
|
} from "react";
|
|
@@ -75642,8 +76612,8 @@ function useViewModeState() {
|
|
|
75642
76612
|
const guardsRef = useRef145(/* @__PURE__ */ new Set());
|
|
75643
76613
|
const acceptedLocationRef = useRef145(readHistoryLocation());
|
|
75644
76614
|
const canSetViewMode = useCallback140((mode) => {
|
|
75645
|
-
for (const
|
|
75646
|
-
if (!
|
|
76615
|
+
for (const guard3 of guardsRef.current) {
|
|
76616
|
+
if (!guard3(mode)) return false;
|
|
75647
76617
|
}
|
|
75648
76618
|
return true;
|
|
75649
76619
|
}, []);
|
|
@@ -75673,13 +76643,13 @@ function useViewModeState() {
|
|
|
75673
76643
|
},
|
|
75674
76644
|
[canSetViewMode]
|
|
75675
76645
|
);
|
|
75676
|
-
const registerViewModeGuard = useCallback140((
|
|
75677
|
-
guardsRef.current.add(
|
|
76646
|
+
const registerViewModeGuard = useCallback140((guard3) => {
|
|
76647
|
+
guardsRef.current.add(guard3);
|
|
75678
76648
|
return () => {
|
|
75679
|
-
guardsRef.current.delete(
|
|
76649
|
+
guardsRef.current.delete(guard3);
|
|
75680
76650
|
};
|
|
75681
76651
|
}, []);
|
|
75682
|
-
return
|
|
76652
|
+
return useMemo68(
|
|
75683
76653
|
() => ({ viewMode, setViewMode, registerViewModeGuard }),
|
|
75684
76654
|
[viewMode, setViewMode, registerViewModeGuard]
|
|
75685
76655
|
);
|
|
@@ -76869,7 +77839,7 @@ function useGestureCommit({
|
|
|
76869
77839
|
}
|
|
76870
77840
|
|
|
76871
77841
|
// src/components/editor/GestureTrailOverlay.tsx
|
|
76872
|
-
import { memo as memo38, useMemo as
|
|
77842
|
+
import { memo as memo38, useMemo as useMemo69 } from "react";
|
|
76873
77843
|
import { Fragment as Fragment49, jsx as jsx172, jsxs as jsxs142 } from "react/jsx-runtime";
|
|
76874
77844
|
var GestureTrailOverlay = memo38(function GestureTrailOverlay2({
|
|
76875
77845
|
samples,
|
|
@@ -76881,7 +77851,7 @@ var GestureTrailOverlay = memo38(function GestureTrailOverlay2({
|
|
|
76881
77851
|
mode,
|
|
76882
77852
|
accentColor = "#3CE6AC"
|
|
76883
77853
|
}) {
|
|
76884
|
-
const trailPoints =
|
|
77854
|
+
const trailPoints = useMemo69(() => {
|
|
76885
77855
|
if (!canvasRect) return "";
|
|
76886
77856
|
if (trail2 && trail2.length > 1) {
|
|
76887
77857
|
return trail2.map((p) => `${p.x - canvasRect.left},${p.y - canvasRect.top}`).join(" ");
|
|
@@ -76889,7 +77859,7 @@ var GestureTrailOverlay = memo38(function GestureTrailOverlay2({
|
|
|
76889
77859
|
if (samples.length === 0) return "";
|
|
76890
77860
|
return samples.filter((s) => s.properties.x != null && s.properties.y != null).map((s) => `${s.properties.x},${s.properties.y}`).join(" ");
|
|
76891
77861
|
}, [samples, trail2, sampleCount, canvasRect?.left, canvasRect?.top]);
|
|
76892
|
-
const simplifiedPath =
|
|
77862
|
+
const simplifiedPath = useMemo69(() => {
|
|
76893
77863
|
if (!simplifiedPoints || simplifiedPoints.size === 0) return "";
|
|
76894
77864
|
const pts = [];
|
|
76895
77865
|
for (const [pct, props] of simplifiedPoints) {
|
|
@@ -76901,7 +77871,7 @@ var GestureTrailOverlay = memo38(function GestureTrailOverlay2({
|
|
|
76901
77871
|
if (pts.length === 0) return "";
|
|
76902
77872
|
return pts.map((p) => `${p.x},${p.y}`).join(" ");
|
|
76903
77873
|
}, [simplifiedPoints]);
|
|
76904
|
-
const diamondPositions =
|
|
77874
|
+
const diamondPositions = useMemo69(() => {
|
|
76905
77875
|
if (!simplifiedPoints || simplifiedPoints.size === 0) return [];
|
|
76906
77876
|
const pts = [];
|
|
76907
77877
|
for (const [pct, props] of simplifiedPoints) {
|
|
@@ -77300,7 +78270,7 @@ var CompositionsTab = memo39(function CompositionsTab2({
|
|
|
77300
78270
|
});
|
|
77301
78271
|
|
|
77302
78272
|
// src/components/sidebar/AssetsTab.tsx
|
|
77303
|
-
import { memo as memo40, useState as useState126, useCallback as useCallback147, useRef as useRef153, useMemo as
|
|
78273
|
+
import { memo as memo40, useState as useState126, useCallback as useCallback147, useRef as useRef153, useMemo as useMemo71, useEffect as useEffect119 } from "react";
|
|
77304
78274
|
|
|
77305
78275
|
// src/components/ui/SearchInput.tsx
|
|
77306
78276
|
import { jsx as jsx174, jsxs as jsxs144 } from "react/jsx-runtime";
|
|
@@ -77912,7 +78882,7 @@ function AudioRow({
|
|
|
77912
78882
|
}
|
|
77913
78883
|
|
|
77914
78884
|
// src/components/sidebar/GlobalAssetsView.tsx
|
|
77915
|
-
import { useEffect as useEffect116, useMemo as
|
|
78885
|
+
import { useEffect as useEffect116, useMemo as useMemo70, useState as useState123 } from "react";
|
|
77916
78886
|
import { jsx as jsx177, jsxs as jsxs147 } from "react/jsx-runtime";
|
|
77917
78887
|
function globalAssetRows(records, query = "") {
|
|
77918
78888
|
const q = query.trim().toLowerCase();
|
|
@@ -77939,7 +78909,7 @@ function GlobalAssetsView({ searchQuery }) {
|
|
|
77939
78909
|
cancelled = true;
|
|
77940
78910
|
};
|
|
77941
78911
|
}, []);
|
|
77942
|
-
const rows =
|
|
78912
|
+
const rows = useMemo70(() => globalAssetRows(records ?? [], searchQuery), [records, searchQuery]);
|
|
77943
78913
|
if (records === null) {
|
|
77944
78914
|
return /* @__PURE__ */ jsx177("p", { className: "px-4 py-3 text-[11px] text-panel-text-5", children: "Loading global assets\u2026" });
|
|
77945
78915
|
}
|
|
@@ -78498,12 +79468,12 @@ var AssetsTab = memo40(function AssetsTab2({
|
|
|
78498
79468
|
setTimeout(() => setCopyFeedback(null), copied ? 1500 : 3e3);
|
|
78499
79469
|
}, []);
|
|
78500
79470
|
const elements = usePlayerStore((s) => s.elements);
|
|
78501
|
-
const usedPaths =
|
|
78502
|
-
const allMediaAssets =
|
|
79471
|
+
const usedPaths = useMemo71(() => deriveUsedPaths(elements), [elements]);
|
|
79472
|
+
const allMediaAssets = useMemo71(
|
|
78503
79473
|
() => assets.filter((a) => MEDIA_EXT.test(a) || FONT_EXT.test(a)),
|
|
78504
79474
|
[assets]
|
|
78505
79475
|
);
|
|
78506
|
-
const mediaAssets =
|
|
79476
|
+
const mediaAssets = useMemo71(() => {
|
|
78507
79477
|
const all = filterByUsage(allMediaAssets, usedPaths, usageFilter);
|
|
78508
79478
|
if (!searchQuery) return all;
|
|
78509
79479
|
const q = searchQuery.toLowerCase();
|
|
@@ -78513,7 +79483,7 @@ var AssetsTab = memo40(function AssetsTab2({
|
|
|
78513
79483
|
return rec?.description?.toLowerCase().includes(q);
|
|
78514
79484
|
});
|
|
78515
79485
|
}, [allMediaAssets, searchQuery, manifest, usageFilter, usedPaths]);
|
|
78516
|
-
const categorized =
|
|
79486
|
+
const categorized = useMemo71(() => {
|
|
78517
79487
|
const groups = { audio: [], images: [], video: [], fonts: [] };
|
|
78518
79488
|
for (const a of mediaAssets) {
|
|
78519
79489
|
const cat = getCategory(a);
|
|
@@ -78528,12 +79498,12 @@ var AssetsTab = memo40(function AssetsTab2({
|
|
|
78528
79498
|
}
|
|
78529
79499
|
return groups;
|
|
78530
79500
|
}, [mediaAssets, usedPaths]);
|
|
78531
|
-
const counts =
|
|
79501
|
+
const counts = useMemo71(() => {
|
|
78532
79502
|
const c = { all: mediaAssets.length };
|
|
78533
79503
|
for (const cat of FILTER_ORDER) c[cat] = categorized[cat].length;
|
|
78534
79504
|
return c;
|
|
78535
79505
|
}, [mediaAssets, categorized]);
|
|
78536
|
-
const usageCounts =
|
|
79506
|
+
const usageCounts = useMemo71(
|
|
78537
79507
|
() => countUsage(
|
|
78538
79508
|
assets.filter((a) => MEDIA_EXT.test(a) || FONT_EXT.test(a)),
|
|
78539
79509
|
usedPaths
|
|
@@ -82896,7 +83866,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
|
|
|
82896
83866
|
}
|
|
82897
83867
|
|
|
82898
83868
|
// src/components/panels/VariablesPanel.tsx
|
|
82899
|
-
import { memo as memo51, useCallback as useCallback163, useEffect as useEffect129, useMemo as
|
|
83869
|
+
import { memo as memo51, useCallback as useCallback163, useEffect as useEffect129, useMemo as useMemo72, useState as useState144 } from "react";
|
|
82900
83870
|
|
|
82901
83871
|
// src/hooks/useVariablesPersist.ts
|
|
82902
83872
|
import { useCallback as useCallback160 } from "react";
|
|
@@ -83600,22 +84570,22 @@ var VariablesPanel = memo51(function VariablesPanel2({
|
|
|
83600
84570
|
domEditSaveTimestampRef,
|
|
83601
84571
|
publishSdkSession
|
|
83602
84572
|
});
|
|
83603
|
-
const declarations =
|
|
84573
|
+
const declarations = useMemo72(
|
|
83604
84574
|
() => sdkSession?.getVariableDeclarations() ?? [],
|
|
83605
84575
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
83606
84576
|
[sdkSession, refreshKey, revision]
|
|
83607
84577
|
);
|
|
83608
|
-
const usage =
|
|
84578
|
+
const usage = useMemo72(
|
|
83609
84579
|
() => sdkSession?.getVariableUsage() ?? null,
|
|
83610
84580
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
83611
84581
|
[sdkSession, refreshKey, revision]
|
|
83612
84582
|
);
|
|
83613
|
-
const issues =
|
|
84583
|
+
const issues = useMemo72(
|
|
83614
84584
|
() => previewValues && sdkSession ? sdkSession.validateVariableValues(previewValues) : [],
|
|
83615
84585
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
83616
84586
|
[sdkSession, previewValues, refreshKey, revision]
|
|
83617
84587
|
);
|
|
83618
|
-
const effectiveValues =
|
|
84588
|
+
const effectiveValues = useMemo72(
|
|
83619
84589
|
() => sdkSession?.getVariableValues(previewValues ?? void 0) ?? {},
|
|
83620
84590
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
83621
84591
|
[sdkSession, previewValues, refreshKey, revision]
|
|
@@ -83954,15 +84924,15 @@ function useSlideshowPersist({
|
|
|
83954
84924
|
}
|
|
83955
84925
|
|
|
83956
84926
|
// src/hooks/useSlideshowTabState.ts
|
|
83957
|
-
import { useEffect as useEffect130, useMemo as
|
|
84927
|
+
import { useEffect as useEffect130, useMemo as useMemo73 } from "react";
|
|
83958
84928
|
import { SLIDESHOW_ISLAND_TYPE as SLIDESHOW_ISLAND_TYPE2, slideshowIslandRegex as slideshowIslandRegex2 } from "@hyperframes/core/slideshow";
|
|
83959
84929
|
function useSlideshowTabState(params) {
|
|
83960
84930
|
const { editingFileContent, previewIframeRef, refreshKey, rightPanelTab, setRightPanelTab } = params;
|
|
83961
|
-
const isSlideshowComposition =
|
|
84931
|
+
const isSlideshowComposition = useMemo73(() => {
|
|
83962
84932
|
if (!editingFileContent || !editingFileContent.includes(SLIDESHOW_ISLAND_TYPE2)) return false;
|
|
83963
84933
|
return slideshowIslandRegex2("i").test(editingFileContent);
|
|
83964
84934
|
}, [editingFileContent]);
|
|
83965
|
-
const slideshowScenes =
|
|
84935
|
+
const slideshowScenes = useMemo73(() => {
|
|
83966
84936
|
try {
|
|
83967
84937
|
const win = previewIframeRef.current?.contentWindow;
|
|
83968
84938
|
return (win?.__clipManifest?.scenes ?? []).map((s) => ({
|
|
@@ -85634,7 +86604,7 @@ function useProjectSignaturePoll(projectId, currentSignature, onChange) {
|
|
|
85634
86604
|
}
|
|
85635
86605
|
|
|
85636
86606
|
// src/components/storyboard/StoryboardLoaded.tsx
|
|
85637
|
-
import { useEffect as useEffect141, useMemo as
|
|
86607
|
+
import { useEffect as useEffect141, useMemo as useMemo76, useState as useState152 } from "react";
|
|
85638
86608
|
|
|
85639
86609
|
// src/components/storyboard/StoryboardDirection.tsx
|
|
85640
86610
|
import { jsx as jsx205, jsxs as jsxs170 } from "react/jsx-runtime";
|
|
@@ -85871,7 +86841,7 @@ function StoryboardScriptPanel({ script }) {
|
|
|
85871
86841
|
}
|
|
85872
86842
|
|
|
85873
86843
|
// src/components/storyboard/StoryboardSourceEditor.tsx
|
|
85874
|
-
import { useCallback as useCallback172, useEffect as useEffect137, useMemo as
|
|
86844
|
+
import { useCallback as useCallback172, useEffect as useEffect137, useMemo as useMemo74, useRef as useRef169, useState as useState148 } from "react";
|
|
85875
86845
|
import { marked } from "marked";
|
|
85876
86846
|
import DOMPurify from "dompurify";
|
|
85877
86847
|
import { jsx as jsx210, jsxs as jsxs173 } from "react/jsx-runtime";
|
|
@@ -85930,7 +86900,7 @@ function useMarkdownPreview(source) {
|
|
|
85930
86900
|
const id = window.setTimeout(() => setDebounced(source), 200);
|
|
85931
86901
|
return () => window.clearTimeout(id);
|
|
85932
86902
|
}, [source]);
|
|
85933
|
-
return
|
|
86903
|
+
return useMemo74(() => {
|
|
85934
86904
|
const html2 = marked.parse(debounced, { async: false });
|
|
85935
86905
|
DOMPurify.addHook("afterSanitizeAttributes", hardenLinks);
|
|
85936
86906
|
try {
|
|
@@ -86602,7 +87572,7 @@ function progressLabel(summary) {
|
|
|
86602
87572
|
}
|
|
86603
87573
|
|
|
86604
87574
|
// src/components/storyboard/useFrameComments.ts
|
|
86605
|
-
import { useCallback as useCallback174, useEffect as useEffect140, useMemo as
|
|
87575
|
+
import { useCallback as useCallback174, useEffect as useEffect140, useMemo as useMemo75, useState as useState151 } from "react";
|
|
86606
87576
|
|
|
86607
87577
|
// src/components/storyboard/frameComments.ts
|
|
86608
87578
|
var FRAME_COMMENTS_PATH = ".hyperframes/frame-comments.json";
|
|
@@ -86684,7 +87654,7 @@ function useFrameComments(frames) {
|
|
|
86684
87654
|
const setDraft = useCallback174((index, text) => {
|
|
86685
87655
|
setDrafts((prev) => ({ ...prev, [index]: text }));
|
|
86686
87656
|
}, []);
|
|
86687
|
-
const draftCount =
|
|
87657
|
+
const draftCount = useMemo75(
|
|
86688
87658
|
() => Object.values(drafts).filter((text) => text.trim().length > 0).length,
|
|
86689
87659
|
[drafts]
|
|
86690
87660
|
);
|
|
@@ -86752,7 +87722,7 @@ function StoryboardLoaded({
|
|
|
86752
87722
|
setFeedbackMessageCopied(false);
|
|
86753
87723
|
}
|
|
86754
87724
|
};
|
|
86755
|
-
const sourceFiles =
|
|
87725
|
+
const sourceFiles = useMemo76(() => {
|
|
86756
87726
|
const files = [{ path: data.path, label: data.path }];
|
|
86757
87727
|
if (data.script?.exists) files.push({ path: data.script.path, label: data.script.path });
|
|
86758
87728
|
return files;
|
|
@@ -87238,7 +88208,7 @@ function StudioApp() {
|
|
|
87238
88208
|
domEditSaveTimestampRef,
|
|
87239
88209
|
setRefreshKey
|
|
87240
88210
|
});
|
|
87241
|
-
const masterCompPath =
|
|
88211
|
+
const masterCompPath = useMemo77(
|
|
87242
88212
|
() => resolveMasterCompositionPath(fileManager.fileTree),
|
|
87243
88213
|
[fileManager.fileTree]
|
|
87244
88214
|
);
|
|
@@ -87558,11 +88528,13 @@ function StudioApp() {
|
|
|
87558
88528
|
handleRedo: appHotkeys.handleRedo,
|
|
87559
88529
|
renderQueue,
|
|
87560
88530
|
compositionDimensions,
|
|
88531
|
+
domEditSaveQueuePaused: previewPersistence.domEditSaveQueuePaused,
|
|
88532
|
+
externalFileConflict: externalFileChanges.blocked !== null,
|
|
87561
88533
|
waitForPendingDomEditSaves: previewPersistence.waitForPendingDomEditSaves,
|
|
87562
88534
|
handlePreviewIframeRef,
|
|
87563
88535
|
refreshPreviewDocumentVersion
|
|
87564
88536
|
});
|
|
87565
|
-
const timelineToolbar =
|
|
88537
|
+
const timelineToolbar = useMemo77(
|
|
87566
88538
|
() => /* @__PURE__ */ jsx217(
|
|
87567
88539
|
TimelineToolbar,
|
|
87568
88540
|
{
|