@dailies-studio/shot-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -0,0 +1,1606 @@
|
|
|
1
|
+
// EVERY BINDING THIS CHROME HAS, as a table — and the one place a combo string
|
|
2
|
+
// is written down.
|
|
3
|
+
//
|
|
4
|
+
// It lived inside HelpSection.tsx, which was fine while the manual was its only
|
|
5
|
+
// reader. The app menu is the second, and a data table reached through a React
|
|
6
|
+
// component is a data table that drags a Dialog into `state/menu.ts` (which is
|
|
7
|
+
// deliberately JSX-free). So it moved here, beside `keymap.ts`, which is what
|
|
8
|
+
// it documents. The manual imports it and re-exports it; nothing else changed.
|
|
9
|
+
//
|
|
10
|
+
// ── WHY IT IS HAND-WRITTEN AND NOT DERIVED ──────────────────────────────
|
|
11
|
+
//
|
|
12
|
+
// `resolveKey` is a FUNCTION with seven ordered lanes, not a table: the same
|
|
13
|
+
// letter is two commands depending on where the pointer is, Escape is a
|
|
14
|
+
// four-rung ladder, and one lane blanks every other key on purpose. There is no
|
|
15
|
+
// inverse to invert.
|
|
16
|
+
//
|
|
17
|
+
// So each row carries the EXACT EVENT it claims to describe plus the action
|
|
18
|
+
// kind the keymap must answer for it, and test/helpSection.test.tsx walks every
|
|
19
|
+
// row through the real `resolveKey`. A binding change without a table edit is a
|
|
20
|
+
// red test, not a stale manual — and now, not a stale menu either.
|
|
21
|
+
//
|
|
22
|
+
// The SHAPE of every row is still written out here. What some rows no longer
|
|
23
|
+
// write is their WORDS: the three layout keys take combo, prose and menu id
|
|
24
|
+
// from `state/layouts.ts`, which is the table the toolbar and the View menu
|
|
25
|
+
// already read, so a rename cannot reach two of those three surfaces and stop.
|
|
26
|
+
import type { KeyAction, KeyContext, KeyEventLike, SketchKeyAction } from "./keymap.js";
|
|
27
|
+
import { cameraVerb } from "./cameraVerbs.js";
|
|
28
|
+
import { LAYOUT_MODES, layoutSpec } from "./layouts.js";
|
|
29
|
+
import { SHADING_MODES, shadingLabel } from "../stage/shading.js";
|
|
30
|
+
import { VIEW_MODE_SPECS } from "./viewMode.js";
|
|
31
|
+
import { specOf } from "../tools.js";
|
|
32
|
+
import type { ShotTool } from "../tools.js";
|
|
33
|
+
import { ALIGN_LABELS } from "@dailies-studio/shot/doc";
|
|
34
|
+
|
|
35
|
+
/** The manual's sections, in the order it prints them. It reads this to render
|
|
36
|
+
* them, so a new group is one edit here rather than a union the manual has to
|
|
37
|
+
* be taught a second time. */
|
|
38
|
+
export const SHORTCUT_GROUPS = [
|
|
39
|
+
"View",
|
|
40
|
+
// ITS OWN GROUP, not rows folded into "View". Every one of these moves the
|
|
41
|
+
// PLAYHEAD or the clock that drives it, which is the one subject an author
|
|
42
|
+
// looks for as a set — and the set is now large enough (a shuttle, three
|
|
43
|
+
// kinds of jump, a work area) that scattering it through the view keys would
|
|
44
|
+
// hide it. `HelpSection` derives its sections from this list, so the heading
|
|
45
|
+
// costs one word here and nothing there.
|
|
46
|
+
"Transport",
|
|
47
|
+
"3D view",
|
|
48
|
+
"Edit",
|
|
49
|
+
"Animate & camera",
|
|
50
|
+
"Material nodes",
|
|
51
|
+
"Storyboard",
|
|
52
|
+
] as const;
|
|
53
|
+
|
|
54
|
+
export type ShortcutGroup = (typeof SHORTCUT_GROUPS)[number];
|
|
55
|
+
|
|
56
|
+
export interface ShortcutRow {
|
|
57
|
+
/** What the manual shows, house glyph style (⌘ ⇧ ⌥ — ⌘ is Ctrl on Windows). */
|
|
58
|
+
combo: string;
|
|
59
|
+
/**
|
|
60
|
+
* The Mac's chord, for the rows whose Mac keys are NOT the same keys under
|
|
61
|
+
* the Mac's glyphs, with the event it sends. Arrange's front and back are
|
|
62
|
+
* Ctrl+⇧] on Windows and Linux and ⌥⌘] on a Mac, because ⌘⇧] and ⌘⇧[ are
|
|
63
|
+
* the Mac's tab switch (founder decision 3, critic 5). `comboOf` prints this
|
|
64
|
+
* on a Mac and `combo` everywhere else; the manual's test walks both probes.
|
|
65
|
+
*/
|
|
66
|
+
mac?: { combo: string; probe: KeyEventLike };
|
|
67
|
+
does: string;
|
|
68
|
+
group: ShortcutGroup;
|
|
69
|
+
/** The event this combo produces, so the test can ask the keymap itself. */
|
|
70
|
+
probe: KeyEventLike;
|
|
71
|
+
/**
|
|
72
|
+
* The app-menu row this binding belongs to, when one exists.
|
|
73
|
+
*
|
|
74
|
+
* ── WHY THE MENU READS THIS TABLE RATHER THAN TYPING ITS OWN GLYPHS ────
|
|
75
|
+
*
|
|
76
|
+
* `ZoomControls.tsx` typed its own hint and it drifted (it still claimed ⇧Z
|
|
77
|
+
* for a fit that moved to Home). The menu is forty-odd more chances to do
|
|
78
|
+
* that. Pointing a row at a table entry costs one field and inherits that
|
|
79
|
+
* entry's test: rebind the key, and the walk-through fails on the row whose
|
|
80
|
+
* accelerator the menu was about to render.
|
|
81
|
+
*
|
|
82
|
+
* A row that covers SEVERAL keys in one line of prose ("Num2 / 4 / 6 / 8")
|
|
83
|
+
* carries no id — its `combo` is not a single accelerator, and a menu row
|
|
84
|
+
* showing it would be lying about which key it is. Where a menu wanted the
|
|
85
|
+
* digits individually the prose row was SPLIT instead (the three layout
|
|
86
|
+
* digits, the two zoom directions, the two clear-transform channels), so
|
|
87
|
+
* every id maps to exactly one key and the manual gained the detail too.
|
|
88
|
+
*/
|
|
89
|
+
menuId?: string;
|
|
90
|
+
/** Arrows answer differently with a selection; everything else omits it. */
|
|
91
|
+
hasSelection?: boolean;
|
|
92
|
+
/** The pen's two rows answer differently with a path open, and ONLY with one
|
|
93
|
+
* open — Escape means deselect the rest of the time, which is why the
|
|
94
|
+
* context carries "holds points" and not "the pen tool is armed". */
|
|
95
|
+
penActive?: boolean;
|
|
96
|
+
/** Escape's third meaning: leave one level of isolation. It only answers
|
|
97
|
+
* that with NOTHING selected, so the row carries the bit and the test
|
|
98
|
+
* probes with `hasSelection` false. */
|
|
99
|
+
isolation?: boolean;
|
|
100
|
+
/** Escape's FIRST meaning, above all the others: put the editor back. Only
|
|
101
|
+
* while Video is on screen, so the row carries the bit. */
|
|
102
|
+
video?: boolean;
|
|
103
|
+
/** The modal starters (G/R/S) answer only with the cursor over the working
|
|
104
|
+
* pane — the same letters arm tools everywhere else, and both readings
|
|
105
|
+
* have rows. */
|
|
106
|
+
workingPane?: boolean;
|
|
107
|
+
/** The axis and terminator rows answer only while a transform is LIVE. */
|
|
108
|
+
transformModal?: boolean;
|
|
109
|
+
/** The material-graph rows answer only inside the node canvas — and answer
|
|
110
|
+
* something ELSE outside it, which is the whole reason the lane exists. */
|
|
111
|
+
materialGraph?: boolean;
|
|
112
|
+
/** The storyboard rows answer only while the board is open, and answer
|
|
113
|
+
* something ELSE outside it: Delete removes a layer there. */
|
|
114
|
+
storyboard?: boolean;
|
|
115
|
+
/** A shot board's sketch keys (`resolveShotBoardKey`). */
|
|
116
|
+
shotBoard?: boolean;
|
|
117
|
+
/** The rest of the context a row answers under, for the bits only a row or
|
|
118
|
+
* two read (Escape's lower rungs, the clipboards). The manual's test spreads
|
|
119
|
+
* it into the probe's context. */
|
|
120
|
+
context?: Omit<KeyContext, "editing" | "hasSelection">;
|
|
121
|
+
/** The action `resolveKey(probe)` must return (`resolveShotBoardKey` for a
|
|
122
|
+
* shot board row). */
|
|
123
|
+
action: KeyAction["kind"] | SketchKeyAction["kind"];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const probe = (
|
|
127
|
+
key: string,
|
|
128
|
+
m: { shift?: boolean; ctrl?: boolean; alt?: boolean; meta?: boolean; code?: string } = {},
|
|
129
|
+
): KeyEventLike => ({
|
|
130
|
+
key,
|
|
131
|
+
shiftKey: m.shift === true,
|
|
132
|
+
altKey: m.alt === true,
|
|
133
|
+
ctrlKey: m.ctrl === true,
|
|
134
|
+
metaKey: m.meta === true,
|
|
135
|
+
...(m.code === undefined ? {} : { code: m.code }),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export const SHORTCUTS: readonly ShortcutRow[] = [
|
|
139
|
+
{
|
|
140
|
+
group: "View",
|
|
141
|
+
combo: "⇧S",
|
|
142
|
+
does: "Open or close the Storyboard",
|
|
143
|
+
probe: probe("s", { shift: true }),
|
|
144
|
+
action: "storyboardToggle",
|
|
145
|
+
menuId: "ctx.storyboard",
|
|
146
|
+
},
|
|
147
|
+
// ── THE TRANSPORT ────────────────────────────────────────────────────────
|
|
148
|
+
//
|
|
149
|
+
// Space and the step keys were rows of "View" when the playhead had three
|
|
150
|
+
// bindings between them. It has eleven now, so they moved into the group the
|
|
151
|
+
// rest of them are in rather than leaving the set split across two headings.
|
|
152
|
+
{
|
|
153
|
+
group: "Transport",
|
|
154
|
+
combo: "Space",
|
|
155
|
+
does: "Play / pause",
|
|
156
|
+
probe: probe(" "),
|
|
157
|
+
action: "playToggle",
|
|
158
|
+
menuId: "time.play",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
group: "Transport",
|
|
162
|
+
combo: "← →",
|
|
163
|
+
does: "Step one frame (nothing selected)",
|
|
164
|
+
probe: probe("ArrowRight"),
|
|
165
|
+
action: "step",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
group: "Transport",
|
|
169
|
+
combo: "⇧← ⇧→",
|
|
170
|
+
does: "Step one second (nothing selected)",
|
|
171
|
+
probe: probe("ArrowRight", { shift: true }),
|
|
172
|
+
action: "stepSeconds",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
group: "Transport",
|
|
176
|
+
combo: "↑ ↓",
|
|
177
|
+
does: "To the previous / next edit point: a shot boundary, a lifespan edge, a marker",
|
|
178
|
+
probe: probe("ArrowUp"),
|
|
179
|
+
action: "jumpEdit",
|
|
180
|
+
},
|
|
181
|
+
// ── THE SHUTTLE, AND WHY IT IS NOT J / K / L ─────────────────────────────
|
|
182
|
+
//
|
|
183
|
+
// `L` is the Line tool and `⇧L` is the Arrow tool (`TOOL_SPECS`), and ⌥L is
|
|
184
|
+
// the lock. A shuttle on any of the three would shadow a binding that has no
|
|
185
|
+
// other key, so the run's third rung is ⇧K. See the block in keymap.ts.
|
|
186
|
+
{
|
|
187
|
+
group: "Transport",
|
|
188
|
+
combo: "J",
|
|
189
|
+
does: "Shuttle backwards: 1x, then 2x, then 4x in reverse",
|
|
190
|
+
probe: probe("j"),
|
|
191
|
+
action: "shuttle",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
group: "Transport",
|
|
195
|
+
combo: "K",
|
|
196
|
+
does: "Stop, and put the speed back to 1x",
|
|
197
|
+
probe: probe("k"),
|
|
198
|
+
action: "shuttleStop",
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
group: "Transport",
|
|
202
|
+
combo: "⇧K",
|
|
203
|
+
does: "Shuttle forwards: 1x, then 2x, then 4x (L is the Line tool)",
|
|
204
|
+
probe: probe("k", { shift: true }),
|
|
205
|
+
action: "shuttle",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
group: "Transport",
|
|
209
|
+
combo: ";",
|
|
210
|
+
does: "To the selection's in point",
|
|
211
|
+
probe: probe(";"),
|
|
212
|
+
action: "seekSelectionEdge",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
group: "Transport",
|
|
216
|
+
combo: "'",
|
|
217
|
+
does: "To the selection's out point",
|
|
218
|
+
probe: probe("'"),
|
|
219
|
+
action: "seekSelectionEdge",
|
|
220
|
+
},
|
|
221
|
+
// ── THE WORK AREA ────────────────────────────────────────────────────────
|
|
222
|
+
//
|
|
223
|
+
// Session state, never the document: it is what playback loops inside while
|
|
224
|
+
// an author watches one stretch over and over. See `ShotEditorState.workArea`
|
|
225
|
+
// for why it is not a document field.
|
|
226
|
+
{
|
|
227
|
+
group: "Transport",
|
|
228
|
+
combo: "⌥I",
|
|
229
|
+
does: "Work area in at the playhead (playback loops inside it)",
|
|
230
|
+
probe: probe("i", { alt: true }),
|
|
231
|
+
action: "setWorkArea",
|
|
232
|
+
menuId: "time.workArea.in",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
group: "Transport",
|
|
236
|
+
combo: "⌥O",
|
|
237
|
+
does: "Work area out at the playhead",
|
|
238
|
+
probe: probe("o", { alt: true }),
|
|
239
|
+
action: "setWorkArea",
|
|
240
|
+
menuId: "time.workArea.out",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
group: "Transport",
|
|
244
|
+
combo: "⌥X",
|
|
245
|
+
does: "Clear the work area: loop the whole film again",
|
|
246
|
+
probe: probe("x", { alt: true }),
|
|
247
|
+
action: "clearWorkArea",
|
|
248
|
+
menuId: "time.workArea.clear",
|
|
249
|
+
},
|
|
250
|
+
// THE KEYFRAME JUMPS, on the alt lane since the shuttle took J and K. They
|
|
251
|
+
// had no row at all before, so the manual was silent about a working pair of
|
|
252
|
+
// keys — which is the state this table exists to prevent.
|
|
253
|
+
{
|
|
254
|
+
group: "Transport",
|
|
255
|
+
combo: "⌥J",
|
|
256
|
+
does: "To the previous keyframe on the focused track",
|
|
257
|
+
probe: probe("j", { alt: true }),
|
|
258
|
+
action: "jumpKeyframe",
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
group: "Transport",
|
|
262
|
+
combo: "⌥K",
|
|
263
|
+
does: "To the next keyframe on the focused track",
|
|
264
|
+
probe: probe("k", { alt: true }),
|
|
265
|
+
action: "jumpKeyframe",
|
|
266
|
+
},
|
|
267
|
+
// THREE ROWS, one per layout, in key order. Still one row per key, so the
|
|
268
|
+
// View ▸ Layout entries can each name their own and each is walked through
|
|
269
|
+
// `resolveKey` on its own, which a single "⇧1 / ⇧2 / ⇧3" line could not do.
|
|
270
|
+
// The words, the keys and the menu ids come out of `state/layouts.ts`, so a
|
|
271
|
+
// rename or a rebind reaches the manual with no edit here. `probe` is
|
|
272
|
+
// module-private, which is why the derivation lives in this file rather than
|
|
273
|
+
// beside the table. The probe's `key` is the digit (what AZERTY reports);
|
|
274
|
+
// the keymap reads `code`, so a US board's "!" answers the same.
|
|
275
|
+
...LAYOUT_MODES.map((mode): ShortcutRow => {
|
|
276
|
+
const spec = layoutSpec(mode);
|
|
277
|
+
return {
|
|
278
|
+
group: "View",
|
|
279
|
+
combo: spec.key,
|
|
280
|
+
does: spec.does,
|
|
281
|
+
probe: probe(spec.code.slice("Digit".length), { shift: true, code: spec.code }),
|
|
282
|
+
action: "stageLayout",
|
|
283
|
+
menuId: spec.menuId,
|
|
284
|
+
};
|
|
285
|
+
}),
|
|
286
|
+
// The two view-mode rows stay written out: Video's key and Escape's are not a
|
|
287
|
+
// pair (`VIEW_MODE_SPECS` says so), and the Escape row carries a context bit
|
|
288
|
+
// no table can hold. What they no longer own is the key or the word.
|
|
289
|
+
{
|
|
290
|
+
group: "View",
|
|
291
|
+
combo: VIEW_MODE_SPECS.video.key,
|
|
292
|
+
does: `${VIEW_MODE_SPECS.video.label}: the finished picture, and the only view that plays`,
|
|
293
|
+
probe: probe("$", { shift: true, code: VIEW_MODE_SPECS.video.code }),
|
|
294
|
+
action: "viewMode",
|
|
295
|
+
menuId: "view.video",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
group: "View",
|
|
299
|
+
// The spec's `key` is the GLYPH the manual prints ("Esc"); the event this
|
|
300
|
+
// sends is the browser's "Escape". Two spellings of one key, which is why
|
|
301
|
+
// the probe is not built from the combo the way the layout rows' are.
|
|
302
|
+
combo: VIEW_MODE_SPECS.edit.key,
|
|
303
|
+
does: `Leave ${VIEW_MODE_SPECS.video.label}, and put the editor back`,
|
|
304
|
+
probe: probe("Escape"),
|
|
305
|
+
video: true,
|
|
306
|
+
action: "viewMode",
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
group: "View",
|
|
310
|
+
combo: "[",
|
|
311
|
+
does: "Hide / show the left panel",
|
|
312
|
+
probe: probe("["),
|
|
313
|
+
action: "togglePanel",
|
|
314
|
+
menuId: "view.panel.rail",
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
group: "View",
|
|
318
|
+
combo: "]",
|
|
319
|
+
does: "Hide / show the right panel",
|
|
320
|
+
probe: probe("]"),
|
|
321
|
+
action: "togglePanel",
|
|
322
|
+
menuId: "view.panel.inspector",
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
group: "View",
|
|
326
|
+
combo: ".",
|
|
327
|
+
does: "Hide every panel",
|
|
328
|
+
probe: probe("."),
|
|
329
|
+
action: "togglePanel",
|
|
330
|
+
menuId: "view.panel.all",
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
group: "View",
|
|
334
|
+
combo: "⌥M",
|
|
335
|
+
does: "Sound mode on / off: the timeline's tracks and mixer",
|
|
336
|
+
probe: probe("m", { alt: true }),
|
|
337
|
+
action: "soundMode",
|
|
338
|
+
menuId: "time.mode.sound",
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
group: "View",
|
|
342
|
+
combo: "\\",
|
|
343
|
+
does: "Fit the timeline",
|
|
344
|
+
probe: probe("\\"),
|
|
345
|
+
action: "fitTimeline",
|
|
346
|
+
menuId: "time.fit",
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
group: "View",
|
|
350
|
+
combo: "⌘+",
|
|
351
|
+
does: "Zoom in",
|
|
352
|
+
probe: probe("=", { ctrl: true }),
|
|
353
|
+
action: "zoom",
|
|
354
|
+
menuId: "view.zoomIn",
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
group: "View",
|
|
358
|
+
combo: "⌘−",
|
|
359
|
+
does: "Zoom out",
|
|
360
|
+
probe: probe("-", { ctrl: true }),
|
|
361
|
+
action: "zoom",
|
|
362
|
+
menuId: "view.zoomOut",
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
group: "View",
|
|
366
|
+
combo: "⌘0",
|
|
367
|
+
does: "Zoom to 100%",
|
|
368
|
+
probe: probe("0", { ctrl: true }),
|
|
369
|
+
action: "zoomReset",
|
|
370
|
+
menuId: "view.zoomReset",
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
group: "View",
|
|
374
|
+
combo: "Home",
|
|
375
|
+
does: "Fit the active pane",
|
|
376
|
+
probe: probe("Home"),
|
|
377
|
+
action: "fit",
|
|
378
|
+
menuId: "view.fit",
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
group: "View",
|
|
382
|
+
combo: "⇧C",
|
|
383
|
+
does: "Fit the active pane (Blender's frame all)",
|
|
384
|
+
probe: probe("c", { shift: true }),
|
|
385
|
+
action: "fit",
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
group: "Edit",
|
|
389
|
+
combo: "C",
|
|
390
|
+
does: "Comment on the picture at the playhead",
|
|
391
|
+
probe: probe("c"),
|
|
392
|
+
action: "storyboardComment",
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
group: "View",
|
|
396
|
+
combo: "Z",
|
|
397
|
+
does: `Cycle working-pane shading (${SHADING_MODES.map((m) => shadingLabel(m).toLowerCase()).join(" / ")})`,
|
|
398
|
+
probe: probe("z"),
|
|
399
|
+
action: "shadingCycle",
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
group: "View",
|
|
403
|
+
combo: "⇧Z",
|
|
404
|
+
does: `${shadingLabel("wireframe")} on / off`,
|
|
405
|
+
probe: probe("z", { shift: true }),
|
|
406
|
+
action: "wireframeToggle",
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
group: "View",
|
|
410
|
+
combo: "⌥Z",
|
|
411
|
+
does: "Grid floor on / off (working pane)",
|
|
412
|
+
probe: probe("z", { alt: true }),
|
|
413
|
+
action: "gridToggle",
|
|
414
|
+
menuId: "view.grid",
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
group: "View",
|
|
418
|
+
combo: "⇧R",
|
|
419
|
+
does: "Rulers on / off (camera pane)",
|
|
420
|
+
probe: probe("r", { shift: true }),
|
|
421
|
+
action: "rulersToggle",
|
|
422
|
+
menuId: "view.rulers",
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
{
|
|
426
|
+
group: "3D view",
|
|
427
|
+
combo: "G",
|
|
428
|
+
does: "Grab the selection: move it with the mouse, click to place",
|
|
429
|
+
probe: probe("g"),
|
|
430
|
+
workingPane: true,
|
|
431
|
+
hasSelection: true,
|
|
432
|
+
action: "modalStart",
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
group: "3D view",
|
|
436
|
+
combo: "R",
|
|
437
|
+
// A Shot's 3D view only: inside a definition the one pane is the frame,
|
|
438
|
+
// where R arms Rect (the keymap's `framePane` bit). A code component's
|
|
439
|
+
// preview is no frame, so R keeps its rotate there.
|
|
440
|
+
does: "Rotate the selection (roll). Inside a Studio-made component, R arms Rect",
|
|
441
|
+
probe: probe("r"),
|
|
442
|
+
workingPane: true,
|
|
443
|
+
hasSelection: true,
|
|
444
|
+
action: "modalStart",
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
group: "3D view",
|
|
448
|
+
combo: "S",
|
|
449
|
+
does: "Scale the selection",
|
|
450
|
+
probe: probe("s"),
|
|
451
|
+
workingPane: true,
|
|
452
|
+
hasSelection: true,
|
|
453
|
+
action: "modalStart",
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
group: "3D view",
|
|
457
|
+
combo: "G / R / S",
|
|
458
|
+
does: "Nothing selected: move / roll / zoom the camera",
|
|
459
|
+
probe: probe("g"),
|
|
460
|
+
workingPane: true,
|
|
461
|
+
action: "modalStart",
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
group: "3D view",
|
|
465
|
+
combo: "X / Y / Z",
|
|
466
|
+
does: "Constrain the transform to an axis (again clears; G+Z = depth)",
|
|
467
|
+
probe: probe("x"),
|
|
468
|
+
transformModal: true,
|
|
469
|
+
action: "modalAxis",
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
group: "3D view",
|
|
473
|
+
combo: "⇧X / ⇧Y / ⇧Z",
|
|
474
|
+
does: "Constrain a move to the PLANE normal to that axis (⇧X moves in YZ)",
|
|
475
|
+
probe: probe("x", { shift: true }),
|
|
476
|
+
transformModal: true,
|
|
477
|
+
action: "modalAxis",
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
group: "3D view",
|
|
481
|
+
combo: "0-9",
|
|
482
|
+
does: "Type an exact value: G X 200 Enter",
|
|
483
|
+
probe: probe("2"),
|
|
484
|
+
transformModal: true,
|
|
485
|
+
action: "modalNumber",
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
group: "3D view",
|
|
489
|
+
combo: "Backspace",
|
|
490
|
+
does: "Erase a typed digit (empty gives the mouse back)",
|
|
491
|
+
probe: probe("Backspace"),
|
|
492
|
+
transformModal: true,
|
|
493
|
+
action: "modalNumberBack",
|
|
494
|
+
},
|
|
495
|
+
// `hasSelection` only because the row PROBES with R over a live modal, and
|
|
496
|
+
// the manual's example is a layer transform; the camera rolls under R as
|
|
497
|
+
// well (`framingDeltaFor`, and the gizmo's own arc).
|
|
498
|
+
{
|
|
499
|
+
group: "3D view",
|
|
500
|
+
combo: "G / R / S",
|
|
501
|
+
does: "Switch transform without leaving the gesture",
|
|
502
|
+
probe: probe("r"),
|
|
503
|
+
transformModal: true,
|
|
504
|
+
hasSelection: true,
|
|
505
|
+
action: "modalStart",
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
group: "3D view",
|
|
509
|
+
combo: "Enter",
|
|
510
|
+
does: "Confirm the transform (or click)",
|
|
511
|
+
probe: probe("Enter"),
|
|
512
|
+
transformModal: true,
|
|
513
|
+
action: "modalConfirm",
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
group: "3D view",
|
|
517
|
+
combo: "Esc",
|
|
518
|
+
does: "Cancel the transform (or right-click)",
|
|
519
|
+
probe: probe("Escape"),
|
|
520
|
+
transformModal: true,
|
|
521
|
+
action: "modalCancel",
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
group: "3D view",
|
|
525
|
+
combo: "Num1 / 3 / 7",
|
|
526
|
+
does: "Front / right / top view (⌘ = the opposite)",
|
|
527
|
+
probe: probe("1", { code: "Numpad1" }),
|
|
528
|
+
action: "workingViewPreset",
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
group: "3D view",
|
|
532
|
+
combo: "Num0",
|
|
533
|
+
// The verb table's own sentence, for the reason the layout rows take
|
|
534
|
+
// theirs from `layouts.ts`: the bar, the menu and this manual must not
|
|
535
|
+
// spell one verb three ways again.
|
|
536
|
+
does: cameraVerb("pilot").sentence,
|
|
537
|
+
probe: probe("0", { code: "Numpad0" }),
|
|
538
|
+
action: "pilotToggle",
|
|
539
|
+
menuId: cameraVerb("pilot").menuId,
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
group: "3D view",
|
|
543
|
+
combo: "Esc",
|
|
544
|
+
does: "Stop piloting the camera",
|
|
545
|
+
probe: probe("Escape"),
|
|
546
|
+
context: { pilot: true },
|
|
547
|
+
action: "pilotEject",
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
group: "3D view",
|
|
551
|
+
combo: "Num.",
|
|
552
|
+
does: "Frame the selection",
|
|
553
|
+
probe: probe(".", { code: "NumpadDecimal" }),
|
|
554
|
+
action: "fitSelection",
|
|
555
|
+
menuId: "view.fitSelection",
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
group: "3D view",
|
|
559
|
+
combo: "Num2 / 4 / 6 / 8",
|
|
560
|
+
does: "Orbit 15°; Num9 looks from the opposite side",
|
|
561
|
+
probe: probe("4", { code: "Numpad4" }),
|
|
562
|
+
action: "orbitStep",
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
group: "3D view",
|
|
566
|
+
combo: "Num5",
|
|
567
|
+
does: "Orthographic / perspective",
|
|
568
|
+
probe: probe("5", { code: "Numpad5" }),
|
|
569
|
+
action: "toggleWorkingOrtho",
|
|
570
|
+
menuId: "view.ortho",
|
|
571
|
+
},
|
|
572
|
+
// NO NUMPAD, NO PROBLEM. The numpad lane keys on `event.code`, so a laptop
|
|
573
|
+
// without one had no keyboard path at all to the three views a 3D pane is
|
|
574
|
+
// most often asked for. Blender maps the bare top-row digits; here those are
|
|
575
|
+
// kept free, so the alt lane takes them.
|
|
576
|
+
{
|
|
577
|
+
group: "3D view",
|
|
578
|
+
combo: "⌥1 / ⌥3 / ⌥7",
|
|
579
|
+
does: "Front / right / top view without a numpad (add ⇧ for the opposite side)",
|
|
580
|
+
probe: probe("1", { alt: true }),
|
|
581
|
+
action: "workingViewPreset",
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
group: "3D view",
|
|
585
|
+
combo: "⌥5",
|
|
586
|
+
does: "Orthographic / perspective without a numpad",
|
|
587
|
+
probe: probe("5", { alt: true }),
|
|
588
|
+
action: "toggleWorkingOrtho",
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
group: "3D view",
|
|
592
|
+
combo: "Num/",
|
|
593
|
+
does: "Go inside the selection, and back out",
|
|
594
|
+
probe: probe("/", { code: "NumpadDivide" }),
|
|
595
|
+
action: "toggleIsolation",
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
group: "3D view",
|
|
599
|
+
combo: "Num+ / Num−",
|
|
600
|
+
does: "Zoom the active pane",
|
|
601
|
+
probe: probe("+", { code: "NumpadAdd" }),
|
|
602
|
+
action: "zoom",
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
{
|
|
606
|
+
group: "Edit",
|
|
607
|
+
combo: "Arrows",
|
|
608
|
+
does: "Nudge selection 1 px (⇧ = 10)",
|
|
609
|
+
probe: probe("ArrowRight"),
|
|
610
|
+
hasSelection: true,
|
|
611
|
+
action: "nudge",
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
group: "Edit",
|
|
615
|
+
combo: "← →",
|
|
616
|
+
does: "Move the selected keyframes 1 frame (⇧ = 10)",
|
|
617
|
+
probe: probe("ArrowRight"),
|
|
618
|
+
context: { hasKeyframeSelection: true },
|
|
619
|
+
action: "nudgeKeys",
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
group: "Edit",
|
|
623
|
+
combo: "⌘Z",
|
|
624
|
+
does: "Undo",
|
|
625
|
+
probe: probe("z", { ctrl: true }),
|
|
626
|
+
action: "undo",
|
|
627
|
+
menuId: "edit.undo",
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
group: "Edit",
|
|
631
|
+
combo: "⌘⇧Z",
|
|
632
|
+
does: "Redo",
|
|
633
|
+
probe: probe("z", { ctrl: true, shift: true }),
|
|
634
|
+
action: "redo",
|
|
635
|
+
menuId: "edit.redo",
|
|
636
|
+
},
|
|
637
|
+
// The OTHER redo, which every Windows editor binds and this one has always
|
|
638
|
+
// answered. It had no row, so the deleted docs table was the only place it
|
|
639
|
+
// was written down. No `menuId`: one menu row cannot print two accelerators,
|
|
640
|
+
// and ⌘⇧Z is the one it shows.
|
|
641
|
+
{
|
|
642
|
+
group: "Edit",
|
|
643
|
+
combo: "⌘Y",
|
|
644
|
+
does: "Redo (the other one)",
|
|
645
|
+
probe: probe("y", { ctrl: true }),
|
|
646
|
+
action: "redo",
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
group: "Edit",
|
|
650
|
+
combo: "⌘C",
|
|
651
|
+
does: "Copy the selected layers",
|
|
652
|
+
probe: probe("c", { ctrl: true }),
|
|
653
|
+
hasSelection: true,
|
|
654
|
+
action: "copyLayers",
|
|
655
|
+
menuId: "edit.copy",
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
group: "Edit",
|
|
659
|
+
combo: "⌘X",
|
|
660
|
+
does: "Cut the selected layers",
|
|
661
|
+
probe: probe("x", { ctrl: true }),
|
|
662
|
+
hasSelection: true,
|
|
663
|
+
action: "cutLayers",
|
|
664
|
+
menuId: "edit.cut",
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
group: "Edit",
|
|
668
|
+
combo: "⌘V",
|
|
669
|
+
does: "Paste the copied layers into their Shot",
|
|
670
|
+
probe: probe("v", { ctrl: true }),
|
|
671
|
+
context: { hasLayerClip: true },
|
|
672
|
+
action: "pasteLayers",
|
|
673
|
+
menuId: "edit.paste",
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
group: "Edit",
|
|
677
|
+
combo: "⌘C",
|
|
678
|
+
does: "Copy the selected keyframes",
|
|
679
|
+
probe: probe("c", { ctrl: true }),
|
|
680
|
+
context: { hasKeyframeSelection: true },
|
|
681
|
+
action: "copyKeyframes",
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
group: "Edit",
|
|
685
|
+
combo: "⌘V",
|
|
686
|
+
does: "Paste the copied keyframes at the playhead",
|
|
687
|
+
probe: probe("v", { ctrl: true }),
|
|
688
|
+
context: { hasKeyframeClip: true },
|
|
689
|
+
action: "pasteKeyframes",
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
group: "Edit",
|
|
693
|
+
combo: "⌘⌥C",
|
|
694
|
+
does: "Copy the selected layer's animation",
|
|
695
|
+
probe: probe("c", { ctrl: true, alt: true }),
|
|
696
|
+
// A Mac's Option composes the letter, so its probe carries the composed
|
|
697
|
+
// key and the `code` the keymap reads instead (`chordChar`).
|
|
698
|
+
mac: { combo: "⌥⌘C", probe: probe("ç", { meta: true, alt: true, code: "KeyC" }) },
|
|
699
|
+
// ⌥⌘C is also Chrome's View > Developer > Inspect Elements on a Mac. The
|
|
700
|
+
// page still gets the keydown first: Chromium's
|
|
701
|
+
// `BrowserCommandController::IsReservedCommandOrKey` reserves only the
|
|
702
|
+
// tab, window and quit commands (not `IDC_DEV_TOOLS_INSPECT`), so a
|
|
703
|
+
// claimed chord is `preventDefault`ed and Inspect never opens. With no
|
|
704
|
+
// layer selected the keymap leaves it to Chrome. Checked against the
|
|
705
|
+
// Chromium source (2026-10-12), not yet on Mac hardware: confirm there.
|
|
706
|
+
hasSelection: true,
|
|
707
|
+
action: "copyAnimation",
|
|
708
|
+
menuId: "edit.copyAnimation",
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
group: "Edit",
|
|
712
|
+
combo: "⌘⌥V",
|
|
713
|
+
does: "Paste the copied animation onto the selected layers, at the playhead",
|
|
714
|
+
probe: probe("v", { ctrl: true, alt: true }),
|
|
715
|
+
mac: { combo: "⌥⌘V", probe: probe("√", { meta: true, alt: true, code: "KeyV" }) },
|
|
716
|
+
hasSelection: true,
|
|
717
|
+
context: { hasAnimationClip: true },
|
|
718
|
+
action: "pasteAnimation",
|
|
719
|
+
menuId: "edit.pasteAnimation.playhead",
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
group: "Edit",
|
|
723
|
+
combo: "⌘⇧⌥C",
|
|
724
|
+
does: "Copy the selected layer's effects",
|
|
725
|
+
probe: probe("c", { ctrl: true, alt: true, shift: true }),
|
|
726
|
+
mac: { combo: "⇧⌥⌘C", probe: probe("Ç", { meta: true, alt: true, shift: true, code: "KeyC" }) },
|
|
727
|
+
hasSelection: true,
|
|
728
|
+
action: "copyEffects",
|
|
729
|
+
menuId: "edit.copyEffects",
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
group: "Edit",
|
|
733
|
+
combo: "⌘⇧⌥V",
|
|
734
|
+
does: "Paste the copied effects onto the selected layers",
|
|
735
|
+
probe: probe("v", { ctrl: true, alt: true, shift: true }),
|
|
736
|
+
mac: { combo: "⇧⌥⌘V", probe: probe("◊", { meta: true, alt: true, shift: true, code: "KeyV" }) },
|
|
737
|
+
hasSelection: true,
|
|
738
|
+
context: { hasEffectsClip: true },
|
|
739
|
+
action: "pasteEffects",
|
|
740
|
+
menuId: "edit.pasteEffects",
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
group: "Edit",
|
|
744
|
+
combo: "⌘S",
|
|
745
|
+
does: "Save a named version",
|
|
746
|
+
probe: probe("s", { ctrl: true }),
|
|
747
|
+
action: "save",
|
|
748
|
+
menuId: "file.save",
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
group: "Edit",
|
|
752
|
+
combo: "⌘D",
|
|
753
|
+
does: "Duplicate",
|
|
754
|
+
probe: probe("d", { ctrl: true }),
|
|
755
|
+
action: "duplicate",
|
|
756
|
+
menuId: "edit.duplicate",
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
group: "Edit",
|
|
760
|
+
combo: "⌘A",
|
|
761
|
+
does: "Select all, in the scope you are in",
|
|
762
|
+
probe: probe("a", { ctrl: true }),
|
|
763
|
+
action: "selectAll",
|
|
764
|
+
menuId: "edit.selectAll",
|
|
765
|
+
},
|
|
766
|
+
// Blender's bare A, the same verb through a second key. Split off ⌘A's row so
|
|
767
|
+
// that row's combo is one accelerator the menu can print; this one carries no
|
|
768
|
+
// id, because a row may name only one key.
|
|
769
|
+
{
|
|
770
|
+
group: "Edit",
|
|
771
|
+
combo: "A",
|
|
772
|
+
does: "Select all (Blender's key)",
|
|
773
|
+
probe: probe("a"),
|
|
774
|
+
action: "selectAll",
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
group: "Edit",
|
|
778
|
+
combo: "⌘⇧A",
|
|
779
|
+
does: "Deselect all",
|
|
780
|
+
probe: probe("a", { ctrl: true, shift: true }),
|
|
781
|
+
action: "deselect",
|
|
782
|
+
menuId: "edit.deselect",
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
group: "Edit",
|
|
786
|
+
combo: "B",
|
|
787
|
+
does: "Box select with the next drag",
|
|
788
|
+
probe: probe("b"),
|
|
789
|
+
action: "boxSelect",
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
group: "Edit",
|
|
793
|
+
combo: "⇧D",
|
|
794
|
+
does: "Duplicate",
|
|
795
|
+
probe: probe("d", { shift: true }),
|
|
796
|
+
action: "duplicate",
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
group: "Edit",
|
|
800
|
+
combo: "Esc",
|
|
801
|
+
does: "Deselect",
|
|
802
|
+
probe: probe("Escape"),
|
|
803
|
+
action: "deselect",
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
group: "Edit",
|
|
807
|
+
combo: "Del",
|
|
808
|
+
does: "Delete selection",
|
|
809
|
+
probe: probe("Delete"),
|
|
810
|
+
action: "deleteSelection",
|
|
811
|
+
menuId: "edit.delete",
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
group: "Edit",
|
|
815
|
+
combo: "⇧Backspace",
|
|
816
|
+
does: "Ripple delete: remove the layers and close the gap",
|
|
817
|
+
probe: probe("Backspace", { shift: true }),
|
|
818
|
+
action: "rippleDelete",
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
group: "Edit",
|
|
822
|
+
combo: "⌘G",
|
|
823
|
+
does: "Group",
|
|
824
|
+
probe: probe("g", { ctrl: true }),
|
|
825
|
+
action: "group",
|
|
826
|
+
menuId: "edit.group",
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
group: "Edit",
|
|
830
|
+
combo: "⌘⇧G",
|
|
831
|
+
does: "Ungroup",
|
|
832
|
+
probe: probe("g", { ctrl: true, shift: true }),
|
|
833
|
+
action: "ungroup",
|
|
834
|
+
menuId: "edit.ungroup",
|
|
835
|
+
},
|
|
836
|
+
// ── ARRANGE, Figma's bracket chords ─────────────────────────────────────
|
|
837
|
+
//
|
|
838
|
+
// Each layer among its own siblings, which is paint order (the layers panel
|
|
839
|
+
// lists them front first). The probes carry `code`, because the keymap keys
|
|
840
|
+
// on the physical key: Shift turns `]` into `}` and Option into a quote. The
|
|
841
|
+
// front and back rows split by platform (`mac`): ⌘⇧] and ⌘⇧[ are the Mac's
|
|
842
|
+
// tab switch and are never claimed.
|
|
843
|
+
{
|
|
844
|
+
group: "Edit",
|
|
845
|
+
combo: "⌘]",
|
|
846
|
+
does: "Bring forward, past one sibling",
|
|
847
|
+
probe: probe("]", { ctrl: true, code: "BracketRight" }),
|
|
848
|
+
action: "arrange",
|
|
849
|
+
menuId: "layout.arrange.forward",
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
group: "Edit",
|
|
853
|
+
combo: "⌘[",
|
|
854
|
+
does: "Send backward, past one sibling",
|
|
855
|
+
probe: probe("[", { ctrl: true, code: "BracketLeft" }),
|
|
856
|
+
action: "arrange",
|
|
857
|
+
menuId: "layout.arrange.backward",
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
group: "Edit",
|
|
861
|
+
combo: "⌘⇧]",
|
|
862
|
+
does: "Bring to front, among its siblings",
|
|
863
|
+
probe: probe("}", { ctrl: true, shift: true, code: "BracketRight" }),
|
|
864
|
+
mac: { combo: "⌥⌘]", probe: probe("‘", { meta: true, alt: true, code: "BracketRight" }) },
|
|
865
|
+
action: "arrange",
|
|
866
|
+
menuId: "layout.arrange.front",
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
group: "Edit",
|
|
870
|
+
combo: "⌘⇧[",
|
|
871
|
+
does: "Send to back, among its siblings",
|
|
872
|
+
probe: probe("{", { ctrl: true, shift: true, code: "BracketLeft" }),
|
|
873
|
+
mac: { combo: "⌥⌘[", probe: probe("“", { meta: true, alt: true, code: "BracketLeft" }) },
|
|
874
|
+
action: "arrange",
|
|
875
|
+
menuId: "layout.arrange.back",
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
group: "Edit",
|
|
879
|
+
combo: "⌥L",
|
|
880
|
+
does: "Lock / unlock",
|
|
881
|
+
probe: probe("l", { alt: true }),
|
|
882
|
+
action: "toggleLock",
|
|
883
|
+
menuId: "layer.lock",
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
group: "Edit",
|
|
887
|
+
combo: "H",
|
|
888
|
+
does: "Hide the selection",
|
|
889
|
+
probe: probe("h"),
|
|
890
|
+
action: "setHidden",
|
|
891
|
+
menuId: "layer.hide",
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
group: "Edit",
|
|
895
|
+
combo: "⌥⇧H",
|
|
896
|
+
does: "Hide everything else",
|
|
897
|
+
probe: probe("Ó", { alt: true, shift: true, code: "KeyH" }),
|
|
898
|
+
action: "setHidden",
|
|
899
|
+
menuId: "layer.hideOthers",
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
group: "Edit",
|
|
903
|
+
combo: "⌘⇧H",
|
|
904
|
+
does: "Show everything again",
|
|
905
|
+
probe: probe("h", { ctrl: true, shift: true }),
|
|
906
|
+
action: "setHidden",
|
|
907
|
+
menuId: "layer.reveal",
|
|
908
|
+
},
|
|
909
|
+
// ── ALIGN, ⌥ + A D W S H V ──────────────────────────────────────────────
|
|
910
|
+
//
|
|
911
|
+
// The words come from `ALIGN_LABELS`, the table the align grid and the
|
|
912
|
+
// Arrange menu already read. Each probe carries `code`: a Mac's Option
|
|
913
|
+
// composes the letter.
|
|
914
|
+
...(
|
|
915
|
+
[
|
|
916
|
+
["left", "A"],
|
|
917
|
+
["right", "D"],
|
|
918
|
+
["top", "W"],
|
|
919
|
+
["bottom", "S"],
|
|
920
|
+
["hcenter", "H"],
|
|
921
|
+
["vcenter", "V"],
|
|
922
|
+
] as const
|
|
923
|
+
).map(
|
|
924
|
+
([mode, letter]): ShortcutRow => ({
|
|
925
|
+
group: "Edit",
|
|
926
|
+
combo: `⌥${letter}`,
|
|
927
|
+
does: ALIGN_LABELS[mode],
|
|
928
|
+
probe: probe(letter.toLowerCase(), { alt: true, code: `Key${letter}` }),
|
|
929
|
+
action: "align",
|
|
930
|
+
menuId: `layout.align.${mode}`,
|
|
931
|
+
}),
|
|
932
|
+
),
|
|
933
|
+
// ── OPACITY ON THE BARE DIGITS ──────────────────────────────────────────
|
|
934
|
+
//
|
|
935
|
+
// One row for the tenths and one for 0, the Help sheet's two lines. Neither
|
|
936
|
+
// has a menu id: a row may name only one key.
|
|
937
|
+
{
|
|
938
|
+
group: "Edit",
|
|
939
|
+
combo: "1 to 9",
|
|
940
|
+
does: "Opacity 10% to 90%",
|
|
941
|
+
probe: probe("5", { code: "Digit5" }),
|
|
942
|
+
hasSelection: true,
|
|
943
|
+
action: "opacity",
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
group: "Edit",
|
|
947
|
+
combo: "0",
|
|
948
|
+
does: "Opacity 100%",
|
|
949
|
+
probe: probe("0", { code: "Digit0" }),
|
|
950
|
+
hasSelection: true,
|
|
951
|
+
action: "opacity",
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
group: "Edit",
|
|
955
|
+
combo: "Q",
|
|
956
|
+
does: "Trim the start to the playhead",
|
|
957
|
+
probe: probe("q"),
|
|
958
|
+
hasSelection: true,
|
|
959
|
+
action: "trimToPlayhead",
|
|
960
|
+
menuId: "layer.trim.in",
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
group: "Edit",
|
|
964
|
+
combo: "W",
|
|
965
|
+
does: "Trim the end to the playhead (keeps that frame)",
|
|
966
|
+
probe: probe("w"),
|
|
967
|
+
hasSelection: true,
|
|
968
|
+
action: "trimToPlayhead",
|
|
969
|
+
menuId: "layer.trim.out",
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
group: "Edit",
|
|
973
|
+
combo: "⇧H",
|
|
974
|
+
does: "Flip horizontal",
|
|
975
|
+
probe: probe("H", { shift: true, code: "KeyH" }),
|
|
976
|
+
hasSelection: true,
|
|
977
|
+
action: "flip",
|
|
978
|
+
menuId: "layer.flip.x",
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
group: "Edit",
|
|
982
|
+
combo: "⇧V",
|
|
983
|
+
does: "Flip vertical",
|
|
984
|
+
probe: probe("V", { shift: true, code: "KeyV" }),
|
|
985
|
+
hasSelection: true,
|
|
986
|
+
action: "flip",
|
|
987
|
+
menuId: "layer.flip.y",
|
|
988
|
+
},
|
|
989
|
+
// SPLIT INTO TWO from one "⌥G / ⌥R" line, for the layout digits' reason: the
|
|
990
|
+
// Layer ▸ Clear rows name one channel each.
|
|
991
|
+
{
|
|
992
|
+
group: "Edit",
|
|
993
|
+
combo: "⌥G",
|
|
994
|
+
does: "Clear the position",
|
|
995
|
+
probe: probe("g", { alt: true }),
|
|
996
|
+
action: "clearTransform",
|
|
997
|
+
menuId: "layer.clear.position",
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
group: "Edit",
|
|
1001
|
+
combo: "⌥R",
|
|
1002
|
+
does: "Clear the rotation",
|
|
1003
|
+
probe: probe("r", { alt: true }),
|
|
1004
|
+
action: "clearTransform",
|
|
1005
|
+
menuId: "layer.clear.rotation",
|
|
1006
|
+
},
|
|
1007
|
+
// The third channel. It was live in `keymap.ts` with no row here, so the only
|
|
1008
|
+
// place it was written down was the hand-kept table in docs/shot.md, and
|
|
1009
|
+
// deleting that table left a working key documented nowhere. No `menuId`:
|
|
1010
|
+
// the Layer ▸ Clear submenu carries position and rotation only.
|
|
1011
|
+
{
|
|
1012
|
+
group: "Edit",
|
|
1013
|
+
combo: "⌥⇧S",
|
|
1014
|
+
does: "Clear the size",
|
|
1015
|
+
probe: probe("S", { alt: true, shift: true, code: "KeyS" }),
|
|
1016
|
+
action: "clearTransform",
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
group: "Edit",
|
|
1020
|
+
combo: "Enter",
|
|
1021
|
+
does: "Edit inside the selection",
|
|
1022
|
+
probe: probe("Enter"),
|
|
1023
|
+
hasSelection: true,
|
|
1024
|
+
action: "enterIsolation",
|
|
1025
|
+
menuId: "layer.isolate",
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
group: "Edit",
|
|
1029
|
+
combo: "Esc",
|
|
1030
|
+
does: "Come back out one level",
|
|
1031
|
+
probe: probe("Escape"),
|
|
1032
|
+
isolation: true,
|
|
1033
|
+
action: "exitIsolation",
|
|
1034
|
+
menuId: "layer.isolate.exit",
|
|
1035
|
+
},
|
|
1036
|
+
// The rest of Escape's ladder, one level per press with nothing selected.
|
|
1037
|
+
{
|
|
1038
|
+
group: "Edit",
|
|
1039
|
+
combo: "Esc",
|
|
1040
|
+
does: "Back to the Shot you opened the component from",
|
|
1041
|
+
probe: probe("Escape"),
|
|
1042
|
+
context: { editingDef: true, componentsOrigin: true },
|
|
1043
|
+
action: "backToShot",
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
group: "Edit",
|
|
1047
|
+
combo: "Esc",
|
|
1048
|
+
does: "Leave the component",
|
|
1049
|
+
probe: probe("Escape"),
|
|
1050
|
+
context: { editingDef: true },
|
|
1051
|
+
action: "closeComponentContext",
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
group: "Edit",
|
|
1055
|
+
combo: "Esc",
|
|
1056
|
+
does: "Leave the code component",
|
|
1057
|
+
probe: probe("Escape"),
|
|
1058
|
+
context: { codeComponent: true },
|
|
1059
|
+
action: "closeCodeComponent",
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
group: "Edit",
|
|
1063
|
+
combo: "Esc",
|
|
1064
|
+
does: "Leave Components",
|
|
1065
|
+
probe: probe("Escape"),
|
|
1066
|
+
context: { componentsOpen: true },
|
|
1067
|
+
action: "leaveComponents",
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
group: "Edit",
|
|
1071
|
+
combo: "Esc",
|
|
1072
|
+
does: "Leave the Shot, back to all Shots",
|
|
1073
|
+
probe: probe("Escape"),
|
|
1074
|
+
context: { enteredScene: true },
|
|
1075
|
+
action: "leaveScene",
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
group: "Edit",
|
|
1079
|
+
combo: "Enter",
|
|
1080
|
+
does: "Finish the path you are drawing",
|
|
1081
|
+
probe: probe("Enter"),
|
|
1082
|
+
penActive: true,
|
|
1083
|
+
action: "penCommit",
|
|
1084
|
+
},
|
|
1085
|
+
{
|
|
1086
|
+
group: "Edit",
|
|
1087
|
+
combo: "Esc",
|
|
1088
|
+
does: "Throw away the path you are drawing",
|
|
1089
|
+
probe: probe("Escape"),
|
|
1090
|
+
penActive: true,
|
|
1091
|
+
action: "penAbandon",
|
|
1092
|
+
},
|
|
1093
|
+
|
|
1094
|
+
{
|
|
1095
|
+
group: "Animate & camera",
|
|
1096
|
+
combo: "⇧A",
|
|
1097
|
+
does: "Animate: edits write keyframes at the playhead",
|
|
1098
|
+
probe: probe("a", { shift: true }),
|
|
1099
|
+
action: "animateToggle",
|
|
1100
|
+
menuId: "tool.animate",
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
group: "Animate & camera",
|
|
1104
|
+
combo: "M",
|
|
1105
|
+
does: "Drop a marker at the playhead",
|
|
1106
|
+
probe: probe("m"),
|
|
1107
|
+
action: "addMarker",
|
|
1108
|
+
menuId: "time.marker.add",
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
group: "Animate & camera",
|
|
1112
|
+
combo: "⌥C",
|
|
1113
|
+
does: cameraVerb("toView").sentence,
|
|
1114
|
+
probe: probe("c", { alt: true }),
|
|
1115
|
+
action: "alignCameraToView",
|
|
1116
|
+
menuId: cameraVerb("toView").menuId,
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
group: "Animate & camera",
|
|
1120
|
+
combo: "⌘B",
|
|
1121
|
+
does: "Split the layers under the playhead",
|
|
1122
|
+
probe: probe("b", { ctrl: true }),
|
|
1123
|
+
action: "splitAtPlayhead",
|
|
1124
|
+
menuId: "edit.split",
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
group: "Animate & camera",
|
|
1128
|
+
combo: "⌘⇧B",
|
|
1129
|
+
does: "Split the shot at the playhead",
|
|
1130
|
+
probe: probe("b", { ctrl: true, shift: true }),
|
|
1131
|
+
action: "splitSceneAtPlayhead",
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
group: "Animate & camera",
|
|
1135
|
+
combo: "⌘E",
|
|
1136
|
+
does: "Export video",
|
|
1137
|
+
probe: probe("e", { ctrl: true }),
|
|
1138
|
+
action: "openExport",
|
|
1139
|
+
menuId: "file.export",
|
|
1140
|
+
},
|
|
1141
|
+
// In "View" would be truer, and it is here because the manual's four groups
|
|
1142
|
+
// are about the DOCUMENT and this one is about the chrome. It is last in the
|
|
1143
|
+
// table so it reads as the outlier it is.
|
|
1144
|
+
{
|
|
1145
|
+
group: "View",
|
|
1146
|
+
combo: "⌘K",
|
|
1147
|
+
does: "Open the menu, ready to search",
|
|
1148
|
+
probe: probe("k", { ctrl: true }),
|
|
1149
|
+
action: "appMenu",
|
|
1150
|
+
menuId: "app.menu",
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
group: "View",
|
|
1154
|
+
combo: "⌘,",
|
|
1155
|
+
does: "Open Preferences",
|
|
1156
|
+
probe: probe(",", { ctrl: true }),
|
|
1157
|
+
action: "openPrefs",
|
|
1158
|
+
menuId: "prefs.open",
|
|
1159
|
+
},
|
|
1160
|
+
// The key the left column's search field shows as its hint, so the hint and
|
|
1161
|
+
// the manual are one claim with one test behind it.
|
|
1162
|
+
{
|
|
1163
|
+
group: "View",
|
|
1164
|
+
combo: "/",
|
|
1165
|
+
does: "Search the left column",
|
|
1166
|
+
probe: probe("/", { code: "Slash" }),
|
|
1167
|
+
action: "focusSearch",
|
|
1168
|
+
},
|
|
1169
|
+
];
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* THE MATERIAL GRAPH'S BINDINGS, in a table of their own.
|
|
1173
|
+
*
|
|
1174
|
+
* ── WHY THEY ARE NOT ROWS OF `SHORTCUTS` ────────────────────────────────
|
|
1175
|
+
*
|
|
1176
|
+
* Every row of that table is walked through `resolveKey` with one fixed
|
|
1177
|
+
* context, and these twelve answer their action ONLY with `materialGraph:
|
|
1178
|
+
* true` — outside the canvas ⌘D duplicates a layer, Delete deletes the mesh
|
|
1179
|
+
* and `F` arms a tool. Folded into the main table, each row would need the
|
|
1180
|
+
* context bit set and the manual would print a key beside a sentence that is
|
|
1181
|
+
* true in one place and false everywhere else.
|
|
1182
|
+
*
|
|
1183
|
+
* So: a second table, the same `ShortcutRow` shape, the same test treatment
|
|
1184
|
+
* (see test/materialKeymap.test.ts), and `shortcutFor` indexes both — which is
|
|
1185
|
+
* what lets the node right-click menu print an accelerator without typing one.
|
|
1186
|
+
*/
|
|
1187
|
+
/** The board's own lane, walked by test/storyboardKeymap.test.ts the way the
|
|
1188
|
+
* material rows are: every one means something else outside the board. The
|
|
1189
|
+
* two `storyboardCards` rows say they need a card selected. */
|
|
1190
|
+
export const STORYBOARD_SHORTCUTS: readonly ShortcutRow[] = [
|
|
1191
|
+
{
|
|
1192
|
+
group: "Storyboard",
|
|
1193
|
+
combo: "Del",
|
|
1194
|
+
does: "Delete the selected cards",
|
|
1195
|
+
probe: probe("Delete"),
|
|
1196
|
+
storyboard: true,
|
|
1197
|
+
action: "storyboardDelete",
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
group: "Storyboard",
|
|
1201
|
+
combo: "C",
|
|
1202
|
+
does: "Comment on the selected card",
|
|
1203
|
+
probe: probe("c"),
|
|
1204
|
+
storyboard: true,
|
|
1205
|
+
action: "storyboardComment",
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
group: "Storyboard",
|
|
1209
|
+
combo: "N",
|
|
1210
|
+
does: "Next open comment",
|
|
1211
|
+
probe: probe("n"),
|
|
1212
|
+
storyboard: true,
|
|
1213
|
+
action: "storyboardNextThread",
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
group: "Storyboard",
|
|
1217
|
+
combo: "⇧N",
|
|
1218
|
+
does: "Previous open comment",
|
|
1219
|
+
probe: probe("n", { shift: true }),
|
|
1220
|
+
storyboard: true,
|
|
1221
|
+
action: "storyboardPrevThread",
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
group: "Storyboard",
|
|
1225
|
+
combo: "⌘A",
|
|
1226
|
+
does: "Select every card",
|
|
1227
|
+
probe: probe("a", { ctrl: true }),
|
|
1228
|
+
storyboard: true,
|
|
1229
|
+
action: "storyboardSelectAll",
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
group: "Storyboard",
|
|
1233
|
+
combo: "⌘D",
|
|
1234
|
+
does: "Duplicate the selected cards",
|
|
1235
|
+
probe: probe("d", { ctrl: true }),
|
|
1236
|
+
storyboard: true,
|
|
1237
|
+
action: "storyboardDuplicate",
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
group: "Storyboard",
|
|
1241
|
+
combo: "F2",
|
|
1242
|
+
does: "Rename the selected planned shot",
|
|
1243
|
+
probe: probe("F2"),
|
|
1244
|
+
storyboard: true,
|
|
1245
|
+
action: "storyboardRename",
|
|
1246
|
+
},
|
|
1247
|
+
// ── THE THREE CLIPBOARD ROWS ─────────────────────────────────────────────
|
|
1248
|
+
//
|
|
1249
|
+
// Documented here so the combos appear in the shortcut sheet, and resolving
|
|
1250
|
+
// to an arm that returns FALSE so behaviour is byte-identical to leaving
|
|
1251
|
+
// them unbound. The
|
|
1252
|
+
// work is done by native document handlers in StoryboardView.tsx, because binding
|
|
1253
|
+
// these for real would `preventDefault` the keydown, cancel the browser's
|
|
1254
|
+
// own paste event, and silently take away pasting an image FILE onto the
|
|
1255
|
+
// board. See `storyboardClipboard` in keymap.ts.
|
|
1256
|
+
{
|
|
1257
|
+
group: "Storyboard",
|
|
1258
|
+
combo: "⌘C",
|
|
1259
|
+
does: "Copy the selection",
|
|
1260
|
+
probe: probe("c", { ctrl: true }),
|
|
1261
|
+
storyboard: true,
|
|
1262
|
+
action: "storyboardClipboard",
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
group: "Storyboard",
|
|
1266
|
+
combo: "⌘X",
|
|
1267
|
+
does: "Cut the selection",
|
|
1268
|
+
probe: probe("x", { ctrl: true }),
|
|
1269
|
+
storyboard: true,
|
|
1270
|
+
action: "storyboardClipboard",
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
group: "Storyboard",
|
|
1274
|
+
combo: "⌘V",
|
|
1275
|
+
does: "Paste, including an image or video from the system clipboard",
|
|
1276
|
+
probe: probe("v", { ctrl: true }),
|
|
1277
|
+
storyboard: true,
|
|
1278
|
+
action: "storyboardClipboard",
|
|
1279
|
+
},
|
|
1280
|
+
{
|
|
1281
|
+
group: "Storyboard",
|
|
1282
|
+
combo: "Esc",
|
|
1283
|
+
does: "Clear the board selection, then the layer selection",
|
|
1284
|
+
probe: probe("Escape"),
|
|
1285
|
+
storyboard: true,
|
|
1286
|
+
action: "storyboardEscape",
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
group: "Storyboard",
|
|
1290
|
+
combo: "⇧S",
|
|
1291
|
+
does: "Close the Storyboard",
|
|
1292
|
+
probe: probe("s", { shift: true }),
|
|
1293
|
+
storyboard: true,
|
|
1294
|
+
action: "storyboardToggle",
|
|
1295
|
+
},
|
|
1296
|
+
// ── A SHOT BOARD'S SKETCH KEYS ───────────────────────────────────────────
|
|
1297
|
+
//
|
|
1298
|
+
// Answered by `resolveShotBoardKey` on the shot board's own element
|
|
1299
|
+
// (ShotBoard.tsx), so each row says `shotBoard`. Bare C arms the Component
|
|
1300
|
+
// tool there; on the whole board it still comments.
|
|
1301
|
+
sketchRow("Arrows", "Move the selected sketch items 1px", probe("ArrowRight"), "sketchMove"),
|
|
1302
|
+
sketchRow("⇧ Arrows", "Move the selected sketch items 10px", probe("ArrowRight", { shift: true }), "sketchMove"),
|
|
1303
|
+
sketchRow("[", "Send the selected sketch item back one step", probe("["), "sketchRestack"),
|
|
1304
|
+
sketchRow("]", "Bring the selected sketch item forward one step", probe("]"), "sketchRestack"),
|
|
1305
|
+
sketchRow("Enter", "Edit the selected text on a sketch", probe("Enter"), "sketchEdit"),
|
|
1306
|
+
sketchRow("V", "Sketch: Select", probe("v"), "sketchTool"),
|
|
1307
|
+
sketchRow("T", "Sketch: Text", probe("t"), "sketchTool"),
|
|
1308
|
+
sketchRow("R", "Sketch: Box", probe("r"), "sketchTool"),
|
|
1309
|
+
sketchRow("A", "Sketch: Arrow", probe("a"), "sketchTool"),
|
|
1310
|
+
sketchRow("I", "Sketch: add an image", probe("i"), "sketchTool"),
|
|
1311
|
+
sketchRow("C", "Sketch: add a component", probe("c"), "sketchTool"),
|
|
1312
|
+
];
|
|
1313
|
+
|
|
1314
|
+
/** One shot board row. */
|
|
1315
|
+
function sketchRow(combo: string, does: string, at: KeyEventLike, action: SketchKeyAction["kind"]): ShortcutRow {
|
|
1316
|
+
return { group: "Storyboard", combo, does, probe: at, storyboard: true, shotBoard: true, action };
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
export const MATERIAL_SHORTCUTS: readonly ShortcutRow[] = [
|
|
1320
|
+
{
|
|
1321
|
+
group: "Material nodes",
|
|
1322
|
+
combo: "Tab",
|
|
1323
|
+
does: "Open the node palette",
|
|
1324
|
+
probe: probe("Tab"),
|
|
1325
|
+
materialGraph: true,
|
|
1326
|
+
action: "graphPalette",
|
|
1327
|
+
menuId: "graph.add",
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
group: "Material nodes",
|
|
1331
|
+
combo: "⇧A",
|
|
1332
|
+
does: "Open the node palette (Blender's Add)",
|
|
1333
|
+
probe: probe("a", { shift: true }),
|
|
1334
|
+
materialGraph: true,
|
|
1335
|
+
action: "graphPalette",
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
group: "Material nodes",
|
|
1339
|
+
combo: "⌘D",
|
|
1340
|
+
does: "Duplicate the selected nodes",
|
|
1341
|
+
probe: probe("d", { ctrl: true }),
|
|
1342
|
+
materialGraph: true,
|
|
1343
|
+
action: "graphDuplicate",
|
|
1344
|
+
menuId: "graph.duplicate",
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
group: "Material nodes",
|
|
1348
|
+
combo: "⇧D",
|
|
1349
|
+
does: "Duplicate the selected nodes",
|
|
1350
|
+
probe: probe("d", { shift: true }),
|
|
1351
|
+
materialGraph: true,
|
|
1352
|
+
action: "graphDuplicate",
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
group: "Material nodes",
|
|
1356
|
+
combo: "⌘C",
|
|
1357
|
+
does: "Copy the selected nodes",
|
|
1358
|
+
probe: probe("c", { ctrl: true }),
|
|
1359
|
+
materialGraph: true,
|
|
1360
|
+
action: "graphCopy",
|
|
1361
|
+
menuId: "graph.copy",
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
group: "Material nodes",
|
|
1365
|
+
combo: "⌘X",
|
|
1366
|
+
does: "Cut the selected nodes",
|
|
1367
|
+
probe: probe("x", { ctrl: true }),
|
|
1368
|
+
materialGraph: true,
|
|
1369
|
+
action: "graphCut",
|
|
1370
|
+
menuId: "graph.cut",
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
group: "Material nodes",
|
|
1374
|
+
combo: "⌘V",
|
|
1375
|
+
does: "Paste nodes",
|
|
1376
|
+
probe: probe("v", { ctrl: true }),
|
|
1377
|
+
materialGraph: true,
|
|
1378
|
+
action: "graphPaste",
|
|
1379
|
+
menuId: "graph.paste",
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
group: "Material nodes",
|
|
1383
|
+
combo: "⌘A",
|
|
1384
|
+
does: "Select every node in this scope",
|
|
1385
|
+
probe: probe("a", { ctrl: true }),
|
|
1386
|
+
materialGraph: true,
|
|
1387
|
+
action: "graphSelectAll",
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
group: "Material nodes",
|
|
1391
|
+
combo: "⌘G",
|
|
1392
|
+
does: "Box the selected nodes into a group",
|
|
1393
|
+
probe: probe("g", { ctrl: true }),
|
|
1394
|
+
materialGraph: true,
|
|
1395
|
+
action: "graphGroup",
|
|
1396
|
+
menuId: "graph.group",
|
|
1397
|
+
},
|
|
1398
|
+
{
|
|
1399
|
+
group: "Material nodes",
|
|
1400
|
+
combo: "⌘⇧G",
|
|
1401
|
+
does: "Take the group box away",
|
|
1402
|
+
probe: probe("g", { ctrl: true, shift: true }),
|
|
1403
|
+
materialGraph: true,
|
|
1404
|
+
action: "graphUngroup",
|
|
1405
|
+
menuId: "graph.ungroup",
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
group: "Material nodes",
|
|
1409
|
+
combo: "Enter",
|
|
1410
|
+
does: "Open the selected subgraph",
|
|
1411
|
+
probe: probe("Enter"),
|
|
1412
|
+
materialGraph: true,
|
|
1413
|
+
action: "graphEnter",
|
|
1414
|
+
menuId: "graph.enter",
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
group: "Material nodes",
|
|
1418
|
+
combo: "P",
|
|
1419
|
+
does: "Show / hide the preview on the selected cards",
|
|
1420
|
+
probe: probe("p"),
|
|
1421
|
+
materialGraph: true,
|
|
1422
|
+
action: "graphPreviewToggle",
|
|
1423
|
+
menuId: "graph.preview",
|
|
1424
|
+
},
|
|
1425
|
+
{
|
|
1426
|
+
group: "Material nodes",
|
|
1427
|
+
combo: "Home",
|
|
1428
|
+
does: "Fit the whole graph",
|
|
1429
|
+
probe: probe("Home"),
|
|
1430
|
+
materialGraph: true,
|
|
1431
|
+
action: "graphFrame",
|
|
1432
|
+
},
|
|
1433
|
+
{
|
|
1434
|
+
group: "Material nodes",
|
|
1435
|
+
combo: "F",
|
|
1436
|
+
does: "Fit the whole graph",
|
|
1437
|
+
probe: probe("f"),
|
|
1438
|
+
materialGraph: true,
|
|
1439
|
+
action: "graphFrame",
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
group: "Material nodes",
|
|
1443
|
+
combo: "T",
|
|
1444
|
+
does: "Tidy the graph, or just the selection",
|
|
1445
|
+
probe: probe("t"),
|
|
1446
|
+
materialGraph: true,
|
|
1447
|
+
action: "graphTidy",
|
|
1448
|
+
menuId: "graph.tidy",
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
group: "Material nodes",
|
|
1452
|
+
combo: "Arrows",
|
|
1453
|
+
does: "Nudge the selected nodes 8 px (⇧ = 80)",
|
|
1454
|
+
probe: probe("ArrowRight"),
|
|
1455
|
+
materialGraph: true,
|
|
1456
|
+
action: "graphNudge",
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
group: "Material nodes",
|
|
1460
|
+
combo: "Del",
|
|
1461
|
+
does: "Delete the selected nodes and wires",
|
|
1462
|
+
probe: probe("Delete"),
|
|
1463
|
+
materialGraph: true,
|
|
1464
|
+
action: "graphDelete",
|
|
1465
|
+
menuId: "graph.delete",
|
|
1466
|
+
},
|
|
1467
|
+
{
|
|
1468
|
+
group: "Material nodes",
|
|
1469
|
+
combo: "Esc",
|
|
1470
|
+
does: "Close the palette, then the selection, then one subgraph level, then the editor",
|
|
1471
|
+
probe: probe("Escape"),
|
|
1472
|
+
materialGraph: true,
|
|
1473
|
+
action: "graphEscape",
|
|
1474
|
+
},
|
|
1475
|
+
];
|
|
1476
|
+
|
|
1477
|
+
/** By menu id, over BOTH tables: a node menu row prints its accelerator the
|
|
1478
|
+
* same way a layer menu row does. Built once — both tables are constants.
|
|
1479
|
+
* The storyboard rows name no menu row, so they are not indexed. */
|
|
1480
|
+
const BY_MENU_ID: ReadonlyMap<string, ShortcutRow> = new Map(
|
|
1481
|
+
[...SHORTCUTS, ...MATERIAL_SHORTCUTS].flatMap((row) =>
|
|
1482
|
+
row.menuId === undefined ? [] : [[row.menuId, row] as const],
|
|
1483
|
+
),
|
|
1484
|
+
);
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* Is this a Mac? Read ONCE — the platform does not change mid-session, and a
|
|
1488
|
+
* per-render probe would put a `navigator` touch inside every menu build.
|
|
1489
|
+
*
|
|
1490
|
+
* `navigator.platform` is deprecated but it is the only synchronous answer that
|
|
1491
|
+
* works in a plain iframe, and a wrong guess here costs a glyph rather than a
|
|
1492
|
+
* behaviour. Anything that is not a Mac gets the Windows/Linux spelling, which
|
|
1493
|
+
* is the right default for the one that is not a Mac.
|
|
1494
|
+
*/
|
|
1495
|
+
const IS_MAC =
|
|
1496
|
+
typeof navigator !== "undefined" &&
|
|
1497
|
+
/Mac|iPhone|iPad|iPod/.test(navigator.platform ?? "");
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* A combo in the spelling THIS machine's keyboard uses.
|
|
1501
|
+
*
|
|
1502
|
+
* THE PLATFORM BRANCH THE OLD COMMENT PROMISED. It used to say there was none,
|
|
1503
|
+
* deliberately, and that the whole chrome should take it at once or not at all —
|
|
1504
|
+
* because a menu printing `Ctrl+Z` beside a manual printing ⌘Z is a second
|
|
1505
|
+
* vocabulary for one key rather than a fix. That was right, and this is the
|
|
1506
|
+
* "at once": both readers, the menus through `shortcutFor` and the manual
|
|
1507
|
+
* through `HelpSection`, come through here, and the manual's footnote about ⌘
|
|
1508
|
+
* meaning Ctrl is gone because the table now says Ctrl itself.
|
|
1509
|
+
*
|
|
1510
|
+
* The glyphs stay the authored form in `SHORTCUTS`, so there is still exactly
|
|
1511
|
+
* one place a combo is written down. This is a rendering step, not a fork.
|
|
1512
|
+
*
|
|
1513
|
+
* A glyph NOT followed by a key is prose (`"⌘ and a click"`), so it becomes the
|
|
1514
|
+
* bare word rather than a dangling `Ctrl+`.
|
|
1515
|
+
*/
|
|
1516
|
+
export function formatCombo(combo: string, mac: boolean = IS_MAC): string {
|
|
1517
|
+
if (mac) return combo;
|
|
1518
|
+
return combo
|
|
1519
|
+
.replace(/⌘(?=\S)/g, "Ctrl+")
|
|
1520
|
+
.replace(/⌘/g, "Ctrl")
|
|
1521
|
+
.replace(/⇧(?=\S)/g, "Shift+")
|
|
1522
|
+
.replace(/⇧/g, "Shift")
|
|
1523
|
+
.replace(/⌥(?=\S)/g, "Alt+")
|
|
1524
|
+
.replace(/⌥/g, "Alt");
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/** The key that arms a tool, in this machine's spelling ("R", "⇧L" on a Mac,
|
|
1528
|
+
* "Shift+L" elsewhere), or null for a tool with no binding. */
|
|
1529
|
+
export function toolKey(t: ShotTool, mac: boolean = IS_MAC): string | null {
|
|
1530
|
+
const spec = specOf(t);
|
|
1531
|
+
return spec.key === null ? null : formatCombo(`${spec.shift === true ? "⇧" : ""}${spec.key.toUpperCase()}`, mac);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* A row's chord in this machine's spelling: its own `mac` chord on a Mac when
|
|
1536
|
+
* it has one (the keys differ there, not only the glyphs), `combo` otherwise.
|
|
1537
|
+
* Every reader goes through here, so the menus and the manual agree.
|
|
1538
|
+
*/
|
|
1539
|
+
export function comboOf(row: ShortcutRow, mac: boolean = IS_MAC): string {
|
|
1540
|
+
return mac && row.mac !== undefined ? row.mac.combo : formatCombo(row.combo, mac);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
/** The combo string for a menu row, in this machine's spelling. */
|
|
1544
|
+
export function shortcutFor(menuId: string): string | undefined {
|
|
1545
|
+
const row = BY_MENU_ID.get(menuId);
|
|
1546
|
+
return row === undefined ? undefined : comboOf(row);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* The combo for an ACTION, in this machine's spelling, for a refusal that has
|
|
1551
|
+
* to name a key.
|
|
1552
|
+
*
|
|
1553
|
+
* `shortcutFor` cannot answer it: a binding only has a menu id when a menu row
|
|
1554
|
+
* prints it, and the one caller here is a sentence rather than a row. Typing
|
|
1555
|
+
* the glyphs into that sentence instead would be a second place a combo is
|
|
1556
|
+
* written down, which this file's header exists to prevent, and a sentence is
|
|
1557
|
+
* the surface least likely to be found when the key moves.
|
|
1558
|
+
*
|
|
1559
|
+
* The FIRST row claiming the action, which is the only row for every action a
|
|
1560
|
+
* refusal names today. A second row on one action would be an alias, and an
|
|
1561
|
+
* alias's first spelling is the one to teach.
|
|
1562
|
+
*/
|
|
1563
|
+
export function comboForAction(action: KeyAction["kind"]): string | undefined {
|
|
1564
|
+
const row = SHORTCUTS.find((r) => r.action === action);
|
|
1565
|
+
return row === undefined ? undefined : comboOf(row);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* A `title=` suffix: " (Ctrl+Z)", or "" for a row with no binding.
|
|
1570
|
+
*
|
|
1571
|
+
* Here rather than in each toolbar, because every header control was writing
|
|
1572
|
+
* the same three lines and several had skipped them entirely in favour of a
|
|
1573
|
+
* literal "⌘Z" — which is the wrong spelling on every Windows machine the
|
|
1574
|
+
* editor runs on. An UNBOUND row returning "" is the point: the timeline
|
|
1575
|
+
* toggle has no key, and the caller should not have to know that.
|
|
1576
|
+
*/
|
|
1577
|
+
export function shortcutHint(menuId: string): string {
|
|
1578
|
+
const combo = shortcutFor(menuId);
|
|
1579
|
+
return combo === undefined ? "" : ` (${combo})`;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* The exact event a menu row's key produces, for the handful of rows whose verb
|
|
1584
|
+
* lives INSIDE `handleKey` and nowhere else.
|
|
1585
|
+
*
|
|
1586
|
+
* ── WHY A MENU ROW WOULD EVER SYNTHESISE A KEY ──────────────────────────
|
|
1587
|
+
*
|
|
1588
|
+
* `state/menu.ts`'s rule is that every entry calls an existing store verb, so a
|
|
1589
|
+
* menu can never be a second implementation of an edit. Six actions have no
|
|
1590
|
+
* verb to call: the scoped hide triple (⌥⇧H / ⌘⇧H reach for `relativesOf` and the
|
|
1591
|
+
* isolation scope), the two clear-transform channels, and the zoom reset. Each
|
|
1592
|
+
* is a thirty-line arm of `handleKey`'s switch, and copying one into a menu
|
|
1593
|
+
* builder is exactly the second implementation the rule exists to prevent.
|
|
1594
|
+
*
|
|
1595
|
+
* `store.handleKey(probe)` is not a second binding site — it is a call INTO the
|
|
1596
|
+
* one that exists, with the event that key produces. The arm derives its own
|
|
1597
|
+
* context from state, so the menu row refuses for the same reasons and with the
|
|
1598
|
+
* same sentence the key does. `resolveKey` reads `editing` off `event.target`,
|
|
1599
|
+
* which a probe does not carry, so `isEditable(null)` is false and the call can
|
|
1600
|
+
* never be swallowed by the field the author is typing in.
|
|
1601
|
+
*
|
|
1602
|
+
* Prefer a verb wherever one exists. This is for the rows where none does.
|
|
1603
|
+
*/
|
|
1604
|
+
export function probeFor(menuId: string): KeyEventLike | undefined {
|
|
1605
|
+
return BY_MENU_ID.get(menuId)?.probe;
|
|
1606
|
+
}
|