@hyperframes/studio 0.6.6 → 0.6.7
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-T-ME1rqL.js → hyperframes-player-D0Yi3xMP.js} +2 -2
- package/dist/assets/{index-Bne9FFeo.css → index-Ckqo37Co.css} +1 -1
- package/dist/assets/index-Yvtxngdi.js +116 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +54 -31
- package/src/components/StudioGlobalDragOverlay.tsx +26 -0
- package/src/components/StudioRightPanel.tsx +0 -2
- package/src/components/editor/DomEditOverlay.test.ts +1 -0
- package/src/components/editor/DomEditOverlay.tsx +2 -1
- package/src/components/editor/PropertyPanel.tsx +27 -36
- package/src/components/editor/domEditingElement.ts +1 -0
- package/src/components/editor/manualEdits.test.ts +39 -466
- package/src/components/editor/manualEdits.ts +6 -168
- package/src/components/editor/manualEditsDom.ts +361 -1
- package/src/components/editor/manualEditsParsing.ts +2 -240
- package/src/components/editor/manualEditsTypes.ts +1 -40
- package/src/components/editor/useDomEditOverlayGestures.ts +25 -8
- package/src/components/nle/NLEPreview.tsx +1 -1
- package/src/components/sidebar/CompositionsTab.tsx +9 -3
- package/src/contexts/DomEditContext.tsx +3 -0
- package/src/contexts/FileManagerContext.tsx +3 -0
- package/src/hooks/useAppHotkeys.ts +1 -4
- package/src/hooks/useDomEditCommits.ts +82 -77
- package/src/hooks/useDomEditSession.ts +4 -16
- package/src/hooks/useFileManager.ts +10 -1
- package/src/hooks/useManifestPersistence.ts +51 -187
- package/src/hooks/usePanelLayout.ts +10 -3
- package/src/hooks/usePreviewInteraction.ts +0 -1
- package/src/hooks/useStudioUrlState.ts +188 -0
- package/src/player/components/Player.tsx +15 -1
- package/src/player/components/PlayerControls.test.ts +17 -0
- package/src/player/components/PlayerControls.tsx +61 -0
- package/src/player/hooks/usePlaybackKeyboard.test.ts +174 -0
- package/src/player/hooks/usePlaybackKeyboard.ts +18 -15
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +329 -0
- package/src/player/hooks/useTimelinePlayer.ts +76 -18
- package/src/player/hooks/useTimelineSyncCallbacks.ts +10 -4
- package/src/player/lib/playbackAdapter.test.ts +50 -0
- package/src/player/lib/playbackAdapter.ts +2 -2
- package/src/player/lib/playbackTypes.ts +1 -1
- package/src/player/lib/timelineDOM.ts +4 -2
- package/src/player/lib/timelineIframeHelpers.ts +63 -7
- package/src/player/store/playerStore.test.ts +105 -1
- package/src/player/store/playerStore.ts +12 -1
- package/src/utils/projectRouting.test.ts +15 -0
- package/src/utils/projectRouting.ts +46 -9
- package/src/utils/sourcePatcher.ts +50 -14
- package/src/utils/studioPreviewHelpers.test.ts +56 -0
- package/src/utils/studioPreviewHelpers.ts +51 -13
- package/src/utils/studioUiPreferences.test.ts +3 -0
- package/src/utils/studioUiPreferences.ts +4 -0
- package/src/utils/studioUrlState.test.ts +249 -0
- package/src/utils/studioUrlState.ts +135 -0
- package/dist/assets/index-DYqqzECY.js +0 -117
|
@@ -6,12 +6,21 @@ import { saveProjectFilesWithHistory } from "../utils/studioFileHistory";
|
|
|
6
6
|
import { primaryFontFamilyValue } from "../utils/studioFontHelpers";
|
|
7
7
|
import { getDomEditTargetKey, type DomEditSelection } from "../components/editor/domEditing";
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
applyStudioPathOffset,
|
|
10
|
+
applyStudioBoxSize,
|
|
11
|
+
applyStudioRotation,
|
|
12
|
+
clearStudioPathOffset,
|
|
13
|
+
clearStudioBoxSize,
|
|
14
|
+
clearStudioRotation,
|
|
14
15
|
} from "../components/editor/manualEdits";
|
|
16
|
+
import {
|
|
17
|
+
buildPathOffsetPatches,
|
|
18
|
+
buildBoxSizePatches,
|
|
19
|
+
buildRotationPatches,
|
|
20
|
+
buildClearPathOffsetPatches,
|
|
21
|
+
buildClearBoxSizePatches,
|
|
22
|
+
buildClearRotationPatches,
|
|
23
|
+
} from "../components/editor/manualEditsDom";
|
|
15
24
|
import {
|
|
16
25
|
removeStudioMotionForSelection,
|
|
17
26
|
type StudioGsapMotion,
|
|
@@ -48,15 +57,11 @@ export interface UseDomEditCommitsParams {
|
|
|
48
57
|
activeCompPath: string | null;
|
|
49
58
|
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
|
50
59
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
51
|
-
|
|
52
|
-
updateManifest: (manifest: StudioManualEditManifest) => StudioManualEditManifest,
|
|
53
|
-
options: { label: string; coalesceKey: string },
|
|
54
|
-
) => void;
|
|
60
|
+
queueDomEditSave: (save: () => Promise<void>) => Promise<void>;
|
|
55
61
|
commitStudioMotionManifestOptimistically: (
|
|
56
62
|
updateManifest: (manifest: StudioMotionManifest) => StudioMotionManifest,
|
|
57
63
|
options: { label: string; coalesceKey: string },
|
|
58
64
|
) => void;
|
|
59
|
-
applyCurrentStudioManualEditsToPreview: (iframe: HTMLIFrameElement | null) => void;
|
|
60
65
|
applyCurrentStudioMotionToPreview: (iframe: HTMLIFrameElement | null) => void;
|
|
61
66
|
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
62
67
|
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
@@ -69,15 +74,12 @@ export interface UseDomEditCommitsParams {
|
|
|
69
74
|
|
|
70
75
|
// From useDomSelection
|
|
71
76
|
domEditSelection: DomEditSelection | null;
|
|
72
|
-
domEditSelectionRef: React.MutableRefObject<DomEditSelection | null>;
|
|
73
|
-
domEditGroupSelectionsRef: React.MutableRefObject<DomEditSelection[]>;
|
|
74
77
|
applyDomSelection: (
|
|
75
78
|
selection: DomEditSelection | null,
|
|
76
79
|
options?: { revealPanel?: boolean; additive?: boolean; preserveGroup?: boolean },
|
|
77
80
|
) => void;
|
|
78
81
|
clearDomSelection: () => void;
|
|
79
82
|
refreshDomEditSelectionFromPreview: (selection: DomEditSelection) => void;
|
|
80
|
-
refreshDomEditGroupSelectionsFromPreview: (selections: DomEditSelection[]) => void;
|
|
81
83
|
buildDomSelectionFromTarget: (
|
|
82
84
|
target: HTMLElement,
|
|
83
85
|
options?: { preferClipAncestor?: boolean },
|
|
@@ -90,9 +92,8 @@ export function useDomEditCommits({
|
|
|
90
92
|
activeCompPath,
|
|
91
93
|
previewIframeRef,
|
|
92
94
|
showToast,
|
|
93
|
-
|
|
95
|
+
queueDomEditSave,
|
|
94
96
|
commitStudioMotionManifestOptimistically,
|
|
95
|
-
applyCurrentStudioManualEditsToPreview,
|
|
96
97
|
applyCurrentStudioMotionToPreview,
|
|
97
98
|
writeProjectFile,
|
|
98
99
|
domEditSaveTimestampRef,
|
|
@@ -103,11 +104,9 @@ export function useDomEditCommits({
|
|
|
103
104
|
projectIdRef,
|
|
104
105
|
reloadPreview,
|
|
105
106
|
domEditSelection,
|
|
106
|
-
domEditGroupSelectionsRef,
|
|
107
107
|
applyDomSelection,
|
|
108
108
|
clearDomSelection,
|
|
109
109
|
refreshDomEditSelectionFromPreview,
|
|
110
|
-
refreshDomEditGroupSelectionsFromPreview,
|
|
111
110
|
buildDomSelectionFromTarget,
|
|
112
111
|
}: UseDomEditCommitsParams) {
|
|
113
112
|
const resolveImportedFontAsset = useCallback(
|
|
@@ -211,98 +210,104 @@ export function useDomEditCommits({
|
|
|
211
210
|
resolveImportedFontAsset,
|
|
212
211
|
});
|
|
213
212
|
|
|
214
|
-
// ──
|
|
213
|
+
// ── Position patch helper ──
|
|
214
|
+
|
|
215
|
+
const commitPositionPatchToHtml = useCallback(
|
|
216
|
+
(
|
|
217
|
+
selection: DomEditSelection,
|
|
218
|
+
patches: Parameters<typeof applyPatchByTarget>[2][],
|
|
219
|
+
options: { label: string; coalesceKey: string; skipRefresh?: boolean },
|
|
220
|
+
) => {
|
|
221
|
+
void queueDomEditSave(async () => {
|
|
222
|
+
await persistDomEditOperations(selection, patches, {
|
|
223
|
+
label: options.label,
|
|
224
|
+
coalesceKey: options.coalesceKey,
|
|
225
|
+
skipRefresh: options.skipRefresh ?? true,
|
|
226
|
+
});
|
|
227
|
+
}).catch((error) => {
|
|
228
|
+
const message = error instanceof Error ? error.message : "Failed to save position";
|
|
229
|
+
showToast(message);
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
[persistDomEditOperations, queueDomEditSave, showToast],
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// ── Position commits ──
|
|
215
236
|
|
|
216
237
|
const handleDomPathOffsetCommit = useCallback(
|
|
217
238
|
(selection: DomEditSelection, next: { x: number; y: number }) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
},
|
|
224
|
-
);
|
|
225
|
-
refreshDomEditSelectionFromPreview(selection);
|
|
239
|
+
applyStudioPathOffset(selection.element, next);
|
|
240
|
+
commitPositionPatchToHtml(selection, buildPathOffsetPatches(selection.element), {
|
|
241
|
+
label: "Move layer",
|
|
242
|
+
coalesceKey: `path-offset:${getDomEditTargetKey(selection)}`,
|
|
243
|
+
});
|
|
226
244
|
},
|
|
227
|
-
[
|
|
245
|
+
[commitPositionPatchToHtml],
|
|
228
246
|
);
|
|
229
247
|
|
|
230
248
|
const handleDomGroupPathOffsetCommit = useCallback(
|
|
231
249
|
(updates: DomEditGroupPathOffsetCommit[]) => {
|
|
232
250
|
if (updates.length === 0) return;
|
|
233
251
|
const coalesceKey = updates
|
|
234
|
-
.map((
|
|
252
|
+
.map((u) => getDomEditTargetKey(u.selection))
|
|
235
253
|
.sort()
|
|
236
254
|
.join(":");
|
|
237
|
-
|
|
238
|
-
(
|
|
239
|
-
|
|
240
|
-
(nextManifest, update) =>
|
|
241
|
-
upsertStudioPathOffsetEdit(nextManifest, update.selection, update.next),
|
|
242
|
-
manifest,
|
|
243
|
-
),
|
|
244
|
-
{
|
|
255
|
+
for (const { selection, next } of updates) {
|
|
256
|
+
applyStudioPathOffset(selection.element, next);
|
|
257
|
+
commitPositionPatchToHtml(selection, buildPathOffsetPatches(selection.element), {
|
|
245
258
|
label: `Move ${updates.length} layers`,
|
|
246
259
|
coalesceKey: `group-path-offset:${coalesceKey}`,
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
refreshDomEditGroupSelectionsFromPreview(domEditGroupSelectionsRef.current);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
250
262
|
},
|
|
251
|
-
[
|
|
252
|
-
commitStudioManualEditManifestOptimistically,
|
|
253
|
-
domEditGroupSelectionsRef,
|
|
254
|
-
refreshDomEditGroupSelectionsFromPreview,
|
|
255
|
-
],
|
|
263
|
+
[commitPositionPatchToHtml],
|
|
256
264
|
);
|
|
257
265
|
|
|
258
266
|
const handleDomBoxSizeCommit = useCallback(
|
|
259
267
|
(selection: DomEditSelection, next: { width: number; height: number }) => {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
},
|
|
266
|
-
);
|
|
267
|
-
refreshDomEditSelectionFromPreview(selection);
|
|
268
|
+
applyStudioBoxSize(selection.element, next);
|
|
269
|
+
commitPositionPatchToHtml(selection, buildBoxSizePatches(selection.element), {
|
|
270
|
+
label: "Resize layer box",
|
|
271
|
+
coalesceKey: `box-size:${getDomEditTargetKey(selection)}`,
|
|
272
|
+
});
|
|
268
273
|
},
|
|
269
|
-
[
|
|
274
|
+
[commitPositionPatchToHtml],
|
|
270
275
|
);
|
|
271
276
|
|
|
272
277
|
const handleDomRotationCommit = useCallback(
|
|
273
278
|
(selection: DomEditSelection, next: { angle: number }) => {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
},
|
|
280
|
-
);
|
|
281
|
-
refreshDomEditSelectionFromPreview(selection);
|
|
279
|
+
applyStudioRotation(selection.element, next);
|
|
280
|
+
commitPositionPatchToHtml(selection, buildRotationPatches(selection.element), {
|
|
281
|
+
label: "Rotate layer",
|
|
282
|
+
coalesceKey: `rotation:${getDomEditTargetKey(selection)}`,
|
|
283
|
+
});
|
|
282
284
|
},
|
|
283
|
-
[
|
|
285
|
+
[commitPositionPatchToHtml],
|
|
284
286
|
);
|
|
285
287
|
|
|
286
288
|
const handleDomManualEditsReset = useCallback(
|
|
287
289
|
(selection: DomEditSelection) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
|
|
290
|
+
const element = selection.element;
|
|
291
|
+
const clearPatches = [
|
|
292
|
+
...buildClearPathOffsetPatches(element),
|
|
293
|
+
...buildClearBoxSizePatches(element),
|
|
294
|
+
...buildClearRotationPatches(element),
|
|
295
|
+
];
|
|
296
|
+
clearStudioPathOffset(element);
|
|
297
|
+
clearStudioBoxSize(element);
|
|
298
|
+
clearStudioRotation(element);
|
|
299
|
+
// skipRefresh:false triggers reloadPreview() which re-syncs selection on load
|
|
300
|
+
commitPositionPatchToHtml(selection, clearPatches, {
|
|
301
|
+
label: "Reset layer edits",
|
|
302
|
+
coalesceKey: `manual-reset:${getDomEditTargetKey(selection)}`,
|
|
303
|
+
skipRefresh: false,
|
|
304
|
+
});
|
|
297
305
|
},
|
|
298
|
-
[
|
|
299
|
-
applyCurrentStudioManualEditsToPreview,
|
|
300
|
-
commitStudioManualEditManifestOptimistically,
|
|
301
|
-
refreshDomEditSelectionFromPreview,
|
|
302
|
-
previewIframeRef,
|
|
303
|
-
],
|
|
306
|
+
[commitPositionPatchToHtml],
|
|
304
307
|
);
|
|
305
308
|
|
|
309
|
+
// ── Motion commits ──
|
|
310
|
+
|
|
306
311
|
const handleDomMotionCommit = useCallback(
|
|
307
312
|
(
|
|
308
313
|
selection: DomEditSelection,
|
|
@@ -2,7 +2,6 @@ import { useEffect } from "react";
|
|
|
2
2
|
import type { TimelineElement } from "../player";
|
|
3
3
|
import { STUDIO_INSPECTOR_PANELS_ENABLED } from "../components/editor/manualEditingAvailability";
|
|
4
4
|
import { findElementForSelection } from "../components/editor/domEditing";
|
|
5
|
-
import type { StudioManualEditManifest } from "../components/editor/manualEdits";
|
|
6
5
|
import type { StudioMotionManifest } from "../components/editor/studioMotion";
|
|
7
6
|
import type { ImportedFontAsset } from "../components/editor/fontAssets";
|
|
8
7
|
import type { EditHistoryKind } from "../utils/editHistory";
|
|
@@ -36,15 +35,11 @@ export interface UseDomEditSessionParams {
|
|
|
36
35
|
setRightPanelTab: (tab: RightPanelTab) => void;
|
|
37
36
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
38
37
|
refreshPreviewDocumentVersion: () => void;
|
|
39
|
-
|
|
40
|
-
updateManifest: (manifest: StudioManualEditManifest) => StudioManualEditManifest,
|
|
41
|
-
options: { label: string; coalesceKey: string },
|
|
42
|
-
) => void;
|
|
38
|
+
queueDomEditSave: (save: () => Promise<void>) => Promise<void>;
|
|
43
39
|
commitStudioMotionManifestOptimistically: (
|
|
44
40
|
updateManifest: (manifest: StudioMotionManifest) => StudioMotionManifest,
|
|
45
41
|
options: { label: string; coalesceKey: string },
|
|
46
42
|
) => void;
|
|
47
|
-
applyCurrentStudioManualEditsToPreview: (iframe: HTMLIFrameElement | null) => void;
|
|
48
43
|
applyCurrentStudioMotionToPreview: (iframe: HTMLIFrameElement | null) => void;
|
|
49
44
|
readProjectFile: (path: string) => Promise<string>;
|
|
50
45
|
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
@@ -85,9 +80,8 @@ export function useDomEditSession({
|
|
|
85
80
|
setRightPanelTab,
|
|
86
81
|
showToast,
|
|
87
82
|
refreshPreviewDocumentVersion,
|
|
88
|
-
|
|
83
|
+
queueDomEditSave,
|
|
89
84
|
commitStudioMotionManifestOptimistically,
|
|
90
|
-
applyCurrentStudioManualEditsToPreview,
|
|
91
85
|
applyCurrentStudioMotionToPreview,
|
|
92
86
|
readProjectFile: _readProjectFile,
|
|
93
87
|
writeProjectFile,
|
|
@@ -114,7 +108,6 @@ export function useDomEditSession({
|
|
|
114
108
|
domEditGroupSelections,
|
|
115
109
|
domEditHoverSelection,
|
|
116
110
|
domEditSelectionRef,
|
|
117
|
-
domEditGroupSelectionsRef,
|
|
118
111
|
applyDomSelection,
|
|
119
112
|
clearDomSelection,
|
|
120
113
|
buildDomSelectionFromTarget,
|
|
@@ -123,7 +116,6 @@ export function useDomEditSession({
|
|
|
123
116
|
buildDomSelectionForTimelineElement,
|
|
124
117
|
handleTimelineElementSelect,
|
|
125
118
|
refreshDomEditSelectionFromPreview,
|
|
126
|
-
refreshDomEditGroupSelectionsFromPreview,
|
|
127
119
|
} = useDomSelection({
|
|
128
120
|
projectId,
|
|
129
121
|
activeCompPath,
|
|
@@ -176,7 +168,6 @@ export function useDomEditSession({
|
|
|
176
168
|
captionEditMode,
|
|
177
169
|
compositionLoading,
|
|
178
170
|
previewIframeRef,
|
|
179
|
-
activeCompPath,
|
|
180
171
|
showToast,
|
|
181
172
|
applyDomSelection,
|
|
182
173
|
resolveDomSelectionFromPreviewPoint,
|
|
@@ -208,9 +199,8 @@ export function useDomEditSession({
|
|
|
208
199
|
activeCompPath,
|
|
209
200
|
previewIframeRef,
|
|
210
201
|
showToast,
|
|
211
|
-
|
|
202
|
+
queueDomEditSave,
|
|
212
203
|
commitStudioMotionManifestOptimistically,
|
|
213
|
-
applyCurrentStudioManualEditsToPreview,
|
|
214
204
|
applyCurrentStudioMotionToPreview,
|
|
215
205
|
writeProjectFile,
|
|
216
206
|
domEditSaveTimestampRef,
|
|
@@ -221,12 +211,9 @@ export function useDomEditSession({
|
|
|
221
211
|
projectIdRef,
|
|
222
212
|
reloadPreview,
|
|
223
213
|
domEditSelection,
|
|
224
|
-
domEditSelectionRef,
|
|
225
|
-
domEditGroupSelectionsRef,
|
|
226
214
|
applyDomSelection,
|
|
227
215
|
clearDomSelection,
|
|
228
216
|
refreshDomEditSelectionFromPreview,
|
|
229
|
-
refreshDomEditGroupSelectionsFromPreview,
|
|
230
217
|
buildDomSelectionFromTarget,
|
|
231
218
|
});
|
|
232
219
|
|
|
@@ -333,6 +320,7 @@ export function useDomEditSession({
|
|
|
333
320
|
handleBlockedDomMove,
|
|
334
321
|
handleDomManualDragStart,
|
|
335
322
|
handleDomEditElementDelete,
|
|
323
|
+
buildDomSelectionFromTarget,
|
|
336
324
|
buildDomSelectionForTimelineElement,
|
|
337
325
|
updateDomEditHoverSelection,
|
|
338
326
|
resolveImportedFontAsset,
|
|
@@ -36,6 +36,7 @@ export function useFileManager({
|
|
|
36
36
|
const [editingFile, setEditingFile] = useState<EditingFile | null>(null);
|
|
37
37
|
const [projectDir, setProjectDir] = useState<string | null>(null);
|
|
38
38
|
const [fileTree, setFileTree] = useState<string[]>([]);
|
|
39
|
+
const [fileTreeLoaded, setFileTreeLoaded] = useState(false);
|
|
39
40
|
|
|
40
41
|
// ── Refs ──
|
|
41
42
|
|
|
@@ -53,8 +54,12 @@ export function useFileManager({
|
|
|
53
54
|
|
|
54
55
|
// eslint-disable-next-line no-restricted-syntax
|
|
55
56
|
useEffect(() => {
|
|
56
|
-
if (!projectId)
|
|
57
|
+
if (!projectId) {
|
|
58
|
+
setFileTreeLoaded(false);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
57
61
|
let cancelled = false;
|
|
62
|
+
setFileTreeLoaded(false);
|
|
58
63
|
fetch(`/api/projects/${projectId}`)
|
|
59
64
|
.then((r) => r.json())
|
|
60
65
|
.then((data: { files?: string[]; dir?: string }) => {
|
|
@@ -63,6 +68,9 @@ export function useFileManager({
|
|
|
63
68
|
})
|
|
64
69
|
.catch(() => {
|
|
65
70
|
if (!cancelled) setProjectDir(null);
|
|
71
|
+
})
|
|
72
|
+
.finally(() => {
|
|
73
|
+
if (!cancelled) setFileTreeLoaded(true);
|
|
66
74
|
});
|
|
67
75
|
return () => {
|
|
68
76
|
cancelled = true;
|
|
@@ -396,6 +404,7 @@ export function useFileManager({
|
|
|
396
404
|
setEditingFile,
|
|
397
405
|
projectDir,
|
|
398
406
|
fileTree,
|
|
407
|
+
fileTreeLoaded,
|
|
399
408
|
setFileTree,
|
|
400
409
|
|
|
401
410
|
// Refs
|