@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,407 @@
|
|
|
1
|
+
// THE GENERATE DRAWER: Sound mode's right column, swapped in for the mixer.
|
|
2
|
+
//
|
|
3
|
+
// A sentence becomes a sound effect, a music bed or a voice line, through
|
|
4
|
+
// `/studio/audio/generate` on either host (api.ts). The take is previewed
|
|
5
|
+
// with the browser's own <audio> before it touches the document, and nothing
|
|
6
|
+
// is placed until "Add to <track> at playhead", which is one undo entry.
|
|
7
|
+
//
|
|
8
|
+
// Locally the take is already a stored asset (the CLI stores it as it
|
|
9
|
+
// generates), so placing it is `placeStoredAudio`. Hosted, the take waits in
|
|
10
|
+
// R2 for a day and placing it is `importAudio` of its bytes, which uploads it
|
|
11
|
+
// into the Project like a dropped file. Either way a past take in the recent
|
|
12
|
+
// list is placed again without generating it twice.
|
|
13
|
+
//
|
|
14
|
+
// It runs on the user's own ElevenLabs key: the environment's locally, the
|
|
15
|
+
// workspace's hosted, which an Owner or Admin adds in Settings, AI keys.
|
|
16
|
+
//
|
|
17
|
+
// A SOUND EFFECT IS TWO TAKES by default: both are made at once, measured in
|
|
18
|
+
// this tab and ranked (`judgeTakes`, a chunk of its own), so the one that
|
|
19
|
+
// measures better leads, with the reason and a tone check when it skews
|
|
20
|
+
// bright. Both stay playable and addable: the ranking advises, the ear decides.
|
|
21
|
+
//
|
|
22
|
+
// MUSIC CAN BE SCORED TO THE PROJECT: "Score to the cuts" sends the film's
|
|
23
|
+
// sections (`scoreSectionsOf`), so the track changes where the film does, and
|
|
24
|
+
// adds it at frame 0, where its sections line up.
|
|
25
|
+
import { useEffect, useMemo, useState } from "react";
|
|
26
|
+
import { SegmentedControl } from "@dailies-studio/ui";
|
|
27
|
+
import { audioTrackOf, audioTracksOf, scoreSectionsOf } from "@dailies-studio/shot/doc";
|
|
28
|
+
import { generateAudioTake, generationStatus } from "../../api.js";
|
|
29
|
+
import type { GenerationBody, GenerationKind, GenerationStatus, GenerationTake } from "../../api.js";
|
|
30
|
+
import { CLOSE_ICON, SPARKLES_ICON } from "../../icons.js";
|
|
31
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
32
|
+
|
|
33
|
+
const KIND_LABEL: Record<GenerationKind, string> = { sfx: "Sound effect", music: "Music", voice: "Voice" };
|
|
34
|
+
const PLACEHOLDER: Record<GenerationKind, string> = {
|
|
35
|
+
sfx: "Short airy whoosh, left to right",
|
|
36
|
+
music: "Warm lo-fi beat, soft keys, 90 bpm",
|
|
37
|
+
voice: "What the voice says, as it should be spoken.",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function errorText(e: unknown): string {
|
|
41
|
+
return e instanceof Error ? e.message : String(e);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The dashboard's AI keys tab. The editor host sends `/projects/*` to the
|
|
45
|
+
* main host, so the relative link works there (as `MANAGE_FONTS_HREF`). */
|
|
46
|
+
export const ADD_KEY_HREF = "/projects/?view=settings-keys";
|
|
47
|
+
|
|
48
|
+
/** Whose ElevenLabs key a take runs on, under the Generate button. */
|
|
49
|
+
export function keyLine(status: GenerationStatus, takes = 1): string {
|
|
50
|
+
const whose = status.host === "local" ? "Uses ELEVENLABS_API_KEY on this machine" : "Uses this workspace's ElevenLabs key";
|
|
51
|
+
return `${whose}${takes > 1 ? " · two takes are two generations" : ""}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function GenerateDrawer(props: { onClose: () => void }) {
|
|
55
|
+
const store = useStore();
|
|
56
|
+
const doc = useShotEditor((s) => s.doc);
|
|
57
|
+
const project = useShotEditor((s) => s.name);
|
|
58
|
+
const selected = useShotEditor((s) => s.selectedAudio);
|
|
59
|
+
const [status, setStatus] = useState<GenerationStatus | null>(null);
|
|
60
|
+
const [kind, setKind] = useState<GenerationKind>("sfx");
|
|
61
|
+
const [prompts, setPrompts] = useState<Record<GenerationKind, string>>({ sfx: "", music: "", voice: "" });
|
|
62
|
+
const [seconds, setSeconds] = useState<{ sfx: number; music: number }>({ sfx: 4, music: 30 });
|
|
63
|
+
const [voice, setVoice] = useState<string | null>(null);
|
|
64
|
+
const [instrumental, setInstrumental] = useState(true);
|
|
65
|
+
const [loop, setLoop] = useState(false);
|
|
66
|
+
/** Sound effects: two takes, the better kept (the default). */
|
|
67
|
+
const [twoTakes, setTwoTakes] = useState(true);
|
|
68
|
+
/** Music: compose to the Project's cuts instead of a length. */
|
|
69
|
+
const [fit, setFit] = useState(false);
|
|
70
|
+
const [busy, setBusy] = useState<"generate" | "place" | null>(null);
|
|
71
|
+
const [error, setError] = useState<string | null>(null);
|
|
72
|
+
const [made, setMade] = useState<{ order: GenerationTake[]; reasons: string[]; tone: string | null; scored: boolean } | null>(null);
|
|
73
|
+
const [trackId, setTrackId] = useState<string | null>(null);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (project === null) return;
|
|
77
|
+
let live = true;
|
|
78
|
+
generationStatus(project).then(
|
|
79
|
+
(s) => {
|
|
80
|
+
if (!live) return;
|
|
81
|
+
setStatus(s);
|
|
82
|
+
setSeconds({ sfx: s.caps.sfx.defaultSeconds, music: s.caps.music.defaultSeconds });
|
|
83
|
+
},
|
|
84
|
+
(e: unknown) => {
|
|
85
|
+
if (live) setError(errorText(e));
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
return () => {
|
|
89
|
+
live = false;
|
|
90
|
+
};
|
|
91
|
+
}, [project]);
|
|
92
|
+
|
|
93
|
+
const tracks = doc === null ? [] : audioTracksOf(doc);
|
|
94
|
+
// The selected clip's track, else the first: where "Add" lands unless the
|
|
95
|
+
// author picks another.
|
|
96
|
+
const defaultTrack = useMemo(() => {
|
|
97
|
+
if (doc === null) return undefined;
|
|
98
|
+
const clip = selected.length > 0 ? doc.audio?.[selected[0]!] : undefined;
|
|
99
|
+
return audioTrackOf(doc, clip?.track).id;
|
|
100
|
+
}, [doc, selected]);
|
|
101
|
+
const target = tracks.find((t) => t.id === trackId) ?? tracks.find((t) => t.id === defaultTrack) ?? tracks[0];
|
|
102
|
+
const sections = useMemo(() => (doc === null ? null : scoreSectionsOf(doc)), [doc]);
|
|
103
|
+
|
|
104
|
+
if (doc === null || project === null) return null;
|
|
105
|
+
const prompt = prompts[kind];
|
|
106
|
+
const available = status?.kinds[kind];
|
|
107
|
+
const voices = status?.voices ?? [];
|
|
108
|
+
const pickedVoice = voice ?? voices[0]?.id;
|
|
109
|
+
// Scoring needs a film music can be as long as: under 3 s there is no
|
|
110
|
+
// section, and past the music cap there is no take.
|
|
111
|
+
const filmMs = sections === null ? 0 : sections.reduce((sum, s) => sum + s.durationMs, 0);
|
|
112
|
+
const scoreRefusal =
|
|
113
|
+
sections === null
|
|
114
|
+
? "The film is shorter than 3 seconds, the shortest a section can be"
|
|
115
|
+
: status !== null && filmMs > status.caps.music.maxSeconds * 1000
|
|
116
|
+
? `The film is ${Math.round(filmMs / 1000)} s; music can be at most ${status.caps.music.maxSeconds} s`
|
|
117
|
+
: null;
|
|
118
|
+
const scored = kind === "music" && fit && sections !== null && scoreRefusal === null;
|
|
119
|
+
const takes = kind === "sfx" && twoTakes ? 2 : 1;
|
|
120
|
+
const body: GenerationBody = {
|
|
121
|
+
project,
|
|
122
|
+
kind,
|
|
123
|
+
prompt,
|
|
124
|
+
...(kind === "voice"
|
|
125
|
+
? pickedVoice === undefined
|
|
126
|
+
? {}
|
|
127
|
+
: { voice: pickedVoice }
|
|
128
|
+
: scored
|
|
129
|
+
? {
|
|
130
|
+
seconds: filmMs / 1000,
|
|
131
|
+
instrumental,
|
|
132
|
+
sections: sections.map(({ name, durationMs }) => ({ name, durationMs })),
|
|
133
|
+
}
|
|
134
|
+
: { seconds: seconds[kind], ...(kind === "music" ? { instrumental } : { loop }) }),
|
|
135
|
+
};
|
|
136
|
+
const maxChars = status === null ? 0 : kind === "voice" ? status.caps.voice.maxChars : status.caps.promptMaxChars;
|
|
137
|
+
const caps = status !== null && kind !== "voice" ? status.caps[kind] : null;
|
|
138
|
+
const canGenerate = busy === null && available?.ok === true && prompt.trim() !== "";
|
|
139
|
+
|
|
140
|
+
const generate = async (): Promise<void> => {
|
|
141
|
+
setBusy("generate");
|
|
142
|
+
setError(null);
|
|
143
|
+
try {
|
|
144
|
+
const asked = { ...body };
|
|
145
|
+
const settled = await Promise.allSettled(Array.from({ length: takes }, () => generateAudioTake(asked)));
|
|
146
|
+
const arrived = settled.flatMap((r) => (r.status === "fulfilled" ? [r.value.take] : []));
|
|
147
|
+
if (arrived.length === 0) throw (settled[0] as PromiseRejectedResult).reason;
|
|
148
|
+
// The judge is its own chunk, fetched the first time a sound effect
|
|
149
|
+
// lands: it ranks two takes, and gives one take its tone check. A chunk
|
|
150
|
+
// that does not arrive (a deploy replaced it under this tab) leaves the
|
|
151
|
+
// takes shown, unranked, rather than lost behind an error.
|
|
152
|
+
let ranked: { order: GenerationTake[]; reasons: string[]; tone: string | null } = { order: arrived, reasons: [], tone: null };
|
|
153
|
+
if (kind === "sfx") {
|
|
154
|
+
try {
|
|
155
|
+
ranked = await (await import("./takeMeasure.js")).judgeTakes(arrived, asked.seconds ?? 0);
|
|
156
|
+
} catch {
|
|
157
|
+
if (arrived.length > 1) ranked = { ...ranked, reasons: ["These takes could not be measured here, so listen to both."] };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const failed = settled.flatMap((r) => (r.status === "rejected" ? [errorText(r.reason)] : []));
|
|
161
|
+
setMade({ ...ranked, reasons: [...ranked.reasons, ...failed.map((f) => `A take failed: ${f}`)], scored });
|
|
162
|
+
setStatus((s) =>
|
|
163
|
+
s === null ? s : { ...s, recent: [...arrived, ...s.recent.filter((r) => !arrived.some((a) => a.id === r.id))] },
|
|
164
|
+
);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
setError(errorText(e));
|
|
167
|
+
} finally {
|
|
168
|
+
setBusy(null);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const place = async (t: GenerationTake, atStart = false): Promise<void> => {
|
|
173
|
+
setBusy("place");
|
|
174
|
+
setError(null);
|
|
175
|
+
try {
|
|
176
|
+
await store.placeTake(t, target?.id, atStart ? 0 : undefined);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
setError(errorText(e));
|
|
179
|
+
} finally {
|
|
180
|
+
setBusy(null);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div className="sound-gen" data-testid="generate-drawer">
|
|
186
|
+
<div className="sound-gen-head">
|
|
187
|
+
<span className="sound-gen-glyph" aria-hidden="true">
|
|
188
|
+
{SPARKLES_ICON}
|
|
189
|
+
</span>
|
|
190
|
+
<span className="sound-gen-title">Generate</span>
|
|
191
|
+
<button type="button" className="btn is-ghost is-icon" aria-label="Close" title="Back to the mixer" onClick={props.onClose}>
|
|
192
|
+
{CLOSE_ICON}
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
<div className="sound-gen-body">
|
|
196
|
+
<SegmentedControl
|
|
197
|
+
className="sound-gen-kinds"
|
|
198
|
+
ariaLabel="What to generate"
|
|
199
|
+
value={kind}
|
|
200
|
+
onChange={(k) => setKind(k === "music" || k === "voice" ? k : "sfx")}
|
|
201
|
+
options={(["sfx", "music", "voice"] as const).map((k) => ({
|
|
202
|
+
value: k,
|
|
203
|
+
label: KIND_LABEL[k],
|
|
204
|
+
testId: `generate-kind-${k}`,
|
|
205
|
+
}))}
|
|
206
|
+
/>
|
|
207
|
+
<textarea
|
|
208
|
+
className="text-input sound-gen-prompt"
|
|
209
|
+
data-testid="generate-prompt"
|
|
210
|
+
aria-label={kind === "voice" ? "Script" : "Describe the sound"}
|
|
211
|
+
placeholder={PLACEHOLDER[kind]}
|
|
212
|
+
value={prompt}
|
|
213
|
+
maxLength={maxChars > 0 ? maxChars : undefined}
|
|
214
|
+
rows={kind === "voice" ? 4 : 2}
|
|
215
|
+
onChange={(e) => {
|
|
216
|
+
const value = e.currentTarget.value;
|
|
217
|
+
setPrompts((p) => ({ ...p, [kind]: value }));
|
|
218
|
+
}}
|
|
219
|
+
onKeyDown={(e) => {
|
|
220
|
+
// The dock's keymap must not see typing (Space plays, Q/W trim).
|
|
221
|
+
e.stopPropagation();
|
|
222
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && canGenerate) {
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
void generate();
|
|
225
|
+
}
|
|
226
|
+
}}
|
|
227
|
+
/>
|
|
228
|
+
{kind === "voice" ? (
|
|
229
|
+
<label className="sound-gen-field">
|
|
230
|
+
<span className="sound-gen-label">Voice</span>
|
|
231
|
+
<select
|
|
232
|
+
className="select sound-gen-select"
|
|
233
|
+
data-testid="generate-voice"
|
|
234
|
+
value={pickedVoice ?? ""}
|
|
235
|
+
onChange={(e) => setVoice(e.currentTarget.value)}
|
|
236
|
+
>
|
|
237
|
+
{voices.map((v) => (
|
|
238
|
+
<option key={v.id} value={v.id}>
|
|
239
|
+
{v.name}, {v.description}
|
|
240
|
+
</option>
|
|
241
|
+
))}
|
|
242
|
+
</select>
|
|
243
|
+
</label>
|
|
244
|
+
) : (
|
|
245
|
+
<>
|
|
246
|
+
<div className="sound-gen-row">
|
|
247
|
+
<label className="sound-gen-field">
|
|
248
|
+
<span className="sound-gen-label">Seconds</span>
|
|
249
|
+
<input
|
|
250
|
+
className="text-input sound-gen-seconds"
|
|
251
|
+
data-testid="generate-seconds"
|
|
252
|
+
type="number"
|
|
253
|
+
min={caps?.minSeconds}
|
|
254
|
+
max={caps?.maxSeconds}
|
|
255
|
+
step={kind === "sfx" ? 0.5 : 1}
|
|
256
|
+
disabled={scored}
|
|
257
|
+
value={scored ? (body.seconds ?? 0) : seconds[kind]}
|
|
258
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
259
|
+
onChange={(e) => {
|
|
260
|
+
const n = Number(e.currentTarget.value);
|
|
261
|
+
if (!Number.isFinite(n) || caps === null) return;
|
|
262
|
+
setSeconds((s) => ({ ...s, [kind]: Math.min(caps.maxSeconds, Math.max(caps.minSeconds, n)) }));
|
|
263
|
+
}}
|
|
264
|
+
/>
|
|
265
|
+
</label>
|
|
266
|
+
<label className="sound-gen-check">
|
|
267
|
+
<input
|
|
268
|
+
type="checkbox"
|
|
269
|
+
checked={kind === "music" ? instrumental : loop}
|
|
270
|
+
onChange={(e) => (kind === "music" ? setInstrumental : setLoop)(e.currentTarget.checked)}
|
|
271
|
+
/>
|
|
272
|
+
{kind === "music" ? "Instrumental" : "Loop"}
|
|
273
|
+
</label>
|
|
274
|
+
</div>
|
|
275
|
+
{kind === "sfx" ? (
|
|
276
|
+
<label className="sound-gen-check" title="Make two takes and lead with the one that measures better">
|
|
277
|
+
<input type="checkbox" data-testid="generate-two-takes" checked={twoTakes} onChange={(e) => setTwoTakes(e.currentTarget.checked)} />
|
|
278
|
+
Two takes, keep the better
|
|
279
|
+
</label>
|
|
280
|
+
) : (
|
|
281
|
+
<label
|
|
282
|
+
className="sound-gen-check"
|
|
283
|
+
title={scoreRefusal ?? "Compose to the Project's cuts and named markers"}
|
|
284
|
+
>
|
|
285
|
+
<input
|
|
286
|
+
type="checkbox"
|
|
287
|
+
data-testid="generate-fit"
|
|
288
|
+
checked={scored}
|
|
289
|
+
disabled={scoreRefusal !== null}
|
|
290
|
+
onChange={(e) => setFit(e.currentTarget.checked)}
|
|
291
|
+
/>
|
|
292
|
+
Score to the cuts{sections === null ? "" : ` (${sections.length} section${sections.length === 1 ? "" : "s"})`}
|
|
293
|
+
</label>
|
|
294
|
+
)}
|
|
295
|
+
</>
|
|
296
|
+
)}
|
|
297
|
+
<div className="sound-gen-price" data-testid="generate-price">
|
|
298
|
+
{status === null
|
|
299
|
+
? "Checking what this studio can make…"
|
|
300
|
+
: available?.ok !== true
|
|
301
|
+
? (available?.reason ?? "Not available here")
|
|
302
|
+
: keyLine(status, takes)}
|
|
303
|
+
{status?.needsKey === "add" ? (
|
|
304
|
+
<>
|
|
305
|
+
{" "}
|
|
306
|
+
<a className="sound-gen-key-link" data-testid="generate-add-key" href={ADD_KEY_HREF} target="_blank" rel="noopener">
|
|
307
|
+
Open AI keys ↗
|
|
308
|
+
</a>
|
|
309
|
+
</>
|
|
310
|
+
) : null}
|
|
311
|
+
</div>
|
|
312
|
+
<button
|
|
313
|
+
type="button"
|
|
314
|
+
className="btn is-primary sound-gen-go"
|
|
315
|
+
data-testid="generate-go"
|
|
316
|
+
disabled={!canGenerate}
|
|
317
|
+
onClick={() => void generate()}
|
|
318
|
+
>
|
|
319
|
+
{busy === "generate" ? "Generating…" : `Generate ${KIND_LABEL[kind].toLowerCase()}`}
|
|
320
|
+
</button>
|
|
321
|
+
{error === null ? null : (
|
|
322
|
+
<p className="sound-gen-error" role="alert">
|
|
323
|
+
{error}
|
|
324
|
+
</p>
|
|
325
|
+
)}
|
|
326
|
+
{made === null ? null : (
|
|
327
|
+
<div className="sound-gen-take" data-testid="generate-take">
|
|
328
|
+
{made.order.map((take, i) => (
|
|
329
|
+
<div key={take.id} className="sound-gen-take-row" data-testid="generate-take-row">
|
|
330
|
+
<span className="sound-gen-take-name" title={take.prompt}>
|
|
331
|
+
{made.order.length > 1 ? (i === 0 ? "Kept · " : "Other take · ") : ""}
|
|
332
|
+
{take.prompt}
|
|
333
|
+
</span>
|
|
334
|
+
<audio className="sound-gen-audio" controls preload="metadata" src={take.url} />
|
|
335
|
+
<button
|
|
336
|
+
type="button"
|
|
337
|
+
className={i === 0 ? "btn is-primary" : "btn is-outline"}
|
|
338
|
+
data-testid={i === 0 ? "generate-place" : "generate-place-other"}
|
|
339
|
+
disabled={busy !== null || target === undefined}
|
|
340
|
+
onClick={() => void place(take, made.scored)}
|
|
341
|
+
>
|
|
342
|
+
Add to {target?.name ?? "track"} {made.scored ? "at the start" : "at playhead"}
|
|
343
|
+
</button>
|
|
344
|
+
</div>
|
|
345
|
+
))}
|
|
346
|
+
{made.reasons.length === 0 ? null : (
|
|
347
|
+
<p className="sound-gen-why" data-testid="generate-why">
|
|
348
|
+
{made.reasons.join(" ")}
|
|
349
|
+
</p>
|
|
350
|
+
)}
|
|
351
|
+
{made.tone === null ? null : (
|
|
352
|
+
<p className="sound-gen-why" data-testid="generate-tone">
|
|
353
|
+
{made.tone}
|
|
354
|
+
</p>
|
|
355
|
+
)}
|
|
356
|
+
<div className="sound-gen-take-actions">
|
|
357
|
+
<button type="button" className="btn is-outline" disabled={!canGenerate} onClick={() => void generate()}>
|
|
358
|
+
{takes > 1 ? "Two more takes" : "Another take"}
|
|
359
|
+
</button>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
{tracks.length > 1 ? (
|
|
364
|
+
<label className="sound-gen-field">
|
|
365
|
+
<span className="sound-gen-label">Track</span>
|
|
366
|
+
<select
|
|
367
|
+
className="select sound-gen-select"
|
|
368
|
+
data-testid="generate-track"
|
|
369
|
+
value={target?.id ?? ""}
|
|
370
|
+
onChange={(e) => setTrackId(e.currentTarget.value)}
|
|
371
|
+
>
|
|
372
|
+
{tracks.map((t) => (
|
|
373
|
+
<option key={t.id} value={t.id}>
|
|
374
|
+
{t.name}
|
|
375
|
+
</option>
|
|
376
|
+
))}
|
|
377
|
+
</select>
|
|
378
|
+
</label>
|
|
379
|
+
) : null}
|
|
380
|
+
{status !== null && status.recent.length > 0 ? (
|
|
381
|
+
<div className="sound-gen-recent">
|
|
382
|
+
<span className="sound-gen-label">Recent</span>
|
|
383
|
+
<ul className="sound-gen-list">
|
|
384
|
+
{status.recent.map((r) => (
|
|
385
|
+
<li key={r.id} className="sound-gen-item">
|
|
386
|
+
<span className="sound-gen-item-kind">{KIND_LABEL[r.kind]}</span>
|
|
387
|
+
<span className="sound-gen-item-prompt" title={r.prompt}>
|
|
388
|
+
{r.prompt}
|
|
389
|
+
</span>
|
|
390
|
+
<button
|
|
391
|
+
type="button"
|
|
392
|
+
className="btn is-ghost sound-gen-item-add"
|
|
393
|
+
disabled={busy !== null || target === undefined}
|
|
394
|
+
title={`Add to ${target?.name ?? "track"} ${r.scored === true ? "at the start, where its sections line up" : "at the playhead"}`}
|
|
395
|
+
onClick={() => void place(r, r.scored === true)}
|
|
396
|
+
>
|
|
397
|
+
Add
|
|
398
|
+
</button>
|
|
399
|
+
</li>
|
|
400
|
+
))}
|
|
401
|
+
</ul>
|
|
402
|
+
</div>
|
|
403
|
+
) : null}
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// THE STRETCH BRACKET (Rows face): the selected keys' span, with a grab tab at
|
|
2
|
+
// each end. Dragging one scales every selected key's frame about the other end
|
|
3
|
+
// (`dragKeyStretch`, `keyStretchMoves`), across tracks, layers and Shots. Graph
|
|
4
|
+
// mode has its own bracket over one curve; both run `retimeLandings`.
|
|
5
|
+
//
|
|
6
|
+
// It is how a motion preset is retimed without the document remembering one:
|
|
7
|
+
// a stamp selects the keys it wrote, and this bracket stands over them.
|
|
8
|
+
//
|
|
9
|
+
// TWO HALVES, because the dock scrolls under a sticky ruler. The tabs and the
|
|
10
|
+
// readout live IN THE RULER (`KeyStretchTabs`): always in sight, and above the
|
|
11
|
+
// tracks, where a tab in the rows would scroll away or slide under the ruler.
|
|
12
|
+
// The guides through the rows live in `.dock-overlay` (`KeyStretchGuides`) and
|
|
13
|
+
// take no pointer. The tabs sit just inside each end of a band over the span,
|
|
14
|
+
// so the ruler's clip never hides one at frame 0. Both read the dock's frames:
|
|
15
|
+
// a Shot's keys plus its start, less the shot context's re-zero. A pointer
|
|
16
|
+
// gesture, like Graph mode's bracket: the keyboard retimes keys by nudging.
|
|
17
|
+
import { useState } from "react";
|
|
18
|
+
import type { PointerEvent as ReactPointerEvent } from "react";
|
|
19
|
+
import { frameToPx, startDrag } from "@dailies-studio/timeline";
|
|
20
|
+
import { isDefId, sceneStart } from "@dailies-studio/shot/doc";
|
|
21
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
22
|
+
import { frameDelta } from "./model.js";
|
|
23
|
+
|
|
24
|
+
interface Span {
|
|
25
|
+
min: number;
|
|
26
|
+
max: number;
|
|
27
|
+
left: number;
|
|
28
|
+
right: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The selected keys' span in dock frames and pixels, or null when there is
|
|
32
|
+
* no bracket to draw: not the Rows face, a viewer, or under two frames. */
|
|
33
|
+
function useStretchSpan(ppf: number, origin: number): Span | null {
|
|
34
|
+
const keys = useShotEditor((s) => s.selectedKeyframes);
|
|
35
|
+
const doc = useShotEditor((s) => s.doc);
|
|
36
|
+
const rows = useShotEditor((s) => s.dockMode === "rows");
|
|
37
|
+
const readOnly = useShotEditor((s) => s.readOnly);
|
|
38
|
+
if (!rows || readOnly || doc === null || keys.length < 2) return null;
|
|
39
|
+
const at = keys.map((k) => k.frame + (isDefId(k.containerId) ? 0 : (sceneStart(doc, k.containerId) ?? 0)));
|
|
40
|
+
const min = Math.min(...at);
|
|
41
|
+
const max = Math.max(...at);
|
|
42
|
+
if (min === max) return null;
|
|
43
|
+
return { min, max, left: frameToPx(min - origin, ppf), right: frameToPx(max - origin, ppf) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The guides through the rows, in `.dock-overlay`. */
|
|
47
|
+
export function KeyStretchGuides({ ppf, origin }: { ppf: number; origin: number }) {
|
|
48
|
+
const span = useStretchSpan(ppf, origin);
|
|
49
|
+
if (span === null) return null;
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<span className="dock-key-stretch-guide" style={{ left: span.left }} />
|
|
53
|
+
<span className="dock-key-stretch-guide" style={{ left: span.right }} />
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The grab tabs and the drag's readout, in the ruler's track area. */
|
|
59
|
+
export function KeyStretchTabs({ ppf, origin }: { ppf: number; origin: number }) {
|
|
60
|
+
const store = useStore();
|
|
61
|
+
const span = useStretchSpan(ppf, origin);
|
|
62
|
+
const [chip, setChip] = useState<{ edge: "left" | "right"; label: string } | null>(null);
|
|
63
|
+
if (span === null) return null;
|
|
64
|
+
|
|
65
|
+
const grab = (edge: "left" | "right") => (e: ReactPointerEvent<HTMLElement>): void => {
|
|
66
|
+
if (e.button !== 0) return;
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
// The ruler's own press seeks: a tab is not the ruler.
|
|
69
|
+
e.stopPropagation();
|
|
70
|
+
if (!store.beginBlockDrag()) return;
|
|
71
|
+
const startX = e.clientX;
|
|
72
|
+
const width = span.max - span.min;
|
|
73
|
+
setChip({ edge, label: "×1.00" });
|
|
74
|
+
startDrag(e, {
|
|
75
|
+
onMove(ev) {
|
|
76
|
+
const factor = store.dragKeyStretch(edge, frameDelta(ev.clientX - startX, ppf));
|
|
77
|
+
if (factor !== null) setChip({ edge, label: `×${factor.toFixed(2)} · ${Math.round(width * factor)}f` });
|
|
78
|
+
},
|
|
79
|
+
onEnd(commit) {
|
|
80
|
+
setChip(null);
|
|
81
|
+
store.endBlockDrag(commit);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<>
|
|
88
|
+
<span className="dock-key-stretch-band" style={{ left: span.left, width: span.right - span.left }} />
|
|
89
|
+
<span
|
|
90
|
+
className="dock-key-stretch-handle is-left"
|
|
91
|
+
data-testid="key-stretch-left"
|
|
92
|
+
title="Drag to stretch the selected keyframes"
|
|
93
|
+
style={{ left: span.left }}
|
|
94
|
+
onPointerDown={grab("left")}
|
|
95
|
+
/>
|
|
96
|
+
<span
|
|
97
|
+
className="dock-key-stretch-handle is-right"
|
|
98
|
+
data-testid="key-stretch-right"
|
|
99
|
+
title="Drag to stretch the selected keyframes"
|
|
100
|
+
style={{ left: span.right }}
|
|
101
|
+
onPointerDown={grab("right")}
|
|
102
|
+
/>
|
|
103
|
+
{chip === null ? null : (
|
|
104
|
+
<span
|
|
105
|
+
className="stagger-chip dock-key-stretch-chip"
|
|
106
|
+
data-testid="key-stretch-readout"
|
|
107
|
+
role="status"
|
|
108
|
+
style={{ left: chip.edge === "left" ? span.left : span.right }}
|
|
109
|
+
>
|
|
110
|
+
<span className="stagger-chip-step">{chip.label}</span>
|
|
111
|
+
</span>
|
|
112
|
+
)}
|
|
113
|
+
</>
|
|
114
|
+
);
|
|
115
|
+
}
|