@hyperframes/studio 0.6.6 → 0.6.7
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-T-ME1rqL.js → hyperframes-player-D0Yi3xMP.js} +2 -2
- package/dist/assets/{index-Bne9FFeo.css → index-Ckqo37Co.css} +1 -1
- package/dist/assets/index-Yvtxngdi.js +116 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +54 -31
- package/src/components/StudioGlobalDragOverlay.tsx +26 -0
- package/src/components/StudioRightPanel.tsx +0 -2
- package/src/components/editor/DomEditOverlay.test.ts +1 -0
- package/src/components/editor/DomEditOverlay.tsx +2 -1
- package/src/components/editor/PropertyPanel.tsx +27 -36
- package/src/components/editor/domEditingElement.ts +1 -0
- package/src/components/editor/manualEdits.test.ts +39 -466
- package/src/components/editor/manualEdits.ts +6 -168
- package/src/components/editor/manualEditsDom.ts +361 -1
- package/src/components/editor/manualEditsParsing.ts +2 -240
- package/src/components/editor/manualEditsTypes.ts +1 -40
- package/src/components/editor/useDomEditOverlayGestures.ts +25 -8
- package/src/components/nle/NLEPreview.tsx +1 -1
- package/src/components/sidebar/CompositionsTab.tsx +9 -3
- package/src/contexts/DomEditContext.tsx +3 -0
- package/src/contexts/FileManagerContext.tsx +3 -0
- package/src/hooks/useAppHotkeys.ts +1 -4
- package/src/hooks/useDomEditCommits.ts +82 -77
- package/src/hooks/useDomEditSession.ts +4 -16
- package/src/hooks/useFileManager.ts +10 -1
- package/src/hooks/useManifestPersistence.ts +51 -187
- package/src/hooks/usePanelLayout.ts +10 -3
- package/src/hooks/usePreviewInteraction.ts +0 -1
- package/src/hooks/useStudioUrlState.ts +188 -0
- package/src/player/components/Player.tsx +15 -1
- package/src/player/components/PlayerControls.test.ts +17 -0
- package/src/player/components/PlayerControls.tsx +61 -0
- package/src/player/hooks/usePlaybackKeyboard.test.ts +174 -0
- package/src/player/hooks/usePlaybackKeyboard.ts +18 -15
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +329 -0
- package/src/player/hooks/useTimelinePlayer.ts +76 -18
- package/src/player/hooks/useTimelineSyncCallbacks.ts +10 -4
- package/src/player/lib/playbackAdapter.test.ts +50 -0
- package/src/player/lib/playbackAdapter.ts +2 -2
- package/src/player/lib/playbackTypes.ts +1 -1
- package/src/player/lib/timelineDOM.ts +4 -2
- package/src/player/lib/timelineIframeHelpers.ts +63 -7
- package/src/player/store/playerStore.test.ts +105 -1
- package/src/player/store/playerStore.ts +12 -1
- package/src/utils/projectRouting.test.ts +15 -0
- package/src/utils/projectRouting.ts +46 -9
- package/src/utils/sourcePatcher.ts +50 -14
- package/src/utils/studioPreviewHelpers.test.ts +56 -0
- package/src/utils/studioPreviewHelpers.ts +51 -13
- package/src/utils/studioUiPreferences.test.ts +3 -0
- package/src/utils/studioUiPreferences.ts +4 -0
- package/src/utils/studioUrlState.test.ts +249 -0
- package/src/utils/studioUrlState.ts +135 -0
- package/dist/assets/index-DYqqzECY.js +0 -117
|
@@ -1,142 +1,10 @@
|
|
|
1
|
-
import type { DomEditSelection } from "./domEditing";
|
|
2
|
-
import type {
|
|
3
|
-
StudioManualEdit,
|
|
4
|
-
StudioManualEditManifest,
|
|
5
|
-
StudioManualEditTarget,
|
|
6
|
-
StudioPathOffsetEdit,
|
|
7
|
-
StudioBoxSizeEdit,
|
|
8
|
-
StudioRotationEdit,
|
|
9
|
-
} from "./manualEditsTypes";
|
|
10
|
-
import { STUDIO_MANUAL_EDITS_PATH } from "./manualEditsTypes";
|
|
11
|
-
|
|
12
1
|
/* ── Helpers ──────────────────────────────────────────────────────── */
|
|
13
2
|
export function finiteNumber(value: unknown): number | null {
|
|
14
3
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
15
4
|
}
|
|
16
5
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return { version: 1, edits: [] };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/* ── Parsing ──────────────────────────────────────────────────────── */
|
|
23
|
-
function parsePathOffsetEdit(value: unknown): StudioPathOffsetEdit | null {
|
|
24
|
-
if (!value || typeof value !== "object") return null;
|
|
25
|
-
const record = value as Record<string, unknown>;
|
|
26
|
-
if (record.kind !== "path-offset") return null;
|
|
27
|
-
const target = record.target;
|
|
28
|
-
if (!target || typeof target !== "object") return null;
|
|
29
|
-
const targetRecord = target as Record<string, unknown>;
|
|
30
|
-
const sourceFile = typeof targetRecord.sourceFile === "string" ? targetRecord.sourceFile : "";
|
|
31
|
-
if (!sourceFile) return null;
|
|
32
|
-
|
|
33
|
-
const selector = typeof targetRecord.selector === "string" ? targetRecord.selector : undefined;
|
|
34
|
-
const id = typeof targetRecord.id === "string" ? targetRecord.id : undefined;
|
|
35
|
-
if (!selector && !id) return null;
|
|
36
|
-
|
|
37
|
-
const x = finiteNumber(record.x);
|
|
38
|
-
const y = finiteNumber(record.y);
|
|
39
|
-
if (x == null || y == null) return null;
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
kind: "path-offset",
|
|
43
|
-
target: {
|
|
44
|
-
sourceFile,
|
|
45
|
-
selector,
|
|
46
|
-
selectorIndex: finiteNumber(targetRecord.selectorIndex) ?? undefined,
|
|
47
|
-
id,
|
|
48
|
-
},
|
|
49
|
-
x,
|
|
50
|
-
y,
|
|
51
|
-
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : undefined,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function parseBoxSizeEdit(value: unknown): StudioBoxSizeEdit | null {
|
|
56
|
-
if (!value || typeof value !== "object") return null;
|
|
57
|
-
const record = value as Record<string, unknown>;
|
|
58
|
-
if (record.kind !== "box-size") return null;
|
|
59
|
-
const target = record.target;
|
|
60
|
-
if (!target || typeof target !== "object") return null;
|
|
61
|
-
const targetRecord = target as Record<string, unknown>;
|
|
62
|
-
const sourceFile = typeof targetRecord.sourceFile === "string" ? targetRecord.sourceFile : "";
|
|
63
|
-
if (!sourceFile) return null;
|
|
64
|
-
|
|
65
|
-
const selector = typeof targetRecord.selector === "string" ? targetRecord.selector : undefined;
|
|
66
|
-
const id = typeof targetRecord.id === "string" ? targetRecord.id : undefined;
|
|
67
|
-
if (!selector && !id) return null;
|
|
68
|
-
|
|
69
|
-
const width = finiteNumber(record.width);
|
|
70
|
-
const height = finiteNumber(record.height);
|
|
71
|
-
if (width == null || height == null || width <= 0 || height <= 0) return null;
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
kind: "box-size",
|
|
75
|
-
target: {
|
|
76
|
-
sourceFile,
|
|
77
|
-
selector,
|
|
78
|
-
selectorIndex: finiteNumber(targetRecord.selectorIndex) ?? undefined,
|
|
79
|
-
id,
|
|
80
|
-
},
|
|
81
|
-
width,
|
|
82
|
-
height,
|
|
83
|
-
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : undefined,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function parseRotationEdit(value: unknown): StudioRotationEdit | null {
|
|
88
|
-
if (!value || typeof value !== "object") return null;
|
|
89
|
-
const record = value as Record<string, unknown>;
|
|
90
|
-
if (record.kind !== "rotation") return null;
|
|
91
|
-
const target = record.target;
|
|
92
|
-
if (!target || typeof target !== "object") return null;
|
|
93
|
-
const targetRecord = target as Record<string, unknown>;
|
|
94
|
-
const sourceFile = typeof targetRecord.sourceFile === "string" ? targetRecord.sourceFile : "";
|
|
95
|
-
if (!sourceFile) return null;
|
|
96
|
-
|
|
97
|
-
const selector = typeof targetRecord.selector === "string" ? targetRecord.selector : undefined;
|
|
98
|
-
const id = typeof targetRecord.id === "string" ? targetRecord.id : undefined;
|
|
99
|
-
if (!selector && !id) return null;
|
|
100
|
-
|
|
101
|
-
const angle = finiteNumber(record.angle);
|
|
102
|
-
if (angle == null) return null;
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
kind: "rotation",
|
|
106
|
-
target: {
|
|
107
|
-
sourceFile,
|
|
108
|
-
selector,
|
|
109
|
-
selectorIndex: finiteNumber(targetRecord.selectorIndex) ?? undefined,
|
|
110
|
-
id,
|
|
111
|
-
},
|
|
112
|
-
angle,
|
|
113
|
-
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : undefined,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function parseManualEdit(value: unknown): StudioManualEdit | null {
|
|
118
|
-
return parsePathOffsetEdit(value) ?? parseBoxSizeEdit(value) ?? parseRotationEdit(value);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function parseStudioManualEditManifest(content: string): StudioManualEditManifest {
|
|
122
|
-
if (!content.trim()) return emptyStudioManualEditManifest();
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
const parsed = JSON.parse(content) as unknown;
|
|
126
|
-
if (!parsed || typeof parsed !== "object") return emptyStudioManualEditManifest();
|
|
127
|
-
const edits = (parsed as { edits?: unknown }).edits;
|
|
128
|
-
if (!Array.isArray(edits)) return emptyStudioManualEditManifest();
|
|
129
|
-
return {
|
|
130
|
-
version: 1,
|
|
131
|
-
edits: edits.map(parseManualEdit).filter((edit): edit is StudioManualEdit => edit !== null),
|
|
132
|
-
};
|
|
133
|
-
} catch {
|
|
134
|
-
return emptyStudioManualEditManifest();
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function serializeStudioManualEditManifest(manifest: StudioManualEditManifest): string {
|
|
139
|
-
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
6
|
+
export function roundRotationAngle(angle: number): number {
|
|
7
|
+
return Math.round(angle * 10) / 10;
|
|
140
8
|
}
|
|
141
9
|
|
|
142
10
|
/* ── File path utilities ──────────────────────────────────────────── */
|
|
@@ -172,109 +40,3 @@ function readStudioFileChangePathFromValue(value: unknown): string | null {
|
|
|
172
40
|
export function readStudioFileChangePath(payload: unknown): string | null {
|
|
173
41
|
return readStudioFileChangePathFromValue(payload);
|
|
174
42
|
}
|
|
175
|
-
|
|
176
|
-
export function isStudioManualEditManifestPath(path: string | null): boolean {
|
|
177
|
-
if (!path) return false;
|
|
178
|
-
const normalized = normalizeStudioFileChangePath(path);
|
|
179
|
-
return (
|
|
180
|
-
normalized === STUDIO_MANUAL_EDITS_PATH || normalized.endsWith(`/${STUDIO_MANUAL_EDITS_PATH}`)
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/* ── Target / upsert helpers ──────────────────────────────────────── */
|
|
185
|
-
function selectionTarget(selection: DomEditSelection): StudioManualEditTarget {
|
|
186
|
-
return {
|
|
187
|
-
sourceFile: selection.sourceFile || "index.html",
|
|
188
|
-
selector: selection.selector,
|
|
189
|
-
selectorIndex: selection.selectorIndex,
|
|
190
|
-
id: selection.id ?? undefined,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function targetKey(target: StudioManualEditTarget): string {
|
|
195
|
-
return [
|
|
196
|
-
target.sourceFile,
|
|
197
|
-
target.id ?? "",
|
|
198
|
-
target.selector ?? "",
|
|
199
|
-
target.selectorIndex ?? "",
|
|
200
|
-
].join("|");
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function roundRotationAngle(angle: number): number {
|
|
204
|
-
return Math.round(angle * 10) / 10;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export function upsertStudioPathOffsetEdit(
|
|
208
|
-
manifest: StudioManualEditManifest,
|
|
209
|
-
selection: DomEditSelection,
|
|
210
|
-
offset: { x: number; y: number },
|
|
211
|
-
): StudioManualEditManifest {
|
|
212
|
-
const target = selectionTarget(selection);
|
|
213
|
-
const key = targetKey(target);
|
|
214
|
-
const nextEdit: StudioPathOffsetEdit = {
|
|
215
|
-
kind: "path-offset",
|
|
216
|
-
target,
|
|
217
|
-
x: Math.round(offset.x),
|
|
218
|
-
y: Math.round(offset.y),
|
|
219
|
-
updatedAt: new Date().toISOString(),
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const edits = manifest.edits.filter(
|
|
223
|
-
(edit) => edit.kind !== "path-offset" || targetKey(edit.target) !== key,
|
|
224
|
-
);
|
|
225
|
-
edits.push(nextEdit);
|
|
226
|
-
return { version: 1, edits };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export function upsertStudioBoxSizeEdit(
|
|
230
|
-
manifest: StudioManualEditManifest,
|
|
231
|
-
selection: DomEditSelection,
|
|
232
|
-
size: { width: number; height: number },
|
|
233
|
-
): StudioManualEditManifest {
|
|
234
|
-
const target = selectionTarget(selection);
|
|
235
|
-
const key = targetKey(target);
|
|
236
|
-
const nextEdit: StudioBoxSizeEdit = {
|
|
237
|
-
kind: "box-size",
|
|
238
|
-
target,
|
|
239
|
-
width: Math.round(Math.max(1, size.width)),
|
|
240
|
-
height: Math.round(Math.max(1, size.height)),
|
|
241
|
-
updatedAt: new Date().toISOString(),
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
const edits = manifest.edits.filter(
|
|
245
|
-
(edit) => edit.kind !== "box-size" || targetKey(edit.target) !== key,
|
|
246
|
-
);
|
|
247
|
-
edits.push(nextEdit);
|
|
248
|
-
return { version: 1, edits };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
export function upsertStudioRotationEdit(
|
|
252
|
-
manifest: StudioManualEditManifest,
|
|
253
|
-
selection: DomEditSelection,
|
|
254
|
-
rotation: { angle: number },
|
|
255
|
-
): StudioManualEditManifest {
|
|
256
|
-
const target = selectionTarget(selection);
|
|
257
|
-
const key = targetKey(target);
|
|
258
|
-
const nextEdit: StudioRotationEdit = {
|
|
259
|
-
kind: "rotation",
|
|
260
|
-
target,
|
|
261
|
-
angle: roundRotationAngle(rotation.angle),
|
|
262
|
-
updatedAt: new Date().toISOString(),
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const edits = manifest.edits.filter(
|
|
266
|
-
(edit) => edit.kind !== "rotation" || targetKey(edit.target) !== key,
|
|
267
|
-
);
|
|
268
|
-
edits.push(nextEdit);
|
|
269
|
-
return { version: 1, edits };
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function removeStudioManualEditsForSelection(
|
|
273
|
-
manifest: StudioManualEditManifest,
|
|
274
|
-
selection: DomEditSelection,
|
|
275
|
-
): StudioManualEditManifest {
|
|
276
|
-
const key = targetKey(selectionTarget(selection));
|
|
277
|
-
const edits = manifest.edits.filter((edit) => targetKey(edit.target) !== key);
|
|
278
|
-
if (edits.length === manifest.edits.length) return manifest;
|
|
279
|
-
return { version: 1, edits };
|
|
280
|
-
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* ── Public constants ──────────────────────────────────────────────── */
|
|
2
|
-
export const STUDIO_MANUAL_EDITS_PATH = ".hyperframes/studio-manual-edits.json";
|
|
3
2
|
export const STUDIO_OFFSET_X_PROP = "--hf-studio-offset-x";
|
|
4
3
|
export const STUDIO_OFFSET_Y_PROP = "--hf-studio-offset-y";
|
|
5
4
|
export const STUDIO_WIDTH_PROP = "--hf-studio-width";
|
|
@@ -40,44 +39,6 @@ export const STUDIO_MANUAL_EDITS_PLAYBACK_FRAME_PROP = "__hfStudioManualEditsPla
|
|
|
40
39
|
|
|
41
40
|
export const STUDIO_ROTATION_TRANSFORM_ORIGIN = "center center";
|
|
42
41
|
|
|
43
|
-
/* ── Edit types ───────────────────────────────────────────────────── */
|
|
44
|
-
export interface StudioManualEditTarget {
|
|
45
|
-
sourceFile: string;
|
|
46
|
-
selector?: string;
|
|
47
|
-
selectorIndex?: number;
|
|
48
|
-
id?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface StudioPathOffsetEdit {
|
|
52
|
-
kind: "path-offset";
|
|
53
|
-
target: StudioManualEditTarget;
|
|
54
|
-
x: number;
|
|
55
|
-
y: number;
|
|
56
|
-
updatedAt?: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface StudioBoxSizeEdit {
|
|
60
|
-
kind: "box-size";
|
|
61
|
-
target: StudioManualEditTarget;
|
|
62
|
-
width: number;
|
|
63
|
-
height: number;
|
|
64
|
-
updatedAt?: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface StudioRotationEdit {
|
|
68
|
-
kind: "rotation";
|
|
69
|
-
target: StudioManualEditTarget;
|
|
70
|
-
angle: number;
|
|
71
|
-
updatedAt?: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export type StudioManualEdit = StudioPathOffsetEdit | StudioBoxSizeEdit | StudioRotationEdit;
|
|
75
|
-
|
|
76
|
-
export interface StudioManualEditManifest {
|
|
77
|
-
version: 1;
|
|
78
|
-
edits: StudioManualEdit[];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
42
|
export type StudioManualEditSeekWindow = Window & {
|
|
82
43
|
__hf?: Record<string, unknown>;
|
|
83
44
|
__player?: Record<string, unknown>;
|
|
@@ -87,7 +48,7 @@ export type StudioManualEditSeekWindow = Window & {
|
|
|
87
48
|
__hfStudioManualEditsPlaybackFrame?: number | null;
|
|
88
49
|
};
|
|
89
50
|
|
|
90
|
-
/* ── Snapshot types
|
|
51
|
+
/* ── Snapshot types (used by drag/drop restore) ───────────────────── */
|
|
91
52
|
export interface StudioBoxSizeSnapshot {
|
|
92
53
|
width: string;
|
|
93
54
|
height: string;
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
restoreStudioPathOffset,
|
|
24
24
|
restoreStudioRotation,
|
|
25
25
|
} from "./manualEdits";
|
|
26
|
-
import { type GroupOverlayItem, type OverlayRect } from "./domEditOverlayGeometry";
|
|
26
|
+
import { type GroupOverlayItem, type OverlayRect, toOverlayRect } from "./domEditOverlayGeometry";
|
|
27
27
|
import {
|
|
28
28
|
BLOCKED_MOVE_THRESHOLD_PX,
|
|
29
29
|
type BlockedMoveState,
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
// Refs are stable across renders; values are read via .current.
|
|
44
44
|
export type UseDomEditOverlayGesturesOptions = {
|
|
45
45
|
overlayRef: RefObject<HTMLDivElement | null>;
|
|
46
|
+
iframeRef: RefObject<HTMLIFrameElement | null>;
|
|
46
47
|
boxRef: RefObject<HTMLDivElement | null>;
|
|
47
48
|
selectionRef: RefObject<DomEditSelection | null>;
|
|
48
49
|
overlayRectRef: RefObject<OverlayRect | null>;
|
|
@@ -194,17 +195,31 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
194
195
|
dy,
|
|
195
196
|
uniform: e.shiftKey,
|
|
196
197
|
});
|
|
198
|
+
applyStudioBoxSizeDraft(sel.element, nextSize);
|
|
199
|
+
|
|
200
|
+
// Re-read BCR after applying dimensions. For elements with a GSAP
|
|
201
|
+
// scale transform and centered transform-origin the visual top-left
|
|
202
|
+
// drifts and the visual size diverges from the raw CSS size, so BCR
|
|
203
|
+
// is the only accurate source for both.
|
|
204
|
+
const overlayEl = opts.overlayRef.current;
|
|
205
|
+
const iframe = opts.iframeRef.current;
|
|
206
|
+
const refreshed = overlayEl && iframe ? toOverlayRect(overlayEl, iframe, sel.element) : null;
|
|
207
|
+
const overlayLeft = refreshed ? refreshed.left : g.originLeft;
|
|
208
|
+
const overlayTop = refreshed ? refreshed.top : g.originTop;
|
|
209
|
+
const overlayWidth = refreshed ? refreshed.width : nextSize.overlayWidth;
|
|
210
|
+
const overlayHeight = refreshed ? refreshed.height : nextSize.overlayHeight;
|
|
211
|
+
box.style.left = `${overlayLeft}px`;
|
|
212
|
+
box.style.top = `${overlayTop}px`;
|
|
213
|
+
box.style.width = `${overlayWidth}px`;
|
|
214
|
+
box.style.height = `${overlayHeight}px`;
|
|
197
215
|
setDraftOverlayRect({
|
|
198
|
-
left:
|
|
199
|
-
top:
|
|
200
|
-
width:
|
|
201
|
-
height:
|
|
216
|
+
left: overlayLeft,
|
|
217
|
+
top: overlayTop,
|
|
218
|
+
width: overlayWidth,
|
|
219
|
+
height: overlayHeight,
|
|
202
220
|
editScaleX: g.editScaleX,
|
|
203
221
|
editScaleY: g.editScaleY,
|
|
204
222
|
});
|
|
205
|
-
box.style.width = `${nextSize.overlayWidth}px`;
|
|
206
|
-
box.style.height = `${nextSize.overlayHeight}px`;
|
|
207
|
-
applyStudioBoxSizeDraft(sel.element, nextSize);
|
|
208
223
|
}
|
|
209
224
|
};
|
|
210
225
|
|
|
@@ -281,6 +296,7 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
281
296
|
box.style.height = `${g.originHeight}px`;
|
|
282
297
|
}
|
|
283
298
|
restoreGestureOverlayRect(g);
|
|
299
|
+
opts.suppressNextBoxClickRef.current = true;
|
|
284
300
|
return;
|
|
285
301
|
}
|
|
286
302
|
|
|
@@ -341,6 +357,7 @@ export function createDomEditOverlayGestureHandlers(opts: UseDomEditOverlayGestu
|
|
|
341
357
|
if (g.pathOffsetMember) endManualOffsetDragMembers([g.pathOffsetMember]);
|
|
342
358
|
});
|
|
343
359
|
} else {
|
|
360
|
+
opts.suppressNextBoxClickRef.current = true;
|
|
344
361
|
const finalSize = readStudioBoxSize(sel.element);
|
|
345
362
|
applyStudioBoxSize(sel.element, finalSize);
|
|
346
363
|
void Promise.resolve(opts.onBoxSizeCommitRef.current(sel, finalSize))
|
|
@@ -264,7 +264,7 @@ export const NLEPreview = memo(function NLEPreview({
|
|
|
264
264
|
<div className="flex flex-col h-full min-h-0">
|
|
265
265
|
<div
|
|
266
266
|
ref={viewportRef}
|
|
267
|
-
className="relative flex-1 flex items-center justify-center p-2 overflow-hidden min-h-0 outline-none focus:ring-1 focus:ring-studio-accent/40"
|
|
267
|
+
className="relative flex-1 flex items-center justify-center p-2 overflow-hidden min-h-0 outline-none focus:ring-1 focus:ring-studio-accent/40 bg-neutral-700"
|
|
268
268
|
tabIndex={0}
|
|
269
269
|
aria-label="Composition preview"
|
|
270
270
|
onPointerDown={handlePointerDown}
|
|
@@ -8,6 +8,8 @@ interface CompositionsTabProps {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
|
|
11
|
+
const CARD_W = 80;
|
|
12
|
+
const CARD_H = 45;
|
|
11
13
|
const THUMBNAIL_SEEK_TIME_SECONDS = 3;
|
|
12
14
|
const THUMBNAIL_PLAYBACK_SYNC_ATTEMPTS = 10;
|
|
13
15
|
|
|
@@ -132,11 +134,13 @@ function CompCard({
|
|
|
132
134
|
const name = comp.replace(/^compositions\//, "").replace(/\.html$/, "");
|
|
133
135
|
const previewUrl = `/api/projects/${projectId}/preview/comp/${comp}`;
|
|
134
136
|
const previewScale = resolveCompositionPreviewScale({
|
|
135
|
-
cardWidth:
|
|
136
|
-
cardHeight:
|
|
137
|
+
cardWidth: CARD_W,
|
|
138
|
+
cardHeight: CARD_H,
|
|
137
139
|
stageWidth: stageSize.width,
|
|
138
140
|
stageHeight: stageSize.height,
|
|
139
141
|
});
|
|
142
|
+
const thumbnailOffsetX = (CARD_W - stageSize.width * previewScale) / 2;
|
|
143
|
+
const thumbnailOffsetY = (CARD_H - stageSize.height * previewScale) / 2;
|
|
140
144
|
|
|
141
145
|
useEffect(() => {
|
|
142
146
|
requestIframePlaybackSync(hovered);
|
|
@@ -166,11 +170,13 @@ function CompCard({
|
|
|
166
170
|
src={previewUrl}
|
|
167
171
|
sandbox="allow-scripts allow-same-origin"
|
|
168
172
|
loading="lazy"
|
|
169
|
-
className="absolute
|
|
173
|
+
className="absolute border-none pointer-events-none"
|
|
170
174
|
style={{
|
|
171
175
|
transformOrigin: "0 0",
|
|
172
176
|
width: stageSize.width,
|
|
173
177
|
height: stageSize.height,
|
|
178
|
+
left: thumbnailOffsetX,
|
|
179
|
+
top: thumbnailOffsetY,
|
|
174
180
|
transform: `scale(${previewScale})`,
|
|
175
181
|
}}
|
|
176
182
|
onLoad={(e) => {
|
|
@@ -44,6 +44,7 @@ export function DomEditProvider({
|
|
|
44
44
|
handleBlockedDomMove,
|
|
45
45
|
handleDomManualDragStart,
|
|
46
46
|
handleDomEditElementDelete,
|
|
47
|
+
buildDomSelectionFromTarget,
|
|
47
48
|
buildDomSelectionForTimelineElement,
|
|
48
49
|
updateDomEditHoverSelection,
|
|
49
50
|
resolveImportedFontAsset,
|
|
@@ -89,6 +90,7 @@ export function DomEditProvider({
|
|
|
89
90
|
handleBlockedDomMove,
|
|
90
91
|
handleDomManualDragStart,
|
|
91
92
|
handleDomEditElementDelete,
|
|
93
|
+
buildDomSelectionFromTarget,
|
|
92
94
|
buildDomSelectionForTimelineElement,
|
|
93
95
|
updateDomEditHoverSelection,
|
|
94
96
|
resolveImportedFontAsset,
|
|
@@ -128,6 +130,7 @@ export function DomEditProvider({
|
|
|
128
130
|
handleBlockedDomMove,
|
|
129
131
|
handleDomManualDragStart,
|
|
130
132
|
handleDomEditElementDelete,
|
|
133
|
+
buildDomSelectionFromTarget,
|
|
131
134
|
buildDomSelectionForTimelineElement,
|
|
132
135
|
updateDomEditHoverSelection,
|
|
133
136
|
resolveImportedFontAsset,
|
|
@@ -17,6 +17,7 @@ export function FileManagerProvider({
|
|
|
17
17
|
setEditingFile,
|
|
18
18
|
projectDir,
|
|
19
19
|
fileTree,
|
|
20
|
+
fileTreeLoaded,
|
|
20
21
|
setFileTree,
|
|
21
22
|
editingPathRef,
|
|
22
23
|
projectIdRef,
|
|
@@ -52,6 +53,7 @@ export function FileManagerProvider({
|
|
|
52
53
|
setEditingFile,
|
|
53
54
|
projectDir,
|
|
54
55
|
fileTree,
|
|
56
|
+
fileTreeLoaded,
|
|
55
57
|
setFileTree,
|
|
56
58
|
editingPathRef,
|
|
57
59
|
projectIdRef,
|
|
@@ -81,6 +83,7 @@ export function FileManagerProvider({
|
|
|
81
83
|
setEditingFile,
|
|
82
84
|
projectDir,
|
|
83
85
|
fileTree,
|
|
86
|
+
fileTreeLoaded,
|
|
84
87
|
setFileTree,
|
|
85
88
|
editingPathRef,
|
|
86
89
|
projectIdRef,
|
|
@@ -3,7 +3,6 @@ import { usePlayerStore } from "../player";
|
|
|
3
3
|
import type { TimelineElement } from "../player";
|
|
4
4
|
import type { DomEditSelection } from "../components/editor/domEditing";
|
|
5
5
|
import type { LeftSidebarHandle } from "../components/sidebar/LeftSidebar";
|
|
6
|
-
import { STUDIO_MANUAL_EDITS_PATH } from "../components/editor/manualEdits";
|
|
7
6
|
import { STUDIO_MOTION_PATH } from "../components/editor/studioMotion";
|
|
8
7
|
import { shouldHandleTimelineToggleHotkey, isEditableTarget } from "../utils/timelineDiscovery";
|
|
9
8
|
import { shouldIgnoreHistoryShortcut } from "../utils/studioHelpers";
|
|
@@ -85,9 +84,7 @@ export function useAppHotkeys({
|
|
|
85
84
|
|
|
86
85
|
const readHistoryProjectFile = useCallback(
|
|
87
86
|
async (path: string): Promise<string> => {
|
|
88
|
-
return path ===
|
|
89
|
-
? readOptionalProjectFile(path)
|
|
90
|
-
: readProjectFile(path);
|
|
87
|
+
return path === STUDIO_MOTION_PATH ? readOptionalProjectFile(path) : readProjectFile(path);
|
|
91
88
|
},
|
|
92
89
|
[readOptionalProjectFile, readProjectFile],
|
|
93
90
|
);
|