@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
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
// The ADD menu: the layer kinds a DRAG CANNOT EXPRESS.
|
|
2
|
-
//
|
|
3
|
-
// A rectangle is a box, so the rect tool is a drag. A model is a FILE, a device
|
|
4
|
-
// is a CHOICE FROM A REGISTRY, and a cloner is an operation on what is already
|
|
5
|
-
// selected — none of those is a rubber band, and giving each of them a tool
|
|
6
|
-
// would put three buttons in the dock that all do the same thing when dragged
|
|
7
|
-
// (nothing) and something else entirely when clicked.
|
|
8
|
-
//
|
|
9
|
-
// ── THE ROWS ARE GATED ON THE SCHEMA AT RUNTIME ─────────────────────────
|
|
10
|
-
//
|
|
11
|
-
// `hasLayerKind` reads `LAYER_KINDS`, so a kind removed from the document model
|
|
12
|
-
// removes its row here instead of leaving a button that mints a layer nothing
|
|
13
|
-
// can render. It has to be a runtime test and not `LAYER_KINDS.includes("mesh")`
|
|
14
|
-
// inline: against the literal tuple that call is a TYPE error the day the kind
|
|
15
|
-
// goes, which is a build nobody can ship — the menu could not degrade, only
|
|
16
|
-
// fail to compile.
|
|
17
|
-
//
|
|
18
|
-
// ── ONE FILE INPUT, TWO PICKERS ─────────────────────────────────────────
|
|
19
|
-
//
|
|
20
|
-
// The model row needs one, and so does the FRAME TOOL (`image`, labelled
|
|
21
|
-
// "Frame" because the file it takes may be an image or a video) — which draws
|
|
22
|
-
// its box on the canvas and only then needs a file, so its geometry parks in
|
|
23
|
-
// the store (`imagePick`) and is claimed here. A dialog can only be opened by a
|
|
24
|
-
// click on a real `<input>`, and the store is not one.
|
|
25
|
-
import { useEffect, useRef, useState } from "react";
|
|
26
|
-
import { DEVICE_PRESET_IDS } from "@dailies-studio/shot/doc";
|
|
27
|
-
import { useShotEditor, useStore } from "../../state/context.js";
|
|
28
|
-
import { hasLayerKind } from "../../state/insert.js";
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* What the hidden input is being asked for, or null when idle.
|
|
32
|
-
*
|
|
33
|
-
* An OBJECT and not a bare string, so asking for the same kind twice is a new
|
|
34
|
-
* value: a dialog the author cancelled fires no event at all, and with a string
|
|
35
|
-
* the second "image" would be `===` the stale first one, the effect would not
|
|
36
|
-
* re-run, and the picker would silently never open again.
|
|
37
|
-
*/
|
|
38
|
-
type Pick = { kind: "image" | "model" };
|
|
39
|
-
|
|
40
|
-
export function AddMenu() {
|
|
41
|
-
const store = useStore();
|
|
42
|
-
const imagePick = useShotEditor((s) => s.imagePick);
|
|
43
|
-
const hasSelection = useShotEditor((s) => s.selection.length > 0);
|
|
44
|
-
const canGroup = useShotEditor((s) => s.selection.length >= 2);
|
|
45
|
-
const library = useShotEditor((s) => s.library);
|
|
46
|
-
const libraryLoaded = useShotEditor((s) => s.libraryLoaded);
|
|
47
|
-
const [open, setOpen] = useState(false);
|
|
48
|
-
const [busy, setBusy] = useState(false);
|
|
49
|
-
const [want, setWant] = useState<Pick | null>(null);
|
|
50
|
-
const fileRef = useRef<HTMLInputElement | null>(null);
|
|
51
|
-
|
|
52
|
-
// A drawn image box is waiting on a file: open the dialog for it. An EFFECT
|
|
53
|
-
// is the seam because the draw happened in the STAGE's document, so there is
|
|
54
|
-
// no click of ours to hang it off. User activation propagates from a
|
|
55
|
-
// same-origin iframe, so the dialog is still allowed to open; if a browser
|
|
56
|
-
// ever stops propagating it, the ceiling is that the image tool needs a
|
|
57
|
-
// second click here rather than anything breaking.
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (imagePick === null) return;
|
|
60
|
-
setWant({ kind: "image" });
|
|
61
|
-
}, [imagePick]);
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (want !== null) fileRef.current?.click();
|
|
65
|
-
}, [want]);
|
|
66
|
-
|
|
67
|
-
// The registry, once, when the menu first opens rather than at mount: it is
|
|
68
|
-
// an HTTP call, and a chrome that never adds a component should never make
|
|
69
|
-
// it. A failure leaves the instance rows absent (the error surfaces in the
|
|
70
|
-
// inspector, which is the surface that can say what it means for a layer).
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
if (open && !libraryLoaded) void store.refreshLibrary();
|
|
73
|
-
}, [open, libraryLoaded, store]);
|
|
74
|
-
|
|
75
|
-
const report = (e: unknown): void =>
|
|
76
|
-
store.reportError(e instanceof Error ? e.message : String(e));
|
|
77
|
-
|
|
78
|
-
const chose = (file: File | null): void => {
|
|
79
|
-
const asked = want;
|
|
80
|
-
setWant(null);
|
|
81
|
-
if (asked === null) return;
|
|
82
|
-
setBusy(true);
|
|
83
|
-
const done =
|
|
84
|
-
asked.kind === "image"
|
|
85
|
-
? store.completeImageDraw(file)
|
|
86
|
-
: file === null
|
|
87
|
-
? Promise.resolve()
|
|
88
|
-
: store.addMeshLayer(file);
|
|
89
|
-
void done.catch(report).finally(() => setBusy(false));
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// Every row is gated, so a menu with nothing left in it is not an empty
|
|
93
|
-
// popover — it is no button at all.
|
|
94
|
-
const rows: { key: string; label: string; disabled?: boolean; title?: string; run: () => void }[] = [];
|
|
95
|
-
if (hasLayerKind("mesh")) {
|
|
96
|
-
rows.push({ key: "mesh", label: "3D model…", run: () => setWant({ kind: "model" }) });
|
|
97
|
-
}
|
|
98
|
-
if (hasLayerKind("device")) {
|
|
99
|
-
// FROM THE REGISTRY, never a list written here: a hardcoded one is a second
|
|
100
|
-
// place that knows what a device is, and `validateShotDoc` refuses a preset
|
|
101
|
-
// the registry does not have — so the two disagreeing means a menu row that
|
|
102
|
-
// can only produce a document the author cannot save.
|
|
103
|
-
for (const preset of DEVICE_PRESET_IDS) {
|
|
104
|
-
rows.push({
|
|
105
|
-
key: `device:${preset}`,
|
|
106
|
-
label: preset.charAt(0).toUpperCase() + preset.slice(1),
|
|
107
|
-
run: () => store.addDeviceLayer(preset),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (hasLayerKind("html")) {
|
|
112
|
-
rows.push({ key: "html", label: "Html snippet", run: () => store.addHtmlLayer() });
|
|
113
|
-
}
|
|
114
|
-
if (hasLayerKind("instance")) {
|
|
115
|
-
// FROM THE LIVE REGISTRY, filtered to the only host that can mount inside
|
|
116
|
-
// the shot's 3D canvas. Same reason the device rows come from
|
|
117
|
-
// DEVICE_PRESET_IDS: a hardcoded list would be a second place that knows
|
|
118
|
-
// what a component is, and the save gate refuses anything else — so a row
|
|
119
|
-
// offering a "dom" component could only ever produce a document the author
|
|
120
|
-
// cannot save.
|
|
121
|
-
for (const row of library.filter((r) => r.host === "three")) {
|
|
122
|
-
rows.push({
|
|
123
|
-
key: `instance:${row.id}`,
|
|
124
|
-
label: row.name,
|
|
125
|
-
title: row.id,
|
|
126
|
-
run: () => void store.addInstanceLayer(row.id).catch(report),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (hasLayerKind("group")) {
|
|
131
|
-
rows.push({
|
|
132
|
-
key: "group",
|
|
133
|
-
label: "Group",
|
|
134
|
-
// The pointer twin of Ctrl/Cmd+G. Two members minimum, same floor
|
|
135
|
-
// `groupSelection` itself enforces: a one-layer group is just a layer
|
|
136
|
-
// with an extra transform nobody asked for.
|
|
137
|
-
disabled: !canGroup,
|
|
138
|
-
title: canGroup ? "Group the selection (Ctrl/Cmd+G)" : "Select two or more layers to group",
|
|
139
|
-
run: () => store.groupSelection(),
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
if (hasLayerKind("cloner")) {
|
|
143
|
-
rows.push({
|
|
144
|
-
key: "cloner",
|
|
145
|
-
label: "Cloner",
|
|
146
|
-
// No empty cloner, ever: `validateShotDoc` refuses one with no children,
|
|
147
|
-
// so the row that inserted it would be a row that breaks saving.
|
|
148
|
-
disabled: !hasSelection,
|
|
149
|
-
title: hasSelection ? "Wrap the selection in a cloner" : "Select a layer to clone first",
|
|
150
|
-
run: () => store.addClonerLayer(),
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
if (hasLayerKind("component")) {
|
|
154
|
-
rows.push({
|
|
155
|
-
key: "component",
|
|
156
|
-
label: "Component",
|
|
157
|
-
// A definition is MADE from something, exactly like a cloner: there is no
|
|
158
|
-
// empty one, because a definition with no roots is refused at the gate.
|
|
159
|
-
disabled: !hasSelection,
|
|
160
|
-
title: hasSelection
|
|
161
|
-
? "Make the selection reusable, and leave one placement here"
|
|
162
|
-
: "Select the layers to turn into a component first",
|
|
163
|
-
run: () => store.createComponentFromSelection(),
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
if (rows.length === 0) return null;
|
|
167
|
-
|
|
168
|
-
return (
|
|
169
|
-
<div className="tool-flyout-anchor">
|
|
170
|
-
<button
|
|
171
|
-
type="button"
|
|
172
|
-
className="tool-button"
|
|
173
|
-
aria-haspopup="menu"
|
|
174
|
-
aria-expanded={open}
|
|
175
|
-
title="Add a layer at the playhead"
|
|
176
|
-
data-testid="add-menu-button"
|
|
177
|
-
disabled={busy}
|
|
178
|
-
onClick={() => setOpen((v) => !v)}
|
|
179
|
-
>
|
|
180
|
-
{busy ? "…" : "+"}
|
|
181
|
-
</button>
|
|
182
|
-
{open ? (
|
|
183
|
-
<div className="tool-flyout" role="menu" data-testid="add-menu">
|
|
184
|
-
{rows.map((row) => (
|
|
185
|
-
<button
|
|
186
|
-
key={row.key}
|
|
187
|
-
type="button"
|
|
188
|
-
role="menuitem"
|
|
189
|
-
className="tool-button"
|
|
190
|
-
disabled={row.disabled === true}
|
|
191
|
-
title={row.title ?? row.label}
|
|
192
|
-
onClick={() => {
|
|
193
|
-
setOpen(false);
|
|
194
|
-
row.run();
|
|
195
|
-
}}
|
|
196
|
-
>
|
|
197
|
-
{row.label}
|
|
198
|
-
</button>
|
|
199
|
-
))}
|
|
200
|
-
</div>
|
|
201
|
-
) : null}
|
|
202
|
-
<input
|
|
203
|
-
ref={fileRef}
|
|
204
|
-
className="audio-file-input"
|
|
205
|
-
type="file"
|
|
206
|
-
// EXTENSIONS, NOT `image/*` OR `video/*`. The "sniff" on the far end
|
|
207
|
-
// is a filename extension map (`KIND_BY_EXT` in cli/src/assetStore.ts),
|
|
208
|
-
// and `storeAsset` writes the bytes BEFORE the kind is consulted — so a
|
|
209
|
-
// file the picker offered and the map does not know (.mkv, .svg, .heic)
|
|
210
|
-
// is stored, then refused, leaving an orphan blob in the
|
|
211
|
-
// content-addressed store that nothing ever collects. The list is
|
|
212
|
-
// exactly what the map answers "image" or "video" for.
|
|
213
|
-
accept={
|
|
214
|
-
want?.kind === "model"
|
|
215
|
-
? ".glb,.gltf,model/gltf-binary"
|
|
216
|
-
: ".png,.jpg,.jpeg,.webp,.avif,.gif,.mp4,.webm,.mov,.m4v," +
|
|
217
|
-
"image/png,image/jpeg,image/webp,image/avif,image/gif," +
|
|
218
|
-
"video/mp4,video/webm,video/quicktime"
|
|
219
|
-
}
|
|
220
|
-
hidden
|
|
221
|
-
data-testid="add-file-input"
|
|
222
|
-
onChange={(e) => {
|
|
223
|
-
chose(e.currentTarget.files?.[0] ?? null);
|
|
224
|
-
// Cleared so re-picking the SAME file fires change again.
|
|
225
|
-
e.currentTarget.value = "";
|
|
226
|
-
}}
|
|
227
|
-
/>
|
|
228
|
-
</div>
|
|
229
|
-
);
|
|
230
|
-
}
|
package/src/stage/MoveGizmo.tsx
DELETED
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
// Three drag arms in the working pane, on whatever the author is transforming.
|
|
2
|
-
//
|
|
3
|
-
// ── ONE GIZMO, TWO SUBJECTS, AND THE FORK IS `modalTarget` ──────────────
|
|
4
|
-
//
|
|
5
|
-
// With a selection it hangs on the selection's centre and moves layers. With
|
|
6
|
-
// NOTHING selected it hangs on the shot camera's apex and moves the camera —
|
|
7
|
-
// which is not a second feature bolted on, it is the SAME rule `G` and `S`
|
|
8
|
-
// already follow (modalState.ts: "layers when the selection is non-empty, else
|
|
9
|
-
// the CAMERA's framing"). Importing `modalTarget` rather than restating it is
|
|
10
|
-
// what keeps the drawn gizmo and the keyboard from ever disagreeing about whose
|
|
11
|
-
// transform the author is about to change.
|
|
12
|
-
//
|
|
13
|
-
// The camera's arms mean something slightly different, and `framingArmDelta`
|
|
14
|
-
// owns that: it has no free z, so its Z arm is the DOLLY. See that function.
|
|
15
|
-
//
|
|
16
|
-
// ── WHAT THIS ADDS THAT THE PANE DID NOT HAVE ───────────────────────────
|
|
17
|
-
//
|
|
18
|
-
// Dragging a layer's BODY in this pane unprojects onto a plane, so it can move
|
|
19
|
-
// the layer in two axes at once but never along z. Everything else was on the
|
|
20
|
-
// keyboard: G-X, G-Y, G-Z. This is the pointer's way to say the same three
|
|
21
|
-
// things, which is the one an author reaching for "place it over there" tries
|
|
22
|
-
// first. The camera had even less: a body drag that pans and an apex drag that
|
|
23
|
-
// zooms, neither of which can be constrained to an axis.
|
|
24
|
-
//
|
|
25
|
-
// ROTATE AND SCALE STAY ON R AND S. They already exist, with axis constraints
|
|
26
|
-
// and a live pill (modalState.ts); a second set of rings and boxes would be a
|
|
27
|
-
// duplicate of a lane that works, and every one of them would need its own
|
|
28
|
-
// screen-space arc maths. This gizmo is TRANSLATE ONLY, on purpose.
|
|
29
|
-
//
|
|
30
|
-
// ── WHY THE DRAG SOLVES A LINE AND NOT A PLANE ──────────────────────────
|
|
31
|
-
//
|
|
32
|
-
// An arm the author has grabbed must stay under the cursor, and only the
|
|
33
|
-
// closest-approach solve between the axis line and the pointer ray does that
|
|
34
|
-
// (`axisParam`). Unprojecting onto a plane and zeroing two components — what
|
|
35
|
-
// the modal lane does, because it has no drawn arm to stay under — drifts away
|
|
36
|
-
// from the pointer as the view turns and runs away entirely at a grazing angle.
|
|
37
|
-
//
|
|
38
|
-
// ── EDITOR-ONLY, THE SAME THREE WAYS THE FRUSTUM IS ─────────────────────
|
|
39
|
-
//
|
|
40
|
-
// `layers.set(EDITOR_LAYER)`, SplitRenderer's post pass pinned to bit 0, and no
|
|
41
|
-
// `SHOT_LAYER_KEY` userData. It also writes no document: the DRAG does, through
|
|
42
|
-
// the same `onGestureStart`/`onGesture`/`onGestureEnd` scrub every other
|
|
43
|
-
// transform in this file opens.
|
|
44
|
-
import { useEffect, useMemo } from "react";
|
|
45
|
-
import { useFrame } from "@react-three/fiber";
|
|
46
|
-
import { BufferAttribute, BufferGeometry, Color, LineSegments, Vector3 } from "three";
|
|
47
|
-
import type { Camera, OrthographicCamera, PerspectiveCamera } from "three";
|
|
48
|
-
import { LineBasicNodeMaterial } from "three/webgpu";
|
|
49
|
-
import { EDITOR_LAYER } from "./StageTools.js";
|
|
50
|
-
import { DESIGN_AXES, armGrabbable } from "./gizmoMath.js";
|
|
51
|
-
import type { DesignAxis, Vec3 } from "./gizmoMath.js";
|
|
52
|
-
// `GizmoGrab` lives in gizmoMath.ts with the rest of the axis vocabulary — the
|
|
53
|
-
// pointer lane carries one and must not import a three-side module for a type.
|
|
54
|
-
import { APEX_GRAB_PX } from "./FrustumGizmo.js";
|
|
55
|
-
import { borderDistance, segmentsMoved } from "./frustumGeometry.js";
|
|
56
|
-
import { modalTarget } from "./modalState.js";
|
|
57
|
-
import { toScreen } from "./project.js";
|
|
58
|
-
import { tokenColor } from "./tokenColor.js";
|
|
59
|
-
import type { StageFeed } from "./feed.js";
|
|
60
|
-
import type { PaneRect } from "./panes.js";
|
|
61
|
-
|
|
62
|
-
/** Over everything, and over the frustum too: an arm that a camera gizmo could
|
|
63
|
-
* hide is an arm the author cannot grab. */
|
|
64
|
-
const GIZMO_RENDER_ORDER = 2_000_000;
|
|
65
|
-
|
|
66
|
-
/** Arm tip distance in CSS px, held CONSTANT as the author dollies. A
|
|
67
|
-
* world-space length would be a gizmo that fills the pane up close and
|
|
68
|
-
* disappears far out — and, because the hit slop below is in screen px, one
|
|
69
|
-
* whose grab target changed size with the zoom. */
|
|
70
|
-
const ARM_PX = 90;
|
|
71
|
-
|
|
72
|
-
/** How close a press has to land to an arm, in CSS px. The frustum's own 14 —
|
|
73
|
-
* a line is a line, and the author's finger is the same size in both. */
|
|
74
|
-
const GRAB_PX = 14;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Where an arm STARTS, in CSS px from the anchor.
|
|
78
|
-
*
|
|
79
|
-
* It has to clear the anchor by more than BOTH slops added together, and that
|
|
80
|
-
* is not cosmetic. On the camera the anchor IS the frustum's apex, whose own
|
|
81
|
-
* grab is a 14px screen target (FrustumGizmo's APEX_GRAB_PX) tested AFTER this
|
|
82
|
-
* one in the pointer lane. At any start below `APEX_GRAB_PX + GRAB_PX` a press
|
|
83
|
-
* aimed at the apex lands within `GRAB_PX` of an arm as well, the arms win, and
|
|
84
|
-
* the apex dolly becomes unreachable — silently, because an arm drag looks like
|
|
85
|
-
* a working gesture.
|
|
86
|
-
*
|
|
87
|
-
* 30 > 14 + 14. It also keeps the three roots from piling into one ambiguous
|
|
88
|
-
* blob, which is why the layer gizmo wants it too.
|
|
89
|
-
*/
|
|
90
|
-
const ARM_START_PX = 30;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* How far an arm's ROOT must land from the anchor ON SCREEN to be grabbable.
|
|
94
|
-
*
|
|
95
|
-
* ── WHY `ARM_START_PX` IS NOT ENOUGH ────────────────────────────────────
|
|
96
|
-
*
|
|
97
|
-
* The note above asserts an inequality in SCREEN px ("30 > 14 + 14"), but the
|
|
98
|
-
* value is converted to WORLD units at the anchor's depth and then projected.
|
|
99
|
-
* So the distance the author actually sees is `ARM_START_PX * sin(angle to the
|
|
100
|
-
* view direction)`, and an axis pointing near the camera collapses:
|
|
101
|
-
*
|
|
102
|
-
* At Numpad7 the polar angle clamps to 5 degrees, so the design y arm
|
|
103
|
-
* projects to about 2.6 px and every press near the selection centre grabs
|
|
104
|
-
* it. A 5 px twitch then throws the layer.
|
|
105
|
-
*
|
|
106
|
-
* Under ISOLATION'S ORTHOGRAPHIC RIG there is no vanishing point at all: the
|
|
107
|
-
* design z axis points exactly at the camera, so the z arm projects to a
|
|
108
|
-
* mathematical POINT on the anchor. `borderDistance` to a degenerate segment
|
|
109
|
-
* is the distance to that point, so every press within `GRAB_PX` of the
|
|
110
|
-
* selection centre grabs it — and then `axisParam` divides by a zero
|
|
111
|
-
* denominator, answers null, and `runGizmoGesture` returns before opening a
|
|
112
|
-
* scrub. The press is completely dead, under a blue line that looks grabbable.
|
|
113
|
-
*
|
|
114
|
-
* So the test is done where it is true: after projection, in screen px. An arm
|
|
115
|
-
* that fails it is still DRAWN — it is a real axis and hiding it would be
|
|
116
|
-
* lying about the geometry — but it does not answer presses, so they fall
|
|
117
|
-
* through to the pick lane underneath.
|
|
118
|
-
*
|
|
119
|
-
* ── AND IT MEASURES THE DRAWN LENGTH, NOT THE ROOT OFFSET ───────────────
|
|
120
|
-
*
|
|
121
|
-
* The first version of this gate compared the ROOT's distance from the anchor,
|
|
122
|
-
* on the reasoning that the tip sits three times further out so the root test
|
|
123
|
-
* implies the length test. That is backwards: the root offset is at most
|
|
124
|
-
* `ARM_START_PX = 30` px and the threshold is 28, so at the SHIPPED DEFAULT
|
|
125
|
-
* view (theta 35, phi 65) all three arms measured 20 to 27 px, every one of
|
|
126
|
-
* them failed, `ends` came back empty and the gizmo was inert everywhere. The
|
|
127
|
-
* quantity that decides whether a segment can be grabbed is the segment's own
|
|
128
|
-
* length, so that is what is measured.
|
|
129
|
-
*
|
|
130
|
-
* The APEX CLEARANCE is a separate question and only applies to the camera
|
|
131
|
-
* subject, where the anchor IS the frustum's apex — see `ARM_START_PX`.
|
|
132
|
-
*/
|
|
133
|
-
const MIN_ARM_SCREEN_PX = GRAB_PX * 2;
|
|
134
|
-
|
|
135
|
-
const AXIS_FALLBACK: Readonly<Record<DesignAxis, string>> = {
|
|
136
|
-
x: "#ef4444",
|
|
137
|
-
y: "#22c55e",
|
|
138
|
-
z: "#3b82f6",
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/** How far a hovered arm blends towards white. Enough to read as hot at a
|
|
142
|
-
* glance, little enough that red / green / blue still name the axis. */
|
|
143
|
-
const HOVER_TINT = 0.45;
|
|
144
|
-
const WHITE = new Color(1, 1, 1);
|
|
145
|
-
|
|
146
|
-
const DEG = Math.PI / 180;
|
|
147
|
-
const _origin = new Vector3();
|
|
148
|
-
const _root = new Vector3();
|
|
149
|
-
const _tip = new Vector3();
|
|
150
|
-
const _view = new Vector3();
|
|
151
|
-
/** One arm's six position floats, hoisted: the hot path allocates nothing. */
|
|
152
|
-
const _next = new Float32Array(6);
|
|
153
|
-
|
|
154
|
-
/** Is a world point behind the camera's own plane? View-space z runs NEGATIVE
|
|
155
|
-
* in front, so anything at or past zero has no honest projection. */
|
|
156
|
-
function behind(point: Vector3, camera: Camera): boolean {
|
|
157
|
-
return _view.copy(point).applyMatrix4(camera.matrixWorldInverse).z >= 0;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export interface MoveGizmoProps {
|
|
161
|
-
feed: StageFeed;
|
|
162
|
-
panes: { working: PaneRect | null; camera: PaneRect | null };
|
|
163
|
-
selection: readonly string[];
|
|
164
|
-
/** The shot camera's own position at this frame, or null when no scene is on
|
|
165
|
-
* screen. Passed in rather than read off the feed: `FrustumGizmo` writes its
|
|
166
|
-
* own copy at priority 0 too, and two priority-0 subscribers have no defined
|
|
167
|
-
* order — so this comes from `rig`, which `StageApp` already memoizes. */
|
|
168
|
-
cameraApex: Vec3 | null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function MoveGizmo({ feed, panes, selection, cameraApex }: MoveGizmoProps) {
|
|
172
|
-
// One `LineSegments` per axis rather than one buffer with vertex colours:
|
|
173
|
-
// three objects and three materials is the whole cost, and vertex colours on
|
|
174
|
-
// a node material would be a second thing to keep working across the WebGPU
|
|
175
|
-
// and WebGL2 backends for no gain at three vertices apiece.
|
|
176
|
-
const arms = useMemo(
|
|
177
|
-
() =>
|
|
178
|
-
DESIGN_AXES.map(({ axis }) => {
|
|
179
|
-
const geometry = new BufferGeometry();
|
|
180
|
-
geometry.setAttribute("position", new BufferAttribute(new Float32Array(6), 3));
|
|
181
|
-
const material = new LineBasicNodeMaterial();
|
|
182
|
-
material.color.copy(tokenColor(`--axis-${axis}`, AXIS_FALLBACK[axis]));
|
|
183
|
-
material.depthTest = false;
|
|
184
|
-
material.depthWrite = false;
|
|
185
|
-
material.toneMapped = false;
|
|
186
|
-
// The working pane renders under a clay override in solid/wireframe
|
|
187
|
-
// mode, and a grey gizmo is the one thing this must not be.
|
|
188
|
-
material.allowOverride = false;
|
|
189
|
-
const lines = new LineSegments(geometry, material);
|
|
190
|
-
lines.layers.set(EDITOR_LAYER);
|
|
191
|
-
lines.renderOrder = GIZMO_RENDER_ORDER;
|
|
192
|
-
// World positions rewritten in place — same reason the frustum turns
|
|
193
|
-
// culling off: the bounding sphere is never the one the buffer holds.
|
|
194
|
-
lines.frustumCulled = false;
|
|
195
|
-
lines.matrixAutoUpdate = false;
|
|
196
|
-
// How far towards white the hover has taken this arm, so the frame loop
|
|
197
|
-
// can skip the colour write on every frame that changed nothing.
|
|
198
|
-
return { axis, lines, geometry, material, tint: 0 };
|
|
199
|
-
}),
|
|
200
|
-
[],
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
useEffect(
|
|
204
|
-
() => () => {
|
|
205
|
-
for (const arm of arms) {
|
|
206
|
-
arm.geometry.dispose();
|
|
207
|
-
arm.material.dispose();
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
[arms],
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
useFrame(() => {
|
|
214
|
-
const working = panes.working;
|
|
215
|
-
const camera = feed.workingCamera;
|
|
216
|
-
// `modalTarget` and not `selection.length` written out again: this is the
|
|
217
|
-
// same fork `G` takes, and one of them drifting is a gizmo that moves
|
|
218
|
-
// something other than what the pill says it will.
|
|
219
|
-
//
|
|
220
|
-
// A selection whose layers are all off-frame at this playhead anchors
|
|
221
|
-
// NOWHERE rather than falling through to the camera — silently retargeting
|
|
222
|
-
// a live selection's gizmo at the camera is the worst answer available.
|
|
223
|
-
const subject = modalTarget(selection.length > 0);
|
|
224
|
-
const centre = subject === "camera" ? cameraApex : selectionCentre(feed, selection);
|
|
225
|
-
if (working === null || camera === null || centre === null) {
|
|
226
|
-
for (const arm of arms) arm.lines.visible = false;
|
|
227
|
-
feed.gizmoHit = null;
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
_origin.set(centre[0], centre[1], centre[2]);
|
|
232
|
-
const perPixel = worldPerPixel(camera, _origin, working.height);
|
|
233
|
-
const start = ARM_START_PX * perPixel;
|
|
234
|
-
const length = ARM_PX * perPixel;
|
|
235
|
-
// The anchor ON SCREEN, which is what every arm's own screen length is
|
|
236
|
-
// measured against below. An anchor behind the camera projects to garbage,
|
|
237
|
-
// so nothing is grabbable at all — the same refusal each arm makes.
|
|
238
|
-
const anchorVisible = !behind(_origin, camera);
|
|
239
|
-
const anchor = toScreen(_origin, camera, working.width, working.height);
|
|
240
|
-
// Screen ends, kept beside the world ones so the hit test measures the
|
|
241
|
-
// segment that was actually drawn.
|
|
242
|
-
const ends: {
|
|
243
|
-
axis: DesignAxis;
|
|
244
|
-
world: Vec3;
|
|
245
|
-
a: [number, number];
|
|
246
|
-
b: [number, number];
|
|
247
|
-
}[] = [];
|
|
248
|
-
|
|
249
|
-
for (const [i, { world }] of DESIGN_AXES.entries()) {
|
|
250
|
-
const arm = arms[i]!;
|
|
251
|
-
arm.lines.visible = true;
|
|
252
|
-
const from: Vec3 = [
|
|
253
|
-
centre[0] + world[0] * start,
|
|
254
|
-
centre[1] + world[1] * start,
|
|
255
|
-
centre[2] + world[2] * start,
|
|
256
|
-
];
|
|
257
|
-
const to: Vec3 = [
|
|
258
|
-
centre[0] + world[0] * length,
|
|
259
|
-
centre[1] + world[1] * length,
|
|
260
|
-
centre[2] + world[2] * length,
|
|
261
|
-
];
|
|
262
|
-
const attribute = arm.geometry.getAttribute("position") as BufferAttribute;
|
|
263
|
-
const buffer = attribute.array as Float32Array;
|
|
264
|
-
_next[0] = from[0];
|
|
265
|
-
_next[1] = from[1];
|
|
266
|
-
_next[2] = from[2];
|
|
267
|
-
_next[3] = to[0];
|
|
268
|
-
_next[4] = to[1];
|
|
269
|
-
_next[5] = to[2];
|
|
270
|
-
// The frustum's own half-pixel gate, for its reason: this runs on every
|
|
271
|
-
// committed frame, and a parked playhead with a selection must not
|
|
272
|
-
// re-upload three buffers per seek.
|
|
273
|
-
if (segmentsMoved(buffer, _next)) {
|
|
274
|
-
buffer.set(_next);
|
|
275
|
-
attribute.needsUpdate = true;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// An arm with EITHER end behind the working camera projects to garbage,
|
|
279
|
-
// so it is drawn but not grabbable — the same refusal the frustum's body
|
|
280
|
-
// makes, and for the same reason: there is no screen segment to measure
|
|
281
|
-
// against.
|
|
282
|
-
_root.set(from[0], from[1], from[2]);
|
|
283
|
-
_tip.set(to[0], to[1], to[2]);
|
|
284
|
-
if (!anchorVisible || behind(_root, camera) || behind(_tip, camera)) continue;
|
|
285
|
-
const a = toScreen(_root, camera, working.width, working.height);
|
|
286
|
-
const b = toScreen(_tip, camera, working.width, working.height);
|
|
287
|
-
// AN ARM THAT COLLAPSED ON SCREEN IS DRAWN BUT NOT GRABBABLE, and on the
|
|
288
|
-
// camera it must also clear the apex. Both live in `armGrabbable`, which
|
|
289
|
-
// is pure so a gate that decides whether the whole widget works can be
|
|
290
|
-
// checked without a GL context.
|
|
291
|
-
if (!armGrabbable(a, b, anchor, subject, MIN_ARM_SCREEN_PX, APEX_GRAB_PX + GRAB_PX)) continue;
|
|
292
|
-
ends.push({ axis: arm.axis, world, a, b });
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
feed.gizmoHit = (x, y) => {
|
|
296
|
-
let best: (typeof ends)[number] | null = null;
|
|
297
|
-
let bestDistance = GRAB_PX;
|
|
298
|
-
for (const end of ends) {
|
|
299
|
-
// `borderDistance` walks a CLOSED polygon, so a two-point list is that
|
|
300
|
-
// one segment measured twice — the point-to-segment distance, out of
|
|
301
|
-
// the module that already owns it.
|
|
302
|
-
const distance = borderDistance(x, y, [end.a, end.b]);
|
|
303
|
-
if (distance <= bestDistance) {
|
|
304
|
-
best = end;
|
|
305
|
-
bestDistance = distance;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return best === null
|
|
309
|
-
? null
|
|
310
|
-
: { subject, axis: best.axis, origin: centre, direction: best.world };
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// ── THE HOVERED ARM LIGHTS UP ─────────────────────────────────────────
|
|
314
|
-
//
|
|
315
|
-
// Blender highlights the gizmo element under the cursor, and this pane did
|
|
316
|
-
// not — so "is this grabbable" was something the author learned by trying.
|
|
317
|
-
// Blended TOWARDS WHITE rather than swapped for it, so the axis is still
|
|
318
|
-
// identifiable by colour while it is hot.
|
|
319
|
-
//
|
|
320
|
-
// Here, in the frame loop, off a plain field the pointer lane wrote. A
|
|
321
|
-
// hover therefore costs one uniform write per frame and zero React renders,
|
|
322
|
-
// which is the whole reason `feed.hoverArm` is a field and not state.
|
|
323
|
-
for (const arm of arms) {
|
|
324
|
-
const hot = feed.hoverArm === arm.axis && ends.some((e) => e.axis === arm.axis);
|
|
325
|
-
const want = hot ? HOVER_TINT : 0;
|
|
326
|
-
if (arm.tint === want) continue;
|
|
327
|
-
arm.tint = want;
|
|
328
|
-
arm.material.color.copy(tokenColor(`--axis-${arm.axis}`, AXIS_FALLBACK[arm.axis])).lerp(WHITE, want);
|
|
329
|
-
}
|
|
330
|
-
}, 0);
|
|
331
|
-
|
|
332
|
-
useEffect(
|
|
333
|
-
() => () => {
|
|
334
|
-
feed.gizmoHit = null;
|
|
335
|
-
},
|
|
336
|
-
[feed],
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
return (
|
|
340
|
-
<>
|
|
341
|
-
{arms.map((arm) => (
|
|
342
|
-
<primitive key={arm.axis} object={arm.lines} />
|
|
343
|
-
))}
|
|
344
|
-
</>
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Where the arms sit: the mean of the selected layers' own origins.
|
|
350
|
-
*
|
|
351
|
-
* The GROUP origin out of `feed.frames`, never the mesh's centre — a paint
|
|
352
|
-
* layer's plane is grown by the rasterizer's shadow padding, so the mesh would
|
|
353
|
-
* put the gizmo a blur radius away from the thing it moves (the same reason
|
|
354
|
-
* `collectFrames` reads userData).
|
|
355
|
-
*
|
|
356
|
-
* Null when nothing selected is on screen this frame, which includes a
|
|
357
|
-
* selection whose lifetime has ended: an arm drawn for a layer that is not
|
|
358
|
-
* there would drag something the author cannot see.
|
|
359
|
-
*/
|
|
360
|
-
function selectionCentre(feed: StageFeed, selection: readonly string[]): Vec3 | null {
|
|
361
|
-
const frames = feed.frames;
|
|
362
|
-
if (frames === null || selection.length === 0) return null;
|
|
363
|
-
let x = 0;
|
|
364
|
-
let y = 0;
|
|
365
|
-
let z = 0;
|
|
366
|
-
let count = 0;
|
|
367
|
-
// ONE FRAME PER ID, the first — the same rule `StageTools`' `byId` follows.
|
|
368
|
-
// A cloner stamps its children N times under one id, and averaging all the
|
|
369
|
-
// clones put the gizmo in the empty space between them rather than on the
|
|
370
|
-
// layer the author selected.
|
|
371
|
-
const seen = new Set<string>();
|
|
372
|
-
for (const frame of frames) {
|
|
373
|
-
if (!selection.includes(frame.id) || seen.has(frame.id)) continue;
|
|
374
|
-
seen.add(frame.id);
|
|
375
|
-
const e = frame.matrixWorld.elements;
|
|
376
|
-
x += e[12]!;
|
|
377
|
-
y += e[13]!;
|
|
378
|
-
z += e[14]!;
|
|
379
|
-
count += 1;
|
|
380
|
-
}
|
|
381
|
-
return count === 0 ? null : [x / count, y / count, z / count];
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* World units per CSS pixel AT A POINT, which is what turns the arm's fixed
|
|
386
|
-
* screen length into a world one.
|
|
387
|
-
*
|
|
388
|
-
* Both camera classes, because isolation swaps an ORTHOGRAPHIC camera into this
|
|
389
|
-
* pane's slot (StageTools) and a perspective-only formula would size the gizmo
|
|
390
|
-
* off a `fov` that camera does not have — reading `undefined` and drawing arms
|
|
391
|
-
* of length NaN, which is a gizmo that silently disappears inside isolation.
|
|
392
|
-
*
|
|
393
|
-
* Perspective: the visible height at the point's own view depth, over the pane.
|
|
394
|
-
* Orthographic: its frustum height over the pane, which has no depth term at
|
|
395
|
-
* all — that is what orthographic means.
|
|
396
|
-
*/
|
|
397
|
-
function worldPerPixel(camera: Camera, at: Vector3, paneHeight: number): number {
|
|
398
|
-
const height = Math.max(1, paneHeight);
|
|
399
|
-
const ortho = camera as OrthographicCamera;
|
|
400
|
-
if (ortho.isOrthographicCamera === true) {
|
|
401
|
-
const zoom = ortho.zoom > 0 ? ortho.zoom : 1;
|
|
402
|
-
return Math.abs(ortho.top - ortho.bottom) / zoom / height;
|
|
403
|
-
}
|
|
404
|
-
const perspective = camera as PerspectiveCamera;
|
|
405
|
-
const fov = Number.isFinite(perspective.fov) ? perspective.fov : 50;
|
|
406
|
-
// View-space z runs NEGATIVE in front of the camera, so the depth is its
|
|
407
|
-
// negation — and it is floored off zero, because a selection sitting exactly
|
|
408
|
-
// at the eye would otherwise give every arm zero length.
|
|
409
|
-
const depth = Math.max(-_view.copy(at).applyMatrix4(camera.matrixWorldInverse).z, 1e-3);
|
|
410
|
-
return (2 * Math.tan((fov * DEG) / 2) * depth) / height;
|
|
411
|
-
}
|