@hyperframes/studio 0.6.97 → 0.6.98
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-DgsMQSvV.js +418 -0
- package/dist/assets/index-B62bDCQv.css +1 -0
- package/dist/assets/index-Ce3pBm_I.js +252 -0
- package/dist/assets/{index-HveJ0MuV.js → index-D-ET9M0b.js} +1 -1
- package/dist/assets/index-D-bS9Dxx.js +1 -0
- package/dist/index.html +2 -2
- package/package.json +7 -5
- package/src/App.tsx +182 -177
- package/src/captions/store.ts +11 -11
- package/src/components/StudioHeader.tsx +4 -4
- package/src/components/StudioLeftSidebar.tsx +2 -2
- package/src/components/StudioPreviewArea.tsx +225 -183
- package/src/components/StudioRightPanel.tsx +3 -3
- package/src/components/TimelineToolbar.tsx +25 -0
- package/src/components/editor/DomEditOverlay.tsx +2 -5
- package/src/components/editor/EaseCurveSection.tsx +2 -3
- package/src/components/editor/GestureTrailOverlay.tsx +4 -3
- package/src/components/editor/LayersPanel.tsx +3 -9
- package/src/components/editor/PropertyPanel.tsx +20 -61
- package/src/components/editor/colorValue.ts +3 -1
- package/src/components/editor/domEditOverlayGestures.ts +54 -1
- package/src/components/editor/domEditOverlayStartGesture.ts +5 -2
- package/src/components/editor/gradientValue.ts +3 -3
- package/src/components/editor/keyframeMove.test.ts +101 -0
- package/src/components/editor/keyframeMove.ts +151 -0
- package/src/components/editor/manualEditsDom.ts +0 -12
- package/src/components/editor/propertyPanelHelpers.ts +10 -38
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -5
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -6
- package/src/components/editor/propertyPanelTransformCommit.ts +129 -0
- package/src/components/editor/studioMotionOps.test.ts +1 -1
- package/src/components/editor/studioMotionOps.ts +2 -1
- package/src/components/editor/useDomEditOverlayGestures.ts +1 -46
- package/src/components/nle/NLELayout.tsx +1 -24
- package/src/components/sidebar/BlocksTab.tsx +2 -2
- package/src/contexts/DomEditContext.tsx +134 -31
- package/src/contexts/StudioContext.tsx +90 -40
- package/src/contexts/TimelineEditContext.tsx +47 -0
- package/src/hooks/domEditCommitTypes.ts +14 -0
- package/src/hooks/gsapDragCommit.ts +9 -24
- package/src/hooks/gsapKeyframeCacheHelpers.ts +2 -1
- package/src/hooks/gsapKeyframeCommit.ts +5 -15
- package/src/hooks/gsapRuntimeBridge.ts +18 -52
- package/src/hooks/gsapRuntimeKeyframes.ts +8 -57
- package/src/hooks/gsapRuntimeReaders.ts +19 -26
- package/src/hooks/gsapScriptCommitHelpers.ts +1 -11
- package/src/hooks/gsapScriptCommitTypes.ts +58 -0
- package/src/hooks/gsapShared.ts +157 -0
- package/src/hooks/timelineEditingHelpers.ts +63 -2
- package/src/hooks/useAnimatedPropertyCommit.ts +3 -25
- package/src/hooks/useAppHotkeys.ts +299 -377
- package/src/hooks/useConsoleErrorCapture.ts +33 -5
- package/src/hooks/useDomEditCommits.ts +35 -293
- package/src/hooks/useDomEditPositionPatchCommit.ts +1 -1
- package/src/hooks/useDomEditSession.ts +78 -249
- package/src/hooks/useDomEditTextCommits.ts +1 -1
- package/src/hooks/useDomEditWiring.ts +255 -0
- package/src/hooks/useDomGeometryCommits.ts +181 -0
- package/src/hooks/useDomSelection.ts +10 -27
- package/src/hooks/useEditorSave.ts +82 -0
- package/src/hooks/useElementLifecycleOps.ts +177 -0
- package/src/hooks/useEnableKeyframes.ts +10 -15
- package/src/hooks/useFileManager.ts +32 -114
- package/src/hooks/useFileTree.ts +80 -0
- package/src/hooks/useGestureCommit.ts +7 -5
- package/src/hooks/useGestureRecording.ts +1 -1
- package/src/hooks/useGsapAnimationOps.ts +122 -0
- package/src/hooks/useGsapArcPathOps.ts +61 -0
- package/src/hooks/useGsapAwareEditing.ts +242 -0
- package/src/hooks/useGsapKeyframeOps.ts +167 -0
- package/src/hooks/useGsapPropertyDebounce.ts +135 -0
- package/src/hooks/useGsapScriptCommits.ts +58 -570
- package/src/hooks/useGsapSelectionHandlers.ts +22 -9
- package/src/hooks/useGsapTweenCache.ts +35 -29
- package/src/hooks/useLintModal.ts +7 -0
- package/src/hooks/useMusicBeatAnalysis.ts +152 -0
- package/src/hooks/useRazorSplit.ts +1 -1
- package/src/hooks/useRenderClipContent.ts +46 -21
- package/src/hooks/useTimelineEditing.ts +48 -4
- package/src/player/components/AudioWaveform.tsx +29 -4
- package/src/player/components/BeatStrip.tsx +166 -0
- package/src/player/components/Timeline.tsx +39 -18
- package/src/player/components/TimelineCanvas.tsx +52 -12
- package/src/player/components/TimelineClipDiamonds.tsx +130 -20
- package/src/player/components/TimelinePropertyRows.tsx +8 -2
- package/src/player/components/TimelineRuler.tsx +36 -2
- package/src/player/components/timelineEditing.ts +30 -5
- package/src/player/components/useTimelineClipDrag.ts +155 -4
- package/src/player/components/useTimelinePlayhead.ts +30 -1
- package/src/player/hooks/useTimelinePlayer.ts +47 -45
- package/src/player/lib/mediaProbe.ts +46 -3
- package/src/player/lib/playbackScrub.ts +16 -0
- package/src/player/lib/timelineDOM.ts +10 -2
- package/src/player/lib/timelineIframeHelpers.ts +89 -0
- package/src/player/store/playerStore.ts +92 -33
- package/src/utils/beatEditActions.ts +109 -0
- package/src/utils/beatEditing.ts +136 -0
- package/src/utils/clipboardPayload.ts +3 -2
- package/src/utils/compositionPatterns.ts +2 -0
- package/src/utils/keyframeSelection.test.ts +45 -0
- package/src/utils/keyframeSelection.ts +29 -0
- package/src/utils/rounding.ts +9 -0
- package/src/utils/studioHelpers.ts +5 -2
- package/src/utils/studioUrlState.ts +2 -1
- package/src/utils/timelineAssetDrop.ts +6 -5
- package/src/utils/timelineInspector.ts +15 -100
- package/dist/assets/hyperframes-player-Daj5djxa.js +0 -418
- package/dist/assets/index-B0twsRu0.css +0 -1
- package/dist/assets/index-Cfye9xzo.js +0 -251
- package/src/components/editor/DopesheetStrip.tsx +0 -141
- package/src/components/editor/StaggerControls.tsx +0 -61
- package/src/components/editor/TimelineLayerPanel.test.ts +0 -42
- package/src/components/editor/TimelineLayerPanel.tsx +0 -15
- package/src/components/nle/TimelineEditorNotice.tsx +0 -133
- package/src/hooks/gsapRuntimePreview.ts +0 -19
- package/src/player/components/timelineUtils.ts +0 -211
- package/src/utils/audioBeatDetection.ts +0 -58
- package/src/utils/keyframeSnapping.test.ts +0 -74
- package/src/utils/keyframeSnapping.ts +0 -63
- package/src/utils/timelineInspector.test.ts +0 -79
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { TimelineElement } from "../player";
|
|
1
|
+
import type { TimelineElement } from "../player/store/playerStore";
|
|
2
2
|
import type { DomEditSelection } from "../components/editor/domEditing";
|
|
3
3
|
import type { TimelineAssetKind } from "./timelineAssetDrop";
|
|
4
|
+
import { roundToCenti } from "./rounding";
|
|
4
5
|
|
|
5
6
|
export interface EditingFile {
|
|
6
7
|
path: string;
|
|
@@ -171,6 +172,8 @@ export function clampNumber(value: number, min: number, max: number): number {
|
|
|
171
172
|
return Math.min(Math.max(value, min), max);
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
export { COMPOSITION_ROOT_OPEN_TAG_RE } from "./compositionPatterns";
|
|
176
|
+
|
|
174
177
|
export function collectHtmlIds(source: string): string[] {
|
|
175
178
|
return Array.from(source.matchAll(/\bid="([^"]+)"/g), (match) => match[1] ?? "");
|
|
176
179
|
}
|
|
@@ -205,7 +208,7 @@ export async function resolveDroppedAssetDuration(
|
|
|
205
208
|
const raw = Number(media.duration);
|
|
206
209
|
finalize(
|
|
207
210
|
Number.isFinite(raw) && raw > 0
|
|
208
|
-
?
|
|
211
|
+
? roundToCenti(raw)
|
|
209
212
|
: DEFAULT_TIMELINE_ASSET_DURATION[kind],
|
|
210
213
|
);
|
|
211
214
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RightPanelTab } from "./studioHelpers";
|
|
2
2
|
import { buildProjectHash, parseProjectHashRoute } from "./projectRouting";
|
|
3
3
|
import { STUDIO_INSPECTOR_PANELS_ENABLED } from "../components/editor/manualEditingAvailability";
|
|
4
|
+
import { roundTo3 } from "./rounding";
|
|
4
5
|
|
|
5
6
|
export interface StudioUrlSelectionState {
|
|
6
7
|
sourceFile?: string;
|
|
@@ -111,7 +112,7 @@ export function buildStudioHash(projectId: string, state: StudioUrlState): strin
|
|
|
111
112
|
params.set("v", "1");
|
|
112
113
|
if (state.activeCompPath) params.set("comp", state.activeCompPath);
|
|
113
114
|
if (state.currentTime != null && Number.isFinite(state.currentTime)) {
|
|
114
|
-
params.set("t", String(Math.max(0,
|
|
115
|
+
params.set("t", String(Math.max(0, roundTo3(state.currentTime))));
|
|
115
116
|
}
|
|
116
117
|
if (state.rightPanelTab) params.set("tab", state.rightPanelTab);
|
|
117
118
|
if (state.rightCollapsed != null) params.set("rc", state.rightCollapsed ? "1" : "0");
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { AUDIO_EXT, IMAGE_EXT, VIDEO_EXT } from "./mediaTypes";
|
|
2
|
+
import { roundToCenti } from "./rounding";
|
|
3
|
+
import { COMPOSITION_ROOT_OPEN_TAG_RE } from "./compositionPatterns";
|
|
2
4
|
|
|
3
5
|
export const TIMELINE_ASSET_MIME = "application/x-hyperframes-asset";
|
|
4
6
|
export const TIMELINE_BLOCK_MIME = "application/x-hyperframes-block";
|
|
@@ -51,13 +53,13 @@ export function buildTimelineFileDropPlacements(
|
|
|
51
53
|
durations: number[],
|
|
52
54
|
occupiedClips: Array<{ start: number; duration: number; track: number }> = [],
|
|
53
55
|
): Array<{ start: number; track: number }> {
|
|
54
|
-
let nextStart =
|
|
56
|
+
let nextStart = roundToCenti(Math.max(0, placement.start));
|
|
55
57
|
const sequenceStart = nextStart;
|
|
56
58
|
const resolvedDurations = durations.map((duration) =>
|
|
57
59
|
Number.isFinite(duration) && duration > 0 ? duration : FALLBACK_TIMELINE_FILE_DROP_DURATION,
|
|
58
60
|
);
|
|
59
61
|
const sequenceEnd = resolvedDurations.reduce(
|
|
60
|
-
(end, duration) =>
|
|
62
|
+
(end, duration) => roundToCenti(end + duration),
|
|
61
63
|
sequenceStart,
|
|
62
64
|
);
|
|
63
65
|
const overlapsDropTrack = occupiedClips.some((clip) => {
|
|
@@ -72,7 +74,7 @@ export function buildTimelineFileDropPlacements(
|
|
|
72
74
|
|
|
73
75
|
return resolvedDurations.map((duration) => {
|
|
74
76
|
const start = nextStart;
|
|
75
|
-
nextStart =
|
|
77
|
+
nextStart = roundToCenti(nextStart + duration);
|
|
76
78
|
return { start, track };
|
|
77
79
|
});
|
|
78
80
|
}
|
|
@@ -120,8 +122,7 @@ export function buildTimelineAssetInsertHtml(input: {
|
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
export function insertTimelineAssetIntoSource(source: string, assetHtml: string): string {
|
|
123
|
-
const
|
|
124
|
-
const match = rootOpenTag.exec(source);
|
|
125
|
+
const match = COMPOSITION_ROOT_OPEN_TAG_RE.exec(source);
|
|
125
126
|
if (!match || match.index == null) {
|
|
126
127
|
throw new Error("No composition root found in target source");
|
|
127
128
|
}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import type { TimelineElement } from "../player";
|
|
2
2
|
|
|
3
|
-
const TIMELINE_INSPECTOR_BOUNDARY_EPSILON_SECONDS = 0.08;
|
|
4
|
-
|
|
5
3
|
const AUDIO_TIMELINE_TAGS = new Set(["audio", "music", "sfx", "sound", "narration"]);
|
|
6
4
|
const AUDIO_SOURCE_EXT_RE = /\.(aac|flac|m4a|mp3|ogg|opus|wav)(?:[?#].*)?$/i;
|
|
5
|
+
const MUSIC_ID_RE = /\b(music|bgm|soundtrack|background[-_]?music)\b/i;
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
element: Pick<TimelineElement, "id" | "key"> | null | undefined,
|
|
10
|
-
): string | null {
|
|
11
|
-
if (!element) return null;
|
|
12
|
-
return element.key ?? element.id;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function isAudioTimelineElement(
|
|
7
|
+
function isAudioTimelineElement(
|
|
16
8
|
element: Pick<TimelineElement, "tag" | "src"> | null | undefined,
|
|
17
9
|
): boolean {
|
|
18
10
|
if (!element) return false;
|
|
@@ -21,96 +13,19 @@ export function isAudioTimelineElement(
|
|
|
21
13
|
return Boolean(element.src && AUDIO_SOURCE_EXT_RE.test(element.src));
|
|
22
14
|
}
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
element: Pick<TimelineElement, "start" | "duration"> | null | undefined,
|
|
33
|
-
epsilonSeconds = TIMELINE_INSPECTOR_BOUNDARY_EPSILON_SECONDS,
|
|
34
|
-
): boolean {
|
|
35
|
-
if (!element) return false;
|
|
36
|
-
if (!Number.isFinite(currentTime)) return false;
|
|
37
|
-
if (!Number.isFinite(element.start) || !Number.isFinite(element.duration)) return false;
|
|
38
|
-
const start = Math.max(0, element.start);
|
|
39
|
-
const end = Math.max(start, start + Math.max(0, element.duration));
|
|
40
|
-
const epsilon = Math.max(0, epsilonSeconds);
|
|
41
|
-
return Math.abs(currentTime - start) <= epsilon || Math.abs(currentTime - end) <= epsilon;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function isTimelineElementActiveAtTime(
|
|
45
|
-
currentTime: number,
|
|
46
|
-
element: Pick<TimelineElement, "start" | "duration"> | null | undefined,
|
|
47
|
-
epsilonSeconds = TIMELINE_INSPECTOR_BOUNDARY_EPSILON_SECONDS,
|
|
16
|
+
/** True for the music track: an audio element with data-timeline-role="music",
|
|
17
|
+
* or — when no role is set — an id matching the music regex. Voiceover/other
|
|
18
|
+
* audio (explicit non-music role) is excluded. */
|
|
19
|
+
export function isMusicTrack(
|
|
20
|
+
element:
|
|
21
|
+
| Pick<TimelineElement, "tag" | "src" | "id" | "domId" | "timelineRole">
|
|
22
|
+
| null
|
|
23
|
+
| undefined,
|
|
48
24
|
): boolean {
|
|
49
25
|
if (!element) return false;
|
|
50
|
-
if (!
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
return currentTime >= start - epsilon && currentTime <= end + epsilon;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface TimelineLayerVisibility {
|
|
59
|
-
visible: boolean;
|
|
60
|
-
compositeOpacity: number;
|
|
61
|
-
hasBox: boolean;
|
|
62
|
-
inViewport: boolean;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function getTimelineLayerVisibilityInPreview(
|
|
66
|
-
element: HTMLElement,
|
|
67
|
-
options: { minCompositeOpacity?: number } = {},
|
|
68
|
-
): TimelineLayerVisibility {
|
|
69
|
-
const hidden: TimelineLayerVisibility = {
|
|
70
|
-
visible: false,
|
|
71
|
-
compositeOpacity: 0,
|
|
72
|
-
hasBox: false,
|
|
73
|
-
inViewport: false,
|
|
74
|
-
};
|
|
75
|
-
if (!element.isConnected) return hidden;
|
|
76
|
-
const doc = element.ownerDocument;
|
|
77
|
-
const win = doc.defaultView;
|
|
78
|
-
if (!win) return hidden;
|
|
79
|
-
|
|
80
|
-
const minCompositeOpacity = options.minCompositeOpacity ?? 0.01;
|
|
81
|
-
let compositeOpacity = 1;
|
|
82
|
-
let current: HTMLElement | null = element;
|
|
83
|
-
while (current && current !== doc.body && current !== doc.documentElement) {
|
|
84
|
-
const style = win.getComputedStyle(current);
|
|
85
|
-
if (style.display === "none" || style.visibility === "hidden") {
|
|
86
|
-
return { ...hidden, compositeOpacity };
|
|
87
|
-
}
|
|
88
|
-
compositeOpacity *= Number.parseFloat(style.opacity || "1");
|
|
89
|
-
if (compositeOpacity <= minCompositeOpacity) {
|
|
90
|
-
return { ...hidden, compositeOpacity };
|
|
91
|
-
}
|
|
92
|
-
current = current.parentElement;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const rect = element.getBoundingClientRect();
|
|
96
|
-
const hasBox = rect.width > 0.5 && rect.height > 0.5;
|
|
97
|
-
if (!hasBox) return { visible: false, compositeOpacity, hasBox, inViewport: false };
|
|
98
|
-
|
|
99
|
-
const viewportWidth = win.innerWidth || doc.documentElement.clientWidth;
|
|
100
|
-
const viewportHeight = win.innerHeight || doc.documentElement.clientHeight;
|
|
101
|
-
const inViewport =
|
|
102
|
-
rect.right > 0 && rect.bottom > 0 && rect.left < viewportWidth && rect.top < viewportHeight;
|
|
103
|
-
return {
|
|
104
|
-
visible: inViewport,
|
|
105
|
-
compositeOpacity,
|
|
106
|
-
hasBox,
|
|
107
|
-
inViewport,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function isTimelineLayerVisibleInPreview(
|
|
112
|
-
element: HTMLElement,
|
|
113
|
-
options: { minCompositeOpacity?: number } = {},
|
|
114
|
-
): boolean {
|
|
115
|
-
return getTimelineLayerVisibilityInPreview(element, options).visible;
|
|
26
|
+
if (!isAudioTimelineElement(element)) return false;
|
|
27
|
+
if (element.timelineRole === "music") return true;
|
|
28
|
+
if (element.timelineRole && element.timelineRole !== "music") return false;
|
|
29
|
+
const id = element.domId ?? element.id ?? "";
|
|
30
|
+
return MUSIC_ID_RE.test(id);
|
|
116
31
|
}
|