@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
|
@@ -6,25 +6,54 @@
|
|
|
6
6
|
//
|
|
7
7
|
// `image` and `video` are one pipeline everywhere it matters: the runtime
|
|
8
8
|
// collapses both to {asset, fit, crop} and draws them through one plane. So the
|
|
9
|
-
// controls they share are authored once here, and the
|
|
10
|
-
// `alt` on an image
|
|
11
|
-
//
|
|
9
|
+
// controls they share are authored once here, and the one field that differs —
|
|
10
|
+
// `alt` on an image — is a row that only its own kind renders rather than a
|
|
11
|
+
// second section.
|
|
12
12
|
//
|
|
13
13
|
// ── WHY THIS SECTION HAD TO EXIST ───────────────────────────────────────
|
|
14
14
|
//
|
|
15
|
-
// `fit`, `crop
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// `fit`, `crop` and `alt` were all reachable ONLY through an MCP call. A
|
|
16
|
+
// document can therefore hold a crop the author cannot see, let alone undo,
|
|
17
|
+
// from the chrome that wrote the layer.
|
|
18
|
+
//
|
|
19
|
+
// ── WHAT THIS SECTION IS NOT, AND WHERE THOSE THREE WENT ────────────────
|
|
20
|
+
//
|
|
21
|
+
// `offsetFrames` ("Starts at"), `loop` ("Past end") and `timeScale` ("Rate")
|
|
22
|
+
// USED TO BE HERE and are now `TimeSection`'s, because all three are WHEN and
|
|
23
|
+
// this section is WHAT. Each had exactly one home before and still does: a
|
|
24
|
+
// field with two homes is two readouts of one number that disagree the moment
|
|
25
|
+
// either one compensates and the other does not.
|
|
26
|
+
//
|
|
27
|
+
// They did not simply move, either. The source offset is now spelled in the
|
|
28
|
+
// ruler an author states an edit in — "play the recording from source frame
|
|
29
|
+
// 120", through `setVideoSource` — and the rate goes through `retimeVideo`, so
|
|
30
|
+
// a speed change keeps the window and the frame it opens on instead of scrubbing
|
|
31
|
+
// the footage sideways under a cut that is already timed. Neither behaviour was
|
|
32
|
+
// reachable from the raw fields that stood here.
|
|
18
33
|
import {
|
|
34
|
+
IconMinus,
|
|
35
|
+
IconPlus,
|
|
19
36
|
InspectorRow,
|
|
20
37
|
InspectorSection,
|
|
21
38
|
NumberField,
|
|
22
39
|
PropertyGrid,
|
|
23
40
|
SegmentedControl,
|
|
24
41
|
TextField,
|
|
42
|
+
useLocked,
|
|
43
|
+
wordOptions,
|
|
25
44
|
} from "@dailies-studio/ui";
|
|
45
|
+
import { useRef, useState } from "react";
|
|
46
|
+
import { KeyframeDot } from "../KeyframeDot.js";
|
|
47
|
+
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
48
|
+
import { IMAGE_ACCEPT, VIDEO_ACCEPT } from "../../toolbar/useAddRows.js";
|
|
49
|
+
import { cropAnchor, cropMagnification, zoomCrop } from "@dailies-studio/shot/doc";
|
|
26
50
|
import type { Crop, ImageLayer3D, Layer3D, LayerFit, VideoLayer3D } from "@dailies-studio/shot/doc";
|
|
27
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
useSelectedLayers,
|
|
53
|
+
useSelectionScrub,
|
|
54
|
+
useSelectionValue,
|
|
55
|
+
useSelectionWrite,
|
|
56
|
+
} from "../useDocWrite.js";
|
|
28
57
|
|
|
29
58
|
/**
|
|
30
59
|
* The two content blocks as ONE shape.
|
|
@@ -46,132 +75,211 @@ const FITS = ["fill", "fit", "stretch"] as const satisfies readonly LayerFit[];
|
|
|
46
75
|
* source" is exactly what an absent crop already means. */
|
|
47
76
|
const FULL_CROP: Crop = { x: 0, y: 0, w: 1, h: 1 };
|
|
48
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The crop's quantum, and it is NOT 0.01.
|
|
80
|
+
*
|
|
81
|
+
* These edges are min 0 / max 1. What 0.05 buys is the KEYBOARD and the drag's
|
|
82
|
+
* snap — an arrow key crosses the source in 20 presses instead of 100, and the
|
|
83
|
+
* pill lands on 21 stops rather than 101 two-pixel ones. It is also what the
|
|
84
|
+
* rest of this chrome already picked for a bounded 0..1 field (EffectsSection's
|
|
85
|
+
* vignette and ghost amounts). Alt divides the quantum by ten and places
|
|
86
|
+
* continuously; typing is unquantised either way.
|
|
87
|
+
*/
|
|
88
|
+
const CROP_STEP = 0.05;
|
|
89
|
+
|
|
90
|
+
/** The crop's four edges, the order the fields list them in, and how the
|
|
91
|
+
* Zoom diamond's tooltip names them (it keys all four together). */
|
|
92
|
+
const CROP_AXES = ["x", "y", "w", "h"] as const satisfies readonly (keyof Crop)[];
|
|
93
|
+
const CROP_PARTS = ["Crop X", "Crop Y", "Crop W", "Crop H"] as const;
|
|
94
|
+
|
|
49
95
|
export function MediaSection() {
|
|
50
96
|
const write = useSelectionWrite();
|
|
51
97
|
const layers = useSelectedLayers();
|
|
52
|
-
|
|
98
|
+
// A SCALAR, one hook per row, and it is `CropField` below that holds the last
|
|
99
|
+
// of them. This section used to read the whole media block here and hand its
|
|
100
|
+
// `mixed` to every field, and `useSelectionValue` compares with `Object.is` —
|
|
101
|
+
// so every multi-selection reported "Mixed" on all five fields, including a
|
|
102
|
+
// selection whose blocks are identical, for ever and with no way to tell it
|
|
103
|
+
// from a real disagreement.
|
|
104
|
+
//
|
|
105
|
+
// The zoom's own drag bracket. Re-derived from the gesture's BASE document
|
|
106
|
+
// like every other capture here, so a drag compounds nothing: without it the
|
|
107
|
+
// magnification would read its own last write and run away from the cursor.
|
|
108
|
+
const zoomScrub = useSelectionScrub((layer, raw) =>
|
|
109
|
+
applyMedia(layer, (m) => ({ ...m, crop: zoomCrop(cropAnchor(m.crop), raw as number) })),
|
|
110
|
+
);
|
|
111
|
+
// Mixed over what the Zoom field SHOWS, the magnification, per layer: two
|
|
112
|
+
// crops of one size at different places agree on it.
|
|
113
|
+
const zoom = useSelectionValue((layer) => {
|
|
114
|
+
const own = mediaOf(layer)?.crop;
|
|
115
|
+
return own === undefined ? undefined : cropMagnification(own);
|
|
116
|
+
});
|
|
53
117
|
// The first media layer in the selection, which is what the title names and
|
|
54
118
|
// what the per-kind rows are chosen by. Read from the LIST rather than from
|
|
55
119
|
// `media.value` (which is the first SELECTED layer's, media or not), so a
|
|
56
120
|
// rect clicked before an image does not empty the section.
|
|
57
121
|
const subject = layers.find(isMedia);
|
|
122
|
+
const store = useStore();
|
|
123
|
+
const fileRef = useRef<HTMLInputElement>(null);
|
|
124
|
+
const [busy, setBusy] = useState(false);
|
|
125
|
+
// The crop action and Replace are plain buttons, so they read a viewer's
|
|
126
|
+
// lock here; every other row is a widget that reads it itself.
|
|
127
|
+
const locked = useLocked();
|
|
128
|
+
const assetPath = useShotEditor((s) => {
|
|
129
|
+
const id = subject === undefined ? undefined : mediaOf(subject)?.asset;
|
|
130
|
+
return id === undefined ? undefined : s.doc?.assets[id]?.path;
|
|
131
|
+
});
|
|
58
132
|
if (subject === undefined) return null;
|
|
59
133
|
const block = mediaOf(subject);
|
|
60
134
|
if (block === undefined) return null;
|
|
61
135
|
|
|
62
|
-
/** Every write goes through one mapper, KIND-GUARDED INSIDE IT: the seam fans
|
|
63
|
-
* out over the whole selection, and a rect has no `fit` to set.
|
|
64
|
-
*
|
|
65
|
-
* `only` narrows that fan-out further, and the per-kind rows MUST use it.
|
|
66
|
-
* The rows are chosen by the first media layer in the selection, but the
|
|
67
|
-
* write reaches every one of them — so with an image and a video selected
|
|
68
|
-
* together, the image's Alt row would otherwise write `alt` into the video
|
|
69
|
-
* block and the video's Start row would write `offsetFrames` into the image.
|
|
70
|
-
* `MediaBlock` is the intersection of the two, which makes both foreign keys
|
|
71
|
-
* type-legal, and `validateShotDoc` is deliberately not stricter than the
|
|
72
|
-
* resolver, so nothing downstream refuses them. They surface much later, as
|
|
73
|
-
* an excess-property error in the generated `satisfies Doc3D` literal. */
|
|
74
136
|
const patch = (next: (media: MediaBlock) => MediaBlock, only?: "image" | "video"): void =>
|
|
75
|
-
write((layer) =>
|
|
76
|
-
layer.kind === "image" && only !== "video"
|
|
77
|
-
? { ...layer, image: next(layer.image) }
|
|
78
|
-
: layer.kind === "video" && only !== "image"
|
|
79
|
-
? { ...layer, video: next(layer.video) }
|
|
80
|
-
: layer,
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const setCrop = (key: keyof Crop, value: number): void =>
|
|
84
|
-
patch((m) => ({ ...m, crop: { ...(m.crop ?? FULL_CROP), [key]: clamp01(value) } }));
|
|
137
|
+
write((layer) => applyMedia(layer, next, only));
|
|
85
138
|
|
|
86
139
|
const crop = block.crop;
|
|
140
|
+
// ONE media layer only: a replace swaps one layer's file, and with two
|
|
141
|
+
// selected there is no single file for the row to name.
|
|
142
|
+
const media = subject;
|
|
143
|
+
const medias = layers.filter(isMedia);
|
|
144
|
+
const replaceable = medias.length === 1;
|
|
145
|
+
// Mixed among the layers each row WRITES, as the Html rows ask theirs: Fit
|
|
146
|
+
// reaches every media layer and Alt every image, and a rect beside them has
|
|
147
|
+
// no fit or description to disagree with. Both rows used to show the first
|
|
148
|
+
// media layer's value as if the selection shared it.
|
|
149
|
+
const fitMixed = medias.some((l) => mediaOf(l)?.fit !== block.fit);
|
|
150
|
+
const altMixed = medias.some((l) => l.kind === "image" && (l.image.alt ?? "") !== (block.alt ?? ""));
|
|
151
|
+
const pick = (file: File): void => {
|
|
152
|
+
setBusy(true);
|
|
153
|
+
void store
|
|
154
|
+
.replaceLayerMedia(media.id, file)
|
|
155
|
+
.catch((e: unknown) => store.reportError(e instanceof Error ? e.message : String(e)))
|
|
156
|
+
.finally(() => setBusy(false));
|
|
157
|
+
};
|
|
87
158
|
|
|
88
159
|
return (
|
|
89
160
|
<InspectorSection
|
|
161
|
+
className="insp-object"
|
|
90
162
|
title={subject.kind === "video" ? "Video" : "Image"}
|
|
91
163
|
persistKey="shot:media"
|
|
92
164
|
action={
|
|
93
165
|
<button
|
|
94
166
|
type="button"
|
|
95
|
-
className="section-action"
|
|
167
|
+
className="btn is-icon insp-section-action"
|
|
96
168
|
title={crop === undefined ? "Add crop" : "Remove crop"}
|
|
169
|
+
aria-label={crop === undefined ? "Add crop" : "Remove crop"}
|
|
170
|
+
disabled={locked}
|
|
97
171
|
onClick={() =>
|
|
98
172
|
patch((m) => (m.crop === undefined ? { ...m, crop: FULL_CROP } : without(m, "crop")))
|
|
99
173
|
}
|
|
100
174
|
>
|
|
101
|
-
{crop === undefined ?
|
|
175
|
+
{crop === undefined ? <IconPlus /> : <IconMinus />}
|
|
102
176
|
</button>
|
|
103
177
|
}
|
|
104
178
|
>
|
|
179
|
+
{replaceable ? (
|
|
180
|
+
<InspectorRow label="Source">
|
|
181
|
+
{/* A file NAME, so the row's own type and not the numeral face a
|
|
182
|
+
number reads in; it ellipsizes before the button. */}
|
|
183
|
+
<span className="insp-dim is-text" title={assetPath}>
|
|
184
|
+
{assetPath === undefined ? "File missing" : assetPath.replace(/^[0-9a-f]{8}-/u, "")}
|
|
185
|
+
</span>
|
|
186
|
+
<button type="button" className="btn" disabled={locked || busy} onClick={() => fileRef.current?.click()}>
|
|
187
|
+
Replace…
|
|
188
|
+
</button>
|
|
189
|
+
<input
|
|
190
|
+
ref={fileRef}
|
|
191
|
+
type="file"
|
|
192
|
+
hidden
|
|
193
|
+
aria-label={subject.kind === "video" ? "Replace video file" : "Replace image file"}
|
|
194
|
+
accept={subject.kind === "video" ? VIDEO_ACCEPT : IMAGE_ACCEPT}
|
|
195
|
+
onChange={(event) => {
|
|
196
|
+
const file = event.currentTarget.files?.[0];
|
|
197
|
+
event.currentTarget.value = "";
|
|
198
|
+
if (file !== undefined) pick(file);
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
</InspectorRow>
|
|
202
|
+
) : null}
|
|
203
|
+
|
|
105
204
|
<InspectorRow label="Fit">
|
|
106
205
|
<SegmentedControl
|
|
107
206
|
ariaLabel="Media fit"
|
|
108
|
-
value
|
|
109
|
-
|
|
207
|
+
// An unmatched value is how a segmented control says Mixed.
|
|
208
|
+
value={fitMixed ? "" : block.fit}
|
|
209
|
+
options={wordOptions(FITS)}
|
|
110
210
|
onChange={(next) => patch((m) => ({ ...m, fit: next as LayerFit }))}
|
|
111
211
|
/>
|
|
112
212
|
</InspectorRow>
|
|
113
213
|
|
|
214
|
+
{/* THE PUNCH-IN, as one number.
|
|
215
|
+
|
|
216
|
+
"Zoom into that button" is this product class's defining gesture, and
|
|
217
|
+
it was expressible only by typing four crop edges by hand. It is a CROP
|
|
218
|
+
and not a scale or a camera move, because those two magnify the wrong
|
|
219
|
+
thing: a camera `zoom` takes the background and the border with it, and
|
|
220
|
+
a layer scale works about `transform.position`, which IS the layer's
|
|
221
|
+
centre — `Transform3D` has no anchor, so a scale can only ever punch
|
|
222
|
+
into the middle of a recording.
|
|
223
|
+
|
|
224
|
+
It writes the same four `video.crop.*` / `image.crop.*` numbers the
|
|
225
|
+
edges below do, so an ANIMATED punch-in is the diamond workflow: key
|
|
226
|
+
the zoom at 1x, move the playhead, key it at 2x. Its box carries ONE
|
|
227
|
+
diamond over the four crop tracks, keyed together like the Corner
|
|
228
|
+
row's four radii. */}
|
|
229
|
+
{crop !== undefined ? (
|
|
230
|
+
<InspectorRow>
|
|
231
|
+
<NumberField
|
|
232
|
+
value={cropMagnification(crop)}
|
|
233
|
+
mixed={zoom.mixed}
|
|
234
|
+
label="Zoom"
|
|
235
|
+
title="Magnify a region of the source. Keeps the plate, the border and the background where they are"
|
|
236
|
+
ariaLabel="Media zoom"
|
|
237
|
+
unit="x"
|
|
238
|
+
min={1}
|
|
239
|
+
// Below 1 there is no source left to sample.
|
|
240
|
+
step={0.1}
|
|
241
|
+
trailing={
|
|
242
|
+
<KeyframeDot
|
|
243
|
+
path={CROP_AXES.map((axis) => `${subject.kind}.crop.${axis}`)}
|
|
244
|
+
label="Zoom"
|
|
245
|
+
parts={CROP_PARTS}
|
|
246
|
+
/>
|
|
247
|
+
}
|
|
248
|
+
onScrubStart={zoomScrub.start}
|
|
249
|
+
onScrub={zoomScrub.move}
|
|
250
|
+
onScrubEnd={zoomScrub.end}
|
|
251
|
+
onCommit={(next) =>
|
|
252
|
+
write((layer) =>
|
|
253
|
+
applyMedia(layer, (m) => ({ ...m, crop: zoomCrop(cropAnchor(m.crop), next) })),
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
/>
|
|
257
|
+
</InspectorRow>
|
|
258
|
+
) : null}
|
|
259
|
+
|
|
114
260
|
{/* Fractions of the SOURCE, not of the box, and y counts DOWN from the
|
|
115
261
|
top row the way a screenshot's own pixels are numbered — media.ts owns
|
|
116
262
|
the flip into uv space, so these are the numbers a design tool would
|
|
117
263
|
report for the same rectangle. */}
|
|
118
264
|
{crop !== undefined ? (
|
|
119
265
|
<PropertyGrid>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
onCommit={(next) => setCrop("x", next)}
|
|
130
|
-
/>
|
|
131
|
-
</InspectorRow>
|
|
132
|
-
<InspectorRow>
|
|
133
|
-
<NumberField
|
|
134
|
-
value={crop.y}
|
|
135
|
-
mixed={media.mixed}
|
|
136
|
-
label="Y"
|
|
137
|
-
ariaLabel="Crop y"
|
|
138
|
-
min={0}
|
|
139
|
-
max={1}
|
|
140
|
-
step={0.01}
|
|
141
|
-
onCommit={(next) => setCrop("y", next)}
|
|
142
|
-
/>
|
|
143
|
-
</InspectorRow>
|
|
144
|
-
<InspectorRow>
|
|
145
|
-
<NumberField
|
|
146
|
-
value={crop.w}
|
|
147
|
-
mixed={media.mixed}
|
|
148
|
-
label="W"
|
|
149
|
-
ariaLabel="Crop width"
|
|
150
|
-
min={0}
|
|
151
|
-
max={1}
|
|
152
|
-
step={0.01}
|
|
153
|
-
onCommit={(next) => setCrop("w", next)}
|
|
154
|
-
/>
|
|
155
|
-
</InspectorRow>
|
|
156
|
-
<InspectorRow>
|
|
157
|
-
<NumberField
|
|
158
|
-
value={crop.h}
|
|
159
|
-
mixed={media.mixed}
|
|
160
|
-
label="H"
|
|
161
|
-
ariaLabel="Crop height"
|
|
162
|
-
min={0}
|
|
163
|
-
max={1}
|
|
164
|
-
step={0.01}
|
|
165
|
-
onCommit={(next) => setCrop("h", next)}
|
|
166
|
-
/>
|
|
167
|
-
</InspectorRow>
|
|
266
|
+
{/* THE KEN BURNS MOVE. The track path is per KIND — there is no
|
|
267
|
+
top-level `crop`, each media kind owns one — so the subject's own
|
|
268
|
+
kind names it. A mixed image+video selection hides the diamonds by
|
|
269
|
+
itself: `KeyframeDot` needs every selected layer to accept the
|
|
270
|
+
path, and neither kind accepts the other's. */}
|
|
271
|
+
<CropField axis="x" label="X" ariaLabel="Crop x" value={crop.x} kind={subject.kind} />
|
|
272
|
+
<CropField axis="y" label="Y" ariaLabel="Crop y" value={crop.y} kind={subject.kind} />
|
|
273
|
+
<CropField axis="w" label="W" ariaLabel="Crop width" value={crop.w} kind={subject.kind} />
|
|
274
|
+
<CropField axis="h" label="H" ariaLabel="Crop height" value={crop.h} kind={subject.kind} />
|
|
168
275
|
</PropertyGrid>
|
|
169
276
|
) : null}
|
|
170
277
|
|
|
171
278
|
{subject.kind === "image" ? (
|
|
172
279
|
<InspectorRow label="Alt">
|
|
173
280
|
<TextField
|
|
174
|
-
value={block.alt ?? ""}
|
|
281
|
+
value={altMixed ? "" : (block.alt ?? "")}
|
|
282
|
+
{...(altMixed ? { placeholder: "Mixed" } : {})}
|
|
175
283
|
ariaLabel="Alt text"
|
|
176
284
|
onCommit={(next) =>
|
|
177
285
|
patch((m) => {
|
|
@@ -185,46 +293,91 @@ export function MediaSection() {
|
|
|
185
293
|
/>
|
|
186
294
|
</InspectorRow>
|
|
187
295
|
) : null}
|
|
188
|
-
|
|
189
|
-
{subject.kind === "video" ? (
|
|
190
|
-
// PLACEMENT, NOT A TRIM. This is the scene frame the source's own t=0
|
|
191
|
-
// lands on, so every frame before it holds the source's FIRST frame and
|
|
192
|
-
// nothing is cut off the head. Audio's identically-named `offsetFrames`
|
|
193
|
-
// is the other thing — a trim into the source — and confusing the two
|
|
194
|
-
// is why the label says "starts at" rather than "start".
|
|
195
|
-
<InspectorRow label="Starts at" labelTitle="The scene frame this clip's first frame lands on — a delay, never a trim into the source">
|
|
196
|
-
<NumberField
|
|
197
|
-
value={block.offsetFrames ?? 0}
|
|
198
|
-
mixed={media.mixed}
|
|
199
|
-
ariaLabel="Video start frame"
|
|
200
|
-
unit="f"
|
|
201
|
-
step={1}
|
|
202
|
-
// NO `min`. NumberField clamps against it on every commit path —
|
|
203
|
-
// typing, the stepper and the drag — so `min={0}` would turn the
|
|
204
|
-
// one value this feature exists to preserve into 0 the first time
|
|
205
|
-
// the author touched the field, and the `frames === 0` branch below
|
|
206
|
-
// would then DELETE it, jumping the footage. A negative offset is
|
|
207
|
-
// what `splitScene` produces for a clip already playing at the cut,
|
|
208
|
-
// and it is untypeable, so erasing one is unrecoverable.
|
|
209
|
-
//
|
|
210
|
-
// 0 DELETES the key: absent already means "starts at frame 0", and
|
|
211
|
-
// an explicit 0 is a second way to say the same thing that codegen
|
|
212
|
-
// then prints. `Math.round` covers the only bound the save gate
|
|
213
|
-
// actually holds — the value must be an integer.
|
|
214
|
-
onCommit={(next) => {
|
|
215
|
-
const frames = Math.round(next);
|
|
216
|
-
patch(
|
|
217
|
-
(m) => (frames === 0 ? without(m, "offsetFrames") : { ...m, offsetFrames: frames }),
|
|
218
|
-
"video",
|
|
219
|
-
);
|
|
220
|
-
}}
|
|
221
|
-
/>
|
|
222
|
-
</InspectorRow>
|
|
223
|
-
) : null}
|
|
224
296
|
</InspectorSection>
|
|
225
297
|
);
|
|
226
298
|
}
|
|
227
299
|
|
|
300
|
+
/**
|
|
301
|
+
* One crop edge, and its own drag bracket.
|
|
302
|
+
*
|
|
303
|
+
* A row rather than inline JSX because a bracket is a HOOK and there is one
|
|
304
|
+
* gesture per edge. Both halves go through `withCrop`, because a capture is
|
|
305
|
+
* handed a layer out of the gesture's BASE document: an edge that re-derived
|
|
306
|
+
* itself from what the panel last rendered would compound its own moves and run
|
|
307
|
+
* away from the cursor.
|
|
308
|
+
*/
|
|
309
|
+
function CropField(props: {
|
|
310
|
+
axis: keyof Crop;
|
|
311
|
+
label: string;
|
|
312
|
+
ariaLabel: string;
|
|
313
|
+
value: number;
|
|
314
|
+
kind: Layer3D["kind"];
|
|
315
|
+
}) {
|
|
316
|
+
const { axis } = props;
|
|
317
|
+
const write = useSelectionWrite();
|
|
318
|
+
// This edge's own agreement, read as the SCALAR the row displays.
|
|
319
|
+
const { mixed } = useSelectionValue((layer) => mediaOf(layer)?.crop?.[axis]);
|
|
320
|
+
const scrub = useSelectionScrub((layer, raw) =>
|
|
321
|
+
applyMedia(layer, (m) => withCrop(m, axis, raw as number)),
|
|
322
|
+
);
|
|
323
|
+
return (
|
|
324
|
+
<InspectorRow>
|
|
325
|
+
<NumberField
|
|
326
|
+
value={props.value}
|
|
327
|
+
mixed={mixed}
|
|
328
|
+
label={props.label}
|
|
329
|
+
ariaLabel={props.ariaLabel}
|
|
330
|
+
min={0}
|
|
331
|
+
max={1}
|
|
332
|
+
step={CROP_STEP}
|
|
333
|
+
trailing={<KeyframeDot path={`${props.kind}.crop.${axis}`} label={props.ariaLabel} />}
|
|
334
|
+
onCommit={(next) => write((layer) => applyMedia(layer, (m) => withCrop(m, axis, next)))}
|
|
335
|
+
onScrubStart={scrub.start}
|
|
336
|
+
onScrub={scrub.move}
|
|
337
|
+
onScrubEnd={scrub.end}
|
|
338
|
+
/>
|
|
339
|
+
</InspectorRow>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Every write goes through one mapper, KIND-GUARDED INSIDE IT: the seam fans
|
|
345
|
+
* out over the whole selection, and a rect has no `fit` to set.
|
|
346
|
+
*
|
|
347
|
+
* `only` narrows that fan-out further, and the per-kind rows MUST use it. The
|
|
348
|
+
* rows are chosen by the first media layer in the selection, but the write
|
|
349
|
+
* reaches every one of them — so with an image and a video selected together,
|
|
350
|
+
* the image's Alt row would otherwise write `alt` into the video block.
|
|
351
|
+
* `MediaBlock` is the intersection of the two, which makes such a foreign key
|
|
352
|
+
* type-legal, and `validateShotDoc` is deliberately not stricter than the
|
|
353
|
+
* resolver, so nothing downstream refuses it. It surfaces much later, as an
|
|
354
|
+
* excess-property error in the generated `satisfies Doc3D` literal.
|
|
355
|
+
*
|
|
356
|
+
* ONE row uses it today (`alt`), because the three video-only rows that used to
|
|
357
|
+
* need it are `TimeSection`'s now and that section writes through the clip ops
|
|
358
|
+
* over ONE layer. The parameter stays: `only` is the rule, not a workaround for
|
|
359
|
+
* the rows that happened to exist, and the next per-kind field needs it on its
|
|
360
|
+
* first day.
|
|
361
|
+
*
|
|
362
|
+
* PURE and module-level rather than closed over `write`, because the drag needs
|
|
363
|
+
* the same mapper and a capture only ever gets a layer handed to it.
|
|
364
|
+
*/
|
|
365
|
+
function applyMedia(
|
|
366
|
+
layer: Layer3D,
|
|
367
|
+
next: (media: MediaBlock) => MediaBlock,
|
|
368
|
+
only?: "image" | "video",
|
|
369
|
+
): Layer3D {
|
|
370
|
+
return layer.kind === "image" && only !== "video"
|
|
371
|
+
? { ...layer, image: next(layer.image) }
|
|
372
|
+
: layer.kind === "video" && only !== "image"
|
|
373
|
+
? { ...layer, video: next(layer.video) }
|
|
374
|
+
: layer;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function withCrop(media: MediaBlock, key: keyof Crop, value: number): MediaBlock {
|
|
378
|
+
return { ...media, crop: { ...(media.crop ?? FULL_CROP), [key]: clamp01(value) } };
|
|
379
|
+
}
|
|
380
|
+
|
|
228
381
|
function isMedia(layer: Layer3D): boolean {
|
|
229
382
|
return layer.kind === "image" || layer.kind === "video";
|
|
230
383
|
}
|
|
@@ -238,7 +391,7 @@ function mediaOf(layer: Layer3D): MediaBlock | undefined {
|
|
|
238
391
|
/** Drop one optional key, rather than setting it to `undefined`. Under
|
|
239
392
|
* exactOptionalPropertyTypes those are different values, and codegen prints
|
|
240
393
|
* the block by key order — a present-but-undefined key is a key. */
|
|
241
|
-
function without(media: MediaBlock, key: "crop" | "alt"
|
|
394
|
+
function without(media: MediaBlock, key: "crop" | "alt"): MediaBlock {
|
|
242
395
|
const { [key]: _drop, ...rest } = media;
|
|
243
396
|
return rest;
|
|
244
397
|
}
|