@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,251 @@
|
|
|
1
|
+
// ONE SHOT CARD on the board (Paper SB1): one planned shot, or one Shot no
|
|
2
|
+
// plan links, read in playing order (`storyEntries`). A storyboard panel:
|
|
3
|
+
//
|
|
4
|
+
// picture the built frame once "Mark shot built" ran (with a Built
|
|
5
|
+
// badge), else Sketch 1 as the still stage draws it (`MiniSketch`
|
|
6
|
+
// with `useSketchThumbs`, drawn in the DOM until that is ready)
|
|
7
|
+
// with one dot per sketch to peek at the others, else the Shot's
|
|
8
|
+
// first still, else empty paper.
|
|
9
|
+
// body the number, the name and the status badge (`shotStatusOf`:
|
|
10
|
+
// Built, Building while this session's agent run is out for it,
|
|
11
|
+
// else Planned; never stored), then one line: the voice in curly
|
|
12
|
+
// quotes, else What we see.
|
|
13
|
+
// footer how many sketches and pinned cards, and the length.
|
|
14
|
+
//
|
|
15
|
+
// A PRESS SELECTS (the Plan panel opens on the right), A DOUBLE PRESS OR
|
|
16
|
+
// ENTER OPENS THE SHOT BOARD, never the Studio. The board owns the double
|
|
17
|
+
// press (BoardCanvas), because the first press can rewrap the board under the
|
|
18
|
+
// pointer. A DRAG MOVES IT (`SHOT_MIME`):
|
|
19
|
+
// the board draws the insertion line and writes the order (`moveBoardEntry`).
|
|
20
|
+
// Its open comment counts show while it is selected (S5: the plan never
|
|
21
|
+
// hides work).
|
|
22
|
+
//
|
|
23
|
+
// A DROP PINS STRAIGHT TO THIS SHOT (S2 note 3): a board card moves onto it,
|
|
24
|
+
// files upload onto it, no dialog. A Shot no plan links gets its planned shot
|
|
25
|
+
// in the same edit (`scene`, `pinPartFor`).
|
|
26
|
+
//
|
|
27
|
+
// `data-comment-target` names the card for the comment tools (lane M).
|
|
28
|
+
import { useState } from "react";
|
|
29
|
+
import type { DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent } from "react";
|
|
30
|
+
import { framesToSeconds } from "@dailies-studio/engine";
|
|
31
|
+
import type { Doc3D } from "@dailies-studio/shot/doc";
|
|
32
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
33
|
+
import { SHOT_STATUS_LABEL, isReferenceId, shotLengthOf, shotStatusOf } from "../../state/storyboardModel.js";
|
|
34
|
+
import type { ShotStatus, StoryEntry } from "../../state/storyboardModel.js";
|
|
35
|
+
import { CHECK_ICON } from "../../icons.js";
|
|
36
|
+
import { ASSET_MIME, readAssetDrag } from "../files/assetDrag.js";
|
|
37
|
+
import { CardCommentCounts } from "../comments/parts.js";
|
|
38
|
+
import { formatDuration } from "../timeline/model.js";
|
|
39
|
+
import type { Press } from "./boardRows.js";
|
|
40
|
+
import { entryIdOf } from "./boardOrder.js";
|
|
41
|
+
import { MiniSketch } from "./MiniSketch.js";
|
|
42
|
+
import { useSketchThumbs, useStillThumb } from "./StillThumbs.js";
|
|
43
|
+
import { STORYBOARD_MIME, acceptedFiles, readDragPayload, refusedFilesMessage } from "./storyboardDrop.js";
|
|
44
|
+
import { useBuildingParts } from "../agent/AgentDots.js";
|
|
45
|
+
|
|
46
|
+
export { entryIdOf } from "./boardOrder.js";
|
|
47
|
+
|
|
48
|
+
/** A shot card dragged to a new place on the board: its entry id. Its own
|
|
49
|
+
* type, so a card drop (`STORYBOARD_MIME`) never takes it. */
|
|
50
|
+
export const SHOT_MIME = "application/x-dailies-shot";
|
|
51
|
+
/** What a drop on a card can take. */
|
|
52
|
+
const CARD_DROP_TYPES = [STORYBOARD_MIME, ASSET_MIME, "Files"];
|
|
53
|
+
/** The card's picture width, CSS px (Paper 346): what its images fetch for. */
|
|
54
|
+
const PICTURE_PX = 346;
|
|
55
|
+
|
|
56
|
+
/** The badge tone each status reads in (G1 `.badge`). */
|
|
57
|
+
const STATUS_TONE: Record<ShotStatus, string> = { planned: "", building: " is-building", built: " is-success" };
|
|
58
|
+
|
|
59
|
+
/** "2 sketches · 1 pinned". */
|
|
60
|
+
function sketchPinWords(sketches: number, pinned: number): string {
|
|
61
|
+
return `${sketches} ${sketches === 1 ? "sketch" : "sketches"} · ${pinned} pinned`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Six dots: the drag grip (Paper SB1, on hover). */
|
|
65
|
+
const GRIP_ICON = (
|
|
66
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
67
|
+
<circle cx="6" cy="4" r="1.25" />
|
|
68
|
+
<circle cx="10" cy="4" r="1.25" />
|
|
69
|
+
<circle cx="6" cy="8" r="1.25" />
|
|
70
|
+
<circle cx="10" cy="8" r="1.25" />
|
|
71
|
+
<circle cx="6" cy="12" r="1.25" />
|
|
72
|
+
<circle cx="10" cy="12" r="1.25" />
|
|
73
|
+
</svg>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
export function ShotCard(props: {
|
|
77
|
+
doc: Doc3D;
|
|
78
|
+
entry: StoryEntry;
|
|
79
|
+
/** Its place in `storyEntries`, from 0. */
|
|
80
|
+
at: number;
|
|
81
|
+
selected: boolean;
|
|
82
|
+
onPress: (id: string, press: Press) => void;
|
|
83
|
+
onOpen: (entry: StoryEntry) => void;
|
|
84
|
+
onMenu: (entry: StoryEntry, event: ReactMouseEvent) => void;
|
|
85
|
+
/** A drag of this card began or ended; the board draws the insertion line. */
|
|
86
|
+
onDragMove?: (id: string | null) => void;
|
|
87
|
+
}) {
|
|
88
|
+
const { doc, entry, at, selected } = props;
|
|
89
|
+
const store = useStore();
|
|
90
|
+
const building = useBuildingParts();
|
|
91
|
+
const readOnly = useShotEditor((s) => s.readOnly);
|
|
92
|
+
const part = entry.kind === "shot" ? entry.shot.part : entry.part;
|
|
93
|
+
const shot = entry.kind === "shot" ? entry.shot : null;
|
|
94
|
+
const id = entryIdOf(entry);
|
|
95
|
+
const name = entry.kind === "shot" ? entry.name : entry.part.label;
|
|
96
|
+
const status = shotStatusOf(part, building);
|
|
97
|
+
const length = shotLengthOf(doc, part, shot?.scene ?? null);
|
|
98
|
+
const pins = part === null ? 0 : (doc.resources?.references ?? []).filter((r) => r.part === part.id).length;
|
|
99
|
+
const number = String(at + 1).padStart(2, "0");
|
|
100
|
+
const line = part?.voice !== undefined ? `“${part.voice}”` : part?.notes;
|
|
101
|
+
|
|
102
|
+
const onDragOver = (event: ReactDragEvent): void => {
|
|
103
|
+
const types = Array.from(event.dataTransfer.types ?? []);
|
|
104
|
+
if (!types.some((type) => CARD_DROP_TYPES.includes(type))) return;
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
event.dataTransfer.dropEffect = types.includes("Files") ? "copy" : "move";
|
|
108
|
+
event.currentTarget.classList.add("is-drop");
|
|
109
|
+
};
|
|
110
|
+
const onDragLeave = (event: ReactDragEvent<HTMLElement>): void => {
|
|
111
|
+
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) event.currentTarget.classList.remove("is-drop");
|
|
112
|
+
};
|
|
113
|
+
const onDrop = (event: ReactDragEvent<HTMLElement>): void => {
|
|
114
|
+
event.currentTarget.classList.remove("is-drop");
|
|
115
|
+
const data = event.dataTransfer;
|
|
116
|
+
const lane = part?.id ?? null;
|
|
117
|
+
const opts = part === null && shot !== null ? { scene: shot.sceneId } : {};
|
|
118
|
+
const payload = readDragPayload(data);
|
|
119
|
+
const asset = payload === null ? readAssetDrag(data) : null;
|
|
120
|
+
const files = Array.from(data.files ?? []);
|
|
121
|
+
if (payload === null && asset === null && files.length === 0) return;
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
event.stopPropagation();
|
|
124
|
+
if (payload?.kind === "reference") {
|
|
125
|
+
const live = store.getState().storyboardSelection;
|
|
126
|
+
const ids = live.includes(payload.id) ? live.filter(isReferenceId) : [payload.id];
|
|
127
|
+
store.attachStoryboardItems(ids, lane, opts);
|
|
128
|
+
} else if (asset !== null) {
|
|
129
|
+
store.addReferenceForAsset(asset.asset, lane, { ...opts, select: false });
|
|
130
|
+
} else if (files.length > 0) {
|
|
131
|
+
const taken = acceptedFiles(files);
|
|
132
|
+
if (taken.length > 0) void store.addStoryboardFiles(taken, lane, { ...opts, select: false });
|
|
133
|
+
const refused = refusedFilesMessage(files);
|
|
134
|
+
if (refused !== null) store.reportError(refused);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<article
|
|
140
|
+
className={`shot-card${selected ? " is-selected" : ""}`}
|
|
141
|
+
data-testid={`shot-card-${id}`}
|
|
142
|
+
data-comment-target={id}
|
|
143
|
+
data-status={status}
|
|
144
|
+
role="option"
|
|
145
|
+
aria-selected={selected}
|
|
146
|
+
aria-label={`${number} ${name}, ${SHOT_STATUS_LABEL[status]}`}
|
|
147
|
+
tabIndex={0}
|
|
148
|
+
draggable={!readOnly}
|
|
149
|
+
onDragStart={(event) => {
|
|
150
|
+
if (event.target !== event.currentTarget) return;
|
|
151
|
+
event.dataTransfer.setData(SHOT_MIME, id);
|
|
152
|
+
event.dataTransfer.effectAllowed = "move";
|
|
153
|
+
props.onDragMove?.(id);
|
|
154
|
+
}}
|
|
155
|
+
onDragEnd={() => props.onDragMove?.(null)}
|
|
156
|
+
onClick={(event) => props.onPress(id, event)}
|
|
157
|
+
onKeyDown={(event) => {
|
|
158
|
+
if (event.target !== event.currentTarget) return;
|
|
159
|
+
if (event.key === "Enter") props.onOpen(entry);
|
|
160
|
+
else if (event.key === " ") {
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
props.onPress(id, event);
|
|
163
|
+
}
|
|
164
|
+
}}
|
|
165
|
+
onContextMenu={(event) => props.onMenu(entry, event)}
|
|
166
|
+
onDragOver={onDragOver}
|
|
167
|
+
onDragLeave={onDragLeave}
|
|
168
|
+
onDrop={onDrop}
|
|
169
|
+
>
|
|
170
|
+
<ShotPicture doc={doc} entry={entry} />
|
|
171
|
+
{readOnly ? null : (
|
|
172
|
+
<span className="shot-card-grip" aria-hidden="true" title="Drag to move">
|
|
173
|
+
{GRIP_ICON}
|
|
174
|
+
</span>
|
|
175
|
+
)}
|
|
176
|
+
<div className="shot-card-body">
|
|
177
|
+
<div className="shot-card-title">
|
|
178
|
+
<span className="shot-card-number">{number}</span>
|
|
179
|
+
<span className="shot-card-name">{name}</span>
|
|
180
|
+
<span className={`badge${STATUS_TONE[status]}`} data-status={status} data-testid="shot-status">
|
|
181
|
+
{SHOT_STATUS_LABEL[status]}
|
|
182
|
+
</span>
|
|
183
|
+
</div>
|
|
184
|
+
{line === undefined ? null : (
|
|
185
|
+
<p className={part?.voice !== undefined ? "shot-card-voice" : "shot-card-notes"} title={line}>
|
|
186
|
+
{line}
|
|
187
|
+
</p>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
<footer className="shot-card-foot">
|
|
191
|
+
<span className="shot-card-counts">{sketchPinWords(part?.sketches?.length ?? 0, pins)}</span>
|
|
192
|
+
{/* The counts show on the selected card only (S5). */}
|
|
193
|
+
{part === null || !selected ? null : <CardCommentCounts target={{ partId: part.id }} />}
|
|
194
|
+
<span className="shot-card-length">{formatDuration(framesToSeconds(length, doc.fps))}</span>
|
|
195
|
+
</footer>
|
|
196
|
+
</article>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The card's picture: the built frame, Sketch 1 (and a peek at the others),
|
|
201
|
+
* the Shot's first still, or empty paper. */
|
|
202
|
+
function ShotPicture({ doc, entry }: { doc: Doc3D; entry: StoryEntry }) {
|
|
203
|
+
const part = entry.kind === "shot" ? entry.shot.part : entry.part;
|
|
204
|
+
const shot = entry.kind === "shot" ? entry.shot : null;
|
|
205
|
+
const sketches = part?.sketches ?? [];
|
|
206
|
+
const [peek, setPeek] = useState(0);
|
|
207
|
+
const built = shot !== null && part?.built !== undefined;
|
|
208
|
+
// Both stills draw through the board's still queue, so a picture shown here
|
|
209
|
+
// and in the Studio is drawn once.
|
|
210
|
+
const frame = shot === null ? null : built ? part!.built!.frame : sketches.length > 0 ? null : (shot.stills[0]?.frame ?? null);
|
|
211
|
+
const thumb = useStillThumb(shot?.sceneId ?? "", frame ?? 0);
|
|
212
|
+
const shown = sketches[Math.min(peek, sketches.length - 1)];
|
|
213
|
+
// The shown sketch as the still stage draws it, asked for only when the
|
|
214
|
+
// card shows a sketch.
|
|
215
|
+
const sketchPicture = useSketchThumbs(part?.id ?? "", (shot !== null && frame !== null) || shown === undefined ? [] : [shown.id]);
|
|
216
|
+
if (shot !== null && frame !== null) {
|
|
217
|
+
return (
|
|
218
|
+
<div className="shot-card-picture" data-picture={built ? "built" : "frame"} data-status={thumb?.status ?? "pending"}>
|
|
219
|
+
{thumb?.url === undefined ? null : <img src={thumb.url} alt="" draggable={false} />}
|
|
220
|
+
{built ? (
|
|
221
|
+
<span className="shot-card-built">
|
|
222
|
+
{CHECK_ICON}
|
|
223
|
+
Built
|
|
224
|
+
</span>
|
|
225
|
+
) : null}
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (shown === undefined) {
|
|
230
|
+
return (
|
|
231
|
+
<div className="shot-card-picture is-paper" data-picture="empty">
|
|
232
|
+
<span className="shot-card-sketch-empty">No sketch yet</span>
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return (
|
|
237
|
+
<div className="shot-card-picture is-paper" data-picture="sketch" onPointerLeave={() => setPeek(0)}>
|
|
238
|
+
<MiniSketch doc={doc} sketch={shown} width={PICTURE_PX} picture={sketchPicture.get(shown.id) ?? null} />
|
|
239
|
+
<span className="shot-card-dots">
|
|
240
|
+
{sketches.map((sketch, index) => (
|
|
241
|
+
<span
|
|
242
|
+
key={sketch.id}
|
|
243
|
+
className={`shot-card-dot${sketch === shown ? " is-shown" : ""}`}
|
|
244
|
+
title={sketch.label ?? `Sketch ${index + 1}`}
|
|
245
|
+
onPointerEnter={() => setPeek(index)}
|
|
246
|
+
/>
|
|
247
|
+
))}
|
|
248
|
+
</span>
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// THE ONE LIVE PREVIEW STAGE: any in-memory `Doc3D`, drawn by a second
|
|
2
|
+
// `/shot-stage.html`, looped or held on one frame. The component card's
|
|
3
|
+
// preview (`ComponentStage`, which builds its one-instance document) and the
|
|
4
|
+
// shot board's Play (`sketchStageDoc`) both mount it.
|
|
5
|
+
//
|
|
6
|
+
// WHY A SECOND STAGE AND NOT THE HARNESS. `/preview.html?component=<id>`
|
|
7
|
+
// frames a three host through `DefaultRig` (a generic fov-50 camera at z 5),
|
|
8
|
+
// while a shot-lane component is authored in stage pixels on the Project's
|
|
9
|
+
// frame box, so the harness showed a CROP of the middle of the design (9 of
|
|
10
|
+
// the 20 shipped components one flat colour, measured 2026-09-25). The stage
|
|
11
|
+
// takes any in-memory `Doc3D` through `loadDoc` (it never fetches one), so
|
|
12
|
+
// this mounts `/shot-stage.html` a second time and hands it the caller's
|
|
13
|
+
// document. Nothing is saved, and no bridge message is new.
|
|
14
|
+
//
|
|
15
|
+
// LOOPING, OR ONE FRAME. The stage owns no clock (the chrome drives every
|
|
16
|
+
// seek), so the preview plays by seeking its own stage from a rAF loop, at the
|
|
17
|
+
// document's fps, over `lengthFrames`. A caller that names a `frame` shows
|
|
18
|
+
// that frame and holds it.
|
|
19
|
+
//
|
|
20
|
+
// HOSTED, StagePanel's rule: no src until `__dailiesHost.ready` settles. But
|
|
21
|
+
// NOT `host.stageLoaded`: the host tracks ONE stage (the main one, which it
|
|
22
|
+
// reloads when the Service Worker refuses it a module), so this preview does
|
|
23
|
+
// that call's two jobs itself: a document the Service Worker does not control
|
|
24
|
+
// is reloaded once, and the current epoch is posted again on every load,
|
|
25
|
+
// because the epoch channel does not replay. Locally there is no host and none
|
|
26
|
+
// of this runs.
|
|
27
|
+
//
|
|
28
|
+
// IN VIEW AND IN BUDGET. The stage mounts only while it is on screen and holds
|
|
29
|
+
// a slot of the shared budget (liveCap.ts), keyed by the caller's `slotKey`,
|
|
30
|
+
// so a card that already holds that slot shares it rather than taking a
|
|
31
|
+
// second. A caller may hold it back (`live`): the board's cards go live only
|
|
32
|
+
// on hover, focus or selection, and show their POSTER at rest (posterCache.ts),
|
|
33
|
+
// which a live stage captures under the caller's `posterKey` once it is ready.
|
|
34
|
+
//
|
|
35
|
+
// NEVER `.stage-frame` or `.stage-window`: the first is pinned full-window by
|
|
36
|
+
// shell.css, and `measureInset` reads the second as the main stage's hole.
|
|
37
|
+
// The classes stay `component-stage*`, where storyboard.css styles them.
|
|
38
|
+
import { useEffect, useRef, useState } from "react";
|
|
39
|
+
import type { ReactNode } from "react";
|
|
40
|
+
import type { Doc3D } from "@dailies-studio/shot/doc";
|
|
41
|
+
import { readStudioThemePreference, resolveStudioTheme } from "@dailies-studio/ui/theme";
|
|
42
|
+
import { connectStage } from "../../bridge.js";
|
|
43
|
+
import { MODULE_EPOCH_CHANNEL, dailiesHost } from "../../hostApi.js";
|
|
44
|
+
import type { ShotBridgeHandlers, ShotStageBridge } from "../../bridgeTypes.js";
|
|
45
|
+
import { stageSrc } from "../stage/StagePanel.js";
|
|
46
|
+
import { useInView, useLiveSlot } from "./liveCap.js";
|
|
47
|
+
import { usePoster, writePoster } from "./posterCache.js";
|
|
48
|
+
|
|
49
|
+
/** The poster's width in px: twice the widest card that shows it. */
|
|
50
|
+
export const POSTER_WIDTH = 480;
|
|
51
|
+
|
|
52
|
+
/** The poster's size for a frame box: `POSTER_WIDTH` wide, the box's aspect. */
|
|
53
|
+
export function posterSize(size: { width: number; height: number }): { width: number; height: number } {
|
|
54
|
+
return { width: POSTER_WIDTH, height: Math.max(1, Math.round((POSTER_WIDTH * size.height) / size.width)) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The frame a loop that started `elapsedMs` ago shows: the document's fps,
|
|
58
|
+
* wrapped over `lengthFrames` (at least one). */
|
|
59
|
+
export function loopFrameAt(elapsedMs: number, fps: number, lengthFrames: number): number {
|
|
60
|
+
return Math.floor((Math.max(0, elapsedMs) / 1000) * fps) % Math.max(1, Math.round(lengthFrames));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface StagePreviewProps {
|
|
64
|
+
/** What the stage draws. Null holds it on its loading line. A new identity
|
|
65
|
+
* reloads the stage document, so a caller memoizes it on what it reads. */
|
|
66
|
+
doc: Doc3D | null;
|
|
67
|
+
/** The frame box: the aspect ratio and the poster's. */
|
|
68
|
+
size: { width: number; height: number };
|
|
69
|
+
/** How many frames the loop runs over. */
|
|
70
|
+
lengthFrames: number;
|
|
71
|
+
/** Shows this frame and holds it: no loop runs. */
|
|
72
|
+
frame?: number;
|
|
73
|
+
/** Whether the stage may go live. False shows the poster (default true). */
|
|
74
|
+
live?: boolean;
|
|
75
|
+
/** The live budget slot this preview takes (liveCap.ts). */
|
|
76
|
+
slotKey: string;
|
|
77
|
+
/** The poster cache key (posterCache.ts). Null captures and shows none. */
|
|
78
|
+
posterKey: string | null;
|
|
79
|
+
/** The iframe's accessible name. */
|
|
80
|
+
title: string;
|
|
81
|
+
className?: string;
|
|
82
|
+
/** What shows while the stage is not live and no poster is cached yet. */
|
|
83
|
+
fallback?: ReactNode;
|
|
84
|
+
/** The stage said it drew the document. */
|
|
85
|
+
onReady?: () => void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function StagePreview(props: StagePreviewProps) {
|
|
89
|
+
const { doc, size, lengthFrames, frame } = props;
|
|
90
|
+
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
|
91
|
+
const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null);
|
|
92
|
+
const inView = useInView(wrapper);
|
|
93
|
+
const wantLive = props.live ?? true;
|
|
94
|
+
const live = useLiveSlot(props.slotKey, inView && wantLive);
|
|
95
|
+
// THE SRC IS FIXED AT MOUNT, StagePanel's rule: a src that followed the
|
|
96
|
+
// theme would reboot the stage on every appearance change. Read from the
|
|
97
|
+
// provider's own source, not its context, so a preview mounted outside the
|
|
98
|
+
// provider (a suite) draws rather than taking the inspector down with it.
|
|
99
|
+
const src = useRef(stageSrc(resolveStudioTheme(readStudioThemePreference()))).current;
|
|
100
|
+
// Read once: the boot script installs it before the chrome runs, or never.
|
|
101
|
+
const [host] = useState(dailiesHost);
|
|
102
|
+
const [hostSettled, setHostSettled] = useState(host === undefined);
|
|
103
|
+
const reloaded = useRef(false);
|
|
104
|
+
const [bridge, setBridge] = useState<ShotStageBridge | null>(null);
|
|
105
|
+
const [status, setStatus] = useState<"loading" | "ready" | string>("loading");
|
|
106
|
+
// A ref, so a caller's fresh callback never reconnects the stage.
|
|
107
|
+
const onReady = useRef(props.onReady);
|
|
108
|
+
onReady.current = props.onReady;
|
|
109
|
+
const fps = doc === null ? 0 : doc.fps.num / doc.fps.den;
|
|
110
|
+
const poster = usePoster(props.posterKey);
|
|
111
|
+
// TRUE WHILE A POSTER CAPTURE IS OUT: the loop holds its seeks, which would
|
|
112
|
+
// move the stage under the capture.
|
|
113
|
+
const capturing = useRef(false);
|
|
114
|
+
|
|
115
|
+
// ── CONNECT, AND AGAIN ON EVERY LOAD ────────────────────────────────────
|
|
116
|
+
//
|
|
117
|
+
// Saving a component's source reloads every stage iframe, this one too, and
|
|
118
|
+
// a bridge from the old document is dead. So every load event reconnects.
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (host === undefined) return;
|
|
121
|
+
let alive = true;
|
|
122
|
+
// A host that failed says so on the main stage; this one still loads.
|
|
123
|
+
const settle = (): void => {
|
|
124
|
+
if (alive) setHostSettled(true);
|
|
125
|
+
};
|
|
126
|
+
host.ready.then(settle, settle);
|
|
127
|
+
return () => {
|
|
128
|
+
alive = false;
|
|
129
|
+
};
|
|
130
|
+
}, [host]);
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
const iframe = iframeRef.current;
|
|
134
|
+
if (iframe === null || !hostSettled || !live) return;
|
|
135
|
+
const aborter = new AbortController();
|
|
136
|
+
const handlers = previewHandlers({
|
|
137
|
+
onReady: () => {
|
|
138
|
+
setStatus("ready");
|
|
139
|
+
onReady.current?.();
|
|
140
|
+
},
|
|
141
|
+
onError: (message) => setStatus(message),
|
|
142
|
+
});
|
|
143
|
+
const connect = (): void => {
|
|
144
|
+
setBridge(null);
|
|
145
|
+
setStatus("loading");
|
|
146
|
+
void connectStage(iframe, handlers, { signal: aborter.signal })
|
|
147
|
+
.then((next) => {
|
|
148
|
+
if (aborter.signal.aborted) return;
|
|
149
|
+
// THE CAMERA ONLY, no furniture: the storyboard preview's own mode.
|
|
150
|
+
next.setStoryboardPreview(true);
|
|
151
|
+
setBridge(next);
|
|
152
|
+
})
|
|
153
|
+
.catch((e: unknown) => {
|
|
154
|
+
if (!aborter.signal.aborted) setStatus(e instanceof Error ? e.message : String(e));
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
const onLoad = (): void => {
|
|
158
|
+
const stage = iframe.contentWindow;
|
|
159
|
+
if (host !== undefined && stage !== null) {
|
|
160
|
+
if (stage.navigator.serviceWorker?.controller == null && !reloaded.current) {
|
|
161
|
+
reloaded.current = true;
|
|
162
|
+
stage.location.reload();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const version = host.compileVersion();
|
|
166
|
+
if (version !== null) {
|
|
167
|
+
const channel = new BroadcastChannel(MODULE_EPOCH_CHANNEL);
|
|
168
|
+
channel.postMessage(version);
|
|
169
|
+
channel.close();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
connect();
|
|
173
|
+
};
|
|
174
|
+
iframe.addEventListener("load", onLoad);
|
|
175
|
+
// Hosted, the src was set in the commit this effect follows, so the load
|
|
176
|
+
// is still ahead.
|
|
177
|
+
if (host === undefined) connect();
|
|
178
|
+
return () => {
|
|
179
|
+
aborter.abort();
|
|
180
|
+
iframe.removeEventListener("load", onLoad);
|
|
181
|
+
// A slot handed on unmounts the iframe, and its bridge with it: drop
|
|
182
|
+
// it, or the loop below keeps seeking a dead document.
|
|
183
|
+
setBridge(null);
|
|
184
|
+
setStatus("loading");
|
|
185
|
+
};
|
|
186
|
+
}, [host, hostSettled, live]);
|
|
187
|
+
|
|
188
|
+
// ── THE DOCUMENT, then the frame ────────────────────────────────────────
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
if (bridge === null || doc === null) return;
|
|
191
|
+
bridge.loadDoc(doc);
|
|
192
|
+
bridge.seek(frame ?? 0);
|
|
193
|
+
}, [bridge, doc, frame]);
|
|
194
|
+
|
|
195
|
+
// ── THE POSTER, once the stage is ready ─────────────────────────────────
|
|
196
|
+
//
|
|
197
|
+
// Each live session captures again, so a poster made before a code change
|
|
198
|
+
// the key does not see is replaced the next time the card goes live.
|
|
199
|
+
const posterKey = props.posterKey;
|
|
200
|
+
const { width, height } = size;
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (bridge === null || status !== "ready" || posterKey === null) return;
|
|
203
|
+
const aborter = new AbortController();
|
|
204
|
+
capturing.current = true;
|
|
205
|
+
const out = posterSize({ width, height });
|
|
206
|
+
void bridge
|
|
207
|
+
.captureFrame({ frame: frame ?? 0, width: out.width, height: out.height, format: "webp", signal: aborter.signal })
|
|
208
|
+
.then((blob) => {
|
|
209
|
+
if (!aborter.signal.aborted) writePoster(posterKey, blob);
|
|
210
|
+
})
|
|
211
|
+
// A failed poster leaves the caller's fallback; the live stage still plays.
|
|
212
|
+
.catch(() => {})
|
|
213
|
+
.finally(() => {
|
|
214
|
+
capturing.current = false;
|
|
215
|
+
});
|
|
216
|
+
return () => {
|
|
217
|
+
aborter.abort();
|
|
218
|
+
capturing.current = false;
|
|
219
|
+
};
|
|
220
|
+
}, [bridge, status, posterKey, frame, width, height]);
|
|
221
|
+
|
|
222
|
+
// ── THE LOOP, when no frame is named ────────────────────────────────────
|
|
223
|
+
//
|
|
224
|
+
// One seek per NEW frame, at the document's fps: a rAF that seeked every
|
|
225
|
+
// tick would ask the stage for the same picture twice at 60 Hz.
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (bridge === null || frame !== undefined || fps <= 0) return;
|
|
228
|
+
let raf = 0;
|
|
229
|
+
let last = -1;
|
|
230
|
+
const start = performance.now();
|
|
231
|
+
const tick = (now: number): void => {
|
|
232
|
+
const next = loopFrameAt(now - start, fps, lengthFrames);
|
|
233
|
+
if (next !== last && !capturing.current) {
|
|
234
|
+
last = next;
|
|
235
|
+
bridge.seek(next);
|
|
236
|
+
}
|
|
237
|
+
raf = requestAnimationFrame(tick);
|
|
238
|
+
};
|
|
239
|
+
raf = requestAnimationFrame(tick);
|
|
240
|
+
return () => cancelAnimationFrame(raf);
|
|
241
|
+
}, [bridge, frame, fps, lengthFrames]);
|
|
242
|
+
|
|
243
|
+
const ready = live && status === "ready";
|
|
244
|
+
const aspectRatio = `${width} / ${height}`;
|
|
245
|
+
return (
|
|
246
|
+
<div
|
|
247
|
+
ref={setWrapper}
|
|
248
|
+
className={props.className === undefined ? "component-stage" : `component-stage ${props.className}`}
|
|
249
|
+
data-live={ready ? "true" : undefined}
|
|
250
|
+
style={{ aspectRatio }}
|
|
251
|
+
>
|
|
252
|
+
{/* THE POSTER UNDER A STAGE THAT IS NOT READY, so going live never
|
|
253
|
+
flashes an empty ground. */}
|
|
254
|
+
{!ready && poster !== null ? <img className="component-stage-poster" src={poster} alt="" draggable={false} /> : null}
|
|
255
|
+
{!live && poster === null ? props.fallback : null}
|
|
256
|
+
{live ? (
|
|
257
|
+
<iframe
|
|
258
|
+
ref={iframeRef}
|
|
259
|
+
className="component-stage-frame"
|
|
260
|
+
src={hostSettled ? src : undefined}
|
|
261
|
+
title={props.title}
|
|
262
|
+
tabIndex={-1}
|
|
263
|
+
style={{ aspectRatio, opacity: ready || poster === null ? 1 : 0 }}
|
|
264
|
+
/>
|
|
265
|
+
) : null}
|
|
266
|
+
{!live ? (
|
|
267
|
+
inView && wantLive && poster === null ? (
|
|
268
|
+
<span className="component-stage-status" role="status">
|
|
269
|
+
Preview paused
|
|
270
|
+
</span>
|
|
271
|
+
) : null
|
|
272
|
+
) : status === "ready" || poster !== null ? null : (
|
|
273
|
+
<span className="component-stage-status" role="status">
|
|
274
|
+
{status === "loading" ? "Loading the preview…" : status}
|
|
275
|
+
</span>
|
|
276
|
+
)}
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Every bridge handler, as a no-op except the two this preview reads. An
|
|
283
|
+
* EXPLICIT object, so a handler the bridge grows later is a compile error
|
|
284
|
+
* here rather than a silent `undefined` the stage calls. The boolean ones
|
|
285
|
+
* answer false: the preview takes no gesture, no key and no key light.
|
|
286
|
+
*/
|
|
287
|
+
function previewHandlers(on: { onReady(): void; onError(message: string): void }): ShotBridgeHandlers {
|
|
288
|
+
const none = (): void => {};
|
|
289
|
+
const refuse = (): boolean => false;
|
|
290
|
+
return {
|
|
291
|
+
onReady: () => on.onReady(),
|
|
292
|
+
onError: (message) => on.onError(message),
|
|
293
|
+
onStageBusy: none,
|
|
294
|
+
onPerformance: none,
|
|
295
|
+
onMaterialCompile: none,
|
|
296
|
+
onPick: none,
|
|
297
|
+
onEnterLayer: none,
|
|
298
|
+
onTextEdit: none,
|
|
299
|
+
onContextMenu: none,
|
|
300
|
+
onMeshStats: none,
|
|
301
|
+
onMeshMaterialSlots: none,
|
|
302
|
+
onInstanceNaturalSize: none,
|
|
303
|
+
onTextAutoSize: none,
|
|
304
|
+
onCommentPin: none,
|
|
305
|
+
onStudioCommentAt: none,
|
|
306
|
+
onKeyLightStart: refuse,
|
|
307
|
+
onKeyLight: none,
|
|
308
|
+
onKeyLightEnd: none,
|
|
309
|
+
onIsolationOrbitTaken: none,
|
|
310
|
+
onFitRequest: none,
|
|
311
|
+
onViewChange: none,
|
|
312
|
+
onWorkingViewChange: none,
|
|
313
|
+
onActivePane: none,
|
|
314
|
+
onGestureStart: refuse,
|
|
315
|
+
onGesture: none,
|
|
316
|
+
onGestureEnd: none,
|
|
317
|
+
onFramingGestureStart: refuse,
|
|
318
|
+
onFramingGesture: refuse,
|
|
319
|
+
onFramingGestureEnd: none,
|
|
320
|
+
onLeavePilot: none,
|
|
321
|
+
onCameraDriver: none,
|
|
322
|
+
onWaypointGesture: none,
|
|
323
|
+
onDraw: none,
|
|
324
|
+
onPenCommit: none,
|
|
325
|
+
onSplineCreateCommit: none,
|
|
326
|
+
onSplinePointPick: none,
|
|
327
|
+
onSplinePointGestureStart: refuse,
|
|
328
|
+
onSplinePointGesture: none,
|
|
329
|
+
onSplinePointGestureEnd: none,
|
|
330
|
+
onSplineSegmentInsert: none,
|
|
331
|
+
onSplinePointDelete: none,
|
|
332
|
+
onSplinePointModeEnd: none,
|
|
333
|
+
onGuideGestureStart: refuse,
|
|
334
|
+
onGuideGesture: none,
|
|
335
|
+
onGuideGestureEnd: none,
|
|
336
|
+
onGuideSet: none,
|
|
337
|
+
onGuideRemove: none,
|
|
338
|
+
onPathKeyGestureStart: refuse,
|
|
339
|
+
onPathKeyGesture: none,
|
|
340
|
+
onPathKeyGestureEnd: none,
|
|
341
|
+
onPathKeyDoubleClick: none,
|
|
342
|
+
onKey: refuse,
|
|
343
|
+
// The preview's own failures show in its status line. Stage health is
|
|
344
|
+
// the main stage's report to an agent, never this one's.
|
|
345
|
+
onPieceFailures: none,
|
|
346
|
+
onStageHealth: none,
|
|
347
|
+
};
|
|
348
|
+
}
|