@hyperframes/studio 0.6.0-alpha.9 → 0.6.1

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.
Files changed (111) hide show
  1. package/dist/assets/hyperframes-player-CzwFysqv.js +418 -0
  2. package/dist/assets/index-D1JDq7Gg.css +1 -0
  3. package/dist/assets/index-hYc4aP7M.js +117 -0
  4. package/dist/favicon.svg +14 -0
  5. package/dist/index.html +3 -2
  6. package/package.json +9 -9
  7. package/src/App.tsx +421 -4303
  8. package/src/captions/components/CaptionOverlay.tsx +13 -246
  9. package/src/captions/components/CaptionOverlayUtils.ts +221 -0
  10. package/src/components/AskAgentModal.tsx +120 -0
  11. package/src/components/StudioHeader.tsx +133 -0
  12. package/src/components/StudioLeftSidebar.tsx +125 -0
  13. package/src/components/StudioPreviewArea.tsx +167 -0
  14. package/src/components/StudioRightPanel.tsx +198 -0
  15. package/src/components/TimelineToolbar.tsx +89 -0
  16. package/src/components/editor/DomEditOverlay.tsx +88 -993
  17. package/src/components/editor/EaseCurveEditor.tsx +221 -0
  18. package/src/components/editor/FileTree.tsx +13 -621
  19. package/src/components/editor/FileTreeIcons.tsx +128 -0
  20. package/src/components/editor/FileTreeNodes.tsx +496 -0
  21. package/src/components/editor/MotionPanel.tsx +16 -390
  22. package/src/components/editor/MotionPanelFields.tsx +185 -0
  23. package/src/components/editor/PropertyPanel.test.ts +0 -49
  24. package/src/components/editor/PropertyPanel.tsx +132 -2763
  25. package/src/components/editor/domEditOverlayGeometry.ts +211 -0
  26. package/src/components/editor/domEditOverlayGestures.ts +138 -0
  27. package/src/components/editor/domEditOverlayStartGesture.ts +155 -0
  28. package/src/components/editor/domEditing.ts +44 -1117
  29. package/src/components/editor/domEditingAgentPrompt.ts +97 -0
  30. package/src/components/editor/domEditingDom.ts +266 -0
  31. package/src/components/editor/domEditingElement.ts +329 -0
  32. package/src/components/editor/domEditingLayers.ts +460 -0
  33. package/src/components/editor/domEditingTypes.ts +125 -0
  34. package/src/components/editor/manualEditingAvailability.test.ts +2 -2
  35. package/src/components/editor/manualEditingAvailability.ts +1 -1
  36. package/src/components/editor/manualEdits.ts +84 -1049
  37. package/src/components/editor/manualEditsDom.ts +436 -0
  38. package/src/components/editor/manualEditsParsing.ts +280 -0
  39. package/src/components/editor/manualEditsSnapshot.ts +333 -0
  40. package/src/components/editor/manualEditsTypes.ts +141 -0
  41. package/src/components/editor/propertyPanelColor.tsx +371 -0
  42. package/src/components/editor/propertyPanelFill.tsx +421 -0
  43. package/src/components/editor/propertyPanelFont.tsx +455 -0
  44. package/src/components/editor/propertyPanelHelpers.ts +401 -0
  45. package/src/components/editor/propertyPanelPrimitives.tsx +357 -0
  46. package/src/components/editor/propertyPanelSections.tsx +453 -0
  47. package/src/components/editor/propertyPanelStyleSections.tsx +411 -0
  48. package/src/components/editor/studioMotion.ts +47 -434
  49. package/src/components/editor/studioMotionOps.ts +299 -0
  50. package/src/components/editor/studioMotionTypes.ts +168 -0
  51. package/src/components/editor/useDomEditOverlayGestures.ts +393 -0
  52. package/src/components/editor/useDomEditOverlayRects.ts +207 -0
  53. package/src/components/nle/NLELayout.tsx +68 -155
  54. package/src/components/nle/NLEPreview.tsx +3 -0
  55. package/src/components/nle/useCompositionStack.ts +126 -0
  56. package/src/components/renders/RenderQueue.tsx +102 -31
  57. package/src/components/renders/useRenderQueue.ts +8 -2
  58. package/src/components/sidebar/LeftSidebar.tsx +186 -186
  59. package/src/contexts/DomEditContext.tsx +137 -0
  60. package/src/contexts/FileManagerContext.tsx +110 -0
  61. package/src/contexts/PanelLayoutContext.tsx +68 -0
  62. package/src/contexts/StudioContext.tsx +135 -0
  63. package/src/hooks/useAppHotkeys.ts +326 -0
  64. package/src/hooks/useAskAgentModal.ts +162 -0
  65. package/src/hooks/useCaptionDetection.ts +132 -0
  66. package/src/hooks/useCompositionDimensions.ts +25 -0
  67. package/src/hooks/useConsoleErrorCapture.ts +60 -0
  68. package/src/hooks/useDomEditCommits.ts +437 -0
  69. package/src/hooks/useDomEditSession.ts +342 -0
  70. package/src/hooks/useDomEditTextCommits.ts +330 -0
  71. package/src/hooks/useDomSelection.ts +398 -0
  72. package/src/hooks/useFileManager.ts +431 -0
  73. package/src/hooks/useFrameCapture.ts +77 -0
  74. package/src/hooks/useLintModal.ts +35 -0
  75. package/src/hooks/useManifestPersistence.ts +492 -0
  76. package/src/hooks/usePanelLayout.ts +68 -0
  77. package/src/hooks/usePreviewInteraction.ts +153 -0
  78. package/src/hooks/useRenderClipContent.ts +124 -0
  79. package/src/hooks/useTimelineEditing.ts +472 -0
  80. package/src/hooks/useToast.ts +20 -0
  81. package/src/player/components/Player.tsx +33 -2
  82. package/src/player/components/Timeline.test.ts +0 -8
  83. package/src/player/components/Timeline.tsx +196 -1518
  84. package/src/player/components/TimelineCanvas.tsx +434 -0
  85. package/src/player/components/TimelineClip.tsx +9 -244
  86. package/src/player/components/TimelineEmptyState.tsx +102 -0
  87. package/src/player/components/TimelineRuler.tsx +90 -0
  88. package/src/player/components/timelineIcons.tsx +49 -0
  89. package/src/player/components/timelineLayout.ts +215 -0
  90. package/src/player/components/timelineUtils.ts +211 -0
  91. package/src/player/components/useTimelineClipDrag.ts +388 -0
  92. package/src/player/components/useTimelinePlayhead.ts +200 -0
  93. package/src/player/components/useTimelineRangeSelection.ts +135 -0
  94. package/src/player/hooks/usePlaybackKeyboard.ts +171 -0
  95. package/src/player/hooks/useTimelinePlayer.ts +105 -1371
  96. package/src/player/hooks/useTimelineSyncCallbacks.ts +288 -0
  97. package/src/player/lib/playbackAdapter.ts +145 -0
  98. package/src/player/lib/playbackShortcuts.ts +68 -0
  99. package/src/player/lib/playbackTypes.ts +60 -0
  100. package/src/player/lib/timelineDOM.ts +373 -0
  101. package/src/player/lib/timelineElementHelpers.ts +303 -0
  102. package/src/player/lib/timelineIframeHelpers.ts +269 -0
  103. package/src/utils/domEditHelpers.ts +50 -0
  104. package/src/utils/studioFontHelpers.ts +83 -0
  105. package/src/utils/studioHelpers.ts +214 -0
  106. package/src/utils/studioPreviewHelpers.ts +185 -0
  107. package/src/utils/timelineDiscovery.ts +1 -1
  108. package/dist/assets/hyperframes-player-DjsVzYFP.js +0 -418
  109. package/dist/assets/index-14zH9lqh.css +0 -1
  110. package/dist/assets/index-DYCiFGWQ.js +0 -108
  111. package/src/player/components/TimelineClip.test.ts +0 -92
@@ -0,0 +1,411 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Eye, Layers, Palette, Settings, Square, Zap } from "../../icons/SystemIcons";
3
+ import { buildDefaultGradientModel, serializeGradient } from "./gradientValue";
4
+ import { isTextEditableSelection, type DomEditSelection } from "./domEditing";
5
+ import {
6
+ buildBoxShadowPresetValue,
7
+ buildClipPathValue,
8
+ buildInsetClipPathValue,
9
+ buildStrokeStyleUpdates,
10
+ buildStrokeWidthStyleUpdates,
11
+ extractBackgroundImageUrl,
12
+ formatNumericValue,
13
+ formatPxMetricValue,
14
+ getCssFilterFunctionPx,
15
+ getClipPathInsetPx,
16
+ inferBoxShadowPreset,
17
+ inferClipPathPreset,
18
+ LABEL,
19
+ normalizePanelPxValue,
20
+ parseNumericValue,
21
+ parsePxMetricValue,
22
+ RESPONSIVE_GRID,
23
+ setCssFilterFunctionPx,
24
+ type BoxShadowPreset,
25
+ } from "./propertyPanelHelpers";
26
+ import {
27
+ DetailField,
28
+ MetricField,
29
+ Section,
30
+ SegmentedControl,
31
+ SelectField,
32
+ SliderControl,
33
+ } from "./propertyPanelPrimitives";
34
+ import { ColorField } from "./propertyPanelColor";
35
+ import { GradientField, ImageFillField } from "./propertyPanelFill";
36
+
37
+ export function StyleSections({
38
+ projectId,
39
+ element,
40
+ styles,
41
+ assets,
42
+ onSetStyle,
43
+ onImportAssets,
44
+ }: {
45
+ projectId: string;
46
+ element: DomEditSelection;
47
+ styles: Record<string, string>;
48
+ assets: string[];
49
+ onSetStyle: (prop: string, value: string) => void | Promise<void>;
50
+ onImportAssets?: (files: FileList) => Promise<string[]>;
51
+ }) {
52
+ const styleEditingDisabled = !element.capabilities.canEditStyles;
53
+ const isFlex = styles.display === "flex" || styles.display === "inline-flex";
54
+ const radiusValue = parseNumericValue(styles["border-radius"]) ?? 0;
55
+ const opacityValue = Math.round((parseNumericValue(styles.opacity) ?? 1) * 100);
56
+ const borderWidthValue =
57
+ parsePxMetricValue(styles["border-width"] ?? "") ??
58
+ parsePxMetricValue(styles["border-top-width"] ?? "") ??
59
+ 0;
60
+ const hasVisualBackground =
61
+ (styles.background != null && styles.background !== "none" && styles.background !== "") ||
62
+ (styles["background-color"] != null &&
63
+ styles["background-color"] !== "transparent" &&
64
+ styles["background-color"] !== "rgba(0, 0, 0, 0)" &&
65
+ styles["background-color"] !== "") ||
66
+ (styles["background-image"] != null &&
67
+ styles["background-image"] !== "none" &&
68
+ styles["background-image"] !== "") ||
69
+ borderWidthValue > 0;
70
+ const borderStyleValue = styles["border-style"] || styles["border-top-style"] || "none";
71
+ const borderColorValue =
72
+ styles["border-color"] || styles["border-top-color"] || "rgba(255, 255, 255, 0.18)";
73
+ const boxShadowPreset = inferBoxShadowPreset(styles["box-shadow"]);
74
+ const filterBlurValue = getCssFilterFunctionPx(styles.filter, "blur");
75
+ const backdropBlurValue = getCssFilterFunctionPx(styles["backdrop-filter"], "blur");
76
+ const clipPathValue = styles["clip-path"] || "none";
77
+ const clipPathPreset = inferClipPathPreset(clipPathValue);
78
+ const clipInsetValue = getClipPathInsetPx(clipPathValue);
79
+ const backgroundImage = styles["background-image"] ?? "none";
80
+ const hasTextControls = isTextEditableSelection(element);
81
+
82
+ const fillMode =
83
+ backgroundImage && backgroundImage !== "none"
84
+ ? backgroundImage.includes("gradient")
85
+ ? "Gradient"
86
+ : "Image"
87
+ : "Solid";
88
+ const [preferredFillMode, setPreferredFillMode] = useState(fillMode);
89
+ const imageUrl = extractBackgroundImageUrl(backgroundImage);
90
+
91
+ useEffect(() => {
92
+ setPreferredFillMode(fillMode);
93
+ }, [fillMode, element.id, element.selector, backgroundImage]);
94
+
95
+ const handleFillModeChange = (nextMode: string) => {
96
+ setPreferredFillMode(nextMode);
97
+ if (nextMode === "Solid") {
98
+ onSetStyle("background-image", "none");
99
+ return;
100
+ }
101
+ if (nextMode === "Gradient" && !backgroundImage.includes("gradient")) {
102
+ onSetStyle(
103
+ "background-image",
104
+ serializeGradient(buildDefaultGradientModel(styles["background-color"])),
105
+ );
106
+ }
107
+ };
108
+
109
+ return (
110
+ <>
111
+ {isFlex && (
112
+ <Section title="Flex" icon={<Layers size={15} />}>
113
+ <div className="space-y-4">
114
+ <SegmentedControl
115
+ disabled={styleEditingDisabled}
116
+ value={styles["flex-direction"] || "row"}
117
+ onChange={(next) => onSetStyle("flex-direction", next)}
118
+ options={[
119
+ { label: "→ Row", value: "row" },
120
+ { label: "↓ Column", value: "column" },
121
+ ]}
122
+ />
123
+ <div className={RESPONSIVE_GRID}>
124
+ <SelectField
125
+ label="Justify"
126
+ value={styles["justify-content"] || "flex-start"}
127
+ disabled={styleEditingDisabled}
128
+ onChange={(next) => onSetStyle("justify-content", next)}
129
+ options={[
130
+ "flex-start",
131
+ "center",
132
+ "space-between",
133
+ "space-around",
134
+ "space-evenly",
135
+ "flex-end",
136
+ ]}
137
+ />
138
+ <SelectField
139
+ label="Align"
140
+ value={styles["align-items"] || "stretch"}
141
+ disabled={styleEditingDisabled}
142
+ onChange={(next) => onSetStyle("align-items", next)}
143
+ options={["stretch", "flex-start", "center", "flex-end", "baseline"]}
144
+ />
145
+ </div>
146
+ <DetailField
147
+ label="Gap"
148
+ value={styles.gap ?? "0px"}
149
+ disabled={styleEditingDisabled}
150
+ onCommit={(next) => onSetStyle("gap", next.endsWith("px") ? next : `${next}px`)}
151
+ />
152
+ </div>
153
+ </Section>
154
+ )}
155
+
156
+ {hasVisualBackground && (
157
+ <Section title="Radius" icon={<Settings size={15} />}>
158
+ <SliderControl
159
+ value={radiusValue}
160
+ min={0}
161
+ max={Math.max(240, Math.ceil(radiusValue))}
162
+ step={1}
163
+ disabled={styleEditingDisabled}
164
+ displayValue={`${formatNumericValue(radiusValue)}px`}
165
+ formatDisplayValue={(next) => `${formatNumericValue(next)}px`}
166
+ onCommit={(next) => onSetStyle("border-radius", `${formatNumericValue(next)}px`)}
167
+ />
168
+ </Section>
169
+ )}
170
+
171
+ <Section title="Stroke" icon={<Square size={15} />}>
172
+ <div className="space-y-4">
173
+ <div className={RESPONSIVE_GRID}>
174
+ <MetricField
175
+ label="Width"
176
+ value={formatPxMetricValue(borderWidthValue)}
177
+ disabled={styleEditingDisabled}
178
+ liveCommit
179
+ onCommit={async (next) => {
180
+ const normalized = normalizePanelPxValue(next, {
181
+ min: 0,
182
+ max: 200,
183
+ fallback: borderWidthValue,
184
+ });
185
+ if (!normalized) return;
186
+ for (const [property, value] of buildStrokeWidthStyleUpdates(
187
+ normalized,
188
+ borderStyleValue,
189
+ )) {
190
+ await onSetStyle(property, value);
191
+ }
192
+ }}
193
+ />
194
+ <SelectField
195
+ label="Style"
196
+ value={borderStyleValue}
197
+ disabled={styleEditingDisabled}
198
+ onChange={async (next) => {
199
+ for (const [property, value] of buildStrokeStyleUpdates(
200
+ next,
201
+ formatPxMetricValue(borderWidthValue),
202
+ )) {
203
+ await onSetStyle(property, value);
204
+ }
205
+ }}
206
+ options={[
207
+ "none",
208
+ "solid",
209
+ "dashed",
210
+ "dotted",
211
+ "double",
212
+ "hidden",
213
+ "groove",
214
+ "ridge",
215
+ "inset",
216
+ "outset",
217
+ ]}
218
+ />
219
+ </div>
220
+ <ColorField
221
+ label="Stroke color"
222
+ value={borderColorValue}
223
+ disabled={styleEditingDisabled}
224
+ onCommit={(next) => onSetStyle("border-color", next)}
225
+ />
226
+ </div>
227
+ </Section>
228
+
229
+ <Section title="Effects" icon={<Zap size={15} />}>
230
+ <div className="space-y-4">
231
+ <SelectField
232
+ label="Shadow"
233
+ value={boxShadowPreset}
234
+ disabled={styleEditingDisabled}
235
+ onChange={(next) => {
236
+ if (next === "custom") return;
237
+ onSetStyle(
238
+ "box-shadow",
239
+ buildBoxShadowPresetValue(next as BoxShadowPreset, styles["box-shadow"]),
240
+ );
241
+ }}
242
+ options={["custom", "none", "soft", "lift", "glow"]}
243
+ />
244
+ <div className={RESPONSIVE_GRID}>
245
+ <div className="grid min-w-0 gap-1.5">
246
+ <span className={LABEL}>Layer blur</span>
247
+ <SliderControl
248
+ value={filterBlurValue}
249
+ min={0}
250
+ max={Math.max(40, Math.ceil(filterBlurValue))}
251
+ step={1}
252
+ disabled={styleEditingDisabled}
253
+ displayValue={`${formatNumericValue(filterBlurValue)}px`}
254
+ formatDisplayValue={(next) => `${formatNumericValue(next)}px`}
255
+ onCommit={(next) =>
256
+ onSetStyle("filter", setCssFilterFunctionPx(styles.filter, "blur", next))
257
+ }
258
+ />
259
+ </div>
260
+ <div className="grid min-w-0 gap-1.5">
261
+ <span className={LABEL}>Backdrop</span>
262
+ <SliderControl
263
+ value={backdropBlurValue}
264
+ min={0}
265
+ max={Math.max(60, Math.ceil(backdropBlurValue))}
266
+ step={1}
267
+ disabled={styleEditingDisabled}
268
+ displayValue={`${formatNumericValue(backdropBlurValue)}px`}
269
+ formatDisplayValue={(next) => `${formatNumericValue(next)}px`}
270
+ onCommit={(next) =>
271
+ onSetStyle(
272
+ "backdrop-filter",
273
+ setCssFilterFunctionPx(styles["backdrop-filter"], "blur", next),
274
+ )
275
+ }
276
+ />
277
+ </div>
278
+ </div>
279
+ </div>
280
+ </Section>
281
+
282
+ <Section title="Clip" icon={<Layers size={15} />}>
283
+ <div className="space-y-4">
284
+ <div className={RESPONSIVE_GRID}>
285
+ <SelectField
286
+ label="Overflow"
287
+ value={styles.overflow || "visible"}
288
+ disabled={styleEditingDisabled}
289
+ onChange={(next) => onSetStyle("overflow", next)}
290
+ options={["visible", "hidden", "clip", "auto", "scroll"]}
291
+ />
292
+ <SelectField
293
+ label="Mask"
294
+ value={clipPathPreset}
295
+ disabled={styleEditingDisabled}
296
+ onChange={(next) => {
297
+ if (next === "custom") return;
298
+ onSetStyle(
299
+ "clip-path",
300
+ buildClipPathValue(
301
+ next as "none" | "inset" | "circle",
302
+ radiusValue,
303
+ clipPathValue,
304
+ ),
305
+ );
306
+ }}
307
+ options={["custom", "none", "inset", "circle"]}
308
+ />
309
+ </div>
310
+ <div className="grid min-w-0 gap-1.5">
311
+ <span className={LABEL}>Mask inset</span>
312
+ <SliderControl
313
+ value={clipInsetValue}
314
+ min={0}
315
+ max={Math.max(120, Math.ceil(clipInsetValue))}
316
+ step={1}
317
+ disabled={styleEditingDisabled}
318
+ displayValue={`${formatNumericValue(clipInsetValue)}px`}
319
+ formatDisplayValue={(next) => `${formatNumericValue(next)}px`}
320
+ onCommit={(next) =>
321
+ onSetStyle("clip-path", buildInsetClipPathValue(next, radiusValue))
322
+ }
323
+ />
324
+ </div>
325
+ </div>
326
+ </Section>
327
+
328
+ <Section title="Transparency" icon={<Eye size={15} />}>
329
+ <div className="space-y-4">
330
+ <SliderControl
331
+ value={opacityValue}
332
+ min={0}
333
+ max={100}
334
+ step={1}
335
+ disabled={styleEditingDisabled}
336
+ displayValue={`${opacityValue}%`}
337
+ formatDisplayValue={(next) => `${Math.round(next)}%`}
338
+ onCommit={(next) => onSetStyle("opacity", formatNumericValue(next / 100))}
339
+ />
340
+ <SelectField
341
+ label="Mode"
342
+ value={styles["mix-blend-mode"] || "normal"}
343
+ disabled={styleEditingDisabled}
344
+ onChange={(next) => onSetStyle("mix-blend-mode", next)}
345
+ options={["normal", "multiply", "screen", "overlay", "darken", "lighten"]}
346
+ />
347
+ </div>
348
+ </Section>
349
+
350
+ <Section
351
+ title="Fill"
352
+ icon={<Palette size={15} />}
353
+ accessory={
354
+ <div className="rounded-full border border-neutral-700 bg-neutral-900 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.16em] text-neutral-400">
355
+ {preferredFillMode}
356
+ </div>
357
+ }
358
+ >
359
+ <div className="space-y-4">
360
+ <SegmentedControl
361
+ disabled={styleEditingDisabled}
362
+ value={preferredFillMode}
363
+ onChange={handleFillModeChange}
364
+ options={[
365
+ { label: "Solid", value: "Solid" },
366
+ { label: "Gradient", value: "Gradient" },
367
+ { label: "Image", value: "Image" },
368
+ ]}
369
+ />
370
+ {preferredFillMode === "Solid" ? (
371
+ <ColorField
372
+ label="Fill color"
373
+ value={styles["background-color"] ?? "transparent"}
374
+ disabled={styleEditingDisabled}
375
+ onCommit={(next) => onSetStyle("background-color", next)}
376
+ />
377
+ ) : preferredFillMode === "Gradient" ? (
378
+ <GradientField
379
+ value={
380
+ backgroundImage !== "none"
381
+ ? backgroundImage
382
+ : serializeGradient(buildDefaultGradientModel(styles["background-color"]))
383
+ }
384
+ fallbackColor={styles["background-color"]}
385
+ disabled={styleEditingDisabled}
386
+ onCommit={(next) => onSetStyle("background-image", next)}
387
+ />
388
+ ) : (
389
+ <ImageFillField
390
+ projectId={projectId}
391
+ sourceFile={element.sourceFile}
392
+ value={imageUrl}
393
+ assets={assets}
394
+ disabled={styleEditingDisabled}
395
+ onCommit={(next) => onSetStyle("background-image", next)}
396
+ onImportAssets={onImportAssets}
397
+ />
398
+ )}
399
+ {!hasTextControls && (
400
+ <ColorField
401
+ label="Text color"
402
+ value={styles.color ?? "rgb(0, 0, 0)"}
403
+ disabled={styleEditingDisabled}
404
+ onCommit={(next) => onSetStyle("color", next)}
405
+ />
406
+ )}
407
+ </div>
408
+ </Section>
409
+ </>
410
+ );
411
+ }