@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,326 @@
|
|
|
1
|
+
// THE ADD ROWS, their file picker and the media tool's file claim, shared by
|
|
2
|
+
// the Studio dock's Insert button (InsertButton.tsx) and the Insert drawer
|
|
3
|
+
// (InsertDrawer.tsx). The drawer mounts beside the button and opts out of the
|
|
4
|
+
// claim (`claimImagePick: false`), so there is still ONE claimant of
|
|
5
|
+
// `imagePick` on screen at a time.
|
|
6
|
+
//
|
|
7
|
+
// ── THE ROWS ARE GATED ON THE SCHEMA AT RUNTIME ─────────────────────────
|
|
8
|
+
//
|
|
9
|
+
// `hasLayerKind` reads `LAYER_KINDS`, so a kind removed from the document model
|
|
10
|
+
// removes its row here instead of leaving a button that mints a layer nothing
|
|
11
|
+
// can render. It has to be a runtime test and not `LAYER_KINDS.includes("mesh")`
|
|
12
|
+
// inline: against the literal tuple that call is a TYPE error the day the kind
|
|
13
|
+
// goes, which is a build nobody can ship: the menu could not degrade, only
|
|
14
|
+
// fail to compile.
|
|
15
|
+
//
|
|
16
|
+
// ── ONE FILE INPUT, TWO PICKERS ─────────────────────────────────────────
|
|
17
|
+
//
|
|
18
|
+
// The model row needs one, and so does the MEDIA TOOL (`image`, labelled
|
|
19
|
+
// "Media" because the file it takes may be an image or a video), which draws
|
|
20
|
+
// its box on the canvas and only then needs a file, so its geometry parks in
|
|
21
|
+
// the store (`imagePick`) and is claimed here. A dialog can only be opened by a
|
|
22
|
+
// click on a real `<input>`, and the store is not one.
|
|
23
|
+
//
|
|
24
|
+
// The rail's Audio button asks the same input for a sound (`pickAudio`), so
|
|
25
|
+
// the rail still renders one input for every file it takes.
|
|
26
|
+
import { useEffect, useRef, useState } from "react";
|
|
27
|
+
import type { ReactElement } from "react";
|
|
28
|
+
import { DEVICE_PRESET_IDS, MESH_PRIMITIVE_KINDS } from "@dailies-studio/shot/doc";
|
|
29
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
30
|
+
import { hasLayerKind } from "../../state/insert.js";
|
|
31
|
+
import { AUDIO_FILE_ACCEPT } from "../timeline/AudioLane.js";
|
|
32
|
+
// The menu's grouping (the AUTHORING taxonomy, not the schema's) and the
|
|
33
|
+
// particle builtins promoted onto its Generate shelf. Both were written out
|
|
34
|
+
// here and again in `icons.tsx`, which keys its family and builtin glyph maps
|
|
35
|
+
// on the same names; the classification rule and the promoted ids are stated
|
|
36
|
+
// once in the leaf module both files now read.
|
|
37
|
+
import { promotedBuiltinLabel } from "../../addFamilies.js";
|
|
38
|
+
import type { AddFamily } from "../../addFamilies.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What the hidden input is being asked for, or null when idle.
|
|
42
|
+
*
|
|
43
|
+
* An OBJECT and not a bare string, so asking for the same kind twice is a new
|
|
44
|
+
* value: a dialog the author cancelled fires no event at all, and with a string
|
|
45
|
+
* the second "image" would be `===` the stale first one, the effect would not
|
|
46
|
+
* re-run, and the picker would silently never open again.
|
|
47
|
+
*/
|
|
48
|
+
type Pick = { kind: "image" } | { kind: "model" } | { kind: "audio" };
|
|
49
|
+
|
|
50
|
+
// EXTENSIONS, NOT `image/*` OR `video/*`. The "sniff" on the far end is a
|
|
51
|
+
// filename extension map (`KIND_BY_EXT` in cli/src/assetStore.ts), and
|
|
52
|
+
// `storeAsset` writes the bytes BEFORE the kind is consulted. So a file the
|
|
53
|
+
// picker offered and the map does not know (.mkv, .heic) is stored, then
|
|
54
|
+
// refused, leaving an orphan blob in the content-addressed store that nothing
|
|
55
|
+
// ever collects. The lists are exactly what the map answers "image" or "video"
|
|
56
|
+
// for. SVG is one: the map says image, and the player's texture loader sizes
|
|
57
|
+
// an SVG explicitly and refuses one with no drawable size (textureCache.ts).
|
|
58
|
+
const IMAGE_EXTENSIONS = ".png,.jpg,.jpeg,.webp,.avif,.gif,.svg";
|
|
59
|
+
const VIDEO_EXTENSIONS = ".mp4,.webm,.mov,.m4v";
|
|
60
|
+
const IMAGE_TYPES = "image/png,image/jpeg,image/webp,image/avif,image/gif,image/svg+xml";
|
|
61
|
+
const VIDEO_TYPES = "video/mp4,video/webm,video/quicktime";
|
|
62
|
+
const MODEL_ACCEPT = ".glb,.gltf,model/gltf-binary";
|
|
63
|
+
/** One kind's picker filter. Shared with the inspector's Replace (MediaSection). */
|
|
64
|
+
export const IMAGE_ACCEPT = `${IMAGE_EXTENSIONS},${IMAGE_TYPES}`;
|
|
65
|
+
export const VIDEO_ACCEPT = `${VIDEO_EXTENSIONS},${VIDEO_TYPES}`;
|
|
66
|
+
|
|
67
|
+
function acceptFor(want: Pick | null): string {
|
|
68
|
+
if (want?.kind === "model") return MODEL_ACCEPT;
|
|
69
|
+
if (want?.kind === "audio") return AUDIO_FILE_ACCEPT;
|
|
70
|
+
return `${IMAGE_EXTENSIONS},${VIDEO_EXTENSIONS},${IMAGE_TYPES},${VIDEO_TYPES}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** One insert a menu or rail offers. `family` is the Add menu's shelf;
|
|
74
|
+
* `section` is set on the rows the component picker lists (D3). */
|
|
75
|
+
export interface AddRow {
|
|
76
|
+
key: string;
|
|
77
|
+
family: AddFamily;
|
|
78
|
+
section?: "In this project" | "Library" | "Effects" | "Particles";
|
|
79
|
+
label: string;
|
|
80
|
+
disabled?: boolean;
|
|
81
|
+
title?: string;
|
|
82
|
+
run: () => void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function useAddRows(o: {
|
|
86
|
+
/** Fetch the component registry now. The menu asks when it first opens,
|
|
87
|
+
* the rail when it mounts, so a chrome that never adds a component never
|
|
88
|
+
* makes the HTTP call. */
|
|
89
|
+
wantLibrary: boolean;
|
|
90
|
+
/** Called when a file pick starts an insert, so a host can close its menu. */
|
|
91
|
+
onPicked?: () => void;
|
|
92
|
+
/** Answer the media tool's parked box (`imagePick`). True by default. The
|
|
93
|
+
* Insert drawer passes false: it mounts beside the dock's own claimant, and
|
|
94
|
+
* two claimants would open the dialog twice. */
|
|
95
|
+
claimImagePick?: boolean;
|
|
96
|
+
}): {
|
|
97
|
+
rows: AddRow[];
|
|
98
|
+
busy: boolean;
|
|
99
|
+
/** Open the picker for a sound, imported at the playhead into the entered
|
|
100
|
+
* Shot (or onto the film's own lane when none is). */
|
|
101
|
+
pickAudio: () => void;
|
|
102
|
+
/** The ONE hidden input. The host renders it once. */
|
|
103
|
+
fileInput: ReactElement;
|
|
104
|
+
} {
|
|
105
|
+
const store = useStore();
|
|
106
|
+
const imagePick = useShotEditor((s) => s.imagePick);
|
|
107
|
+
const hasSelection = useShotEditor((s) => s.selection.length > 0);
|
|
108
|
+
const definitions = useShotEditor((s) => s.doc?.components);
|
|
109
|
+
const library = useShotEditor((s) => s.library);
|
|
110
|
+
const libraryLoaded = useShotEditor((s) => s.libraryLoaded);
|
|
111
|
+
const [busy, setBusy] = useState(false);
|
|
112
|
+
const [want, setWant] = useState<Pick | null>(null);
|
|
113
|
+
const fileRef = useRef<HTMLInputElement | null>(null);
|
|
114
|
+
|
|
115
|
+
// A drawn image box is waiting on a file: open the dialog for it. An EFFECT
|
|
116
|
+
// is the seam because the draw happened in the STAGE's document, so there is
|
|
117
|
+
// no click of ours to hang it off. User activation propagates from a
|
|
118
|
+
// same-origin iframe, so the dialog is still allowed to open; if a browser
|
|
119
|
+
// ever stops propagating it, the ceiling is that the image tool needs a
|
|
120
|
+
// second click here rather than anything breaking.
|
|
121
|
+
const claims = o.claimImagePick !== false;
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (claims && imagePick !== null) setWant({ kind: "image" });
|
|
124
|
+
}, [claims, imagePick]);
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (want !== null) fileRef.current?.click();
|
|
128
|
+
}, [want]);
|
|
129
|
+
|
|
130
|
+
// A failure leaves the instance rows absent (the error surfaces in the
|
|
131
|
+
// inspector, which is the surface that can say what it means for a layer).
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (o.wantLibrary && !libraryLoaded) void store.refreshLibrary();
|
|
134
|
+
}, [o.wantLibrary, libraryLoaded, store]);
|
|
135
|
+
|
|
136
|
+
const report = (e: unknown): void =>
|
|
137
|
+
store.reportError(e instanceof Error ? e.message : String(e));
|
|
138
|
+
|
|
139
|
+
// SEVERAL MEDIA FILES, in the order picked. The first claims the parked box;
|
|
140
|
+
// each next one parks the same box again and goes through the same claim,
|
|
141
|
+
// so they land where the box was, one at a time. A failed file is reported
|
|
142
|
+
// and the rest still land.
|
|
143
|
+
const placeMedia = async (files: readonly File[]): Promise<void> => {
|
|
144
|
+
const box = store.getState().imagePick;
|
|
145
|
+
await store.completeImageDraw(files[0] ?? null).catch(report);
|
|
146
|
+
for (const file of files.slice(1)) {
|
|
147
|
+
if (box === null) return;
|
|
148
|
+
store.stageHandlers.onDraw("image", box);
|
|
149
|
+
await store.completeImageDraw(file).catch(report);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const chose = (files: readonly File[]): void => {
|
|
154
|
+
const asked = want;
|
|
155
|
+
setWant(null);
|
|
156
|
+
if (asked === null) return;
|
|
157
|
+
o.onPicked?.();
|
|
158
|
+
setBusy(true);
|
|
159
|
+
const s = store.getState();
|
|
160
|
+
const file = files[0] ?? null;
|
|
161
|
+
const done =
|
|
162
|
+
asked.kind === "image"
|
|
163
|
+
? placeMedia(files)
|
|
164
|
+
: file === null
|
|
165
|
+
? Promise.resolve()
|
|
166
|
+
: asked.kind === "audio"
|
|
167
|
+
? store.importAudio(file, s.frame, s.enteredScene ?? undefined)
|
|
168
|
+
: store.addMeshLayer(file);
|
|
169
|
+
void done.catch(report).finally(() => setBusy(false));
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// A CANCELLED dialog is a pick of nothing, so `imagePick` is released rather
|
|
173
|
+
// than left parked for the next mount to reopen the dialog over. Native,
|
|
174
|
+
// because React does not wire `onCancel` on an `<input>`.
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
const el = fileRef.current;
|
|
177
|
+
if (el === null || want === null) return;
|
|
178
|
+
const onCancel = (): void => chose([]);
|
|
179
|
+
el.addEventListener("cancel", onCancel);
|
|
180
|
+
return () => el.removeEventListener("cancel", onCancel);
|
|
181
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
182
|
+
}, [want]);
|
|
183
|
+
|
|
184
|
+
// It goes through `onDraw("image", …)` and not through a new store action,
|
|
185
|
+
// because that is where the box-waits-for-a-file handshake already lives:
|
|
186
|
+
// the geometry parks in `imagePick`, the effect above claims it, and the
|
|
187
|
+
// hidden input finishes the job. A second path would be a second place that
|
|
188
|
+
// knows how a media insert is staged.
|
|
189
|
+
//
|
|
190
|
+
// TWO IDENTICAL POINTS: a click, not a drag. `boxOf` re-shapes that to the
|
|
191
|
+
// source's own aspect once the file has been measured (insert.ts), which is
|
|
192
|
+
// a better box than any rectangle this menu could guess.
|
|
193
|
+
const pickMedia = (): void => {
|
|
194
|
+
// A menu row expresses no position: it lands centred on what the frame
|
|
195
|
+
// shows, like every other add (F54).
|
|
196
|
+
const centre = store.insertOrigin();
|
|
197
|
+
store.stageHandlers.onDraw("image", [centre, centre]);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Rows carry their FAMILY, and the hosts group them, in the taxonomy's own
|
|
201
|
+
// order.
|
|
202
|
+
const rows: AddRow[] = [];
|
|
203
|
+
if (hasLayerKind("image") || hasLayerKind("video")) {
|
|
204
|
+
// THE ONE CAPABILITY THIS PRODUCT IS BUILT AROUND, and until this row it was
|
|
205
|
+
// reachable only by arming a dock tool labelled "Media" and rubber-banding a
|
|
206
|
+
// box. The Add menu listed html, every mesh primitive, a 3D model, every
|
|
207
|
+
// device preset, both particle builtins, a cloner, a group and the whole
|
|
208
|
+
// component registry, and not the one thing an author opens the app to do.
|
|
209
|
+
rows.push({ key: "media", family: "Draw", label: "Image or video…", run: () => pickMedia() });
|
|
210
|
+
}
|
|
211
|
+
if (hasLayerKind("html")) {
|
|
212
|
+
rows.push({ key: "html", family: "Draw", label: "Html snippet", run: () => store.addHtmlLayer() });
|
|
213
|
+
}
|
|
214
|
+
if (hasLayerKind("mesh")) {
|
|
215
|
+
// The parametric shapes (schema v11), from the schema's own list, by the
|
|
216
|
+
// DEVICE_PRESET_IDS rule: a hardcoded copy is a second place that knows
|
|
217
|
+
// what a shape is.
|
|
218
|
+
for (const kind of MESH_PRIMITIVE_KINDS) {
|
|
219
|
+
rows.push({
|
|
220
|
+
key: `shape:${kind}`,
|
|
221
|
+
family: "Solid",
|
|
222
|
+
label: kind.charAt(0).toUpperCase() + kind.slice(1),
|
|
223
|
+
run: () => store.addShapeLayer(kind),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
rows.push({ key: "mesh", family: "Solid", label: "3D model…", run: () => setWant({ kind: "model" }) });
|
|
227
|
+
}
|
|
228
|
+
if (hasLayerKind("device")) {
|
|
229
|
+
// FROM THE REGISTRY, never a list written here: a hardcoded one is a second
|
|
230
|
+
// place that knows what a device is, and `validateShotDoc` refuses a preset
|
|
231
|
+
// the registry does not have, so the two disagreeing means a menu row that
|
|
232
|
+
// can only produce a document the author cannot save.
|
|
233
|
+
for (const preset of DEVICE_PRESET_IDS) {
|
|
234
|
+
rows.push({
|
|
235
|
+
key: `device:${preset}`,
|
|
236
|
+
family: "Solid",
|
|
237
|
+
label: preset.charAt(0).toUpperCase() + preset.slice(1),
|
|
238
|
+
run: () => store.addDeviceLayer(preset),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (hasLayerKind("instance")) {
|
|
243
|
+
// The CURATED Generate shelf: the particle builtins surface as first-class
|
|
244
|
+
// basics with their own names, not as two entries lost in the component
|
|
245
|
+
// list. A chrome curation (which two ids read as "particles" is a product
|
|
246
|
+
// decision), while the rows themselves still come from the live registry:
|
|
247
|
+
// a build without the particles package simply has no rows to promote.
|
|
248
|
+
for (const row of library) {
|
|
249
|
+
if (row.host !== "three") continue;
|
|
250
|
+
const label = promotedBuiltinLabel(row.id);
|
|
251
|
+
if (label === null) continue;
|
|
252
|
+
rows.push({
|
|
253
|
+
key: `instance:${row.id}`,
|
|
254
|
+
family: "Generate",
|
|
255
|
+
section: "Particles",
|
|
256
|
+
label,
|
|
257
|
+
title: row.id,
|
|
258
|
+
run: () => void store.addInstanceLayer(row.id).catch(report),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (hasLayerKind("cloner")) {
|
|
263
|
+
rows.push({
|
|
264
|
+
key: "cloner",
|
|
265
|
+
family: "Generate",
|
|
266
|
+
label: "Cloner",
|
|
267
|
+
// No empty cloner, ever: `validateShotDoc` refuses one with no children,
|
|
268
|
+
// so the row that inserted it would be a row that breaks saving.
|
|
269
|
+
disabled: !hasSelection,
|
|
270
|
+
title: hasSelection ? "Wrap the selection in a cloner" : "Select a layer to clone first",
|
|
271
|
+
run: () => store.addClonerLayer(),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
// ONE COMPONENT PICKER (F56/F59): this Project's own definitions first, then
|
|
275
|
+
// the registry (its TSL effects and particles in sections of their own).
|
|
276
|
+
// Grouping the selection and making a component are verbs on a selection,
|
|
277
|
+
// not inserts, so they live on Ctrl/Cmd+G, the layer menu and the picker's
|
|
278
|
+
// "Make component" footer, never in this list.
|
|
279
|
+
if (hasLayerKind("instance")) {
|
|
280
|
+
for (const def of Object.values(definitions ?? {})) {
|
|
281
|
+
rows.push({
|
|
282
|
+
key: `def:${def.id}`,
|
|
283
|
+
family: "Structure",
|
|
284
|
+
section: "In this project",
|
|
285
|
+
label: def.name,
|
|
286
|
+
title: "A component of this Project",
|
|
287
|
+
run: () => store.placeComponent(def.id),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
// FROM THE LIVE REGISTRY, filtered to the only host that can mount inside
|
|
291
|
+
// the shot's 3D canvas. Same reason the device rows come from
|
|
292
|
+
// DEVICE_PRESET_IDS: a hardcoded list would be a second place that knows
|
|
293
|
+
// what a component is, and the save gate refuses anything else, so a row
|
|
294
|
+
// offering a "dom" component could only ever produce a document the author
|
|
295
|
+
// cannot save.
|
|
296
|
+
for (const row of library.filter((r) => r.host === "three" && promotedBuiltinLabel(r.id) === null)) {
|
|
297
|
+
rows.push({
|
|
298
|
+
key: `instance:${row.id}`,
|
|
299
|
+
family: "Structure",
|
|
300
|
+
section: row.effect === undefined ? "Library" : "Effects",
|
|
301
|
+
label: row.name,
|
|
302
|
+
title: row.id,
|
|
303
|
+
run: () => void store.addInstanceLayer(row.id).catch(report),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const fileInput = (
|
|
309
|
+
<input
|
|
310
|
+
ref={fileRef}
|
|
311
|
+
className="audio-file-input"
|
|
312
|
+
type="file"
|
|
313
|
+
accept={acceptFor(want)}
|
|
314
|
+
multiple={want?.kind === "image"}
|
|
315
|
+
hidden
|
|
316
|
+
data-testid="add-file-input"
|
|
317
|
+
onChange={(e) => {
|
|
318
|
+
chose(Array.from(e.currentTarget.files ?? []));
|
|
319
|
+
// Cleared so re-picking the SAME file fires change again.
|
|
320
|
+
e.currentTarget.value = "";
|
|
321
|
+
}}
|
|
322
|
+
/>
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
return { rows, busy, pickAudio: () => setWant({ kind: "audio" }), fileInput };
|
|
326
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// ONE DISMISS RULE for the tool menus (the dock's Shapes and Add, the board
|
|
2
|
+
// rail's flyouts): while open, a press outside the anchor, Escape anywhere, or
|
|
3
|
+
// the window losing focus closes it. The listeners mirror ui `Popover`'s pair.
|
|
4
|
+
//
|
|
5
|
+
// THE STAGE IS ANOTHER DOCUMENT. A press in its iframe never reaches this
|
|
6
|
+
// window, so the pair is also set on each same-origin frame's window, in the
|
|
7
|
+
// capture phase there, where the press and the Escape stop: an outside press
|
|
8
|
+
// on the stage only dismisses, with no pick, no deselect and no drag under it.
|
|
9
|
+
// The window `blur` is skipped when focus went INTO one of those frames (a
|
|
10
|
+
// cross-origin one still dismisses through it): the stage takes
|
|
11
|
+
// focus as the pointer crosses it (StagePanel `focusStage`), which closed a
|
|
12
|
+
// menu the author was only moving towards.
|
|
13
|
+
//
|
|
14
|
+
// The stage's own capture-phase Escape lanes (pen, spline, G/R/S) were
|
|
15
|
+
// registered on that window first, so they run before this one: while a menu
|
|
16
|
+
// is open the frame's root carries `TOOL_MENU_ATTR` and they yield Escape
|
|
17
|
+
// (`toolMenuOpen`).
|
|
18
|
+
//
|
|
19
|
+
// The frame half tells `close` which frame window the press or Escape came
|
|
20
|
+
// from, so a menu can hand focus to that frame (the font picker does).
|
|
21
|
+
//
|
|
22
|
+
// A MENU ON A ui `Popover` (the font picker) takes only the frame half
|
|
23
|
+
// (`window: false`): the Popover already closes on a press outside it and on
|
|
24
|
+
// Escape in this window. It also keeps `blur: false` when it opens the OS file
|
|
25
|
+
// dialog, which blurs the window.
|
|
26
|
+
import { useEffect, useRef } from "react";
|
|
27
|
+
import type { RefObject } from "react";
|
|
28
|
+
|
|
29
|
+
const TOOL_MENU_ATTR = "data-tool-menu-open";
|
|
30
|
+
|
|
31
|
+
/** A chrome tool menu is open over this document: Escape is the menu's. */
|
|
32
|
+
export function toolMenuOpen(): boolean {
|
|
33
|
+
return document.documentElement.hasAttribute(TOOL_MENU_ATTR);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DismissOptions {
|
|
37
|
+
/** Close on a press outside `anchor` and on Escape in this window. Default
|
|
38
|
+
* true; false for a menu whose `Popover` does both already. */
|
|
39
|
+
window?: boolean;
|
|
40
|
+
/** Close when this window loses focus to anything but a stage frame.
|
|
41
|
+
* Default true. */
|
|
42
|
+
blur?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useDismiss(
|
|
46
|
+
open: boolean,
|
|
47
|
+
anchor: RefObject<HTMLElement | null>,
|
|
48
|
+
close: (frame?: Window) => void,
|
|
49
|
+
options: DismissOptions = {},
|
|
50
|
+
): void {
|
|
51
|
+
const latest = useRef(close);
|
|
52
|
+
latest.current = close;
|
|
53
|
+
const onWindow = options.window ?? true;
|
|
54
|
+
const onBlurToo = options.blur ?? true;
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!open) return;
|
|
57
|
+
const onDown = (e: PointerEvent): void => {
|
|
58
|
+
if (!(anchor.current?.contains(e.target as Node) ?? false)) latest.current();
|
|
59
|
+
};
|
|
60
|
+
const onKey = (e: KeyboardEvent): void => {
|
|
61
|
+
if (e.key !== "Escape") return;
|
|
62
|
+
// Escape closes the menu and nothing else (not a deselect under it).
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
latest.current();
|
|
65
|
+
};
|
|
66
|
+
const frames: { w: Window; down: (e: PointerEvent) => void; key: (e: KeyboardEvent) => void }[] = [];
|
|
67
|
+
const listened = new Set<Element>();
|
|
68
|
+
for (const frame of document.querySelectorAll("iframe")) {
|
|
69
|
+
try {
|
|
70
|
+
const w = frame.contentWindow;
|
|
71
|
+
if (w === null) continue;
|
|
72
|
+
// The loop's `w`, not `e.view`: a synthetic event has no view.
|
|
73
|
+
const down = (e: PointerEvent): void => {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
latest.current(w);
|
|
77
|
+
};
|
|
78
|
+
const key = (e: KeyboardEvent): void => {
|
|
79
|
+
if (e.key !== "Escape") return;
|
|
80
|
+
e.stopPropagation();
|
|
81
|
+
latest.current(w);
|
|
82
|
+
};
|
|
83
|
+
w.addEventListener("pointerdown", down, true);
|
|
84
|
+
w.addEventListener("keydown", key, true);
|
|
85
|
+
w.document.documentElement.setAttribute(TOOL_MENU_ATTR, "");
|
|
86
|
+
frames.push({ w, down, key });
|
|
87
|
+
listened.add(frame);
|
|
88
|
+
} catch {
|
|
89
|
+
// A cross-origin frame: a press there reaches this window as a blur.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Read after the blur, once focus has landed: browsers differ on whether
|
|
93
|
+
// `activeElement` is the frame yet while the event runs.
|
|
94
|
+
let blurTimer: ReturnType<typeof setTimeout> | undefined;
|
|
95
|
+
const onBlur = (): void => {
|
|
96
|
+
blurTimer = setTimeout(() => {
|
|
97
|
+
const into = document.activeElement;
|
|
98
|
+
if (into === null || !listened.has(into)) latest.current();
|
|
99
|
+
}, 0);
|
|
100
|
+
};
|
|
101
|
+
if (onWindow) {
|
|
102
|
+
window.addEventListener("pointerdown", onDown, true);
|
|
103
|
+
window.addEventListener("keydown", onKey, true);
|
|
104
|
+
}
|
|
105
|
+
if (onBlurToo) window.addEventListener("blur", onBlur);
|
|
106
|
+
return () => {
|
|
107
|
+
clearTimeout(blurTimer);
|
|
108
|
+
window.removeEventListener("pointerdown", onDown, true);
|
|
109
|
+
window.removeEventListener("keydown", onKey, true);
|
|
110
|
+
window.removeEventListener("blur", onBlur);
|
|
111
|
+
for (const { w, down, key } of frames) {
|
|
112
|
+
try {
|
|
113
|
+
w.removeEventListener("pointerdown", down, true);
|
|
114
|
+
w.removeEventListener("keydown", key, true);
|
|
115
|
+
w.document.documentElement.removeAttribute(TOOL_MENU_ATTR);
|
|
116
|
+
} catch {
|
|
117
|
+
// The frame navigated away and is cross-origin now.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}, [open, anchor, onWindow, onBlurToo]);
|
|
122
|
+
}
|