@hyperframes/studio 0.8.120 → 0.8.122
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-i4gqkhjz.js → hyperframes-player-DMzBHhbH.js} +1 -1
- package/dist/assets/{index-Ce3Lr-Gt.js → index-B-YDp8hN.js} +174 -174
- package/dist/assets/{index-uWOhRqxe.js → index-CBRX2eZX.js} +1 -1
- package/dist/assets/{index-C7LHDM18.js → index-lyjhSEzi.js} +1 -1
- package/dist/{chunk-J6Q572FA.js → chunk-246VB2G3.js} +3 -1
- package/dist/chunk-246VB2G3.js.map +1 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.html +1 -1
- package/dist/index.js +416 -301
- package/dist/index.js.map +1 -1
- package/dist/player.js +1 -1
- package/package.json +8 -8
- package/src/components/TimelineToolbar.test.tsx +25 -0
- package/src/components/TimelineToolbar.tsx +5 -1
- package/src/components/editor/MotionPathNode.tsx +2 -1
- package/src/components/editor/MotionPathOverlay.layerPress.test.tsx +99 -0
- package/src/components/editor/MotionPathOverlay.tsx +6 -0
- package/src/components/editor/domEditOverlayStartGesture.blocked.test.ts +57 -0
- package/src/components/editor/domEditOverlayStartGesture.ts +1 -1
- package/src/components/editor/motionPathGeometry.test.ts +10 -0
- package/src/components/editor/motionPathGeometry.ts +8 -5
- package/src/components/editor/motionPathLayerNode.test.ts +82 -0
- package/src/components/editor/motionPathLayerNode.ts +28 -0
- package/src/components/editor/useDomEditNudge.test.tsx +53 -1
- package/src/components/editor/useDomEditNudge.ts +2 -2
- package/src/components/editor/useMotionPathData.test.tsx +30 -0
- package/src/components/editor/useMotionPathData.ts +3 -1
- package/src/hooks/gsapValueAtPlayhead.test.ts +46 -3
- package/src/hooks/gsapValueAtPlayhead.ts +12 -2
- package/src/hooks/linkShortcuts.test.ts +16 -0
- package/src/hooks/linkShortcuts.ts +2 -0
- package/src/hooks/useAppHotkeys.ts +1 -2
- package/src/index.ts +2 -0
- package/src/player/components/TimelineClipFades.tsx +1 -0
- package/src/player/components/timelineClipDragPreview.test.ts +68 -0
- package/src/player/components/timelineClipDragPreview.ts +39 -14
- package/src/player/components/timelineLayout.test.ts +24 -1
- package/src/player/components/timelineRulerGeometry.ts +24 -3
- package/src/player/components/timelineSnapping.test.ts +43 -12
- package/src/player/components/timelineSnapping.ts +33 -15
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +54 -54
- package/src/player/components/useTimelineClipDrag.ts +17 -29
- package/src/player/components/useTimelineProviderState.tsx +0 -1
- package/src/player/components/useTimelineTicks.ts +6 -3
- package/src/player/hooks/timelinePlayerTestHarness.ts +2 -0
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +66 -0
- package/src/player/hooks/useTimelinePlayer.ts +6 -2
- package/src/player/lib/time.ts +3 -0
- package/src/player/store/previewFrameStore.ts +48 -0
- package/src/player/store/timelineElement.ts +12 -0
- package/src/timelineChromeExports.test.tsx +6 -0
- package/src/utils/gsapSoftReload.test.ts +50 -0
- package/src/utils/gsapSoftReload.ts +22 -2
- package/dist/chunk-J6Q572FA.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Player,
|
|
9
9
|
STUDIO_PREVIEW_FPS,
|
|
10
10
|
applyPreviewVariablesToUrl,
|
|
11
|
+
clampToDuration,
|
|
11
12
|
computePinnedZoomPercent,
|
|
12
13
|
createTimelineResetState,
|
|
13
14
|
effectiveThumbnailMode,
|
|
@@ -36,7 +37,7 @@ import {
|
|
|
36
37
|
usePreviewVariablesStore,
|
|
37
38
|
whenPreviewBooted,
|
|
38
39
|
writeStudioUiPreferences
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-246VB2G3.js";
|
|
40
41
|
import {
|
|
41
42
|
Button,
|
|
42
43
|
ContextMenu,
|
|
@@ -2542,6 +2543,11 @@ import { clampPlaybackRate } from "@hyperframes/parsers/media-duration";
|
|
|
2542
2543
|
|
|
2543
2544
|
// src/player/store/timelineElement.ts
|
|
2544
2545
|
var authoredOffset = (element) => element.authoredStartIsMasterTime ? 0 : element.parentCompositionStart ?? 0;
|
|
2546
|
+
function savedClipEdges(element, start, resizedDuration) {
|
|
2547
|
+
const savedStart = authoredOffset(element) + roundToCenti(toAuthoredStart(element, start));
|
|
2548
|
+
const duration = resizedDuration === void 0 ? element.duration : roundToCenti(resizedDuration);
|
|
2549
|
+
return { start: savedStart, end: savedStart + duration };
|
|
2550
|
+
}
|
|
2545
2551
|
function clampToHostStart(element, masterTime) {
|
|
2546
2552
|
return Math.max(Math.min(authoredOffset(element), element.start), masterTime);
|
|
2547
2553
|
}
|
|
@@ -3405,7 +3411,8 @@ var TIMELINE_SNAP_PX = 8;
|
|
|
3405
3411
|
var TYPE_PRIORITY = {
|
|
3406
3412
|
playhead: 0,
|
|
3407
3413
|
"clip-edge": 1,
|
|
3408
|
-
beat: 2
|
|
3414
|
+
beat: 2,
|
|
3415
|
+
grid: 3
|
|
3409
3416
|
};
|
|
3410
3417
|
function collectTimelineSnapTargets(input) {
|
|
3411
3418
|
const byTime = /* @__PURE__ */ new Map();
|
|
@@ -3423,11 +3430,18 @@ function collectTimelineSnapTargets(input) {
|
|
|
3423
3430
|
add(el.start, "clip-edge");
|
|
3424
3431
|
add(el.start + el.duration, "clip-edge");
|
|
3425
3432
|
}
|
|
3426
|
-
if (input.playheadTime != null
|
|
3427
|
-
add(input.playheadTime, "playhead");
|
|
3433
|
+
if (input.playheadTime != null) add(input.playheadTime, "playhead");
|
|
3428
3434
|
return Array.from(byTime.values()).sort((a, b) => a.time - b.time);
|
|
3429
3435
|
}
|
|
3430
|
-
function
|
|
3436
|
+
function nearestGridLine(time, gridStep, thresholdSecs) {
|
|
3437
|
+
if (!(gridStep > 0)) return null;
|
|
3438
|
+
const line = Math.round(time / gridStep) * gridStep;
|
|
3439
|
+
const saved2 = roundToCenti(line);
|
|
3440
|
+
const pixelsPerSecond = TIMELINE_SNAP_PX / thresholdSecs;
|
|
3441
|
+
if (Math.abs(saved2 - line) * pixelsPerSecond >= 1) return null;
|
|
3442
|
+
return Math.abs(saved2 - time) < thresholdSecs ? { time: saved2, type: "grid" } : null;
|
|
3443
|
+
}
|
|
3444
|
+
function snapTimelineTime(time, targets, thresholdSecs, gridStep = 0) {
|
|
3431
3445
|
let best = null;
|
|
3432
3446
|
let bestDist = thresholdSecs;
|
|
3433
3447
|
for (const target of targets) {
|
|
@@ -3437,21 +3451,23 @@ function snapTimelineTime(time, targets, thresholdSecs) {
|
|
|
3437
3451
|
best = target;
|
|
3438
3452
|
}
|
|
3439
3453
|
}
|
|
3454
|
+
best ??= nearestGridLine(time, gridStep, thresholdSecs);
|
|
3440
3455
|
return best ? { time: best.time, target: best } : { time, target: null };
|
|
3441
3456
|
}
|
|
3442
|
-
function snapMoveToTargets(start, duration, targets, pixelsPerSecond, timelineDuration2) {
|
|
3443
|
-
if (targets.length === 0) return { start, snapTime: null, snapType: null };
|
|
3457
|
+
function snapMoveToTargets(start, duration, targets, pixelsPerSecond, timelineDuration2, gridStep = 0) {
|
|
3458
|
+
if (targets.length === 0 && !(gridStep > 0)) return { start, snapTime: null, snapType: null };
|
|
3444
3459
|
const thresholdSecs = TIMELINE_SNAP_PX / Math.max(pixelsPerSecond, 1);
|
|
3445
|
-
const startSnap = snapTimelineTime(start, targets, thresholdSecs);
|
|
3446
|
-
const endSnap = snapTimelineTime(start + duration, targets, thresholdSecs);
|
|
3447
|
-
const
|
|
3448
|
-
const
|
|
3460
|
+
const startSnap = snapTimelineTime(start, targets, thresholdSecs, gridStep);
|
|
3461
|
+
const endSnap = snapTimelineTime(start + duration, targets, thresholdSecs, gridStep);
|
|
3462
|
+
const rank = (snap, edgeTime2) => snap.target === null ? Infinity : Math.abs(snap.time - edgeTime2) + (snap.target.type === "grid" ? thresholdSecs : 0);
|
|
3463
|
+
const startRank = rank(startSnap, start);
|
|
3464
|
+
const endRank = rank(endSnap, start + duration);
|
|
3449
3465
|
let candidate = start;
|
|
3450
3466
|
let target = null;
|
|
3451
|
-
if (
|
|
3467
|
+
if (startRank !== Infinity && startRank <= endRank) {
|
|
3452
3468
|
candidate = startSnap.time;
|
|
3453
3469
|
target = startSnap.target;
|
|
3454
|
-
} else if (
|
|
3470
|
+
} else if (endRank !== Infinity) {
|
|
3455
3471
|
candidate = endSnap.time - duration;
|
|
3456
3472
|
target = endSnap.target;
|
|
3457
3473
|
}
|
|
@@ -3692,6 +3708,165 @@ function useTimelineAssetDrop({
|
|
|
3692
3708
|
// src/player/components/useTimelineClipDrag.ts
|
|
3693
3709
|
import { useRef as useRef12, useState as useState7, useCallback as useCallback9, useMemo as useMemo7, useEffect as useEffect11 } from "react";
|
|
3694
3710
|
|
|
3711
|
+
// src/player/components/timelineRulerGeometry.ts
|
|
3712
|
+
function getTimelineMajorTickInterval(duration, pixelsPerSecond, frameRate) {
|
|
3713
|
+
const zoomIntervals = [
|
|
3714
|
+
0.02,
|
|
3715
|
+
0.05,
|
|
3716
|
+
0.1,
|
|
3717
|
+
0.2,
|
|
3718
|
+
0.5,
|
|
3719
|
+
1,
|
|
3720
|
+
2,
|
|
3721
|
+
5,
|
|
3722
|
+
10,
|
|
3723
|
+
15,
|
|
3724
|
+
30,
|
|
3725
|
+
60,
|
|
3726
|
+
120,
|
|
3727
|
+
300,
|
|
3728
|
+
600,
|
|
3729
|
+
900,
|
|
3730
|
+
1800,
|
|
3731
|
+
3600
|
|
3732
|
+
];
|
|
3733
|
+
let interval;
|
|
3734
|
+
if (Number.isFinite(pixelsPerSecond) && (pixelsPerSecond ?? 0) > 0) {
|
|
3735
|
+
const targetMajorPx = 88;
|
|
3736
|
+
interval = zoomIntervals.find((candidate) => candidate * (pixelsPerSecond ?? 0) >= targetMajorPx) ?? 3600;
|
|
3737
|
+
} else {
|
|
3738
|
+
const durationIntervals = [0.25, 0.5, 1, 2, 5, 10, 15, 30, 60];
|
|
3739
|
+
const target = duration / 6;
|
|
3740
|
+
interval = durationIntervals.find((candidate) => candidate >= target) ?? 60;
|
|
3741
|
+
}
|
|
3742
|
+
if (Number.isFinite(frameRate) && (frameRate ?? 0) > 0) {
|
|
3743
|
+
const fps = frameRate ?? 0;
|
|
3744
|
+
return Math.max(1, Math.ceil(interval * fps - 1e-6)) / fps;
|
|
3745
|
+
}
|
|
3746
|
+
return interval;
|
|
3747
|
+
}
|
|
3748
|
+
function getMinorSubdivisions(majorInterval, pixelsPerSecond, frameRate) {
|
|
3749
|
+
const pps = Number.isFinite(pixelsPerSecond) ? pixelsPerSecond ?? 0 : 0;
|
|
3750
|
+
if (pps <= 0) return 4;
|
|
3751
|
+
const fps = Number.isFinite(frameRate) ? frameRate ?? 0 : 0;
|
|
3752
|
+
const majorFrames = fps > 0 ? Math.round(majorInterval * fps) : 0;
|
|
3753
|
+
const candidates = fps > 0 ? [4, 5, 3, 2] : [4, 2];
|
|
3754
|
+
for (const parts of candidates) {
|
|
3755
|
+
if (fps > 0 && majorFrames % parts !== 0) continue;
|
|
3756
|
+
if (majorInterval / parts * pps >= 8) return parts;
|
|
3757
|
+
}
|
|
3758
|
+
return 0;
|
|
3759
|
+
}
|
|
3760
|
+
function roundTickValue(time) {
|
|
3761
|
+
return Math.round(time * 1e6) / 1e6;
|
|
3762
|
+
}
|
|
3763
|
+
function isSupportedTickDuration(duration) {
|
|
3764
|
+
return duration > 0 && Number.isFinite(duration) && duration <= 14400;
|
|
3765
|
+
}
|
|
3766
|
+
function getTickRange(duration, range) {
|
|
3767
|
+
return {
|
|
3768
|
+
start: Math.max(0, range?.start ?? 0),
|
|
3769
|
+
end: Math.min(duration, range?.end ?? duration)
|
|
3770
|
+
};
|
|
3771
|
+
}
|
|
3772
|
+
function appendMinorTicks(minor, majorTime, minorInterval, subdivisions, range, maxTicks, majorCount) {
|
|
3773
|
+
for (let part = 1; part < subdivisions && majorCount + minor.length < maxTicks; part++) {
|
|
3774
|
+
const time = majorTime + part * minorInterval;
|
|
3775
|
+
if (time >= range.start - 1e-3 && time <= range.end + 1e-3) {
|
|
3776
|
+
minor.push(roundTickValue(time));
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
function tickSpacing(duration, pixelsPerSecond, frameRate) {
|
|
3781
|
+
const majorInterval = getTimelineMajorTickInterval(duration, pixelsPerSecond, frameRate);
|
|
3782
|
+
const subdivisions = getMinorSubdivisions(majorInterval, pixelsPerSecond, frameRate);
|
|
3783
|
+
return { majorInterval, subdivisions };
|
|
3784
|
+
}
|
|
3785
|
+
function rulerFrameRate(mode) {
|
|
3786
|
+
return mode === "frame" ? STUDIO_PREVIEW_FPS : void 0;
|
|
3787
|
+
}
|
|
3788
|
+
function getTimelineGridStep(duration, pixelsPerSecond, frameRate) {
|
|
3789
|
+
if (!isSupportedTickDuration(duration)) return 0;
|
|
3790
|
+
const { majorInterval, subdivisions } = tickSpacing(duration, pixelsPerSecond, frameRate);
|
|
3791
|
+
return subdivisions > 0 ? majorInterval / subdivisions : majorInterval;
|
|
3792
|
+
}
|
|
3793
|
+
function generateTicks(duration, pixelsPerSecond, frameRate, range) {
|
|
3794
|
+
if (!isSupportedTickDuration(duration)) return { major: [], minor: [] };
|
|
3795
|
+
const { majorInterval, subdivisions } = tickSpacing(duration, pixelsPerSecond, frameRate);
|
|
3796
|
+
const minorInterval = subdivisions > 0 ? majorInterval / subdivisions : 0;
|
|
3797
|
+
const major = [];
|
|
3798
|
+
const minor = [];
|
|
3799
|
+
const maxTicks = 2e3;
|
|
3800
|
+
const tickRange = getTickRange(duration, range);
|
|
3801
|
+
const firstMajorIndex = Math.max(0, Math.floor(tickRange.start / majorInterval));
|
|
3802
|
+
for (let index = firstMajorIndex; major.length < maxTicks; index++) {
|
|
3803
|
+
const time = index * majorInterval;
|
|
3804
|
+
if (time > tickRange.end + 1e-3) break;
|
|
3805
|
+
if (time >= tickRange.start - 1e-3) major.push(roundTickValue(time));
|
|
3806
|
+
appendMinorTicks(minor, time, minorInterval, subdivisions, tickRange, maxTicks, major.length);
|
|
3807
|
+
}
|
|
3808
|
+
return { major, minor };
|
|
3809
|
+
}
|
|
3810
|
+
function formatTimelineTickLabel(time, duration, majorInterval) {
|
|
3811
|
+
if (!Number.isFinite(time)) return "00:00";
|
|
3812
|
+
const safeTime = Math.max(0, time);
|
|
3813
|
+
if (majorInterval < 0.1) {
|
|
3814
|
+
const totalHundredths = Math.round(safeTime * 100);
|
|
3815
|
+
const wholeSeconds = Math.floor(totalHundredths / 100);
|
|
3816
|
+
const hundredth = totalHundredths % 100;
|
|
3817
|
+
return `${formatTime(wholeSeconds)}.${hundredth.toString().padStart(2, "0")}`;
|
|
3818
|
+
}
|
|
3819
|
+
if (majorInterval < 1) {
|
|
3820
|
+
const totalTenths = Math.round(safeTime * 10);
|
|
3821
|
+
const wholeSeconds = Math.floor(totalTenths / 10);
|
|
3822
|
+
const tenth = totalTenths % 10;
|
|
3823
|
+
return `${formatTime(wholeSeconds)}.${tenth}`;
|
|
3824
|
+
}
|
|
3825
|
+
if (duration >= 3600 || safeTime >= 3600) {
|
|
3826
|
+
const totalSeconds = Math.floor(safeTime);
|
|
3827
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
3828
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
3829
|
+
const seconds = totalSeconds % 60;
|
|
3830
|
+
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
3831
|
+
}
|
|
3832
|
+
return formatTime(safeTime);
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
// src/player/store/previewFrameStore.ts
|
|
3836
|
+
import { create } from "zustand";
|
|
3837
|
+
var usePreviewFrameStore = create(() => ({ time: null }));
|
|
3838
|
+
var setPreviewFrame = (time) => usePreviewFrameStore.setState({ time });
|
|
3839
|
+
function showingPreviewFrame(adapter) {
|
|
3840
|
+
let seeked = false;
|
|
3841
|
+
const time = () => seeked ? adapter.getTime() : usePlayerStore.getState().currentTime;
|
|
3842
|
+
return {
|
|
3843
|
+
play: () => {
|
|
3844
|
+
adapter.seek(time());
|
|
3845
|
+
adapter.play();
|
|
3846
|
+
},
|
|
3847
|
+
pause: () => adapter.pause(),
|
|
3848
|
+
seek: (t2, options) => {
|
|
3849
|
+
seeked = true;
|
|
3850
|
+
adapter.seek(t2, options);
|
|
3851
|
+
},
|
|
3852
|
+
getTime: time,
|
|
3853
|
+
getDuration: () => adapter.getDuration(),
|
|
3854
|
+
isPlaying: () => adapter.isPlaying()
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
function transportAdapter(adapter) {
|
|
3858
|
+
const showing = usePreviewFrameStore.getState().time !== null;
|
|
3859
|
+
return adapter && showing && !usePlayerStore.getState().isPlaying ? showingPreviewFrame(adapter) : adapter;
|
|
3860
|
+
}
|
|
3861
|
+
function subscribePreviewFrame(getAdapter) {
|
|
3862
|
+
return usePreviewFrameStore.subscribe((state, prev) => {
|
|
3863
|
+
if (state.time === prev.time || usePlayerStore.getState().isPlaying) return;
|
|
3864
|
+
const adapter = getAdapter();
|
|
3865
|
+
const time = state.time ?? usePlayerStore.getState().currentTime;
|
|
3866
|
+
adapter?.seek(clampToDuration(time, adapter.getDuration()));
|
|
3867
|
+
});
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3695
3870
|
// src/player/components/timelineAuthoredMoveTarget.ts
|
|
3696
3871
|
function canMoveTimelineElement(element) {
|
|
3697
3872
|
return getTimelineEditCapabilities({
|
|
@@ -4019,7 +4194,8 @@ function computeDragPreview(drag, clientX, clientY, ctx3) {
|
|
|
4019
4194
|
pps,
|
|
4020
4195
|
// Relaxed clamp: allow the snapped start past the content, up to the
|
|
4021
4196
|
// rendered extent (see dragMaxStart) — the composition grows on commit.
|
|
4022
|
-
dragMaxStart + drag.element.duration
|
|
4197
|
+
dragMaxStart + drag.element.duration,
|
|
4198
|
+
ctx3.gridStep
|
|
4023
4199
|
);
|
|
4024
4200
|
const group = resolveGroupDrag(drag, ctx3);
|
|
4025
4201
|
const dragKey = drag.element.key ?? drag.element.id;
|
|
@@ -4037,6 +4213,8 @@ function computeDragPreview(drag, clientX, clientY, ctx3) {
|
|
|
4037
4213
|
return { ...drag, started: true };
|
|
4038
4214
|
}
|
|
4039
4215
|
const { track: previewTrack, insertRow } = placement;
|
|
4216
|
+
const target = placement.start === snap.start && snap.snapTime !== null && snap.snapType !== null ? { time: snap.snapTime, type: snap.snapType } : null;
|
|
4217
|
+
const guide = guideIfSaved(drag.element, { start: snap.start }, target, pps);
|
|
4040
4218
|
return {
|
|
4041
4219
|
...drag,
|
|
4042
4220
|
started: true,
|
|
@@ -4048,10 +4226,16 @@ function computeDragPreview(drag, clientX, clientY, ctx3) {
|
|
|
4048
4226
|
// tell a deliberate vertical lane change from a horizontal drag.
|
|
4049
4227
|
desiredTrack: nextMove.track,
|
|
4050
4228
|
insertRow,
|
|
4051
|
-
snapTime:
|
|
4052
|
-
snapType:
|
|
4229
|
+
snapTime: guide?.time ?? null,
|
|
4230
|
+
snapType: guide?.type ?? null
|
|
4053
4231
|
};
|
|
4054
4232
|
}
|
|
4233
|
+
function guideIfSaved(element, clip, target, pps) {
|
|
4234
|
+
if (!target) return null;
|
|
4235
|
+
const saved2 = savedClipEdges(element, clip.start, clip.duration);
|
|
4236
|
+
const off = Math.min(Math.abs(saved2.start - target.time), Math.abs(saved2.end - target.time));
|
|
4237
|
+
return off * pps < 1 ? target : null;
|
|
4238
|
+
}
|
|
4055
4239
|
var TRIM_END_FRAME_LEAD_S = 1 / STUDIO_PREVIEW_FPS;
|
|
4056
4240
|
function trimPreviewTime(edge, start, duration) {
|
|
4057
4241
|
return edge === "start" ? start : Math.max(start, start + duration - TRIM_END_FRAME_LEAD_S);
|
|
@@ -4085,28 +4269,33 @@ function computeResizePreview(resize2, clientX, ctx3) {
|
|
|
4085
4269
|
);
|
|
4086
4270
|
const trimTargets = buildSnapTargets(
|
|
4087
4271
|
resize2.element.key ?? resize2.element.id,
|
|
4088
|
-
!isMusicTrack(resize2.element)
|
|
4089
|
-
false
|
|
4272
|
+
!isMusicTrack(resize2.element)
|
|
4090
4273
|
);
|
|
4274
|
+
const gridStep = ctx3.gridStep ?? 0;
|
|
4091
4275
|
let snap = null;
|
|
4092
|
-
if (trimTargets.length > 0) {
|
|
4276
|
+
if (trimTargets.length > 0 || gridStep > 0) {
|
|
4093
4277
|
const snapSecs = TIMELINE_SNAP_PX / Math.max(pps, 1);
|
|
4094
4278
|
if (resize2.edge === "end") {
|
|
4095
4279
|
const edgeTime2 = nextResize.start + nextResize.duration;
|
|
4096
|
-
const { time: snapped, target } = snapTimelineTime(edgeTime2, trimTargets, snapSecs);
|
|
4280
|
+
const { time: snapped, target } = snapTimelineTime(edgeTime2, trimTargets, snapSecs, gridStep);
|
|
4097
4281
|
const snappedDuration = Math.round((snapped - nextResize.start) * 1e3) / 1e3;
|
|
4098
4282
|
if (target && snapped <= maxEnd + 1e-6 && snappedDuration >= resolveTimelineMinDuration() - 1e-6) {
|
|
4099
4283
|
if (snapped !== edgeTime2) nextResize = { ...nextResize, duration: snappedDuration };
|
|
4100
|
-
snap = target;
|
|
4284
|
+
snap = guideIfSaved(resize2.element, nextResize, target, pps);
|
|
4101
4285
|
}
|
|
4102
4286
|
} else {
|
|
4103
|
-
const { time: snapped, target } = snapTimelineTime(
|
|
4287
|
+
const { time: snapped, target } = snapTimelineTime(
|
|
4288
|
+
nextResize.start,
|
|
4289
|
+
trimTargets,
|
|
4290
|
+
snapSecs,
|
|
4291
|
+
gridStep
|
|
4292
|
+
);
|
|
4104
4293
|
const clip = { ...nextResize, playbackRate: resize2.element.playbackRate };
|
|
4105
4294
|
const delta = snapped - nextResize.start;
|
|
4106
4295
|
const bounds = clipStartTrimDeltaBounds(clip, minStart, resolveTimelineMinDuration());
|
|
4107
4296
|
if (target && delta >= bounds.minDelta - 1e-6 && delta <= bounds.maxDelta + 1e-6) {
|
|
4108
4297
|
if (snapped !== nextResize.start) nextResize = applyClipStartTrimDelta(clip, delta);
|
|
4109
|
-
snap = target;
|
|
4298
|
+
snap = guideIfSaved(resize2.element, nextResize, target, pps);
|
|
4110
4299
|
}
|
|
4111
4300
|
}
|
|
4112
4301
|
}
|
|
@@ -4137,8 +4326,8 @@ function previewGroupResize(session, next, setResizeState) {
|
|
|
4137
4326
|
}
|
|
4138
4327
|
|
|
4139
4328
|
// src/utils/linkedClipPreferences.ts
|
|
4140
|
-
import { create } from "zustand";
|
|
4141
|
-
var useLinkedClipPreferences =
|
|
4329
|
+
import { create as create2 } from "zustand";
|
|
4330
|
+
var useLinkedClipPreferences = create2((set) => ({
|
|
4142
4331
|
linkedSelection: readStudioUiPreferences().linkedSelectionEnabled ?? true,
|
|
4143
4332
|
setLinkedSelection: (enabled) => {
|
|
4144
4333
|
writeStudioUiPreferences({ linkedSelectionEnabled: enabled });
|
|
@@ -9719,7 +9908,6 @@ function useTimelineClipDrag({
|
|
|
9719
9908
|
onResizeElements,
|
|
9720
9909
|
onBlockedEditAttempt,
|
|
9721
9910
|
onLinkEdit,
|
|
9722
|
-
onSeek,
|
|
9723
9911
|
setShowPopover,
|
|
9724
9912
|
setRangeSelectionRef,
|
|
9725
9913
|
readZIndex,
|
|
@@ -9769,23 +9957,27 @@ function useTimelineClipDrag({
|
|
|
9769
9957
|
const snapTargetsCacheRef = useRef12(/* @__PURE__ */ new Map());
|
|
9770
9958
|
const dragAudioTracksRef = useRef12(null);
|
|
9771
9959
|
const buildSnapTargets = useCallback9(
|
|
9772
|
-
(excludeElementKey, includeBeats
|
|
9960
|
+
(excludeElementKey, includeBeats) => {
|
|
9773
9961
|
if (!snapContextRef.current.enabled) return [];
|
|
9774
|
-
const cacheKey = `${excludeElementKey ?? ""}|${includeBeats ? 1 : 0}
|
|
9962
|
+
const cacheKey = `${excludeElementKey ?? ""}|${includeBeats ? 1 : 0}`;
|
|
9775
9963
|
const cached5 = snapTargetsCacheRef.current.get(cacheKey);
|
|
9776
9964
|
if (cached5) return cached5;
|
|
9777
9965
|
const targets = collectTimelineSnapTargets({
|
|
9778
9966
|
elements: elementsRef.current,
|
|
9779
9967
|
playheadTime: usePlayerStore.getState().currentTime,
|
|
9780
9968
|
beatTimes: includeBeats ? snapContextRef.current.beatTimes : [],
|
|
9781
|
-
excludeElementKey
|
|
9782
|
-
includePlayhead
|
|
9969
|
+
excludeElementKey
|
|
9783
9970
|
});
|
|
9784
9971
|
snapTargetsCacheRef.current.set(cacheKey, targets);
|
|
9785
9972
|
return targets;
|
|
9786
9973
|
},
|
|
9787
9974
|
[]
|
|
9788
9975
|
);
|
|
9976
|
+
const snapGridStep = useCallback9(() => {
|
|
9977
|
+
if (!snapContextRef.current.enabled) return 0;
|
|
9978
|
+
const frameRate = rulerFrameRate(usePlayerStore.getState().timeDisplayMode);
|
|
9979
|
+
return getTimelineGridStep(durationRef.current, ppsRef.current, frameRate);
|
|
9980
|
+
}, [durationRef, ppsRef]);
|
|
9789
9981
|
const [draggedClip, setDraggedClipState] = useState7(null);
|
|
9790
9982
|
const draggedClipRef = useRef12(null);
|
|
9791
9983
|
const publishDraggedClip = useCallback9((next) => {
|
|
@@ -9874,9 +10066,6 @@ function useTimelineClipDrag({
|
|
|
9874
10066
|
onResizeElementRef.current = onResizeElement;
|
|
9875
10067
|
const onResizeElementsRef = useRef12(onResizeElements);
|
|
9876
10068
|
onResizeElementsRef.current = onResizeElements;
|
|
9877
|
-
const onSeekRef = useRef12(onSeek);
|
|
9878
|
-
onSeekRef.current = onSeek;
|
|
9879
|
-
const trimSeekOriginRef = useRef12(null);
|
|
9880
10069
|
const readZIndexRef = useRef12(readZIndex);
|
|
9881
10070
|
readZIndexRef.current = readZIndex;
|
|
9882
10071
|
const onStackingPatchesRef = useRef12(onStackingPatches);
|
|
@@ -9902,7 +10091,8 @@ function useTimelineClipDrag({
|
|
|
9902
10091
|
elements: elementsRef.current,
|
|
9903
10092
|
selectedKeys: gestureSelectedKeysRef.current,
|
|
9904
10093
|
buildSnapTargets,
|
|
9905
|
-
audioTracks: dragAudioTracksRef.current
|
|
10094
|
+
audioTracks: dragAudioTracksRef.current,
|
|
10095
|
+
gridStep: snapGridStep()
|
|
9906
10096
|
});
|
|
9907
10097
|
},
|
|
9908
10098
|
[
|
|
@@ -9912,7 +10102,8 @@ function useTimelineClipDrag({
|
|
|
9912
10102
|
trackOrderRef,
|
|
9913
10103
|
trackInsertLayoutRef,
|
|
9914
10104
|
rowGeometryRef,
|
|
9915
|
-
buildSnapTargets
|
|
10105
|
+
buildSnapTargets,
|
|
10106
|
+
snapGridStep
|
|
9916
10107
|
]
|
|
9917
10108
|
);
|
|
9918
10109
|
const applyResizePointer = useCallback9(
|
|
@@ -9922,14 +10113,11 @@ function useTimelineClipDrag({
|
|
|
9922
10113
|
pps: ppsRef.current,
|
|
9923
10114
|
buildSnapTargets,
|
|
9924
10115
|
elements: elementsRef.current,
|
|
9925
|
-
gestureKeys: gestureSelectedKeysRef.current
|
|
10116
|
+
gestureKeys: gestureSelectedKeysRef.current,
|
|
10117
|
+
gridStep: snapGridStep()
|
|
9926
10118
|
});
|
|
9927
|
-
trimSeekOriginRef.current ??= usePlayerStore.getState().currentTime;
|
|
9928
10119
|
const setResizeState = (v) => {
|
|
9929
|
-
|
|
9930
|
-
keepPlaying: true,
|
|
9931
|
-
follow: false
|
|
9932
|
-
});
|
|
10120
|
+
setPreviewFrame(trimPreviewTime(resize2.edge, v.previewStart, v.previewDuration));
|
|
9933
10121
|
publishResizingClip(
|
|
9934
10122
|
resizingClipRef.current ? { ...resizingClipRef.current, started: true, ...v } : null
|
|
9935
10123
|
);
|
|
@@ -9958,7 +10146,7 @@ function useTimelineClipDrag({
|
|
|
9958
10146
|
}
|
|
9959
10147
|
previewGroupResize(session, next, setResizeState);
|
|
9960
10148
|
},
|
|
9961
|
-
[scrollRef, ppsRef, buildSnapTargets, publishResizingClip]
|
|
10149
|
+
[scrollRef, ppsRef, buildSnapTargets, snapGridStep, publishResizingClip]
|
|
9962
10150
|
);
|
|
9963
10151
|
const applyResizePointerRef = useRef12(applyResizePointer);
|
|
9964
10152
|
applyResizePointerRef.current = applyResizePointer;
|
|
@@ -9968,12 +10156,7 @@ function useTimelineClipDrag({
|
|
|
9968
10156
|
cancelAnimationFrame(clipDragScrollRaf.current);
|
|
9969
10157
|
clipDragScrollRaf.current = 0;
|
|
9970
10158
|
}
|
|
9971
|
-
|
|
9972
|
-
if (!usePlayerStore.getState().isPlaying) {
|
|
9973
|
-
onSeekRef.current?.(trimSeekOriginRef.current, { keepPlaying: true, follow: false });
|
|
9974
|
-
}
|
|
9975
|
-
trimSeekOriginRef.current = null;
|
|
9976
|
-
}
|
|
10159
|
+
setPreviewFrame(null);
|
|
9977
10160
|
snapTargetsCacheRef.current.clear();
|
|
9978
10161
|
dragAudioTracksRef.current = null;
|
|
9979
10162
|
}, []);
|
|
@@ -13152,122 +13335,8 @@ function useTimelineShiftModifier() {
|
|
|
13152
13335
|
|
|
13153
13336
|
// src/player/components/useTimelineTicks.ts
|
|
13154
13337
|
import { useMemo as useMemo18 } from "react";
|
|
13155
|
-
|
|
13156
|
-
// src/player/components/timelineRulerGeometry.ts
|
|
13157
|
-
function getTimelineMajorTickInterval(duration, pixelsPerSecond, frameRate) {
|
|
13158
|
-
const zoomIntervals = [
|
|
13159
|
-
0.02,
|
|
13160
|
-
0.05,
|
|
13161
|
-
0.1,
|
|
13162
|
-
0.2,
|
|
13163
|
-
0.5,
|
|
13164
|
-
1,
|
|
13165
|
-
2,
|
|
13166
|
-
5,
|
|
13167
|
-
10,
|
|
13168
|
-
15,
|
|
13169
|
-
30,
|
|
13170
|
-
60,
|
|
13171
|
-
120,
|
|
13172
|
-
300,
|
|
13173
|
-
600,
|
|
13174
|
-
900,
|
|
13175
|
-
1800,
|
|
13176
|
-
3600
|
|
13177
|
-
];
|
|
13178
|
-
let interval;
|
|
13179
|
-
if (Number.isFinite(pixelsPerSecond) && (pixelsPerSecond ?? 0) > 0) {
|
|
13180
|
-
const targetMajorPx = 88;
|
|
13181
|
-
interval = zoomIntervals.find((candidate) => candidate * (pixelsPerSecond ?? 0) >= targetMajorPx) ?? 3600;
|
|
13182
|
-
} else {
|
|
13183
|
-
const durationIntervals = [0.25, 0.5, 1, 2, 5, 10, 15, 30, 60];
|
|
13184
|
-
const target = duration / 6;
|
|
13185
|
-
interval = durationIntervals.find((candidate) => candidate >= target) ?? 60;
|
|
13186
|
-
}
|
|
13187
|
-
if (Number.isFinite(frameRate) && (frameRate ?? 0) > 0) {
|
|
13188
|
-
const fps = frameRate ?? 0;
|
|
13189
|
-
return Math.max(1, Math.ceil(interval * fps - 1e-6)) / fps;
|
|
13190
|
-
}
|
|
13191
|
-
return interval;
|
|
13192
|
-
}
|
|
13193
|
-
function getMinorSubdivisions(majorInterval, pixelsPerSecond, frameRate) {
|
|
13194
|
-
const pps = Number.isFinite(pixelsPerSecond) ? pixelsPerSecond ?? 0 : 0;
|
|
13195
|
-
if (pps <= 0) return 4;
|
|
13196
|
-
const fps = Number.isFinite(frameRate) ? frameRate ?? 0 : 0;
|
|
13197
|
-
const majorFrames = fps > 0 ? Math.round(majorInterval * fps) : 0;
|
|
13198
|
-
const candidates = fps > 0 ? [4, 5, 3, 2] : [4, 2];
|
|
13199
|
-
for (const parts of candidates) {
|
|
13200
|
-
if (fps > 0 && majorFrames % parts !== 0) continue;
|
|
13201
|
-
if (majorInterval / parts * pps >= 8) return parts;
|
|
13202
|
-
}
|
|
13203
|
-
return 0;
|
|
13204
|
-
}
|
|
13205
|
-
function roundTickValue(time) {
|
|
13206
|
-
return Math.round(time * 1e6) / 1e6;
|
|
13207
|
-
}
|
|
13208
|
-
function isSupportedTickDuration(duration) {
|
|
13209
|
-
return duration > 0 && Number.isFinite(duration) && duration <= 14400;
|
|
13210
|
-
}
|
|
13211
|
-
function getTickRange(duration, range) {
|
|
13212
|
-
return {
|
|
13213
|
-
start: Math.max(0, range?.start ?? 0),
|
|
13214
|
-
end: Math.min(duration, range?.end ?? duration)
|
|
13215
|
-
};
|
|
13216
|
-
}
|
|
13217
|
-
function appendMinorTicks(minor, majorTime, minorInterval, subdivisions, range, maxTicks, majorCount) {
|
|
13218
|
-
for (let part = 1; part < subdivisions && majorCount + minor.length < maxTicks; part++) {
|
|
13219
|
-
const time = majorTime + part * minorInterval;
|
|
13220
|
-
if (time >= range.start - 1e-3 && time <= range.end + 1e-3) {
|
|
13221
|
-
minor.push(roundTickValue(time));
|
|
13222
|
-
}
|
|
13223
|
-
}
|
|
13224
|
-
}
|
|
13225
|
-
function generateTicks(duration, pixelsPerSecond, frameRate, range) {
|
|
13226
|
-
if (!isSupportedTickDuration(duration)) return { major: [], minor: [] };
|
|
13227
|
-
const majorInterval = getTimelineMajorTickInterval(duration, pixelsPerSecond, frameRate);
|
|
13228
|
-
const subdivisions = getMinorSubdivisions(majorInterval, pixelsPerSecond, frameRate);
|
|
13229
|
-
const minorInterval = subdivisions > 0 ? majorInterval / subdivisions : 0;
|
|
13230
|
-
const major = [];
|
|
13231
|
-
const minor = [];
|
|
13232
|
-
const maxTicks = 2e3;
|
|
13233
|
-
const tickRange = getTickRange(duration, range);
|
|
13234
|
-
const firstMajorIndex = Math.max(0, Math.floor(tickRange.start / majorInterval));
|
|
13235
|
-
for (let index = firstMajorIndex; major.length < maxTicks; index++) {
|
|
13236
|
-
const time = index * majorInterval;
|
|
13237
|
-
if (time > tickRange.end + 1e-3) break;
|
|
13238
|
-
if (time >= tickRange.start - 1e-3) major.push(roundTickValue(time));
|
|
13239
|
-
appendMinorTicks(minor, time, minorInterval, subdivisions, tickRange, maxTicks, major.length);
|
|
13240
|
-
}
|
|
13241
|
-
return { major, minor };
|
|
13242
|
-
}
|
|
13243
|
-
function formatTimelineTickLabel(time, duration, majorInterval) {
|
|
13244
|
-
if (!Number.isFinite(time)) return "00:00";
|
|
13245
|
-
const safeTime = Math.max(0, time);
|
|
13246
|
-
if (majorInterval < 0.1) {
|
|
13247
|
-
const totalHundredths = Math.round(safeTime * 100);
|
|
13248
|
-
const wholeSeconds = Math.floor(totalHundredths / 100);
|
|
13249
|
-
const hundredth = totalHundredths % 100;
|
|
13250
|
-
return `${formatTime(wholeSeconds)}.${hundredth.toString().padStart(2, "0")}`;
|
|
13251
|
-
}
|
|
13252
|
-
if (majorInterval < 1) {
|
|
13253
|
-
const totalTenths = Math.round(safeTime * 10);
|
|
13254
|
-
const wholeSeconds = Math.floor(totalTenths / 10);
|
|
13255
|
-
const tenth = totalTenths % 10;
|
|
13256
|
-
return `${formatTime(wholeSeconds)}.${tenth}`;
|
|
13257
|
-
}
|
|
13258
|
-
if (duration >= 3600 || safeTime >= 3600) {
|
|
13259
|
-
const totalSeconds = Math.floor(safeTime);
|
|
13260
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
13261
|
-
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
13262
|
-
const seconds = totalSeconds % 60;
|
|
13263
|
-
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
13264
|
-
}
|
|
13265
|
-
return formatTime(safeTime);
|
|
13266
|
-
}
|
|
13267
|
-
|
|
13268
|
-
// src/player/components/useTimelineTicks.ts
|
|
13269
13338
|
function useTimelineTicks(duration, pixelsPerSecond, timeDisplayMode, renderTimeRange) {
|
|
13270
|
-
const frameRate = timeDisplayMode
|
|
13339
|
+
const frameRate = rulerFrameRate(timeDisplayMode);
|
|
13271
13340
|
const ticks = useMemo18(
|
|
13272
13341
|
() => generateTicks(duration, pixelsPerSecond, frameRate, renderTimeRange),
|
|
13273
13342
|
[duration, frameRate, pixelsPerSecond, renderTimeRange]
|
|
@@ -14206,7 +14275,6 @@ function useTimelineProviderState({
|
|
|
14206
14275
|
onResizeElements: pinnedOnResizeElements,
|
|
14207
14276
|
onBlockedEditAttempt: oneShotEdits.onBlockedEditAttempt,
|
|
14208
14277
|
onLinkEdit: oneShotEdits.onLinkEdit,
|
|
14209
|
-
onSeek,
|
|
14210
14278
|
setShowPopover,
|
|
14211
14279
|
setRangeSelectionRef,
|
|
14212
14280
|
readZIndex: zSyncEnabled ? readClipZIndex : void 0,
|
|
@@ -14997,7 +15065,8 @@ var DEFAULT_FADE_SECONDS = 0.5;
|
|
|
14997
15065
|
var SNAP_LABEL = {
|
|
14998
15066
|
playhead: "playhead",
|
|
14999
15067
|
"clip-edge": "clip edge",
|
|
15000
|
-
beat: "beat"
|
|
15068
|
+
beat: "beat",
|
|
15069
|
+
grid: "grid line"
|
|
15001
15070
|
};
|
|
15002
15071
|
var ClipFadesContext = createContext4(null);
|
|
15003
15072
|
function useClipFadeDraft(el) {
|
|
@@ -16348,8 +16417,8 @@ import { HF_AUDIO_FX_ATTR } from "@hyperframes/core/audio-fx";
|
|
|
16348
16417
|
import { HF_COLOR_GRADING_ATTR } from "@hyperframes/core/color-grading";
|
|
16349
16418
|
|
|
16350
16419
|
// src/player/store/previewIframeStore.ts
|
|
16351
|
-
import { create as
|
|
16352
|
-
var usePreviewIframeStore =
|
|
16420
|
+
import { create as create3 } from "zustand";
|
|
16421
|
+
var usePreviewIframeStore = create3((set) => ({
|
|
16353
16422
|
iframe: null,
|
|
16354
16423
|
// Return the same state reference on a no-op set so zustand skips the
|
|
16355
16424
|
// notify (Object.is check in setState) instead of merging a new object
|
|
@@ -17061,7 +17130,10 @@ function fileDocs(ownFile, written, nestedFiles) {
|
|
|
17061
17130
|
}
|
|
17062
17131
|
|
|
17063
17132
|
// src/utils/gsapSoftReload.ts
|
|
17133
|
+
var pendingReadyRetry = null;
|
|
17064
17134
|
function ensureMotionPathPluginLoaded(iframe) {
|
|
17135
|
+
if (pendingReadyRetry) window.removeEventListener("message", pendingReadyRetry);
|
|
17136
|
+
pendingReadyRetry = null;
|
|
17065
17137
|
if (!iframe?.contentWindow || !iframe.contentDocument) return;
|
|
17066
17138
|
const win = iframe.contentWindow;
|
|
17067
17139
|
const doc = iframe.contentDocument;
|
|
@@ -17072,7 +17144,19 @@ function ensureMotionPathPluginLoaded(iframe) {
|
|
|
17072
17144
|
}
|
|
17073
17145
|
return;
|
|
17074
17146
|
}
|
|
17075
|
-
if (!win.gsap?.registerPlugin)
|
|
17147
|
+
if (!win.gsap?.registerPlugin) {
|
|
17148
|
+
if (win.__playerReady) return;
|
|
17149
|
+
const retry = (event) => {
|
|
17150
|
+
const data = event.data;
|
|
17151
|
+
if (event.source !== win || data?.source !== "hf-preview" || data.type !== "ready") return;
|
|
17152
|
+
window.removeEventListener("message", retry);
|
|
17153
|
+
pendingReadyRetry = null;
|
|
17154
|
+
if (win.gsap?.registerPlugin) ensureMotionPathPluginLoaded(iframe);
|
|
17155
|
+
};
|
|
17156
|
+
pendingReadyRetry = retry;
|
|
17157
|
+
window.addEventListener("message", retry);
|
|
17158
|
+
return;
|
|
17159
|
+
}
|
|
17076
17160
|
if (win.__hfMotionPathPluginLoading) return;
|
|
17077
17161
|
try {
|
|
17078
17162
|
win.__hfMotionPathPluginLoading = true;
|
|
@@ -18062,8 +18146,8 @@ async function persistElementAttribute({
|
|
|
18062
18146
|
}
|
|
18063
18147
|
|
|
18064
18148
|
// src/components/editor/cropPresetStore.ts
|
|
18065
|
-
import { create as
|
|
18066
|
-
var useCropPresetBarStore =
|
|
18149
|
+
import { create as create4 } from "zustand";
|
|
18150
|
+
var useCropPresetBarStore = create4((set) => ({
|
|
18067
18151
|
openFor: null,
|
|
18068
18152
|
open: (target) => set({ openFor: target }),
|
|
18069
18153
|
close: () => set({ openFor: null })
|
|
@@ -24330,8 +24414,8 @@ async function normalizeClipsLoudness(projectId, elements, writers) {
|
|
|
24330
24414
|
}
|
|
24331
24415
|
|
|
24332
24416
|
// src/player/components/audioGainDialogStore.ts
|
|
24333
|
-
import { create as
|
|
24334
|
-
var useAudioGainDialogStore =
|
|
24417
|
+
import { create as create5 } from "zustand";
|
|
24418
|
+
var useAudioGainDialogStore = create5((set) => ({
|
|
24335
24419
|
targetKeys: null,
|
|
24336
24420
|
open: (keys) => set({ targetKeys: keys }),
|
|
24337
24421
|
close: () => set({ targetKeys: null })
|
|
@@ -27332,7 +27416,7 @@ import { useRef as useRef48, useCallback as useCallback42, useEffect as useEffec
|
|
|
27332
27416
|
import { useRef as useRef45, useCallback as useCallback38 } from "react";
|
|
27333
27417
|
|
|
27334
27418
|
// src/captions/store.ts
|
|
27335
|
-
import { create as
|
|
27419
|
+
import { create as create6 } from "zustand";
|
|
27336
27420
|
var nextSplitId = 0;
|
|
27337
27421
|
var HISTORY_CAP = 50;
|
|
27338
27422
|
var HISTORY_COALESCE_MS = 800;
|
|
@@ -27362,7 +27446,7 @@ function pushHistory(state, key2) {
|
|
|
27362
27446
|
const past = [...state.past, state.model].slice(-HISTORY_CAP);
|
|
27363
27447
|
return { past, future: [] };
|
|
27364
27448
|
}
|
|
27365
|
-
var useCaptionStore =
|
|
27449
|
+
var useCaptionStore = create6((set, get) => ({
|
|
27366
27450
|
...initialState,
|
|
27367
27451
|
// Basic
|
|
27368
27452
|
setEditMode: (active) => set({ isEditMode: active }),
|
|
@@ -30830,10 +30914,11 @@ function useTimelinePlayer({
|
|
|
30830
30914
|
const iframe = overrideIframe !== void 0 ? overrideIframe : iframeRef.current;
|
|
30831
30915
|
const win = iframe?.contentWindow;
|
|
30832
30916
|
if (!iframe || !win) return null;
|
|
30833
|
-
|
|
30917
|
+
const adapter = resolvePlaybackAdapter(iframe, win, {
|
|
30834
30918
|
cache: staticSeekAdapterRef,
|
|
30835
30919
|
warned: staticSeekWarnedRef
|
|
30836
30920
|
});
|
|
30921
|
+
return transportAdapter(adapter);
|
|
30837
30922
|
} catch {
|
|
30838
30923
|
return null;
|
|
30839
30924
|
}
|
|
@@ -30977,7 +31062,7 @@ function useTimelinePlayer({
|
|
|
30977
31062
|
return false;
|
|
30978
31063
|
}
|
|
30979
31064
|
const duration = Math.max(0, adapter.getDuration());
|
|
30980
|
-
const nextTime =
|
|
31065
|
+
const nextTime = clampToDuration(time, duration);
|
|
30981
31066
|
const keepPlaying = options?.keepPlaying === true;
|
|
30982
31067
|
const shouldResumeAfterSeek = shouldResumeForwardPlaybackAfterSeek({
|
|
30983
31068
|
keepPlaying,
|
|
@@ -31038,6 +31123,7 @@ function useTimelinePlayer({
|
|
|
31038
31123
|
}
|
|
31039
31124
|
});
|
|
31040
31125
|
}, [seek, play, pause]);
|
|
31126
|
+
useEffect35(() => subscribePreviewFrame(getAdapter), [getAdapter]);
|
|
31041
31127
|
const { playbackKeyDownRef, playbackKeyUpRef, attachIframeShortcutListeners, togglePlay } = usePlaybackKeyboard({
|
|
31042
31128
|
iframeRef,
|
|
31043
31129
|
shuttleDirectionRef,
|
|
@@ -31333,8 +31419,8 @@ function useCompositionStack({
|
|
|
31333
31419
|
}
|
|
31334
31420
|
|
|
31335
31421
|
// src/utils/assetPreviewStore.ts
|
|
31336
|
-
import { create as
|
|
31337
|
-
var useAssetPreviewStore =
|
|
31422
|
+
import { create as create7 } from "zustand";
|
|
31423
|
+
var useAssetPreviewStore = create7((set) => ({
|
|
31338
31424
|
previewAsset: null,
|
|
31339
31425
|
previewProjectId: null,
|
|
31340
31426
|
setPreviewAsset: (asset, projectId) => set({ previewAsset: asset, previewProjectId: projectId }),
|
|
@@ -31758,9 +31844,9 @@ function resolvePreviewVisibleRegion(input) {
|
|
|
31758
31844
|
}
|
|
31759
31845
|
|
|
31760
31846
|
// src/components/editor/previewGuidesStore.ts
|
|
31761
|
-
import { create as
|
|
31847
|
+
import { create as create8 } from "zustand";
|
|
31762
31848
|
var RULER_GUTTER_PX = 16;
|
|
31763
|
-
var usePreviewGuidesStore =
|
|
31849
|
+
var usePreviewGuidesStore = create8((set, get) => {
|
|
31764
31850
|
const stored = readStudioUiPreferences();
|
|
31765
31851
|
return {
|
|
31766
31852
|
rulerVisible: stored.rulerVisible ?? false,
|
|
@@ -33303,8 +33389,8 @@ import {
|
|
|
33303
33389
|
} from "@hyperframes/core/audio-gain";
|
|
33304
33390
|
|
|
33305
33391
|
// src/utils/audioMeterVisibility.ts
|
|
33306
|
-
import { create as
|
|
33307
|
-
var useAudioMetersVisible =
|
|
33392
|
+
import { create as create9 } from "zustand";
|
|
33393
|
+
var useAudioMetersVisible = create9((set) => ({
|
|
33308
33394
|
visible: readStudioUiPreferences().audioMetersVisible ?? false,
|
|
33309
33395
|
setVisible: (visible) => {
|
|
33310
33396
|
writeStudioUiPreferences({ audioMetersVisible: visible });
|
|
@@ -37894,7 +37980,6 @@ function startGesture(kind, e, opts, options) {
|
|
|
37894
37980
|
let pathOffsetMember;
|
|
37895
37981
|
if (kind === "drag") {
|
|
37896
37982
|
opts.onManualDragStartRef.current?.();
|
|
37897
|
-
opts.rafPausedRef.current = true;
|
|
37898
37983
|
const result = createManualOffsetDragMember({
|
|
37899
37984
|
key: selectionCacheKey(sel),
|
|
37900
37985
|
selection: sel,
|
|
@@ -37904,6 +37989,7 @@ function startGesture(kind, e, opts, options) {
|
|
|
37904
37989
|
});
|
|
37905
37990
|
if (!result.ok) {
|
|
37906
37991
|
opts.onBlockedMoveRef.current(result.selection, result.reason);
|
|
37992
|
+
e.preventDefault();
|
|
37907
37993
|
return false;
|
|
37908
37994
|
}
|
|
37909
37995
|
pathOffsetMember = result.member;
|
|
@@ -38691,6 +38777,7 @@ function useDomEditNudge(params) {
|
|
|
38691
38777
|
const isGroup = groupItems.length > 1;
|
|
38692
38778
|
const targets = isGroup ? resolveGroupNudgeTargets(groupItems) : resolveSingleNudgeTarget(p.selectionRef.current, p.overlayRectRef.current);
|
|
38693
38779
|
if (!targets) return null;
|
|
38780
|
+
p.onManualDragStartRef.current?.();
|
|
38694
38781
|
const members = [];
|
|
38695
38782
|
for (const target of targets) {
|
|
38696
38783
|
const result = createManualOffsetDragMember({ ...target, gesture: "nudge" });
|
|
@@ -38702,7 +38789,6 @@ function useDomEditNudge(params) {
|
|
|
38702
38789
|
members.push(result.member);
|
|
38703
38790
|
}
|
|
38704
38791
|
if (members.length === 0) return null;
|
|
38705
|
-
p.onManualDragStartRef.current?.();
|
|
38706
38792
|
const edit = beginStudioPendingEdit(manualOffsetMoveRevert(members));
|
|
38707
38793
|
return { members, isGroup, accum: { x: 0, y: 0 }, timer: null, edit };
|
|
38708
38794
|
};
|
|
@@ -45100,7 +45186,7 @@ function nearestPointOnPath(px, py, nodes) {
|
|
|
45100
45186
|
}
|
|
45101
45187
|
return best;
|
|
45102
45188
|
}
|
|
45103
|
-
function buildMotionPathGeometry(read) {
|
|
45189
|
+
function buildMotionPathGeometry(read, base2 = { x: 0, y: 0 }) {
|
|
45104
45190
|
if (!read) return null;
|
|
45105
45191
|
const isArc = Boolean(read.arcPath);
|
|
45106
45192
|
const nodes = [];
|
|
@@ -45112,8 +45198,8 @@ function buildMotionPathGeometry(read) {
|
|
|
45112
45198
|
if (tweenHasX && !finite(kf.properties.x)) return;
|
|
45113
45199
|
if (tweenHasY && !finite(kf.properties.y)) return;
|
|
45114
45200
|
nodes.push({
|
|
45115
|
-
x: tweenHasX ? kf.properties.x :
|
|
45116
|
-
y: tweenHasY ? kf.properties.y :
|
|
45201
|
+
x: tweenHasX ? kf.properties.x : base2.x,
|
|
45202
|
+
y: tweenHasY ? kf.properties.y : base2.y,
|
|
45117
45203
|
ref: isArc ? { type: "waypoint", index: i } : { type: "keyframe", pct: kf.percentage }
|
|
45118
45204
|
});
|
|
45119
45205
|
});
|
|
@@ -45139,6 +45225,97 @@ function editableAnimationId(animations, kind, sel) {
|
|
|
45139
45225
|
return a?.id ?? null;
|
|
45140
45226
|
}
|
|
45141
45227
|
|
|
45228
|
+
// src/components/editor/motionPathLayerNode.ts
|
|
45229
|
+
var LAYER_BOX = '[data-dom-edit-selection-box="true"]';
|
|
45230
|
+
var dotRadius = (r, selected) => selected ? r * 1.5 : r;
|
|
45231
|
+
function pressBelongsToLayer(e, point, pressed, dotR, live) {
|
|
45232
|
+
if (live && Math.abs(pressed.x - live.x) < 0.5 && Math.abs(pressed.y - live.y) < 0.5) return true;
|
|
45233
|
+
const hits = e.currentTarget.ownerDocument.elementsFromPoint(e.clientX, e.clientY);
|
|
45234
|
+
const inBox = hits.some((el) => el.closest(LAYER_BOX));
|
|
45235
|
+
return inBox && Math.hypot(point.x - pressed.ax, point.y - pressed.ay) > dotR;
|
|
45236
|
+
}
|
|
45237
|
+
function pressSelectedLayer(e) {
|
|
45238
|
+
const box = e.currentTarget.ownerDocument.querySelector(LAYER_BOX);
|
|
45239
|
+
if (!box || getComputedStyle(box).pointerEvents === "none") return false;
|
|
45240
|
+
return !box.dispatchEvent(new PointerEvent("pointerdown", e.nativeEvent));
|
|
45241
|
+
}
|
|
45242
|
+
|
|
45243
|
+
// src/hooks/gsapPositionDetection.ts
|
|
45244
|
+
function readGsapPositionFromIframe(iframe, elementSelector) {
|
|
45245
|
+
const gsap3 = getIframeGsap(iframe);
|
|
45246
|
+
if (!gsap3) return null;
|
|
45247
|
+
const element = queryIframeElement(iframe, elementSelector);
|
|
45248
|
+
if (!element) return null;
|
|
45249
|
+
const x = Number(gsap3.getProperty(element, "x")) || 0;
|
|
45250
|
+
const y = Number(gsap3.getProperty(element, "y")) || 0;
|
|
45251
|
+
return { x, y };
|
|
45252
|
+
}
|
|
45253
|
+
function animHasPosition(anim) {
|
|
45254
|
+
if (anim.keyframes?.keyframes.some((kf) => "x" in kf.properties || "y" in kf.properties))
|
|
45255
|
+
return true;
|
|
45256
|
+
if (anim.method === "fromTo") {
|
|
45257
|
+
const from = anim.fromProperties;
|
|
45258
|
+
return "x" in anim.properties || "y" in anim.properties || !!(from && ("x" in from || "y" in from));
|
|
45259
|
+
}
|
|
45260
|
+
return "x" in anim.properties || "y" in anim.properties;
|
|
45261
|
+
}
|
|
45262
|
+
function findGsapPositionAnimation(animations, selector) {
|
|
45263
|
+
if (animations.length === 0) return null;
|
|
45264
|
+
const currentTime = usePlayerStore.getState().currentTime;
|
|
45265
|
+
const scored = animations.filter(animHasPosition).map((a) => {
|
|
45266
|
+
let score = 0;
|
|
45267
|
+
if (a.keyframes) score += 5;
|
|
45268
|
+
if (selector && a.targetSelector === selector) score += 8;
|
|
45269
|
+
else if (a.targetSelector.includes(",")) score -= 5;
|
|
45270
|
+
return { anim: a, score: score + playheadProximity(a, currentTime) };
|
|
45271
|
+
});
|
|
45272
|
+
scored.sort((a, b) => b.score - a.score);
|
|
45273
|
+
return scored[0]?.anim ?? null;
|
|
45274
|
+
}
|
|
45275
|
+
function playheadProximity(a, currentTime) {
|
|
45276
|
+
const pos = a.resolvedStart ?? (typeof a.position === "number" ? a.position : 0);
|
|
45277
|
+
const dur = a.duration ?? 0;
|
|
45278
|
+
if (currentTime >= pos - 0.05 && currentTime <= pos + dur + 0.05) return 50;
|
|
45279
|
+
return -Math.round(Math.min(Math.abs(currentTime - pos), Math.abs(currentTime - pos - dur)) * 5);
|
|
45280
|
+
}
|
|
45281
|
+
function statesValueAt(anim, time) {
|
|
45282
|
+
const duration = resolveTweenDuration(anim);
|
|
45283
|
+
if (!(duration > 0)) return false;
|
|
45284
|
+
const pct = (time - (resolveTweenStart(anim) ?? 0)) / duration * 100;
|
|
45285
|
+
const near3 = (keyframe) => Math.abs(keyframe - pct) <= KEYFRAME_PCT_MATCH;
|
|
45286
|
+
if (anim.keyframes) return anim.keyframes.keyframes.some((kf) => near3(kf.percentage));
|
|
45287
|
+
if (anim.method === "from") return near3(0);
|
|
45288
|
+
return near3(100) || anim.method === "fromTo" && near3(0);
|
|
45289
|
+
}
|
|
45290
|
+
function pickClosestToPlayhead(anims) {
|
|
45291
|
+
if (anims.length <= 1) return anims[0] ?? null;
|
|
45292
|
+
const ct = usePlayerStore.getState().currentTime;
|
|
45293
|
+
return anims.reduce((best, a) => {
|
|
45294
|
+
const dist = playheadDistance(a, ct);
|
|
45295
|
+
const bestDist = playheadDistance(best, ct);
|
|
45296
|
+
if (dist < bestDist) return a;
|
|
45297
|
+
const stated = dist === 0 && bestDist === 0 ? statedValueWinner(a, best, ct) : null;
|
|
45298
|
+
if (stated) return stated;
|
|
45299
|
+
if (dist === bestDist && keyframeCount(a) > keyframeCount(best)) return a;
|
|
45300
|
+
return best;
|
|
45301
|
+
});
|
|
45302
|
+
}
|
|
45303
|
+
function playheadDistance(anim, time) {
|
|
45304
|
+
const s = resolveTweenStart(anim) ?? 0;
|
|
45305
|
+
const e = s + resolveTweenDuration(anim);
|
|
45306
|
+
return time >= s && time <= e ? 0 : Math.min(Math.abs(time - s), Math.abs(time - e));
|
|
45307
|
+
}
|
|
45308
|
+
function statedValueWinner(a, b, time) {
|
|
45309
|
+
const [mine, theirs] = [statesValueAt(a, time), statesValueAt(b, time)];
|
|
45310
|
+
if (mine !== theirs) return mine ? a : b;
|
|
45311
|
+
const [s, bS] = [resolveTweenStart(a) ?? 0, resolveTweenStart(b) ?? 0];
|
|
45312
|
+
if (mine && s !== bS) return s > bS ? a : b;
|
|
45313
|
+
return null;
|
|
45314
|
+
}
|
|
45315
|
+
function keyframeCount(anim) {
|
|
45316
|
+
return anim.keyframes?.keyframes.length ?? 0;
|
|
45317
|
+
}
|
|
45318
|
+
|
|
45142
45319
|
// src/components/editor/MotionPathNode.tsx
|
|
45143
45320
|
import { jsx as jsx103, jsxs as jsxs74 } from "react/jsx-runtime";
|
|
45144
45321
|
var ACCENT = "var(--hf-accent, #3CE6AC)";
|
|
@@ -45147,7 +45324,7 @@ function MotionPathNode(props) {
|
|
|
45147
45324
|
const bx = cx + r * 1.8;
|
|
45148
45325
|
const by = cy - r * 1.8;
|
|
45149
45326
|
const k = r * 0.55;
|
|
45150
|
-
const side = (
|
|
45327
|
+
const side = dotRadius(r, selected) * 1.414;
|
|
45151
45328
|
return /* @__PURE__ */ jsxs74("g", { onPointerEnter: props.onEnter, onPointerLeave: props.onLeave, children: [
|
|
45152
45329
|
/* @__PURE__ */ jsx103(
|
|
45153
45330
|
"rect",
|
|
@@ -45394,7 +45571,8 @@ function useMotionPathData(iframeRef, selector) {
|
|
|
45394
45571
|
}
|
|
45395
45572
|
const recompute = () => {
|
|
45396
45573
|
const read = readRuntimeKeyframes(iframeRef.current, selector, void 0, ["x", "y"]);
|
|
45397
|
-
const
|
|
45574
|
+
const base2 = read ? readGsapPositionFromIframe(iframeRef.current, selector) : null;
|
|
45575
|
+
const next = buildMotionPathGeometry(read, base2 ?? void 0);
|
|
45398
45576
|
setGeometry(
|
|
45399
45577
|
(prev) => prev?.points === next?.points && prev?.kind === next?.kind ? prev : next
|
|
45400
45578
|
);
|
|
@@ -45564,6 +45742,10 @@ var MotionPathOverlay = memo29(function MotionPathOverlay2({
|
|
|
45564
45742
|
if (!interactive) return;
|
|
45565
45743
|
if (e.button !== 0) return;
|
|
45566
45744
|
e.stopPropagation();
|
|
45745
|
+
const live = readGsapPositionFromIframe(iframeRef.current, selectorFor(selection) ?? "");
|
|
45746
|
+
const dotR = dotRadius(nodeR, ref.type === "keyframe" && ref.pct === activeKeyframePct);
|
|
45747
|
+
if (pressBelongsToLayer(e, clientToComp(e), abs[index], dotR, live) && pressSelectedLayer(e))
|
|
45748
|
+
return;
|
|
45567
45749
|
e.target.setPointerCapture(e.pointerId);
|
|
45568
45750
|
dragRef.current = {
|
|
45569
45751
|
index,
|
|
@@ -46532,10 +46714,10 @@ function normalize(edit) {
|
|
|
46532
46714
|
const { anim } = edit;
|
|
46533
46715
|
const data = anim.keyframes;
|
|
46534
46716
|
if (data) {
|
|
46535
|
-
if (!isLinear(anim.ease) || !isLinear(data.ease)) return { reason: "eased-keyframes" };
|
|
46536
46717
|
if (data.format === "simple-array") return { reason: "simple-array-keyframes" };
|
|
46537
46718
|
const arrayStep = data.format === "object-array";
|
|
46538
46719
|
return {
|
|
46720
|
+
runEase: data.ease ?? anim.ease,
|
|
46539
46721
|
keyframes: data.keyframes.map((kf) => ({
|
|
46540
46722
|
...kf,
|
|
46541
46723
|
properties: { ...kf.properties },
|
|
@@ -46573,8 +46755,9 @@ function valueAt(keyframes, prop, side) {
|
|
|
46573
46755
|
const kf = side === "first" ? carrying[0] : carrying.at(-1);
|
|
46574
46756
|
return kf ? { percentage: kf.percentage, value: kf.properties[prop] } : null;
|
|
46575
46757
|
}
|
|
46758
|
+
var keyframeAt = (keyframes, percentage) => keyframes.find((kf) => Math.abs(kf.percentage - percentage) <= KEYFRAME_PCT_MATCH);
|
|
46576
46759
|
function upsert(keyframes, percentage, properties, ease) {
|
|
46577
|
-
const hit = keyframes
|
|
46760
|
+
const hit = keyframeAt(keyframes, percentage);
|
|
46578
46761
|
if (hit) {
|
|
46579
46762
|
Object.assign(hit.properties, properties);
|
|
46580
46763
|
return hit;
|
|
@@ -46622,11 +46805,15 @@ function planValueAtPlayhead(edit) {
|
|
|
46622
46805
|
let position = start;
|
|
46623
46806
|
let span = duration;
|
|
46624
46807
|
const pct = "percentage" in edit.at ? edit.at.percentage : roundPct2((edit.at.time - start) / duration * 100);
|
|
46808
|
+
const eased = !isLinear(norm.runEase);
|
|
46625
46809
|
if (pct >= -KEYFRAME_PCT_MATCH && pct <= 100 + KEYFRAME_PCT_MATCH) {
|
|
46626
46810
|
const at = Math.min(100, Math.max(0, pct));
|
|
46811
|
+
const hit = keyframeAt(keyframes, at);
|
|
46812
|
+
if (eased && hit?.percentage !== 0 && hit?.percentage !== 100) return refuse("eased-keyframes");
|
|
46627
46813
|
const next = [...keyframes].sort((a, b) => a.percentage - b.percentage).find((kf) => kf.percentage > at + KEYFRAME_PCT_MATCH);
|
|
46628
46814
|
upsert(keyframes, at, { ...values }, next?.ease);
|
|
46629
46815
|
} else {
|
|
46816
|
+
if (eased) return refuse("eased-keyframes");
|
|
46630
46817
|
const time = edit.at.time;
|
|
46631
46818
|
const before = time < start;
|
|
46632
46819
|
const held = heldEnds(norm, before ? "start" : "end", edit.implicitEndValue, backfilled);
|
|
@@ -46654,6 +46841,7 @@ function planValueAtPlayhead(edit) {
|
|
|
46654
46841
|
position: roundTo3(position),
|
|
46655
46842
|
duration: roundTo3(span),
|
|
46656
46843
|
keyframes,
|
|
46844
|
+
...norm.runEase ? { ease: norm.runEase } : {},
|
|
46657
46845
|
...norm.easeEach ? { easeEach: norm.easeEach } : {}
|
|
46658
46846
|
},
|
|
46659
46847
|
added: keyframes.length > authored
|
|
@@ -46785,82 +46973,6 @@ async function commitGsapPositionFromDrag(selection, anim, studioOffset, gsapPos
|
|
|
46785
46973
|
});
|
|
46786
46974
|
}
|
|
46787
46975
|
|
|
46788
|
-
// src/hooks/gsapPositionDetection.ts
|
|
46789
|
-
function readGsapPositionFromIframe(iframe, elementSelector) {
|
|
46790
|
-
const gsap3 = getIframeGsap(iframe);
|
|
46791
|
-
if (!gsap3) return null;
|
|
46792
|
-
const element = queryIframeElement(iframe, elementSelector);
|
|
46793
|
-
if (!element) return null;
|
|
46794
|
-
const x = Number(gsap3.getProperty(element, "x")) || 0;
|
|
46795
|
-
const y = Number(gsap3.getProperty(element, "y")) || 0;
|
|
46796
|
-
return { x, y };
|
|
46797
|
-
}
|
|
46798
|
-
function animHasPosition(anim) {
|
|
46799
|
-
if (anim.keyframes?.keyframes.some((kf) => "x" in kf.properties || "y" in kf.properties))
|
|
46800
|
-
return true;
|
|
46801
|
-
if (anim.method === "fromTo") {
|
|
46802
|
-
const from = anim.fromProperties;
|
|
46803
|
-
return "x" in anim.properties || "y" in anim.properties || !!(from && ("x" in from || "y" in from));
|
|
46804
|
-
}
|
|
46805
|
-
return "x" in anim.properties || "y" in anim.properties;
|
|
46806
|
-
}
|
|
46807
|
-
function findGsapPositionAnimation(animations, selector) {
|
|
46808
|
-
if (animations.length === 0) return null;
|
|
46809
|
-
const currentTime = usePlayerStore.getState().currentTime;
|
|
46810
|
-
const scored = animations.filter(animHasPosition).map((a) => {
|
|
46811
|
-
let score = 0;
|
|
46812
|
-
if (a.keyframes) score += 5;
|
|
46813
|
-
if (selector && a.targetSelector === selector) score += 8;
|
|
46814
|
-
else if (a.targetSelector.includes(",")) score -= 5;
|
|
46815
|
-
return { anim: a, score: score + playheadProximity(a, currentTime) };
|
|
46816
|
-
});
|
|
46817
|
-
scored.sort((a, b) => b.score - a.score);
|
|
46818
|
-
return scored[0]?.anim ?? null;
|
|
46819
|
-
}
|
|
46820
|
-
function playheadProximity(a, currentTime) {
|
|
46821
|
-
const pos = a.resolvedStart ?? (typeof a.position === "number" ? a.position : 0);
|
|
46822
|
-
const dur = a.duration ?? 0;
|
|
46823
|
-
if (currentTime >= pos - 0.05 && currentTime <= pos + dur + 0.05) return 50;
|
|
46824
|
-
return -Math.round(Math.min(Math.abs(currentTime - pos), Math.abs(currentTime - pos - dur)) * 5);
|
|
46825
|
-
}
|
|
46826
|
-
function statesValueAt(anim, time) {
|
|
46827
|
-
const duration = resolveTweenDuration(anim);
|
|
46828
|
-
if (!(duration > 0)) return false;
|
|
46829
|
-
const pct = (time - (resolveTweenStart(anim) ?? 0)) / duration * 100;
|
|
46830
|
-
const near3 = (keyframe) => Math.abs(keyframe - pct) <= KEYFRAME_PCT_MATCH;
|
|
46831
|
-
if (anim.keyframes) return anim.keyframes.keyframes.some((kf) => near3(kf.percentage));
|
|
46832
|
-
if (anim.method === "from") return near3(0);
|
|
46833
|
-
return near3(100) || anim.method === "fromTo" && near3(0);
|
|
46834
|
-
}
|
|
46835
|
-
function pickClosestToPlayhead(anims) {
|
|
46836
|
-
if (anims.length <= 1) return anims[0] ?? null;
|
|
46837
|
-
const ct = usePlayerStore.getState().currentTime;
|
|
46838
|
-
return anims.reduce((best, a) => {
|
|
46839
|
-
const dist = playheadDistance(a, ct);
|
|
46840
|
-
const bestDist = playheadDistance(best, ct);
|
|
46841
|
-
if (dist < bestDist) return a;
|
|
46842
|
-
const stated = dist === 0 && bestDist === 0 ? statedValueWinner(a, best, ct) : null;
|
|
46843
|
-
if (stated) return stated;
|
|
46844
|
-
if (dist === bestDist && keyframeCount(a) > keyframeCount(best)) return a;
|
|
46845
|
-
return best;
|
|
46846
|
-
});
|
|
46847
|
-
}
|
|
46848
|
-
function playheadDistance(anim, time) {
|
|
46849
|
-
const s = resolveTweenStart(anim) ?? 0;
|
|
46850
|
-
const e = s + resolveTweenDuration(anim);
|
|
46851
|
-
return time >= s && time <= e ? 0 : Math.min(Math.abs(time - s), Math.abs(time - e));
|
|
46852
|
-
}
|
|
46853
|
-
function statedValueWinner(a, b, time) {
|
|
46854
|
-
const [mine, theirs] = [statesValueAt(a, time), statesValueAt(b, time)];
|
|
46855
|
-
if (mine !== theirs) return mine ? a : b;
|
|
46856
|
-
const [s, bS] = [resolveTweenStart(a) ?? 0, resolveTweenStart(b) ?? 0];
|
|
46857
|
-
if (mine && s !== bS) return s > bS ? a : b;
|
|
46858
|
-
return null;
|
|
46859
|
-
}
|
|
46860
|
-
function keyframeCount(anim) {
|
|
46861
|
-
return anim.keyframes?.keyframes.length ?? 0;
|
|
46862
|
-
}
|
|
46863
|
-
|
|
46864
46976
|
// src/hooks/gsapElementMatch.ts
|
|
46865
46977
|
function getAnimationsForElement(animations, target, element) {
|
|
46866
46978
|
const matchers = /* @__PURE__ */ new Set();
|
|
@@ -49544,8 +49656,8 @@ import { useCallback as useCallback71, useSyncExternalStore as useSyncExternalSt
|
|
|
49544
49656
|
import { CornersIn, CornersOut, DotsThree, X as X2 } from "@phosphor-icons/react";
|
|
49545
49657
|
|
|
49546
49658
|
// src/components/dock/dockLayoutStore.ts
|
|
49547
|
-
import { create as
|
|
49548
|
-
var useDockLayoutStore =
|
|
49659
|
+
import { create as create10 } from "zustand";
|
|
49660
|
+
var useDockLayoutStore = create10((set, get) => ({
|
|
49549
49661
|
controller: null,
|
|
49550
49662
|
panels: PANEL_IDS,
|
|
49551
49663
|
openPanels: new Set(PANEL_IDS),
|
|
@@ -50380,6 +50492,37 @@ function EditorShellBody({
|
|
|
50380
50492
|
);
|
|
50381
50493
|
}
|
|
50382
50494
|
|
|
50495
|
+
// src/hooks/linkShortcuts.ts
|
|
50496
|
+
var isDeleteKey = (event) => event.key === "Backspace" || event.key === "Delete";
|
|
50497
|
+
var hasMod = (event) => event.metaKey || event.ctrlKey;
|
|
50498
|
+
var SHORTCUTS = [
|
|
50499
|
+
["\u2318L", (e) => hasMod(e) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === "l"],
|
|
50500
|
+
["\u2325\u21E7D", (e) => !hasMod(e) && e.altKey && e.shiftKey && e.code === "KeyD"],
|
|
50501
|
+
["\u2325\u232B", (e) => !hasMod(e) && e.altKey && !e.shiftKey && isDeleteKey(e)]
|
|
50502
|
+
];
|
|
50503
|
+
function shortcutOf(event) {
|
|
50504
|
+
return SHORTCUTS.find(([, matches]) => matches(event))?.[0] ?? null;
|
|
50505
|
+
}
|
|
50506
|
+
function dispatchLinkShortcut(event, cb) {
|
|
50507
|
+
if (isTypingTarget(event.target)) return false;
|
|
50508
|
+
const shortcut = shortcutOf(event);
|
|
50509
|
+
if (!shortcut) return false;
|
|
50510
|
+
const { elements, selectedElementId, selectedElementIds } = usePlayerStore.getState();
|
|
50511
|
+
const element = elements.find((el) => (el.key ?? el.id) === selectedElementId);
|
|
50512
|
+
if (!element) return false;
|
|
50513
|
+
const item = resolveLinkMenuItems({
|
|
50514
|
+
element,
|
|
50515
|
+
elements,
|
|
50516
|
+
selectedKeys: selectedElementIds,
|
|
50517
|
+
onLinkEdit: cb.handleLinkEdit,
|
|
50518
|
+
onDeleteElementOnly: cb.handleTimelineElementDeleteOnly
|
|
50519
|
+
}).find((candidate) => candidate.shortcut === shortcut);
|
|
50520
|
+
if (!item) return false;
|
|
50521
|
+
event.preventDefault();
|
|
50522
|
+
item.run();
|
|
50523
|
+
return true;
|
|
50524
|
+
}
|
|
50525
|
+
|
|
50383
50526
|
// src/player/components/ImageThumbnail.tsx
|
|
50384
50527
|
import { memo as memo34, useMemo as useMemo48 } from "react";
|
|
50385
50528
|
|
|
@@ -85076,36 +85219,6 @@ function useBlockHandlers({
|
|
|
85076
85219
|
// src/hooks/useAppHotkeys.ts
|
|
85077
85220
|
import { useCallback as useCallback154, useEffect as useEffect128, useRef as useRef158 } from "react";
|
|
85078
85221
|
|
|
85079
|
-
// src/hooks/linkShortcuts.ts
|
|
85080
|
-
var isDeleteKey = (event) => event.key === "Backspace" || event.key === "Delete";
|
|
85081
|
-
var hasMod = (event) => event.metaKey || event.ctrlKey;
|
|
85082
|
-
var SHORTCUTS = [
|
|
85083
|
-
["\u2318L", (e) => hasMod(e) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === "l"],
|
|
85084
|
-
["\u2325\u21E7D", (e) => !hasMod(e) && e.altKey && e.shiftKey && e.code === "KeyD"],
|
|
85085
|
-
["\u2325\u232B", (e) => !hasMod(e) && e.altKey && !e.shiftKey && isDeleteKey(e)]
|
|
85086
|
-
];
|
|
85087
|
-
function shortcutOf(event) {
|
|
85088
|
-
return SHORTCUTS.find(([, matches]) => matches(event))?.[0] ?? null;
|
|
85089
|
-
}
|
|
85090
|
-
function dispatchLinkShortcut(event, cb) {
|
|
85091
|
-
const shortcut = shortcutOf(event);
|
|
85092
|
-
if (!shortcut) return false;
|
|
85093
|
-
const { elements, selectedElementId, selectedElementIds } = usePlayerStore.getState();
|
|
85094
|
-
const element = elements.find((el) => (el.key ?? el.id) === selectedElementId);
|
|
85095
|
-
if (!element) return false;
|
|
85096
|
-
const item = resolveLinkMenuItems({
|
|
85097
|
-
element,
|
|
85098
|
-
elements,
|
|
85099
|
-
selectedKeys: selectedElementIds,
|
|
85100
|
-
onLinkEdit: cb.handleLinkEdit,
|
|
85101
|
-
onDeleteElementOnly: cb.handleTimelineElementDeleteOnly
|
|
85102
|
-
}).find((candidate) => candidate.shortcut === shortcut);
|
|
85103
|
-
if (!item) return false;
|
|
85104
|
-
event.preventDefault();
|
|
85105
|
-
item.run();
|
|
85106
|
-
return true;
|
|
85107
|
-
}
|
|
85108
|
-
|
|
85109
85222
|
// src/hooks/appHotkeysDispatch.ts
|
|
85110
85223
|
function handleUndoRedoKey(event, onUndo, onRedo) {
|
|
85111
85224
|
const key2 = event.key.toLowerCase();
|
|
@@ -85516,7 +85629,7 @@ function useAppHotkeys({
|
|
|
85516
85629
|
const handleAppKeyDown = useCallback154((event) => {
|
|
85517
85630
|
const cb = cbRef.current;
|
|
85518
85631
|
const key2 = event.key.toLowerCase();
|
|
85519
|
-
if (
|
|
85632
|
+
if (dispatchLinkShortcut(event, cb)) return;
|
|
85520
85633
|
if (event.metaKey || event.ctrlKey) {
|
|
85521
85634
|
dispatchModifierKey(event, key2, cb);
|
|
85522
85635
|
return;
|
|
@@ -96558,6 +96671,7 @@ function useKeyframeToggle(session) {
|
|
|
96558
96671
|
var TimelineToolbar = memo60(function TimelineToolbar2({
|
|
96559
96672
|
domEditSession,
|
|
96560
96673
|
onSplitElement,
|
|
96674
|
+
splitShortcut = SPLIT_SHORTCUT_HINT,
|
|
96561
96675
|
history: history2,
|
|
96562
96676
|
showHistory = true,
|
|
96563
96677
|
showSelectAroundPlayhead = true,
|
|
@@ -96725,7 +96839,7 @@ var TimelineToolbar = memo60(function TimelineToolbar2({
|
|
|
96725
96839
|
return /* @__PURE__ */ jsx243(
|
|
96726
96840
|
Tooltip,
|
|
96727
96841
|
{
|
|
96728
|
-
label: canSplit ?
|
|
96842
|
+
label: canSplit ? `Split at playhead (${splitShortcut})` : splittable ? "Move the playhead inside the clip to split" : "Select a clip to split",
|
|
96729
96843
|
children: /* @__PURE__ */ jsx243(
|
|
96730
96844
|
"button",
|
|
96731
96845
|
{
|
|
@@ -98306,6 +98420,7 @@ export {
|
|
|
98306
98420
|
cn,
|
|
98307
98421
|
collectSnapContext,
|
|
98308
98422
|
collectStudioLookScene,
|
|
98423
|
+
dispatchLinkShortcut,
|
|
98309
98424
|
displayTrackOrder,
|
|
98310
98425
|
extractSnapTargets,
|
|
98311
98426
|
fadeHandleBoxes,
|