@hyperframes/studio 0.6.73 → 0.6.75
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-DcyZuBcU.css +1 -0
- package/dist/assets/index-uB_W2GDl.js +140 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +30 -24
- package/src/components/StudioPreviewArea.tsx +101 -26
- package/src/components/StudioRightPanel.tsx +3 -0
- package/src/components/StudioToast.tsx +18 -0
- package/src/components/TimelineToolbar.tsx +230 -4
- package/src/components/editor/AnimationCard.tsx +68 -4
- package/src/components/editor/DomEditOverlay.tsx +70 -1
- package/src/components/editor/GridOverlay.tsx +50 -0
- package/src/components/editor/KeyframeDiamond.tsx +49 -0
- package/src/components/editor/KeyframeNavigation.tsx +139 -0
- package/src/components/editor/LayersPanel.test.ts +135 -0
- package/src/components/editor/LayersPanel.tsx +151 -15
- package/src/components/editor/PropertyPanel.tsx +293 -140
- package/src/components/editor/SnapGuideOverlay.tsx +166 -0
- package/src/components/editor/SnapToolbar.tsx +163 -0
- package/src/components/editor/SpringEaseEditor.tsx +256 -0
- package/src/components/editor/domEditOverlayGestures.ts +7 -0
- package/src/components/editor/domEditOverlayStartGesture.ts +28 -0
- package/src/components/editor/gsapAnimationConstants.ts +42 -0
- package/src/components/editor/gsapAnimationHelpers.ts +2 -1
- package/src/components/editor/manualEditingAvailability.ts +6 -0
- package/src/components/editor/manualEditsDom.ts +56 -2
- package/src/components/editor/manualOffsetDrag.ts +19 -3
- package/src/components/editor/propertyPanelHelpers.ts +90 -0
- package/src/components/editor/propertyPanelTimingSection.tsx +64 -0
- package/src/components/editor/snapEngine.test.ts +657 -0
- package/src/components/editor/snapEngine.ts +575 -0
- package/src/components/editor/snapTargetCollection.ts +147 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +137 -10
- package/src/components/editor/useLayerDrag.ts +213 -0
- package/src/components/nle/NLELayout.tsx +18 -0
- package/src/contexts/DomEditContext.tsx +27 -0
- package/src/hooks/gsapRuntimeBridge.ts +585 -0
- package/src/hooks/gsapRuntimeKeyframes.ts +170 -0
- package/src/hooks/useAnimatedPropertyCommit.ts +131 -0
- package/src/hooks/useAppHotkeys.ts +63 -1
- package/src/hooks/useDomEditCommits.ts +88 -4
- package/src/hooks/useDomEditSession.ts +179 -65
- package/src/hooks/useGsapScriptCommits.ts +144 -7
- package/src/hooks/useGsapSelectionHandlers.ts +202 -0
- package/src/hooks/useGsapTweenCache.ts +174 -3
- package/src/hooks/useTimelineEditing.ts +93 -0
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/player/components/ClipContextMenu.tsx +99 -0
- package/src/player/components/KeyframeDiamondContextMenu.tsx +164 -0
- package/src/player/components/Timeline.test.ts +2 -1
- package/src/player/components/Timeline.tsx +108 -68
- package/src/player/components/TimelineCanvas.tsx +47 -1
- package/src/player/components/TimelineClip.tsx +8 -3
- package/src/player/components/TimelineClipDiamonds.tsx +174 -0
- package/src/player/components/timelineDragDrop.ts +103 -0
- package/src/player/components/timelineLayout.ts +1 -1
- package/src/player/store/playerStore.ts +42 -0
- package/src/utils/editHistory.ts +1 -1
- package/src/utils/optimisticUpdate.test.ts +53 -0
- package/src/utils/optimisticUpdate.ts +18 -0
- package/src/utils/studioUiPreferences.ts +17 -0
- package/dist/assets/index-CrxThtSJ.css +0 -1
- package/dist/assets/index-Dc2HfqON.js +0 -140
|
@@ -236,9 +236,25 @@ export function createManualOffsetDragMember(input: {
|
|
|
236
236
|
const gestureToken = beginStudioManualEditGesture(input.element);
|
|
237
237
|
const measured = measureManualOffsetDragScreenToOffsetMatrix(input.element, initialOffset);
|
|
238
238
|
if (!measured.ok) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
// Fallback: when GSAP transforms interfere with probe measurement, use
|
|
240
|
+
// the preview scale as an approximation. The commit path reads the actual
|
|
241
|
+
// GSAP position from the iframe runtime, so visual imprecision during
|
|
242
|
+
// drag is acceptable — the final committed position is always exact.
|
|
243
|
+
const scaleX = input.rect.editScaleX || 1;
|
|
244
|
+
const scaleY = input.rect.editScaleY || 1;
|
|
245
|
+
return {
|
|
246
|
+
ok: true,
|
|
247
|
+
member: {
|
|
248
|
+
key: input.key,
|
|
249
|
+
selection: input.selection,
|
|
250
|
+
element: input.element,
|
|
251
|
+
initialOffset,
|
|
252
|
+
initialPathOffset,
|
|
253
|
+
gestureToken,
|
|
254
|
+
screenToOffset: { a: 1 / scaleX, b: 0, c: 0, d: 1 / scaleY },
|
|
255
|
+
originRect: input.rect,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
242
258
|
}
|
|
243
259
|
|
|
244
260
|
return {
|
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
import { parseCssColor, type ParsedColor } from "./colorValue";
|
|
2
2
|
import { COMMON_LOCAL_FONT_FAMILIES } from "./fontCatalog";
|
|
3
|
+
import type { DomEditSelection } from "./domEditing";
|
|
4
|
+
import type { ImportedFontAsset } from "./fontAssets";
|
|
5
|
+
|
|
6
|
+
export interface PropertyPanelProps {
|
|
7
|
+
projectId: string;
|
|
8
|
+
projectDir: string | null;
|
|
9
|
+
assets: string[];
|
|
10
|
+
element: DomEditSelection | null;
|
|
11
|
+
multiSelectCount?: number;
|
|
12
|
+
copiedAgentPrompt: boolean;
|
|
13
|
+
onClearSelection: () => void;
|
|
14
|
+
onSetStyle: (prop: string, value: string) => void | Promise<void>;
|
|
15
|
+
onSetAttribute: (attr: string, value: string) => void | Promise<void>;
|
|
16
|
+
onSetHtmlAttribute: (attr: string, value: string | null) => void | Promise<void>;
|
|
17
|
+
onSetManualOffset: (element: DomEditSelection, next: { x: number; y: number }) => void;
|
|
18
|
+
onSetManualSize: (element: DomEditSelection, next: { width: number; height: number }) => void;
|
|
19
|
+
onSetManualRotation: (element: DomEditSelection, next: { angle: number }) => void;
|
|
20
|
+
onSetText: (value: string, fieldKey?: string) => void;
|
|
21
|
+
onSetTextFieldStyle: (fieldKey: string, property: string, value: string) => void;
|
|
22
|
+
onAddTextField: (afterFieldKey?: string) => string | Promise<string | null> | null;
|
|
23
|
+
onRemoveTextField: (fieldKey: string) => void;
|
|
24
|
+
onAskAgent: () => void;
|
|
25
|
+
onImportAssets?: (files: FileList) => Promise<string[]>;
|
|
26
|
+
fontAssets?: ImportedFontAsset[];
|
|
27
|
+
onImportFonts?: (files: FileList | File[]) => Promise<ImportedFontAsset[]>;
|
|
28
|
+
previewIframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
|
29
|
+
gsapAnimations?: import("@hyperframes/core/gsap-parser").GsapAnimation[];
|
|
30
|
+
gsapMultipleTimelines?: boolean;
|
|
31
|
+
gsapUnsupportedTimelinePattern?: boolean;
|
|
32
|
+
onUpdateGsapProperty?: (animId: string, prop: string, value: number | string) => void;
|
|
33
|
+
onUpdateGsapMeta?: (
|
|
34
|
+
animId: string,
|
|
35
|
+
updates: { duration?: number; ease?: string; position?: number },
|
|
36
|
+
) => void;
|
|
37
|
+
onDeleteGsapAnimation?: (animId: string) => void;
|
|
38
|
+
onAddGsapProperty?: (animId: string, prop: string) => void;
|
|
39
|
+
onRemoveGsapProperty?: (animId: string, prop: string) => void;
|
|
40
|
+
onUpdateGsapFromProperty?: (animId: string, prop: string, value: number | string) => void;
|
|
41
|
+
onAddGsapFromProperty?: (animId: string, prop: string) => void;
|
|
42
|
+
onRemoveGsapFromProperty?: (animId: string, prop: string) => void;
|
|
43
|
+
onAddGsapAnimation?: (method: "to" | "from" | "set" | "fromTo") => void;
|
|
44
|
+
onAddKeyframe?: (
|
|
45
|
+
animationId: string,
|
|
46
|
+
percentage: number,
|
|
47
|
+
property: string,
|
|
48
|
+
value: number | string,
|
|
49
|
+
) => void;
|
|
50
|
+
onRemoveKeyframe?: (animationId: string, percentage: number) => void;
|
|
51
|
+
onConvertToKeyframes?: (animationId: string) => void;
|
|
52
|
+
onCommitAnimatedProperty?: (
|
|
53
|
+
selection: DomEditSelection,
|
|
54
|
+
property: string,
|
|
55
|
+
value: number | string,
|
|
56
|
+
) => Promise<void>;
|
|
57
|
+
onSeekToTime?: (time: number) => void;
|
|
58
|
+
}
|
|
3
59
|
|
|
4
60
|
/* ------------------------------------------------------------------ */
|
|
5
61
|
/* Font types & constants (shared by font and section modules) */
|
|
@@ -399,3 +455,37 @@ export function extractBackgroundImageUrl(value: string | undefined): string {
|
|
|
399
455
|
if (endParen < index) return "";
|
|
400
456
|
return value.slice(index, endParen).trim();
|
|
401
457
|
}
|
|
458
|
+
|
|
459
|
+
// ── Fit to children ──────────────────────────────────────────────────
|
|
460
|
+
|
|
461
|
+
export function computeFitToChildrenSize(
|
|
462
|
+
element: DomEditSelection,
|
|
463
|
+
): { width: number; height: number } | null {
|
|
464
|
+
const el = element.element;
|
|
465
|
+
const win = el.ownerDocument?.defaultView;
|
|
466
|
+
const children = Array.from(el.children).filter((c): c is HTMLElement => c.nodeType === 1);
|
|
467
|
+
if (children.length === 0) return null;
|
|
468
|
+
let minX = Infinity,
|
|
469
|
+
minY = Infinity,
|
|
470
|
+
maxX = -Infinity,
|
|
471
|
+
maxY = -Infinity;
|
|
472
|
+
for (const child of children) {
|
|
473
|
+
if (win) {
|
|
474
|
+
const cs = win.getComputedStyle(child);
|
|
475
|
+
if (cs.visibility === "hidden" || cs.display === "none") continue;
|
|
476
|
+
}
|
|
477
|
+
const r = child.getBoundingClientRect();
|
|
478
|
+
if (r.width === 0 && r.height === 0) continue;
|
|
479
|
+
minX = Math.min(minX, r.left);
|
|
480
|
+
minY = Math.min(minY, r.top);
|
|
481
|
+
maxX = Math.max(maxX, r.right);
|
|
482
|
+
maxY = Math.max(maxY, r.bottom);
|
|
483
|
+
}
|
|
484
|
+
if (!isFinite(minX)) return null;
|
|
485
|
+
const parentRect = el.getBoundingClientRect();
|
|
486
|
+
const scaleX = parentRect.width > 0 ? element.boundingBox.width / parentRect.width : 1;
|
|
487
|
+
const scaleY = parentRect.height > 0 ? element.boundingBox.height / parentRect.height : 1;
|
|
488
|
+
const width = Math.round((maxX - minX) * scaleX);
|
|
489
|
+
const height = Math.round((maxY - minY) * scaleY);
|
|
490
|
+
return width > 0 && height > 0 ? { width, height } : null;
|
|
491
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Clock } from "../../icons/SystemIcons";
|
|
2
|
+
import type { DomEditSelection } from "./domEditing";
|
|
3
|
+
import { RESPONSIVE_GRID } from "./propertyPanelHelpers";
|
|
4
|
+
import { MetricField, Section } from "./propertyPanelPrimitives";
|
|
5
|
+
|
|
6
|
+
function formatTimingValue(seconds: number): string {
|
|
7
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "0.00s";
|
|
8
|
+
return `${seconds.toFixed(2)}s`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseTimingValue(input: string): number | null {
|
|
12
|
+
const cleaned = input.replace(/s$/i, "").trim();
|
|
13
|
+
const parsed = Number.parseFloat(cleaned);
|
|
14
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function TimingSection({
|
|
18
|
+
element,
|
|
19
|
+
onSetAttribute,
|
|
20
|
+
}: {
|
|
21
|
+
element: DomEditSelection;
|
|
22
|
+
onSetAttribute: (attr: string, value: string) => void | Promise<void>;
|
|
23
|
+
}) {
|
|
24
|
+
const start = Number.parseFloat(element.dataAttributes.start ?? "0") || 0;
|
|
25
|
+
const duration =
|
|
26
|
+
Number.parseFloat(
|
|
27
|
+
element.dataAttributes.duration ?? element.dataAttributes["hf-authored-duration"] ?? "0",
|
|
28
|
+
) || 0;
|
|
29
|
+
const end = start + duration;
|
|
30
|
+
|
|
31
|
+
const commitStart = (nextValue: string) => {
|
|
32
|
+
const parsed = parseTimingValue(nextValue);
|
|
33
|
+
if (parsed == null) return;
|
|
34
|
+
void onSetAttribute("start", parsed.toFixed(2));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const commitDuration = (nextValue: string) => {
|
|
38
|
+
const parsed = parseTimingValue(nextValue);
|
|
39
|
+
if (parsed == null || parsed <= 0) return;
|
|
40
|
+
void onSetAttribute("duration", parsed.toFixed(2));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const commitEnd = (nextValue: string) => {
|
|
44
|
+
const parsed = parseTimingValue(nextValue);
|
|
45
|
+
if (parsed == null || parsed <= start) return;
|
|
46
|
+
void onSetAttribute("duration", (parsed - start).toFixed(2));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Section title="Timing" icon={<Clock size={15} />}>
|
|
51
|
+
<div className={RESPONSIVE_GRID}>
|
|
52
|
+
<MetricField label="Start" value={formatTimingValue(start)} onCommit={commitStart} />
|
|
53
|
+
<MetricField label="End" value={formatTimingValue(end)} onCommit={commitEnd} />
|
|
54
|
+
</div>
|
|
55
|
+
<div className="mt-3">
|
|
56
|
+
<MetricField
|
|
57
|
+
label="Duration"
|
|
58
|
+
value={formatTimingValue(duration)}
|
|
59
|
+
onCommit={commitDuration}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
</Section>
|
|
63
|
+
);
|
|
64
|
+
}
|