@hyperframes/studio 0.8.100 → 0.8.101
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-fWt87gyh.js → hyperframes-player-CFJLrHRF.js} +1 -1
- package/dist/assets/{index-D6TjRAvz.js → index-BBNVnnyH.js} +114 -114
- package/dist/assets/{index-DofdzdxJ.js → index-C33EDBL2.js} +1 -1
- package/dist/assets/{index-D34Oy5DN.js → index-CJOzbslX.js} +1 -1
- package/dist/{chunk-U3EE4ACX.js → chunk-7X4XRP3L.js} +14 -13
- package/dist/chunk-7X4XRP3L.js.map +1 -0
- package/dist/index.d.ts +91 -21
- package/dist/index.html +1 -1
- package/dist/index.js +403 -226
- package/dist/index.js.map +1 -1
- package/dist/player.js +1 -1
- package/package.json +8 -8
- package/src/App.tsx +2 -0
- package/src/components/dock/Dock.test.tsx +78 -3
- package/src/components/dock/Dock.tsx +45 -18
- package/src/components/dock/dockLayout.ts +27 -14
- package/src/components/dock/dockLayoutSchema.ts +11 -4
- package/src/components/dock/dockLayoutStore.ts +4 -2
- package/src/hooks/appHotkeysDispatch.ts +1 -7
- package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +67 -2
- package/src/hooks/useAppHotkeys.ts +18 -45
- package/src/hooks/useEditHistoryActions.paint.test.tsx +166 -0
- package/src/hooks/useEditHistoryActions.test.tsx +67 -9
- package/src/hooks/useEditHistoryActions.ts +36 -8
- package/src/hooks/usePersistentEditHistory.test.ts +46 -0
- package/src/hooks/usePersistentEditHistory.ts +101 -19
- package/src/hooks/usePreviewPersistence.ts +25 -2
- package/src/hooks/useStudioExternalFileChanges.ts +7 -3
- package/src/index.ts +4 -0
- package/src/player/components/timelineDragDrop.test.tsx +61 -5
- package/src/player/components/timelineDragDrop.ts +29 -8
- package/src/player/components/useTimelineProviderState.tsx +1 -0
- package/src/utils/domEditSaveQueue.ts +7 -2
- package/src/utils/gsapSoftReload.ts +1 -1
- package/src/utils/gsapUndoRestore.test.ts +46 -0
- package/src/utils/gsapUndoRestore.ts +166 -91
- package/src/utils/studioPendingEdits.ts +14 -5
- package/src/utils/studioUiPreferences.ts +8 -6
- package/dist/chunk-U3EE4ACX.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
isTimelineIgnoredElement,
|
|
44
44
|
liveTime,
|
|
45
45
|
panelsInZone,
|
|
46
|
+
parseDockLayout,
|
|
46
47
|
playbackStartAttributeForElement,
|
|
47
48
|
previewElementFinder,
|
|
48
49
|
readLayerRevealPriorZ,
|
|
@@ -66,7 +67,7 @@ import {
|
|
|
66
67
|
whenPreviewBooted,
|
|
67
68
|
wrapTimeline,
|
|
68
69
|
writeStudioUiPreferences
|
|
69
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-7X4XRP3L.js";
|
|
70
71
|
import {
|
|
71
72
|
Button,
|
|
72
73
|
ContextMenu,
|
|
@@ -3338,6 +3339,73 @@ function isLaneFree(elements, track2, start, end, excludeKey) {
|
|
|
3338
3339
|
);
|
|
3339
3340
|
}
|
|
3340
3341
|
|
|
3342
|
+
// src/player/components/timelineSnapping.ts
|
|
3343
|
+
function resolveSnapGuide(moving, trimming) {
|
|
3344
|
+
const source = moving?.started ? moving : trimming;
|
|
3345
|
+
if (source?.snapTime == null || source.snapType == null) return null;
|
|
3346
|
+
return { time: source.snapTime, type: source.snapType };
|
|
3347
|
+
}
|
|
3348
|
+
var TIMELINE_SNAP_PX = 8;
|
|
3349
|
+
var TYPE_PRIORITY = {
|
|
3350
|
+
playhead: 0,
|
|
3351
|
+
"clip-edge": 1,
|
|
3352
|
+
beat: 2
|
|
3353
|
+
};
|
|
3354
|
+
function collectTimelineSnapTargets(input) {
|
|
3355
|
+
const byTime = /* @__PURE__ */ new Map();
|
|
3356
|
+
const add = (time, type) => {
|
|
3357
|
+
if (!Number.isFinite(time) || time < 0) return;
|
|
3358
|
+
const rounded2 = Math.round(time * 1e3) / 1e3;
|
|
3359
|
+
const existing = byTime.get(rounded2);
|
|
3360
|
+
if (!existing || TYPE_PRIORITY[type] < TYPE_PRIORITY[existing.type]) {
|
|
3361
|
+
byTime.set(rounded2, { time: rounded2, type });
|
|
3362
|
+
}
|
|
3363
|
+
};
|
|
3364
|
+
for (const beat of input.beatTimes) add(beat, "beat");
|
|
3365
|
+
for (const el of input.elements) {
|
|
3366
|
+
if (input.excludeElementKey != null && (el.key ?? el.id) === input.excludeElementKey) continue;
|
|
3367
|
+
add(el.start, "clip-edge");
|
|
3368
|
+
add(el.start + el.duration, "clip-edge");
|
|
3369
|
+
}
|
|
3370
|
+
if (input.playheadTime != null && input.includePlayhead !== false)
|
|
3371
|
+
add(input.playheadTime, "playhead");
|
|
3372
|
+
return Array.from(byTime.values()).sort((a, b) => a.time - b.time);
|
|
3373
|
+
}
|
|
3374
|
+
function snapTimelineTime(time, targets, thresholdSecs) {
|
|
3375
|
+
let best = null;
|
|
3376
|
+
let bestDist = thresholdSecs;
|
|
3377
|
+
for (const target of targets) {
|
|
3378
|
+
const d = Math.abs(target.time - time);
|
|
3379
|
+
if (d < bestDist || d === bestDist && best && TYPE_PRIORITY[target.type] < TYPE_PRIORITY[best.type]) {
|
|
3380
|
+
bestDist = d;
|
|
3381
|
+
best = target;
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
return best ? { time: best.time, target: best } : { time, target: null };
|
|
3385
|
+
}
|
|
3386
|
+
function snapMoveToTargets(start, duration, targets, pixelsPerSecond, timelineDuration2) {
|
|
3387
|
+
if (targets.length === 0) return { start, snapTime: null, snapType: null };
|
|
3388
|
+
const thresholdSecs = TIMELINE_SNAP_PX / Math.max(pixelsPerSecond, 1);
|
|
3389
|
+
const startSnap = snapTimelineTime(start, targets, thresholdSecs);
|
|
3390
|
+
const endSnap = snapTimelineTime(start + duration, targets, thresholdSecs);
|
|
3391
|
+
const startMoved = startSnap.target !== null;
|
|
3392
|
+
const endMoved = endSnap.target !== null;
|
|
3393
|
+
let candidate = start;
|
|
3394
|
+
let target = null;
|
|
3395
|
+
if (startMoved && (!endMoved || Math.abs(startSnap.time - start) <= Math.abs(endSnap.time - (start + duration)))) {
|
|
3396
|
+
candidate = startSnap.time;
|
|
3397
|
+
target = startSnap.target;
|
|
3398
|
+
} else if (endMoved) {
|
|
3399
|
+
candidate = endSnap.time - duration;
|
|
3400
|
+
target = endSnap.target;
|
|
3401
|
+
}
|
|
3402
|
+
const maxStart = Math.max(0, timelineDuration2 - duration);
|
|
3403
|
+
const roundedCandidate = Math.round(candidate * 1e3) / 1e3;
|
|
3404
|
+
const clamped = Math.max(0, Math.min(maxStart, roundedCandidate));
|
|
3405
|
+
if (target && Math.abs(clamped - roundedCandidate) > 1e-6) target = null;
|
|
3406
|
+
return { start: clamped, snapTime: target?.time ?? null, snapType: target?.type ?? null };
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3341
3409
|
// src/player/components/timelineDragDrop.ts
|
|
3342
3410
|
function applyJsonDropPayload(raw, pick, apply, placement) {
|
|
3343
3411
|
try {
|
|
@@ -3358,12 +3426,20 @@ function resolveDropInsertRow(contentY, rowHeights, trackCount) {
|
|
|
3358
3426
|
if (rowFloat >= trackCount) return null;
|
|
3359
3427
|
return resolveInsertRow(rowFloat, trackCount);
|
|
3360
3428
|
}
|
|
3361
|
-
function
|
|
3429
|
+
function alignDropStart(placement, elements, pixelsPerSecond) {
|
|
3430
|
+
const laneHasClips = placement.insertRow == null && elements.some((el) => el.track === placement.track);
|
|
3431
|
+
if (!laneHasClips) return { ...placement, start: 0 };
|
|
3432
|
+
if (!usePlayerStore.getState().timelineSnapEnabled) return placement;
|
|
3433
|
+
const targets = collectTimelineSnapTargets({ elements, playheadTime: null, beatTimes: [] });
|
|
3434
|
+
const threshold = TIMELINE_SNAP_PX / Math.max(pixelsPerSecond, 1);
|
|
3435
|
+
return { ...placement, start: snapTimelineTime(placement.start, targets, threshold).time };
|
|
3436
|
+
}
|
|
3437
|
+
function placeDrop(geometry, elements, clientX, clientY, canInsertTrack) {
|
|
3362
3438
|
const placement = resolveTimelineAssetDrop(geometry, clientX, clientY);
|
|
3363
|
-
if (!canInsertTrack) return placement;
|
|
3364
3439
|
const contentY = clientY - geometry.rectTop + geometry.scrollTop;
|
|
3365
|
-
const insertRow = resolveDropInsertRow(contentY, geometry.rowHeights, geometry.trackOrder.length);
|
|
3366
|
-
|
|
3440
|
+
const insertRow = canInsertTrack ? resolveDropInsertRow(contentY, geometry.rowHeights, geometry.trackOrder.length) : null;
|
|
3441
|
+
const aimed = insertRow == null ? placement : { ...placement, insertRow, trackOrder: geometry.trackOrder };
|
|
3442
|
+
return alignDropStart(aimed, elements, geometry.pixelsPerSecond);
|
|
3367
3443
|
}
|
|
3368
3444
|
function invokeDropCallback(callback) {
|
|
3369
3445
|
try {
|
|
@@ -3391,6 +3467,7 @@ function useTimelineAssetDrop({
|
|
|
3391
3467
|
scrollRef,
|
|
3392
3468
|
ppsRef,
|
|
3393
3469
|
trackOrderRef,
|
|
3470
|
+
elementsRef,
|
|
3394
3471
|
rowGeometryRef,
|
|
3395
3472
|
contentOrigin,
|
|
3396
3473
|
onFileDrop,
|
|
@@ -3458,12 +3535,13 @@ function useTimelineAssetDrop({
|
|
|
3458
3535
|
rowHeights: rowGeometryRef.current.rowHeights,
|
|
3459
3536
|
trackOrder: trackOrderRef.current
|
|
3460
3537
|
},
|
|
3538
|
+
elementsRef.current,
|
|
3461
3539
|
clientX,
|
|
3462
3540
|
clientY,
|
|
3463
3541
|
canInsertTrack
|
|
3464
3542
|
);
|
|
3465
3543
|
},
|
|
3466
|
-
[scrollRef, ppsRef, trackOrderRef, rowGeometryRef, contentOrigin]
|
|
3544
|
+
[scrollRef, ppsRef, trackOrderRef, elementsRef, rowGeometryRef, contentOrigin]
|
|
3467
3545
|
);
|
|
3468
3546
|
const handleAssetDragOver = useCallback6(
|
|
3469
3547
|
(e) => {
|
|
@@ -3558,73 +3636,6 @@ function useTimelineAssetDrop({
|
|
|
3558
3636
|
// src/player/components/useTimelineClipDrag.ts
|
|
3559
3637
|
import { useRef as useRef7, useState as useState4, useCallback as useCallback7, useMemo as useMemo3, useEffect as useEffect7 } from "react";
|
|
3560
3638
|
|
|
3561
|
-
// src/player/components/timelineSnapping.ts
|
|
3562
|
-
function resolveSnapGuide(moving, trimming) {
|
|
3563
|
-
const source = moving?.started ? moving : trimming;
|
|
3564
|
-
if (source?.snapTime == null || source.snapType == null) return null;
|
|
3565
|
-
return { time: source.snapTime, type: source.snapType };
|
|
3566
|
-
}
|
|
3567
|
-
var TIMELINE_SNAP_PX = 8;
|
|
3568
|
-
var TYPE_PRIORITY = {
|
|
3569
|
-
playhead: 0,
|
|
3570
|
-
"clip-edge": 1,
|
|
3571
|
-
beat: 2
|
|
3572
|
-
};
|
|
3573
|
-
function collectTimelineSnapTargets(input) {
|
|
3574
|
-
const byTime = /* @__PURE__ */ new Map();
|
|
3575
|
-
const add = (time, type) => {
|
|
3576
|
-
if (!Number.isFinite(time) || time < 0) return;
|
|
3577
|
-
const rounded2 = Math.round(time * 1e3) / 1e3;
|
|
3578
|
-
const existing = byTime.get(rounded2);
|
|
3579
|
-
if (!existing || TYPE_PRIORITY[type] < TYPE_PRIORITY[existing.type]) {
|
|
3580
|
-
byTime.set(rounded2, { time: rounded2, type });
|
|
3581
|
-
}
|
|
3582
|
-
};
|
|
3583
|
-
for (const beat of input.beatTimes) add(beat, "beat");
|
|
3584
|
-
for (const el of input.elements) {
|
|
3585
|
-
if (input.excludeElementKey != null && (el.key ?? el.id) === input.excludeElementKey) continue;
|
|
3586
|
-
add(el.start, "clip-edge");
|
|
3587
|
-
add(el.start + el.duration, "clip-edge");
|
|
3588
|
-
}
|
|
3589
|
-
if (input.playheadTime != null && input.includePlayhead !== false)
|
|
3590
|
-
add(input.playheadTime, "playhead");
|
|
3591
|
-
return Array.from(byTime.values()).sort((a, b) => a.time - b.time);
|
|
3592
|
-
}
|
|
3593
|
-
function snapTimelineTime(time, targets, thresholdSecs) {
|
|
3594
|
-
let best = null;
|
|
3595
|
-
let bestDist = thresholdSecs;
|
|
3596
|
-
for (const target of targets) {
|
|
3597
|
-
const d = Math.abs(target.time - time);
|
|
3598
|
-
if (d < bestDist || d === bestDist && best && TYPE_PRIORITY[target.type] < TYPE_PRIORITY[best.type]) {
|
|
3599
|
-
bestDist = d;
|
|
3600
|
-
best = target;
|
|
3601
|
-
}
|
|
3602
|
-
}
|
|
3603
|
-
return best ? { time: best.time, target: best } : { time, target: null };
|
|
3604
|
-
}
|
|
3605
|
-
function snapMoveToTargets(start, duration, targets, pixelsPerSecond, timelineDuration2) {
|
|
3606
|
-
if (targets.length === 0) return { start, snapTime: null, snapType: null };
|
|
3607
|
-
const thresholdSecs = TIMELINE_SNAP_PX / Math.max(pixelsPerSecond, 1);
|
|
3608
|
-
const startSnap = snapTimelineTime(start, targets, thresholdSecs);
|
|
3609
|
-
const endSnap = snapTimelineTime(start + duration, targets, thresholdSecs);
|
|
3610
|
-
const startMoved = startSnap.target !== null;
|
|
3611
|
-
const endMoved = endSnap.target !== null;
|
|
3612
|
-
let candidate = start;
|
|
3613
|
-
let target = null;
|
|
3614
|
-
if (startMoved && (!endMoved || Math.abs(startSnap.time - start) <= Math.abs(endSnap.time - (start + duration)))) {
|
|
3615
|
-
candidate = startSnap.time;
|
|
3616
|
-
target = startSnap.target;
|
|
3617
|
-
} else if (endMoved) {
|
|
3618
|
-
candidate = endSnap.time - duration;
|
|
3619
|
-
target = endSnap.target;
|
|
3620
|
-
}
|
|
3621
|
-
const maxStart = Math.max(0, timelineDuration2 - duration);
|
|
3622
|
-
const roundedCandidate = Math.round(candidate * 1e3) / 1e3;
|
|
3623
|
-
const clamped = Math.max(0, Math.min(maxStart, roundedCandidate));
|
|
3624
|
-
if (target && Math.abs(clamped - roundedCandidate) > 1e-6) target = null;
|
|
3625
|
-
return { start: clamped, snapTime: target?.time ?? null, snapType: target?.type ?? null };
|
|
3626
|
-
}
|
|
3627
|
-
|
|
3628
3639
|
// src/player/components/timelineAuthoredMoveTarget.ts
|
|
3629
3640
|
function canMoveTimelineElement(element) {
|
|
3630
3641
|
return getTimelineEditCapabilities({
|
|
@@ -13033,6 +13044,7 @@ function useTimelineProviderState({
|
|
|
13033
13044
|
scrollRef,
|
|
13034
13045
|
ppsRef,
|
|
13035
13046
|
trackOrderRef,
|
|
13047
|
+
elementsRef: timelineElementsRef,
|
|
13036
13048
|
rowGeometryRef,
|
|
13037
13049
|
contentOrigin,
|
|
13038
13050
|
onFileDrop: pinnedOnFileDrop,
|
|
@@ -33955,6 +33967,7 @@ var DomEditSaveQueueOpenError = class extends Error {
|
|
|
33955
33967
|
function createDomEditSaveQueue(options = {}) {
|
|
33956
33968
|
const failureThreshold = options.failureThreshold ?? DEFAULT_FAILURE_THRESHOLD;
|
|
33957
33969
|
let tail = Promise.resolve();
|
|
33970
|
+
let queuedCount = 0;
|
|
33958
33971
|
let consecutiveFailures = 0;
|
|
33959
33972
|
let breakerOpen = false;
|
|
33960
33973
|
let drainError = null;
|
|
@@ -33993,13 +34006,14 @@ function createDomEditSaveQueue(options = {}) {
|
|
|
33993
34006
|
return {
|
|
33994
34007
|
enqueue(save) {
|
|
33995
34008
|
if (breakerOpen) return Promise.reject(new DomEditSaveQueueOpenError());
|
|
34009
|
+
queuedCount += 1;
|
|
33996
34010
|
const queued = tail.catch(() => void 0).then(() => {
|
|
33997
34011
|
if (breakerOpen) throw new DomEditSaveQueueOpenError();
|
|
33998
34012
|
return run(save);
|
|
33999
34013
|
});
|
|
34000
34014
|
tail = queued.then(
|
|
34001
|
-
() => void
|
|
34002
|
-
() => void
|
|
34015
|
+
() => void (queuedCount -= 1),
|
|
34016
|
+
() => void (queuedCount -= 1)
|
|
34003
34017
|
);
|
|
34004
34018
|
return queued;
|
|
34005
34019
|
},
|
|
@@ -34011,6 +34025,7 @@ function createDomEditSaveQueue(options = {}) {
|
|
|
34011
34025
|
if (drainError != null) return { status: "failed", error: drainError };
|
|
34012
34026
|
return { status: "clean" };
|
|
34013
34027
|
},
|
|
34028
|
+
isIdle: () => queuedCount === 0,
|
|
34014
34029
|
reset,
|
|
34015
34030
|
destroy() {
|
|
34016
34031
|
reset(false);
|
|
@@ -45120,12 +45135,13 @@ import { CornersIn, CornersOut, DotsThree, X as X2 } from "@phosphor-icons/react
|
|
|
45120
45135
|
import { create as create6 } from "zustand";
|
|
45121
45136
|
var useDockLayoutStore = create6((set, get) => ({
|
|
45122
45137
|
controller: null,
|
|
45138
|
+
panels: PANEL_IDS,
|
|
45123
45139
|
openPanels: new Set(PANEL_IDS),
|
|
45124
45140
|
visiblePanels: new Set(PANEL_IDS),
|
|
45125
45141
|
activePanel: null,
|
|
45126
45142
|
lastActive: {},
|
|
45127
45143
|
pendingActivation: null,
|
|
45128
|
-
attach: (controller) => set({ controller }),
|
|
45144
|
+
attach: (controller, panels = PANEL_IDS) => set({ controller, panels }),
|
|
45129
45145
|
detach: () => set({ controller: null, lastActive: {} }),
|
|
45130
45146
|
sync: (snapshot3) => set((state) => {
|
|
45131
45147
|
const lastActive = { ...state.lastActive };
|
|
@@ -45401,6 +45417,8 @@ var MIN_TIMELINE_H = 100;
|
|
|
45401
45417
|
var MIN_SIDE_W = 200;
|
|
45402
45418
|
var MIN_SIDE_W_FLOOR = 120;
|
|
45403
45419
|
var DEFAULT_TIMELINE_H = 360;
|
|
45420
|
+
var DEFAULT_LEFT = ["compositions", "assets", "code", "catalog"];
|
|
45421
|
+
var DEFAULT_RIGHT = ["design", "layers", "renders", "variables"];
|
|
45404
45422
|
function defaultSideWidths(viewportWidth) {
|
|
45405
45423
|
const floor = sideMinimumWidth(viewportWidth);
|
|
45406
45424
|
const left = Math.max(floor, Math.min(384, Math.round(viewportWidth * 0.257)));
|
|
@@ -45456,24 +45474,25 @@ function addRegisteredPanel(api, id, position) {
|
|
|
45456
45474
|
...position ? { position } : {}
|
|
45457
45475
|
});
|
|
45458
45476
|
}
|
|
45459
|
-
function
|
|
45477
|
+
function addSideColumn(api, ids, panels, direction) {
|
|
45478
|
+
const [first, ...rest] = ids.filter((id) => panels.includes(id));
|
|
45479
|
+
if (!first) return void 0;
|
|
45480
|
+
addRegisteredPanel(api, first, { referencePanel: "preview", direction });
|
|
45481
|
+
for (const id of rest)
|
|
45482
|
+
addRegisteredPanel(api, id, { referencePanel: first, direction: "within" });
|
|
45483
|
+
api.getPanel(first)?.api.setActive();
|
|
45484
|
+
return first;
|
|
45485
|
+
}
|
|
45486
|
+
function buildEditLayout(api, viewportWidth, panels = PANEL_IDS) {
|
|
45460
45487
|
api.clear();
|
|
45461
45488
|
const widths = defaultSideWidths(viewportWidth);
|
|
45462
45489
|
addRegisteredPanel(api, "preview");
|
|
45463
45490
|
addRegisteredPanel(api, "timeline", { referencePanel: "preview", direction: "below" });
|
|
45464
|
-
|
|
45465
|
-
|
|
45466
|
-
addRegisteredPanel(api, id, { referencePanel: "compositions", direction: "within" });
|
|
45467
|
-
}
|
|
45468
|
-
addRegisteredPanel(api, "design", { referencePanel: "preview", direction: "right" });
|
|
45469
|
-
for (const id of ["layers", "renders", "variables"]) {
|
|
45470
|
-
addRegisteredPanel(api, id, { referencePanel: "design", direction: "within" });
|
|
45471
|
-
}
|
|
45472
|
-
api.getPanel("compositions")?.api.setActive();
|
|
45473
|
-
api.getPanel("design")?.api.setActive();
|
|
45491
|
+
const left = addSideColumn(api, DEFAULT_LEFT, panels, "left");
|
|
45492
|
+
const right = addSideColumn(api, DEFAULT_RIGHT, panels, "right");
|
|
45474
45493
|
applySideMinimums(api, viewportWidth);
|
|
45475
|
-
api.getPanel(
|
|
45476
|
-
api.getPanel(
|
|
45494
|
+
if (left) api.getPanel(left)?.group.api.setSize({ width: widths.left });
|
|
45495
|
+
if (right) api.getPanel(right)?.group.api.setSize({ width: widths.right });
|
|
45477
45496
|
api.getPanel("timeline")?.group.api.setSize({ height: DEFAULT_TIMELINE_H });
|
|
45478
45497
|
}
|
|
45479
45498
|
|
|
@@ -45518,10 +45537,11 @@ function snapshot(api) {
|
|
|
45518
45537
|
groupActivePanels
|
|
45519
45538
|
};
|
|
45520
45539
|
}
|
|
45521
|
-
function createController(api) {
|
|
45540
|
+
function createController(api, options) {
|
|
45522
45541
|
const open = (id) => {
|
|
45523
45542
|
if (api.getPanel(id)) return;
|
|
45524
|
-
const { zone, reopen } = PANEL_DEFINITIONS[id];
|
|
45543
|
+
const { zone, reopen: preferred } = PANEL_DEFINITIONS[id];
|
|
45544
|
+
const reopen = zone === "center" || options.panels.includes(preferred.near) ? preferred : { near: "preview", direction: zone };
|
|
45525
45545
|
const sibling = zone === "center" ? void 0 : panelsInZone(zone).find((other) => other !== id && api.getPanel(other));
|
|
45526
45546
|
if (sibling) {
|
|
45527
45547
|
addRegisteredPanel(api, id, { referencePanel: sibling, direction: "within" });
|
|
@@ -45543,11 +45563,12 @@ function createController(api) {
|
|
|
45543
45563
|
},
|
|
45544
45564
|
setTitle: (id, title) => api.getPanel(id)?.api.setTitle(title),
|
|
45545
45565
|
setGroupVisible: (id, visible) => api.getPanel(id)?.group.api.setVisible(visible),
|
|
45546
|
-
reset: () => buildEditLayout(api,
|
|
45566
|
+
reset: () => buildEditLayout(api, options.dockWidth(), options.panels)
|
|
45547
45567
|
};
|
|
45548
45568
|
}
|
|
45549
|
-
function restoreOrBuild(api, projectId) {
|
|
45550
|
-
const
|
|
45569
|
+
function restoreOrBuild(api, projectId, options) {
|
|
45570
|
+
const { dockLayout } = readStudioUiPreferences(void 0, projectId, options.storageKey);
|
|
45571
|
+
const stored = parseDockLayout(dockLayout, options.panels);
|
|
45551
45572
|
if (stored) {
|
|
45552
45573
|
try {
|
|
45553
45574
|
api.fromJSON(stored);
|
|
@@ -45555,9 +45576,16 @@ function restoreOrBuild(api, projectId) {
|
|
|
45555
45576
|
} catch {
|
|
45556
45577
|
}
|
|
45557
45578
|
}
|
|
45558
|
-
buildEditLayout(api,
|
|
45579
|
+
buildEditLayout(api, options.dockWidth(), options.panels);
|
|
45559
45580
|
}
|
|
45560
|
-
|
|
45581
|
+
var windowWidth = () => window.innerWidth;
|
|
45582
|
+
function Root({
|
|
45583
|
+
projectId,
|
|
45584
|
+
panels = PANEL_IDS,
|
|
45585
|
+
storageKey,
|
|
45586
|
+
dockWidth = windowWidth,
|
|
45587
|
+
children
|
|
45588
|
+
}) {
|
|
45561
45589
|
const [slots, setSlots] = useState50({});
|
|
45562
45590
|
const registerSlot = useCallback70((id, element) => {
|
|
45563
45591
|
setSlots((prev) => {
|
|
@@ -45574,17 +45602,18 @@ function Root({ projectId, children }) {
|
|
|
45574
45602
|
const onReady = useCallback70(
|
|
45575
45603
|
({ api }) => {
|
|
45576
45604
|
disposeRef.current();
|
|
45577
|
-
|
|
45578
|
-
|
|
45605
|
+
const options = { panels, storageKey, dockWidth };
|
|
45606
|
+
restoreOrBuild(api, projectId, options);
|
|
45607
|
+
applySideMinimums(api, dockWidth());
|
|
45579
45608
|
const root = api.groups[0]?.element.closest(".hf-dock");
|
|
45580
45609
|
const disposeAccessibility = root ? installDockAccessibility(api, root) : () => {
|
|
45581
45610
|
};
|
|
45582
45611
|
const disposeTabFill = root ? installTabFill(api, root) : () => {
|
|
45583
45612
|
};
|
|
45584
|
-
const resizeObserver = new ResizeObserver(() => applySideMinimums(api,
|
|
45613
|
+
const resizeObserver = new ResizeObserver(() => applySideMinimums(api, dockWidth()));
|
|
45585
45614
|
if (root) resizeObserver.observe(root);
|
|
45586
45615
|
const store = useDockLayoutStore.getState();
|
|
45587
|
-
store.attach(createController(api));
|
|
45616
|
+
store.attach(createController(api, options), panels);
|
|
45588
45617
|
store.sync(snapshot(api));
|
|
45589
45618
|
const pending2 = store.takePendingActivation();
|
|
45590
45619
|
if (pending2) api.getPanel(pending2)?.api.setActive();
|
|
@@ -45592,7 +45621,7 @@ function Root({ projectId, children }) {
|
|
|
45592
45621
|
const persist2 = () => {
|
|
45593
45622
|
clearTimeout(timer);
|
|
45594
45623
|
timer = setTimeout(() => {
|
|
45595
|
-
writeStudioUiPreferences({ dockLayout: api.toJSON() }, void 0, projectId);
|
|
45624
|
+
writeStudioUiPreferences({ dockLayout: api.toJSON() }, void 0, projectId, storageKey);
|
|
45596
45625
|
}, PERSIST_DEBOUNCE_MS2);
|
|
45597
45626
|
};
|
|
45598
45627
|
const onDockChange = () => {
|
|
@@ -45601,11 +45630,11 @@ function Root({ projectId, children }) {
|
|
|
45601
45630
|
};
|
|
45602
45631
|
const subscriptions = [
|
|
45603
45632
|
api.onDidAddPanel(() => {
|
|
45604
|
-
applySideMinimums(api);
|
|
45633
|
+
applySideMinimums(api, dockWidth());
|
|
45605
45634
|
onDockChange();
|
|
45606
45635
|
}),
|
|
45607
45636
|
api.onDidMovePanel(() => {
|
|
45608
|
-
applySideMinimums(api);
|
|
45637
|
+
applySideMinimums(api, dockWidth());
|
|
45609
45638
|
onDockChange();
|
|
45610
45639
|
}),
|
|
45611
45640
|
api.onDidRemovePanel(onDockChange),
|
|
@@ -45621,7 +45650,7 @@ function Root({ projectId, children }) {
|
|
|
45621
45650
|
useDockLayoutStore.getState().detach();
|
|
45622
45651
|
};
|
|
45623
45652
|
},
|
|
45624
|
-
[projectId]
|
|
45653
|
+
[projectId, panels, storageKey, dockWidth]
|
|
45625
45654
|
);
|
|
45626
45655
|
return /* @__PURE__ */ jsxs71(SlotsContext.Provider, { value: { slots, registerSlot }, children: [
|
|
45627
45656
|
/* @__PURE__ */ jsx101(
|
|
@@ -45656,6 +45685,7 @@ function Panel({ id, title, children }) {
|
|
|
45656
45685
|
}
|
|
45657
45686
|
function WindowMenu() {
|
|
45658
45687
|
const openPanels = useDockLayoutStore((state) => state.openPanels);
|
|
45688
|
+
const panels = useDockLayoutStore((state) => state.panels);
|
|
45659
45689
|
const togglePanel = useDockLayoutStore((state) => state.togglePanel);
|
|
45660
45690
|
const resetLayout = useDockLayoutStore((state) => state.resetLayout);
|
|
45661
45691
|
const [open, setOpen] = useState50(false);
|
|
@@ -45686,7 +45716,7 @@ function WindowMenu() {
|
|
|
45686
45716
|
role: "menu",
|
|
45687
45717
|
className: "absolute right-0 top-8 z-50 w-44 rounded-md border border-neutral-800 bg-neutral-900 py-1 shadow-lg",
|
|
45688
45718
|
children: [
|
|
45689
|
-
|
|
45719
|
+
panels.map((id) => /* @__PURE__ */ jsxs71(
|
|
45690
45720
|
"button",
|
|
45691
45721
|
{
|
|
45692
45722
|
type: "button",
|
|
@@ -49064,6 +49094,13 @@ function inspectDrainFailures(results) {
|
|
|
49064
49094
|
}
|
|
49065
49095
|
return { firstFailure };
|
|
49066
49096
|
}
|
|
49097
|
+
function focusedField() {
|
|
49098
|
+
const active = document.activeElement;
|
|
49099
|
+
return active instanceof HTMLElement && active.matches('input, textarea, select, [contenteditable="true"], [role="textbox"]') ? active : null;
|
|
49100
|
+
}
|
|
49101
|
+
function hasStudioPendingEdits() {
|
|
49102
|
+
return pendingEditPromises.size > 0 || focusedField() !== null;
|
|
49103
|
+
}
|
|
49067
49104
|
function trackStudioPendingEdit(result) {
|
|
49068
49105
|
if (!result) return void 0;
|
|
49069
49106
|
const promise = Promise.resolve(result);
|
|
@@ -49075,8 +49112,8 @@ function trackStudioPendingEdit(result) {
|
|
|
49075
49112
|
return promise;
|
|
49076
49113
|
}
|
|
49077
49114
|
async function flushStudioPendingEdits() {
|
|
49078
|
-
const active =
|
|
49079
|
-
if (active
|
|
49115
|
+
const active = focusedField();
|
|
49116
|
+
if (active) {
|
|
49080
49117
|
active.blur();
|
|
49081
49118
|
await Promise.resolve();
|
|
49082
49119
|
await waitForPostBlurEffects();
|
|
@@ -68103,6 +68140,60 @@ function useCaptionSync(projectId) {
|
|
|
68103
68140
|
// src/hooks/usePersistentEditHistory.ts
|
|
68104
68141
|
import { useCallback as useCallback97, useEffect as useEffect105, useMemo as useMemo65, useRef as useRef124, useState as useState104 } from "react";
|
|
68105
68142
|
var EMPTY3 = { entries: [], back: null, forward: null };
|
|
68143
|
+
var OWN_ENTRIES_KEPT = 100;
|
|
68144
|
+
function createOwnHistory() {
|
|
68145
|
+
const own = /* @__PURE__ */ new Map();
|
|
68146
|
+
let next = null;
|
|
68147
|
+
let changes = 0;
|
|
68148
|
+
const remember = (id, files) => {
|
|
68149
|
+
const known = own.get(id) ?? {};
|
|
68150
|
+
for (const [path, { before, after }] of Object.entries(files)) {
|
|
68151
|
+
known[path] = { before: known[path]?.before ?? before, after };
|
|
68152
|
+
}
|
|
68153
|
+
own.delete(id);
|
|
68154
|
+
own.set(id, known);
|
|
68155
|
+
if (own.size > OWN_ENTRIES_KEPT) own.delete(own.keys().next().value);
|
|
68156
|
+
};
|
|
68157
|
+
return {
|
|
68158
|
+
remember,
|
|
68159
|
+
overtake: () => {
|
|
68160
|
+
changes += 1;
|
|
68161
|
+
next = null;
|
|
68162
|
+
return changes;
|
|
68163
|
+
},
|
|
68164
|
+
offered: (seen, view) => {
|
|
68165
|
+
if (seen === changes) next = { undo: view.back, redo: view.forward };
|
|
68166
|
+
},
|
|
68167
|
+
changes: () => changes,
|
|
68168
|
+
stepped: (entry2) => {
|
|
68169
|
+
const undone = entry2.undoes ? own.get(entry2.undoes) : void 0;
|
|
68170
|
+
if (!undone) return;
|
|
68171
|
+
const swapped = Object.entries(undone).map(([path, f]) => [
|
|
68172
|
+
path,
|
|
68173
|
+
{ before: f.after, after: f.before }
|
|
68174
|
+
]);
|
|
68175
|
+
remember(entry2.id, Object.fromEntries(swapped));
|
|
68176
|
+
},
|
|
68177
|
+
afterOf: (id) => Object.fromEntries(Object.entries(id && own.get(id) || {}).map(([p, f]) => [p, f.after])),
|
|
68178
|
+
predict: (direction) => {
|
|
68179
|
+
const step = next?.[direction];
|
|
68180
|
+
const files = step ? own.get(step.id) : void 0;
|
|
68181
|
+
const paths = files ? Object.keys(files) : [];
|
|
68182
|
+
if (!files || paths.length !== step.paths.length || !paths.every((p) => step.paths.includes(p)))
|
|
68183
|
+
return null;
|
|
68184
|
+
const restore = paths.map((path) => [
|
|
68185
|
+
path,
|
|
68186
|
+
{ previous: files[path].after, restored: files[path].before }
|
|
68187
|
+
]);
|
|
68188
|
+
return { id: step.id, files: Object.fromEntries(restore) };
|
|
68189
|
+
},
|
|
68190
|
+
clear: () => {
|
|
68191
|
+
own.clear();
|
|
68192
|
+
next = null;
|
|
68193
|
+
changes += 1;
|
|
68194
|
+
}
|
|
68195
|
+
};
|
|
68196
|
+
}
|
|
68106
68197
|
var DEFAULT_COALESCE_MS = 300;
|
|
68107
68198
|
function historyUrl(projectId, path = "") {
|
|
68108
68199
|
return `/api/projects/${encodeURIComponent(projectId)}/history${path}`;
|
|
@@ -68121,10 +68212,11 @@ async function post(url, body, headers = {}) {
|
|
|
68121
68212
|
return { ok: false, status: response.status, error: reply?.error ?? `HTTP ${response.status}` };
|
|
68122
68213
|
}
|
|
68123
68214
|
function claimHeld(reply, label) {
|
|
68124
|
-
if (reply.ok)
|
|
68215
|
+
if (reply.ok)
|
|
68216
|
+
return reply.body?.claimed?.id ?? null;
|
|
68125
68217
|
if (reply.status !== 404)
|
|
68126
68218
|
console.error(`"${label}" was not recorded as your edit: ${reply.error}`);
|
|
68127
|
-
return
|
|
68219
|
+
return null;
|
|
68128
68220
|
}
|
|
68129
68221
|
async function overwroteVersions(files) {
|
|
68130
68222
|
const pairs = await Promise.all(
|
|
@@ -68162,38 +68254,47 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
68162
68254
|
const heldClaimRef = useRef124(null);
|
|
68163
68255
|
const projectIdRef = useRef124(projectId);
|
|
68164
68256
|
projectIdRef.current = projectId;
|
|
68257
|
+
const [own] = useState104(createOwnHistory);
|
|
68165
68258
|
const refresh = useCallback97(async () => {
|
|
68166
68259
|
if (!projectId) return;
|
|
68260
|
+
const seen = own.changes();
|
|
68167
68261
|
const response = await fetch(historyUrl(projectId)).catch(() => null);
|
|
68168
68262
|
const next = response?.ok ? await response.json() : EMPTY3;
|
|
68169
|
-
if (projectIdRef.current
|
|
68170
|
-
|
|
68263
|
+
if (projectIdRef.current !== projectId) return;
|
|
68264
|
+
setView(next);
|
|
68265
|
+
own.offered(seen, next);
|
|
68266
|
+
}, [projectId, own]);
|
|
68171
68267
|
useEffect105(() => {
|
|
68172
68268
|
setView(EMPTY3);
|
|
68173
68269
|
setLoaded(false);
|
|
68174
68270
|
heldClaimRef.current = null;
|
|
68271
|
+
own.clear();
|
|
68175
68272
|
void refresh().finally(() => setLoaded(true));
|
|
68176
|
-
}, [refresh]);
|
|
68273
|
+
}, [refresh, own]);
|
|
68177
68274
|
const recordEdit = useCallback97(
|
|
68178
68275
|
async ({ label, coalesceKey, coalesceMs, files }) => {
|
|
68179
68276
|
if (!projectId) return;
|
|
68180
68277
|
const paths = Object.keys(files);
|
|
68278
|
+
own.overtake();
|
|
68181
68279
|
const reply = await post(historyUrl(projectId, "/claim"), {
|
|
68182
68280
|
label,
|
|
68183
68281
|
paths,
|
|
68184
68282
|
overwrote: await overwroteVersions(files),
|
|
68185
68283
|
...coalesceKey && { coalesceKey, idleMs: coalesceMs ?? DEFAULT_COALESCE_MS }
|
|
68186
68284
|
});
|
|
68187
|
-
|
|
68285
|
+
const claimed = claimHeld(reply, label);
|
|
68286
|
+
if (claimed) own.remember(claimed, files);
|
|
68287
|
+
heldClaimRef.current = claimed && coalesceKey ? { paths, at: Date.now() } : null;
|
|
68188
68288
|
void refresh();
|
|
68189
68289
|
},
|
|
68190
|
-
[projectId, refresh]
|
|
68290
|
+
[projectId, refresh, own]
|
|
68191
68291
|
);
|
|
68192
68292
|
const step = useCallback97(
|
|
68193
68293
|
async (direction, callbacks) => {
|
|
68194
68294
|
if (!projectId) return { ok: false, reason: "empty" };
|
|
68195
68295
|
const next = direction === "undo" ? view.back : view.forward;
|
|
68196
68296
|
const paths = [.../* @__PURE__ */ new Set([...next?.paths ?? [], ...heldClaimRef.current?.paths ?? []])];
|
|
68297
|
+
own.overtake();
|
|
68197
68298
|
const run = async () => {
|
|
68198
68299
|
const previous = await readAll(paths, callbacks.readFile);
|
|
68199
68300
|
const posted = await post(
|
|
@@ -68211,18 +68312,28 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
68211
68312
|
return { ok: false, reason: "content-mismatch", paths: files };
|
|
68212
68313
|
}
|
|
68213
68314
|
if (!reply.entry) return { ok: false, reason: "empty" };
|
|
68315
|
+
own.stepped(reply.entry);
|
|
68214
68316
|
const changed = reply.entry.files.map((file) => file.path);
|
|
68215
68317
|
return {
|
|
68216
68318
|
ok: true,
|
|
68217
68319
|
label: reply.entry.label,
|
|
68320
|
+
undoes: reply.entry.undoes,
|
|
68218
68321
|
paths: changed,
|
|
68219
|
-
files: await restoredFiles(
|
|
68322
|
+
files: await restoredFiles(
|
|
68323
|
+
changed,
|
|
68324
|
+
{ ...own.afterOf(reply.entry.undoes), ...previous },
|
|
68325
|
+
callbacks.readFile
|
|
68326
|
+
)
|
|
68220
68327
|
};
|
|
68221
68328
|
};
|
|
68222
68329
|
return callbacks.serialize ? callbacks.serialize(paths, run) : run();
|
|
68223
68330
|
},
|
|
68224
|
-
[projectId, view, refresh]
|
|
68331
|
+
[projectId, view, refresh, own]
|
|
68225
68332
|
);
|
|
68333
|
+
const noteOutsideChange = useCallback97(() => {
|
|
68334
|
+
own.overtake();
|
|
68335
|
+
void refresh();
|
|
68336
|
+
}, [own, refresh]);
|
|
68226
68337
|
const undo = useCallback97((callbacks) => step("undo", callbacks), [step]);
|
|
68227
68338
|
const redo = useCallback97((callbacks) => step("redo", callbacks), [step]);
|
|
68228
68339
|
const state = useMemo65(() => {
|
|
@@ -68244,7 +68355,9 @@ function usePersistentEditHistory({ projectId }) {
|
|
|
68244
68355
|
state,
|
|
68245
68356
|
recordEdit,
|
|
68246
68357
|
undo,
|
|
68247
|
-
redo
|
|
68358
|
+
redo,
|
|
68359
|
+
predict: own.predict,
|
|
68360
|
+
noteOutsideChange
|
|
68248
68361
|
};
|
|
68249
68362
|
}
|
|
68250
68363
|
|
|
@@ -69010,6 +69123,7 @@ function useFileManager({
|
|
|
69010
69123
|
import { useCallback as useCallback103, useRef as useRef129, useState as useState107 } from "react";
|
|
69011
69124
|
|
|
69012
69125
|
// src/utils/gsapUndoRestore.ts
|
|
69126
|
+
import { isCompositionTemplate as isCompositionTemplate2 } from "@hyperframes/parsers/hf-ids";
|
|
69013
69127
|
function elementIdentityKey(el) {
|
|
69014
69128
|
const hfId = el.getAttribute("data-hf-id");
|
|
69015
69129
|
if (hfId) return `hf:${hfId}`;
|
|
@@ -69018,9 +69132,9 @@ function elementIdentityKey(el) {
|
|
|
69018
69132
|
return null;
|
|
69019
69133
|
}
|
|
69020
69134
|
var IDENTITY_SELECTOR = "[id], [data-hf-id]";
|
|
69021
|
-
function identityElementMap(
|
|
69135
|
+
function identityElementMap(root) {
|
|
69022
69136
|
const map = /* @__PURE__ */ new Map();
|
|
69023
|
-
for (const el of
|
|
69137
|
+
for (const el of root.querySelectorAll(IDENTITY_SELECTOR)) {
|
|
69024
69138
|
const key2 = elementIdentityKey(el);
|
|
69025
69139
|
if (!key2) continue;
|
|
69026
69140
|
if (map.has(key2)) return null;
|
|
@@ -69028,8 +69142,18 @@ function identityElementMap(doc) {
|
|
|
69028
69142
|
}
|
|
69029
69143
|
return map;
|
|
69030
69144
|
}
|
|
69031
|
-
function
|
|
69032
|
-
|
|
69145
|
+
function parseRestoreSource(html2) {
|
|
69146
|
+
const doc = new DOMParser().parseFromString(html2, "text/html");
|
|
69147
|
+
for (const template of Array.from(doc.querySelectorAll("template")).filter(
|
|
69148
|
+
isCompositionTemplate2
|
|
69149
|
+
)) {
|
|
69150
|
+
template.replaceWith(template.content);
|
|
69151
|
+
}
|
|
69152
|
+
return doc;
|
|
69153
|
+
}
|
|
69154
|
+
function normalizeSoftResidual(root) {
|
|
69155
|
+
const self = root.matches(IDENTITY_SELECTOR) ? [root] : [];
|
|
69156
|
+
for (const el of [...self, ...root.querySelectorAll(IDENTITY_SELECTOR)]) {
|
|
69033
69157
|
const id = el.getAttribute("id");
|
|
69034
69158
|
const hfId = el.getAttribute("data-hf-id");
|
|
69035
69159
|
for (const name of [...el.getAttributeNames()]) {
|
|
@@ -69038,7 +69162,7 @@ function normalizeSoftResidual(doc) {
|
|
|
69038
69162
|
if (id) el.setAttribute("id", id);
|
|
69039
69163
|
if (hfId) el.setAttribute("data-hf-id", hfId);
|
|
69040
69164
|
}
|
|
69041
|
-
for (const script of findGsapScriptElements(
|
|
69165
|
+
for (const script of findGsapScriptElements(root)) script.textContent = "";
|
|
69042
69166
|
}
|
|
69043
69167
|
function attributesEqual(a, b) {
|
|
69044
69168
|
const aNames = a.getAttributeNames();
|
|
@@ -69048,15 +69172,7 @@ function attributesEqual(a, b) {
|
|
|
69048
69172
|
}
|
|
69049
69173
|
return true;
|
|
69050
69174
|
}
|
|
69051
|
-
function
|
|
69052
|
-
let prevDoc;
|
|
69053
|
-
let nextDoc;
|
|
69054
|
-
try {
|
|
69055
|
-
prevDoc = new DOMParser().parseFromString(previous, "text/html");
|
|
69056
|
-
nextDoc = new DOMParser().parseFromString(restored, "text/html");
|
|
69057
|
-
} catch {
|
|
69058
|
-
return null;
|
|
69059
|
-
}
|
|
69175
|
+
function diffRestoreDocs(prevDoc, nextDoc) {
|
|
69060
69176
|
const prevByKey = identityElementMap(prevDoc);
|
|
69061
69177
|
const nextByKey = identityElementMap(nextDoc);
|
|
69062
69178
|
if (!prevByKey || !nextByKey) return null;
|
|
@@ -69067,10 +69183,12 @@ function diffSoftReloadableRestore(previous, restored) {
|
|
|
69067
69183
|
if (!prevEl || prevEl.tagName !== nextEl.tagName) return null;
|
|
69068
69184
|
if (!attributesEqual(prevEl, nextEl)) changedElementKeys.push(key2);
|
|
69069
69185
|
}
|
|
69070
|
-
|
|
69071
|
-
|
|
69072
|
-
|
|
69073
|
-
|
|
69186
|
+
const [prevRest, nextRest] = [prevDoc, nextDoc].map((doc) => {
|
|
69187
|
+
const root = doc.documentElement.cloneNode(true);
|
|
69188
|
+
normalizeSoftResidual(root);
|
|
69189
|
+
return root.outerHTML;
|
|
69190
|
+
});
|
|
69191
|
+
return prevRest === nextRest ? changedElementKeys : null;
|
|
69074
69192
|
}
|
|
69075
69193
|
function syncElementAttributes(target, source) {
|
|
69076
69194
|
for (const name of [...target.getAttributeNames()]) {
|
|
@@ -69089,16 +69207,84 @@ function syncStaleEditMarks(doc, restored) {
|
|
|
69089
69207
|
}
|
|
69090
69208
|
}
|
|
69091
69209
|
}
|
|
69092
|
-
function
|
|
69093
|
-
const doc = new DOMParser().parseFromString(html2, "text/html");
|
|
69094
|
-
return findGsapScriptElements(doc).map((script) => script.textContent ?? "");
|
|
69095
|
-
}
|
|
69096
|
-
function hasAmbiguousGsapScriptChange(previous, restored) {
|
|
69097
|
-
const previousScripts = readGsapScriptTexts(previous);
|
|
69098
|
-
const restoredScripts = readGsapScriptTexts(restored);
|
|
69210
|
+
function hasAmbiguousGsapScriptChange(previousScripts, restoredScripts) {
|
|
69099
69211
|
if (previousScripts.length <= 1 && restoredScripts.length <= 1) return false;
|
|
69100
69212
|
return previousScripts.length !== restoredScripts.length || previousScripts.some((script, index) => script !== restoredScripts[index]);
|
|
69101
69213
|
}
|
|
69214
|
+
function restoreScripted(prevDoc, nextDoc, isActive) {
|
|
69215
|
+
const [before, after] = [prevDoc, nextDoc].map(
|
|
69216
|
+
(doc) => findGsapScriptElements(doc).map((script) => script.textContent ?? "")
|
|
69217
|
+
);
|
|
69218
|
+
const scripted = before.length > 0 || after.length > 0;
|
|
69219
|
+
if (scripted && !isActive) return null;
|
|
69220
|
+
return hasAmbiguousGsapScriptChange(before, after) ? null : scripted;
|
|
69221
|
+
}
|
|
69222
|
+
function liveScopes(doc, path, isActive) {
|
|
69223
|
+
if (isActive) return [doc];
|
|
69224
|
+
return Array.from(doc.querySelectorAll(`[data-composition-file="${CSS.escape(path)}"]`));
|
|
69225
|
+
}
|
|
69226
|
+
function scopeTargets(scope, keys, restoredByKey) {
|
|
69227
|
+
const liveByKey = identityElementMap(scope);
|
|
69228
|
+
if (!liveByKey) return null;
|
|
69229
|
+
const targets = [];
|
|
69230
|
+
for (const key2 of keys) {
|
|
69231
|
+
const live = liveByKey.get(key2);
|
|
69232
|
+
const restored = restoredByKey.get(key2);
|
|
69233
|
+
if (!live || !restored || live.hasAttribute("data-hf-inner-root")) return null;
|
|
69234
|
+
targets.push({ live, restored });
|
|
69235
|
+
}
|
|
69236
|
+
return targets;
|
|
69237
|
+
}
|
|
69238
|
+
function fileTargets(doc, path, isActive, file) {
|
|
69239
|
+
const prevDoc = parseRestoreSource(file.previous);
|
|
69240
|
+
const nextDoc = parseRestoreSource(file.restored);
|
|
69241
|
+
const scripted = restoreScripted(prevDoc, nextDoc, isActive);
|
|
69242
|
+
if (scripted === null) return null;
|
|
69243
|
+
const keys = diffRestoreDocs(prevDoc, nextDoc);
|
|
69244
|
+
const restoredByKey = identityElementMap(nextDoc);
|
|
69245
|
+
if (!keys || !restoredByKey) return null;
|
|
69246
|
+
const found = liveScopes(doc, path, isActive).map(
|
|
69247
|
+
(scope) => scopeTargets(scope, keys, restoredByKey)
|
|
69248
|
+
);
|
|
69249
|
+
if (!found.length || found.includes(null)) return null;
|
|
69250
|
+
return { targets: found.flat(), scripted };
|
|
69251
|
+
}
|
|
69252
|
+
function planRestoreTargets(doc, activeDocPath, files) {
|
|
69253
|
+
const plan = { targets: [], scripted: false };
|
|
69254
|
+
for (const [path, file] of Object.entries(files)) {
|
|
69255
|
+
const found = fileTargets(doc, path, path === activeDocPath, file);
|
|
69256
|
+
if (!found) return null;
|
|
69257
|
+
plan.targets.push(...found.targets);
|
|
69258
|
+
plan.scripted ||= found.scripted;
|
|
69259
|
+
}
|
|
69260
|
+
return plan;
|
|
69261
|
+
}
|
|
69262
|
+
function finalizeInPlace(iframe, win, currentTime) {
|
|
69263
|
+
if (applySoftReloadFinalization(iframe, currentTime)) return true;
|
|
69264
|
+
try {
|
|
69265
|
+
win.__player?.seek?.(currentTime);
|
|
69266
|
+
win.__hfStudioManualEditsApply?.();
|
|
69267
|
+
return true;
|
|
69268
|
+
} catch {
|
|
69269
|
+
return false;
|
|
69270
|
+
}
|
|
69271
|
+
}
|
|
69272
|
+
function showRestoreInPlace(iframe, activeCompPath, files, currentTime) {
|
|
69273
|
+
const doc = iframe?.contentDocument;
|
|
69274
|
+
const win = iframe?.contentWindow;
|
|
69275
|
+
const plan = doc && win ? planRestoreTargets(doc, activeCompPath ?? "index.html", files) : null;
|
|
69276
|
+
if (!iframe || !win || !plan || plan.scripted) return null;
|
|
69277
|
+
const before = plan.targets.map(({ live }) => [live, live.cloneNode(false)]);
|
|
69278
|
+
for (const { live, restored } of plan.targets) syncElementAttributes(live, restored);
|
|
69279
|
+
const putBack = (time) => {
|
|
69280
|
+
if (iframe.contentDocument !== doc) return false;
|
|
69281
|
+
for (const [live, attributes] of before) syncElementAttributes(live, attributes);
|
|
69282
|
+
return finalizeInPlace(iframe, win, time);
|
|
69283
|
+
};
|
|
69284
|
+
if (finalizeInPlace(iframe, win, currentTime)) return putBack;
|
|
69285
|
+
putBack(currentTime);
|
|
69286
|
+
return null;
|
|
69287
|
+
}
|
|
69102
69288
|
function applyUndoRestoreToPreview(iframe, activeCompPath, files, currentTime, reload) {
|
|
69103
69289
|
const activeDocPath = activeCompPath ?? "index.html";
|
|
69104
69290
|
const paths = files ? Object.keys(files) : [];
|
|
@@ -69106,51 +69292,23 @@ function applyUndoRestoreToPreview(iframe, activeCompPath, files, currentTime, r
|
|
|
69106
69292
|
markScenesStale(iframe, paths);
|
|
69107
69293
|
reload();
|
|
69108
69294
|
};
|
|
69109
|
-
|
|
69110
|
-
|
|
69111
|
-
|
|
69112
|
-
|
|
69113
|
-
const doc = iframe.contentDocument;
|
|
69114
|
-
const win = iframe.contentWindow;
|
|
69115
|
-
if (!doc || !win) {
|
|
69116
|
-
reloadPreview();
|
|
69117
|
-
return "full";
|
|
69118
|
-
}
|
|
69119
|
-
const { previous, restored } = files[activeDocPath];
|
|
69120
|
-
const diff = diffSoftReloadableRestore(previous, restored);
|
|
69121
|
-
if (!diff) {
|
|
69122
|
-
reloadPreview();
|
|
69123
|
-
return "full";
|
|
69124
|
-
}
|
|
69125
|
-
if (hasAmbiguousGsapScriptChange(previous, restored)) {
|
|
69126
|
-
reloadPreview();
|
|
69127
|
-
return "full";
|
|
69128
|
-
}
|
|
69129
|
-
const liveByKey = identityElementMap(doc);
|
|
69130
|
-
const restoredByKey = identityElementMap(new DOMParser().parseFromString(restored, "text/html"));
|
|
69131
|
-
if (!liveByKey || !restoredByKey) {
|
|
69295
|
+
const doc = iframe?.contentDocument;
|
|
69296
|
+
const win = iframe?.contentWindow;
|
|
69297
|
+
const plan = doc && files && win ? planRestoreTargets(doc, activeDocPath, files) : null;
|
|
69298
|
+
if (!iframe || !doc || !files || !win || !plan) {
|
|
69132
69299
|
reloadPreview();
|
|
69133
69300
|
return "full";
|
|
69134
69301
|
}
|
|
69135
|
-
const
|
|
69136
|
-
|
|
69137
|
-
|
|
69138
|
-
|
|
69139
|
-
if (!liveEl || !restoredEl) {
|
|
69140
|
-
reloadPreview();
|
|
69141
|
-
return "full";
|
|
69142
|
-
}
|
|
69143
|
-
changedTargets.push({ live: liveEl, restored: restoredEl });
|
|
69144
|
-
}
|
|
69145
|
-
for (const target of changedTargets) syncElementAttributes(target.live, target.restored);
|
|
69146
|
-
const restoredScript = extractGsapScriptText(restored);
|
|
69147
|
-
const previousScript = extractGsapScriptText(previous);
|
|
69302
|
+
for (const target of plan.targets) syncElementAttributes(target.live, target.restored);
|
|
69303
|
+
const active = files[activeDocPath];
|
|
69304
|
+
const restoredScript = active ? extractGsapScriptText(active.restored) : null;
|
|
69305
|
+
const previousScript = active ? extractGsapScriptText(active.previous) : null;
|
|
69148
69306
|
if (restoredScript && restoredScript !== previousScript) {
|
|
69149
|
-
syncStaleEditMarks(doc, restored);
|
|
69307
|
+
syncStaleEditMarks(doc, active.restored);
|
|
69150
69308
|
const result = applySoftReload(iframe, restoredScript, {
|
|
69151
69309
|
onAsyncFailure: reloadPreview,
|
|
69152
69310
|
currentTimeOverride: currentTime,
|
|
69153
|
-
authoredHtml: restored
|
|
69311
|
+
authoredHtml: active.restored
|
|
69154
69312
|
});
|
|
69155
69313
|
if (result === "cannot-soft-reload") {
|
|
69156
69314
|
reloadPreview();
|
|
@@ -69158,15 +69316,9 @@ function applyUndoRestoreToPreview(iframe, activeCompPath, files, currentTime, r
|
|
|
69158
69316
|
}
|
|
69159
69317
|
return "soft";
|
|
69160
69318
|
}
|
|
69161
|
-
if (
|
|
69162
|
-
|
|
69163
|
-
|
|
69164
|
-
win.__hfStudioManualEditsApply?.();
|
|
69165
|
-
} catch {
|
|
69166
|
-
reloadPreview();
|
|
69167
|
-
return "full";
|
|
69168
|
-
}
|
|
69169
|
-
return "soft";
|
|
69319
|
+
if (finalizeInPlace(iframe, win, currentTime)) return "soft";
|
|
69320
|
+
reloadPreview();
|
|
69321
|
+
return "full";
|
|
69170
69322
|
}
|
|
69171
69323
|
|
|
69172
69324
|
// src/player/lib/automationStoreSync.ts
|
|
@@ -69350,6 +69502,20 @@ function usePreviewPersistence({
|
|
|
69350
69502
|
},
|
|
69351
69503
|
[previewIframeRef, activeCompPathRef, reloadPreview]
|
|
69352
69504
|
);
|
|
69505
|
+
const showHistoryRestoreNow = useCallback103(
|
|
69506
|
+
(files) => {
|
|
69507
|
+
if (!domEditSaveQueueRef.current?.isIdle() || hasStudioPendingEdits()) return null;
|
|
69508
|
+
const iframe = previewIframeRef.current;
|
|
69509
|
+
const now2 = () => usePlayerStore.getState().currentTime;
|
|
69510
|
+
const putBack = showRestoreInPlace(iframe, activeCompPathRef.current, files, now2());
|
|
69511
|
+
if (!putBack) return null;
|
|
69512
|
+
return () => {
|
|
69513
|
+
if (putBack(now2())) syncStoredAutomationFromPreview(iframe?.contentDocument ?? null);
|
|
69514
|
+
else void syncHistoryPreviewAfterApply({ paths: Object.keys(files) });
|
|
69515
|
+
};
|
|
69516
|
+
},
|
|
69517
|
+
[previewIframeRef, activeCompPathRef, syncHistoryPreviewAfterApply]
|
|
69518
|
+
);
|
|
69353
69519
|
useMountEffect(() => {
|
|
69354
69520
|
void clearLegacyStudioMotionFile(_readOptionalProjectFile, _writeProjectFile);
|
|
69355
69521
|
});
|
|
@@ -69364,7 +69530,8 @@ function usePreviewPersistence({
|
|
|
69364
69530
|
resetDomEditSaveQueueBreaker,
|
|
69365
69531
|
applyCurrentStudioManualEditsToPreview,
|
|
69366
69532
|
applyStudioManualEditsToPreview,
|
|
69367
|
-
syncHistoryPreviewAfterApply
|
|
69533
|
+
syncHistoryPreviewAfterApply,
|
|
69534
|
+
showHistoryRestoreNow
|
|
69368
69535
|
};
|
|
69369
69536
|
}
|
|
69370
69537
|
|
|
@@ -79630,14 +79797,18 @@ function useStudioExternalFileChanges({
|
|
|
79630
79797
|
fileManager,
|
|
79631
79798
|
previewPersistence,
|
|
79632
79799
|
pendingTimelineEditPathRef,
|
|
79633
|
-
reloadPreview
|
|
79800
|
+
reloadPreview,
|
|
79801
|
+
onOutsideChange
|
|
79634
79802
|
}) {
|
|
79635
79803
|
const { flushPendingSourceSave, discardPendingSourceSave } = fileManager;
|
|
79636
79804
|
const { drainPendingDomEditSaves, resetDomEditSaveQueueBreaker } = previewPersistence;
|
|
79637
79805
|
const bumpThumbnailRevisions = usePlayerStore((state) => state.bumpThumbnailRevisions);
|
|
79638
79806
|
const onAcceptedPersistedFileChange = useCallback143(
|
|
79639
|
-
(_path, affectedCompositions) =>
|
|
79640
|
-
|
|
79807
|
+
(_path, affectedCompositions) => {
|
|
79808
|
+
onOutsideChange?.();
|
|
79809
|
+
bumpThumbnailRevisions(affectedCompositions);
|
|
79810
|
+
},
|
|
79811
|
+
[bumpThumbnailRevisions, onOutsideChange]
|
|
79641
79812
|
);
|
|
79642
79813
|
const drainPendingChanges = useCallback143(async () => {
|
|
79643
79814
|
const source = await flushPendingSourceSave();
|
|
@@ -80204,6 +80375,7 @@ function useEditHistoryActions({
|
|
|
80204
80375
|
writeProjectFile,
|
|
80205
80376
|
showToast,
|
|
80206
80377
|
syncHistoryPreviewAfterApply,
|
|
80378
|
+
showHistoryRestoreNow,
|
|
80207
80379
|
waitForPendingDomEditSaves,
|
|
80208
80380
|
onAfterUndoRedo,
|
|
80209
80381
|
activeCompPath,
|
|
@@ -80220,11 +80392,20 @@ function useEditHistoryActions({
|
|
|
80220
80392
|
const apply = useCallback145(
|
|
80221
80393
|
async (direction) => {
|
|
80222
80394
|
const noun = direction === "undo" ? "Undo" : "Redo";
|
|
80223
|
-
|
|
80224
|
-
const
|
|
80225
|
-
|
|
80226
|
-
|
|
80227
|
-
|
|
80395
|
+
const predicted = editHistory.predict?.(direction) ?? null;
|
|
80396
|
+
const putBack = predicted ? showHistoryRestoreNow?.(predicted.files) ?? null : null;
|
|
80397
|
+
let result = { ok: false, reason: "failed" };
|
|
80398
|
+
let serverSteppedShown = false;
|
|
80399
|
+
try {
|
|
80400
|
+
await waitForPendingDomEditSaves();
|
|
80401
|
+
result = await editHistory[direction]({
|
|
80402
|
+
readFile: readHistoryFile,
|
|
80403
|
+
serialize: serializeHistoryFiles
|
|
80404
|
+
});
|
|
80405
|
+
serverSteppedShown = Boolean(putBack && result.ok && result.label) && result.undoes === predicted?.id;
|
|
80406
|
+
} finally {
|
|
80407
|
+
if (putBack && !serverSteppedShown) putBack();
|
|
80408
|
+
}
|
|
80228
80409
|
if (!result.ok && result.reason === "content-mismatch") {
|
|
80229
80410
|
showToast(
|
|
80230
80411
|
`Can't ${direction}: ${result.paths?.join(", ")} changed since that edit.`,
|
|
@@ -80237,7 +80418,8 @@ function useEditHistoryActions({
|
|
|
80237
80418
|
return;
|
|
80238
80419
|
}
|
|
80239
80420
|
if (result.ok && result.label) {
|
|
80240
|
-
const
|
|
80421
|
+
const files = serverSteppedShown ? fromShown(result.files, predicted.files) : result.files;
|
|
80422
|
+
const restore = { paths: result.paths, files };
|
|
80241
80423
|
onAfterUndoRedo?.(restore);
|
|
80242
80424
|
if (activeCompPath && result.paths?.includes(activeCompPath)) {
|
|
80243
80425
|
forceReloadSdkSession?.();
|
|
@@ -80251,6 +80433,7 @@ function useEditHistoryActions({
|
|
|
80251
80433
|
readHistoryFile,
|
|
80252
80434
|
showToast,
|
|
80253
80435
|
syncHistoryPreviewAfterApply,
|
|
80436
|
+
showHistoryRestoreNow,
|
|
80254
80437
|
waitForPendingDomEditSaves,
|
|
80255
80438
|
serializeHistoryFiles,
|
|
80256
80439
|
onAfterUndoRedo,
|
|
@@ -80262,6 +80445,15 @@ function useEditHistoryActions({
|
|
|
80262
80445
|
const redo = useCallback145(() => apply("redo"), [apply]);
|
|
80263
80446
|
return useMemo71(() => ({ undo, redo }), [undo, redo]);
|
|
80264
80447
|
}
|
|
80448
|
+
function fromShown(files, shown2) {
|
|
80449
|
+
if (!files) return files;
|
|
80450
|
+
return Object.fromEntries(
|
|
80451
|
+
Object.entries(files).map(([path, f]) => [
|
|
80452
|
+
path,
|
|
80453
|
+
{ previous: shown2[path]?.restored ?? f.previous, restored: f.restored }
|
|
80454
|
+
])
|
|
80455
|
+
);
|
|
80456
|
+
}
|
|
80265
80457
|
|
|
80266
80458
|
// src/hooks/useAppHotkeys.ts
|
|
80267
80459
|
function iframeContentWindow(iframe) {
|
|
@@ -80277,9 +80469,9 @@ function safeAddListener(t, type, h, capture = false) {
|
|
|
80277
80469
|
} catch {
|
|
80278
80470
|
}
|
|
80279
80471
|
}
|
|
80280
|
-
function safeRemoveListener(t, type, h) {
|
|
80472
|
+
function safeRemoveListener(t, type, h, capture = false) {
|
|
80281
80473
|
try {
|
|
80282
|
-
t?.removeEventListener(type, h);
|
|
80474
|
+
t?.removeEventListener(type, h, capture);
|
|
80283
80475
|
} catch {
|
|
80284
80476
|
}
|
|
80285
80477
|
}
|
|
@@ -80306,6 +80498,7 @@ function useAppHotkeys({
|
|
|
80306
80498
|
writeProjectFile,
|
|
80307
80499
|
showToast,
|
|
80308
80500
|
syncHistoryPreviewAfterApply,
|
|
80501
|
+
showHistoryRestoreNow,
|
|
80309
80502
|
waitForPendingDomEditSaves,
|
|
80310
80503
|
handleCopy: handleCopy2,
|
|
80311
80504
|
handlePaste: handlePaste2,
|
|
@@ -80329,6 +80522,7 @@ function useAppHotkeys({
|
|
|
80329
80522
|
writeProjectFile,
|
|
80330
80523
|
showToast,
|
|
80331
80524
|
syncHistoryPreviewAfterApply,
|
|
80525
|
+
showHistoryRestoreNow,
|
|
80332
80526
|
waitForPendingDomEditSaves,
|
|
80333
80527
|
onAfterUndoRedo,
|
|
80334
80528
|
activeCompPath,
|
|
@@ -80385,38 +80579,17 @@ function useAppHotkeys({
|
|
|
80385
80579
|
window.addEventListener("keydown", handleAppKeyDown, true);
|
|
80386
80580
|
return () => window.removeEventListener("keydown", handleAppKeyDown, true);
|
|
80387
80581
|
}, [handleAppKeyDown]);
|
|
80388
|
-
const handleHistoryHotkey = useCallback146((event) => {
|
|
80389
|
-
if (!(event.metaKey || event.ctrlKey) || shouldIgnoreHistoryShortcut(event.target)) return;
|
|
80390
|
-
handleUndoRedoKey(
|
|
80391
|
-
event,
|
|
80392
|
-
() => void cbRef.current.handleUndo(),
|
|
80393
|
-
() => void cbRef.current.handleRedo()
|
|
80394
|
-
);
|
|
80395
|
-
}, []);
|
|
80396
80582
|
const syncPreviewHotkeys = useCallback146(
|
|
80397
80583
|
(iframe) => {
|
|
80398
80584
|
previewHistoryCleanupRef.current?.();
|
|
80399
80585
|
previewHistoryCleanupRef.current = null;
|
|
80400
80586
|
const win = iframeContentWindow(iframe);
|
|
80401
|
-
|
|
80402
|
-
try {
|
|
80403
|
-
doc = iframe?.contentDocument ?? null;
|
|
80404
|
-
} catch {
|
|
80405
|
-
doc = null;
|
|
80406
|
-
}
|
|
80407
|
-
if (!win && !doc) return;
|
|
80408
|
-
const handler = handleHistoryHotkey;
|
|
80587
|
+
if (!win) return;
|
|
80409
80588
|
const appHandler = handleAppKeyDown;
|
|
80410
|
-
safeAddListener(win, "keydown", handler, true);
|
|
80411
80589
|
safeAddListener(win, "keydown", appHandler, true);
|
|
80412
|
-
|
|
80413
|
-
previewHistoryCleanupRef.current = () => {
|
|
80414
|
-
safeRemoveListener(win, "keydown", handler);
|
|
80415
|
-
safeRemoveListener(win, "keydown", appHandler);
|
|
80416
|
-
doc?.removeEventListener("keydown", handleHistoryHotkey, true);
|
|
80417
|
-
};
|
|
80590
|
+
previewHistoryCleanupRef.current = () => safeRemoveListener(win, "keydown", appHandler, true);
|
|
80418
80591
|
},
|
|
80419
|
-
[handleAppKeyDown
|
|
80592
|
+
[handleAppKeyDown]
|
|
80420
80593
|
);
|
|
80421
80594
|
useEffect121(
|
|
80422
80595
|
() => () => {
|
|
@@ -91802,7 +91975,8 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
|
|
|
91802
91975
|
fileManager,
|
|
91803
91976
|
previewPersistence,
|
|
91804
91977
|
pendingTimelineEditPathRef,
|
|
91805
|
-
reloadPreview
|
|
91978
|
+
reloadPreview,
|
|
91979
|
+
onOutsideChange: editHistory.noteOutsideChange
|
|
91806
91980
|
});
|
|
91807
91981
|
const invalidateGsapCacheRef = useRef181(() => {
|
|
91808
91982
|
});
|
|
@@ -91904,6 +92078,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
|
|
|
91904
92078
|
writeProjectFile: fileManager.writeProjectFile,
|
|
91905
92079
|
showToast,
|
|
91906
92080
|
syncHistoryPreviewAfterApply: previewPersistence.syncHistoryPreviewAfterApply,
|
|
92081
|
+
showHistoryRestoreNow: previewPersistence.showHistoryRestoreNow,
|
|
91907
92082
|
waitForPendingDomEditSaves: previewPersistence.waitForPendingDomEditSaves,
|
|
91908
92083
|
handleCopy: handleCopy2,
|
|
91909
92084
|
handlePaste: handlePaste2,
|
|
@@ -92792,6 +92967,7 @@ export {
|
|
|
92792
92967
|
ConnectedDomEditOverlay,
|
|
92793
92968
|
ContextMenu,
|
|
92794
92969
|
DEFAULT_SHORTCUT_SECTIONS,
|
|
92970
|
+
Dock,
|
|
92795
92971
|
DomEditOverlay,
|
|
92796
92972
|
DomEditProvider,
|
|
92797
92973
|
EditorShell,
|
|
@@ -92887,6 +93063,7 @@ export {
|
|
|
92887
93063
|
snapEngagedForTravel,
|
|
92888
93064
|
useClipboard,
|
|
92889
93065
|
useCompositionStack,
|
|
93066
|
+
useDockLayoutStore,
|
|
92890
93067
|
useDomEditSelectionContext,
|
|
92891
93068
|
useDomEditSession,
|
|
92892
93069
|
useDomGeometryCommit,
|