@hyperframes/studio 0.4.13-alpha.1 → 0.4.13-alpha.3
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/{index-rN5doSq1.js → index-BNJpPMh6.js} +22 -22
- package/dist/index.html +1 -1
- package/package.json +4 -4
- package/src/player/components/Timeline.tsx +2 -0
- package/src/player/components/TimelineClip.tsx +8 -5
- package/src/player/components/timelineEditing.test.ts +37 -8
- package/src/player/components/timelineEditing.ts +19 -1
package/dist/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
6
|
<title>HyperFrames Studio</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-BNJpPMh6.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-BKkR67xb.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.4.13-alpha.
|
|
3
|
+
"version": "0.4.13-alpha.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"@phosphor-icons/react": "^2.1.10",
|
|
33
33
|
"codemirror": "^6.0.1",
|
|
34
34
|
"motion": "^12.38.0",
|
|
35
|
-
"@hyperframes/core": "0.4.13-alpha.
|
|
36
|
-
"@hyperframes/player": "0.4.13-alpha.
|
|
35
|
+
"@hyperframes/core": "0.4.13-alpha.3",
|
|
36
|
+
"@hyperframes/player": "0.4.13-alpha.3"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/react": "^19.0.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"vite": "^6.4.2",
|
|
48
48
|
"vitest": "^3.2.4",
|
|
49
49
|
"zustand": "^5.0.0",
|
|
50
|
-
"@hyperframes/producer": "0.4.13-alpha.
|
|
50
|
+
"@hyperframes/producer": "0.4.13-alpha.3"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -10,6 +10,7 @@ import { formatTime } from "../lib/time";
|
|
|
10
10
|
import { TimelineClip } from "./TimelineClip";
|
|
11
11
|
import { EditPopover } from "./EditModal";
|
|
12
12
|
import {
|
|
13
|
+
canOffsetTrimClipStart,
|
|
13
14
|
resolveTimelineAutoScroll,
|
|
14
15
|
resolveTimelineMove,
|
|
15
16
|
resolveTimelineResize,
|
|
@@ -1109,6 +1110,7 @@ export const Timeline = memo(function Timeline({
|
|
|
1109
1110
|
onHoverEnd={() => setHoveredClip(null)}
|
|
1110
1111
|
onResizeStart={(edge, e) => {
|
|
1111
1112
|
if (e.button !== 0 || e.shiftKey || !onResizeElement) return;
|
|
1113
|
+
if (edge === "start" && !canOffsetTrimClipStart(el)) return;
|
|
1112
1114
|
e.stopPropagation();
|
|
1113
1115
|
setShowPopover(false);
|
|
1114
1116
|
setRangeSelection(null);
|
|
@@ -4,6 +4,7 @@ import type { TimelineTrackStyle } from "./timelineTheme";
|
|
|
4
4
|
import { memo, type ReactNode } from "react";
|
|
5
5
|
import type { TimelineElement } from "../store/playerStore";
|
|
6
6
|
import { defaultTimelineTheme, getClipHandleOpacity, type TimelineTheme } from "./timelineTheme";
|
|
7
|
+
import { canOffsetTrimClipStart } from "./timelineEditing";
|
|
7
8
|
|
|
8
9
|
interface TimelineClipProps {
|
|
9
10
|
el: TimelineElement;
|
|
@@ -59,6 +60,7 @@ export const TimelineClip = memo(function TimelineClip({
|
|
|
59
60
|
: isHovered
|
|
60
61
|
? theme.clipShadowHover
|
|
61
62
|
: theme.clipShadow;
|
|
63
|
+
const canTrimStart = canOffsetTrimClipStart(el);
|
|
62
64
|
const showHandles = handleOpacity > 0.01;
|
|
63
65
|
|
|
64
66
|
return (
|
|
@@ -109,14 +111,15 @@ export const TimelineClip = memo(function TimelineClip({
|
|
|
109
111
|
top: 0,
|
|
110
112
|
bottom: 0,
|
|
111
113
|
width: 18,
|
|
112
|
-
opacity: showHandles ? 1 : 0,
|
|
113
|
-
pointerEvents: onResizeStart ? "auto" : "none",
|
|
114
|
+
opacity: showHandles && canTrimStart ? 1 : 0,
|
|
115
|
+
pointerEvents: onResizeStart && canTrimStart ? "auto" : "none",
|
|
114
116
|
zIndex: 4,
|
|
115
117
|
transition: "opacity 120ms ease-out",
|
|
116
118
|
cursor: "col-resize",
|
|
117
|
-
background:
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
background:
|
|
120
|
+
showHandles && canTrimStart
|
|
121
|
+
? `linear-gradient(90deg, ${trackStyle.accent}4d 0%, ${trackStyle.accent}22 42%, transparent 100%)`
|
|
122
|
+
: "transparent",
|
|
120
123
|
}}
|
|
121
124
|
>
|
|
122
125
|
<div
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
|
-
buildTrackZIndexMap,
|
|
4
3
|
buildPromptCopyText,
|
|
5
4
|
buildTimelineAgentPrompt,
|
|
5
|
+
buildTrackZIndexMap,
|
|
6
|
+
canOffsetTrimClipStart,
|
|
6
7
|
resolveTimelineAutoScroll,
|
|
7
8
|
resolveTimelineMove,
|
|
8
9
|
resolveTimelineResize,
|
|
@@ -154,13 +155,13 @@ describe("resolveTimelineMove", () => {
|
|
|
154
155
|
});
|
|
155
156
|
|
|
156
157
|
describe("buildTrackZIndexMap", () => {
|
|
157
|
-
it("maps
|
|
158
|
+
it("maps visually higher tracks onto higher z-index values", () => {
|
|
158
159
|
expect(buildTrackZIndexMap([-2, -1, 0, 3])).toEqual(
|
|
159
160
|
new Map([
|
|
160
|
-
[-2,
|
|
161
|
-
[-1,
|
|
162
|
-
[0,
|
|
163
|
-
[3,
|
|
161
|
+
[-2, 4],
|
|
162
|
+
[-1, 3],
|
|
163
|
+
[0, 2],
|
|
164
|
+
[3, 1],
|
|
164
165
|
]),
|
|
165
166
|
);
|
|
166
167
|
});
|
|
@@ -168,14 +169,42 @@ describe("buildTrackZIndexMap", () => {
|
|
|
168
169
|
it("deduplicates tracks before assigning z-index values", () => {
|
|
169
170
|
expect(buildTrackZIndexMap([-1, 0, -1, 3, 3])).toEqual(
|
|
170
171
|
new Map([
|
|
171
|
-
[-1,
|
|
172
|
+
[-1, 3],
|
|
172
173
|
[0, 2],
|
|
173
|
-
[3,
|
|
174
|
+
[3, 1],
|
|
174
175
|
]),
|
|
175
176
|
);
|
|
176
177
|
});
|
|
177
178
|
});
|
|
178
179
|
|
|
180
|
+
describe("canOffsetTrimClipStart", () => {
|
|
181
|
+
it("allows front trim for clips that carry playback offset metadata", () => {
|
|
182
|
+
expect(
|
|
183
|
+
canOffsetTrimClipStart({
|
|
184
|
+
tag: "div",
|
|
185
|
+
playbackStartAttr: "media-start",
|
|
186
|
+
}),
|
|
187
|
+
).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("allows front trim for media clips with source duration metadata", () => {
|
|
191
|
+
expect(
|
|
192
|
+
canOffsetTrimClipStart({
|
|
193
|
+
tag: "video",
|
|
194
|
+
sourceDuration: 12,
|
|
195
|
+
}),
|
|
196
|
+
).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("blocks front trim for generic motion clips", () => {
|
|
200
|
+
expect(
|
|
201
|
+
canOffsetTrimClipStart({
|
|
202
|
+
tag: "section",
|
|
203
|
+
}),
|
|
204
|
+
).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
179
208
|
describe("resolveTimelineAutoScroll", () => {
|
|
180
209
|
it("does not scroll when the pointer stays away from the edges", () => {
|
|
181
210
|
expect(
|
|
@@ -116,7 +116,8 @@ export function resolveTimelineMove(
|
|
|
116
116
|
|
|
117
117
|
export function buildTrackZIndexMap(tracks: number[]): Map<number, number> {
|
|
118
118
|
const uniqueTracks = Array.from(new Set(tracks)).sort((a, b) => a - b);
|
|
119
|
-
|
|
119
|
+
const maxZIndex = uniqueTracks.length;
|
|
120
|
+
return new Map(uniqueTracks.map((track, index) => [track, maxZIndex - index]));
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
export function resolveTimelineResize(
|
|
@@ -168,6 +169,23 @@ export interface TimelinePromptElement {
|
|
|
168
169
|
track: number;
|
|
169
170
|
}
|
|
170
171
|
|
|
172
|
+
export function canOffsetTrimClipStart(input: {
|
|
173
|
+
tag: string;
|
|
174
|
+
playbackStart?: number;
|
|
175
|
+
playbackStartAttr?: "media-start" | "playback-start";
|
|
176
|
+
sourceDuration?: number;
|
|
177
|
+
}): boolean {
|
|
178
|
+
if (input.playbackStartAttr != null) return true;
|
|
179
|
+
if (input.playbackStart != null) return true;
|
|
180
|
+
const normalizedTag = input.tag.toLowerCase();
|
|
181
|
+
if (!["video", "audio"].includes(normalizedTag)) return false;
|
|
182
|
+
return (
|
|
183
|
+
input.sourceDuration != null &&
|
|
184
|
+
Number.isFinite(input.sourceDuration) &&
|
|
185
|
+
input.sourceDuration > 0
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
171
189
|
export function buildTimelineAgentPrompt({
|
|
172
190
|
rangeStart,
|
|
173
191
|
rangeEnd,
|