@dailies-studio/shot-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -1,45 +1,198 @@
|
|
|
1
|
-
// How
|
|
1
|
+
// How a shot hands over to the next one.
|
|
2
2
|
//
|
|
3
|
-
// A transition is stored on the OUTGOING shot
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// nothing after it to blend into. It stays EDITABLE there rather than hidden,
|
|
3
|
+
// A transition is stored on the OUTGOING shot and plays over the opening
|
|
4
|
+
// frames of the one after it. The last shot's controls are inert: there is
|
|
5
|
+
// nothing after it to blend into. They stay EDITABLE rather than hidden,
|
|
7
6
|
// because reordering can make any shot last and then not last again, and a
|
|
8
7
|
// setting that vanishes when you drag a bar looks like data loss.
|
|
9
8
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
9
|
+
// ── TWO DOORS, ONE SET OF ROWS ───────────────────────────────────────────
|
|
10
|
+
//
|
|
11
|
+
// Studio's project context mounts `ShotTransitionSection`: the selected shot's
|
|
12
|
+
// own rows, then these. The board mounts `StoryboardCutSection` when a cut is
|
|
13
|
+
// its subject. Both render `TransitionRows` with an EXPLICIT shot id. The rows
|
|
14
|
+
// used to read `state.sceneId`, which every seek re-pins, so a scrub across
|
|
15
|
+
// the seam would have switched them to the incoming shot halfway through.
|
|
16
|
+
//
|
|
17
|
+
// ── PRESETS AS FOUR BUTTONS, THE REST BEHIND "Browse all" ────────────────
|
|
18
|
+
//
|
|
19
|
+
// Cut, Fade, Directional Wipe and Circle Open are plain buttons with
|
|
20
|
+
// `aria-pressed`, NOT a `SegmentedControl`. A radio group commits on an arrow
|
|
21
|
+
// key, and an arrow step over Cut deletes the record: the next step rebuilds
|
|
22
|
+
// it at the default length with its ease, params and Outgoing gone. A stored
|
|
23
|
+
// kind outside the four takes the fourth slot, so the lit button always names
|
|
24
|
+
// what is stored. The full registry stays one press away in the Combobox.
|
|
25
|
+
//
|
|
26
|
+
// The preset list comes from the ROOT of @dailies-studio/transitions, never
|
|
27
|
+
// from its ./gl entry: that entry's first import is `three/tsl`, and the
|
|
28
|
+
// chrome bundle must not carry a shader graph layer to draw a dropdown. The
|
|
29
|
+
// root's `glPresetSpecs` is the same data gl.ts builds against, asserted in
|
|
30
|
+
// lockstep at import.
|
|
31
|
+
import { Fragment, useId, useState } from "react";
|
|
15
32
|
import {
|
|
33
|
+
Combobox,
|
|
34
|
+
FieldBox,
|
|
35
|
+
FieldLabels,
|
|
16
36
|
InspectorRow,
|
|
17
37
|
InspectorSection,
|
|
18
38
|
NumberField,
|
|
39
|
+
SegmentedControl,
|
|
40
|
+
useLocked,
|
|
19
41
|
} from "@dailies-studio/ui";
|
|
42
|
+
import type { ComboboxOption, ScrubHandlers } from "@dailies-studio/ui";
|
|
20
43
|
import { glPresetSpecs, resolveGlPresetSpec } from "@dailies-studio/transitions";
|
|
21
|
-
import { MIN_TRANSITION_FRAMES } from "@dailies-studio/shot/doc";
|
|
22
|
-
import type {
|
|
44
|
+
import { DEFAULT_TRANSITION_FRAMES, MIN_TRANSITION_FRAMES, partOfScene, seamWindow, sceneStart } from "@dailies-studio/shot/doc";
|
|
45
|
+
import type { Doc3D, SceneTransition } from "@dailies-studio/shot/doc";
|
|
46
|
+
import { EASE_PRESETS } from "@dailies-studio/timeline";
|
|
47
|
+
import { framesToSeconds } from "@dailies-studio/engine";
|
|
48
|
+
import type { Ease, Value } from "@dailies-studio/engine";
|
|
23
49
|
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
50
|
+
import { boardSubjectOf } from "../../../state/storyboardModel.js";
|
|
51
|
+
import { SECTION_ICONS } from "../../../icons.js";
|
|
52
|
+
import { formatDuration, timecode } from "../../timeline/model.js";
|
|
53
|
+
import { ShotRows } from "./SceneSection.js";
|
|
24
54
|
import { ParamRow } from "../ParamRow.js";
|
|
55
|
+
import { EaseControl } from "../animate/EaseControl.js";
|
|
25
56
|
|
|
26
|
-
/** "no transition", as a
|
|
57
|
+
/** "no transition", as a picker value. Not a preset id, and cannot be one. */
|
|
27
58
|
const CUT = "";
|
|
28
59
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
60
|
+
/** The three presets with a button of their own, after Cut. */
|
|
61
|
+
const TILE_KINDS = ["gl/fade", "gl/directional-wipe", "gl/circle-open"] as const;
|
|
62
|
+
|
|
63
|
+
/** Each tile's picture of its blend, 28x14: A is the outgoing frame (the
|
|
64
|
+
* outline), B the incoming one (the fill). A kind outside the four gets the
|
|
65
|
+
* plain A-to-B glyph. */
|
|
66
|
+
function TileGlyph({ kind }: { kind: string }) {
|
|
67
|
+
// Per mount: a fixed id repeats in every tile set on the page (the board and
|
|
68
|
+
// the inspector), and `url(#id)` then paints from whichever came first.
|
|
69
|
+
const fade = useId();
|
|
70
|
+
return (
|
|
71
|
+
<svg className="insp-tile-glyph" viewBox="0 0 28 14" aria-hidden="true">
|
|
72
|
+
{kind === CUT ? (
|
|
73
|
+
<>
|
|
74
|
+
<rect x="1" y="1" width="11" height="12" rx="1.5" />
|
|
75
|
+
<rect x="16" y="1" width="11" height="12" rx="1.5" className="is-b" />
|
|
76
|
+
</>
|
|
77
|
+
) : kind === "gl/fade" ? (
|
|
78
|
+
<>
|
|
79
|
+
<defs>
|
|
80
|
+
<linearGradient id={fade}>
|
|
81
|
+
<stop offset="0" stopColor="currentColor" stopOpacity="0" />
|
|
82
|
+
<stop offset="1" stopColor="currentColor" stopOpacity="0.9" />
|
|
83
|
+
</linearGradient>
|
|
84
|
+
</defs>
|
|
85
|
+
<rect x="1" y="1" width="26" height="12" rx="1.5" fill={`url(#${fade})`} />
|
|
86
|
+
</>
|
|
87
|
+
) : kind === "gl/directional-wipe" ? (
|
|
88
|
+
<>
|
|
89
|
+
<rect x="1" y="1" width="26" height="12" rx="1.5" />
|
|
90
|
+
<path d="M16 1 L27 1 L27 13 L10 13 Z" className="is-b" />
|
|
91
|
+
</>
|
|
92
|
+
) : kind === "gl/circle-open" ? (
|
|
93
|
+
<>
|
|
94
|
+
<rect x="1" y="1" width="26" height="12" rx="1.5" />
|
|
95
|
+
<circle cx="14" cy="7" r="4" className="is-b" />
|
|
96
|
+
</>
|
|
97
|
+
) : (
|
|
98
|
+
<>
|
|
99
|
+
<rect x="1" y="1" width="26" height="12" rx="1.5" />
|
|
100
|
+
<path d="M8 7 H20 M16 4 L20 7 L16 10" />
|
|
101
|
+
</>
|
|
102
|
+
)}
|
|
103
|
+
</svg>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** What an absent ease PLAYS: the sampler resolves a stored transition with no
|
|
108
|
+
* ease as linear. Every write below passes this rather than nothing, because
|
|
109
|
+
* `setSceneTransition` fills an absent ease with ease-in-out, and a Length
|
|
110
|
+
* edit must not quietly re-time a legacy linear blend. */
|
|
111
|
+
const LINEAR: Ease = EASE_PRESETS.find((p) => p.id === "linear")!.ease;
|
|
32
112
|
|
|
33
|
-
|
|
113
|
+
/** The eight directions the stepper walks, counter-clockwise in the presets'
|
|
114
|
+
* y-up space from +x. Not normalised: the registry's default is [1, -1], and
|
|
115
|
+
* a walk that cannot land on it again never gives the default back. */
|
|
116
|
+
const DIRECTIONS: ReadonlyArray<readonly [number, number]> = [
|
|
117
|
+
[1, 0],
|
|
118
|
+
[1, 1],
|
|
119
|
+
[0, 1],
|
|
120
|
+
[-1, 1],
|
|
121
|
+
[-1, 0],
|
|
122
|
+
[-1, -1],
|
|
123
|
+
[0, -1],
|
|
124
|
+
[1, -1],
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
/** The next direction after `value`: the nearest of the eight by angle, then
|
|
128
|
+
* one step on. A hand-typed vector steps from wherever it points. */
|
|
129
|
+
export function nextDirection(value: Value | undefined): [number, number] {
|
|
130
|
+
const [x, y] = Array.isArray(value) && value.length >= 2 ? [Number(value[0]), Number(value[1])] : [1, -1];
|
|
131
|
+
const angle = Math.atan2(Number.isFinite(y) ? y : 0, Number.isFinite(x) ? x : 1);
|
|
132
|
+
const step = (((Math.round(angle / (Math.PI / 4)) % 8) + 8) % 8 + 1) % 8;
|
|
133
|
+
const [nx, ny] = DIRECTIONS[step]!;
|
|
134
|
+
return [nx, ny];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The closed head's line: the preset's label, "Cut" without one. A STRING
|
|
138
|
+
* selector, so it re-renders on the label and never allocates. */
|
|
139
|
+
function useTransitionSummary(): string | null {
|
|
140
|
+
return useShotEditor((s) => {
|
|
141
|
+
if (s.doc === null || s.sceneId === null) return null;
|
|
142
|
+
const scene = s.doc.scenesById[s.sceneId];
|
|
143
|
+
if (scene === undefined) return null;
|
|
144
|
+
const kind = scene.transition?.kind;
|
|
145
|
+
if (kind === undefined) return "Cut";
|
|
146
|
+
return resolveGlPresetSpec(kind)?.label ?? `${kind} (unknown)`;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function ShotTransitionSection() {
|
|
151
|
+
const summary = useTransitionSummary();
|
|
152
|
+
const sceneId = useShotEditor((s) => s.sceneId);
|
|
153
|
+
if (summary === null || sceneId === null) return null;
|
|
154
|
+
return (
|
|
155
|
+
// A NEW collapse bit. Neither old one ("shot:scene", "shot:transition")
|
|
156
|
+
// describes this section, and inheriting either would open or close it on
|
|
157
|
+
// a choice the author made about something else. `ShotRows` look here
|
|
158
|
+
// exactly as they do in the Shot section, and the length's readout has its
|
|
159
|
+
// face, because both are the house's (widgets.css), with no section scope.
|
|
160
|
+
<InspectorSection
|
|
161
|
+
title="Shot & transition"
|
|
162
|
+
icon={SECTION_ICONS.shot}
|
|
163
|
+
summary={summary}
|
|
164
|
+
persistKey="shot:shot-transition"
|
|
165
|
+
>
|
|
166
|
+
<ShotRows />
|
|
167
|
+
<TransitionRows sceneId={sceneId} lengthLabel="Duration" heading />
|
|
168
|
+
</InspectorSection>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** How shot `sceneId` hands over to the next one. `heading` draws the
|
|
173
|
+
* "Transition · into Shot 02" line over the rows: Studio's section holds the
|
|
174
|
+
* shot's rows too, while the board's section is the transition alone and its
|
|
175
|
+
* head already names both shots.
|
|
176
|
+
*
|
|
177
|
+
* `lengthLabel` is the word on the length's box. Studio says "Duration",
|
|
178
|
+
* the word its section's other frame count (the shot's) already uses, for
|
|
179
|
+
* the reason `ShotRows` gives; the board's Between has no other length
|
|
180
|
+
* beside it and says "Length". */
|
|
181
|
+
export function TransitionRows(props: { sceneId: string; lengthLabel: string; heading?: boolean }) {
|
|
182
|
+
const { sceneId, lengthLabel } = props;
|
|
34
183
|
const store = useStore();
|
|
35
184
|
const doc = useShotEditor((s) => s.doc);
|
|
36
|
-
const
|
|
37
|
-
|
|
185
|
+
const [browsing, setBrowsing] = useState(false);
|
|
186
|
+
// The preset buttons and the direction step are plain buttons, so they read
|
|
187
|
+
// a viewer's lock themselves; every widget below reads it on its own.
|
|
188
|
+
const locked = useLocked();
|
|
189
|
+
if (doc === null) return null;
|
|
38
190
|
const scene = doc.scenesById[sceneId];
|
|
39
191
|
if (scene === undefined) return null;
|
|
40
192
|
|
|
41
193
|
const transition = scene.transition;
|
|
42
194
|
const spec = transition === undefined ? undefined : resolveGlPresetSpec(transition.kind);
|
|
195
|
+
const ease = transition?.ease ?? LINEAR;
|
|
43
196
|
const isLast = doc.scenes[doc.scenes.length - 1] === sceneId;
|
|
44
197
|
const next = isLast ? null : (doc.scenesById[doc.scenes[doc.scenes.indexOf(sceneId) + 1]!] ?? null);
|
|
45
198
|
|
|
@@ -48,89 +201,387 @@ export function TransitionSection() {
|
|
|
48
201
|
store.setSceneTransition(sceneId, null);
|
|
49
202
|
return;
|
|
50
203
|
}
|
|
204
|
+
if (kind === transition?.kind) return;
|
|
51
205
|
// Params are PRESET-SCOPED, so switching preset starts from that preset's
|
|
52
|
-
// own defaults rather than carrying the previous one's names across
|
|
53
|
-
// same reasoning as setBlockPreset.
|
|
206
|
+
// own defaults rather than carrying the previous one's names across: the
|
|
207
|
+
// same reasoning as setBlockPreset. The ease is not passed either, so a new
|
|
208
|
+
// blend is minted with the house ease-in-out.
|
|
54
209
|
store.setSceneTransition(sceneId, {
|
|
55
210
|
kind,
|
|
56
|
-
durationFrames: transition?.durationFrames ??
|
|
211
|
+
durationFrames: transition?.durationFrames ?? DEFAULT_TRANSITION_FRAMES,
|
|
57
212
|
});
|
|
58
213
|
};
|
|
59
214
|
|
|
215
|
+
/** Every edit of a stored transition: the record with `patch` over it, and
|
|
216
|
+
* the ease it plays, never absent. */
|
|
217
|
+
const write = (patch: Partial<SceneTransition>): void => {
|
|
218
|
+
if (transition === undefined) return;
|
|
219
|
+
store.setSceneTransition(sceneId, { ...transition, ease, ...patch });
|
|
220
|
+
};
|
|
221
|
+
|
|
60
222
|
const setParam = (name: string, value: Value): void => {
|
|
61
223
|
if (transition === undefined) return;
|
|
62
|
-
|
|
63
|
-
...transition,
|
|
64
|
-
params: { ...transition.params, [name]: value },
|
|
65
|
-
});
|
|
224
|
+
write({ params: { ...transition.params, [name]: value } });
|
|
66
225
|
};
|
|
67
226
|
|
|
227
|
+
/** A DRAG on the length or a param: the panel scrub around a write, which
|
|
228
|
+
* `setSceneTransition` captures into while it is open (store.ts), so one
|
|
229
|
+
* drag is one undo entry and the stage blends under the pointer instead of
|
|
230
|
+
* waiting for the release. */
|
|
231
|
+
const drag = (move: (raw: unknown) => void): ScrubHandlers => ({
|
|
232
|
+
start: () => store.beginScrub(),
|
|
233
|
+
move,
|
|
234
|
+
end: (commit) => store.endScrub(commit),
|
|
235
|
+
});
|
|
236
|
+
const lengthDrag = drag((raw) => write({ durationFrames: Math.round(Number(raw)) }));
|
|
237
|
+
|
|
238
|
+
const options: ComboboxOption[] = [
|
|
239
|
+
{ value: CUT, label: "Cut" },
|
|
240
|
+
...Object.values(glPresetSpecs).map((p) => ({ value: p.id, label: p.label })),
|
|
241
|
+
// A document may name a preset this build does not have. It renders as a
|
|
242
|
+
// cut, and the picker says so instead of silently showing "Cut" and
|
|
243
|
+
// rewriting it on the next edit.
|
|
244
|
+
...(transition !== undefined && spec === undefined
|
|
245
|
+
? [{ value: transition.kind, label: `${transition.kind} (unknown)` }]
|
|
246
|
+
: []),
|
|
247
|
+
];
|
|
248
|
+
const labelOf = (kind: string): string =>
|
|
249
|
+
options.find((o) => o.value === kind)?.label ?? kind;
|
|
250
|
+
const stored = transition?.kind ?? CUT;
|
|
251
|
+
// The fourth slot shows a stored kind outside the first three.
|
|
252
|
+
const tiles = [CUT, TILE_KINDS[0], TILE_KINDS[1], (TILE_KINDS as readonly string[]).includes(stored) || stored === CUT ? TILE_KINDS[2] : stored];
|
|
253
|
+
const presetCount = Object.keys(glPresetSpecs).length;
|
|
254
|
+
const outgoing = transition?.outgoing ?? "hold";
|
|
255
|
+
|
|
68
256
|
return (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
257
|
+
<>
|
|
258
|
+
{/* WHICH SEAM, in the heading. These are the only rows in the rail
|
|
259
|
+
about a JOIN, and naming the shot on the far side is what stops them
|
|
260
|
+
reading as a property of one shot alone. */}
|
|
261
|
+
{props.heading === true ? (
|
|
262
|
+
<h4 className="insp-transition-head">
|
|
263
|
+
Transition
|
|
264
|
+
{next === null ? null : (
|
|
265
|
+
<span className="insp-transition-into">
|
|
266
|
+
{" · into "}
|
|
267
|
+
<span data-testid="transition-into">{next.name}</span>
|
|
268
|
+
</span>
|
|
269
|
+
)}
|
|
270
|
+
</h4>
|
|
271
|
+
) : null}
|
|
272
|
+
<InspectorRow>
|
|
273
|
+
<div className="insp-tiles" role="group" aria-label="Transition presets">
|
|
274
|
+
{tiles.map((kind) => (
|
|
275
|
+
<button
|
|
276
|
+
key={kind === CUT ? "cut" : kind}
|
|
277
|
+
type="button"
|
|
278
|
+
className="btn insp-tile"
|
|
279
|
+
aria-pressed={stored === kind}
|
|
280
|
+
disabled={locked}
|
|
281
|
+
onClick={() => setKind(kind)}
|
|
282
|
+
>
|
|
283
|
+
<TileGlyph kind={kind} />
|
|
284
|
+
<span>{labelOf(kind)}</span>
|
|
285
|
+
</button>
|
|
82
286
|
))}
|
|
83
|
-
|
|
84
|
-
as a cut, and the picker says so instead of silently showing
|
|
85
|
-
"Cut" and rewriting it on the next edit. */}
|
|
86
|
-
{transition !== undefined && spec === undefined ? (
|
|
87
|
-
<option value={transition.kind}>{transition.kind} (unknown)</option>
|
|
88
|
-
) : null}
|
|
89
|
-
</select>
|
|
287
|
+
</div>
|
|
90
288
|
</InspectorRow>
|
|
289
|
+
{/* A viewer's panel has no list to open (a locked Combobox will not), and
|
|
290
|
+
the pressed tile already names what is stored, so the door is not
|
|
291
|
+
drawn there at all. */}
|
|
292
|
+
{locked ? null : (
|
|
293
|
+
<InspectorRow>
|
|
294
|
+
{browsing ? (
|
|
295
|
+
<Combobox
|
|
296
|
+
ariaLabel="Transition preset"
|
|
297
|
+
options={options}
|
|
298
|
+
value={stored}
|
|
299
|
+
// The trigger shows the PLACEHOLDER for an empty value, so "Cut"
|
|
300
|
+
// is spelled twice: once as the option, once as the closed face.
|
|
301
|
+
placeholder="Cut"
|
|
302
|
+
autoOpen
|
|
303
|
+
onChange={(kind) => {
|
|
304
|
+
setBrowsing(false);
|
|
305
|
+
setKind(kind);
|
|
306
|
+
}}
|
|
307
|
+
onClose={() => setBrowsing(false)}
|
|
308
|
+
/>
|
|
309
|
+
) : (
|
|
310
|
+
<button type="button" className="link-button" onClick={() => setBrowsing(true)}>
|
|
311
|
+
Browse all {presetCount}
|
|
312
|
+
</button>
|
|
313
|
+
)}
|
|
314
|
+
</InspectorRow>
|
|
315
|
+
)}
|
|
316
|
+
|
|
317
|
+
{/* THE LENGTH, a standard field: its name inside, the frames, and the
|
|
318
|
+
seconds they last beside them in the same box. A cut has no length
|
|
319
|
+
and says so in the same place, in the house readout, rather than
|
|
320
|
+
leaving a hole where the field will be. */}
|
|
321
|
+
{transition === undefined ? (
|
|
322
|
+
<InspectorRow label={lengthLabel} className="insp-readout">
|
|
323
|
+
<span className="insp-dim" data-testid="transition-instant">
|
|
324
|
+
0 f · instant
|
|
325
|
+
</span>
|
|
326
|
+
</InspectorRow>
|
|
327
|
+
) : (
|
|
328
|
+
<InspectorRow>
|
|
329
|
+
<FieldBox>
|
|
330
|
+
{/* The name starts with the word in the box, then says what the
|
|
331
|
+
number is, so a screen reader and a speech command use the
|
|
332
|
+
word the author reads, in both chromes. No ceiling: the stored
|
|
333
|
+
number is the author's, and the resolver clamps it to what the
|
|
334
|
+
next shot can hold (see the note below).
|
|
335
|
+
|
|
336
|
+
The word stays IN the box whatever the field-label preference:
|
|
337
|
+
it is a word already, and named mode would print the whole
|
|
338
|
+
accessible name ("Duration, transition frames") above it. */}
|
|
339
|
+
<FieldLabels value="compact">
|
|
340
|
+
<NumberField
|
|
341
|
+
value={transition.durationFrames}
|
|
342
|
+
label={lengthLabel}
|
|
343
|
+
ariaLabel={`${lengthLabel}, transition frames`}
|
|
344
|
+
unit="f"
|
|
345
|
+
min={MIN_TRANSITION_FRAMES}
|
|
346
|
+
step={1}
|
|
347
|
+
onScrubStart={lengthDrag.start}
|
|
348
|
+
onScrub={lengthDrag.move}
|
|
349
|
+
onScrubEnd={lengthDrag.end}
|
|
350
|
+
onCommit={(v) => write({ durationFrames: Math.round(v) })}
|
|
351
|
+
/>
|
|
352
|
+
</FieldLabels>
|
|
353
|
+
<span className="insp-length-secs" data-testid="transition-seconds">
|
|
354
|
+
{formatDuration(framesToSeconds(transition.durationFrames, doc.fps))}
|
|
355
|
+
</span>
|
|
356
|
+
</FieldBox>
|
|
357
|
+
</InspectorRow>
|
|
358
|
+
)}
|
|
91
359
|
|
|
92
360
|
{transition !== undefined ? (
|
|
93
361
|
<>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
362
|
+
{/* The timing curve over the blend, THE SAME `Ease` a camera move
|
|
363
|
+
carries, through the same `EaseControl`. An absent ease
|
|
364
|
+
shows as Linear because Linear is what it plays. The bezier pad's
|
|
365
|
+
handle drag goes through `onScrub` inside the pad's own scrub
|
|
366
|
+
bracket, so one drag is one undo entry. */}
|
|
367
|
+
<EaseControl
|
|
368
|
+
ease={ease}
|
|
369
|
+
ariaLabel="Transition ease"
|
|
370
|
+
onEase={(next) => write({ ease: next })}
|
|
371
|
+
onScrub={(next) => write({ ease: next })}
|
|
372
|
+
/>
|
|
373
|
+
<InspectorRow label="Outgoing">
|
|
374
|
+
<SegmentedControl
|
|
375
|
+
ariaLabel="Outgoing"
|
|
376
|
+
options={[
|
|
377
|
+
{ value: "hold", label: "Hold" },
|
|
378
|
+
{ value: "run", label: "Run" },
|
|
379
|
+
]}
|
|
380
|
+
value={outgoing}
|
|
381
|
+
onChange={(v) => write({ outgoing: v === "run" ? "run" : "hold" })}
|
|
106
382
|
/>
|
|
107
383
|
</InspectorRow>
|
|
384
|
+
<p className="insp-note">
|
|
385
|
+
Hold freezes {scene.name} on its last frame. Run keeps it playing.
|
|
386
|
+
</p>
|
|
387
|
+
<p className="insp-note" data-testid="transition-held">
|
|
388
|
+
{outgoing === "hold"
|
|
389
|
+
? `Held at ${timecode(scene.durationFrames, doc.fps)}`
|
|
390
|
+
: `${scene.name} keeps playing under the blend.`}
|
|
391
|
+
</p>
|
|
108
392
|
{/* The stored number is the AUTHOR's, and the resolver clamps it to
|
|
109
|
-
whatever the next shot can hold
|
|
110
|
-
back again does not quietly rewrite what was typed.
|
|
111
|
-
because the discrepancy is otherwise invisible. */}
|
|
393
|
+
whatever the next shot can hold, so trimming that shot short and
|
|
394
|
+
back again does not quietly rewrite what was typed. */}
|
|
112
395
|
{next !== null && transition.durationFrames > next.durationFrames ? (
|
|
113
|
-
<p className="
|
|
114
|
-
Longer than {next.name} ({next.durationFrames}f)
|
|
396
|
+
<p className="insp-note">
|
|
397
|
+
Longer than {next.name} ({next.durationFrames}f), so it plays over
|
|
115
398
|
that whole shot.
|
|
116
399
|
</p>
|
|
117
400
|
) : null}
|
|
118
401
|
{isLast ? (
|
|
119
|
-
<p className="
|
|
402
|
+
<p className="insp-note">
|
|
120
403
|
This is the last shot, so nothing plays. Kept for when one follows
|
|
121
404
|
it.
|
|
122
405
|
</p>
|
|
123
406
|
) : null}
|
|
124
|
-
{spec?.params.map((p) =>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
407
|
+
{spec?.params.map((p) => {
|
|
408
|
+
const value = transition.params?.[p.name] ?? p.default;
|
|
409
|
+
return (
|
|
410
|
+
<Fragment key={p.name}>
|
|
411
|
+
<ParamRow
|
|
412
|
+
spec={p}
|
|
413
|
+
value={value}
|
|
414
|
+
onCommit={(v) => setParam(p.name, v)}
|
|
415
|
+
// ParamRow coerces a drag's value through the lane's one
|
|
416
|
+
// param coercion before it gets here.
|
|
417
|
+
scrub={drag((v) => setParam(p.name, v as Value))}
|
|
418
|
+
/>
|
|
419
|
+
{p.kind === "vec2" && p.name === "direction" ? (
|
|
420
|
+
<DirectionStepper value={value} onStep={(v) => setParam(p.name, v)} />
|
|
421
|
+
) : null}
|
|
422
|
+
</Fragment>
|
|
423
|
+
);
|
|
424
|
+
})}
|
|
132
425
|
</>
|
|
133
426
|
) : null}
|
|
427
|
+
</>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** One press turns the direction an eighth of a circle. The arrow is drawn
|
|
432
|
+
* in the presets' y-up space, so screen rotation is the negated angle.
|
|
433
|
+
*
|
|
434
|
+
* Its name starts with the words it shows ("x 1, y -1, step direction"), the
|
|
435
|
+
* house "visible words, then what" pattern: a name of its verb alone did not
|
|
436
|
+
* contain the vector the author reads on it, so a speech command saying what
|
|
437
|
+
* is on screen found no button. */
|
|
438
|
+
function DirectionStepper(props: { value: Value; onStep(next: [number, number]): void }) {
|
|
439
|
+
const locked = useLocked();
|
|
440
|
+
const v = Array.isArray(props.value) ? props.value : [1, -1];
|
|
441
|
+
const x = Number(v[0] ?? 1);
|
|
442
|
+
const y = Number(v[1] ?? -1);
|
|
443
|
+
const degrees = (Math.atan2(y, x) * 180) / Math.PI;
|
|
444
|
+
const shown = `x ${x}, y ${y}`;
|
|
445
|
+
return (
|
|
446
|
+
<InspectorRow>
|
|
447
|
+
<div className="insp-buttons">
|
|
448
|
+
<button
|
|
449
|
+
type="button"
|
|
450
|
+
className="btn insp-direction"
|
|
451
|
+
aria-label={`${shown}, step direction`}
|
|
452
|
+
title="Turn the direction an eighth of a circle"
|
|
453
|
+
disabled={locked}
|
|
454
|
+
onClick={() => props.onStep(nextDirection(props.value))}
|
|
455
|
+
>
|
|
456
|
+
<span className="insp-direction-arrow" style={{ transform: `rotate(${-degrees}deg)` }} aria-hidden>
|
|
457
|
+
{"→"}
|
|
458
|
+
</span>
|
|
459
|
+
<span data-testid="direction-readout">{shown}</span>
|
|
460
|
+
</button>
|
|
461
|
+
</div>
|
|
462
|
+
</InspectorRow>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ── THE BOARD'S CUT ──────────────────────────────────────────────────────────
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* THE CUT ON THE BOARD: the seam's rows for the shot it is saved on, how it
|
|
470
|
+
* hands over, where the blend sits, and the way into Studio. The board does
|
|
471
|
+
* not play, so the seam is drawn, not looped: Studio plays it. Self-gates on
|
|
472
|
+
* the board's subject being a cut, like the other storyboard sections.
|
|
473
|
+
*/
|
|
474
|
+
export function StoryboardCutSection() {
|
|
475
|
+
const store = useStore();
|
|
476
|
+
const open = useShotEditor((s) => s.storyboardOpen);
|
|
477
|
+
const selection = useShotEditor((s) => s.storyboardSelection);
|
|
478
|
+
const doc = useShotEditor((s) => s.doc);
|
|
479
|
+
const subject = open ? boardSubjectOf(doc, selection) : null;
|
|
480
|
+
if (doc === null || subject === null || subject.kind !== "cut") return null;
|
|
481
|
+
const sceneId = subject.sceneId;
|
|
482
|
+
const scene = doc.scenesById[sceneId];
|
|
483
|
+
if (scene === undefined) return null;
|
|
484
|
+
const window = seamWindow(doc, sceneId);
|
|
485
|
+
|
|
486
|
+
const fineTune = (): void => {
|
|
487
|
+
store.setStoryboardOpen(false);
|
|
488
|
+
store.leaveScene();
|
|
489
|
+
store.setScene(sceneId);
|
|
490
|
+
// The frame the cut diamond parks on: the last one the blend starts from.
|
|
491
|
+
// Without the seek, the next one re-pins the shot to wherever the playhead was.
|
|
492
|
+
const start = sceneStart(doc, sceneId) ?? 0;
|
|
493
|
+
store.seek(Math.max(start, start + scene.durationFrames - 1));
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<InspectorSection title="Transition" persistKey="shot:storyboard-cut" collapsible={false}>
|
|
498
|
+
<TransitionRows sceneId={sceneId} lengthLabel="Length" />
|
|
499
|
+
<Handover doc={doc} sceneId={sceneId} />
|
|
500
|
+
{/* A READOUT, not a field: the seam is where the blend sits, drawn and
|
|
501
|
+
read, and nothing in it takes input. So it wears the house readout
|
|
502
|
+
(no hover, the reading in the numeral face), never the pill an
|
|
503
|
+
author would try to type into. */}
|
|
504
|
+
{window === null ? null : (
|
|
505
|
+
<InspectorRow label="Seam" className="insp-readout">
|
|
506
|
+
<div className="insp-seam">
|
|
507
|
+
<SeamTrack start={window.start} end={window.end} cut={window.cut} d={window.durationFrames} fps={doc.fps} />
|
|
508
|
+
</div>
|
|
509
|
+
</InspectorRow>
|
|
510
|
+
)}
|
|
511
|
+
<InspectorRow>
|
|
512
|
+
<button type="button" className="link-button" onClick={fineTune}>
|
|
513
|
+
Fine-tune in Studio
|
|
514
|
+
</button>
|
|
515
|
+
</InspectorRow>
|
|
134
516
|
</InspectorSection>
|
|
135
517
|
);
|
|
136
518
|
}
|
|
519
|
+
|
|
520
|
+
/** HOW IT HANDS OVER: the notes stored on the outgoing planned shot, or,
|
|
521
|
+
* without any, what the transition itself says. */
|
|
522
|
+
function Handover(props: { doc: Doc3D; sceneId: string }) {
|
|
523
|
+
const { doc, sceneId } = props;
|
|
524
|
+
const scene = doc.scenesById[sceneId]!;
|
|
525
|
+
const handover = partOfScene(doc, sceneId)?.handover;
|
|
526
|
+
const notes = handover?.notes?.trim() ?? "";
|
|
527
|
+
const beats = handover?.motion ?? [];
|
|
528
|
+
const transition = scene.transition;
|
|
529
|
+
const derived =
|
|
530
|
+
transition === undefined
|
|
531
|
+
? ["A hard cut."]
|
|
532
|
+
: [
|
|
533
|
+
`${resolveGlPresetSpec(transition.kind)?.label ?? transition.kind} over ${formatDuration(framesToSeconds(transition.durationFrames, doc.fps))}.`,
|
|
534
|
+
transition.outgoing === "run"
|
|
535
|
+
? `${scene.name} keeps playing under it.`
|
|
536
|
+
: `${scene.name} holds on its last frame.`,
|
|
537
|
+
];
|
|
538
|
+
return (
|
|
539
|
+
<section className="notes-block-section" data-testid="cut-handover">
|
|
540
|
+
<h4 className="notes-block-heading">How it hands over</h4>
|
|
541
|
+
{notes === "" && beats.length === 0 ? (
|
|
542
|
+
derived.map((line) => (
|
|
543
|
+
<p key={line} className="notes-block-text">
|
|
544
|
+
{line}
|
|
545
|
+
</p>
|
|
546
|
+
))
|
|
547
|
+
) : (
|
|
548
|
+
<>
|
|
549
|
+
{notes === "" ? null : <p className="notes-block-text">{notes}</p>}
|
|
550
|
+
{beats.length === 0 ? null : (
|
|
551
|
+
<ol className="notes-block-beats">
|
|
552
|
+
{beats.map((beat, at) => (
|
|
553
|
+
<li key={at} className="notes-block-beat">
|
|
554
|
+
<span className="notes-block-number">{at + 1}</span>
|
|
555
|
+
<span className="notes-block-beat-text">{beat.text}</span>
|
|
556
|
+
</li>
|
|
557
|
+
))}
|
|
558
|
+
</ol>
|
|
559
|
+
)}
|
|
560
|
+
</>
|
|
561
|
+
)}
|
|
562
|
+
</section>
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** The seam's three spans (the lead-in, the blend, the run-out) and where the
|
|
567
|
+
* playhead is inside the blend. Its own component: it reads the playhead,
|
|
568
|
+
* which moves with every scrub. */
|
|
569
|
+
function SeamTrack(props: { start: number; end: number; cut: number; d: number; fps: Doc3D["fps"] }) {
|
|
570
|
+
const { start, end, cut, d, fps } = props;
|
|
571
|
+
const frame = useShotEditor((s) => s.frame);
|
|
572
|
+
const total = Math.max(1, end - start);
|
|
573
|
+
const pct = (n: number): string => `${(Math.max(0, n) / total) * 100}%`;
|
|
574
|
+
const into = Math.min(Math.max(frame - cut, 0), d);
|
|
575
|
+
return (
|
|
576
|
+
<>
|
|
577
|
+
<div className="insp-seam-track" aria-hidden>
|
|
578
|
+
<span className="insp-seam-span" style={{ width: pct(cut - start) }} />
|
|
579
|
+
<span className="insp-seam-span is-blend" style={{ width: pct(d) }} />
|
|
580
|
+
<span className="insp-seam-span" style={{ width: pct(end - cut - d) }} />
|
|
581
|
+
</div>
|
|
582
|
+
<span className="insp-dim" data-testid="seam-readout">
|
|
583
|
+
{`${timecode(into, fps)} / ${formatDuration(framesToSeconds(d, fps))}`}
|
|
584
|
+
</span>
|
|
585
|
+
</>
|
|
586
|
+
);
|
|
587
|
+
}
|