@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,501 @@
|
|
|
1
|
+
// THE MATERIAL MODE: the node editor, in the docked frame.
|
|
2
|
+
//
|
|
3
|
+
// It is chrome-only. Nodes remain plain shot data and the mounted StagePanel
|
|
4
|
+
// stays exactly where it is: this draws beside it and leaves one hole for it to
|
|
5
|
+
// render the isolated object into. `closeMaterialWorkspace` puts back the
|
|
6
|
+
// layout, the shading, the tool, the isolation and the selection the entry
|
|
7
|
+
// borrowed.
|
|
8
|
+
//
|
|
9
|
+
// ── WHERE EACH PART LANDS ───────────────────────────────────────────────
|
|
10
|
+
//
|
|
11
|
+
// One component, portalled into the shell's cells (panels/stage/cellSlots.tsx),
|
|
12
|
+
// so its state and the graph's React Flow provider stay one React subtree:
|
|
13
|
+
//
|
|
14
|
+
// LEFT the Project's graphs and the starters (MaterialLibrary.tsx);
|
|
15
|
+
// BAR the way back, the trail, the slot, the compile status, then the
|
|
16
|
+
// TSL toggle, the graph's zoom and Recenter pushed right;
|
|
17
|
+
// CENTRE the graph (or its TSL source), a seam, and the object's HOLE at
|
|
18
|
+
// the right of the canvas cell. The setup screen instead, while
|
|
19
|
+
// the mesh has no graph.
|
|
20
|
+
//
|
|
21
|
+
// The top bar, the inspector and the timeline stay on screen around it. The
|
|
22
|
+
// material's own rows (name, graph, exposed controls) are the inspector's
|
|
23
|
+
// MaterialSection: an exposed control is a per-mesh value with a keyframe
|
|
24
|
+
// dot, authored against the playhead, and the timeline under the graph is
|
|
25
|
+
// where it is scrubbed.
|
|
26
|
+
//
|
|
27
|
+
// ── WHY THE NODE INSPECTOR IS NOT HERE ──────────────────────────────────
|
|
28
|
+
//
|
|
29
|
+
// It showed ONE selected node's value away from the card the author was
|
|
30
|
+
// looking at, and it was the only way to type a number. Every field it had is
|
|
31
|
+
// on the card itself now (`MaterialNode.tsx`), which also removed a second
|
|
32
|
+
// commit path into a node's value.
|
|
33
|
+
//
|
|
34
|
+
// ── AND WHY THE SLOT PICKER IS IN THE BAR ───────────────────────────────
|
|
35
|
+
//
|
|
36
|
+
// A model ships one material per submesh, and schema v17 lets each carry its
|
|
37
|
+
// own graph. Which one is on the canvas is a property of the WORKSPACE and not
|
|
38
|
+
// of the document, exactly like the trail beside it, so it sits in the same
|
|
39
|
+
// row, and everything below is resolved through it.
|
|
40
|
+
import { lazy, Suspense, useRef, useState } from "react";
|
|
41
|
+
import { Combobox } from "@dailies-studio/ui";
|
|
42
|
+
import {
|
|
43
|
+
emitMaterialGraphTsl,
|
|
44
|
+
homeOf,
|
|
45
|
+
isGraphMaterial,
|
|
46
|
+
materialBindingForSlot,
|
|
47
|
+
validateMaterialGraph,
|
|
48
|
+
} from "@dailies-studio/shot/doc";
|
|
49
|
+
import type { MaterialGraph3D, MeshMaterialSlotInfo } from "@dailies-studio/shot/doc";
|
|
50
|
+
import { CHEVRON_LEFT_ICON } from "../../icons.js";
|
|
51
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
52
|
+
import { MATERIAL_COLUMN } from "../../state/panelSize.js";
|
|
53
|
+
import { shortcutHint } from "../../state/shortcuts.js";
|
|
54
|
+
import { workspaceOf } from "../../state/workspace.js";
|
|
55
|
+
import { ResizeHandle } from "../ResizeHandle.js";
|
|
56
|
+
import { InCell } from "../stage/cellSlots.js";
|
|
57
|
+
import { MATERIAL_STARTERS, MaterialLibrary } from "./MaterialLibrary.js";
|
|
58
|
+
|
|
59
|
+
const LazyMaterialGraphCanvas = lazy(async () => {
|
|
60
|
+
const module = await import("./MaterialGraphCanvas.js");
|
|
61
|
+
return { default: module.MaterialGraphCanvas };
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/** How wide the preview hole may be: half the canvas cell, read LIVE at the
|
|
65
|
+
* drag. Undefined when the cell has no box yet (nothing laid out). */
|
|
66
|
+
function previewCeiling(centre: HTMLElement | null): number | undefined {
|
|
67
|
+
const width = centre?.getBoundingClientRect().width ?? 0;
|
|
68
|
+
return width > 0 ? width / 2 : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function MaterialWorkspace() {
|
|
72
|
+
const store = useStore();
|
|
73
|
+
// THE MODE PORTALS ONLY IN ITS OWN WORKSPACE, so two modes can never fill
|
|
74
|
+
// one cell.
|
|
75
|
+
const own = useShotEditor((state) => workspaceOf(state) === "material");
|
|
76
|
+
const workspace = useShotEditor((state) => state.materialWorkspace);
|
|
77
|
+
const doc = useShotEditor((state) => state.doc);
|
|
78
|
+
const name = useShotEditor((state) => state.name);
|
|
79
|
+
const materialCompile = useShotEditor((state) => state.materialCompile);
|
|
80
|
+
const path = useShotEditor((state) => state.materialGraphPath);
|
|
81
|
+
const slots = useShotEditor((state) =>
|
|
82
|
+
state.materialWorkspace === null ? undefined : state.meshMaterialSlots[state.materialWorkspace.layerId],
|
|
83
|
+
);
|
|
84
|
+
// The camera is derived until the author orbits it, and a drag on the hole
|
|
85
|
+
// always orbits. Recenter is the visible way back, offered exactly when it
|
|
86
|
+
// is true.
|
|
87
|
+
const orbited = useShotEditor((state) => state.isolationOrbit);
|
|
88
|
+
const [sourceOpen, setSourceOpen] = useState(false);
|
|
89
|
+
// The place in the bar the graph's zoom is portalled into, from INSIDE the
|
|
90
|
+
// lazy canvas's React Flow provider (MaterialGraphCanvas.tsx). A state and
|
|
91
|
+
// not a ref, so the canvas renders again once the place exists.
|
|
92
|
+
const [zoomSlot, setZoomSlot] = useState<HTMLElement | null>(null);
|
|
93
|
+
const centreRef = useRef<HTMLElement | null>(null);
|
|
94
|
+
const holeRef = useRef<HTMLDivElement | null>(null);
|
|
95
|
+
|
|
96
|
+
if (!own || workspace === null || doc === null) return null;
|
|
97
|
+
const layer = homeOf(doc, workspace.containerId)?.layersById[workspace.layerId];
|
|
98
|
+
if (layer?.kind !== "mesh") return null;
|
|
99
|
+
// THROUGH THE SLOT. `workspaceMaterialId` in the store resolves it the same
|
|
100
|
+
// way for the keymap; both call `materialBindingForSlot` rather than each
|
|
101
|
+
// reading `material.slots` themselves.
|
|
102
|
+
const binding = isGraphMaterial(layer.mesh.material)
|
|
103
|
+
? materialBindingForSlot(layer.mesh.material, workspace.slot)
|
|
104
|
+
: null;
|
|
105
|
+
const graph = binding === null ? undefined : doc.materials?.[binding.materialId];
|
|
106
|
+
const layerName = layer.label?.trim() || "Mesh";
|
|
107
|
+
const projectName = doc.title ?? name ?? doc.name;
|
|
108
|
+
const contextName =
|
|
109
|
+
doc.scenesById[workspace.containerId]?.name ?? doc.components?.[workspace.containerId]?.name ?? "Shot";
|
|
110
|
+
|
|
111
|
+
const problems = graph === undefined ? [] : validateMaterialGraph(graph, doc.assets);
|
|
112
|
+
// No slot test here: the store already resolves the verdict for the open
|
|
113
|
+
// slot (`cachedMaterialCompile`), and a slot without its own graph shows the
|
|
114
|
+
// whole model's verdict, which the stage reports under slot null.
|
|
115
|
+
const compile =
|
|
116
|
+
graph !== undefined &&
|
|
117
|
+
materialCompile.status !== "idle" &&
|
|
118
|
+
materialCompile.materialId === graph.id &&
|
|
119
|
+
materialCompile.layerId === layer.id
|
|
120
|
+
? materialCompile
|
|
121
|
+
: { status: "idle" as const };
|
|
122
|
+
const compileLabel =
|
|
123
|
+
problems.length > 0
|
|
124
|
+
? `${problems.length} issue${problems.length === 1 ? "" : "s"}`
|
|
125
|
+
: compile.status === "compiling"
|
|
126
|
+
? "Compiling…"
|
|
127
|
+
: compile.status === "error"
|
|
128
|
+
? "GPU error"
|
|
129
|
+
: compile.status === "ready"
|
|
130
|
+
? "Live"
|
|
131
|
+
: "Waiting";
|
|
132
|
+
const compileTitle =
|
|
133
|
+
problems[0]?.message ??
|
|
134
|
+
(compile.status === "error"
|
|
135
|
+
? compile.message
|
|
136
|
+
: compile.status === "compiling"
|
|
137
|
+
? "The stage is compiling this material graph."
|
|
138
|
+
: compile.status === "ready"
|
|
139
|
+
? "The stage compiled this graph and is previewing it on the isolated mesh."
|
|
140
|
+
: "Waiting for the stage compiler to report this material.");
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<>
|
|
144
|
+
<InCell name="left">
|
|
145
|
+
<MaterialLibrary layer={layer} layerName={layerName} currentId={graph?.id ?? null} slot={workspace.slot} />
|
|
146
|
+
</InCell>
|
|
147
|
+
|
|
148
|
+
<InCell name="bar">
|
|
149
|
+
<div className="material-bar">
|
|
150
|
+
{/* THE WAY OUT, first in the bar like every mode's. The trail's upper
|
|
151
|
+
rungs leave too; this is the one that is always in the same place. */}
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
className="back-chip material-back"
|
|
155
|
+
data-testid="material-back"
|
|
156
|
+
aria-label="Close material nodes"
|
|
157
|
+
title="Close material nodes (Esc)"
|
|
158
|
+
onClick={() => store.closeMaterialWorkspace()}
|
|
159
|
+
>
|
|
160
|
+
<span className="tool-icon" aria-hidden="true">
|
|
161
|
+
{CHEVRON_LEFT_ICON}
|
|
162
|
+
</span>
|
|
163
|
+
</button>
|
|
164
|
+
<MaterialCrumbs
|
|
165
|
+
projectName={projectName}
|
|
166
|
+
contextName={contextName}
|
|
167
|
+
layerName={layerName}
|
|
168
|
+
graphName={graph?.name ?? "Material"}
|
|
169
|
+
functionNames={path.map((id) => graph?.functions?.[id]?.name ?? id)}
|
|
170
|
+
/>
|
|
171
|
+
<SlotPicker slots={slots} slot={workspace.slot} />
|
|
172
|
+
{graph === undefined ? null : (
|
|
173
|
+
<span
|
|
174
|
+
className={`material-compile-status${problems.length > 0 || compile.status === "error" ? " is-error" : ""}${compile.status === "compiling" ? " is-compiling" : ""}`}
|
|
175
|
+
role="status"
|
|
176
|
+
title={compileTitle}
|
|
177
|
+
>
|
|
178
|
+
<span aria-hidden="true" />
|
|
179
|
+
{compileLabel}
|
|
180
|
+
</span>
|
|
181
|
+
)}
|
|
182
|
+
{graph === undefined ? null : (
|
|
183
|
+
<div className="material-bar-end">
|
|
184
|
+
{/* The visible opener; Tab, Shift+A and a double-click on the
|
|
185
|
+
pane open the same palette (MaterialNodePalette.tsx). */}
|
|
186
|
+
<button
|
|
187
|
+
type="button"
|
|
188
|
+
className="btn"
|
|
189
|
+
data-testid="material-add-node"
|
|
190
|
+
title={`Add node${shortcutHint("graph.add")}`}
|
|
191
|
+
onClick={() => store.openMaterialPalette(null)}
|
|
192
|
+
>
|
|
193
|
+
Add node
|
|
194
|
+
</button>
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
className={`btn${sourceOpen ? " is-active" : ""}`}
|
|
198
|
+
aria-pressed={sourceOpen}
|
|
199
|
+
onClick={() => setSourceOpen((open) => !open)}
|
|
200
|
+
>
|
|
201
|
+
TSL
|
|
202
|
+
</button>
|
|
203
|
+
{/* Filled by the canvas while the graph is on screen; takes no
|
|
204
|
+
room of its own (`display: contents`). */}
|
|
205
|
+
<div className="material-zoom-slot" ref={setZoomSlot} />
|
|
206
|
+
{/* THE WAY BACK for an orbited preview. Nothing re-derives an
|
|
207
|
+
orbited camera, by design, and the hole takes no pointer (a
|
|
208
|
+
drag on it reaches the stage), so a control over it could not
|
|
209
|
+
be pressed. The stage owns the double-click (`onFitRequest`). */}
|
|
210
|
+
{orbited ? (
|
|
211
|
+
<button
|
|
212
|
+
type="button"
|
|
213
|
+
className="btn"
|
|
214
|
+
data-testid="material-refit"
|
|
215
|
+
title="Frame the object head-on again (Home)"
|
|
216
|
+
onClick={() => store.fitWorkingView()}
|
|
217
|
+
>
|
|
218
|
+
Recenter
|
|
219
|
+
</button>
|
|
220
|
+
) : null}
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
</InCell>
|
|
225
|
+
|
|
226
|
+
<InCell name="centre">
|
|
227
|
+
<section
|
|
228
|
+
className="material-workspace"
|
|
229
|
+
aria-label={`Material graph for ${layerName}`}
|
|
230
|
+
ref={centreRef}
|
|
231
|
+
>
|
|
232
|
+
{graph === undefined ? (
|
|
233
|
+
<MaterialSetup
|
|
234
|
+
layerId={layer.id}
|
|
235
|
+
legacy={layer.mesh.material !== undefined && !isGraphMaterial(layer.mesh.material)}
|
|
236
|
+
/>
|
|
237
|
+
) : (
|
|
238
|
+
<>
|
|
239
|
+
<div className="material-graph-region">
|
|
240
|
+
{/* THE GPU'S OWN WORDS, on screen and not only in the pill's
|
|
241
|
+
tooltip, which a touch user cannot reach. Validator problems
|
|
242
|
+
need no strip: their node cards already show them. */}
|
|
243
|
+
{compile.status === "error" ? (
|
|
244
|
+
<div className="code-studio-strip is-error" role="alert" data-testid="material-compile-error">
|
|
245
|
+
{compile.message}
|
|
246
|
+
</div>
|
|
247
|
+
) : null}
|
|
248
|
+
{sourceOpen ? (
|
|
249
|
+
<SourceView graph={graph} />
|
|
250
|
+
) : (
|
|
251
|
+
<Suspense
|
|
252
|
+
fallback={
|
|
253
|
+
<section className="material-graph-canvas is-loading" aria-label="Loading material node graph">
|
|
254
|
+
<p className="material-empty-copy">Loading node canvas…</p>
|
|
255
|
+
</section>
|
|
256
|
+
}
|
|
257
|
+
>
|
|
258
|
+
<LazyMaterialGraphCanvas
|
|
259
|
+
graph={graph}
|
|
260
|
+
materialId={graph.id}
|
|
261
|
+
layerId={layer.id}
|
|
262
|
+
slot={workspace.slot}
|
|
263
|
+
zoomSlot={zoomSlot}
|
|
264
|
+
/>
|
|
265
|
+
</Suspense>
|
|
266
|
+
)}
|
|
267
|
+
</div>
|
|
268
|
+
{/* THE OBJECT, AND NOTHING ELSE IN HERE WITH IT.
|
|
269
|
+
A HOLE and not a component: the stage iframe behind the shell
|
|
270
|
+
is still rendering the isolated layer, and `measureInset` hands
|
|
271
|
+
it this element's rect to frame into. So what shows here is the
|
|
272
|
+
real object under the real material, which is the only reason
|
|
273
|
+
this mode can claim preview === export. `.stage-window` is the
|
|
274
|
+
class `measureInset` LOOKS FOR, shared with every mode's hole.
|
|
275
|
+
An `aria-hidden` empty div, because a screen reader has nothing
|
|
276
|
+
to read in a GL surface this document does not own. */}
|
|
277
|
+
<div
|
|
278
|
+
className="stage-window material-preview-hole"
|
|
279
|
+
aria-hidden="true"
|
|
280
|
+
data-testid="material-preview-hole"
|
|
281
|
+
ref={holeRef}
|
|
282
|
+
/>
|
|
283
|
+
{/* ON THE SEAM, beside the hole it sizes, from the same variable
|
|
284
|
+
(`--material-preview-w`). A drag starts from the width the hole
|
|
285
|
+
is DRAWN at and is capped by half the live canvas cell, so the
|
|
286
|
+
edge moves with the pointer from the first pixel. */}
|
|
287
|
+
<ResizeHandle
|
|
288
|
+
axis="x"
|
|
289
|
+
getValue={() =>
|
|
290
|
+
holeRef.current?.getBoundingClientRect().width || store.getState().materialColumnWidth
|
|
291
|
+
}
|
|
292
|
+
// The hole is on the RIGHT, so moving the pointer along +x
|
|
293
|
+
// shrinks it: the inspector's rule.
|
|
294
|
+
sign={-1}
|
|
295
|
+
onChange={(px) => store.setMaterialColumnWidth(px, previewCeiling(centreRef.current))}
|
|
296
|
+
onReset={() => store.setMaterialColumnWidth(MATERIAL_COLUMN.default)}
|
|
297
|
+
className="material-workspace-resize"
|
|
298
|
+
label="Resize the material preview"
|
|
299
|
+
/>
|
|
300
|
+
</>
|
|
301
|
+
)}
|
|
302
|
+
</section>
|
|
303
|
+
</InCell>
|
|
304
|
+
</>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The trail: Project › Shot › Mesh › Material › function › function.
|
|
310
|
+
*
|
|
311
|
+
* The first three rungs are buttons, and each is a way out.
|
|
312
|
+
*
|
|
313
|
+
* The first three rungs all close the workspace, because that is what going
|
|
314
|
+
* back up from a shader means: `closeMaterialWorkspace` restores the viewport,
|
|
315
|
+
* the tool and the selection the entry borrowed. The Project rung additionally
|
|
316
|
+
* leaves the shot, which is the one place the two differ.
|
|
317
|
+
*/
|
|
318
|
+
function MaterialCrumbs({
|
|
319
|
+
projectName,
|
|
320
|
+
contextName,
|
|
321
|
+
layerName,
|
|
322
|
+
graphName,
|
|
323
|
+
functionNames,
|
|
324
|
+
}: {
|
|
325
|
+
projectName: string;
|
|
326
|
+
contextName: string;
|
|
327
|
+
layerName: string;
|
|
328
|
+
graphName: string;
|
|
329
|
+
functionNames: readonly string[];
|
|
330
|
+
}) {
|
|
331
|
+
const store = useStore();
|
|
332
|
+
const depth = functionNames.length;
|
|
333
|
+
const leave = (): void => store.closeMaterialWorkspace();
|
|
334
|
+
return (
|
|
335
|
+
<nav className="material-workspace-crumbs" data-testid="material-crumbs">
|
|
336
|
+
<button
|
|
337
|
+
type="button"
|
|
338
|
+
className="crumb"
|
|
339
|
+
title="The whole Project"
|
|
340
|
+
onClick={() => {
|
|
341
|
+
leave();
|
|
342
|
+
store.leaveScene();
|
|
343
|
+
}}
|
|
344
|
+
>
|
|
345
|
+
{projectName}
|
|
346
|
+
</button>
|
|
347
|
+
<button type="button" className="crumb" onClick={leave}>
|
|
348
|
+
{contextName}
|
|
349
|
+
</button>
|
|
350
|
+
<button type="button" className="crumb" onClick={leave}>
|
|
351
|
+
{layerName}
|
|
352
|
+
</button>
|
|
353
|
+
<button
|
|
354
|
+
type="button"
|
|
355
|
+
className={`crumb${depth === 0 ? " is-current" : ""}`}
|
|
356
|
+
aria-current={depth === 0}
|
|
357
|
+
// ONE `exitMaterialSubgraph` PER LEVEL rather than a "go to root" verb,
|
|
358
|
+
// because the store's rung is one level and adding a second spelling of
|
|
359
|
+
// the same climb is how the two end up disagreeing about what clears.
|
|
360
|
+
onClick={() => {
|
|
361
|
+
for (let level = 0; level < depth; level += 1) store.exitMaterialSubgraph();
|
|
362
|
+
}}
|
|
363
|
+
>
|
|
364
|
+
{graphName}
|
|
365
|
+
</button>
|
|
366
|
+
{functionNames.map((fnName, index) => (
|
|
367
|
+
<button
|
|
368
|
+
key={`${index}:${fnName}`}
|
|
369
|
+
type="button"
|
|
370
|
+
className={`crumb${index === depth - 1 ? " is-current" : ""}`}
|
|
371
|
+
aria-current={index === depth - 1}
|
|
372
|
+
onClick={() => {
|
|
373
|
+
for (let level = index; level < depth - 1; level += 1) store.exitMaterialSubgraph();
|
|
374
|
+
}}
|
|
375
|
+
>
|
|
376
|
+
{fnName}
|
|
377
|
+
</button>
|
|
378
|
+
))}
|
|
379
|
+
</nav>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** The rail's value for the layer-wide binding. Not a slot index, so it cannot
|
|
384
|
+
* collide with one. */
|
|
385
|
+
const WHOLE_MODEL = "all";
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Which of a model's materials is on the canvas.
|
|
389
|
+
*
|
|
390
|
+
* ABSENT when the mesh has no separate materials, which is every primitive and
|
|
391
|
+
* every model with one: a dropdown with a single unchangeable row is furniture
|
|
392
|
+
* that says nothing. "Whole model" is first and is the default, because it is
|
|
393
|
+
* the binding a mesh has before anybody picks a submesh and the only one a
|
|
394
|
+
* primitive can have.
|
|
395
|
+
*/
|
|
396
|
+
function SlotPicker({
|
|
397
|
+
slots,
|
|
398
|
+
slot,
|
|
399
|
+
}: {
|
|
400
|
+
slots: readonly MeshMaterialSlotInfo[] | undefined;
|
|
401
|
+
slot: number | null;
|
|
402
|
+
}) {
|
|
403
|
+
const store = useStore();
|
|
404
|
+
if (slots === undefined || slots.length === 0) return null;
|
|
405
|
+
return (
|
|
406
|
+
<Combobox
|
|
407
|
+
className="material-slot-picker"
|
|
408
|
+
// `WHOLE` and not the empty string, which `Combobox` reads as "no value"
|
|
409
|
+
// and renders as a placeholder, so the layer-wide binding (the default
|
|
410
|
+
// and the commonest state) showed as a blank control.
|
|
411
|
+
value={slot === null ? WHOLE_MODEL : String(slot)}
|
|
412
|
+
ariaLabel="Material slot"
|
|
413
|
+
options={[
|
|
414
|
+
{ value: WHOLE_MODEL, label: "Whole model" },
|
|
415
|
+
...slots.map((entry) => ({
|
|
416
|
+
value: String(entry.index),
|
|
417
|
+
label: `${entry.index} · ${entry.name.trim() === "" ? "Material" : entry.name}`,
|
|
418
|
+
})),
|
|
419
|
+
]}
|
|
420
|
+
onChange={(value) => store.setMaterialSlot(value === WHOLE_MODEL ? null : Number(value))}
|
|
421
|
+
/>
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* The centre while the mesh has no graph: what this place is, and the three
|
|
427
|
+
* ways to start. The graphs the Project already has are one column over, in
|
|
428
|
+
* the library, which offers the same starters as rows.
|
|
429
|
+
*/
|
|
430
|
+
function MaterialSetup({ layerId, legacy }: { layerId: string; legacy: boolean }) {
|
|
431
|
+
const store = useStore();
|
|
432
|
+
return (
|
|
433
|
+
<div className="material-setup">
|
|
434
|
+
<div className="material-setup-intro">
|
|
435
|
+
<span className="material-eyebrow">Material nodes</span>
|
|
436
|
+
<h2>Build the surface on the real object.</h2>
|
|
437
|
+
<p>
|
|
438
|
+
The mesh stays isolated on the stage while nodes describe its TSL material. Start from a neutral model
|
|
439
|
+
here, or pick a graph this Project already has from the library on the left.
|
|
440
|
+
</p>
|
|
441
|
+
{legacy ? (
|
|
442
|
+
<p className="material-legacy-note">
|
|
443
|
+
This mesh uses a legacy catalog material. It will keep rendering until you replace it with a node graph.
|
|
444
|
+
</p>
|
|
445
|
+
) : null}
|
|
446
|
+
</div>
|
|
447
|
+
<section className="material-starters" aria-label="Start a material">
|
|
448
|
+
{MATERIAL_STARTERS.map((starter) => (
|
|
449
|
+
<button
|
|
450
|
+
type="button"
|
|
451
|
+
className="material-starter-card"
|
|
452
|
+
key={starter.id}
|
|
453
|
+
onClick={() => store.createMeshGraphMaterial(layerId, starter.id)}
|
|
454
|
+
>
|
|
455
|
+
<span className={`material-starter-orb is-${starter.id}`} aria-hidden="true" />
|
|
456
|
+
<span>
|
|
457
|
+
<strong>{starter.name}</strong>
|
|
458
|
+
<small>{starter.detail}</small>
|
|
459
|
+
</span>
|
|
460
|
+
</button>
|
|
461
|
+
))}
|
|
462
|
+
</section>
|
|
463
|
+
</div>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** The generated TSL, read-only, in place of the graph. */
|
|
468
|
+
function SourceView({ graph }: { graph: MaterialGraph3D }) {
|
|
469
|
+
const source = sourceOf(graph);
|
|
470
|
+
return (
|
|
471
|
+
<section className="material-source-view" aria-label="Generated TSL source">
|
|
472
|
+
<div className="material-source-heading">
|
|
473
|
+
<div>
|
|
474
|
+
<strong>Generated TSL</strong>
|
|
475
|
+
<span>Read-only reference · runtime compiles the data graph directly</span>
|
|
476
|
+
</div>
|
|
477
|
+
<button
|
|
478
|
+
type="button"
|
|
479
|
+
className="btn"
|
|
480
|
+
onClick={() => void navigator.clipboard?.writeText(source.text)}
|
|
481
|
+
disabled={source.error !== null}
|
|
482
|
+
>
|
|
483
|
+
Copy
|
|
484
|
+
</button>
|
|
485
|
+
</div>
|
|
486
|
+
{source.error === null ? (
|
|
487
|
+
<pre tabIndex={0}>{source.text}</pre>
|
|
488
|
+
) : (
|
|
489
|
+
<div className="material-source-error">{source.error}</div>
|
|
490
|
+
)}
|
|
491
|
+
</section>
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function sourceOf(graph: MaterialGraph3D): { text: string; error: string | null } {
|
|
496
|
+
try {
|
|
497
|
+
return { text: emitMaterialGraphTsl(graph), error: null };
|
|
498
|
+
} catch (error) {
|
|
499
|
+
return { text: "", error: error instanceof Error ? error.message : "Unable to generate TSL." };
|
|
500
|
+
}
|
|
501
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// The live thumbnail on a card, and the context that keeps it off every other
|
|
2
|
+
// card's props.
|
|
3
|
+
//
|
|
4
|
+
// The stage answers `requestNodePreviews` with `ImageBitmap`s. A bitmap is not
|
|
5
|
+
// a React value: it cannot be compared, it cannot be serialised, and it holds
|
|
6
|
+
// GPU memory until somebody closes it. So it never enters `MaterialFlowData` —
|
|
7
|
+
// see that module's header. It rides a context whose value changes when a batch
|
|
8
|
+
// lands, which re-renders the handful of components that actually read one.
|
|
9
|
+
//
|
|
10
|
+
// Drawn with `drawImage` into a `<canvas>` rather than handed to an `<img>`,
|
|
11
|
+
// because an ImageBitmap has no URL: `createObjectURL` does not take one, and
|
|
12
|
+
// the alternative (encode to a blob per thumbnail) is a second copy of every
|
|
13
|
+
// pixel for a picture that is about to be scaled down anyway.
|
|
14
|
+
import { createContext, useContext, useEffect, useRef } from "react";
|
|
15
|
+
import type { ReactNode } from "react";
|
|
16
|
+
import type { PreviewEntry } from "./previewScheduler.js";
|
|
17
|
+
|
|
18
|
+
const PreviewContext = createContext<ReadonlyMap<string, PreviewEntry>>(new Map());
|
|
19
|
+
|
|
20
|
+
export function MaterialPreviewProvider({
|
|
21
|
+
entries,
|
|
22
|
+
children,
|
|
23
|
+
}: {
|
|
24
|
+
entries: ReadonlyMap<string, PreviewEntry>;
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}) {
|
|
27
|
+
return <PreviewContext.Provider value={entries}>{children}</PreviewContext.Provider>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What the stage last said about one node: a picture, a sentence, or nothing
|
|
31
|
+
* yet. */
|
|
32
|
+
export function useNodePreview(nodeId: string): PreviewEntry | undefined {
|
|
33
|
+
return useContext(PreviewContext).get(nodeId);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The thumbnail edge in CSS px. The request asks for the same number in device
|
|
37
|
+
* px, so a retina card draws a downscaled bitmap rather than a soft one. */
|
|
38
|
+
export const NODE_PREVIEW_PX = 96;
|
|
39
|
+
|
|
40
|
+
export function NodePreview({ nodeId }: { nodeId: string }) {
|
|
41
|
+
const entry = useNodePreview(nodeId);
|
|
42
|
+
const canvas = useRef<HTMLCanvasElement | null>(null);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const el = canvas.current;
|
|
46
|
+
if (el === null || entry === undefined || typeof entry === "string") return;
|
|
47
|
+
const context = el.getContext("2d");
|
|
48
|
+
if (context === null) return;
|
|
49
|
+
// The bitmap arrives at whatever size the stage rendered; the card decides
|
|
50
|
+
// how big it is drawn. `clearRect` first, because a batch that failed for
|
|
51
|
+
// this node leaves the previous picture behind otherwise.
|
|
52
|
+
context.clearRect(0, 0, el.width, el.height);
|
|
53
|
+
context.drawImage(entry, 0, 0, el.width, el.height);
|
|
54
|
+
}, [entry]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="material-node-preview" aria-hidden={typeof entry !== "string"}>
|
|
58
|
+
{typeof entry === "string" ? (
|
|
59
|
+
<span className="material-node-preview-message">{entry}</span>
|
|
60
|
+
) : (
|
|
61
|
+
<canvas
|
|
62
|
+
ref={canvas}
|
|
63
|
+
className={`material-node-preview-canvas${entry === undefined ? " is-waiting" : ""}`}
|
|
64
|
+
width={NODE_PREVIEW_PX}
|
|
65
|
+
height={NODE_PREVIEW_PX}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|