@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,441 @@
|
|
|
1
|
+
// The graph, as React Flow wants it — and the memo that makes a keystroke cost
|
|
2
|
+
// ONE card.
|
|
3
|
+
//
|
|
4
|
+
// ── THE DEFECT THIS MODULE EXISTS FOR ───────────────────────────────────
|
|
5
|
+
//
|
|
6
|
+
// v1 built the node array inside the canvas with a `useMemo` over the whole
|
|
7
|
+
// graph, and every node's `data` was a fresh object literal carrying the WHOLE
|
|
8
|
+
// graph. So typing one digit into one Float field rebuilt every card's props,
|
|
9
|
+
// and React Flow re-rendered all of them: at forty nodes a held arrow key was
|
|
10
|
+
// visibly behind the author's finger, and the profile was ninety-five percent
|
|
11
|
+
// cards that had not changed.
|
|
12
|
+
//
|
|
13
|
+
// The fix is not "memo the components". A `memo()` compares props, and props
|
|
14
|
+
// that are rebuilt every render compare unequal every render. The fix is to
|
|
15
|
+
// stop rebuilding them: a WeakMap keyed on the GRAPH NODE OBJECT holds the flow
|
|
16
|
+
// node it produced, beside a key naming everything about the card that is NOT
|
|
17
|
+
// the node itself (its wires, its selection, its group's origin, its problem).
|
|
18
|
+
// The store writes graphs with structural sharing — `{...graph, nodesById: {…}}`
|
|
19
|
+
// — so an untouched node keeps its identity across an edit, hits the cache, and
|
|
20
|
+
// its React Flow node object comes back `===`. React Flow's own reconciler then
|
|
21
|
+
// skips it, and so does React.
|
|
22
|
+
//
|
|
23
|
+
// `materialGraphCanvas.test.tsx` pins that directly: build twice across a
|
|
24
|
+
// one-node edit and assert the OTHER node's flow object is identity-equal.
|
|
25
|
+
//
|
|
26
|
+
// ── WHAT MAY NOT GO IN `data` ───────────────────────────────────────────
|
|
27
|
+
//
|
|
28
|
+
// Anything that changes for a reason unrelated to this card. The graph itself
|
|
29
|
+
// is the obvious one and is gone. Node PREVIEWS are the subtle one: a thumbnail
|
|
30
|
+
// arrives asynchronously for a handful of cards, and threading it through here
|
|
31
|
+
// would invalidate the memo for every card each time one landed. They ride a
|
|
32
|
+
// React context instead (`NodePreview.tsx`), which re-renders the previews and
|
|
33
|
+
// nothing else.
|
|
34
|
+
import type { Edge, Node } from "@xyflow/react";
|
|
35
|
+
import { materialGraphNodeSockets, materialGraphNodeSpec } from "@dailies-studio/shot/doc";
|
|
36
|
+
import type {
|
|
37
|
+
MaterialGraph3D,
|
|
38
|
+
MaterialGraphNode,
|
|
39
|
+
MaterialGraphNodeSpec,
|
|
40
|
+
MaterialGraphParameter,
|
|
41
|
+
MaterialGraphProblem,
|
|
42
|
+
MaterialGraphResolvedType,
|
|
43
|
+
MaterialGraphSocketSpec,
|
|
44
|
+
MaterialGraphTypeIndex,
|
|
45
|
+
ShotAsset,
|
|
46
|
+
} from "@dailies-studio/shot/doc";
|
|
47
|
+
import type { MaterialGraphSelection } from "../../state/types.js";
|
|
48
|
+
|
|
49
|
+
/** The four renderers. `group` and `comment` are inert boxes, `reroute` is a
|
|
50
|
+
* dot with one handle a side, and everything else is a compute card. */
|
|
51
|
+
export type MaterialFlowNodeType = "material" | "materialGroup" | "materialComment" | "materialReroute";
|
|
52
|
+
|
|
53
|
+
/** The two dropdown vocabularies every card shares. Built ONCE per graph and
|
|
54
|
+
* handed to all of them by identity, so forty cards hold one array rather than
|
|
55
|
+
* forty copies of the same options. */
|
|
56
|
+
export interface MaterialChoices {
|
|
57
|
+
images: readonly { value: string; label: string }[];
|
|
58
|
+
parameters: readonly { value: string; label: string }[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface MaterialFlowData extends Record<string, unknown> {
|
|
62
|
+
/** Carried rather than read from the store at commit time, for the reason
|
|
63
|
+
* every graph verb takes them as parameters: a card built inside a function
|
|
64
|
+
* must write to that function even after the canvas has climbed out. */
|
|
65
|
+
materialId: string;
|
|
66
|
+
path: readonly string[];
|
|
67
|
+
node: MaterialGraphNode;
|
|
68
|
+
spec: MaterialGraphNodeSpec;
|
|
69
|
+
/** Resolved PER NODE — a subgraph call's sockets are its function's
|
|
70
|
+
* signature, not the static spec's empty lists. */
|
|
71
|
+
inputs: readonly MaterialGraphSocketSpec[];
|
|
72
|
+
outputs: readonly MaterialGraphSocketSpec[];
|
|
73
|
+
/** `"in:<socket>"` / `"out:<socket>"` for every socket a wire lands on. The
|
|
74
|
+
* card needs it for the ink and the widgets need it to know an input is
|
|
75
|
+
* driven and its literal no longer says anything. */
|
|
76
|
+
connected: ReadonlySet<string>;
|
|
77
|
+
parameter: MaterialGraphParameter | undefined;
|
|
78
|
+
assetLabel: string | undefined;
|
|
79
|
+
/** What this card actually produces, once the wires upstream are resolved.
|
|
80
|
+
* Null while the type index cannot say, which is what a cycle or a dangling
|
|
81
|
+
* wire looks like from here. */
|
|
82
|
+
outputType: MaterialGraphResolvedType | null;
|
|
83
|
+
/** The validator's sentence about this node, or null. */
|
|
84
|
+
problem: string | null;
|
|
85
|
+
choices: MaterialChoices;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type MaterialFlowNode = Node<MaterialFlowData, MaterialFlowNodeType>;
|
|
89
|
+
|
|
90
|
+
export interface MaterialFlowInput {
|
|
91
|
+
graph: MaterialGraph3D;
|
|
92
|
+
materialId: string;
|
|
93
|
+
/** The scope trail on screen. Only nodes whose `scope` is its last element
|
|
94
|
+
* are drawn — a function body is a different canvas. */
|
|
95
|
+
path: readonly string[];
|
|
96
|
+
assets: Readonly<Record<string, ShotAsset>>;
|
|
97
|
+
selection: MaterialGraphSelection;
|
|
98
|
+
problems: readonly MaterialGraphProblem[];
|
|
99
|
+
/** Built once by the caller, which needs it for `isValidConnection` anyway. */
|
|
100
|
+
types: MaterialGraphTypeIndex;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The nodes belonging to one scope. Exported because the palette, the
|
|
104
|
+
* scheduler and Select All all need the same answer. */
|
|
105
|
+
export function nodesInScope(graph: MaterialGraph3D, path: readonly string[]): MaterialGraphNode[] {
|
|
106
|
+
const scope = path.length === 0 ? undefined : path[path.length - 1];
|
|
107
|
+
return Object.values(graph.nodesById).filter((node) => (node.scope ?? undefined) === scope);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Which renderer draws this kind. */
|
|
111
|
+
function flowTypeOf(node: MaterialGraphNode): MaterialFlowNodeType {
|
|
112
|
+
if (node.kind === "group") return "materialGroup";
|
|
113
|
+
if (node.kind === "comment") return "materialComment";
|
|
114
|
+
if (node.kind === "reroute") return "materialReroute";
|
|
115
|
+
return "material";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── the choices memo ────────────────────────────────────────────────────────
|
|
119
|
+
//
|
|
120
|
+
// A one-entry cache keyed on the two SOURCE objects. Both are replaced by
|
|
121
|
+
// identity when their contents change (the store never mutates in place), so
|
|
122
|
+
// identity is the whole test — and one entry is enough because exactly one
|
|
123
|
+
// graph is on screen.
|
|
124
|
+
let choicesMemo: {
|
|
125
|
+
assets: object;
|
|
126
|
+
parameters: object;
|
|
127
|
+
value: MaterialChoices;
|
|
128
|
+
token: number;
|
|
129
|
+
} | null = null;
|
|
130
|
+
let choicesSerial = 0;
|
|
131
|
+
|
|
132
|
+
function choicesFor(
|
|
133
|
+
assets: Readonly<Record<string, ShotAsset>>,
|
|
134
|
+
parameters: Readonly<Record<string, MaterialGraphParameter>>,
|
|
135
|
+
): { value: MaterialChoices; token: number } {
|
|
136
|
+
if (choicesMemo !== null && choicesMemo.assets === assets && choicesMemo.parameters === parameters) {
|
|
137
|
+
return { value: choicesMemo.value, token: choicesMemo.token };
|
|
138
|
+
}
|
|
139
|
+
choicesSerial += 1;
|
|
140
|
+
const value: MaterialChoices = {
|
|
141
|
+
images: Object.values(assets)
|
|
142
|
+
.filter((asset) => asset.kind === "image")
|
|
143
|
+
.map((asset) => ({ value: asset.id, label: asset.path.split("/").pop() ?? asset.id }))
|
|
144
|
+
.sort((a, b) => a.label.localeCompare(b.label)),
|
|
145
|
+
parameters: Object.values(parameters)
|
|
146
|
+
.sort((a, b) => a.label.localeCompare(b.label))
|
|
147
|
+
.map((parameter) => ({ value: parameter.id, label: `${parameter.label} · ${parameter.type}` })),
|
|
148
|
+
};
|
|
149
|
+
choicesMemo = { assets, parameters, value, token: choicesSerial };
|
|
150
|
+
return { value, token: choicesSerial };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The suites build several unrelated documents in one process, and a stale
|
|
154
|
+
* one-entry memo across them would hand the second document the first's
|
|
155
|
+
* options. Never called by the app, where the cache is a hit or a miss and
|
|
156
|
+
* both are correct. */
|
|
157
|
+
export function resetMaterialChoicesMemo(): void {
|
|
158
|
+
choicesMemo = null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── the node cache ──────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
interface FlowCacheEntry {
|
|
164
|
+
key: string;
|
|
165
|
+
flow: MaterialFlowNode;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const FLOW_CACHE = new WeakMap<MaterialGraphNode, FlowCacheEntry>();
|
|
169
|
+
const EDGE_CACHE = new WeakMap<object, { key: string; edge: Edge }>();
|
|
170
|
+
|
|
171
|
+
/** Which sockets on each node carry a wire, in one pass over the edges. */
|
|
172
|
+
function connectionsOf(graph: MaterialGraph3D): Map<string, Set<string>> {
|
|
173
|
+
const out = new Map<string, Set<string>>();
|
|
174
|
+
const add = (nodeId: string, key: string): void => {
|
|
175
|
+
const found = out.get(nodeId);
|
|
176
|
+
if (found === undefined) out.set(nodeId, new Set([key]));
|
|
177
|
+
else found.add(key);
|
|
178
|
+
};
|
|
179
|
+
for (const edge of graph.edges) {
|
|
180
|
+
add(edge.source.nodeId, `out:${edge.source.socket}`);
|
|
181
|
+
add(edge.target.nodeId, `in:${edge.target.socket}`);
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The validator's problems, indexed by the node they are about.
|
|
188
|
+
*
|
|
189
|
+
* `at` is a document path — `nodesById.mgn_x.kind` — so the node id is its
|
|
190
|
+
* second segment. Only the FIRST problem per node is kept: a card has room for
|
|
191
|
+
* one sentence, and the panel below the canvas lists them all.
|
|
192
|
+
*/
|
|
193
|
+
function problemsByNode(problems: readonly MaterialGraphProblem[]): Map<string, string> {
|
|
194
|
+
const out = new Map<string, string>();
|
|
195
|
+
for (const problem of problems) {
|
|
196
|
+
const parts = problem.at.split(".");
|
|
197
|
+
if (parts[0] !== "nodesById") continue;
|
|
198
|
+
const id = parts[1];
|
|
199
|
+
if (id === undefined || out.has(id)) continue;
|
|
200
|
+
out.set(id, problem.message);
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const EMPTY_SOCKETS: ReadonlySet<string> = new Set<string>();
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Build the whole canvas.
|
|
209
|
+
*
|
|
210
|
+
* GROUPS COME FIRST in the array, which is a React Flow requirement rather than
|
|
211
|
+
* a preference: a subflow child is dropped outright if its parent has not been
|
|
212
|
+
* seen yet, so a member whose group sorted after it would simply not render.
|
|
213
|
+
*/
|
|
214
|
+
export function buildMaterialFlow(input: MaterialFlowInput): {
|
|
215
|
+
nodes: MaterialFlowNode[];
|
|
216
|
+
edges: Edge[];
|
|
217
|
+
} {
|
|
218
|
+
const { graph, materialId, path, assets, selection, problems, types } = input;
|
|
219
|
+
const scoped = nodesInScope(graph, path);
|
|
220
|
+
const connections = connectionsOf(graph);
|
|
221
|
+
const problemOf = problemsByNode(problems);
|
|
222
|
+
const selected = new Set(selection.nodeIds);
|
|
223
|
+
const selectedEdges = new Set(selection.edgeIds);
|
|
224
|
+
const { value: choices, token: choicesToken } = choicesFor(assets, graph.parameters);
|
|
225
|
+
|
|
226
|
+
const groups = scoped.filter((node) => node.kind === "group");
|
|
227
|
+
const rest = scoped.filter((node) => node.kind !== "group");
|
|
228
|
+
const nodes: MaterialFlowNode[] = [];
|
|
229
|
+
|
|
230
|
+
for (const node of [...groups, ...rest]) {
|
|
231
|
+
// A member's position is stored ABSOLUTE in the document and React Flow
|
|
232
|
+
// wants it relative to the parent, so the group's own origin is part of
|
|
233
|
+
// what this card depends on — and therefore part of its cache key.
|
|
234
|
+
const parent = node.groupId === undefined ? undefined : graph.nodesById[node.groupId];
|
|
235
|
+
const parentUsable = parent?.kind === "group" && (parent.scope ?? undefined) === (node.scope ?? undefined);
|
|
236
|
+
const origin = parentUsable ? parent.position : null;
|
|
237
|
+
const connected = connections.get(node.id) ?? EMPTY_SOCKETS;
|
|
238
|
+
const problem = problemOf.get(node.id) ?? null;
|
|
239
|
+
const isSelected = selected.has(node.id);
|
|
240
|
+
const spec = materialGraphNodeSpec(node.kind);
|
|
241
|
+
const sockets = spec === null ? { inputs: [], outputs: [] } : materialGraphNodeSockets(graph, node);
|
|
242
|
+
const outputType =
|
|
243
|
+
sockets.outputs[0] === undefined ? null : types.outputTypeOf(node.id, sockets.outputs[0].id);
|
|
244
|
+
const parameter = node.parameterId === undefined ? undefined : graph.parameters[node.parameterId];
|
|
245
|
+
const assetLabel =
|
|
246
|
+
node.assetId === undefined
|
|
247
|
+
? undefined
|
|
248
|
+
: (assets[node.assetId]?.path.split("/").pop() ?? node.assetId);
|
|
249
|
+
|
|
250
|
+
const key = [
|
|
251
|
+
isSelected ? "1" : "0",
|
|
252
|
+
[...connected].sort().join(","),
|
|
253
|
+
origin === null ? "-" : `${origin[0]},${origin[1]}`,
|
|
254
|
+
problem ?? "",
|
|
255
|
+
outputType ?? "",
|
|
256
|
+
parameter === undefined ? "" : `${parameter.id}:${parameter.label}:${parameter.type}`,
|
|
257
|
+
assetLabel ?? "",
|
|
258
|
+
String(choicesToken),
|
|
259
|
+
// Dynamic sockets belong to the function, not the node — a rename of a
|
|
260
|
+
// function input changes this card and nothing about `node`.
|
|
261
|
+
sockets.inputs.map((s) => `${s.id}:${s.type}`).join("|"),
|
|
262
|
+
sockets.outputs.map((s) => `${s.id}:${s.type}`).join("|"),
|
|
263
|
+
].join("");
|
|
264
|
+
|
|
265
|
+
const hit = FLOW_CACHE.get(node);
|
|
266
|
+
if (hit !== undefined && hit.key === key) {
|
|
267
|
+
nodes.push(hit.flow);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (spec === null) continue;
|
|
271
|
+
|
|
272
|
+
const data: MaterialFlowData = {
|
|
273
|
+
materialId,
|
|
274
|
+
path,
|
|
275
|
+
node,
|
|
276
|
+
spec,
|
|
277
|
+
inputs: sockets.inputs,
|
|
278
|
+
outputs: sockets.outputs,
|
|
279
|
+
connected,
|
|
280
|
+
parameter,
|
|
281
|
+
assetLabel,
|
|
282
|
+
outputType,
|
|
283
|
+
problem,
|
|
284
|
+
choices,
|
|
285
|
+
};
|
|
286
|
+
const flow: MaterialFlowNode = {
|
|
287
|
+
id: node.id,
|
|
288
|
+
type: flowTypeOf(node),
|
|
289
|
+
position:
|
|
290
|
+
origin === null
|
|
291
|
+
? { x: node.position[0], y: node.position[1] }
|
|
292
|
+
: { x: node.position[0] - origin[0], y: node.position[1] - origin[1] },
|
|
293
|
+
selected: isSelected,
|
|
294
|
+
data,
|
|
295
|
+
...(origin === null ? {} : { parentId: node.groupId! }),
|
|
296
|
+
...(node.kind === "group" || node.kind === "comment"
|
|
297
|
+
? { style: { width: node.size?.[0] ?? 240, height: node.size?.[1] ?? 160 } }
|
|
298
|
+
: {}),
|
|
299
|
+
};
|
|
300
|
+
FLOW_CACHE.set(node, { key, flow });
|
|
301
|
+
nodes.push(flow);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const edges: Edge[] = [];
|
|
305
|
+
const inScope = new Set(scoped.map((node) => node.id));
|
|
306
|
+
for (const edge of graph.edges) {
|
|
307
|
+
// An edge is drawn only when BOTH ends are on this canvas. A cross-scope
|
|
308
|
+
// wire cannot exist (`canConnect` refuses one), so this is really "the
|
|
309
|
+
// other scope's wires stay in the other scope".
|
|
310
|
+
if (!inScope.has(edge.source.nodeId) || !inScope.has(edge.target.nodeId)) continue;
|
|
311
|
+
const key = selectedEdges.has(edge.id) ? "1" : "0";
|
|
312
|
+
const hit = EDGE_CACHE.get(edge);
|
|
313
|
+
if (hit !== undefined && hit.key === key) {
|
|
314
|
+
edges.push(hit.edge);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const flow: Edge = {
|
|
318
|
+
id: edge.id,
|
|
319
|
+
source: edge.source.nodeId,
|
|
320
|
+
sourceHandle: edge.source.socket,
|
|
321
|
+
target: edge.target.nodeId,
|
|
322
|
+
targetHandle: edge.target.socket,
|
|
323
|
+
type: "smoothstep",
|
|
324
|
+
className: "material-edge",
|
|
325
|
+
selected: key === "1",
|
|
326
|
+
reconnectable: true,
|
|
327
|
+
};
|
|
328
|
+
EDGE_CACHE.set(edge, { key, edge: flow });
|
|
329
|
+
edges.push(flow);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return { nodes, edges };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* A dragged React Flow node's position, back in DOCUMENT coordinates.
|
|
337
|
+
*
|
|
338
|
+
* The one place the subflow offset is undone. Doing it at each call site is how
|
|
339
|
+
* a group member ends up saved at its group-relative position and jumps to the
|
|
340
|
+
* top-left corner of the box on the next load.
|
|
341
|
+
*/
|
|
342
|
+
export function graphPositionOf(
|
|
343
|
+
flow: { position: { x: number; y: number }; parentId?: string | undefined },
|
|
344
|
+
graph: MaterialGraph3D,
|
|
345
|
+
): [number, number] {
|
|
346
|
+
const parent = flow.parentId === undefined ? undefined : graph.nodesById[flow.parentId];
|
|
347
|
+
if (parent === undefined) return [Math.round(flow.position.x), Math.round(flow.position.y)];
|
|
348
|
+
return [
|
|
349
|
+
Math.round(flow.position.x + parent.position[0]),
|
|
350
|
+
Math.round(flow.position.y + parent.position[1]),
|
|
351
|
+
];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export interface FlowRect {
|
|
355
|
+
x: number;
|
|
356
|
+
y: number;
|
|
357
|
+
width: number;
|
|
358
|
+
height: number;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Does the segment a→b cross the rect?
|
|
363
|
+
*
|
|
364
|
+
* Liang–Barsky, which answers "does it cross" without computing where — the
|
|
365
|
+
* canvas only needs the boolean, once per edge per pointer frame. The two
|
|
366
|
+
* degenerate cases matter and are handled: a segment PARALLEL to a slab is in
|
|
367
|
+
* only if it already lies inside it, and a zero-length segment reduces to a
|
|
368
|
+
* point test.
|
|
369
|
+
*/
|
|
370
|
+
export function segmentIntersectsRect(
|
|
371
|
+
a: { x: number; y: number },
|
|
372
|
+
b: { x: number; y: number },
|
|
373
|
+
rect: FlowRect,
|
|
374
|
+
): boolean {
|
|
375
|
+
const dx = b.x - a.x;
|
|
376
|
+
const dy = b.y - a.y;
|
|
377
|
+
let t0 = 0;
|
|
378
|
+
let t1 = 1;
|
|
379
|
+
const slabs: readonly [number, number][] = [
|
|
380
|
+
[-dx, a.x - rect.x],
|
|
381
|
+
[dx, rect.x + rect.width - a.x],
|
|
382
|
+
[-dy, a.y - rect.y],
|
|
383
|
+
[dy, rect.y + rect.height - a.y],
|
|
384
|
+
];
|
|
385
|
+
for (const [p, q] of slabs) {
|
|
386
|
+
if (p === 0) {
|
|
387
|
+
// Parallel to this slab: outside it means it can never enter.
|
|
388
|
+
if (q < 0) return false;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const r = q / p;
|
|
392
|
+
if (p < 0) {
|
|
393
|
+
if (r > t1) return false;
|
|
394
|
+
if (r > t0) t0 = r;
|
|
395
|
+
} else {
|
|
396
|
+
if (r < t0) return false;
|
|
397
|
+
if (r < t1) t1 = r;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** A group box's document rect. Mirrors the fallback `buildMaterialFlow` uses
|
|
404
|
+
* when a box carries no size, so the picture and the hit test agree. */
|
|
405
|
+
function groupRect(node: MaterialGraphNode): FlowRect {
|
|
406
|
+
return {
|
|
407
|
+
x: node.position[0],
|
|
408
|
+
y: node.position[1],
|
|
409
|
+
width: node.size?.[0] ?? 240,
|
|
410
|
+
height: node.size?.[1] ?? 160,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* The group box under a DOCUMENT point, or null.
|
|
416
|
+
*
|
|
417
|
+
* SMALLEST AREA WINS. Boxes do not nest in schema v17, but two of them can
|
|
418
|
+
* still overlap because nothing stops an author dragging one over another, and
|
|
419
|
+
* "the one I am pointing at" is the tighter of the two every time. Ties break
|
|
420
|
+
* on id so a drop is not decided by object order.
|
|
421
|
+
*/
|
|
422
|
+
export function groupUnderPoint(
|
|
423
|
+
graph: MaterialGraph3D,
|
|
424
|
+
path: readonly string[],
|
|
425
|
+
point: readonly [number, number],
|
|
426
|
+
): string | null {
|
|
427
|
+
const scope = path.length === 0 ? undefined : path[path.length - 1];
|
|
428
|
+
let best: { id: string; area: number } | null = null;
|
|
429
|
+
for (const node of Object.values(graph.nodesById)) {
|
|
430
|
+
if (node.kind !== "group") continue;
|
|
431
|
+
if ((node.scope ?? undefined) !== scope) continue;
|
|
432
|
+
const rect = groupRect(node);
|
|
433
|
+
if (point[0] < rect.x || point[0] > rect.x + rect.width) continue;
|
|
434
|
+
if (point[1] < rect.y || point[1] > rect.y + rect.height) continue;
|
|
435
|
+
const area = rect.width * rect.height;
|
|
436
|
+
if (best === null || area < best.area || (area === best.area && node.id < best.id)) {
|
|
437
|
+
best = { id: node.id, area };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return best === null ? null : best.id;
|
|
441
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// ONE GRAPH PARAMETER'S VALUES ON EVERY MESH THAT WEARS THE GRAPH: the per-mesh
|
|
2
|
+
// override and the keyframe track, on the whole model's binding and on each
|
|
3
|
+
// submesh slot that binds the graph.
|
|
4
|
+
//
|
|
5
|
+
// A reusable graph's parameter is edited in one place and lives in many: each
|
|
6
|
+
// mesh binding the graph may override it, and may animate it on a track at the
|
|
7
|
+
// binding's own path (`materialGraphParamPath(id, slot)`). Two edits of the
|
|
8
|
+
// parameter reach all of them at once and must agree about where they are:
|
|
9
|
+
// deleting it (every override and track that names it goes too, or the save
|
|
10
|
+
// refuses the orphans) and narrowing its range (a value left outside the new
|
|
11
|
+
// range is refused by the save as well). Both walk the bindings here, so a
|
|
12
|
+
// slot binding is never the one the walk forgot. Pure and React-free.
|
|
13
|
+
import { valueEquals } from "@dailies-studio/engine";
|
|
14
|
+
import type { Value } from "@dailies-studio/engine";
|
|
15
|
+
import {
|
|
16
|
+
isGraphMaterial,
|
|
17
|
+
isMaterialGraphValueForType,
|
|
18
|
+
materialGraphParamPath,
|
|
19
|
+
parseMaterialSlotKey,
|
|
20
|
+
} from "@dailies-studio/shot/doc";
|
|
21
|
+
import type {
|
|
22
|
+
Doc3D,
|
|
23
|
+
Layer3D,
|
|
24
|
+
MaterialGraphBinding,
|
|
25
|
+
MaterialGraphParameter,
|
|
26
|
+
MaterialGraphValue,
|
|
27
|
+
ShotTrack,
|
|
28
|
+
ShotTracks,
|
|
29
|
+
} from "@dailies-studio/shot/doc";
|
|
30
|
+
|
|
31
|
+
/** One binding's half of a parameter: its override and its track. */
|
|
32
|
+
export interface ParameterValues {
|
|
33
|
+
override: MaterialGraphValue | undefined;
|
|
34
|
+
track: ShotTrack | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A new override and track for a binding's old ones. Returning the inputs
|
|
38
|
+
* (by identity) changes nothing; undefined drops that half. */
|
|
39
|
+
type Rewrite = (values: ParameterValues) => ParameterValues;
|
|
40
|
+
|
|
41
|
+
type Overrides = Record<string, MaterialGraphValue> | undefined;
|
|
42
|
+
|
|
43
|
+
function rewriteLayer(layer: Layer3D, materialId: string, parameterId: string, rewrite: Rewrite): Layer3D {
|
|
44
|
+
if (layer.kind !== "mesh" || !isGraphMaterial(layer.mesh.material)) return layer;
|
|
45
|
+
const binding = layer.mesh.material;
|
|
46
|
+
let tracks: ShotTracks | undefined = layer.tracks;
|
|
47
|
+
let tracksMoved = false;
|
|
48
|
+
|
|
49
|
+
/** The binding's overrides after the rewrite (the same object when its
|
|
50
|
+
* override did not move), with the track written into `tracks`. */
|
|
51
|
+
const visit = (slot: number | null, overrides: Overrides): Overrides => {
|
|
52
|
+
const path = materialGraphParamPath(parameterId, slot);
|
|
53
|
+
const before: ParameterValues = { override: overrides?.[parameterId], track: tracks?.[path] };
|
|
54
|
+
const after = rewrite(before);
|
|
55
|
+
if (after.track !== before.track) {
|
|
56
|
+
const next: ShotTracks = { ...tracks };
|
|
57
|
+
if (after.track === undefined) delete next[path];
|
|
58
|
+
else next[path] = after.track;
|
|
59
|
+
tracks = Object.keys(next).length === 0 ? undefined : next;
|
|
60
|
+
tracksMoved = true;
|
|
61
|
+
}
|
|
62
|
+
if (after.override === before.override) return overrides;
|
|
63
|
+
const next = { ...overrides };
|
|
64
|
+
if (after.override === undefined) delete next[parameterId];
|
|
65
|
+
else next[parameterId] = after.override;
|
|
66
|
+
return Object.keys(next).length === 0 ? undefined : next;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
let material: MaterialGraphBinding = binding;
|
|
70
|
+
// The whole model's binding, only when it is THIS graph: another graph may
|
|
71
|
+
// hold a parameter with the same id, and its path is this one's too.
|
|
72
|
+
if (binding.materialId === materialId) {
|
|
73
|
+
const overrides = visit(null, binding.overrides);
|
|
74
|
+
if (overrides !== binding.overrides) {
|
|
75
|
+
const { overrides: _old, ...rest } = binding;
|
|
76
|
+
material = overrides === undefined ? rest : { ...rest, overrides };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (binding.slots !== undefined) {
|
|
80
|
+
let slots = binding.slots;
|
|
81
|
+
for (const key of Object.keys(binding.slots)) {
|
|
82
|
+
const index = parseMaterialSlotKey(key);
|
|
83
|
+
const slot = Object.prototype.hasOwnProperty.call(binding.slots, key) ? binding.slots[key] : undefined;
|
|
84
|
+
if (index === null || slot === undefined || slot.materialId !== materialId) continue;
|
|
85
|
+
const overrides = visit(index, slot.overrides);
|
|
86
|
+
if (overrides === slot.overrides) continue;
|
|
87
|
+
if (slots === binding.slots) slots = { ...binding.slots };
|
|
88
|
+
slots[key] = overrides === undefined ? { materialId: slot.materialId } : { materialId: slot.materialId, overrides };
|
|
89
|
+
}
|
|
90
|
+
if (slots !== binding.slots) material = { ...material, slots };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (material === binding && !tracksMoved) return layer;
|
|
94
|
+
const next: Layer3D = { ...layer, mesh: { ...layer.mesh, material } };
|
|
95
|
+
if (!tracksMoved) return next;
|
|
96
|
+
if (tracks !== undefined) return { ...next, tracks };
|
|
97
|
+
const { tracks: _dropped, ...bare } = next;
|
|
98
|
+
return bare as Layer3D;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function rewriteLayers(
|
|
102
|
+
layers: Readonly<Record<string, Layer3D>>,
|
|
103
|
+
materialId: string,
|
|
104
|
+
parameterId: string,
|
|
105
|
+
rewrite: Rewrite,
|
|
106
|
+
): Readonly<Record<string, Layer3D>> {
|
|
107
|
+
let out: Record<string, Layer3D> | null = null;
|
|
108
|
+
for (const [id, layer] of Object.entries(layers)) {
|
|
109
|
+
const next = rewriteLayer(layer, materialId, parameterId, rewrite);
|
|
110
|
+
if (next === layer) continue;
|
|
111
|
+
out ??= { ...layers };
|
|
112
|
+
out[id] = next;
|
|
113
|
+
}
|
|
114
|
+
return out ?? layers;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The document with `rewrite` applied to one parameter's override and track
|
|
119
|
+
* on every binding of `materialId`, in every Shot and every definition. The
|
|
120
|
+
* same document when nothing moved.
|
|
121
|
+
*/
|
|
122
|
+
export function rewriteParameterValues(doc: Doc3D, materialId: string, parameterId: string, rewrite: Rewrite): Doc3D {
|
|
123
|
+
let scenesById: Doc3D["scenesById"] | null = null;
|
|
124
|
+
for (const [id, scene] of Object.entries(doc.scenesById)) {
|
|
125
|
+
const layersById = rewriteLayers(scene.layersById, materialId, parameterId, rewrite);
|
|
126
|
+
if (layersById === scene.layersById) continue;
|
|
127
|
+
scenesById ??= { ...doc.scenesById };
|
|
128
|
+
scenesById[id] = { ...scene, layersById: layersById as Record<string, Layer3D> };
|
|
129
|
+
}
|
|
130
|
+
let components: NonNullable<Doc3D["components"]> | null = null;
|
|
131
|
+
for (const [id, def] of Object.entries(doc.components ?? {})) {
|
|
132
|
+
const layersById = rewriteLayers(def.layersById, materialId, parameterId, rewrite);
|
|
133
|
+
if (layersById === def.layersById) continue;
|
|
134
|
+
components ??= { ...doc.components };
|
|
135
|
+
components[id] = { ...def, layersById: layersById as Record<string, Layer3D> };
|
|
136
|
+
}
|
|
137
|
+
if (scenesById === null && components === null) return doc;
|
|
138
|
+
return { ...doc, ...(scenesById === null ? {} : { scenesById }), ...(components === null ? {} : { components }) };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** A float parameter's range; an absent end is open. */
|
|
142
|
+
export interface ParameterRange {
|
|
143
|
+
min?: number;
|
|
144
|
+
max?: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function clampTo(value: number, range: ParameterRange): number {
|
|
148
|
+
return Math.max(range.min ?? -Infinity, Math.min(range.max ?? Infinity, value));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function outside(value: unknown, range: ParameterRange): boolean {
|
|
152
|
+
return typeof value === "number" && clampTo(value, range) !== value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** How many per-mesh values (overrides and keyframes) of a float parameter
|
|
156
|
+
* sit outside `range`: what a narrowed range would leave the save to refuse. */
|
|
157
|
+
export function valuesOutsideRange(doc: Doc3D, materialId: string, parameterId: string, range: ParameterRange): number {
|
|
158
|
+
let count = 0;
|
|
159
|
+
rewriteParameterValues(doc, materialId, parameterId, (values) => {
|
|
160
|
+
if (outside(values.override, range)) count += 1;
|
|
161
|
+
for (const key of values.track?.keyframes ?? []) if (outside(key.value, range)) count += 1;
|
|
162
|
+
return values;
|
|
163
|
+
});
|
|
164
|
+
return count;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** A parameter with its range set (an absent end is open) and its default
|
|
168
|
+
* moved inside it, which the graph's own check requires. The same object
|
|
169
|
+
* when nothing moves. */
|
|
170
|
+
export function withParameterRange(parameter: MaterialGraphParameter, range: ParameterRange): MaterialGraphParameter {
|
|
171
|
+
const fallback =
|
|
172
|
+
typeof parameter.default === "number" ? clampTo(parameter.default, range) : parameter.default;
|
|
173
|
+
if (parameter.min === range.min && parameter.max === range.max && fallback === parameter.default) return parameter;
|
|
174
|
+
const { min: _min, max: _max, ...rest } = parameter;
|
|
175
|
+
return {
|
|
176
|
+
...rest,
|
|
177
|
+
...(range.min === undefined ? {} : { min: range.min }),
|
|
178
|
+
...(range.max === undefined ? {} : { max: range.max }),
|
|
179
|
+
default: fallback,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* A parameter's DEFAULT written for the length of a drag: the narrow pure
|
|
185
|
+
* write a scrub re-derives from its base on every move (`writeMaterialNodeValue`'s
|
|
186
|
+
* precedent). It types the value by the parameter and keeps a float inside its
|
|
187
|
+
* range, so it can never change the graph's shape or break its own check; a
|
|
188
|
+
* typed value crosses `updateProjectMaterial`'s validated door instead.
|
|
189
|
+
*/
|
|
190
|
+
export function writeParameterDefault(
|
|
191
|
+
doc: Doc3D,
|
|
192
|
+
materialId: string,
|
|
193
|
+
parameterId: string,
|
|
194
|
+
value: unknown,
|
|
195
|
+
): Doc3D {
|
|
196
|
+
const graph = doc.materials?.[materialId];
|
|
197
|
+
if (graph === undefined || !Object.prototype.hasOwnProperty.call(graph.parameters, parameterId)) return doc;
|
|
198
|
+
const parameter = graph.parameters[parameterId]!;
|
|
199
|
+
if (!isMaterialGraphValueForType(value, parameter.type)) return doc;
|
|
200
|
+
const next: MaterialGraphValue =
|
|
201
|
+
typeof value === "number"
|
|
202
|
+
? clampTo(value, {
|
|
203
|
+
...(parameter.min === undefined ? {} : { min: parameter.min }),
|
|
204
|
+
...(parameter.max === undefined ? {} : { max: parameter.max }),
|
|
205
|
+
})
|
|
206
|
+
: value;
|
|
207
|
+
if (valueEquals(parameter.default as Value, next as Value)) return doc;
|
|
208
|
+
return {
|
|
209
|
+
...doc,
|
|
210
|
+
materials: {
|
|
211
|
+
...doc.materials,
|
|
212
|
+
[materialId]: { ...graph, parameters: { ...graph.parameters, [parameterId]: { ...parameter, default: next } } },
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** The document with every per-mesh override and keyframe value of a float
|
|
218
|
+
* parameter clamped into `range`. */
|
|
219
|
+
export function clampParameterValues(doc: Doc3D, materialId: string, parameterId: string, range: ParameterRange): Doc3D {
|
|
220
|
+
return rewriteParameterValues(doc, materialId, parameterId, ({ override, track }) => {
|
|
221
|
+
const clampedOverride = typeof override === "number" ? clampTo(override, range) : override;
|
|
222
|
+
let clampedTrack = track;
|
|
223
|
+
if (track !== undefined && track.keyframes.some((key) => outside(key.value, range))) {
|
|
224
|
+
clampedTrack = {
|
|
225
|
+
...track,
|
|
226
|
+
keyframes: track.keyframes.map((key) => (outside(key.value, range) ? { ...key, value: clampTo(key.value as number, range) } : key)),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return { override: clampedOverride, track: clampedTrack };
|
|
230
|
+
});
|
|
231
|
+
}
|