@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,180 @@
|
|
|
1
|
+
// MAKE IT YOURS: the open Project's template fields (schema v50), one row each,
|
|
2
|
+
// for the person who started from a template and now swaps in their own
|
|
3
|
+
// screenshot, headline and logo.
|
|
4
|
+
//
|
|
5
|
+
// A container section: it shows with nothing selected in Studio, above the
|
|
6
|
+
// Shots and inside one alike, and only while `swapFieldsOf` lists a field (a
|
|
7
|
+
// data gate, layout.ts). The rows keep that list's order, the order a viewer
|
|
8
|
+
// meets the fields: the Shots in playing order, then the definitions.
|
|
9
|
+
//
|
|
10
|
+
// ── EVERY WRITE IS AN EXISTING ONE, AIMED AT THE ROW'S OWN LAYER ────────
|
|
11
|
+
//
|
|
12
|
+
// A media row's Replace is the Media section's `replaceLayerMedia`, and a text
|
|
13
|
+
// row writes `text.content` through `writeSelection`, the Text section's path,
|
|
14
|
+
// each told the field's container: a field can sit in a Shot the playhead is
|
|
15
|
+
// not in, or in a definition. So a viewer's refusal, the lock rule and the one
|
|
16
|
+
// undo step are theirs. A locked layer keeps its row, its control locked, and
|
|
17
|
+
// the lock's own sentence on it. The row's name only navigates (into its Shot,
|
|
18
|
+
// or its component, with the layer selected), so it stays live for a viewer.
|
|
19
|
+
import { useMemo, useRef, useState } from "react";
|
|
20
|
+
import { InspectorSection, TextField, useLocked } from "@dailies-studio/ui";
|
|
21
|
+
import { containerFrame, homeOf, lockRefusal, swapFieldsOf } from "@dailies-studio/shot/doc";
|
|
22
|
+
import type { Doc3D, Layer3D, SwapField } from "@dailies-studio/shot/doc";
|
|
23
|
+
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
24
|
+
import type { ShotEditorStore } from "../../../state/store.js";
|
|
25
|
+
import { writeSelection } from "../../../state/fieldWrite.js";
|
|
26
|
+
import { authoringFrame } from "../../../state/viewMode.js";
|
|
27
|
+
import { IMAGE_ACCEPT, VIDEO_ACCEPT } from "../../toolbar/useAddRows.js";
|
|
28
|
+
import { MediaPoster, assetUrl } from "../../storyboard/ReferencePreview.js";
|
|
29
|
+
|
|
30
|
+
export function MakeItYoursSection() {
|
|
31
|
+
const doc = useShotEditor((s) => s.doc);
|
|
32
|
+
const fields = useMemo(() => (doc === null ? [] : swapFieldsOf(doc)), [doc]);
|
|
33
|
+
if (doc === null || fields.length === 0) return null;
|
|
34
|
+
return (
|
|
35
|
+
<InspectorSection
|
|
36
|
+
title="Make it yours"
|
|
37
|
+
persistKey="shot:make-it-yours"
|
|
38
|
+
summary={`${fields.length} ${fields.length === 1 ? "field" : "fields"}`}
|
|
39
|
+
>
|
|
40
|
+
<ul className="make-it-yours" data-testid="make-it-yours">
|
|
41
|
+
{fields.map((field) => (
|
|
42
|
+
<FieldRow key={`${field.containerId}/${field.layerId}`} doc={doc} field={field} />
|
|
43
|
+
))}
|
|
44
|
+
</ul>
|
|
45
|
+
</InspectorSection>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function FieldRow({ doc, field }: { doc: Doc3D; field: SwapField }) {
|
|
50
|
+
const store = useStore();
|
|
51
|
+
const layer = homeOf(doc, field.containerId)?.layersById[field.layerId];
|
|
52
|
+
if (layer === undefined) return null;
|
|
53
|
+
const lock = layer.locked === true ? lockRefusal(layer) : null;
|
|
54
|
+
const where = [
|
|
55
|
+
field.containerName,
|
|
56
|
+
...(field.container === "component" ? ["changes every use"] : []),
|
|
57
|
+
...(lock === null ? [] : ["Locked"]),
|
|
58
|
+
].join(" · ");
|
|
59
|
+
return (
|
|
60
|
+
<li className="make-it-yours-row" data-field={field.layerId}>
|
|
61
|
+
<div className="make-it-yours-head">
|
|
62
|
+
{layer.kind === "image" || layer.kind === "video" ? <MediaThumb doc={doc} layer={layer} /> : null}
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
className="make-it-yours-name"
|
|
66
|
+
title={field.container === "component" ? `Open ${field.containerName} and select this layer` : `Go to ${field.containerName} and select this layer`}
|
|
67
|
+
onClick={() => goToField(store, field)}
|
|
68
|
+
>
|
|
69
|
+
<span className="make-it-yours-label">{field.label}</span>
|
|
70
|
+
<span className="make-it-yours-where">{where}</span>
|
|
71
|
+
</button>
|
|
72
|
+
{layer.kind === "image" || layer.kind === "video" ? <ReplaceButton field={field} kind={layer.kind} lock={lock} /> : null}
|
|
73
|
+
</div>
|
|
74
|
+
{layer.kind === "text" ? (
|
|
75
|
+
<TextField
|
|
76
|
+
value={layer.text.content}
|
|
77
|
+
ariaLabel={`${field.label}, text`}
|
|
78
|
+
// A one-line field commits on Enter; content that already breaks a
|
|
79
|
+
// line keeps the textarea, so an edit never folds its lines.
|
|
80
|
+
multiline={layer.text.content.includes("\n")}
|
|
81
|
+
{...(lock === null ? {} : { readOnly: true, title: lock })}
|
|
82
|
+
onCommit={(content) => writeText(store, field, content)}
|
|
83
|
+
/>
|
|
84
|
+
) : null}
|
|
85
|
+
</li>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The field's picture as it stands: the stored file, small. */
|
|
90
|
+
function MediaThumb({ doc, layer }: { doc: Doc3D; layer: Extract<Layer3D, { kind: "image" | "video" }> }) {
|
|
91
|
+
const asset = doc.assets[layer.kind === "image" ? layer.image.asset : layer.video.asset];
|
|
92
|
+
return (
|
|
93
|
+
<span className="make-it-yours-thumb" aria-hidden="true" {...(asset === undefined ? { title: "File missing" } : {})}>
|
|
94
|
+
{asset === undefined ? null : layer.kind === "image" ? (
|
|
95
|
+
<img className="make-it-yours-media" src={assetUrl(asset.path)} alt="" draggable={false} loading="lazy" />
|
|
96
|
+
) : (
|
|
97
|
+
<MediaPoster className="make-it-yours-media" src={assetUrl(asset.path)} atSeconds={null} />
|
|
98
|
+
)}
|
|
99
|
+
</span>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Replace…, the Media section's verb, for the row's layer. A locked layer's
|
|
104
|
+
* press says why instead of opening the picker. */
|
|
105
|
+
function ReplaceButton({ field, kind, lock }: { field: SwapField; kind: "image" | "video"; lock: string | null }) {
|
|
106
|
+
const store = useStore();
|
|
107
|
+
const locked = useLocked();
|
|
108
|
+
const fileRef = useRef<HTMLInputElement>(null);
|
|
109
|
+
const [busy, setBusy] = useState(false);
|
|
110
|
+
const pick = (file: File): void => {
|
|
111
|
+
setBusy(true);
|
|
112
|
+
void store
|
|
113
|
+
.replaceLayerMedia(field.layerId, file, field.containerId)
|
|
114
|
+
.catch((e: unknown) => store.reportError(e instanceof Error ? e.message : String(e)))
|
|
115
|
+
.finally(() => setBusy(false));
|
|
116
|
+
};
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
className="btn"
|
|
122
|
+
aria-label={`Replace, ${field.label}`}
|
|
123
|
+
title={lock ?? `Swap ${field.label} for a file of your own`}
|
|
124
|
+
disabled={locked || busy}
|
|
125
|
+
aria-disabled={lock !== null || undefined}
|
|
126
|
+
onClick={() => {
|
|
127
|
+
if (lock === null) fileRef.current?.click();
|
|
128
|
+
else store.reportError(lock);
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
Replace…
|
|
132
|
+
</button>
|
|
133
|
+
<input
|
|
134
|
+
ref={fileRef}
|
|
135
|
+
type="file"
|
|
136
|
+
hidden
|
|
137
|
+
aria-label={`${field.label}, ${kind} file`}
|
|
138
|
+
accept={kind === "video" ? VIDEO_ACCEPT : IMAGE_ACCEPT}
|
|
139
|
+
onChange={(event) => {
|
|
140
|
+
const file = event.currentTarget.files?.[0];
|
|
141
|
+
event.currentTarget.value = "";
|
|
142
|
+
if (file !== undefined) pick(file);
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
</>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The Text section's write, `writeSelection` over the one layer, in the
|
|
150
|
+
* field's own container: one undo step, a locked layer named. */
|
|
151
|
+
function writeText(store: ShotEditorStore, field: SwapField, content: string): void {
|
|
152
|
+
const s = store.getState();
|
|
153
|
+
if (s.doc === null) return;
|
|
154
|
+
const at = containerFrame(s.doc, field.containerId, authoringFrame(s));
|
|
155
|
+
let refusal: string | null = null;
|
|
156
|
+
const ran = store.edit((doc) => {
|
|
157
|
+
const out = writeSelection(
|
|
158
|
+
doc,
|
|
159
|
+
field.containerId,
|
|
160
|
+
[field.layerId],
|
|
161
|
+
(layer) => (layer.kind === "text" ? ({ ...layer, text: { ...layer.text, content } } as Layer3D) : layer),
|
|
162
|
+
at,
|
|
163
|
+
);
|
|
164
|
+
refusal = out.refusal;
|
|
165
|
+
return out.doc;
|
|
166
|
+
});
|
|
167
|
+
if (ran && refusal !== null) store.reportError(refusal);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Into the field's Shot, or its component's definition, with the layer
|
|
171
|
+
* selected: the verbs Every use's Go to takes (`goToComponentUse`). */
|
|
172
|
+
function goToField(store: ShotEditorStore, field: SwapField): void {
|
|
173
|
+
if (field.container === "component") {
|
|
174
|
+
store.editComponent(field.containerId);
|
|
175
|
+
if (store.getState().editingDef === field.containerId) store.select([field.layerId]);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
store.enterScene(field.containerId);
|
|
179
|
+
if (store.getState().enteredScene === field.containerId) store.select([field.layerId]);
|
|
180
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// THE PROJECT ITSELF: the document's own four fields, shown in both contexts
|
|
2
|
+
// when nothing is selected.
|
|
3
|
+
//
|
|
4
|
+
// ── COLLAPSIBLE IN BOTH, OPEN BY DEFAULT ONLY WHERE IT IS THE SUBJECT ─────
|
|
5
|
+
//
|
|
6
|
+
// It was pinned open and mounted in the project context alone. Inside a shot
|
|
7
|
+
// the size and rate are still the frame the author is composing into, so the
|
|
8
|
+
// rail now carries the section there too, collapsed to one mono line
|
|
9
|
+
// ("1920×1080 · 30fps") that answers the question without the four rows. In
|
|
10
|
+
// the project context the rows ARE the work, so it opens by default there.
|
|
11
|
+
// The two contexts keep separate collapse bits for that reason: one shared bit
|
|
12
|
+
// would make collapsing it inside a shot hide the project's own panel.
|
|
13
|
+
//
|
|
14
|
+
// ── WHY THIS DID NOT EXIST ──────────────────────────────────────────────
|
|
15
|
+
//
|
|
16
|
+
// The rail's empty-selection branch mounted the SHOT sections in both contexts,
|
|
17
|
+
// so the scene context described whichever shot the playhead was over and
|
|
18
|
+
// changed under every scrub across a cut. The document's own fields had no
|
|
19
|
+
// writer anywhere in the chrome: `doc.fps` and `doc.size` were read by the
|
|
20
|
+
// export dialog and by nothing else, and `doc.name` by nothing at all. A tool
|
|
21
|
+
// that can open a video, arrange it and render it, but cannot say what rate or
|
|
22
|
+
// what size it is, is missing a panel rather than a feature.
|
|
23
|
+
//
|
|
24
|
+
// ── THE RATE IS A PICKER AND NOT A NUMBER ────────────────────────────────
|
|
25
|
+
//
|
|
26
|
+
// `doc.fps` is a RATIONAL, and the rates that matter are not all integers, so
|
|
27
|
+
// the control offers exact pairs from `FPS_OPTIONS` (the one table both editors
|
|
28
|
+
// read) rather than a decimal field. A document naming a rate that list does not
|
|
29
|
+
// have keeps it, labelled, exactly as the transition picker keeps an unknown
|
|
30
|
+
// preset rather than silently rewriting it.
|
|
31
|
+
import { Combobox, InspectorRow, InspectorSection, NumberField, TextField } from "@dailies-studio/ui";
|
|
32
|
+
import type { ComboboxOption } from "@dailies-studio/ui";
|
|
33
|
+
import { FPS_OPTIONS } from "@dailies-studio/timeline";
|
|
34
|
+
import { docDurationFrames } from "@dailies-studio/shot/doc";
|
|
35
|
+
import { framesToSeconds } from "@dailies-studio/engine";
|
|
36
|
+
import type { Rational } from "@dailies-studio/engine";
|
|
37
|
+
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
38
|
+
import { SECTION_ICONS } from "../../../icons.js";
|
|
39
|
+
import { formatDuration } from "../../timeline/model.js";
|
|
40
|
+
|
|
41
|
+
/** "<num>/<den>" — the picker's value, so an exact rational round-trips through
|
|
42
|
+
* a string-valued control without a lookup table on the way back. */
|
|
43
|
+
const rateKey = (fps: Rational): string => `${fps.num}/${fps.den}`;
|
|
44
|
+
|
|
45
|
+
/** Said on the label and on the field, because either one may be what the
|
|
46
|
+
* pointer lands on and a field that moves a source file should warn from
|
|
47
|
+
* both. */
|
|
48
|
+
const COMPONENT_TITLE =
|
|
49
|
+
"The generated component's name. Changing it moves src/shots/<Name>.tsx on the next save.";
|
|
50
|
+
|
|
51
|
+
export function SceneDocSection() {
|
|
52
|
+
const store = useStore();
|
|
53
|
+
const doc = useShotEditor((s) => s.doc);
|
|
54
|
+
const inShot = useShotEditor((s) => s.enteredScene !== null);
|
|
55
|
+
if (doc === null) return null;
|
|
56
|
+
|
|
57
|
+
const total = docDurationFrames(doc);
|
|
58
|
+
const key = rateKey(doc.fps);
|
|
59
|
+
const known = FPS_OPTIONS.some((r) => rateKey(r.fps) === key);
|
|
60
|
+
const options: ComboboxOption[] = [
|
|
61
|
+
// FPS_OPTIONS is fastest-first, as the motion editor has always shown it;
|
|
62
|
+
// this picker reads slowest-first, so the copy is what gets reversed.
|
|
63
|
+
...FPS_OPTIONS.map((r) => ({ value: rateKey(r.fps), label: r.label })).reverse(),
|
|
64
|
+
// A rate this build's list does not carry is KEPT and named. Dropping to
|
|
65
|
+
// the nearest one would retime the whole video on the first unrelated edit.
|
|
66
|
+
...(known ? [] : [{ value: key, label: `${(doc.fps.num / doc.fps.den).toFixed(3)} (${key})` }]),
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// The rate as a plain number: 30, 29.97, 23.976. Three places at most and
|
|
70
|
+
// trailing zeros dropped, so an integer rate does not read "30.000".
|
|
71
|
+
const fps = Number((doc.fps.num / doc.fps.den).toFixed(3));
|
|
72
|
+
|
|
73
|
+
// A DRAG on W or H resizes the frame under the pointer, as every other
|
|
74
|
+
// dragged field writes: the panel scrub around `setDocSize`, which captures
|
|
75
|
+
// into it while it is open (store.ts), so the whole drag is one undo entry.
|
|
76
|
+
// Each move names the one side it moves and keeps the other as drawn.
|
|
77
|
+
const dragSize = (side: "width" | "height") => ({
|
|
78
|
+
onScrubStart: () => store.beginScrub(),
|
|
79
|
+
onScrub: (value: number) => store.setDocSize({ ...doc.size, [side]: value }),
|
|
80
|
+
onScrubEnd: (commit: boolean) => store.endScrub(commit),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<InspectorSection
|
|
85
|
+
title="Project"
|
|
86
|
+
icon={SECTION_ICONS.project}
|
|
87
|
+
summary={`${doc.size.width}×${doc.size.height} · ${fps}fps`}
|
|
88
|
+
numericSummary
|
|
89
|
+
// `shot:document` is the project context's existing bit, kept so nobody's
|
|
90
|
+
// stored collapse state is thrown away.
|
|
91
|
+
persistKey={inShot ? "shot:document:in-shot" : "shot:document"}
|
|
92
|
+
defaultCollapsed={inShot}
|
|
93
|
+
// No section scope: the rhythm, the fields and the readout face its two
|
|
94
|
+
// derived rows wear are the house's (widgets.css), for every panel alike.
|
|
95
|
+
>
|
|
96
|
+
{/* "Component" and not "Generated component": `.insp-label` is capped at
|
|
97
|
+
60% of a 288px pill, so the longer wording ellipsised ITSELF and then
|
|
98
|
+
truncated the value beside it, which is the one thing on the row an
|
|
99
|
+
author needs to read. The full story stays on the label's title and in
|
|
100
|
+
the aria name, where length costs nothing. */}
|
|
101
|
+
<InspectorRow label="Component" labelTitle={COMPONENT_TITLE}>
|
|
102
|
+
{/* `doc.name`, not `state.name`. The latter is the stable Project slug
|
|
103
|
+
used by the URL and server; this is the name the
|
|
104
|
+
generated component takes, so committing it moves
|
|
105
|
+
`src/shots/<Name>.tsx` on the next save. The title says so, because
|
|
106
|
+
a field that silently moves a source file is worse than one that
|
|
107
|
+
warns. */}
|
|
108
|
+
<TextField
|
|
109
|
+
value={doc.name}
|
|
110
|
+
ariaLabel="Generated component name"
|
|
111
|
+
title={COMPONENT_TITLE}
|
|
112
|
+
onCommit={(name) => store.setDocName(name)}
|
|
113
|
+
/>
|
|
114
|
+
</InspectorRow>
|
|
115
|
+
{/* THE FRAME'S SIZE, A PAIR LIKE TRANSFORM'S W | H: two boxes, each
|
|
116
|
+
its own field with its prefix inside, and no "Size" pill around them
|
|
117
|
+
(a labelled box holding two more boxes drew the name twice and
|
|
118
|
+
squeezed both values). Whole pixels from one up, the store's clamp
|
|
119
|
+
stated where the author types. */}
|
|
120
|
+
<InspectorRow>
|
|
121
|
+
<div className="insp-vec">
|
|
122
|
+
<NumberField
|
|
123
|
+
value={doc.size.width}
|
|
124
|
+
label="W"
|
|
125
|
+
ariaLabel="Project width"
|
|
126
|
+
unit="px"
|
|
127
|
+
min={1}
|
|
128
|
+
step={1}
|
|
129
|
+
{...dragSize("width")}
|
|
130
|
+
onCommit={(width) => store.setDocSize({ ...doc.size, width })}
|
|
131
|
+
/>
|
|
132
|
+
<NumberField
|
|
133
|
+
value={doc.size.height}
|
|
134
|
+
label="H"
|
|
135
|
+
ariaLabel="Project height"
|
|
136
|
+
unit="px"
|
|
137
|
+
min={1}
|
|
138
|
+
step={1}
|
|
139
|
+
{...dragSize("height")}
|
|
140
|
+
onCommit={(height) => store.setDocSize({ ...doc.size, height })}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
</InspectorRow>
|
|
144
|
+
<InspectorRow label="Rate">
|
|
145
|
+
<Combobox
|
|
146
|
+
ariaLabel="Frame rate"
|
|
147
|
+
options={options}
|
|
148
|
+
value={key}
|
|
149
|
+
onChange={(next) => {
|
|
150
|
+
const [num, den] = next.split("/").map(Number);
|
|
151
|
+
if (num === undefined || den === undefined) return;
|
|
152
|
+
store.setDocFps({ num, den });
|
|
153
|
+
}}
|
|
154
|
+
/>
|
|
155
|
+
</InspectorRow>
|
|
156
|
+
{/* DERIVED, so both are text and not fields: the length of a scene is the
|
|
157
|
+
sum of its shots' durations, and the one place to change it is the
|
|
158
|
+
Duration row in the Shot section below. A writable total would need a
|
|
159
|
+
rule for which shot absorbed the difference, and there is no honest
|
|
160
|
+
answer to that.
|
|
161
|
+
|
|
162
|
+
SO THE WORD APPEARS TWICE IN THIS RAIL, once read-only here and once
|
|
163
|
+
editable there, and the section heading above each — Project, Shot —
|
|
164
|
+
is what separates them. That is the deliberate trade: the two rows
|
|
165
|
+
hold the same quantity in the same unit, and spelling one of them
|
|
166
|
+
differently would invent a distinction an author would go looking for
|
|
167
|
+
and not find.
|
|
168
|
+
|
|
169
|
+
TWO ROWS AND NOT ONE. "1:04 · 1920f · 7 shots" read as one cryptic
|
|
170
|
+
string because it answered two different questions at once: how long
|
|
171
|
+
the film runs, and how many pieces it is in. A labelled row each says
|
|
172
|
+
which number is which without a legend.
|
|
173
|
+
|
|
174
|
+
IN THE HOUSE READOUT (`insp-readout`, widgets.css), the Time
|
|
175
|
+
section's: the field's box with nothing to type into and no hover, the
|
|
176
|
+
number in the numeral face in the second ink. They were the 10px sans
|
|
177
|
+
`.insp-dim` alone, which set a total in a smaller face than every
|
|
178
|
+
value around it. */}
|
|
179
|
+
<InspectorRow label="Duration" className="insp-readout">
|
|
180
|
+
<span className="insp-dim" data-testid="scene-length">
|
|
181
|
+
{formatDuration(framesToSeconds(total, doc.fps))} · {total}f
|
|
182
|
+
</span>
|
|
183
|
+
</InspectorRow>
|
|
184
|
+
<InspectorRow label="Shots" className="insp-readout">
|
|
185
|
+
<span className="insp-dim" data-testid="scene-shots">
|
|
186
|
+
{doc.scenes.length}
|
|
187
|
+
</span>
|
|
188
|
+
</InspectorRow>
|
|
189
|
+
</InspectorSection>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -1,71 +1,246 @@
|
|
|
1
|
-
// The
|
|
2
|
-
// switch is the SELECTION, not a tab: a layer inspector and a
|
|
1
|
+
// The shot's own properties, shown when nothing is selected. The subject
|
|
2
|
+
// switch is the SELECTION, not a tab: a layer inspector and a shot inspector
|
|
3
3
|
// are the same panel looking at whatever the author last clicked.
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
//
|
|
5
|
+
// ── THE ROWS ARE SHARED, THE SECTION IS NOT ──────────────────────────────
|
|
6
|
+
//
|
|
7
|
+
// `ShotRows` is the body and two sections wear it. Inside a shot it is
|
|
8
|
+
// `SceneSection`, "Shot". In the project context it is the top half of
|
|
9
|
+
// "Shot & transition" (TransitionSection.tsx), because there the shot and the
|
|
10
|
+
// seam after it are read together. One body, so a row added here lands in
|
|
11
|
+
// both and the two can never disagree about what a shot is.
|
|
12
|
+
//
|
|
13
|
+
// Name and Duration are the shot's IDENTITY and its place on the master
|
|
14
|
+
// timeline, so they show in both contexts (the Name row with one exception,
|
|
15
|
+
// see its own note). Background is what the shot LOOKS like, a judgement made
|
|
16
|
+
// against the framed picture, so it stays inside the shot.
|
|
17
|
+
//
|
|
18
|
+
// THE LENS MOVED TO CAMERA as its FOV row. `fovDeg` is stored on the scene,
|
|
19
|
+
// but it is a camera property to the author: it sets how far the camera
|
|
20
|
+
// stands, which is what parallax strength is. `patchScene` is exported for
|
|
21
|
+
// that row so the lens keeps this file's one write path.
|
|
22
|
+
import { InspectorRow, InspectorSection, NumberField, TextField } from "@dailies-studio/ui";
|
|
23
|
+
import { containerFrame, trimScene } from "@dailies-studio/shot/doc";
|
|
24
|
+
import type { Doc3D, Scene3D } from "@dailies-studio/shot/doc";
|
|
25
|
+
import { SceneKeyframeDot } from "../KeyframeDot.js";
|
|
26
|
+
import { SECTION_ICONS, SPARKLES_ICON } from "../../../icons.js";
|
|
27
|
+
import { PaintRows } from "../design/PaintRows.js";
|
|
6
28
|
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
7
|
-
import {
|
|
29
|
+
import { backgroundLayerOf } from "../../../state/insert.js";
|
|
30
|
+
import { useInspectorScene, useScrub, writeScene } from "../useDocWrite.js";
|
|
8
31
|
|
|
32
|
+
/**
|
|
33
|
+
* One scene, patched, over the document it is HANDED.
|
|
34
|
+
*
|
|
35
|
+
* Both write paths run it: a typed value through `edit`, and a drag through
|
|
36
|
+
* `scrubCapture`, which re-derives from the gesture's BASE. Two spellings of
|
|
37
|
+
* this would be two answers to what a background drag did, and the drag's is
|
|
38
|
+
* the one nobody would be watching.
|
|
39
|
+
*/
|
|
40
|
+
export function patchScene(doc: Doc3D, sceneId: string, next: Partial<Scene3D>, frame: number): Doc3D {
|
|
41
|
+
// Through `writeScene`, so a shot that ANIMATES its background takes a
|
|
42
|
+
// keyframe at the playhead rather than a constant the track is already
|
|
43
|
+
// overriding (P5). Nothing else it writes is trackable (a name, a duration
|
|
44
|
+
// and the camera's lens are not per-frame values), so this is one path's
|
|
45
|
+
// rule riding the seam every field already crosses.
|
|
46
|
+
return writeScene(doc, sceneId, (scene) => ({ ...scene, ...next }), containerFrame(doc, sceneId, frame));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Inside a shot, the shot alone: the seam after it is off screen there, so the
|
|
51
|
+
* transition rows are not offered (see `CONTAINER_LAYOUT`).
|
|
52
|
+
*
|
|
53
|
+
* COLLAPSIBLE, like every sibling in the rail. It was pinned open because it
|
|
54
|
+
* was the only thing naming the shot, so collapsing it left the panel
|
|
55
|
+
* describing something it no longer named. The head carries the name now, so
|
|
56
|
+
* the section is free to behave like the rest of the stack. The `persistKey`
|
|
57
|
+
* is unchanged: it is invisible to the author, and renaming it would only
|
|
58
|
+
* throw away the collapse state people already have.
|
|
59
|
+
*/
|
|
9
60
|
export function SceneSection() {
|
|
61
|
+
// A boolean, so the selector returns a primitive and never allocates.
|
|
62
|
+
const has = useShotEditor((s) => s.sceneId !== null && s.doc?.scenesById[s.sceneId] !== undefined);
|
|
63
|
+
if (!has) return null;
|
|
64
|
+
// The same rows in the same look as "Shot & transition", which wears the
|
|
65
|
+
// same body: both are the house rows (widgets.css), with no section scope.
|
|
66
|
+
return (
|
|
67
|
+
<InspectorSection title="Shot" icon={SECTION_ICONS.shot} persistKey="shot:scene">
|
|
68
|
+
<ShotRows />
|
|
69
|
+
</InspectorSection>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The shot's own rows, the body both shot sections wear. */
|
|
74
|
+
export function ShotRows() {
|
|
10
75
|
const store = useStore();
|
|
11
76
|
const doc = useShotEditor((s) => s.doc);
|
|
12
77
|
const sceneId = useShotEditor((s) => s.sceneId);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
78
|
+
/** Inside a shot (or a definition, which mounts its own panel): the
|
|
79
|
+
* background rows below. The same bit the rest of the chrome branches on. */
|
|
80
|
+
const inShot = useShotEditor((s) => s.enteredScene !== null);
|
|
81
|
+
const readOnly = useShotEditor((s) => s.readOnly);
|
|
82
|
+
const ground = useShotEditor((s) =>
|
|
83
|
+
s.enteredScene === null ? null : backgroundLayerOf(s.doc?.scenesById[s.enteredScene]),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// ── THE DRAG HALF, DECLARED BEFORE THE EARLY RETURNS ──────────────────
|
|
87
|
+
//
|
|
88
|
+
// Before, because a hook that sits after a conditional return changes the
|
|
89
|
+
// hook count between renders. ONE bracket for every patchable field: the
|
|
90
|
+
// widget sends the PATCH it is pointing at, so every background control
|
|
91
|
+
// shares one capture rule instead of one hook each.
|
|
92
|
+
//
|
|
93
|
+
// The scene is read from the STORE inside the capture, never closed over
|
|
94
|
+
// from this render. It is the same live read `useSelectionScrub` makes, so a
|
|
95
|
+
// drag held across a scene switch cannot write into the scene the panel last
|
|
96
|
+
// rendered against.
|
|
97
|
+
const dragScene = useScrub((d, raw) => {
|
|
98
|
+
const id = store.getState().sceneId;
|
|
99
|
+
// The frame LIVE at the capture and against the document BEING WRITTEN.
|
|
100
|
+
return id === null ? d : patchScene(d, id, raw as Partial<Scene3D>, store.getState().frame);
|
|
101
|
+
});
|
|
102
|
+
// The duration goes through `trimScene` and not through the patch above,
|
|
103
|
+
// because that op owns the floor clamp and returns the base BY REFERENCE when
|
|
104
|
+
// the number did not move, so a drag that ends where it started commits
|
|
105
|
+
// nothing, exactly as the typed path behaves.
|
|
106
|
+
const dragDuration = useScrub((d, raw) => {
|
|
107
|
+
const id = store.getState().sceneId;
|
|
108
|
+
return id === null ? d : trimScene(d, id, Number(raw));
|
|
109
|
+
});
|
|
110
|
+
// TYPED at the call sites. `move` takes `unknown`, so without this a
|
|
111
|
+
// mistyped field name would ride a drag straight into the document and only
|
|
112
|
+
// show up as a control that does nothing.
|
|
113
|
+
const moveScene = (next: Partial<Scene3D>): void => dragScene.move(next);
|
|
114
|
+
|
|
115
|
+
// THE SHOT AS PAINTED, not the authored constants. `background.color` is
|
|
116
|
+
// keyframable, and once it carries a track the constant this used to read is
|
|
117
|
+
// pinned by `routeTrackedEdit` — so the well showed a colour the stage had
|
|
118
|
+
// long left, and dragging it moved nothing on screen. Display only; every
|
|
119
|
+
// write below still goes through `patchScene` over the handed document.
|
|
120
|
+
const scene = useInspectorScene();
|
|
121
|
+
|
|
122
|
+
if (doc === null || sceneId === null || scene === null) return null;
|
|
16
123
|
|
|
17
124
|
// A block body, not a concise one: `edit` reports whether it RAN (a drag
|
|
18
125
|
// holds the history open and refuses), and this caller has nothing to do with
|
|
19
126
|
// that answer — returning it out of a `void` handler is the type error that
|
|
20
127
|
// says so.
|
|
21
128
|
const patch = (next: Partial<Scene3D>): void => {
|
|
22
|
-
store.edit((d) => (
|
|
23
|
-
...d,
|
|
24
|
-
scenesById: { ...d.scenesById, [sceneId]: { ...d.scenesById[sceneId]!, ...next } },
|
|
25
|
-
}));
|
|
129
|
+
store.edit((d) => patchScene(d, sceneId, next, store.getState().frame));
|
|
26
130
|
};
|
|
27
131
|
|
|
28
132
|
return (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
133
|
+
<>
|
|
134
|
+
{/* ── THE NAME ROW, IN ONE CONTEXT ONLY ────────────────────────────
|
|
135
|
+
Inside a shot the head IS this field (`InspectorHead`'s "Shot"
|
|
136
|
+
subject, renaming through `renameScene`), and a heading plus a Name
|
|
137
|
+
row two lines below it holding the same string is the panel saying
|
|
138
|
+
the same thing twice.
|
|
139
|
+
|
|
140
|
+
In the SCENE context the head names the PROJECT, because that is what
|
|
141
|
+
the panel is a view of there, and the shot below it is one of its
|
|
142
|
+
parts. So the row stays: dropping it in both contexts would leave the
|
|
143
|
+
scene context with no way to rename a shot at all, and a capability's
|
|
144
|
+
only affordance has to remain visible at rest. */}
|
|
145
|
+
{inShot ? null : (
|
|
146
|
+
<InspectorRow label="Name">
|
|
147
|
+
<TextField value={scene.name} ariaLabel="Shot name" onCommit={(name) => patch({ name })} />
|
|
148
|
+
</InspectorRow>
|
|
149
|
+
)}
|
|
33
150
|
<InspectorRow>
|
|
151
|
+
{/* DURATION, in frames, carrying its unit in the field's own suffix the
|
|
152
|
+
way every other frame count in this panel does. `ariaLabel` is
|
|
153
|
+
unchanged: it is what the tests address this field by and it already
|
|
154
|
+
said "duration".
|
|
155
|
+
|
|
156
|
+
THE WORD IS THE PROJECT'S, AND THE PROJECT'S ROW IS NOT A FIELD.
|
|
157
|
+
`SceneDocSection`'s Duration is a DERIVED read-only total
|
|
158
|
+
("1:04 · 1920f", the sum of every shot), and the Project section
|
|
159
|
+
sits directly above this one in both contexts. So the rail now shows the word twice
|
|
160
|
+
there, once read-only and once editable, and the section heading
|
|
161
|
+
above each — Project, Shot — is what separates them. That is the
|
|
162
|
+
trade this rename made, and it is the reason `data-testid` and
|
|
163
|
+
`aria-label` stayed put: they, not the visible word, are how
|
|
164
|
+
anything addresses either row.
|
|
165
|
+
|
|
166
|
+
The transition rows say "Duration" too, because in the project
|
|
167
|
+
context they share this row's section and Duration, Duration, Frames
|
|
168
|
+
for three lengths in frames is the panel inventing a distinction
|
|
169
|
+
that is not there. The row still spelled "Frames",
|
|
170
|
+
`DefinitionSection`'s, never shares a rail with a Duration row, so
|
|
171
|
+
nothing reads two words for one quantity anywhere. That is
|
|
172
|
+
structural and not luck: the definition container renders
|
|
173
|
+
`DefinitionSection` alone. */}
|
|
34
174
|
<NumberField
|
|
35
175
|
value={scene.durationFrames}
|
|
36
|
-
label="
|
|
176
|
+
label="Duration"
|
|
177
|
+
unit="f"
|
|
37
178
|
ariaLabel="Shot duration"
|
|
38
179
|
min={1}
|
|
39
180
|
step={1}
|
|
181
|
+
onScrubStart={dragDuration.start}
|
|
182
|
+
onScrub={dragDuration.move}
|
|
183
|
+
onScrubEnd={dragDuration.end}
|
|
40
184
|
onCommit={(durationFrames) => store.trimScene(sceneId, durationFrames)}
|
|
41
185
|
/>
|
|
42
186
|
</InspectorRow>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
187
|
+
{inShot ? (
|
|
188
|
+
<>
|
|
189
|
+
{/* A scene is OPAQUE by contract (types.ts), so there is no alpha
|
|
190
|
+
anywhere in these rows and none is offered — the renderer clears
|
|
191
|
+
with alpha 1, and a transparent background would be a promise the
|
|
192
|
+
export cannot keep. `opaque` is what keeps that true: without it
|
|
193
|
+
every picker here showed an alpha bar and took an 8-digit hex.
|
|
194
|
+
|
|
195
|
+
The rows write the whole `Paint` union, which is what the renderer
|
|
196
|
+
has always read: `BackgroundPlane` draws both gradients for real
|
|
197
|
+
through the ordinary Canvas2D rasterizer. Before this, the row was
|
|
198
|
+
a solid-only picker whose scrub AND commit each wrote
|
|
199
|
+
`{type: "solid"}`, so touching the swatch DELETED a gradient an
|
|
200
|
+
agent or an MCP call had authored.
|
|
201
|
+
|
|
202
|
+
The keyframe diamond rides the SOLID row only. `background.color`
|
|
203
|
+
is a colour track and the track table refuses it over a gradient,
|
|
204
|
+
which is a paint with no single colour to interpolate. */}
|
|
205
|
+
<PaintRows
|
|
206
|
+
value={scene.background}
|
|
207
|
+
ariaPrefix="Background"
|
|
208
|
+
// The type row READS "Background" here and plain "Type" in
|
|
209
|
+
// `FillSection`, whose own heading already says Fill. Under this
|
|
210
|
+
// heading — "Shot", beside a Duration — "Type" would
|
|
211
|
+
// name nothing it is the type of.
|
|
212
|
+
label="Background"
|
|
213
|
+
opaque
|
|
214
|
+
solidTrailing={<SceneKeyframeDot path="background.color" />}
|
|
215
|
+
onScrubStart={dragScene.start}
|
|
216
|
+
onScrub={(edit) => moveScene({ background: edit(scene.background) })}
|
|
217
|
+
onScrubEnd={dragScene.end}
|
|
218
|
+
onCommit={(edit) => patch({ background: edit(scene.background) })}
|
|
219
|
+
/>
|
|
220
|
+
{/* THE DRAWER'S OTHER DOOR. A shader ground is a locked layer at the
|
|
221
|
+
back, not a Paint, so these rows cannot hold one. This opens the
|
|
222
|
+
Insert drawer at Backgrounds, where "Use as background" places it.
|
|
223
|
+
Once one is placed, the door selects it instead: a locked layer
|
|
224
|
+
skips stage clicks, so this and its outline row are the ways to
|
|
225
|
+
its props. Hidden for a viewer, like the drawer it opens. */}
|
|
226
|
+
{readOnly ? null : (
|
|
227
|
+
<InspectorRow>
|
|
228
|
+
<div className="insp-buttons">
|
|
229
|
+
<button
|
|
230
|
+
type="button"
|
|
231
|
+
className="btn shot-shader-button"
|
|
232
|
+
onClick={() =>
|
|
233
|
+
ground === null ? store.openInsertDrawer("Backgrounds") : store.select([ground])
|
|
234
|
+
}
|
|
235
|
+
>
|
|
236
|
+
{SPARKLES_ICON}
|
|
237
|
+
{ground === null ? "Shader background…" : "Edit background"}
|
|
238
|
+
</button>
|
|
239
|
+
</div>
|
|
240
|
+
</InspectorRow>
|
|
241
|
+
)}
|
|
242
|
+
</>
|
|
243
|
+
) : null}
|
|
244
|
+
</>
|
|
70
245
|
);
|
|
71
246
|
}
|