hyperframes 0.7.24 → 0.7.26
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/cli.js +5872 -1672
- package/dist/commands/layout-audit.browser.js +35 -2
- package/dist/hyperframe-runtime.js +1 -1
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +1 -1
- package/dist/skills/hyperframes/SKILL.md +10 -9
- package/dist/studio/assets/{index-DsVS-Zmd.js → index-BPya4QRs.js} +1 -1
- package/dist/studio/assets/{index-CnZuUYe6.js → index-BZqvc9Ns.js} +185 -185
- package/dist/studio/assets/{index-CGW4LOlP.js → index-D3eh3pRK.js} +1 -1
- package/dist/studio/index.d.ts +6 -0
- package/dist/studio/index.html +1 -1
- package/dist/studio/index.js +377 -210
- package/dist/studio/index.js.map +1 -1
- package/package.json +1 -1
package/dist/studio/index.js
CHANGED
|
@@ -1305,6 +1305,8 @@ var usePlayerStore = create((set, get) => ({
|
|
|
1305
1305
|
setMotionPathArmed: (armed) => set({ motionPathArmed: armed }),
|
|
1306
1306
|
motionPathCreateAvailable: false,
|
|
1307
1307
|
setMotionPathCreateAvailable: (available) => set({ motionPathCreateAvailable: available }),
|
|
1308
|
+
autoKeyframeEnabled: true,
|
|
1309
|
+
setAutoKeyframeEnabled: (enabled) => set({ autoKeyframeEnabled: enabled }),
|
|
1308
1310
|
selectedElementIds: /* @__PURE__ */ new Set(),
|
|
1309
1311
|
toggleSelectedElementId: (id) => set((s) => {
|
|
1310
1312
|
const next = new Set(s.selectedElementIds);
|
|
@@ -4902,10 +4904,18 @@ var TimelineClipDiamonds = memo6(function TimelineClipDiamonds2({
|
|
|
4902
4904
|
onClickKeyframe,
|
|
4903
4905
|
onShiftClickKeyframe,
|
|
4904
4906
|
onContextMenuKeyframe,
|
|
4905
|
-
onMoveKeyframe
|
|
4907
|
+
onMoveKeyframe,
|
|
4908
|
+
suppressClickRef
|
|
4906
4909
|
}) {
|
|
4907
4910
|
const dragRef = useRef11(null);
|
|
4908
4911
|
const [preview, setPreview] = useState10(null);
|
|
4912
|
+
const suppressNextClick = () => {
|
|
4913
|
+
if (!suppressClickRef) return;
|
|
4914
|
+
suppressClickRef.current = true;
|
|
4915
|
+
requestAnimationFrame(() => {
|
|
4916
|
+
suppressClickRef.current = false;
|
|
4917
|
+
});
|
|
4918
|
+
};
|
|
4909
4919
|
if (clipWidthPx < 20) return null;
|
|
4910
4920
|
const diamondSize = Math.round(clipHeightPx * (beatsActive ? 0.45 : DIAMOND_RATIO));
|
|
4911
4921
|
const half = diamondSize / 2;
|
|
@@ -4915,151 +4925,171 @@ var TimelineClipDiamonds = memo6(function TimelineClipDiamonds2({
|
|
|
4915
4925
|
const baseColor = isSelected ? accentColor : "#a3a3a3";
|
|
4916
4926
|
const baseOpacity = isSelected ? 0.4 : 0.25;
|
|
4917
4927
|
const canDrag = isSelected && !!onMoveKeyframe;
|
|
4918
|
-
return /* @__PURE__ */ jsxs11(
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4928
|
+
return /* @__PURE__ */ jsxs11(
|
|
4929
|
+
"div",
|
|
4930
|
+
{
|
|
4931
|
+
className: "absolute inset-0",
|
|
4932
|
+
style: {
|
|
4933
|
+
// Above the clip's trim-handle strips (TimelineClip.tsx, z-index 4) so
|
|
4934
|
+
// a keyframe sitting in the first/last ~14px of the clip stays
|
|
4935
|
+
// clickable instead of being covered by the resize handle. This div
|
|
4936
|
+
// establishes its own stacking context (position + z-index), so the
|
|
4937
|
+
// diamonds' own z-index (1/2) can't escape it on their own — the bump
|
|
4938
|
+
// has to happen here.
|
|
4939
|
+
zIndex: 5,
|
|
4940
|
+
pointerEvents: "none"
|
|
4941
|
+
},
|
|
4942
|
+
children: [
|
|
4943
|
+
sorted.map((kf, i) => {
|
|
4944
|
+
if (i === 0) return null;
|
|
4945
|
+
const prev = sorted[i - 1];
|
|
4946
|
+
const x1 = Math.max(0, Math.min(clipWidthPx, prev.percentage / 100 * clipWidthPx));
|
|
4947
|
+
const x2 = Math.max(0, Math.min(clipWidthPx, kf.percentage / 100 * clipWidthPx));
|
|
4948
|
+
if (x2 - x1 < 1) return null;
|
|
4949
|
+
return /* @__PURE__ */ jsx14(
|
|
4950
|
+
"div",
|
|
4951
|
+
{
|
|
4952
|
+
className: "absolute",
|
|
4953
|
+
style: {
|
|
4954
|
+
left: x1,
|
|
4955
|
+
top: centerY,
|
|
4956
|
+
width: x2 - x1,
|
|
4957
|
+
height: 2,
|
|
4958
|
+
transform: "translateY(-1px)",
|
|
4959
|
+
background: baseColor,
|
|
4960
|
+
opacity: baseOpacity,
|
|
4961
|
+
borderRadius: 1
|
|
4962
|
+
}
|
|
4963
|
+
},
|
|
4964
|
+
`line-${i}-${prev.percentage}-${kf.percentage}`
|
|
4965
|
+
);
|
|
4966
|
+
}),
|
|
4967
|
+
sorted.map((kf, i) => {
|
|
4968
|
+
const kfKey = `${elementId}:${kf.percentage}`;
|
|
4969
|
+
const renderPct = preview?.kfKey === kfKey ? preview.clipPct : kf.percentage;
|
|
4970
|
+
const leftPx = renderPct / 100 * clipWidthPx - half;
|
|
4971
|
+
const isKfSelected = selectedKeyframes.has(kfKey);
|
|
4972
|
+
const atPlayhead = isSelected && Math.abs(kf.percentage - currentPercentage) < 0.5;
|
|
4973
|
+
const isHighlighted = isKfSelected || atPlayhead;
|
|
4974
|
+
const color = isHighlighted ? accentColor : "#a3a3a3";
|
|
4975
|
+
const onPointerDown = (e) => {
|
|
4976
|
+
if (e.button !== 0) return;
|
|
4977
|
+
e.stopPropagation();
|
|
4978
|
+
if (canDrag) {
|
|
4979
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
4980
|
+
dragRef.current = {
|
|
4981
|
+
kfKey,
|
|
4982
|
+
startX: e.clientX,
|
|
4983
|
+
fromClipPct: kf.percentage,
|
|
4984
|
+
moved: false
|
|
4985
|
+
};
|
|
4986
|
+
}
|
|
4961
4987
|
};
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4988
|
+
const onPointerMove = (e) => {
|
|
4989
|
+
const d = dragRef.current;
|
|
4990
|
+
if (!d || d.kfKey !== kfKey) return;
|
|
4991
|
+
if (!d.moved && Math.abs(e.clientX - d.startX) >= KEYFRAME_DRAG_THRESHOLD_PX) {
|
|
4992
|
+
d.moved = true;
|
|
4993
|
+
}
|
|
4994
|
+
if (d.moved) {
|
|
4995
|
+
setPreview({
|
|
4996
|
+
kfKey,
|
|
4997
|
+
clipPct: previewClipPct({
|
|
4998
|
+
pointerDownX: d.startX,
|
|
4999
|
+
pointerMoveX: e.clientX,
|
|
5000
|
+
clipWidthPx,
|
|
5001
|
+
draggedClipPct: d.fromClipPct,
|
|
5002
|
+
draggedIndex: i,
|
|
5003
|
+
sortedClipPcts
|
|
5004
|
+
})
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
};
|
|
5008
|
+
const onPointerUp = (e) => {
|
|
5009
|
+
const d = dragRef.current;
|
|
5010
|
+
if (!d || d.kfKey !== kfKey) {
|
|
5011
|
+
if (e.button !== 0) return;
|
|
5012
|
+
suppressNextClick();
|
|
5013
|
+
if (e.shiftKey) onShiftClickKeyframe?.(elementId, kf.percentage);
|
|
5014
|
+
else onClickKeyframe?.(kf.percentage);
|
|
5015
|
+
return;
|
|
5016
|
+
}
|
|
5017
|
+
e.stopPropagation();
|
|
5018
|
+
dragRef.current = null;
|
|
5019
|
+
setPreview(null);
|
|
5020
|
+
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
5021
|
+
suppressNextClick();
|
|
5022
|
+
const res = resolveKeyframeDrag({
|
|
4974
5023
|
pointerDownX: d.startX,
|
|
4975
|
-
|
|
5024
|
+
pointerUpX: e.clientX,
|
|
4976
5025
|
clipWidthPx,
|
|
4977
5026
|
draggedClipPct: d.fromClipPct,
|
|
4978
5027
|
draggedIndex: i,
|
|
4979
5028
|
sortedClipPcts
|
|
4980
|
-
})
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
return
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
stroke: accentColor,
|
|
5045
|
-
strokeWidth: "0.8",
|
|
5046
|
-
opacity: 0.5
|
|
5047
|
-
}
|
|
5048
|
-
),
|
|
5049
|
-
/* @__PURE__ */ jsx14(
|
|
5050
|
-
"path",
|
|
5051
|
-
{
|
|
5052
|
-
d: "M5 1L9 5L5 9L1 5Z",
|
|
5053
|
-
fill: color,
|
|
5054
|
-
opacity: isKfSelected || atPlayhead ? 1 : 0.55
|
|
5055
|
-
}
|
|
5056
|
-
)
|
|
5057
|
-
] })
|
|
5058
|
-
},
|
|
5059
|
-
`${i}-${kf.percentage}`
|
|
5060
|
-
);
|
|
5061
|
-
})
|
|
5062
|
-
] });
|
|
5029
|
+
});
|
|
5030
|
+
if (res.kind === "click" || res.kind === "noop") {
|
|
5031
|
+
if (e.shiftKey) onShiftClickKeyframe?.(elementId, kf.percentage);
|
|
5032
|
+
else onClickKeyframe?.(kf.percentage);
|
|
5033
|
+
} else if (res.kind === "move" && res.toClipPct != null) {
|
|
5034
|
+
onMoveKeyframe?.(elementId, d.fromClipPct, res.toClipPct);
|
|
5035
|
+
onClickKeyframe?.(res.toClipPct);
|
|
5036
|
+
}
|
|
5037
|
+
};
|
|
5038
|
+
return /* @__PURE__ */ jsx14(
|
|
5039
|
+
"button",
|
|
5040
|
+
{
|
|
5041
|
+
type: "button",
|
|
5042
|
+
className: "absolute",
|
|
5043
|
+
style: {
|
|
5044
|
+
left: leftPx,
|
|
5045
|
+
top: centerY,
|
|
5046
|
+
transform: "translateY(-50%)",
|
|
5047
|
+
width: diamondSize,
|
|
5048
|
+
height: diamondSize,
|
|
5049
|
+
zIndex: isHighlighted ? 2 : 1,
|
|
5050
|
+
pointerEvents: "auto",
|
|
5051
|
+
background: "none",
|
|
5052
|
+
border: "none",
|
|
5053
|
+
cursor: canDrag ? "ew-resize" : "pointer",
|
|
5054
|
+
padding: 0,
|
|
5055
|
+
touchAction: "none"
|
|
5056
|
+
},
|
|
5057
|
+
onPointerDown,
|
|
5058
|
+
onPointerMove,
|
|
5059
|
+
onPointerUp,
|
|
5060
|
+
onContextMenu: (e) => {
|
|
5061
|
+
e.preventDefault();
|
|
5062
|
+
e.stopPropagation();
|
|
5063
|
+
onContextMenuKeyframe?.(e, elementId, kf.percentage);
|
|
5064
|
+
},
|
|
5065
|
+
title: `${kf.percentage}%`,
|
|
5066
|
+
children: /* @__PURE__ */ jsxs11("svg", { width: diamondSize, height: diamondSize, viewBox: "0 0 10 10", children: [
|
|
5067
|
+
isKfSelected && /* @__PURE__ */ jsx14(
|
|
5068
|
+
"path",
|
|
5069
|
+
{
|
|
5070
|
+
d: "M5 0L10 5L5 10L0 5Z",
|
|
5071
|
+
fill: "none",
|
|
5072
|
+
stroke: accentColor,
|
|
5073
|
+
strokeWidth: "0.8",
|
|
5074
|
+
opacity: 0.5
|
|
5075
|
+
}
|
|
5076
|
+
),
|
|
5077
|
+
/* @__PURE__ */ jsx14(
|
|
5078
|
+
"path",
|
|
5079
|
+
{
|
|
5080
|
+
d: "M5 1L9 5L5 9L1 5Z",
|
|
5081
|
+
fill: color,
|
|
5082
|
+
opacity: isKfSelected || atPlayhead ? 1 : 0.55
|
|
5083
|
+
}
|
|
5084
|
+
)
|
|
5085
|
+
] })
|
|
5086
|
+
},
|
|
5087
|
+
`${i}-${kf.percentage}`
|
|
5088
|
+
);
|
|
5089
|
+
})
|
|
5090
|
+
]
|
|
5091
|
+
}
|
|
5092
|
+
);
|
|
5063
5093
|
});
|
|
5064
5094
|
|
|
5065
5095
|
// src/player/components/TimelineRuler.tsx
|
|
@@ -6184,7 +6214,8 @@ var TimelineCanvas = memo8(function TimelineCanvas2({
|
|
|
6184
6214
|
onClickKeyframe: (pct) => onClickKeyframe?.(previewElement, pct),
|
|
6185
6215
|
onShiftClickKeyframe,
|
|
6186
6216
|
onContextMenuKeyframe,
|
|
6187
|
-
onMoveKeyframe
|
|
6217
|
+
onMoveKeyframe,
|
|
6218
|
+
suppressClickRef
|
|
6188
6219
|
}
|
|
6189
6220
|
)
|
|
6190
6221
|
]
|
|
@@ -7214,8 +7245,6 @@ var Timeline = memo11(function Timeline2({
|
|
|
7214
7245
|
if (el) {
|
|
7215
7246
|
setSelectedElementId(elId);
|
|
7216
7247
|
onSelectElement?.(el);
|
|
7217
|
-
const absTime = el.start + pct / 100 * el.duration;
|
|
7218
|
-
onSeek?.(absTime);
|
|
7219
7248
|
}
|
|
7220
7249
|
const kfData = keyframeCache.get(elId);
|
|
7221
7250
|
const kf = kfData?.keyframes.find((k) => Math.abs(k.percentage - pct) < 0.2);
|
|
@@ -9813,7 +9842,7 @@ function verifyTimelinesPopulated(win, targetKeys) {
|
|
|
9813
9842
|
}
|
|
9814
9843
|
return Object.keys(timelines).filter((k) => k !== "__proxied").length > 0;
|
|
9815
9844
|
}
|
|
9816
|
-
function applySoftReload(iframe, scriptText, onAsyncFailure) {
|
|
9845
|
+
function applySoftReload(iframe, scriptText, onAsyncFailure, currentTimeOverride) {
|
|
9817
9846
|
if (!iframe || !scriptText) return "cannot-soft-reload";
|
|
9818
9847
|
const win = iframe.contentWindow;
|
|
9819
9848
|
const doc = iframe.contentDocument;
|
|
@@ -9830,7 +9859,7 @@ function applySoftReload(iframe, scriptText, onAsyncFailure) {
|
|
|
9830
9859
|
})
|
|
9831
9860
|
);
|
|
9832
9861
|
if (gsapScripts.length > 1 && staleScripts.length === 0) return "cannot-soft-reload";
|
|
9833
|
-
const currentTime = win.__player?.getTime?.() ?? 0;
|
|
9862
|
+
const currentTime = currentTimeOverride ?? win.__player?.getTime?.() ?? 0;
|
|
9834
9863
|
let deferredToAsync = false;
|
|
9835
9864
|
const doReload = () => {
|
|
9836
9865
|
const timelines = win.__timelines;
|
|
@@ -9889,8 +9918,8 @@ function applySoftReload(iframe, scriptText, onAsyncFailure) {
|
|
|
9889
9918
|
s.textContent = `(function(){${scriptText}
|
|
9890
9919
|
})();`;
|
|
9891
9920
|
doc.body.appendChild(s);
|
|
9892
|
-
win.__hfForceTimelineRebind?.();
|
|
9893
9921
|
win.__player?.seek?.(currentTime);
|
|
9922
|
+
win.__hfForceTimelineRebind?.();
|
|
9894
9923
|
win.__hfStudioManualEditsApply?.();
|
|
9895
9924
|
};
|
|
9896
9925
|
const needsMotionPath = /motionPath\s*[:{]/.test(scriptText);
|
|
@@ -26987,7 +27016,8 @@ async function mutateGsapScript(projectId, sourceFile, mutation) {
|
|
|
26987
27016
|
return result;
|
|
26988
27017
|
}
|
|
26989
27018
|
function softReloadOrEscalate(iframe, scriptText, reloadPreview, origin) {
|
|
26990
|
-
const
|
|
27019
|
+
const currentTime = usePlayerStore.getState().currentTime;
|
|
27020
|
+
const result = applySoftReload(iframe, scriptText, reloadPreview, currentTime);
|
|
26991
27021
|
if (result === "applied") return;
|
|
26992
27022
|
trackStudioEvent("gsap_soft_reload_outcome", {
|
|
26993
27023
|
origin,
|
|
@@ -27950,6 +27980,102 @@ async function commitGsapPositionFromDrag(selection, anim, studioOffset, gsapPos
|
|
|
27950
27980
|
}
|
|
27951
27981
|
}
|
|
27952
27982
|
|
|
27983
|
+
// src/hooks/gsapTweenSynth.ts
|
|
27984
|
+
function deduplicateKeyframes(keyframes) {
|
|
27985
|
+
const byPct = /* @__PURE__ */ new Map();
|
|
27986
|
+
for (const kf of keyframes) {
|
|
27987
|
+
const existing = byPct.get(kf.percentage);
|
|
27988
|
+
if (existing) {
|
|
27989
|
+
existing.properties = { ...existing.properties, ...kf.properties };
|
|
27990
|
+
if (kf.ease) existing.ease = kf.ease;
|
|
27991
|
+
} else {
|
|
27992
|
+
byPct.set(kf.percentage, { ...kf, properties: { ...kf.properties } });
|
|
27993
|
+
}
|
|
27994
|
+
}
|
|
27995
|
+
return Array.from(byPct.values()).sort((a, b) => a.percentage - b.percentage);
|
|
27996
|
+
}
|
|
27997
|
+
function synthesizeFlatTweenKeyframes(anim) {
|
|
27998
|
+
const hasImmediateRenderHold = anim.extras?.immediateRender === "__raw:true";
|
|
27999
|
+
if (anim.method === "set" || anim.duration === 0 && hasImmediateRenderHold) {
|
|
28000
|
+
return null;
|
|
28001
|
+
}
|
|
28002
|
+
const toProps = anim.properties;
|
|
28003
|
+
const fromProps = anim.fromProperties;
|
|
28004
|
+
if (!toProps || Object.keys(toProps).length === 0) return null;
|
|
28005
|
+
const startProps = {};
|
|
28006
|
+
const endProps = {};
|
|
28007
|
+
if (anim.method === "from") {
|
|
28008
|
+
for (const [k, v] of Object.entries(toProps)) {
|
|
28009
|
+
startProps[k] = v;
|
|
28010
|
+
endProps[k] = PROPERTY_DEFAULTS[k] ?? 0;
|
|
28011
|
+
}
|
|
28012
|
+
} else if (anim.method === "fromTo" && fromProps) {
|
|
28013
|
+
Object.assign(startProps, fromProps);
|
|
28014
|
+
Object.assign(endProps, toProps);
|
|
28015
|
+
} else {
|
|
28016
|
+
for (const [k, v] of Object.entries(toProps)) {
|
|
28017
|
+
startProps[k] = PROPERTY_DEFAULTS[k] ?? 0;
|
|
28018
|
+
endProps[k] = v;
|
|
28019
|
+
}
|
|
28020
|
+
}
|
|
28021
|
+
return {
|
|
28022
|
+
format: "percentage",
|
|
28023
|
+
keyframes: [
|
|
28024
|
+
{ percentage: 0, properties: startProps },
|
|
28025
|
+
{ percentage: 100, properties: endProps }
|
|
28026
|
+
],
|
|
28027
|
+
...anim.ease ? { ease: anim.ease } : {}
|
|
28028
|
+
};
|
|
28029
|
+
}
|
|
28030
|
+
|
|
28031
|
+
// src/hooks/gsapWholePropertyOffsetCommit.ts
|
|
28032
|
+
async function commitWholePropertyOffset(selection, anim, newValues, currentPct, iframe, callbacks, label) {
|
|
28033
|
+
let effectiveAnim = anim;
|
|
28034
|
+
if (anim.keyframes) {
|
|
28035
|
+
const newId = await materializeIfDynamic(anim, iframe, callbacks.commitMutation, selection);
|
|
28036
|
+
if (newId) effectiveAnim = { ...anim, id: newId };
|
|
28037
|
+
}
|
|
28038
|
+
const ts = resolveTweenStart(effectiveAnim);
|
|
28039
|
+
const td = resolveTweenDuration(effectiveAnim);
|
|
28040
|
+
const ease = effectiveAnim.keyframes?.easeEach ?? effectiveAnim.ease;
|
|
28041
|
+
const keys = Object.keys(newValues);
|
|
28042
|
+
const at = (props, key) => typeof props[key] === "number" ? props[key] : PROPERTY_DEFAULTS[key] ?? 0;
|
|
28043
|
+
const kfs = effectiveAnim.keyframes?.keyframes ?? synthesizeFlatTweenKeyframes(effectiveAnim)?.keyframes;
|
|
28044
|
+
if (!kfs || kfs.length === 0) {
|
|
28045
|
+
await callbacks.commitMutation(
|
|
28046
|
+
selection,
|
|
28047
|
+
{ type: "update-properties", animationId: effectiveAnim.id, properties: newValues },
|
|
28048
|
+
{ label, softReload: true }
|
|
28049
|
+
);
|
|
28050
|
+
return;
|
|
28051
|
+
}
|
|
28052
|
+
const nearest = kfs.reduce(
|
|
28053
|
+
(best, kf) => Math.abs(kf.percentage - currentPct) < Math.abs(best.percentage - currentPct) ? kf : best
|
|
28054
|
+
);
|
|
28055
|
+
const shifted = kfs.map((kf) => {
|
|
28056
|
+
const properties = { ...kf.properties };
|
|
28057
|
+
for (const key of keys) {
|
|
28058
|
+
properties[key] = roundTo3(
|
|
28059
|
+
at(properties, key) + (newValues[key] - at(nearest.properties, key))
|
|
28060
|
+
);
|
|
28061
|
+
}
|
|
28062
|
+
return { percentage: kf.percentage, properties, ...kf.ease ? { ease: kf.ease } : {} };
|
|
28063
|
+
});
|
|
28064
|
+
await callbacks.commitMutation(
|
|
28065
|
+
selection,
|
|
28066
|
+
{
|
|
28067
|
+
type: "replace-with-keyframes",
|
|
28068
|
+
animationId: effectiveAnim.id,
|
|
28069
|
+
targetSelector: effectiveAnim.targetSelector,
|
|
28070
|
+
position: roundTo3(ts ?? 0),
|
|
28071
|
+
duration: roundTo3(td || 1),
|
|
28072
|
+
keyframes: shifted,
|
|
28073
|
+
ease
|
|
28074
|
+
},
|
|
28075
|
+
{ label, softReload: true }
|
|
28076
|
+
);
|
|
28077
|
+
}
|
|
28078
|
+
|
|
27953
28079
|
// src/hooks/gsapPositionDetection.ts
|
|
27954
28080
|
function readGsapPositionFromIframe(iframe, elementSelector) {
|
|
27955
28081
|
const gsap2 = getIframeGsap(iframe);
|
|
@@ -28110,7 +28236,8 @@ async function tryGsapDragIntercept(selection, offset, animations, iframe, commi
|
|
|
28110
28236
|
}
|
|
28111
28237
|
}
|
|
28112
28238
|
const cbs = { commitMutation, fetchAnimations: fetchFallbackAnimations };
|
|
28113
|
-
|
|
28239
|
+
const autoKeyframeEnabled = usePlayerStore.getState().autoKeyframeEnabled;
|
|
28240
|
+
if (options?.altKey || !autoKeyframeEnabled) {
|
|
28114
28241
|
await commitWholePathOffset(selection, posAnim, offset, gsapPos, iframe, selector, cbs);
|
|
28115
28242
|
} else {
|
|
28116
28243
|
await commitGsapPositionFromDrag(selection, posAnim, offset, gsapPos, iframe, selector, cbs);
|
|
@@ -28182,6 +28309,19 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
|
|
|
28182
28309
|
height: Math.round(size.height)
|
|
28183
28310
|
};
|
|
28184
28311
|
}
|
|
28312
|
+
if (!usePlayerStore.getState().autoKeyframeEnabled) {
|
|
28313
|
+
if (activeKeyframePct != null) setActiveKeyframePct(null);
|
|
28314
|
+
await commitWholePropertyOffset(
|
|
28315
|
+
selection,
|
|
28316
|
+
anim,
|
|
28317
|
+
resizeProps,
|
|
28318
|
+
pct,
|
|
28319
|
+
iframe,
|
|
28320
|
+
{ commitMutation, fetchAnimations: fetchFallbackAnimations },
|
|
28321
|
+
"Resize animation"
|
|
28322
|
+
);
|
|
28323
|
+
return true;
|
|
28324
|
+
}
|
|
28185
28325
|
const ct = usePlayerStore.getState().currentTime;
|
|
28186
28326
|
const ts = resolveTweenStart(anim);
|
|
28187
28327
|
const td = resolveTweenDuration(anim);
|
|
@@ -28296,6 +28436,18 @@ async function tryGsapRotationIntercept(selection, angle, animations, iframe, co
|
|
|
28296
28436
|
return true;
|
|
28297
28437
|
}
|
|
28298
28438
|
const pct = computeCurrentPercentage(selection, anim);
|
|
28439
|
+
if (!usePlayerStore.getState().autoKeyframeEnabled) {
|
|
28440
|
+
await commitWholePropertyOffset(
|
|
28441
|
+
selection,
|
|
28442
|
+
anim,
|
|
28443
|
+
{ rotation: newRotation },
|
|
28444
|
+
pct,
|
|
28445
|
+
iframe,
|
|
28446
|
+
{ commitMutation, fetchAnimations: fetchFallbackAnimations },
|
|
28447
|
+
"Rotate animation"
|
|
28448
|
+
);
|
|
28449
|
+
return true;
|
|
28450
|
+
}
|
|
28299
28451
|
if (anim.hasUnresolvedKeyframes || anim.hasUnresolvedSelector) {
|
|
28300
28452
|
const newId = await materializeIfDynamic(anim, iframe, commitMutation, selection);
|
|
28301
28453
|
if (newId) anim = { ...anim, id: newId };
|
|
@@ -28327,53 +28479,6 @@ async function tryGsapRotationIntercept(selection, angle, animations, iframe, co
|
|
|
28327
28479
|
return true;
|
|
28328
28480
|
}
|
|
28329
28481
|
|
|
28330
|
-
// src/hooks/gsapTweenSynth.ts
|
|
28331
|
-
function deduplicateKeyframes(keyframes) {
|
|
28332
|
-
const byPct = /* @__PURE__ */ new Map();
|
|
28333
|
-
for (const kf of keyframes) {
|
|
28334
|
-
const existing = byPct.get(kf.percentage);
|
|
28335
|
-
if (existing) {
|
|
28336
|
-
existing.properties = { ...existing.properties, ...kf.properties };
|
|
28337
|
-
if (kf.ease) existing.ease = kf.ease;
|
|
28338
|
-
} else {
|
|
28339
|
-
byPct.set(kf.percentage, { ...kf, properties: { ...kf.properties } });
|
|
28340
|
-
}
|
|
28341
|
-
}
|
|
28342
|
-
return Array.from(byPct.values()).sort((a, b) => a.percentage - b.percentage);
|
|
28343
|
-
}
|
|
28344
|
-
function synthesizeFlatTweenKeyframes(anim) {
|
|
28345
|
-
if (anim.method === "set") {
|
|
28346
|
-
return null;
|
|
28347
|
-
}
|
|
28348
|
-
const toProps = anim.properties;
|
|
28349
|
-
const fromProps = anim.fromProperties;
|
|
28350
|
-
if (!toProps || Object.keys(toProps).length === 0) return null;
|
|
28351
|
-
const startProps = {};
|
|
28352
|
-
const endProps = {};
|
|
28353
|
-
if (anim.method === "from") {
|
|
28354
|
-
for (const [k, v] of Object.entries(toProps)) {
|
|
28355
|
-
startProps[k] = v;
|
|
28356
|
-
endProps[k] = PROPERTY_DEFAULTS[k] ?? 0;
|
|
28357
|
-
}
|
|
28358
|
-
} else if (anim.method === "fromTo" && fromProps) {
|
|
28359
|
-
Object.assign(startProps, fromProps);
|
|
28360
|
-
Object.assign(endProps, toProps);
|
|
28361
|
-
} else {
|
|
28362
|
-
for (const [k, v] of Object.entries(toProps)) {
|
|
28363
|
-
startProps[k] = PROPERTY_DEFAULTS[k] ?? 0;
|
|
28364
|
-
endProps[k] = v;
|
|
28365
|
-
}
|
|
28366
|
-
}
|
|
28367
|
-
return {
|
|
28368
|
-
format: "percentage",
|
|
28369
|
-
keyframes: [
|
|
28370
|
-
{ percentage: 0, properties: startProps },
|
|
28371
|
-
{ percentage: 100, properties: endProps }
|
|
28372
|
-
],
|
|
28373
|
-
...anim.ease ? { ease: anim.ease } : {}
|
|
28374
|
-
};
|
|
28375
|
-
}
|
|
28376
|
-
|
|
28377
28482
|
// src/hooks/useGsapTweenCache.ts
|
|
28378
28483
|
function extractIdFromSelector(selector) {
|
|
28379
28484
|
const match = selector.match(/^#([\w-]+)/);
|
|
@@ -29450,6 +29555,25 @@ function useAnimatedPropertyCommit(deps) {
|
|
|
29450
29555
|
const elementHasKeyframes = selectedGsapAnimations.some((a) => !!a.keyframes);
|
|
29451
29556
|
try {
|
|
29452
29557
|
if (elementHasKeyframes && anim) {
|
|
29558
|
+
if (!usePlayerStore.getState().autoKeyframeEnabled) {
|
|
29559
|
+
const pct = computeElementPercentage(
|
|
29560
|
+
usePlayerStore.getState().currentTime,
|
|
29561
|
+
selection,
|
|
29562
|
+
anim
|
|
29563
|
+
);
|
|
29564
|
+
await commitWholePropertyOffset(
|
|
29565
|
+
selection,
|
|
29566
|
+
anim,
|
|
29567
|
+
Object.fromEntries(
|
|
29568
|
+
propEntries.filter((e) => typeof e[1] === "number")
|
|
29569
|
+
),
|
|
29570
|
+
pct,
|
|
29571
|
+
iframe,
|
|
29572
|
+
{ commitMutation: gsapCommitMutation },
|
|
29573
|
+
`Edit ${primaryProp} (whole animation)`
|
|
29574
|
+
);
|
|
29575
|
+
return;
|
|
29576
|
+
}
|
|
29453
29577
|
await commitKeyframeProps(
|
|
29454
29578
|
selection,
|
|
29455
29579
|
anim,
|
|
@@ -30731,16 +30855,15 @@ function dispatchPlainKey(event, key, cb) {
|
|
|
30731
30855
|
return;
|
|
30732
30856
|
}
|
|
30733
30857
|
if (event.key === "s" && !event.altKey) {
|
|
30858
|
+
event.preventDefault();
|
|
30734
30859
|
const { selectedElementId, elements, currentTime } = usePlayerStore.getState();
|
|
30735
30860
|
if (selectedElementId) {
|
|
30736
30861
|
const el = elements.find((e) => (e.key ?? e.id) === selectedElementId);
|
|
30737
30862
|
if (el && canSplitElement(el) && currentTime > el.start && currentTime < el.start + el.duration) {
|
|
30738
|
-
event.preventDefault();
|
|
30739
30863
|
void cb.handleTimelineElementSplit(el, currentTime);
|
|
30740
30864
|
return;
|
|
30741
30865
|
}
|
|
30742
30866
|
if (!el && selectedElementId.includes("#")) {
|
|
30743
|
-
event.preventDefault();
|
|
30744
30867
|
cb.showToast("Use the razor tool (B) to split clips inside a sub-composition", "info");
|
|
30745
30868
|
return;
|
|
30746
30869
|
}
|
|
@@ -31812,6 +31935,9 @@ function useRenderClipContent({
|
|
|
31812
31935
|
duration: el.duration
|
|
31813
31936
|
});
|
|
31814
31937
|
}
|
|
31938
|
+
if (el.tag === "audio") {
|
|
31939
|
+
return renderAudioClip(el, pid, style.label);
|
|
31940
|
+
}
|
|
31815
31941
|
if (activePreviewUrl && el.duration > 0) {
|
|
31816
31942
|
return createElement(CompositionThumbnail, {
|
|
31817
31943
|
previewUrl: activePreviewUrl,
|
|
@@ -31824,9 +31950,6 @@ function useRenderClipContent({
|
|
|
31824
31950
|
});
|
|
31825
31951
|
}
|
|
31826
31952
|
const htmlPreviewEligible = el.duration > 0 && effectiveTimelineDuration > 0 && el.duration < effectiveTimelineDuration * 0.92 && !/(backdrop|background|overlay|scrim|mask)/i.test(el.id);
|
|
31827
|
-
if (el.tag === "audio") {
|
|
31828
|
-
return renderAudioClip(el, pid, style.label);
|
|
31829
|
-
}
|
|
31830
31953
|
if ((el.tag === "video" || el.tag === "img") && el.src) {
|
|
31831
31954
|
const mediaSrc = el.src.startsWith("http") ? el.src : `/api/projects/${pid}/preview/${el.src}`;
|
|
31832
31955
|
return createElement(VideoThumbnail, {
|
|
@@ -39725,18 +39848,30 @@ var MotionPathOverlay = memo36(function MotionPathOverlay2({
|
|
|
39725
39848
|
clearTimeout(parkTimerRef.current);
|
|
39726
39849
|
if (e.detail < 2) {
|
|
39727
39850
|
parkTimerRef.current = setTimeout(() => {
|
|
39728
|
-
const
|
|
39729
|
-
if (
|
|
39851
|
+
const anim2 = selectedGsapAnimations?.find((a) => a.id === animId);
|
|
39852
|
+
if (anim2) parkPlayheadOnKeyframe(anim2, ref.pct);
|
|
39730
39853
|
}, 250);
|
|
39731
39854
|
}
|
|
39732
39855
|
}
|
|
39733
39856
|
return;
|
|
39734
39857
|
}
|
|
39735
39858
|
if (x === Math.round(d.initX) && y === Math.round(d.initY)) return;
|
|
39736
|
-
|
|
39737
|
-
if (d.ref.type === "keyframe") {
|
|
39738
|
-
|
|
39739
|
-
|
|
39859
|
+
const anim = d.ref.type === "keyframe" ? selectedGsapAnimations?.find((a) => a.id === animId) : void 0;
|
|
39860
|
+
if (d.ref.type === "keyframe" && anim && selection && !usePlayerStore.getState().autoKeyframeEnabled) {
|
|
39861
|
+
void commitWholePropertyOffset(
|
|
39862
|
+
selection,
|
|
39863
|
+
anim,
|
|
39864
|
+
{ x, y },
|
|
39865
|
+
d.ref.pct,
|
|
39866
|
+
iframeRef.current,
|
|
39867
|
+
{ commitMutation: (_sel, mutation, options) => commitMutation(mutation, options) },
|
|
39868
|
+
"Move animation path"
|
|
39869
|
+
);
|
|
39870
|
+
} else {
|
|
39871
|
+
void commitNode(d.ref, x, y, animId, commitMutation);
|
|
39872
|
+
}
|
|
39873
|
+
if (d.ref.type === "keyframe" && anim) {
|
|
39874
|
+
parkPlayheadOnKeyframe(anim, d.ref.pct);
|
|
39740
39875
|
}
|
|
39741
39876
|
};
|
|
39742
39877
|
const onPathHover = (e) => {
|
|
@@ -39935,6 +40070,7 @@ var SnapToolbar = memo37(function SnapToolbar2({ onSnapChange }) {
|
|
|
39935
40070
|
}, [prefs.gridVisible, updatePrefs]);
|
|
39936
40071
|
useEffect56(() => {
|
|
39937
40072
|
const handleKeyDown = (e) => {
|
|
40073
|
+
if (e.defaultPrevented) return;
|
|
39938
40074
|
const t = e.target;
|
|
39939
40075
|
if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) return;
|
|
39940
40076
|
if (t instanceof HTMLElement && t.isContentEditable) return;
|
|
@@ -43831,7 +43967,7 @@ function useKeyframeKeyboard({
|
|
|
43831
43967
|
}
|
|
43832
43968
|
|
|
43833
43969
|
// src/components/TimelineToolbar.tsx
|
|
43834
|
-
import {
|
|
43970
|
+
import { jsx as jsx101, jsxs as jsxs86 } from "react/jsx-runtime";
|
|
43835
43971
|
function useKeyframeToggle(session) {
|
|
43836
43972
|
const currentTime = usePlayerStore((s) => s.currentTime);
|
|
43837
43973
|
const sessionRef = useRef88(session);
|
|
@@ -43863,6 +43999,8 @@ function TimelineToolbar({
|
|
|
43863
43999
|
}) {
|
|
43864
44000
|
const activeTool = usePlayerStore((s) => s.activeTool);
|
|
43865
44001
|
const setActiveTool = usePlayerStore((s) => s.setActiveTool);
|
|
44002
|
+
const autoKeyframeEnabled = usePlayerStore((s) => s.autoKeyframeEnabled);
|
|
44003
|
+
const setAutoKeyframeEnabled = usePlayerStore((s) => s.setAutoKeyframeEnabled);
|
|
43866
44004
|
const currentTime = usePlayerStore((s) => s.currentTime);
|
|
43867
44005
|
const beatAnalysisReady = usePlayerStore((s) => s.beatAnalysis !== null);
|
|
43868
44006
|
const { zoomMode, manualZoomPercent, setZoomMode, setManualZoomPercent } = useTimelineZoom();
|
|
@@ -43899,10 +44037,10 @@ function TimelineToolbar({
|
|
|
43899
44037
|
}
|
|
43900
44038
|
) })
|
|
43901
44039
|
] }),
|
|
43902
|
-
STUDIO_KEYFRAMES_ENABLED && onToggleKeyframe && /* @__PURE__ */ jsx101(
|
|
44040
|
+
STUDIO_KEYFRAMES_ENABLED && onToggleKeyframe && /* @__PURE__ */ jsx101(
|
|
43903
44041
|
Tooltip,
|
|
43904
44042
|
{
|
|
43905
|
-
label: keyframeState === "active" ? "Remove keyframe at playhead (K)" : keyframeState === "inactive" ? keyframeWillExtend ? "Add keyframe at playhead
|
|
44043
|
+
label: keyframeState === "active" ? "Remove keyframe at playhead (K)" : keyframeState === "inactive" ? keyframeWillExtend ? "Add keyframe at playhead, extends animation (K)" : "Add keyframe at playhead (K)" : "Add keyframe (K)",
|
|
43906
44044
|
children: /* @__PURE__ */ jsx101(
|
|
43907
44045
|
"button",
|
|
43908
44046
|
{
|
|
@@ -43921,7 +44059,36 @@ function TimelineToolbar({
|
|
|
43921
44059
|
}
|
|
43922
44060
|
)
|
|
43923
44061
|
}
|
|
43924
|
-
)
|
|
44062
|
+
),
|
|
44063
|
+
STUDIO_KEYFRAMES_ENABLED && /* @__PURE__ */ jsx101(
|
|
44064
|
+
Tooltip,
|
|
44065
|
+
{
|
|
44066
|
+
label: autoKeyframeEnabled ? "Auto-record manual edits as keyframes (click to turn off)" : "Manual edits will not be recorded as keyframes (click to turn on)",
|
|
44067
|
+
children: /* @__PURE__ */ jsx101(
|
|
44068
|
+
"button",
|
|
44069
|
+
{
|
|
44070
|
+
type: "button",
|
|
44071
|
+
onClick: () => setAutoKeyframeEnabled(!autoKeyframeEnabled),
|
|
44072
|
+
"aria-pressed": autoKeyframeEnabled,
|
|
44073
|
+
className: `flex h-7 w-7 items-center justify-center rounded transition-colors ${autoKeyframeEnabled ? "text-red-400 hover:text-red-300" : "text-neutral-600 hover:text-neutral-400"}`,
|
|
44074
|
+
children: /* @__PURE__ */ jsxs86("svg", { width: "18", height: "18", viewBox: "0 0 10 10", fill: "none", children: [
|
|
44075
|
+
/* @__PURE__ */ jsx101("path", { d: "M5 0.7L9.3 5L5 9.3L0.7 5Z", stroke: "currentColor", strokeWidth: "1" }),
|
|
44076
|
+
/* @__PURE__ */ jsx101(
|
|
44077
|
+
"circle",
|
|
44078
|
+
{
|
|
44079
|
+
cx: "5",
|
|
44080
|
+
cy: "5",
|
|
44081
|
+
r: "1.8",
|
|
44082
|
+
fill: autoKeyframeEnabled ? "currentColor" : "none",
|
|
44083
|
+
stroke: "currentColor",
|
|
44084
|
+
strokeWidth: "1"
|
|
44085
|
+
}
|
|
44086
|
+
)
|
|
44087
|
+
] })
|
|
44088
|
+
}
|
|
44089
|
+
)
|
|
44090
|
+
}
|
|
44091
|
+
),
|
|
43925
44092
|
onSplitElement && (() => {
|
|
43926
44093
|
const { selectedElementId, elements, currentTime: currentTime2 } = usePlayerStore.getState();
|
|
43927
44094
|
const el = selectedElementId ? elements.find((e) => (e.key ?? e.id) === selectedElementId) : null;
|