@hyperframes/studio 0.6.112 → 0.6.114

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/index.html CHANGED
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-C9b6Siu5.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-DP8pPIk2.css">
8
+ <script type="module" crossorigin src="/assets/index-DApfviyW.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-DS7Kfzvb.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.6.112",
3
+ "version": "0.6.114",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,9 +36,9 @@
36
36
  "dompurify": "^3.2.4",
37
37
  "marked": "^14.1.4",
38
38
  "mediabunny": "^1.45.3",
39
- "@hyperframes/sdk": "0.6.112",
40
- "@hyperframes/player": "0.6.112",
41
- "@hyperframes/core": "0.6.112"
39
+ "@hyperframes/core": "0.6.114",
40
+ "@hyperframes/player": "0.6.114",
41
+ "@hyperframes/sdk": "0.6.114"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/react": "19",
@@ -52,7 +52,7 @@
52
52
  "vite": "^6.4.2",
53
53
  "vitest": "^3.2.4",
54
54
  "zustand": "^5.0.0",
55
- "@hyperframes/producer": "0.6.112"
55
+ "@hyperframes/producer": "0.6.114"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "react": "19",
package/src/App.tsx CHANGED
@@ -566,6 +566,10 @@ export function StudioApp() {
566
566
  onToggleRecording={
567
567
  STUDIO_KEYFRAMES_ENABLED ? handleToggleRecording : undefined
568
568
  }
569
+ sdkSession={sdkHandle.session}
570
+ reloadPreview={reloadPreview}
571
+ domEditSaveTimestampRef={domEditSaveTimestampRef}
572
+ recordEdit={editHistory.recordEdit}
569
573
  />
570
574
  )}
571
575
  </div>
@@ -1,13 +1,26 @@
1
- import { useCallback, useRef, useState, type PointerEvent as ReactPointerEvent } from "react";
1
+ import {
2
+ useCallback,
3
+ useMemo,
4
+ useRef,
5
+ useState,
6
+ type MutableRefObject,
7
+ type PointerEvent as ReactPointerEvent,
8
+ } from "react";
2
9
  import { Tooltip } from "./ui";
3
10
  import { PropertyPanel } from "./editor/PropertyPanel";
4
11
  import { LayersPanel } from "./editor/LayersPanel";
5
12
  import { CaptionPropertyPanel } from "../captions/components/CaptionPropertyPanel";
6
13
  import { BlockParamsPanel } from "./editor/BlockParamsPanel";
7
14
  import { RenderQueue } from "./renders/RenderQueue";
15
+ import { SlideshowPanel } from "./panels/SlideshowPanel";
16
+ import type { SceneInfo } from "./panels/SlideshowPanel";
8
17
  import type { RenderJob } from "./renders/useRenderQueue";
9
18
  import type { BlockParam } from "@hyperframes/core/registry";
19
+ import type { IframeWindow } from "../player/lib/playbackTypes";
10
20
  import { STUDIO_INSPECTOR_PANELS_ENABLED } from "./editor/manualEditingAvailability";
21
+ import type { Composition } from "@hyperframes/sdk";
22
+ import type { EditHistoryKind } from "../utils/editHistory";
23
+ import { useSlideshowPersist } from "../hooks/useSlideshowPersist";
11
24
 
12
25
  import { useStudioPlaybackContext, useStudioShellContext } from "../contexts/StudioContext";
13
26
  import { usePanelLayoutContext } from "../contexts/PanelLayoutContext";
@@ -30,6 +43,15 @@ export interface StudioRightPanelProps {
30
43
  recordingState?: "idle" | "recording" | "preview";
31
44
  recordingDuration?: number;
32
45
  onToggleRecording?: () => void;
46
+ /** Dependencies for the Slideshow persist callback, threaded from App.tsx. */
47
+ sdkSession: Composition | null;
48
+ reloadPreview: () => void;
49
+ domEditSaveTimestampRef: MutableRefObject<number>;
50
+ recordEdit: (entry: {
51
+ label: string;
52
+ kind: EditHistoryKind;
53
+ files: Record<string, { before: string; after: string }>;
54
+ }) => Promise<void>;
33
55
  }
34
56
 
35
57
  // fallow-ignore-next-line complexity
@@ -40,6 +62,10 @@ export function StudioRightPanel({
40
62
  recordingState,
41
63
  recordingDuration,
42
64
  onToggleRecording,
65
+ sdkSession,
66
+ reloadPreview,
67
+ domEditSaveTimestampRef,
68
+ recordEdit,
43
69
  }: StudioRightPanelProps) {
44
70
  const {
45
71
  rightWidth,
@@ -60,7 +86,7 @@ export function StudioRightPanel({
60
86
  waitForPendingDomEditSaves,
61
87
  renderQueue,
62
88
  } = useStudioShellContext();
63
- const { captionEditMode } = useStudioPlaybackContext();
89
+ const { captionEditMode, refreshKey } = useStudioPlaybackContext();
64
90
 
65
91
  const {
66
92
  domEditSelection,
@@ -100,8 +126,40 @@ export function StudioRightPanel({
100
126
  handleGsapConvertToKeyframes,
101
127
  } = useDomEditContext();
102
128
 
103
- const { assets, fontAssets, projectDir, handleImportFiles, handleImportFonts } =
104
- useFileManagerContext();
129
+ const {
130
+ assets,
131
+ fontAssets,
132
+ projectDir,
133
+ handleImportFiles,
134
+ handleImportFonts,
135
+ readProjectFile,
136
+ writeProjectFile,
137
+ } = useFileManagerContext();
138
+
139
+ // Discrete ops (toggle, reorder, add/delete, hotspot): persist immediately,
140
+ // no coalescing — each is a distinct user action that deserves its own undo entry.
141
+ const onPersistSlideshow = useSlideshowPersist({
142
+ sdkSession,
143
+ activeCompPath,
144
+ readProjectFile,
145
+ writeProjectFile,
146
+ recordEdit,
147
+ reloadPreview,
148
+ domEditSaveTimestampRef,
149
+ });
150
+
151
+ // Notes path: persists are debounced in SlideshowPanel; coalesceKey ensures
152
+ // rapid writes collapse into a single undo entry via the save-queue infra.
153
+ const onPersistSlideshowNotes = useSlideshowPersist({
154
+ sdkSession,
155
+ activeCompPath,
156
+ readProjectFile,
157
+ writeProjectFile,
158
+ recordEdit,
159
+ reloadPreview,
160
+ domEditSaveTimestampRef,
161
+ coalesceKey: activeCompPath ? `slideshow-notes:${activeCompPath}` : "slideshow-notes",
162
+ });
105
163
 
106
164
  const [layersPanePercent, setLayersPanePercent] = useState(40);
107
165
  const splitContainerRef = useRef<HTMLDivElement>(null);
@@ -113,6 +171,23 @@ export function StudioRightPanel({
113
171
 
114
172
  const renderJobs = renderQueue.jobs as RenderJob[];
115
173
  const inspectorTabActive = rightPanelTab === "design" || rightPanelTab === "layers";
174
+
175
+ // Derive scene list from the live clip manifest in the preview iframe.
176
+ // fallow-ignore-next-line complexity
177
+ const slideshowScenes = useMemo<SceneInfo[]>(() => {
178
+ try {
179
+ const win = previewIframeRef.current?.contentWindow as IframeWindow | null;
180
+ return (win?.__clipManifest?.scenes ?? []).map((s) => ({
181
+ id: s.id,
182
+ label: s.label,
183
+ start: s.start,
184
+ duration: s.duration,
185
+ }));
186
+ } catch {
187
+ return [];
188
+ }
189
+ // eslint-disable-next-line react-hooks/exhaustive-deps
190
+ }, [previewIframeRef, rightPanelTab, refreshKey]);
116
191
  const designPaneOpen = inspectorTabActive && rightInspectorPanes.design && designPanelActive;
117
192
  const layersPaneOpen =
118
193
  inspectorTabActive && rightInspectorPanes.layers && STUDIO_INSPECTOR_PANELS_ENABLED;
@@ -291,6 +366,19 @@ export function StudioRightPanel({
291
366
  {renderJobs.length > 0 ? `Renders (${renderJobs.length})` : "Renders"}
292
367
  </button>
293
368
  </Tooltip>
369
+ <Tooltip label="Slideshow branching editor" side="bottom">
370
+ <button
371
+ type="button"
372
+ onClick={() => setRightPanelTab("slideshow")}
373
+ className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
374
+ rightPanelTab === "slideshow"
375
+ ? "bg-neutral-800 text-white"
376
+ : "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
377
+ }`}
378
+ >
379
+ Slideshow
380
+ </button>
381
+ </Tooltip>
294
382
  </div>
295
383
  <div className="min-h-0 flex-1">
296
384
  {rightPanelTab === "block-params" && activeBlockParams ? (
@@ -301,6 +389,12 @@ export function StudioRightPanel({
301
389
  compositionPath={activeBlockParams.compositionPath}
302
390
  onClose={onCloseBlockParams ?? (() => {})}
303
391
  />
392
+ ) : rightPanelTab === "slideshow" ? (
393
+ <SlideshowPanel
394
+ scenes={slideshowScenes}
395
+ onPersist={onPersistSlideshow}
396
+ onPersistNotes={onPersistSlideshowNotes}
397
+ />
304
398
  ) : layersPaneOpen && designPaneOpen ? (
305
399
  <div ref={splitContainerRef} className="flex h-full min-h-0 flex-col">
306
400
  <div