@hyperframes/studio 0.8.105 → 0.8.107
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-tnfCiVdR.js → hyperframes-player-h4mktSdm.js} +1 -1
- package/dist/assets/index-CM1xA_jD.js +394 -0
- package/dist/assets/{index-Ca6LZlVb.js → index-D8GKc2D9.js} +1 -1
- package/dist/assets/index-lrT-gfHa.css +1 -0
- package/dist/assets/{index-zpe332hb.js → index-zoqPmBF6.js} +1 -1
- package/dist/{chunk-QADHJFDF.js → chunk-4OZC426H.js} +13 -3
- package/dist/chunk-4OZC426H.js.map +1 -0
- package/dist/index.d.ts +381 -14
- package/dist/index.html +11 -2
- package/dist/index.js +9974 -9045
- package/dist/index.js.map +1 -1
- package/dist/player.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +9 -9
- package/src/App.tsx +5 -3
- package/src/agentToolsExport.test.ts +8 -0
- package/src/captions/keyboard.test.ts +12 -0
- package/src/captions/keyboard.ts +2 -1
- package/src/components/StudioHeader.dom.test.tsx +31 -9
- package/src/components/StudioHeader.tsx +4 -1
- package/src/components/StudioRightPanels.tsx +2 -0
- package/src/components/ThemeToggle.tsx +99 -0
- package/src/components/TimelineToolbar.test.tsx +27 -0
- package/src/components/TimelineToolbar.tsx +2 -5
- package/src/components/editor/DomEditOverlay.tsx +31 -5
- package/src/components/editor/PropertyPanel.tsx +1 -14
- package/src/components/editor/PropertyPanelFlat.tsx +3 -6
- package/src/components/editor/SnapToolbar.test.tsx +32 -1
- package/src/components/editor/SnapToolbar.tsx +3 -4
- package/src/components/editor/SourceEditor.tsx +2 -6
- package/src/components/editor/domEditOverlayStartGesture.ts +132 -117
- package/src/components/editor/fontAssets.test.ts +12 -22
- package/src/components/editor/gestureHoldsPreviewReload.test.tsx +338 -0
- package/src/components/editor/gsapAnimatesProperty.ts +3 -0
- package/src/components/editor/manualEdits.test.ts +4 -3
- package/src/components/editor/manualEditsDom.ts +58 -2
- package/src/components/editor/manualEditsDomPatches.ts +5 -1
- package/src/components/editor/manualOffsetDrag.ts +4 -1
- package/src/components/editor/marqueeCommit.test.tsx +69 -1
- package/src/components/editor/marqueeCommit.ts +6 -1
- package/src/components/editor/mediaAudioEdits.test.ts +212 -0
- package/src/components/editor/mediaAudioEdits.ts +253 -0
- package/src/components/editor/overlayFrameLoop.test.ts +27 -0
- package/src/components/editor/overlayFrameLoop.ts +21 -0
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +286 -87
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +17 -20
- package/src/components/editor/propertyPanelFlatProps.ts +1 -0
- package/src/components/editor/propertyPanelFont.test.tsx +80 -3
- package/src/components/editor/propertyPanelFont.tsx +69 -46
- package/src/components/editor/propertyPanelHelpers.ts +1 -2
- package/src/components/editor/propertyPanelMediaSection.tsx +17 -27
- package/src/components/editor/propertyPanelTypes.ts +2 -0
- package/src/components/editor/studioCodeTheme.test.ts +20 -0
- package/src/components/editor/studioCodeTheme.ts +99 -0
- package/src/components/editor/useDomEditNudge.test.tsx +165 -2
- package/src/components/editor/useDomEditNudge.ts +43 -25
- package/src/components/editor/useInlineTextEditing.tsx +1 -1
- package/src/components/editor/useMotionPathData.test.tsx +64 -0
- package/src/components/editor/useMotionPathData.ts +5 -1
- package/src/components/panels/SlideshowPanel.test.ts +27 -0
- package/src/components/panels/SlideshowPanel.tsx +12 -7
- package/src/contexts/DomEditContext.test.tsx +62 -0
- package/src/contexts/DomEditContext.tsx +94 -77
- package/src/hooks/appHotkeysDispatch.ts +2 -1
- package/src/hooks/canvasQuickSaves.test.tsx +72 -7
- package/src/hooks/domEditCommitTypes.ts +10 -0
- package/src/hooks/domEditPersistFailure.ts +12 -1
- package/src/hooks/domStyleCommit.fonts.test.ts +5 -4
- package/src/hooks/domStyleCommit.ts +2 -8
- package/src/hooks/gsapDragCommit.ts +3 -8
- package/src/hooks/gsapDragPositionCommit.ts +6 -1
- package/src/hooks/gsapEditOutcome.ts +2 -1
- package/src/hooks/gsapResizeElementSize.test.tsx +136 -0
- package/src/hooks/gsapResizeGeometrySweep.test.ts +3 -1
- package/src/hooks/gsapResizeIntercept.test.ts +3 -11
- package/src/hooks/gsapResizeIntercept.ts +6 -2
- package/src/hooks/gsapResizeMixedTween.test.ts +1 -2
- package/src/hooks/gsapRuntimeBridge.test.ts +55 -0
- package/src/hooks/gsapRuntimeBridge.ts +5 -3
- package/src/hooks/gsapRuntimeKeyframes.ts +1 -3
- package/src/hooks/inspectorSaveReload.test.tsx +107 -0
- package/src/hooks/previewPromotionSelection.test.tsx +180 -0
- package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +16 -1
- package/src/hooks/useAppHotkeys.test.ts +49 -0
- package/src/hooks/useAppHotkeys.textEditing.test.tsx +1 -1
- package/src/hooks/useAppHotkeys.ts +4 -5
- package/src/hooks/useAutomationSelectionKeyboard.test.tsx +17 -0
- package/src/hooks/useAutomationSelectionKeyboard.ts +3 -9
- package/src/hooks/useClipboard.source.test.ts +99 -19
- package/src/hooks/useClipboard.ts +145 -124
- package/src/hooks/useDomEditAttributeCommits.ts +71 -29
- package/src/hooks/useDomEditCommits.test.tsx +172 -92
- package/src/hooks/useDomEditCommits.ts +2 -0
- package/src/hooks/useDomEditCommitsHelpers.ts +4 -6
- package/src/hooks/useDomEditPersist.ts +28 -13
- package/src/hooks/useDomEditPositionPatchCommit.ts +2 -0
- package/src/hooks/useDomEditSession.test.tsx +76 -72
- package/src/hooks/useDomEditSession.ts +9 -46
- package/src/hooks/useDomEditTextCommits.ts +4 -8
- package/src/hooks/useDomGeometryCommits.ts +11 -2
- package/src/hooks/useEditHistoryActions.paint.test.tsx +122 -3
- package/src/hooks/useGsapAwareEditing.ts +6 -6
- package/src/hooks/useGsapScriptCommits.test.tsx +19 -0
- package/src/hooks/useGsapScriptCommits.ts +5 -4
- package/src/hooks/useGsapTweenCache.ts +1 -2
- package/src/hooks/useInlineTextEdit.test.tsx +27 -0
- package/src/hooks/useInlineTextEdit.ts +21 -5
- package/src/hooks/useKeyframeKeyboard.test.tsx +15 -0
- package/src/hooks/useKeyframeKeyboard.ts +2 -8
- package/src/hooks/useLivePreviewIframe.ts +18 -0
- package/src/hooks/usePreviewPersistence.history.test.tsx +47 -0
- package/src/hooks/usePreviewPersistence.ts +8 -5
- package/src/hooks/useRenderClipContent.ts +3 -3
- package/src/hooks/useTrackPendingTimelineEdit.ts +2 -6
- package/src/index.ts +9 -0
- package/src/livePreviewIframeExport.test.tsx +33 -0
- package/src/main.tsx +4 -1
- package/src/player/components/AudioWaveform.test.tsx +36 -15
- package/src/player/components/AudioWaveform.tsx +24 -7
- package/src/player/components/Timeline.test.ts +0 -21
- package/src/player/components/Timeline.tsx +0 -1
- package/src/player/components/TimelineClip.test.tsx +38 -0
- package/src/player/components/TimelineClip.tsx +14 -6
- package/src/player/components/TimelineClipFades.test.tsx +376 -74
- package/src/player/components/TimelineClipFades.tsx +368 -137
- package/src/player/components/timelineLayout.ts +0 -28
- package/src/player/components/useTimelineKeyboardActor.ts +1 -1
- package/src/player/hooks/timelinePlayerTestHarness.ts +27 -0
- package/src/player/hooks/usePlaybackKeyboard.test.ts +43 -0
- package/src/player/hooks/useShadowPreviewReload.ts +72 -5
- package/src/player/lib/automationStoreSync.test.ts +18 -1
- package/src/player/lib/automationStoreSync.ts +29 -17
- package/src/player/lib/playbackShortcuts.ts +3 -7
- package/src/player/lib/timelineElementHelpers.test.ts +16 -0
- package/src/player/lib/timelineElementHelpers.ts +16 -2
- package/src/player/lib/timelineViewportBudgets.test.ts +2 -0
- package/src/player/lib/timelineViewportBudgets.ts +4 -0
- package/src/player/sceneSwap.ts +18 -0
- package/src/styles/components.css +26 -0
- package/src/styles/studio.css +6 -0
- package/src/styles/theme.css +10 -0
- package/src/utils/clipboardPayload.ts +10 -1
- package/src/utils/gsapSoftReload.ts +3 -2
- package/src/utils/gsapUndoRestore.browser.test.ts +94 -0
- package/src/utils/gsapUndoRestore.test.ts +145 -1
- package/src/utils/gsapUndoRestore.ts +70 -21
- package/src/utils/sourcePatcher.ts +1 -1
- package/src/utils/studioFontHelpers.ts +2 -23
- package/src/utils/studioHelpers.test.ts +30 -0
- package/src/utils/studioHelpers.ts +18 -4
- package/src/utils/studioPendingEdits.test.ts +63 -0
- package/src/utils/studioPendingEdits.ts +37 -7
- package/src/utils/studioTheme.test.tsx +141 -0
- package/src/utils/studioTheme.ts +34 -0
- package/src/utils/studioUiPreferences.ts +3 -0
- package/src/utils/typingTarget.test.ts +21 -1
- package/src/utils/typingTarget.ts +8 -0
- package/src/webmcp/useStudioAgentTools.ts +3 -1
- package/dist/assets/index-Cre9X4RS.css +0 -1
- package/dist/assets/index-DEDhPmPD.js +0 -399
- package/dist/chunk-QADHJFDF.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.107",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"@codemirror/language": "^6.12.2",
|
|
51
51
|
"@codemirror/search": "^6.6.0",
|
|
52
52
|
"@codemirror/state": "^6.6.0",
|
|
53
|
-
"@codemirror/theme-one-dark": "^6.1.2",
|
|
54
53
|
"@codemirror/view": "6.40.0",
|
|
54
|
+
"@lezer/highlight": "^1.2.3",
|
|
55
55
|
"@mcp-b/global": "^5.0.1",
|
|
56
56
|
"@phosphor-icons/react": "^2.1.10",
|
|
57
57
|
"@tanstack/react-virtual": "^3.14.13",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"dockview-react": "8.3.1",
|
|
61
61
|
"gsap": "^3.13.0",
|
|
62
62
|
"mediabunny": "^1.45.3",
|
|
63
|
-
"@hyperframes/
|
|
64
|
-
"@hyperframes/
|
|
65
|
-
"@hyperframes/
|
|
66
|
-
"@hyperframes/shader-transitions": "0.8.
|
|
67
|
-
"@hyperframes/studio-server": "0.8.
|
|
68
|
-
"@hyperframes/
|
|
63
|
+
"@hyperframes/parsers": "0.8.107",
|
|
64
|
+
"@hyperframes/core": "0.8.107",
|
|
65
|
+
"@hyperframes/sdk": "0.8.107",
|
|
66
|
+
"@hyperframes/shader-transitions": "0.8.107",
|
|
67
|
+
"@hyperframes/studio-server": "0.8.107",
|
|
68
|
+
"@hyperframes/player": "0.8.107"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@tailwindcss/vite": "^4.3.3",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"vite": "^6.4.2",
|
|
82
82
|
"vitest": "^4.1.11",
|
|
83
83
|
"zustand": "^5.0.0",
|
|
84
|
-
"@hyperframes/producer": "0.8.
|
|
84
|
+
"@hyperframes/producer": "0.8.107"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"react": "19",
|
package/src/App.tsx
CHANGED
|
@@ -30,6 +30,7 @@ import { useStudioSdkSessions } from "./hooks/useStudioSdkSessions";
|
|
|
30
30
|
import { useStudioExternalFileChanges } from "./hooks/useStudioExternalFileChanges";
|
|
31
31
|
import { useBlockHandlers } from "./hooks/useBlockHandlers";
|
|
32
32
|
import { useAppHotkeys } from "./hooks/useAppHotkeys";
|
|
33
|
+
import { trackedStudioEdit } from "./utils/studioPendingEdits";
|
|
33
34
|
import { useClipboard } from "./hooks/useClipboard";
|
|
34
35
|
import { deleteSelectedKeyframes } from "./hooks/timelineEditingHelpers";
|
|
35
36
|
import { useCaptionDetection } from "./hooks/useCaptionDetection";
|
|
@@ -256,7 +257,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
|
|
|
256
257
|
showToast,
|
|
257
258
|
syncHistoryPreviewAfterApply: previewPersistence.syncHistoryPreviewAfterApply,
|
|
258
259
|
showHistoryRestoreNow: previewPersistence.showHistoryRestoreNow,
|
|
259
|
-
|
|
260
|
+
settlePendingEdits: previewPersistence.settlePendingEdits,
|
|
260
261
|
handleCopy,
|
|
261
262
|
handlePaste,
|
|
262
263
|
handleCut,
|
|
@@ -315,9 +316,10 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
|
|
|
315
316
|
readOnlyPreview,
|
|
316
317
|
});
|
|
317
318
|
domEditSelectionBridgeRef.current = domEditSession.domEditSelection;
|
|
318
|
-
|
|
319
|
+
const { handleDomZIndexReorderCommit: zCommit, handleDomEditElementDelete: del } = domEditSession;
|
|
320
|
+
handleDomZIndexReorderCommitRef.current = trackedStudioEdit(zCommit);
|
|
319
321
|
clearDomSelectionRef.current = domEditSession.clearDomSelection;
|
|
320
|
-
handleDomEditElementDeleteRef.current =
|
|
322
|
+
handleDomEditElementDeleteRef.current = trackedStudioEdit(del);
|
|
321
323
|
resetKeyframesRef.current = domEditSession.handleResetSelectedElementKeyframes;
|
|
322
324
|
invalidateGsapCacheRef.current = domEditSession.invalidateGsapCache;
|
|
323
325
|
deleteSelectedKeyframesRef.current = () => deleteSelectedKeyframes(domEditSession);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { expect, it } from "vitest";
|
|
2
|
+
import { buildStudioTools, collectStudioLookScene } from "@hyperframes/studio";
|
|
3
|
+
|
|
4
|
+
it("exports Studio's agent tools for a host's own edit session", () => {
|
|
5
|
+
const names = buildStudioTools({ current: {} as never }).map((tool) => tool.name);
|
|
6
|
+
expect(names).toEqual(expect.arrayContaining(["studio_look", "studio_seek"]));
|
|
7
|
+
expect(typeof collectStudioLookScene).toBe("function");
|
|
8
|
+
});
|
|
@@ -38,6 +38,12 @@ describe("shouldHandleCaptionNudgeKey", () => {
|
|
|
38
38
|
expect(shouldHandleCaptionNudgeKey(mockKeyboardEvent("KeyL"))).toBe(false);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
it("ignores arrows a focused slider owns", () => {
|
|
42
|
+
const slider = document.createElement("div");
|
|
43
|
+
slider.setAttribute("role", "slider");
|
|
44
|
+
expect(shouldHandleCaptionNudgeKey(mockKeyboardEvent("ArrowUp"), slider)).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
41
47
|
it("ignores arrows when the event target is an editable element", () => {
|
|
42
48
|
const input = document.createElement("input");
|
|
43
49
|
const textarea = document.createElement("textarea");
|
|
@@ -47,4 +53,10 @@ describe("shouldHandleCaptionNudgeKey", () => {
|
|
|
47
53
|
expect(shouldHandleCaptionNudgeKey(mockKeyboardEvent("ArrowUp"), div)).toBe(true);
|
|
48
54
|
expect(shouldHandleCaptionNudgeKey(mockKeyboardEvent("ArrowUp"), null)).toBe(true);
|
|
49
55
|
});
|
|
56
|
+
|
|
57
|
+
it.each(["video", "audio"])("leaves the arrows to a focused <%s controls>", (tag) => {
|
|
58
|
+
const player = document.createElement(tag);
|
|
59
|
+
player.setAttribute("controls", "");
|
|
60
|
+
expect(shouldHandleCaptionNudgeKey(mockKeyboardEvent("ArrowRight"), player)).toBe(false);
|
|
61
|
+
});
|
|
50
62
|
});
|
package/src/captions/keyboard.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isEditableTarget } from "../utils/timelineDiscovery";
|
|
2
|
+
import { ownsPlainKeys } from "../utils/typingTarget";
|
|
2
3
|
|
|
3
4
|
const CAPTION_NUDGE_KEYS = new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]);
|
|
4
5
|
|
|
@@ -9,7 +10,7 @@ export function shouldHandleCaptionNudgeKey(
|
|
|
9
10
|
target?: EventTarget | null,
|
|
10
11
|
): boolean {
|
|
11
12
|
if (event.metaKey || event.ctrlKey || event.altKey) return false;
|
|
12
|
-
if (target != null &&
|
|
13
|
+
if (target != null && ownsPlainKeys(target)) return false;
|
|
13
14
|
return CAPTION_NUDGE_KEYS.has(event.key);
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -42,6 +42,7 @@ vi.mock("../contexts/PanelLayoutContext", () => ({
|
|
|
42
42
|
vi.mock("../utils/studioTelemetry", () => ({ trackStudioEvent: vi.fn() }));
|
|
43
43
|
|
|
44
44
|
const { StudioHeader } = await import("./StudioHeader");
|
|
45
|
+
const { ShowThemeToggle } = await import("./ThemeToggle");
|
|
45
46
|
|
|
46
47
|
let mounted: { root: Root; host: HTMLElement } | null = null;
|
|
47
48
|
|
|
@@ -54,6 +55,7 @@ beforeEach(() => {
|
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
afterEach(() => {
|
|
58
|
+
delete document.documentElement.dataset.theme;
|
|
57
59
|
if (!mounted) return;
|
|
58
60
|
const { root, host } = mounted;
|
|
59
61
|
mounted = null;
|
|
@@ -61,21 +63,30 @@ afterEach(() => {
|
|
|
61
63
|
host.remove();
|
|
62
64
|
});
|
|
63
65
|
|
|
64
|
-
function mount(
|
|
66
|
+
function mount(
|
|
67
|
+
props: { inspectorButtonActive?: boolean; themeToggle?: boolean } = {},
|
|
68
|
+
): HTMLElement {
|
|
65
69
|
const host = document.createElement("div");
|
|
66
70
|
document.body.append(host);
|
|
67
71
|
const root = createRoot(host);
|
|
68
72
|
mounted = { root, host };
|
|
73
|
+
const header = (
|
|
74
|
+
<StudioHeader
|
|
75
|
+
captureFrameHref="blob:frame"
|
|
76
|
+
captureFrameFilename="frame.png"
|
|
77
|
+
handleCaptureFrameClick={vi.fn()}
|
|
78
|
+
refreshCaptureFrameTime={vi.fn()}
|
|
79
|
+
inspectorButtonActive={props.inspectorButtonActive ?? false}
|
|
80
|
+
inspectorPanelActive={false}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
69
83
|
act(() =>
|
|
70
84
|
root.render(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
inspectorButtonActive={props.inspectorButtonActive ?? false}
|
|
77
|
-
inspectorPanelActive={false}
|
|
78
|
-
/>,
|
|
85
|
+
props.themeToggle ? (
|
|
86
|
+
<ShowThemeToggle.Provider value>{header}</ShowThemeToggle.Provider>
|
|
87
|
+
) : (
|
|
88
|
+
header
|
|
89
|
+
),
|
|
79
90
|
),
|
|
80
91
|
);
|
|
81
92
|
return host;
|
|
@@ -179,3 +190,14 @@ it("classifies the new header controls for the hotkey filters as the old ones we
|
|
|
179
190
|
expect(shouldIgnorePlaybackShortcutTarget(el), el.tagName).toBe(true);
|
|
180
191
|
}
|
|
181
192
|
});
|
|
193
|
+
|
|
194
|
+
it("shows no theme toggle and leaves the host's data-theme alone when embedded", () => {
|
|
195
|
+
document.documentElement.dataset.theme = "host";
|
|
196
|
+
const host = mount();
|
|
197
|
+
expect(host.querySelector('[aria-label^="Switch to"]')).toBeNull();
|
|
198
|
+
expect(document.documentElement.dataset.theme).toBe("host");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("shows the theme toggle in Studio's own app", () => {
|
|
202
|
+
expect(mount({ themeToggle: true }).querySelector('[aria-label^="Switch to"]')).not.toBeNull();
|
|
203
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { useContext, type MouseEvent } from "react";
|
|
2
2
|
import { Camera } from "../icons/SystemIcons";
|
|
3
3
|
import { useStudioShellContext } from "../contexts/StudioContext";
|
|
4
4
|
import { usePanelLayoutContext } from "../contexts/PanelLayoutContext";
|
|
@@ -7,6 +7,7 @@ import { Button, buttonBase, buttonSizes, buttonVariants, cn, Tooltip } from "./
|
|
|
7
7
|
import { Dock } from "./dock/Dock";
|
|
8
8
|
import { InspectorIcon } from "./icons/InspectorIcon";
|
|
9
9
|
import { HyperframesLogo } from "./ui/HyperframesLogo";
|
|
10
|
+
import { ShowThemeToggle, ThemeToggle } from "./ThemeToggle";
|
|
10
11
|
|
|
11
12
|
export interface StudioHeaderProps {
|
|
12
13
|
captureFrameHref: string;
|
|
@@ -44,6 +45,7 @@ export function StudioHeader({
|
|
|
44
45
|
inspectorPanelActive,
|
|
45
46
|
onExport,
|
|
46
47
|
}: StudioHeaderProps) {
|
|
48
|
+
const showThemeToggle = useContext(ShowThemeToggle);
|
|
47
49
|
const { projectId, renderQueue } = useStudioShellContext();
|
|
48
50
|
const { rightCollapsed, setRightCollapsed, setRightPanelTab } = usePanelLayoutContext();
|
|
49
51
|
const isRendering = renderQueue.isRendering;
|
|
@@ -147,6 +149,7 @@ export function StudioHeader({
|
|
|
147
149
|
</Button>
|
|
148
150
|
</Tooltip>
|
|
149
151
|
</div>
|
|
152
|
+
{showThemeToggle && <ThemeToggle />}
|
|
150
153
|
<Dock.WindowMenu />
|
|
151
154
|
<Tooltip
|
|
152
155
|
label={
|
|
@@ -62,6 +62,7 @@ export function StudioRightPanels({
|
|
|
62
62
|
handleDomAttributeQuietCommit,
|
|
63
63
|
handleDomHtmlAttributeCommit,
|
|
64
64
|
handleDomAttributesCommit,
|
|
65
|
+
handleDomAttributeBatchCommit,
|
|
65
66
|
handleDomPathOffsetCommit,
|
|
66
67
|
handleDomBoxSizeCommit,
|
|
67
68
|
handleDomRotationCommit,
|
|
@@ -210,6 +211,7 @@ export function StudioRightPanels({
|
|
|
210
211
|
onSetStyle={handleDomStyleCommit}
|
|
211
212
|
onSetAttribute={handleDomAttributeCommit}
|
|
212
213
|
onSetAttributes={handleDomAttributesCommit}
|
|
214
|
+
onSetAttributeBatch={handleDomAttributeBatchCommit}
|
|
213
215
|
onSetAttributeLive={setAttributeWhileDragging}
|
|
214
216
|
onSetAttributeQuiet={handleDomAttributeQuietCommit}
|
|
215
217
|
onApplyColorGradingScope={handleApplyColorGradingScope}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import { setStudioTheme, useShownStudioTheme } from "../utils/studioTheme";
|
|
3
|
+
import { IconButton, Tooltip } from "./ui";
|
|
4
|
+
|
|
5
|
+
// hyperframes.dev's theme toggle: the same sun and moon, revealing the new theme in a circle from
|
|
6
|
+
// the button. A page that paints no frame aborts the transition; the theme is applied by then.
|
|
7
|
+
let latest: ViewTransition | null = null;
|
|
8
|
+
|
|
9
|
+
function reveal(transition: ViewTransition, at: string, radiusPct: number) {
|
|
10
|
+
latest = transition;
|
|
11
|
+
const done = () => {
|
|
12
|
+
if (latest === transition) delete document.documentElement.dataset.themeReveal;
|
|
13
|
+
};
|
|
14
|
+
void transition.finished.then(done, done);
|
|
15
|
+
void transition.ready.then(
|
|
16
|
+
() => {
|
|
17
|
+
document.documentElement.animate(
|
|
18
|
+
{ clipPath: [`circle(0% ${at})`, `circle(${radiusPct}% ${at})`] },
|
|
19
|
+
{ duration: 500, easing: "ease-in-out", pseudoElement: "::view-transition-new(root)" },
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
() => {},
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function origin(e: React.MouseEvent<HTMLButtonElement>): [number, number] {
|
|
27
|
+
if (e.detail > 0) return [e.clientX, e.clientY];
|
|
28
|
+
const box = e.currentTarget.getBoundingClientRect();
|
|
29
|
+
return [box.left + box.width / 2, box.top + box.height / 2];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** On only in Studio's own app; a host that embeds Studio owns its theme. */
|
|
33
|
+
export const ShowThemeToggle = createContext(false);
|
|
34
|
+
|
|
35
|
+
function cannotReveal(): boolean {
|
|
36
|
+
return (
|
|
37
|
+
!document.startViewTransition ||
|
|
38
|
+
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const iconProps = {
|
|
43
|
+
width: "14",
|
|
44
|
+
height: "14",
|
|
45
|
+
viewBox: "0 0 24 24",
|
|
46
|
+
fill: "none",
|
|
47
|
+
stroke: "currentColor",
|
|
48
|
+
strokeWidth: "2",
|
|
49
|
+
strokeLinecap: "round",
|
|
50
|
+
strokeLinejoin: "round",
|
|
51
|
+
"aria-hidden": true,
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
export function ThemeToggle() {
|
|
55
|
+
const isDark = useShownStudioTheme() === "dark";
|
|
56
|
+
const label = `Switch to ${isDark ? "light" : "dark"} theme`;
|
|
57
|
+
|
|
58
|
+
function toggle(e: React.MouseEvent<HTMLButtonElement>) {
|
|
59
|
+
const apply = () => setStudioTheme(isDark ? "light" : "dark");
|
|
60
|
+
if (cannotReveal()) return apply();
|
|
61
|
+
const [x, y] = origin(e);
|
|
62
|
+
const w = window.innerWidth;
|
|
63
|
+
const h = window.innerHeight;
|
|
64
|
+
const maxRadius = Math.hypot(Math.max(x, w - x), Math.max(y, h - y));
|
|
65
|
+
const at = `at ${(x / w) * 100}% ${(y / h) * 100}%`;
|
|
66
|
+
const radiusPct = (maxRadius / (Math.hypot(w, h) / Math.SQRT2)) * 100;
|
|
67
|
+
// The switch stays synchronous inside the callback: rendering is paused during it.
|
|
68
|
+
document.documentElement.dataset.themeReveal = "";
|
|
69
|
+
reveal(document.startViewTransition(apply), at, radiusPct);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Tooltip label={label} side="bottom">
|
|
74
|
+
<IconButton
|
|
75
|
+
onClick={toggle}
|
|
76
|
+
aria-label={label}
|
|
77
|
+
icon={
|
|
78
|
+
isDark ? (
|
|
79
|
+
<svg {...iconProps}>
|
|
80
|
+
<circle cx="12" cy="12" r="5" />
|
|
81
|
+
<line x1="12" y1="1" x2="12" y2="3" />
|
|
82
|
+
<line x1="12" y1="21" x2="12" y2="23" />
|
|
83
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
|
84
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
85
|
+
<line x1="1" y1="12" x2="3" y2="12" />
|
|
86
|
+
<line x1="21" y1="12" x2="23" y2="12" />
|
|
87
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
|
88
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
89
|
+
</svg>
|
|
90
|
+
) : (
|
|
91
|
+
<svg {...iconProps}>
|
|
92
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
93
|
+
</svg>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
/>
|
|
97
|
+
</Tooltip>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -211,6 +211,33 @@ describe("TimelineToolbar — keyframes on audio tracks", () => {
|
|
|
211
211
|
});
|
|
212
212
|
});
|
|
213
213
|
|
|
214
|
+
describe("TimelineToolbar snap key", () => {
|
|
215
|
+
let root: ReturnType<typeof createRoot> | null = null;
|
|
216
|
+
const snapBefore = usePlayerStore.getState().timelineSnapEnabled;
|
|
217
|
+
afterEach(() => {
|
|
218
|
+
act(() => root?.unmount());
|
|
219
|
+
root = null;
|
|
220
|
+
usePlayerStore.getState().setTimelineSnapEnabled(snapBefore);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("N toggles snapping, but not while a picker that owns typing has focus", () => {
|
|
224
|
+
root = renderToolbar().root;
|
|
225
|
+
const pressN = (target: EventTarget) =>
|
|
226
|
+
act(() => {
|
|
227
|
+
target.dispatchEvent(new KeyboardEvent("keydown", { key: "n", bubbles: true }));
|
|
228
|
+
});
|
|
229
|
+
const before = usePlayerStore.getState().timelineSnapEnabled;
|
|
230
|
+
const picker = document.createElement("button");
|
|
231
|
+
picker.setAttribute("role", "combobox");
|
|
232
|
+
document.body.append(picker);
|
|
233
|
+
|
|
234
|
+
pressN(picker);
|
|
235
|
+
expect(usePlayerStore.getState().timelineSnapEnabled).toBe(before);
|
|
236
|
+
pressN(document.body);
|
|
237
|
+
expect(usePlayerStore.getState().timelineSnapEnabled).toBe(!before);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
214
241
|
describe("TimelineToolbar Fit", () => {
|
|
215
242
|
it("shows Fit as a named icon and says whether fit is on", () => {
|
|
216
243
|
const { host, root } = renderToolbar();
|
|
@@ -33,6 +33,7 @@ import { canSplitElement } from "../utils/timelineElementSplit";
|
|
|
33
33
|
import { useAudioMetersVisible } from "../utils/audioMeterVisibility";
|
|
34
34
|
import { useProjectHasAudio } from "../utils/audioMeterMath";
|
|
35
35
|
import { canAddBeatAt, addBeatAtCompositionTime } from "../utils/beatEditActions";
|
|
36
|
+
import { isTypingTarget } from "../utils/typingTarget";
|
|
36
37
|
|
|
37
38
|
interface DomEditSessionSlice extends EnableKeyframesSession {
|
|
38
39
|
domEditSelection: DomEditSelection | null;
|
|
@@ -196,14 +197,10 @@ export function TimelineToolbar({
|
|
|
196
197
|
// "N" toggles timeline snapping (industry convention: Resolve/FCP).
|
|
197
198
|
// Skip when typing in an input/contenteditable.
|
|
198
199
|
useEffect(() => {
|
|
199
|
-
// fallow-ignore-next-line complexity
|
|
200
200
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
201
201
|
if (e.key !== "n" && e.key !== "N") return;
|
|
202
202
|
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
203
|
-
|
|
204
|
-
if (target?.isContentEditable) return;
|
|
205
|
-
const tag = target?.tagName?.toLowerCase() ?? "";
|
|
206
|
-
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
203
|
+
if (isTypingTarget(e.target)) return;
|
|
207
204
|
const store = usePlayerStore.getState();
|
|
208
205
|
store.setTimelineSnapEnabled(!store.timelineSnapEnabled);
|
|
209
206
|
};
|
|
@@ -31,6 +31,8 @@ import { useDomEditCompositionRect } from "./useDomEditCompositionRect";
|
|
|
31
31
|
import { CanvasContextMenu } from "./CanvasContextMenu";
|
|
32
32
|
import { useInlineTextEditing } from "./useInlineTextEditing";
|
|
33
33
|
import { usePreviewReadOnly } from "./previewReadOnlyContext";
|
|
34
|
+
import { useMountEffect } from "../../hooks/useMountEffect";
|
|
35
|
+
import { countStudioManualEditSave as counted } from "./manualEditsDom";
|
|
34
36
|
import type { ZOrderAction, ZOrderPatch } from "./canvasContextMenuZOrder";
|
|
35
37
|
import { getPreviewTargetFromPointer } from "../../utils/studioPreviewHelpers";
|
|
36
38
|
import { logSelect } from "../../utils/selectDebug";
|
|
@@ -193,16 +195,28 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
193
195
|
onTextEditingChangeRef.current?.(true);
|
|
194
196
|
return () => onTextEditingChangeRef.current?.(false);
|
|
195
197
|
}, [inlineText.editing]);
|
|
198
|
+
// Each canvas save is counted, so a reload requested before it settles loads again.
|
|
196
199
|
const onPathOffsetCommitRef = useRef(onPathOffsetCommit);
|
|
197
|
-
onPathOffsetCommitRef.current =
|
|
200
|
+
onPathOffsetCommitRef.current = (sel, ...rest) =>
|
|
201
|
+
counted(sel.element, () => onPathOffsetCommit(sel, ...rest));
|
|
198
202
|
const onGroupPathOffsetCommitRef = useRef(onGroupPathOffsetCommit);
|
|
199
|
-
onGroupPathOffsetCommitRef.current =
|
|
203
|
+
onGroupPathOffsetCommitRef.current = (updates) => {
|
|
204
|
+
const save = () => onGroupPathOffsetCommit(updates);
|
|
205
|
+
return updates[0] ? counted(updates[0].selection.element, save) : save();
|
|
206
|
+
};
|
|
200
207
|
const onBoxSizeCommitRef = useRef(onBoxSizeCommit);
|
|
201
|
-
onBoxSizeCommitRef.current =
|
|
208
|
+
onBoxSizeCommitRef.current = (sel, ...rest) =>
|
|
209
|
+
counted(sel.element, () => onBoxSizeCommit(sel, ...rest));
|
|
202
210
|
const onRotationCommitRef = useRef(onRotationCommit);
|
|
203
|
-
onRotationCommitRef.current =
|
|
211
|
+
onRotationCommitRef.current = (sel, next) =>
|
|
212
|
+
counted(sel.element, () => onRotationCommit(sel, next));
|
|
204
213
|
const onStyleCommitRef = useRef(onStyleCommit);
|
|
205
|
-
onStyleCommitRef.current =
|
|
214
|
+
onStyleCommitRef.current =
|
|
215
|
+
onStyleCommit &&
|
|
216
|
+
((property, value) => {
|
|
217
|
+
const save = () => onStyleCommit(property, value);
|
|
218
|
+
return selectionRef.current ? counted(selectionRef.current.element, save) : save();
|
|
219
|
+
});
|
|
206
220
|
const onBlockedMoveRef = useRef(onBlockedMove);
|
|
207
221
|
onBlockedMoveRef.current = onBlockedMove;
|
|
208
222
|
const onManualDragStartRef = useRef(onManualDragStart);
|
|
@@ -282,6 +296,17 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
282
296
|
useEffect(() => {
|
|
283
297
|
if (readOnly) gestures.clearPointerState(selectionRef);
|
|
284
298
|
}, [gestures, readOnly, selectionRef]);
|
|
299
|
+
// A gesture that loses its pointer, the window or the overlay is cancelled, so its mark goes too.
|
|
300
|
+
const cancelGestureRef = useRef(() => {});
|
|
301
|
+
cancelGestureRef.current = () => gestures.clearPointerState(selectionRef);
|
|
302
|
+
useMountEffect(() => {
|
|
303
|
+
const cancel = () => cancelGestureRef.current();
|
|
304
|
+
window.addEventListener("blur", cancel);
|
|
305
|
+
return () => {
|
|
306
|
+
window.removeEventListener("blur", cancel);
|
|
307
|
+
cancel();
|
|
308
|
+
};
|
|
309
|
+
});
|
|
285
310
|
|
|
286
311
|
// Arrow-key nudge (1px, Shift = 10px) — commits through the same
|
|
287
312
|
// path-offset callbacks as a drag, one undo entry per key burst.
|
|
@@ -463,6 +488,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
463
488
|
onPointerLeave={() => onCanvasPointerLeaveRef.current()}
|
|
464
489
|
onPointerUp={marquee.onPointerUp}
|
|
465
490
|
onPointerCancel={marquee.onPointerCancel}
|
|
491
|
+
onLostPointerCapture={() => cancelGestureRef.current()}
|
|
466
492
|
onContextMenu={hostInput ? undefined : handleContextMenu}
|
|
467
493
|
>
|
|
468
494
|
{!hostInput && hoverSelection && hoverRect && compRect.width > 0 && (
|
|
@@ -58,7 +58,6 @@ export {
|
|
|
58
58
|
export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelProps) {
|
|
59
59
|
const {
|
|
60
60
|
projectId,
|
|
61
|
-
projectDir,
|
|
62
61
|
assets,
|
|
63
62
|
element,
|
|
64
63
|
multiSelectCount = 0,
|
|
@@ -72,8 +71,6 @@ export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelPro
|
|
|
72
71
|
onSetAttribute,
|
|
73
72
|
onSetAttributeLive,
|
|
74
73
|
onApplyColorGradingScope,
|
|
75
|
-
onSetHtmlAttribute,
|
|
76
|
-
onRemoveBackground,
|
|
77
74
|
onSetManualOffset,
|
|
78
75
|
onSetManualSize,
|
|
79
76
|
onSetManualRotation,
|
|
@@ -374,17 +371,7 @@ export const PropertyPanel = memo(function PropertyPanel(props: PropertyPanelPro
|
|
|
374
371
|
/>
|
|
375
372
|
)}
|
|
376
373
|
|
|
377
|
-
{sections.media &&
|
|
378
|
-
<MediaSection
|
|
379
|
-
projectDir={projectDir}
|
|
380
|
-
element={element}
|
|
381
|
-
styles={styles}
|
|
382
|
-
onSetStyle={onSetStyle}
|
|
383
|
-
onSetAttribute={onSetAttribute}
|
|
384
|
-
onSetHtmlAttribute={onSetHtmlAttribute}
|
|
385
|
-
onRemoveBackground={onRemoveBackground}
|
|
386
|
-
/>
|
|
387
|
-
)}
|
|
374
|
+
{sections.media && <MediaSection {...props} element={element} styles={styles} />}
|
|
388
375
|
|
|
389
376
|
{sections.layout && (
|
|
390
377
|
<Section title="Layout" icon={<Move size={15} />}>
|
|
@@ -61,7 +61,6 @@ export function PropertyPanelFlat({
|
|
|
61
61
|
clipboardCopied,
|
|
62
62
|
onCopyElementInfo,
|
|
63
63
|
projectId,
|
|
64
|
-
projectDir,
|
|
65
64
|
assets,
|
|
66
65
|
previewIframeRef,
|
|
67
66
|
onClearSelection,
|
|
@@ -73,8 +72,6 @@ export function PropertyPanelFlat({
|
|
|
73
72
|
onSetAttributeLive,
|
|
74
73
|
onSetAttributeQuiet,
|
|
75
74
|
onApplyColorGradingScope,
|
|
76
|
-
onSetHtmlAttribute,
|
|
77
|
-
onRemoveBackground,
|
|
78
75
|
onSetText,
|
|
79
76
|
onSetTextFieldStyle,
|
|
80
77
|
onPreviewTextFieldStyle,
|
|
@@ -130,6 +127,7 @@ export function PropertyPanelFlat({
|
|
|
130
127
|
onUpdateKeyframeEase,
|
|
131
128
|
onUpdateSegmentEase,
|
|
132
129
|
onSetAllKeyframeEases,
|
|
130
|
+
...forwardedProps
|
|
133
131
|
}: PropertyPanelFlatProps) {
|
|
134
132
|
// PropertyPanel keys this component by selection, so the default is per element.
|
|
135
133
|
const [openGroupId, setOpenGroupId] = useState<string>(() =>
|
|
@@ -502,13 +500,12 @@ export function PropertyPanelFlat({
|
|
|
502
500
|
summary: element.tagName,
|
|
503
501
|
content: (
|
|
504
502
|
<FlatMediaSection
|
|
505
|
-
|
|
503
|
+
{...forwardedProps}
|
|
504
|
+
projectId={projectId}
|
|
506
505
|
element={element}
|
|
507
506
|
styles={styles}
|
|
508
507
|
onSetStyle={onSetStyle}
|
|
509
508
|
onSetAttribute={onSetAttribute}
|
|
510
|
-
onSetHtmlAttribute={onSetHtmlAttribute}
|
|
511
|
-
onRemoveBackground={onRemoveBackground}
|
|
512
509
|
{...volumeAutomation}
|
|
513
510
|
/>
|
|
514
511
|
),
|
|
@@ -52,7 +52,7 @@ function AppHotkeyHarness() {
|
|
|
52
52
|
writeProjectFile: vi.fn(async () => undefined),
|
|
53
53
|
showToast: vi.fn(),
|
|
54
54
|
syncHistoryPreviewAfterApply: vi.fn(async () => undefined),
|
|
55
|
-
|
|
55
|
+
settlePendingEdits: vi.fn(async () => undefined),
|
|
56
56
|
handleCopy: vi.fn(() => false),
|
|
57
57
|
handlePaste: vi.fn(async () => undefined),
|
|
58
58
|
handleCut: vi.fn(async () => false),
|
|
@@ -133,6 +133,37 @@ describe("SnapToolbar keyboard shortcuts", () => {
|
|
|
133
133
|
});
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
describe("SnapToolbar keys and a focused control", () => {
|
|
137
|
+
let root: ReturnType<typeof createRoot> | null = null;
|
|
138
|
+
afterEach(() => {
|
|
139
|
+
act(() => root?.unmount());
|
|
140
|
+
root = null;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const controls: Array<[string, string, string, string]> = [
|
|
144
|
+
["a combobox", "button", "role", "combobox"],
|
|
145
|
+
["a select", "select", "name", "font"],
|
|
146
|
+
["a switch", "button", "role", "switch"],
|
|
147
|
+
["a video player", "video", "controls", ""],
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
it.each(controls)("leaves S and G alone while %s has focus", (_name, tag, attr, value) => {
|
|
151
|
+
root = renderToolbar().root;
|
|
152
|
+
const control = document.body.appendChild(document.createElement(tag));
|
|
153
|
+
control.setAttribute(attr, value);
|
|
154
|
+
act(() => {
|
|
155
|
+
for (const key of ["s", "g"])
|
|
156
|
+
control.dispatchEvent(
|
|
157
|
+
new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true }),
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const prefs = JSON.parse(window.localStorage.getItem("hf-studio-ui-preferences") ?? "{}");
|
|
162
|
+
expect(prefs.snapEnabled).not.toBe(false);
|
|
163
|
+
expect(prefs.gridVisible).not.toBe(true);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
136
167
|
describe("SnapToolbar ruler and safe-margin toggles", () => {
|
|
137
168
|
it.each([
|
|
138
169
|
["Toggle ruler", "rulerVisible"],
|
|
@@ -2,6 +2,7 @@ import { memo, useCallback, useEffect, useRef, useState } from "react";
|
|
|
2
2
|
import { MagnetStraight, GridFour, Path, Ruler, FrameCorners } from "@phosphor-icons/react";
|
|
3
3
|
import { usePlayerStore } from "../../player/store/playerStore";
|
|
4
4
|
import { usePreviewOverlayContext } from "./PreviewOverlayProvider";
|
|
5
|
+
import { ownsPlainKeys } from "../../utils/typingTarget";
|
|
5
6
|
|
|
6
7
|
// fallow-ignore-next-line complexity
|
|
7
8
|
export const SnapToolbar = memo(function SnapToolbar() {
|
|
@@ -35,10 +36,8 @@ export const SnapToolbar = memo(function SnapToolbar() {
|
|
|
35
36
|
// fallow-ignore-next-line complexity
|
|
36
37
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
37
38
|
if (e.defaultPrevented) return;
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
if (t instanceof HTMLElement && t.isContentEditable) return;
|
|
41
|
-
if (t instanceof HTMLIFrameElement) return;
|
|
39
|
+
if (ownsPlainKeys(e.target)) return;
|
|
40
|
+
if (e.target instanceof HTMLIFrameElement) return;
|
|
42
41
|
if (e.key === "s" && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
43
42
|
e.preventDefault();
|
|
44
43
|
updatePrefs({ snapEnabled: !prefs.snapEnabled });
|
|
@@ -11,12 +11,12 @@ import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
|
|
11
11
|
import { bracketMatching, foldGutter, indentOnInput } from "@codemirror/language";
|
|
12
12
|
import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
|
|
13
13
|
import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
|
|
14
|
-
import { oneDark } from "@codemirror/theme-one-dark";
|
|
15
14
|
import type { Extension } from "@codemirror/state";
|
|
16
15
|
import { html } from "@codemirror/lang-html";
|
|
17
16
|
import { css } from "@codemirror/lang-css";
|
|
18
17
|
import { javascript } from "@codemirror/lang-javascript";
|
|
19
18
|
import { markdown } from "@codemirror/lang-markdown";
|
|
19
|
+
import { studioCodeTheme } from "./studioCodeTheme";
|
|
20
20
|
|
|
21
21
|
// Marks a programmatic doc sync (external content push — e.g. a manual-edit
|
|
22
22
|
// commit writing the source) so the update listener doesn't mistake it for a
|
|
@@ -114,13 +114,9 @@ export const SourceEditor = memo(function SourceEditor({
|
|
|
114
114
|
highlightSelectionMatches(),
|
|
115
115
|
keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...searchKeymap, ...historyKeymap]),
|
|
116
116
|
getLanguageExtension(lang),
|
|
117
|
-
|
|
117
|
+
studioCodeTheme,
|
|
118
118
|
updateListener,
|
|
119
119
|
EditorState.readOnly.of(readOnly),
|
|
120
|
-
EditorView.theme({
|
|
121
|
-
"&": { height: "100%" },
|
|
122
|
-
".cm-scroller": { overflow: "auto" },
|
|
123
|
-
}),
|
|
124
120
|
],
|
|
125
121
|
});
|
|
126
122
|
|