@hyperframes/studio 0.5.0-alpha.13 → 0.5.0-alpha.15
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-vibA20NC.js → hyperframes-player-Cd8vYWxP.js} +2 -2
- package/dist/assets/index-DFLVGWTx.js +106 -0
- package/dist/assets/index-mXJ-UH9F.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +785 -377
- package/src/captions/generator.test.ts +19 -0
- package/src/captions/generator.ts +9 -2
- package/src/captions/hooks/useCaptionSync.ts +6 -1
- package/src/captions/parser.test.ts +14 -0
- package/src/captions/parser.ts +1 -0
- package/src/components/editor/DomEditOverlay.test.ts +241 -0
- package/src/components/editor/DomEditOverlay.tsx +970 -115
- package/src/components/editor/PropertyPanel.tsx +91 -83
- package/src/components/editor/domEditing.test.ts +161 -29
- package/src/components/editor/domEditing.ts +84 -113
- package/src/components/editor/manualEdits.test.ts +945 -0
- package/src/components/editor/manualEdits.ts +1397 -0
- package/src/components/editor/manualOffsetDrag.test.ts +140 -0
- package/src/components/editor/manualOffsetDrag.ts +307 -0
- package/src/components/renders/RenderQueue.tsx +10 -3
- package/src/hooks/usePersistentEditHistory.test.ts +1 -0
- package/src/hooks/usePersistentEditHistory.ts +3 -2
- package/src/player/components/CompositionThumbnail.test.ts +1 -1
- package/src/player/components/CompositionThumbnail.tsx +1 -1
- package/src/player/components/Player.tsx +54 -9
- package/src/player/hooks/useTimelinePlayer.test.ts +1 -0
- package/src/utils/clipboard.test.ts +1 -0
- package/src/utils/frameCapture.ts +3 -1
- package/src/utils/projectRouting.test.ts +87 -0
- package/src/utils/projectRouting.ts +27 -0
- package/dist/assets/index-JhhmFie-.js +0 -105
- package/dist/assets/index-KioPDrX6.css +0 -1
|
@@ -35,9 +35,13 @@ const CURATED_STYLE_PROPERTIES = [
|
|
|
35
35
|
export interface DomEditCapabilities {
|
|
36
36
|
canSelect: boolean;
|
|
37
37
|
canEditStyles: boolean;
|
|
38
|
+
/** Directly editable authored left/top style fields. Canvas drag uses manual edits instead. */
|
|
38
39
|
canMove: boolean;
|
|
40
|
+
/** Directly editable authored width/height style fields. Canvas resize uses manual edits instead. */
|
|
39
41
|
canResize: boolean;
|
|
40
|
-
|
|
42
|
+
canApplyManualOffset: boolean;
|
|
43
|
+
canApplyManualSize: boolean;
|
|
44
|
+
canApplyManualRotation: boolean;
|
|
41
45
|
reasonIfDisabled?: string;
|
|
42
46
|
}
|
|
43
47
|
|
|
@@ -119,54 +123,6 @@ function isIdentityTransform(value: string | undefined): boolean {
|
|
|
119
123
|
return values.every((part, index) => Math.abs(part - identity[index]) < 0.0001);
|
|
120
124
|
}
|
|
121
125
|
|
|
122
|
-
function isClipClassName(className: string | undefined): boolean {
|
|
123
|
-
return Boolean(className?.split(/\s+/).includes("clip"));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function isInlineTextTag(tagName: string | undefined): boolean {
|
|
127
|
-
return Boolean(
|
|
128
|
-
tagName &&
|
|
129
|
-
["a", "b", "em", "i", "small", "span", "strong", "sub", "sup"].includes(tagName.toLowerCase()),
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function isBlockishTag(tagName: string | undefined): boolean {
|
|
134
|
-
return Boolean(
|
|
135
|
-
tagName &&
|
|
136
|
-
[
|
|
137
|
-
"article",
|
|
138
|
-
"aside",
|
|
139
|
-
"canvas",
|
|
140
|
-
"div",
|
|
141
|
-
"figure",
|
|
142
|
-
"footer",
|
|
143
|
-
"header",
|
|
144
|
-
"img",
|
|
145
|
-
"main",
|
|
146
|
-
"section",
|
|
147
|
-
"svg",
|
|
148
|
-
"video",
|
|
149
|
-
].includes(tagName.toLowerCase()),
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function isBlockishDisplay(display: string | undefined): boolean {
|
|
154
|
-
return Boolean(
|
|
155
|
-
display &&
|
|
156
|
-
[
|
|
157
|
-
"block",
|
|
158
|
-
"flex",
|
|
159
|
-
"flow-root",
|
|
160
|
-
"grid",
|
|
161
|
-
"inline-block",
|
|
162
|
-
"inline-flex",
|
|
163
|
-
"inline-grid",
|
|
164
|
-
"list-item",
|
|
165
|
-
"table",
|
|
166
|
-
].includes(display),
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
126
|
function isTextBearingTag(tagName: string): boolean {
|
|
171
127
|
return ["div", "span", "p", "strong", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tagName);
|
|
172
128
|
}
|
|
@@ -196,16 +152,15 @@ function findClosestByAttribute(el: HTMLElement, attributeNames: string[]): HTML
|
|
|
196
152
|
return null;
|
|
197
153
|
}
|
|
198
154
|
|
|
199
|
-
function getCompositionHost(el: HTMLElement): HTMLElement | null {
|
|
200
|
-
return findClosestByAttribute(el, ["data-composition-src", "data-composition-file"]);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
155
|
function getSourceFileForElement(
|
|
204
156
|
el: HTMLElement,
|
|
205
157
|
activeCompositionPath: string | null,
|
|
206
158
|
): { sourceFile: string; compositionPath: string } {
|
|
159
|
+
const sourceHost = findClosestByAttribute(el, ["data-composition-file", "data-composition-src"]);
|
|
207
160
|
const ownerRoot = findClosestByAttribute(el, ["data-composition-id"]);
|
|
208
161
|
const sourceFile =
|
|
162
|
+
sourceHost?.getAttribute("data-composition-file") ??
|
|
163
|
+
sourceHost?.getAttribute("data-composition-src") ??
|
|
209
164
|
ownerRoot?.getAttribute("data-composition-file") ??
|
|
210
165
|
ownerRoot?.getAttribute("data-composition-src") ??
|
|
211
166
|
activeCompositionPath ??
|
|
@@ -217,21 +172,28 @@ function getSourceFileForElement(
|
|
|
217
172
|
};
|
|
218
173
|
}
|
|
219
174
|
|
|
175
|
+
function getPreferredClipAncestor(startEl: HTMLElement): HTMLElement | null {
|
|
176
|
+
let current: HTMLElement | null = startEl;
|
|
177
|
+
while (current) {
|
|
178
|
+
if (current.classList.contains("clip")) {
|
|
179
|
+
const isCompositionHost =
|
|
180
|
+
current.hasAttribute("data-composition-src") ||
|
|
181
|
+
current.hasAttribute("data-composition-file");
|
|
182
|
+
if (!isCompositionHost || current === startEl) return current;
|
|
183
|
+
}
|
|
184
|
+
current = current.parentElement;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
220
189
|
function getSelectionCandidate(startEl: HTMLElement, options: DomEditContextOptions): HTMLElement {
|
|
221
190
|
if (options.preferClipAncestor) {
|
|
222
|
-
const clipAncestor = startEl
|
|
223
|
-
if (
|
|
191
|
+
const clipAncestor = getPreferredClipAncestor(startEl);
|
|
192
|
+
if (clipAncestor) {
|
|
224
193
|
return clipAncestor;
|
|
225
194
|
}
|
|
226
195
|
}
|
|
227
196
|
|
|
228
|
-
if (!options.isMasterView) return startEl;
|
|
229
|
-
|
|
230
|
-
const compositionHost = getCompositionHost(startEl);
|
|
231
|
-
if (compositionHost && compositionHost !== startEl) {
|
|
232
|
-
return compositionHost;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
197
|
return startEl;
|
|
236
198
|
}
|
|
237
199
|
|
|
@@ -442,7 +404,9 @@ export function resolveDomEditCapabilities(args: {
|
|
|
442
404
|
canEditStyles: false,
|
|
443
405
|
canMove: false,
|
|
444
406
|
canResize: false,
|
|
445
|
-
|
|
407
|
+
canApplyManualOffset: false,
|
|
408
|
+
canApplyManualSize: false,
|
|
409
|
+
canApplyManualRotation: false,
|
|
446
410
|
reasonIfDisabled: "Studio could not resolve a stable patch target for this element.",
|
|
447
411
|
};
|
|
448
412
|
}
|
|
@@ -461,21 +425,13 @@ export function resolveDomEditCapabilities(args: {
|
|
|
461
425
|
!hasTransformDrivenGeometry;
|
|
462
426
|
|
|
463
427
|
const canResize = canMove && (width != null || height != null);
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
!hasTransformDrivenGeometry &&
|
|
472
|
-
isBlockishLayer &&
|
|
473
|
-
(!isInlineTextTag(args.tagName) || isClipClassName(args.className));
|
|
474
|
-
const reasonIfDisabled = !canMove
|
|
475
|
-
? canDetachFromLayout
|
|
476
|
-
? "This layer is controlled by layout."
|
|
477
|
-
: "Direct move/resize is limited to absolute or fixed elements with px geometry and no transform-driven layout."
|
|
478
|
-
: undefined;
|
|
428
|
+
const canApplyManualGeometry = !args.isCompositionHost;
|
|
429
|
+
const canApplyManualOffset = canApplyManualGeometry;
|
|
430
|
+
const canApplyManualSize = canApplyManualGeometry;
|
|
431
|
+
const canApplyManualRotation = canApplyManualGeometry;
|
|
432
|
+
const reasonIfDisabled = canApplyManualGeometry
|
|
433
|
+
? undefined
|
|
434
|
+
: "Select an internal layer to transform it.";
|
|
479
435
|
|
|
480
436
|
if (args.isCompositionHost && args.isMasterView) {
|
|
481
437
|
return {
|
|
@@ -483,7 +439,9 @@ export function resolveDomEditCapabilities(args: {
|
|
|
483
439
|
canEditStyles: false,
|
|
484
440
|
canMove,
|
|
485
441
|
canResize,
|
|
486
|
-
|
|
442
|
+
canApplyManualOffset,
|
|
443
|
+
canApplyManualSize,
|
|
444
|
+
canApplyManualRotation,
|
|
487
445
|
reasonIfDisabled,
|
|
488
446
|
};
|
|
489
447
|
}
|
|
@@ -493,7 +451,9 @@ export function resolveDomEditCapabilities(args: {
|
|
|
493
451
|
canEditStyles: true,
|
|
494
452
|
canMove,
|
|
495
453
|
canResize,
|
|
496
|
-
|
|
454
|
+
canApplyManualOffset,
|
|
455
|
+
canApplyManualSize,
|
|
456
|
+
canApplyManualRotation,
|
|
497
457
|
reasonIfDisabled,
|
|
498
458
|
};
|
|
499
459
|
}
|
|
@@ -585,6 +545,49 @@ export function refreshDomEditSelection(
|
|
|
585
545
|
: null;
|
|
586
546
|
}
|
|
587
547
|
|
|
548
|
+
export function getDomEditTargetKey(
|
|
549
|
+
selection: Pick<DomEditSelection, "id" | "selector" | "selectorIndex" | "sourceFile">,
|
|
550
|
+
): string {
|
|
551
|
+
return [
|
|
552
|
+
selection.sourceFile || "index.html",
|
|
553
|
+
selection.id ?? "",
|
|
554
|
+
selection.selector ?? "",
|
|
555
|
+
selection.selectorIndex ?? "",
|
|
556
|
+
].join("|");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function hasSupportedDirectEdit(capabilities: DomEditCapabilities): boolean {
|
|
560
|
+
return (
|
|
561
|
+
capabilities.canEditStyles ||
|
|
562
|
+
capabilities.canMove ||
|
|
563
|
+
capabilities.canResize ||
|
|
564
|
+
capabilities.canApplyManualOffset ||
|
|
565
|
+
capabilities.canApplyManualSize ||
|
|
566
|
+
capabilities.canApplyManualRotation
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export function getDomEditNonEditableReason(
|
|
571
|
+
element: HTMLElement,
|
|
572
|
+
selection: DomEditSelection | null,
|
|
573
|
+
): string | null {
|
|
574
|
+
if (!selection) {
|
|
575
|
+
return "No stable source target";
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (selection.element !== element) {
|
|
579
|
+
return selection.isCompositionHost
|
|
580
|
+
? "Nested composition boundary"
|
|
581
|
+
: `Selection resolves to ${selection.label}`;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (!hasSupportedDirectEdit(selection.capabilities)) {
|
|
585
|
+
return selection.capabilities.reasonIfDisabled ?? "No supported direct edits";
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
|
|
588
591
|
export function findElementForSelection(
|
|
589
592
|
doc: Document,
|
|
590
593
|
selection: Pick<DomEditSelection, "id" | "selector" | "selectorIndex" | "sourceFile">,
|
|
@@ -624,38 +627,6 @@ export function findElementForSelection(
|
|
|
624
627
|
return matches[0] ?? null;
|
|
625
628
|
}
|
|
626
629
|
|
|
627
|
-
export function buildDomEditMovePatchOperations(left: number, top: number): PatchOperation[] {
|
|
628
|
-
return [
|
|
629
|
-
{ type: "inline-style", property: "left", value: `${Math.round(left)}px` },
|
|
630
|
-
{ type: "inline-style", property: "top", value: `${Math.round(top)}px` },
|
|
631
|
-
];
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
export function buildDomEditResizePatchOperations(width: number, height: number): PatchOperation[] {
|
|
635
|
-
return [
|
|
636
|
-
{ type: "inline-style", property: "width", value: `${Math.round(width)}px` },
|
|
637
|
-
{ type: "inline-style", property: "height", value: `${Math.round(height)}px` },
|
|
638
|
-
];
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
export function buildDomEditDetachPatchOperations(rect: {
|
|
642
|
-
left: number;
|
|
643
|
-
top: number;
|
|
644
|
-
width: number;
|
|
645
|
-
height: number;
|
|
646
|
-
}): PatchOperation[] {
|
|
647
|
-
return [
|
|
648
|
-
{ type: "inline-style", property: "position", value: "absolute" },
|
|
649
|
-
{ type: "inline-style", property: "left", value: `${Math.round(rect.left)}px` },
|
|
650
|
-
{ type: "inline-style", property: "top", value: `${Math.round(rect.top)}px` },
|
|
651
|
-
{ type: "inline-style", property: "width", value: `${Math.round(rect.width)}px` },
|
|
652
|
-
{ type: "inline-style", property: "height", value: `${Math.round(rect.height)}px` },
|
|
653
|
-
{ type: "inline-style", property: "margin", value: "0" },
|
|
654
|
-
{ type: "inline-style", property: "right", value: "auto" },
|
|
655
|
-
{ type: "inline-style", property: "bottom", value: "auto" },
|
|
656
|
-
];
|
|
657
|
-
}
|
|
658
|
-
|
|
659
630
|
export function buildDomEditStylePatchOperation(property: string, value: string): PatchOperation {
|
|
660
631
|
return {
|
|
661
632
|
type: "inline-style",
|
|
@@ -687,7 +658,7 @@ function formatTextFields(fields: DomEditTextField[]): string {
|
|
|
687
658
|
return fields
|
|
688
659
|
.map(
|
|
689
660
|
(field) =>
|
|
690
|
-
`- key=${field.key}; tag=<${field.tagName}>; source=${field.source}; text
|
|
661
|
+
`- key=${field.key}; tag=<${field.tagName}>; source=${field.source}; text=${JSON.stringify(field.value)}`,
|
|
691
662
|
)
|
|
692
663
|
.join("\n");
|
|
693
664
|
}
|