@hyperframes/studio 0.8.79 → 0.8.81
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-C68UawR3.js +464 -0
- package/dist/assets/{index-ORZXYB0k.js → index-CMzPGv9P.js} +1 -1
- package/dist/assets/{index-CJP92F4-.js → index-CS5SImRT.js} +1 -1
- package/dist/assets/{index-DBKySIo7.js → index-hNVM9Pd2.js} +200 -197
- package/dist/index.d.ts +18 -1
- package/dist/index.html +1 -1
- package/dist/index.js +1673 -1348
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/App.tsx +13 -10
- package/src/components/StudioRightPanels.tsx +4 -48
- package/src/components/editor/TimelineFxPopover.test.tsx +1 -1
- package/src/components/editor/TimelineFxPopover.tsx +1 -1
- package/src/components/editor/manualEdits.ts +1 -0
- package/src/components/editor/manualEditsParsing.ts +4 -0
- package/src/components/editor/useFxAudition.ts +3 -3
- package/src/components/nle/useTimelineEditCallbacks.ts +4 -0
- package/src/components/panels/VariablesOtherCompositions.tsx +4 -4
- package/src/components/panels/VariablesPanel.test.tsx +86 -0
- package/src/components/panels/VariablesPanel.tsx +3 -3
- package/src/components/sidebar/SidebarLintButton.test.tsx +29 -0
- package/src/components/sidebar/SidebarLintButton.tsx +4 -0
- package/src/components/studioColorGradingScope.ts +5 -7
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/contexts/TimelineEditContext.tsx +2 -0
- package/src/hooks/liveLanes.ts +147 -0
- package/src/hooks/timelineAudioGroupVolume.ts +81 -47
- package/src/hooks/timelineEditPermission.ts +9 -1
- package/src/hooks/timelineEditingHelpers.test.ts +16 -0
- package/src/hooks/timelineEditingHelpers.ts +69 -43
- package/src/hooks/timelineElementFxAttribute.ts +82 -36
- package/src/hooks/useAppHotkeys.ts +1 -1
- package/src/hooks/useApplyColorGradingScope.test.tsx +53 -0
- package/src/hooks/useApplyColorGradingScope.ts +54 -0
- package/src/hooks/useAutomationSelectionKeyboard.test.tsx +125 -21
- package/src/hooks/useAutomationSelectionKeyboard.ts +21 -25
- package/src/hooks/useBlockHandlers.test.tsx +65 -0
- package/src/hooks/useBlockHandlers.ts +7 -4
- package/src/hooks/useDomEditAttributeCommits.ts +3 -17
- package/src/hooks/useDomEditSession.ts +1 -0
- package/src/hooks/useEditHistoryActions.ts +4 -3
- package/src/hooks/useExternalFileChangeCoordinator.test.tsx +49 -0
- package/src/hooks/useExternalFileChangeCoordinator.ts +15 -6
- package/src/hooks/useLintModal.test.tsx +15 -0
- package/src/hooks/useLintModal.ts +5 -2
- package/src/hooks/useProjectCompositionVariables.ts +3 -3
- package/src/hooks/useTimelineEditing.ts +19 -16
- package/src/hooks/useTimelineGroupEditing.ts +2 -1
- package/src/hooks/useToast.ts +2 -1
- package/src/player/components/ImageThumbnail.tsx +3 -12
- package/src/player/components/Player.test.ts +23 -0
- package/src/player/components/Player.tsx +4 -2
- package/src/player/components/TimelineAutomationLane.test.tsx +38 -0
- package/src/player/components/TimelineAutomationLane.tsx +18 -13
- package/src/player/components/TimelineAutomationLaneSlot.test.tsx +0 -2
- package/src/player/components/TimelineClipFades.test.tsx +24 -3
- package/src/player/components/TimelineClipFades.tsx +5 -2
- package/src/player/components/TimelineFxButton.tsx +1 -1
- package/src/player/components/TimelineGroupHeader.tsx +1 -1
- package/src/player/components/TimelineGroupRow.test.tsx +0 -1
- package/src/player/components/TimelineGroupRow.tsx +17 -11
- package/src/player/components/TimelineTrackHeader.tsx +21 -6
- package/src/player/components/groupAutomationElement.ts +9 -1
- package/src/player/components/timelineCallbacks.ts +2 -0
- package/src/player/components/timelineClipDragPreview.test.ts +41 -0
- package/src/player/components/timelineClipDragPreview.ts +15 -24
- package/src/player/components/timelineEditing.test.ts +99 -1
- package/src/player/components/timelineEditing.ts +5 -8
- package/src/player/components/timelineGroupEditing.test.ts +27 -0
- package/src/player/components/timelineGroupEditing.ts +18 -15
- package/src/player/components/timelineStackingSync.ts +5 -5
- package/src/player/components/timelineZones.ts +2 -3
- package/src/player/components/useAutomationEdgeStretch.ts +4 -4
- package/src/player/components/useAutomationLaneGestures.ts +1 -1
- package/src/player/components/useAutomationLanes.save.test.tsx +1406 -0
- package/src/player/components/useAutomationLanes.ts +58 -74
- package/src/player/lib/automationStoreSync.ts +30 -3
- package/src/player/lib/thumbnailImageDecoder.test.ts +82 -0
- package/src/player/lib/thumbnailImageDecoder.ts +73 -0
- package/src/player/lib/timelineDOM.test.ts +28 -8
- package/src/player/lib/timelineElementHelpers.ts +3 -4
- package/src/player/lib/timelineGroupInfo.ts +23 -23
- package/src/utils/studioFileHistory.ts +36 -30
- package/dist/assets/hyperframes-player-Bx_Kw4Ey.js +0 -464
- package/src/player/components/automationGestureKeys.test.ts +0 -49
- package/src/player/components/automationGestureKeys.ts +0 -49
|
@@ -13,6 +13,12 @@ function roundTimelineTime(value: number): number {
|
|
|
13
13
|
return roundToCenti(value);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
const SAVED_MEDIA_OFFSET_TOLERANCE = 1e-5;
|
|
17
|
+
|
|
18
|
+
function ceilTimelineTime(value: number): number {
|
|
19
|
+
return Math.ceil((value - SAVED_MEDIA_OFFSET_TOLERANCE) * 100) / 100;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
export function resolveTimelineMinDuration(minDuration?: number): number {
|
|
17
23
|
return Math.max(ABSOLUTE_TIMELINE_MIN_DURATION, minDuration ?? DEFAULT_TIMELINE_MIN_DURATION);
|
|
18
24
|
}
|
|
@@ -33,7 +39,7 @@ interface TimelineStartTrimClip {
|
|
|
33
39
|
* Delta bounds for trimming a clip's START edge (shared by single-clip and group
|
|
34
40
|
* resize). Left-bounded by how far the start can move toward `minStart` and by the
|
|
35
41
|
* media in-point (`playbackStart / playbackRate`); right-bounded by `minDuration`.
|
|
36
|
-
* Returned deltas are unrounded
|
|
42
|
+
* Returned deltas are unrounded; applyClipStartTrimDelta does the rounding.
|
|
37
43
|
*/
|
|
38
44
|
export function clipStartTrimDeltaBounds(
|
|
39
45
|
clip: TimelineStartTrimClip,
|
|
@@ -50,21 +56,24 @@ export function clipStartTrimDeltaBounds(
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
/**
|
|
53
|
-
* Apply a start-edge delta to one clip
|
|
54
|
-
*
|
|
55
|
-
*
|
|
59
|
+
* Apply a start-edge delta to one clip: rounds the start once, keeps the end fixed,
|
|
60
|
+
* and derives the media in-point from the rounded start (clamped at 0), so the media
|
|
61
|
+
* clock `start - playbackStart / playbackRate` survives any number of head trims.
|
|
56
62
|
*/
|
|
57
63
|
export function applyClipStartTrimDelta(
|
|
58
64
|
clip: TimelineStartTrimClip,
|
|
59
65
|
delta: number,
|
|
60
66
|
): { start: number; duration: number; playbackStart?: number } {
|
|
61
67
|
const playbackRate = resolveTimelinePlaybackRate(clip.playbackRate);
|
|
68
|
+
const mediaZero =
|
|
69
|
+
clip.playbackStart != null ? clip.start - clip.playbackStart / playbackRate : -Infinity;
|
|
70
|
+
const start = Math.max(roundTimelineTime(clip.start + delta), ceilTimelineTime(mediaZero));
|
|
62
71
|
return {
|
|
63
|
-
start
|
|
64
|
-
duration: clip.duration -
|
|
72
|
+
start,
|
|
73
|
+
duration: roundTimelineTime(clip.start + clip.duration - start),
|
|
65
74
|
playbackStart:
|
|
66
75
|
clip.playbackStart != null
|
|
67
|
-
? Math.max(0, clip.playbackStart +
|
|
76
|
+
? Math.max(0, clip.playbackStart + (start - clip.start) * playbackRate)
|
|
68
77
|
: undefined,
|
|
69
78
|
};
|
|
70
79
|
}
|
|
@@ -126,7 +135,7 @@ export function clampTimelineGroupResizeDelta(
|
|
|
126
135
|
|
|
127
136
|
// Rigid group: the applied delta is bounded by the most-constrained member.
|
|
128
137
|
const bounds = members.map((member) => clipStartTrimDeltaBounds(member, 0, minDuration));
|
|
129
|
-
const minDelta = Math.max(...bounds.map((b) => b.minDelta));
|
|
138
|
+
const minDelta = ceilTimelineTime(Math.max(...bounds.map((b) => b.minDelta)));
|
|
130
139
|
const maxDelta = Math.min(...bounds.map((b) => b.maxDelta));
|
|
131
140
|
return roundTimelineTime(clamp(rawDelta, minDelta, maxDelta));
|
|
132
141
|
}
|
|
@@ -149,13 +158,7 @@ export function resolveTimelineGroupResize(
|
|
|
149
158
|
};
|
|
150
159
|
}
|
|
151
160
|
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
start: roundTimelineTime(trimmed.start),
|
|
155
|
-
duration: roundTimelineTime(trimmed.duration),
|
|
156
|
-
playbackStart:
|
|
157
|
-
trimmed.playbackStart != null ? roundTimelineTime(trimmed.playbackStart) : undefined,
|
|
158
|
-
};
|
|
161
|
+
return applyClipStartTrimDelta(member, delta);
|
|
159
162
|
}),
|
|
160
163
|
};
|
|
161
164
|
}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* style) and apply the returned `StackingPatch[]` however they persist styles.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { spansOverlap } from "@hyperframes/core/clip-facts";
|
|
21
|
+
|
|
20
22
|
/** Minimal element view this module reasons over. */
|
|
21
23
|
export interface StackingElement {
|
|
22
24
|
/** Stable identity (TimelineElement.key ?? id). */
|
|
@@ -69,8 +71,6 @@ export interface StackingPatch {
|
|
|
69
71
|
zIndex: number;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
const EPS = 1e-6;
|
|
73
|
-
|
|
74
74
|
/**
|
|
75
75
|
* Canonical paint-scope key: leaf z-indexes are comparable only within the same
|
|
76
76
|
* source document and CSS stacking context. The ONLY place this normalization
|
|
@@ -90,17 +90,17 @@ export function samePaintScope(
|
|
|
90
90
|
/**
|
|
91
91
|
* Two clips overlap in time when their half-open [start, end) intervals intersect.
|
|
92
92
|
*
|
|
93
|
-
* NOTE the
|
|
93
|
+
* NOTE the float slack: this DELIBERATELY diverges from `timeRangesOverlap`'s exact
|
|
94
94
|
* strict-`<` (timelineCollision.ts). A boolean collision decision is idempotent, so
|
|
95
95
|
* exact `<` is fine there; here the result drives a VISIBLE stacking re-lane, so the
|
|
96
|
-
* epsilon guards against float fuzz (e.g.
|
|
96
|
+
* epsilon guards against float fuzz (e.g. 19.8 + 6.4 vs 26.2) spuriously overlapping two
|
|
97
97
|
* abutting clips and shuffling lanes. The two are intended to differ, not align.
|
|
98
98
|
*/
|
|
99
99
|
function overlapsInTime(
|
|
100
100
|
a: Pick<StackingElement, "start" | "duration">,
|
|
101
101
|
b: Pick<StackingElement, "start" | "duration">,
|
|
102
102
|
): boolean {
|
|
103
|
-
return a.start
|
|
103
|
+
return spansOverlap(a.start, a.start + a.duration, b.start, b.start + b.duration);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spansOverlap } from "@hyperframes/core/clip-facts";
|
|
1
2
|
import type { TimelineElement } from "../store/playerStore";
|
|
2
3
|
import { isAudioTimelineElement } from "../../utils/timelineInspector";
|
|
3
4
|
import { isTransitionPair } from "./timelineTransitionSeams";
|
|
@@ -23,11 +24,9 @@ export function isMainTrackElement(el: TimelineElement): boolean {
|
|
|
23
24
|
|
|
24
25
|
const keyOf = (el: TimelineElement) => el.key ?? el.id;
|
|
25
26
|
|
|
26
|
-
const EPS = 1e-6;
|
|
27
|
-
|
|
28
27
|
/** Two clips overlap when their half-open [start, end) intervals intersect. */
|
|
29
28
|
function overlaps(a: TimelineElement, b: TimelineElement): boolean {
|
|
30
|
-
return a.start
|
|
29
|
+
return spansOverlap(a.start, a.start + a.duration, b.start, b.start + b.duration);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/** Deterministic order on the stable clip id (never the mutated lane/track). */
|
|
@@ -51,7 +51,7 @@ export interface UseAutomationEdgeStretchInput {
|
|
|
51
51
|
/** Pointer position as a clip-local time and a parameter value. */
|
|
52
52
|
pointAt(clientX: number, clientY: number): { t: number; v: number };
|
|
53
53
|
xOf(t: number): number;
|
|
54
|
-
commitPoints(points: HfAutomationLane["points"], persist: boolean): void;
|
|
54
|
+
commitPoints(points: HfAutomationLane["points"], persist: boolean, ended?: boolean): void;
|
|
55
55
|
/** Clamp bound for the dragged edge. */
|
|
56
56
|
duration: number;
|
|
57
57
|
readOnly?: boolean | undefined;
|
|
@@ -252,9 +252,9 @@ export function useAutomationEdgeStretch({
|
|
|
252
252
|
onHint(null);
|
|
253
253
|
if (!crossed.current) return;
|
|
254
254
|
crossed.current = false;
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
commitPoints(points, false);
|
|
255
|
+
// Nothing was persisted: the snapshot goes back as the last live write, which
|
|
256
|
+
// also ends the lane's gesture so its before-value does not outlive it.
|
|
257
|
+
commitPoints(points, false, true);
|
|
258
258
|
onRangeSelect?.(origin.t0, origin.t1, origin.v0, origin.v1);
|
|
259
259
|
}, [drag, onHint, commitPoints, onRangeSelect]);
|
|
260
260
|
|
|
@@ -46,7 +46,7 @@ export interface UseAutomationLaneGesturesInput {
|
|
|
46
46
|
pointAt(clientX: number, clientY: number): { t: number; v: number };
|
|
47
47
|
xOf(t: number): number;
|
|
48
48
|
yOf(v: number): number;
|
|
49
|
-
commitPoints(points: HfAutomationLane["points"], persist: boolean): void;
|
|
49
|
+
commitPoints(points: HfAutomationLane["points"], persist: boolean, ended?: boolean): void;
|
|
50
50
|
/** Clip-local times a dragged point snaps to, on top of its own neighbours. */
|
|
51
51
|
snapTimes?: readonly number[] | undefined;
|
|
52
52
|
readOnly?: boolean | undefined;
|