@hyperframes/studio 0.6.0-alpha.9 → 0.6.0

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 (66) hide show
  1. package/dist/assets/{hyperframes-player-DjsVzYFP.js → hyperframes-player-DOFETgjy.js} +1 -1
  2. package/dist/assets/index-D1JDq7Gg.css +1 -0
  3. package/dist/assets/index-DUqUmaoH.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 +428 -4299
  8. package/src/components/AskAgentModal.tsx +120 -0
  9. package/src/components/StudioHeader.tsx +133 -0
  10. package/src/components/StudioLeftSidebar.tsx +125 -0
  11. package/src/components/StudioPreviewArea.tsx +163 -0
  12. package/src/components/StudioRightPanel.tsx +198 -0
  13. package/src/components/TimelineToolbar.tsx +89 -0
  14. package/src/components/editor/DomEditOverlay.tsx +15 -1
  15. package/src/components/editor/PropertyPanel.test.ts +0 -49
  16. package/src/components/editor/PropertyPanel.tsx +132 -2763
  17. package/src/components/editor/domEditing.ts +38 -5
  18. package/src/components/editor/manualEditingAvailability.test.ts +2 -2
  19. package/src/components/editor/manualEditingAvailability.ts +1 -1
  20. package/src/components/editor/manualEdits.ts +32 -0
  21. package/src/components/editor/propertyPanelColor.tsx +371 -0
  22. package/src/components/editor/propertyPanelFill.tsx +421 -0
  23. package/src/components/editor/propertyPanelFont.tsx +455 -0
  24. package/src/components/editor/propertyPanelHelpers.ts +401 -0
  25. package/src/components/editor/propertyPanelPrimitives.tsx +357 -0
  26. package/src/components/editor/propertyPanelSections.tsx +453 -0
  27. package/src/components/editor/propertyPanelStyleSections.tsx +411 -0
  28. package/src/components/nle/NLELayout.tsx +8 -11
  29. package/src/components/nle/NLEPreview.tsx +3 -0
  30. package/src/components/renders/RenderQueue.tsx +102 -31
  31. package/src/components/renders/useRenderQueue.ts +8 -2
  32. package/src/components/sidebar/LeftSidebar.tsx +186 -186
  33. package/src/contexts/DomEditContext.tsx +137 -0
  34. package/src/contexts/FileManagerContext.tsx +110 -0
  35. package/src/contexts/PanelLayoutContext.tsx +68 -0
  36. package/src/contexts/StudioContext.tsx +135 -0
  37. package/src/hooks/useAppHotkeys.ts +326 -0
  38. package/src/hooks/useAskAgentModal.ts +162 -0
  39. package/src/hooks/useCaptionDetection.ts +132 -0
  40. package/src/hooks/useCompositionDimensions.ts +25 -0
  41. package/src/hooks/useConsoleErrorCapture.ts +60 -0
  42. package/src/hooks/useDomEditCommits.ts +437 -0
  43. package/src/hooks/useDomEditSession.ts +342 -0
  44. package/src/hooks/useDomEditTextCommits.ts +330 -0
  45. package/src/hooks/useDomSelection.ts +398 -0
  46. package/src/hooks/useFileManager.ts +431 -0
  47. package/src/hooks/useFrameCapture.ts +77 -0
  48. package/src/hooks/useLintModal.ts +35 -0
  49. package/src/hooks/useManifestPersistence.ts +492 -0
  50. package/src/hooks/usePanelLayout.ts +68 -0
  51. package/src/hooks/usePreviewInteraction.ts +153 -0
  52. package/src/hooks/useRenderClipContent.ts +124 -0
  53. package/src/hooks/useTimelineEditing.ts +472 -0
  54. package/src/player/components/Player.tsx +33 -2
  55. package/src/player/components/Timeline.test.ts +0 -8
  56. package/src/player/components/Timeline.tsx +10 -103
  57. package/src/player/components/TimelineClip.tsx +9 -244
  58. package/src/player/hooks/useTimelinePlayer.ts +140 -103
  59. package/src/utils/domEditHelpers.ts +50 -0
  60. package/src/utils/studioFontHelpers.ts +83 -0
  61. package/src/utils/studioHelpers.ts +214 -0
  62. package/src/utils/studioPreviewHelpers.ts +185 -0
  63. package/src/utils/timelineDiscovery.ts +1 -1
  64. package/dist/assets/index-14zH9lqh.css +0 -1
  65. package/dist/assets/index-DYCiFGWQ.js +0 -108
  66. package/src/player/components/TimelineClip.test.ts +0 -92
@@ -14,8 +14,14 @@ export interface RenderJob {
14
14
  // Mirrors `CanvasResolution` from @hyperframes/core. Kept local because
15
15
  // studio's tsconfig doesn't include node types, and the core barrel
16
16
  // transitively pulls in modules with `node:fs` imports. Drift risk is
17
- // low (4 string literals tied to a stable enum).
18
- export type ResolutionPreset = "landscape" | "portrait" | "landscape-4k" | "portrait-4k";
17
+ // low (6 string literals kept in sync manually with CANVAS_DIMENSIONS).
18
+ export type ResolutionPreset =
19
+ | "landscape"
20
+ | "portrait"
21
+ | "landscape-4k"
22
+ | "portrait-4k"
23
+ | "square"
24
+ | "square-4k";
19
25
 
20
26
  export interface StartRenderOptions {
21
27
  fps?: number;
@@ -1,10 +1,20 @@
1
- import { memo, useState, useCallback, type ReactNode } from "react";
2
- import { useMountEffect } from "../../hooks/useMountEffect";
1
+ import {
2
+ memo,
3
+ useState,
4
+ useCallback,
5
+ useImperativeHandle,
6
+ forwardRef,
7
+ type ReactNode,
8
+ } from "react";
3
9
  import { CompositionsTab } from "./CompositionsTab";
4
10
  import { AssetsTab } from "./AssetsTab";
5
11
  import { FileTree } from "../editor/FileTree";
6
12
 
7
- type SidebarTab = "compositions" | "assets" | "code";
13
+ export type SidebarTab = "compositions" | "assets" | "code";
14
+
15
+ export interface LeftSidebarHandle {
16
+ selectTab: (tab: SidebarTab) => void;
17
+ }
8
18
 
9
19
  const STORAGE_KEY = "hf-studio-sidebar-tab";
10
20
 
@@ -39,201 +49,191 @@ interface LeftSidebarProps {
39
49
  takeoverContent?: ReactNode;
40
50
  }
41
51
 
42
- export const LeftSidebar = memo(function LeftSidebar({
43
- width = 240,
44
- projectId,
45
- compositions,
46
- assets,
47
- activeComposition,
48
- onSelectComposition,
49
- onImportFiles,
50
- fileTree: fileProp,
51
- editingFile,
52
- onSelectFile,
53
- onCreateFile,
54
- onCreateFolder,
55
- onDeleteFile,
56
- onRenameFile,
57
- onDuplicateFile,
58
- onMoveFile,
59
- codeChildren,
60
- onLint,
61
- linting,
62
- onToggleCollapse,
63
- takeoverContent,
64
- }: LeftSidebarProps) {
65
- const [tab, setTab] = useState<SidebarTab>(getPersistedTab);
52
+ export const LeftSidebar = memo(
53
+ forwardRef<LeftSidebarHandle, LeftSidebarProps>(function LeftSidebar(
54
+ {
55
+ width = 240,
56
+ projectId,
57
+ compositions,
58
+ assets,
59
+ activeComposition,
60
+ onSelectComposition,
61
+ onImportFiles,
62
+ fileTree: fileProp,
63
+ editingFile,
64
+ onSelectFile,
65
+ onCreateFile,
66
+ onCreateFolder,
67
+ onDeleteFile,
68
+ onRenameFile,
69
+ onDuplicateFile,
70
+ onMoveFile,
71
+ codeChildren,
72
+ onLint,
73
+ linting,
74
+ onToggleCollapse,
75
+ takeoverContent,
76
+ },
77
+ ref,
78
+ ) {
79
+ const [tab, setTab] = useState<SidebarTab>(getPersistedTab);
66
80
 
67
- const selectTab = useCallback((t: SidebarTab) => {
68
- setTab(t);
69
- localStorage.setItem(STORAGE_KEY, t);
70
- }, []);
81
+ const selectTab = useCallback((t: SidebarTab) => {
82
+ setTab(t);
83
+ localStorage.setItem(STORAGE_KEY, t);
84
+ }, []);
71
85
 
72
- // Keyboard shortcuts: Cmd+1 for Compositions, Cmd+2 for Assets
73
- useMountEffect(() => {
74
- const handler = (e: KeyboardEvent) => {
75
- if (!e.metaKey && !e.ctrlKey) return;
76
- if (e.key === "1") {
77
- e.preventDefault();
78
- selectTab("compositions");
79
- }
80
- if (e.key === "2") {
81
- e.preventDefault();
82
- selectTab("assets");
83
- }
84
- };
85
- window.addEventListener("keydown", handler);
86
- return () => window.removeEventListener("keydown", handler);
87
- });
86
+ useImperativeHandle(ref, () => ({ selectTab }), [selectTab]);
88
87
 
89
- return (
90
- <div
91
- className="flex flex-col h-full bg-neutral-950 border-r border-neutral-800/50"
92
- style={{ width }}
93
- >
94
- {takeoverContent ? (
95
- <div className="flex min-h-0 flex-1">{takeoverContent}</div>
96
- ) : (
97
- <>
98
- {/* Tabs — Code first */}
99
- <div className="border-b border-neutral-800/50 px-3 py-3 flex-shrink-0">
100
- <div className="flex items-center gap-2">
101
- <div
102
- className="grid min-w-0 flex-1 gap-1 rounded-[18px] bg-neutral-900 p-1 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]"
103
- style={{ gridTemplateColumns: "0.9fr 1.25fr 0.9fr" }}
104
- >
105
- <button
106
- type="button"
107
- onClick={() => selectTab("code")}
108
- className={`rounded-[14px] px-2.5 py-2 text-[10px] font-semibold transition-all ${
109
- tab === "code"
110
- ? "bg-neutral-800 text-white"
111
- : "text-neutral-500 hover:text-neutral-200"
112
- }`}
113
- >
114
- Code
115
- </button>
116
- <button
117
- type="button"
118
- onClick={() => selectTab("compositions")}
119
- className={`rounded-[14px] px-2.5 py-2 text-[10px] font-semibold transition-all ${
120
- tab === "compositions"
121
- ? "bg-neutral-800 text-white"
122
- : "text-neutral-500 hover:text-neutral-200"
123
- }`}
124
- >
125
- Compositions
126
- </button>
127
- <button
128
- type="button"
129
- onClick={() => selectTab("assets")}
130
- className={`rounded-[14px] px-2.5 py-2 text-[10px] font-semibold transition-all ${
131
- tab === "assets"
132
- ? "bg-neutral-800 text-white"
133
- : "text-neutral-500 hover:text-neutral-200"
134
- }`}
88
+ return (
89
+ <div
90
+ className="flex flex-col h-full bg-neutral-950 border-r border-neutral-800/50"
91
+ style={{ width }}
92
+ >
93
+ {takeoverContent ? (
94
+ <div className="flex min-h-0 flex-1">{takeoverContent}</div>
95
+ ) : (
96
+ <>
97
+ {/* Tabs — Code first */}
98
+ <div className="border-b border-neutral-800/50 px-3 py-3 flex-shrink-0">
99
+ <div className="flex items-center gap-2">
100
+ <div
101
+ className="grid min-w-0 flex-1 gap-0.5 rounded-[18px] bg-neutral-900 p-1 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]"
102
+ style={{ gridTemplateColumns: "1fr 1fr 1fr" }}
135
103
  >
136
- Assets
137
- </button>
104
+ <button
105
+ type="button"
106
+ onClick={() => selectTab("code")}
107
+ className={`rounded-[14px] px-1.5 py-2 text-[10px] font-semibold truncate transition-all ${
108
+ tab === "code"
109
+ ? "bg-neutral-800 text-white"
110
+ : "text-neutral-500 hover:text-neutral-200"
111
+ }`}
112
+ >
113
+ Code
114
+ </button>
115
+ <button
116
+ type="button"
117
+ onClick={() => selectTab("compositions")}
118
+ className={`rounded-[14px] px-1.5 py-2 text-[10px] font-semibold truncate transition-all ${
119
+ tab === "compositions"
120
+ ? "bg-neutral-800 text-white"
121
+ : "text-neutral-500 hover:text-neutral-200"
122
+ }`}
123
+ >
124
+ Comps
125
+ </button>
126
+ <button
127
+ type="button"
128
+ onClick={() => selectTab("assets")}
129
+ className={`rounded-[14px] px-1.5 py-2 text-[10px] font-semibold truncate transition-all ${
130
+ tab === "assets"
131
+ ? "bg-neutral-800 text-white"
132
+ : "text-neutral-500 hover:text-neutral-200"
133
+ }`}
134
+ >
135
+ Assets
136
+ </button>
137
+ </div>
138
+ {onToggleCollapse && (
139
+ <button
140
+ type="button"
141
+ onClick={onToggleCollapse}
142
+ className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-transparent text-neutral-500 transition-colors hover:border-neutral-800 hover:bg-neutral-900 hover:text-neutral-300"
143
+ title="Hide sidebar"
144
+ aria-label="Hide sidebar"
145
+ >
146
+ <svg
147
+ width="14"
148
+ height="14"
149
+ viewBox="0 0 24 24"
150
+ fill="none"
151
+ stroke="currentColor"
152
+ strokeWidth="1.5"
153
+ strokeLinecap="round"
154
+ strokeLinejoin="round"
155
+ aria-hidden="true"
156
+ >
157
+ <path d="m14 7-5 5 5 5" />
158
+ <path d="M19 4v16" />
159
+ </svg>
160
+ </button>
161
+ )}
138
162
  </div>
139
- {onToggleCollapse && (
163
+ </div>
164
+
165
+ {/* Tab content */}
166
+ {tab === "compositions" && (
167
+ <CompositionsTab
168
+ projectId={projectId}
169
+ compositions={compositions}
170
+ activeComposition={activeComposition}
171
+ onSelect={onSelectComposition}
172
+ />
173
+ )}
174
+ {tab === "assets" && (
175
+ <AssetsTab
176
+ projectId={projectId}
177
+ assets={assets}
178
+ onImport={onImportFiles}
179
+ onDelete={onDeleteFile}
180
+ onRename={onRenameFile}
181
+ />
182
+ )}
183
+ {tab === "code" && (
184
+ <div className="flex flex-1 min-h-0">
185
+ {(fileProp?.length ?? 0) > 0 && (
186
+ <div className="w-[160px] flex-shrink-0 border-r border-neutral-800 overflow-y-auto">
187
+ <FileTree
188
+ files={fileProp ?? []}
189
+ activeFile={editingFile?.path ?? null}
190
+ onSelectFile={onSelectFile ?? (() => {})}
191
+ onCreateFile={onCreateFile}
192
+ onCreateFolder={onCreateFolder}
193
+ onDeleteFile={onDeleteFile}
194
+ onRenameFile={onRenameFile}
195
+ onDuplicateFile={onDuplicateFile}
196
+ onMoveFile={onMoveFile}
197
+ onImportFiles={onImportFiles}
198
+ />
199
+ </div>
200
+ )}
201
+ <div className="flex-1 overflow-hidden min-w-0">
202
+ {codeChildren ?? (
203
+ <div className="flex items-center justify-center h-full text-neutral-600 text-sm">
204
+ Select a file to edit
205
+ </div>
206
+ )}
207
+ </div>
208
+ </div>
209
+ )}
210
+
211
+ {/* Lint button pinned at the bottom */}
212
+ {onLint && (
213
+ <div className="border-t border-neutral-800 p-2 flex-shrink-0">
140
214
  <button
141
- type="button"
142
- onClick={onToggleCollapse}
143
- className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-transparent text-neutral-500 transition-colors hover:border-neutral-800 hover:bg-neutral-900 hover:text-neutral-300"
144
- title="Hide sidebar"
145
- aria-label="Hide sidebar"
215
+ onClick={onLint}
216
+ disabled={linting}
217
+ className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 rounded-md text-[11px] font-medium text-neutral-500 hover:text-amber-300 hover:bg-neutral-800 transition-colors disabled:opacity-40"
146
218
  >
147
219
  <svg
148
- width="14"
149
- height="14"
220
+ width="12"
221
+ height="12"
150
222
  viewBox="0 0 24 24"
151
223
  fill="none"
152
224
  stroke="currentColor"
153
- strokeWidth="1.5"
154
- strokeLinecap="round"
155
- strokeLinejoin="round"
156
- aria-hidden="true"
225
+ strokeWidth="2"
157
226
  >
158
- <path d="m14 7-5 5 5 5" />
159
- <path d="M19 4v16" />
227
+ <path d="M9 11l3 3L22 4" />
228
+ <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" />
160
229
  </svg>
230
+ {linting ? "Linting…" : "Lint"}
161
231
  </button>
162
- )}
163
- </div>
164
- </div>
165
-
166
- {/* Tab content */}
167
- {tab === "compositions" && (
168
- <CompositionsTab
169
- projectId={projectId}
170
- compositions={compositions}
171
- activeComposition={activeComposition}
172
- onSelect={onSelectComposition}
173
- />
174
- )}
175
- {tab === "assets" && (
176
- <AssetsTab
177
- projectId={projectId}
178
- assets={assets}
179
- onImport={onImportFiles}
180
- onDelete={onDeleteFile}
181
- onRename={onRenameFile}
182
- />
183
- )}
184
- {tab === "code" && (
185
- <div className="flex flex-1 min-h-0">
186
- {(fileProp?.length ?? 0) > 0 && (
187
- <div className="w-[160px] flex-shrink-0 border-r border-neutral-800 overflow-y-auto">
188
- <FileTree
189
- files={fileProp ?? []}
190
- activeFile={editingFile?.path ?? null}
191
- onSelectFile={onSelectFile ?? (() => {})}
192
- onCreateFile={onCreateFile}
193
- onCreateFolder={onCreateFolder}
194
- onDeleteFile={onDeleteFile}
195
- onRenameFile={onRenameFile}
196
- onDuplicateFile={onDuplicateFile}
197
- onMoveFile={onMoveFile}
198
- onImportFiles={onImportFiles}
199
- />
200
- </div>
201
- )}
202
- <div className="flex-1 overflow-hidden min-w-0">
203
- {codeChildren ?? (
204
- <div className="flex items-center justify-center h-full text-neutral-600 text-sm">
205
- Select a file to edit
206
- </div>
207
- )}
208
232
  </div>
209
- </div>
210
- )}
211
-
212
- {/* Lint button pinned at the bottom */}
213
- {onLint && (
214
- <div className="border-t border-neutral-800 p-2 flex-shrink-0">
215
- <button
216
- onClick={onLint}
217
- disabled={linting}
218
- className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 rounded-md text-[11px] font-medium text-neutral-500 hover:text-amber-300 hover:bg-neutral-800 transition-colors disabled:opacity-40"
219
- >
220
- <svg
221
- width="12"
222
- height="12"
223
- viewBox="0 0 24 24"
224
- fill="none"
225
- stroke="currentColor"
226
- strokeWidth="2"
227
- >
228
- <path d="M9 11l3 3L22 4" />
229
- <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" />
230
- </svg>
231
- {linting ? "Linting…" : "Lint"}
232
- </button>
233
- </div>
234
- )}
235
- </>
236
- )}
237
- </div>
238
- );
239
- });
233
+ )}
234
+ </>
235
+ )}
236
+ </div>
237
+ );
238
+ }),
239
+ );
@@ -0,0 +1,137 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
2
+ import type { useDomEditSession } from "../hooks/useDomEditSession";
3
+
4
+ type DomEditValue = ReturnType<typeof useDomEditSession>;
5
+
6
+ const DomEditContext = createContext<DomEditValue | null>(null);
7
+
8
+ export function useDomEditContext(): DomEditValue {
9
+ const ctx = useContext(DomEditContext);
10
+ if (!ctx) throw new Error("useDomEditContext must be used within DomEditProvider");
11
+ return ctx;
12
+ }
13
+
14
+ export function DomEditProvider({
15
+ value: {
16
+ domEditSelection,
17
+ domEditGroupSelections,
18
+ domEditHoverSelection,
19
+ agentModalOpen,
20
+ agentModalAnchorPoint,
21
+ copiedAgentPrompt,
22
+ agentPromptSelectionContext,
23
+ domEditSelectionRef,
24
+ handleTimelineElementSelect,
25
+ handlePreviewCanvasMouseDown,
26
+ handlePreviewCanvasPointerMove,
27
+ handlePreviewCanvasPointerLeave,
28
+ applyDomSelection,
29
+ clearDomSelection,
30
+ handleDomStyleCommit,
31
+ handleDomPathOffsetCommit,
32
+ handleDomGroupPathOffsetCommit,
33
+ handleDomBoxSizeCommit,
34
+ handleDomRotationCommit,
35
+ handleDomManualEditsReset,
36
+ handleDomMotionCommit,
37
+ handleDomMotionClear,
38
+ handleDomTextCommit,
39
+ handleDomTextFieldStyleCommit,
40
+ handleDomAddTextField,
41
+ handleDomRemoveTextField,
42
+ handleAskAgent,
43
+ handleAgentModalSubmit,
44
+ handleBlockedDomMove,
45
+ handleDomManualDragStart,
46
+ handleDomEditElementDelete,
47
+ buildDomSelectionForTimelineElement,
48
+ resolveImportedFontAsset,
49
+ setAgentModalOpen,
50
+ setAgentPromptSelectionContext,
51
+ setAgentModalAnchorPoint,
52
+ },
53
+ children,
54
+ }: {
55
+ value: DomEditValue;
56
+ children: ReactNode;
57
+ }) {
58
+ const stable = useMemo<DomEditValue>(
59
+ () => ({
60
+ domEditSelection,
61
+ domEditGroupSelections,
62
+ domEditHoverSelection,
63
+ agentModalOpen,
64
+ agentModalAnchorPoint,
65
+ copiedAgentPrompt,
66
+ agentPromptSelectionContext,
67
+ domEditSelectionRef,
68
+ handleTimelineElementSelect,
69
+ handlePreviewCanvasMouseDown,
70
+ handlePreviewCanvasPointerMove,
71
+ handlePreviewCanvasPointerLeave,
72
+ applyDomSelection,
73
+ clearDomSelection,
74
+ handleDomStyleCommit,
75
+ handleDomPathOffsetCommit,
76
+ handleDomGroupPathOffsetCommit,
77
+ handleDomBoxSizeCommit,
78
+ handleDomRotationCommit,
79
+ handleDomManualEditsReset,
80
+ handleDomMotionCommit,
81
+ handleDomMotionClear,
82
+ handleDomTextCommit,
83
+ handleDomTextFieldStyleCommit,
84
+ handleDomAddTextField,
85
+ handleDomRemoveTextField,
86
+ handleAskAgent,
87
+ handleAgentModalSubmit,
88
+ handleBlockedDomMove,
89
+ handleDomManualDragStart,
90
+ handleDomEditElementDelete,
91
+ buildDomSelectionForTimelineElement,
92
+ resolveImportedFontAsset,
93
+ setAgentModalOpen,
94
+ setAgentPromptSelectionContext,
95
+ setAgentModalAnchorPoint,
96
+ }),
97
+ [
98
+ domEditSelection,
99
+ domEditGroupSelections,
100
+ domEditHoverSelection,
101
+ agentModalOpen,
102
+ agentModalAnchorPoint,
103
+ copiedAgentPrompt,
104
+ agentPromptSelectionContext,
105
+ domEditSelectionRef,
106
+ handleTimelineElementSelect,
107
+ handlePreviewCanvasMouseDown,
108
+ handlePreviewCanvasPointerMove,
109
+ handlePreviewCanvasPointerLeave,
110
+ applyDomSelection,
111
+ clearDomSelection,
112
+ handleDomStyleCommit,
113
+ handleDomPathOffsetCommit,
114
+ handleDomGroupPathOffsetCommit,
115
+ handleDomBoxSizeCommit,
116
+ handleDomRotationCommit,
117
+ handleDomManualEditsReset,
118
+ handleDomMotionCommit,
119
+ handleDomMotionClear,
120
+ handleDomTextCommit,
121
+ handleDomTextFieldStyleCommit,
122
+ handleDomAddTextField,
123
+ handleDomRemoveTextField,
124
+ handleAskAgent,
125
+ handleAgentModalSubmit,
126
+ handleBlockedDomMove,
127
+ handleDomManualDragStart,
128
+ handleDomEditElementDelete,
129
+ buildDomSelectionForTimelineElement,
130
+ resolveImportedFontAsset,
131
+ setAgentModalOpen,
132
+ setAgentPromptSelectionContext,
133
+ setAgentModalAnchorPoint,
134
+ ],
135
+ );
136
+ return <DomEditContext value={stable}>{children}</DomEditContext>;
137
+ }
@@ -0,0 +1,110 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
2
+ import type { useFileManager } from "../hooks/useFileManager";
3
+
4
+ type FileManagerValue = ReturnType<typeof useFileManager>;
5
+
6
+ const FileManagerContext = createContext<FileManagerValue | null>(null);
7
+
8
+ export function useFileManagerContext(): FileManagerValue {
9
+ const ctx = useContext(FileManagerContext);
10
+ if (!ctx) throw new Error("useFileManagerContext must be used within FileManagerProvider");
11
+ return ctx;
12
+ }
13
+
14
+ export function FileManagerProvider({
15
+ value: {
16
+ editingFile,
17
+ setEditingFile,
18
+ projectDir,
19
+ fileTree,
20
+ setFileTree,
21
+ editingPathRef,
22
+ projectIdRef,
23
+ saveTimerRef,
24
+ importedFontAssetsRef,
25
+ readProjectFile,
26
+ writeProjectFile,
27
+ readOptionalProjectFile,
28
+ handleFileSelect,
29
+ handleContentChange,
30
+ refreshFileTree,
31
+ uploadProjectFiles,
32
+ handleCreateFile,
33
+ handleCreateFolder,
34
+ handleDeleteFile,
35
+ handleRenameFile,
36
+ handleDuplicateFile,
37
+ handleMoveFile,
38
+ handleImportFiles,
39
+ handleImportFonts,
40
+ compositions,
41
+ assets,
42
+ fontAssets,
43
+ },
44
+ children,
45
+ }: {
46
+ value: FileManagerValue;
47
+ children: ReactNode;
48
+ }) {
49
+ const stable = useMemo<FileManagerValue>(
50
+ () => ({
51
+ editingFile,
52
+ setEditingFile,
53
+ projectDir,
54
+ fileTree,
55
+ setFileTree,
56
+ editingPathRef,
57
+ projectIdRef,
58
+ saveTimerRef,
59
+ importedFontAssetsRef,
60
+ readProjectFile,
61
+ writeProjectFile,
62
+ readOptionalProjectFile,
63
+ handleFileSelect,
64
+ handleContentChange,
65
+ refreshFileTree,
66
+ uploadProjectFiles,
67
+ handleCreateFile,
68
+ handleCreateFolder,
69
+ handleDeleteFile,
70
+ handleRenameFile,
71
+ handleDuplicateFile,
72
+ handleMoveFile,
73
+ handleImportFiles,
74
+ handleImportFonts,
75
+ compositions,
76
+ assets,
77
+ fontAssets,
78
+ }),
79
+ [
80
+ editingFile,
81
+ setEditingFile,
82
+ projectDir,
83
+ fileTree,
84
+ setFileTree,
85
+ editingPathRef,
86
+ projectIdRef,
87
+ saveTimerRef,
88
+ importedFontAssetsRef,
89
+ readProjectFile,
90
+ writeProjectFile,
91
+ readOptionalProjectFile,
92
+ handleFileSelect,
93
+ handleContentChange,
94
+ refreshFileTree,
95
+ uploadProjectFiles,
96
+ handleCreateFile,
97
+ handleCreateFolder,
98
+ handleDeleteFile,
99
+ handleRenameFile,
100
+ handleDuplicateFile,
101
+ handleMoveFile,
102
+ handleImportFiles,
103
+ handleImportFonts,
104
+ compositions,
105
+ assets,
106
+ fontAssets,
107
+ ],
108
+ );
109
+ return <FileManagerContext value={stable}>{children}</FileManagerContext>;
110
+ }