@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,293 @@
|
|
|
1
|
+
// The Library's right column for a SOUND PACK: the pack's files in manifest
|
|
2
|
+
// order, an audition per file, the picked file's waveform, the Add that places
|
|
3
|
+
// that one file as a clip in the open Shot, and the Pin that lands it as a
|
|
4
|
+
// sound card. A pack is never a card itself (the validator refuses a `library`
|
|
5
|
+
// card of kind "sounds"), so this column adds one sound at a time and the
|
|
6
|
+
// browser's `onAdd` never sees a pack.
|
|
7
|
+
//
|
|
8
|
+
// ONE AUDITION AT A TIME, through ONE `Audio` element owned by this panel. A
|
|
9
|
+
// second play stops the first; closing the Library or picking another item
|
|
10
|
+
// unmounts the panel, which stops it. It follows the tab's monitor settings
|
|
11
|
+
// (`previewMuted`, `previewVolume`), the ones the transport's speaker sets,
|
|
12
|
+
// because an author who muted the preview did not mean "except the library".
|
|
13
|
+
//
|
|
14
|
+
// THE BYTES COME FROM THE LIBRARY ROUTE, `libraryFileUrl` (`GET /api/library/
|
|
15
|
+
// items/<id>/files/<name>`), never the Project's asset store: auditioning a
|
|
16
|
+
// sound must not import the pack. The waveform is the timeline's own
|
|
17
|
+
// `loadPeaks` and `drawClipWaveform`, keyed on that URL. `loadPeaks` caches by
|
|
18
|
+
// `asset.path` and fetches `/studio/assets/<path>` by default, so the URL rides
|
|
19
|
+
// in as the path and `fetchBytes` fetches it as it is: a root-relative URL
|
|
20
|
+
// can never collide with a Project asset path, so the shared cache stays
|
|
21
|
+
// honest without a second waveform module.
|
|
22
|
+
import { useEffect, useRef, useState } from "react";
|
|
23
|
+
import { libraryFileLabel, libraryFileUrl } from "@dailies-studio/shot/doc";
|
|
24
|
+
import { drawClipWaveform, loadPeaks } from "@dailies-studio/timeline";
|
|
25
|
+
import type { PeakData } from "@dailies-studio/timeline";
|
|
26
|
+
import type { LibraryItem, LibraryItemDetail } from "../../api.js";
|
|
27
|
+
import { ADD_ICON, CHEVRON_DOWN_ICON, PLAY_ICON, STOP_ICON } from "../../icons.js";
|
|
28
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
29
|
+
import { openBoardForPin, placeInShotRefusal } from "../../state/store.js";
|
|
30
|
+
import { formatDuration } from "../timeline/model.js";
|
|
31
|
+
import { displayNameOf, plural } from "./libraryModel.js";
|
|
32
|
+
|
|
33
|
+
type SoundFile = NonNullable<LibraryItemDetail["sounds"]>["files"][number];
|
|
34
|
+
|
|
35
|
+
const NONE: never[] = [];
|
|
36
|
+
|
|
37
|
+
/** Rows shown before "Show all": Paper's fold, enough to read the pack's
|
|
38
|
+
* range without pushing the waveform and the Add off a laptop screen. */
|
|
39
|
+
export const SOUND_FOLD = 7;
|
|
40
|
+
|
|
41
|
+
/** The bare file name `addStoryboardLibrarySound` matches the import on. */
|
|
42
|
+
const fileNameOf = (file: SoundFile): string => file.path.slice(file.path.lastIndexOf("/") + 1);
|
|
43
|
+
|
|
44
|
+
export function LibrarySoundPanel({
|
|
45
|
+
item,
|
|
46
|
+
canAdd,
|
|
47
|
+
adding,
|
|
48
|
+
}: {
|
|
49
|
+
item: LibraryItem;
|
|
50
|
+
canAdd: boolean;
|
|
51
|
+
adding: boolean;
|
|
52
|
+
}) {
|
|
53
|
+
const store = useStore();
|
|
54
|
+
const detail = useShotEditor((s) => s.libraryItems[item.id]);
|
|
55
|
+
const muted = useShotEditor((s) => s.previewMuted);
|
|
56
|
+
const volume = useShotEditor((s) => s.previewVolume);
|
|
57
|
+
const [failed, setFailed] = useState(false);
|
|
58
|
+
const [picked, setPicked] = useState<string | null>(null);
|
|
59
|
+
const [playing, setPlaying] = useState<string | null>(null);
|
|
60
|
+
const [showAll, setShowAll] = useState(false);
|
|
61
|
+
// ponytail: this add's in-flight flag is the panel's own, so the browser's
|
|
62
|
+
// `adding` does not see it; lift it into the browser if a second add door
|
|
63
|
+
// ever shares this column.
|
|
64
|
+
const [busy, setBusy] = useState(false);
|
|
65
|
+
const [error, setError] = useState<string | null>(null);
|
|
66
|
+
const placeRefusal = useShotEditor((s) => placeInShotRefusal(s, "sound"));
|
|
67
|
+
const boardOpen = useShotEditor((s) => s.storyboardOpen);
|
|
68
|
+
const audio = useRef<HTMLAudioElement | null>(null);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
let alive = true;
|
|
72
|
+
void store.loadLibraryItem(item.id).then((d) => {
|
|
73
|
+
if (alive && d === null) setFailed(true);
|
|
74
|
+
});
|
|
75
|
+
return () => {
|
|
76
|
+
alive = false;
|
|
77
|
+
};
|
|
78
|
+
}, [item.id, store]);
|
|
79
|
+
// Stop on unmount: the Library closed, or another item was picked.
|
|
80
|
+
useEffect(
|
|
81
|
+
() => () => {
|
|
82
|
+
audio.current?.pause();
|
|
83
|
+
audio.current = null;
|
|
84
|
+
},
|
|
85
|
+
[],
|
|
86
|
+
);
|
|
87
|
+
// The monitor settings follow live, mid-audition included.
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (audio.current === null) return;
|
|
90
|
+
audio.current.muted = muted;
|
|
91
|
+
audio.current.volume = volume;
|
|
92
|
+
}, [muted, volume]);
|
|
93
|
+
|
|
94
|
+
const files = detail?.sounds?.files ?? NONE;
|
|
95
|
+
const name = displayNameOf(item);
|
|
96
|
+
const selected = files.find((f) => f.path === picked) ?? files[0];
|
|
97
|
+
const shown = showAll ? files : files.slice(0, SOUND_FOLD);
|
|
98
|
+
|
|
99
|
+
const stop = (): void => {
|
|
100
|
+
audio.current?.pause();
|
|
101
|
+
audio.current = null;
|
|
102
|
+
setPlaying(null);
|
|
103
|
+
};
|
|
104
|
+
const audition = (file: SoundFile): void => {
|
|
105
|
+
setPicked(file.path);
|
|
106
|
+
if (playing === file.path) {
|
|
107
|
+
stop();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
audio.current?.pause();
|
|
111
|
+
const el = new Audio(libraryFileUrl(item.id, file.path));
|
|
112
|
+
el.muted = muted;
|
|
113
|
+
el.volume = volume;
|
|
114
|
+
const done = (): void => {
|
|
115
|
+
if (audio.current !== el) return;
|
|
116
|
+
audio.current = null;
|
|
117
|
+
setPlaying(null);
|
|
118
|
+
};
|
|
119
|
+
el.addEventListener("ended", done);
|
|
120
|
+
el.addEventListener("error", done);
|
|
121
|
+
audio.current = el;
|
|
122
|
+
setPlaying(file.path);
|
|
123
|
+
// A refused play (autoplay policy, an unreadable file) ends the audition
|
|
124
|
+
// quietly: the row's button goes back to Play, which is the whole truth.
|
|
125
|
+
void el.play().catch(done);
|
|
126
|
+
};
|
|
127
|
+
// ADD places the sound as a clip at the playhead of the open Shot (D2); PIN
|
|
128
|
+
// makes it a sound card for the agent. Both import that one file only.
|
|
129
|
+
const run = (act: () => Promise<unknown>, placed: boolean): void => {
|
|
130
|
+
setBusy(true);
|
|
131
|
+
setError(null);
|
|
132
|
+
// The store reports its own refusals to the shell's error strip, which
|
|
133
|
+
// sits under this dialog, so the strip is cleared first and whatever the
|
|
134
|
+
// add leaves there is printed here too.
|
|
135
|
+
store.dismissError();
|
|
136
|
+
void act()
|
|
137
|
+
.then(() => {
|
|
138
|
+
const message = store.getState().error;
|
|
139
|
+
if (message !== null) setError(message);
|
|
140
|
+
// A placed sound is on the timeline under this dialog: show it.
|
|
141
|
+
else if (placed && store.getState().libraryBrowserOpen) store.setLibraryBrowserOpen(false);
|
|
142
|
+
})
|
|
143
|
+
.finally(() => setBusy(false));
|
|
144
|
+
};
|
|
145
|
+
const add = (file: SoundFile): void => run(() => store.dropLibraryOnFrame(item.id, fileNameOf(file)), true);
|
|
146
|
+
const pin = (file: SoundFile): void =>
|
|
147
|
+
run(async () => {
|
|
148
|
+
if (openBoardForPin(store)) await store.addStoryboardLibrarySound(null, item.id, fileNameOf(file));
|
|
149
|
+
}, false);
|
|
150
|
+
|
|
151
|
+
let body;
|
|
152
|
+
if (detail === undefined) {
|
|
153
|
+
body = <p className="library-preview-note">{failed ? "The sounds could not be read." : "Loading the sounds…"}</p>;
|
|
154
|
+
} else if (files.length === 0) {
|
|
155
|
+
body = <p className="library-preview-note">This pack ships no sounds.</p>;
|
|
156
|
+
} else {
|
|
157
|
+
body = (
|
|
158
|
+
<>
|
|
159
|
+
<ul className="library-sound-list" aria-label={`Sounds in ${name}`}>
|
|
160
|
+
{shown.map((file) => {
|
|
161
|
+
const label = libraryFileLabel(file);
|
|
162
|
+
const on = file === selected;
|
|
163
|
+
const live = playing === file.path;
|
|
164
|
+
return (
|
|
165
|
+
<li key={file.path} className={on ? "library-sound-row is-selected" : "library-sound-row"} data-file={file.path}>
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
className="library-sound-play"
|
|
169
|
+
aria-label={live ? `Stop ${label}` : `Play ${label}`}
|
|
170
|
+
onClick={() => audition(file)}
|
|
171
|
+
>
|
|
172
|
+
{live ? STOP_ICON : PLAY_ICON}
|
|
173
|
+
</button>
|
|
174
|
+
<button type="button" className="library-sound-pick" aria-pressed={on} onClick={() => setPicked(file.path)}>
|
|
175
|
+
<span className="library-sound-name">{label}</span>
|
|
176
|
+
<span className="library-sound-length">{formatDuration(file.durationMs / 1000)}</span>
|
|
177
|
+
</button>
|
|
178
|
+
</li>
|
|
179
|
+
);
|
|
180
|
+
})}
|
|
181
|
+
</ul>
|
|
182
|
+
{files.length > SOUND_FOLD && !showAll ? (
|
|
183
|
+
<button type="button" className="library-sound-more" onClick={() => setShowAll(true)}>
|
|
184
|
+
Show all {files.length} sounds
|
|
185
|
+
{CHEVRON_DOWN_ICON}
|
|
186
|
+
</button>
|
|
187
|
+
) : null}
|
|
188
|
+
{selected === undefined ? null : (
|
|
189
|
+
<div className="library-sound-selected" data-testid="library-sound-selected">
|
|
190
|
+
<p className="library-sound-selected-name">
|
|
191
|
+
<span>{libraryFileLabel(selected)}</span>
|
|
192
|
+
<span className="library-sound-length">{formatDuration(selected.durationMs / 1000)}</span>
|
|
193
|
+
</p>
|
|
194
|
+
<SoundWave url={libraryFileUrl(item.id, selected.path)} />
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
className="btn is-primary is-lg"
|
|
198
|
+
data-testid="library-add-sound"
|
|
199
|
+
disabled={!canAdd || adding || busy}
|
|
200
|
+
aria-disabled={placeRefusal !== null || undefined}
|
|
201
|
+
title={!canAdd ? "Open a Project to add library items." : (placeRefusal ?? "Import this sound and place it at the playhead of the open Shot.")}
|
|
202
|
+
onClick={() => {
|
|
203
|
+
if (placeRefusal === null) add(selected);
|
|
204
|
+
}}
|
|
205
|
+
>
|
|
206
|
+
{ADD_ICON}
|
|
207
|
+
{busy ? "Adding…" : "Add to shot"}
|
|
208
|
+
</button>
|
|
209
|
+
<button
|
|
210
|
+
type="button"
|
|
211
|
+
className="btn is-outline is-lg"
|
|
212
|
+
data-testid="library-pin-sound"
|
|
213
|
+
disabled={!canAdd || adding || busy}
|
|
214
|
+
title={canAdd ? `Import this sound and pin it as a card the agent reads.${boardOpen ? "" : " Opens the storyboard."}` : "Open a Project to add library items."}
|
|
215
|
+
onClick={() => pin(selected)}
|
|
216
|
+
>
|
|
217
|
+
Pin to board
|
|
218
|
+
</button>
|
|
219
|
+
{error === null ? null : (
|
|
220
|
+
<p className="library-preview-error" role="alert" data-testid="library-add-error">
|
|
221
|
+
{error}
|
|
222
|
+
</p>
|
|
223
|
+
)}
|
|
224
|
+
<p className="library-preview-hint">Add places it in the open Shot. Pin saves it for later.</p>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
</>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<aside className="library-preview is-sounds" aria-label={`Preview: ${name}`} data-testid="library-preview">
|
|
233
|
+
<div className="library-preview-purpose">
|
|
234
|
+
<h2 className="library-preview-name">{name}</h2>
|
|
235
|
+
{files.length === 0 ? null : <p className="library-preview-note">{plural(files.length, "sound")} · Choose one to preview</p>}
|
|
236
|
+
</div>
|
|
237
|
+
{body}
|
|
238
|
+
<footer className="library-preview-meta">
|
|
239
|
+
<span>Sound pack</span>
|
|
240
|
+
{item.version === undefined ? null : <span>v{item.version}</span>}
|
|
241
|
+
</footer>
|
|
242
|
+
</aside>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** The picked sound's envelope. Decoration: a file that will not decode
|
|
247
|
+
* leaves the well empty, and the canvas is hidden from assistive tech. */
|
|
248
|
+
export function SoundWave({ url }: { url: string }) {
|
|
249
|
+
const canvas = useRef<HTMLCanvasElement | null>(null);
|
|
250
|
+
const [data, setData] = useState<PeakData | null>(null);
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
let alive = true;
|
|
253
|
+
setData(null);
|
|
254
|
+
loadPeaks(
|
|
255
|
+
{ path: url },
|
|
256
|
+
{
|
|
257
|
+
fetchBytes: async () => {
|
|
258
|
+
// A stock sound's file is on its site's CDN: asked for with no
|
|
259
|
+
// cookie and no page address, as the search is. A pack sound is
|
|
260
|
+
// the studio's own, read as every asset is.
|
|
261
|
+
const own = new URL(url, location.href).origin === location.origin;
|
|
262
|
+
const res = await fetch(url, own ? {} : { credentials: "omit", referrerPolicy: "no-referrer" });
|
|
263
|
+
if (!res.ok) throw new Error(`${url}: ${res.status}`);
|
|
264
|
+
return await res.arrayBuffer();
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
)
|
|
268
|
+
.then((d) => {
|
|
269
|
+
if (alive) setData(d);
|
|
270
|
+
})
|
|
271
|
+
.catch(() => {});
|
|
272
|
+
return () => {
|
|
273
|
+
alive = false;
|
|
274
|
+
};
|
|
275
|
+
}, [url]);
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
const el = canvas.current;
|
|
278
|
+
if (el === null || data === null || data.peaks.length === 0) return;
|
|
279
|
+
drawClipWaveform(el, {
|
|
280
|
+
data,
|
|
281
|
+
fromSeconds: 0,
|
|
282
|
+
toSeconds: data.sourceSeconds || 1,
|
|
283
|
+
widthPx: el.clientWidth || 240,
|
|
284
|
+
heightPx: el.clientHeight || 46,
|
|
285
|
+
color: getComputedStyle(el).color,
|
|
286
|
+
});
|
|
287
|
+
}, [data]);
|
|
288
|
+
return (
|
|
289
|
+
<div className="library-sound-wave">
|
|
290
|
+
<canvas ref={canvas} aria-hidden="true" data-testid="library-sound-wave" data-drawn={data !== null && data.peaks.length > 0 ? "true" : "false"} />
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// The Library's Generated sounds: the workspace's sound history. The sound
|
|
2
|
+
// effects, music beds and voice lines this member made, or that were made for
|
|
3
|
+
// a Project they may view: hosted, every one added to a Project, and the rest
|
|
4
|
+
// for a day after they were made; locally every take in the store, since a
|
|
5
|
+
// take there is a file. It fills the catalog and preview columns, like Stock
|
|
6
|
+
// sounds.
|
|
7
|
+
//
|
|
8
|
+
// ADD places the take as a bed clip at the playhead (`placeTake`, the Generate
|
|
9
|
+
// drawer's own verb), a scored bed at frame 0 where its sections line up, so a
|
|
10
|
+
// take is reused without paying for it again. DELETE takes it out of the
|
|
11
|
+
// history: hosted its waiting bytes go, locally its file stays for any Project
|
|
12
|
+
// that uses it.
|
|
13
|
+
//
|
|
14
|
+
// A PAGE CAN BE EMPTY AND STILL GO ON: hosted, a page reads a bounded number
|
|
15
|
+
// of rows and keeps the ones this member may see, so "Load more" shows
|
|
16
|
+
// whenever there is a `next`, list or no list.
|
|
17
|
+
import { useEffect, useRef, useState } from "react";
|
|
18
|
+
import { SegmentedControl } from "@dailies-studio/ui";
|
|
19
|
+
import { deleteGeneration, generationHistory } from "../../api.js";
|
|
20
|
+
import type { GenerationKind, GenerationTake } from "../../api.js";
|
|
21
|
+
import { ADD_ICON, PLAY_ICON, STOP_ICON } from "../../icons.js";
|
|
22
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
23
|
+
import { formatDuration } from "../timeline/model.js";
|
|
24
|
+
|
|
25
|
+
type Filter = "all" | GenerationKind;
|
|
26
|
+
|
|
27
|
+
const KIND_WORDS: Record<GenerationKind, string> = { sfx: "Sound effect", music: "Music", voice: "Voice" };
|
|
28
|
+
|
|
29
|
+
export function SoundHistory({ canAdd }: { canAdd: boolean }) {
|
|
30
|
+
const store = useStore();
|
|
31
|
+
const muted = useShotEditor((s) => s.previewMuted);
|
|
32
|
+
const volume = useShotEditor((s) => s.previewVolume);
|
|
33
|
+
const [takes, setTakes] = useState<GenerationTake[] | null>(null);
|
|
34
|
+
const [next, setNext] = useState<string | null>(null);
|
|
35
|
+
/** A page on its way: a second "Load more" waits for it. */
|
|
36
|
+
const [loading, setLoading] = useState(false);
|
|
37
|
+
const [failed, setFailed] = useState<string | null>(null);
|
|
38
|
+
const [filter, setFilter] = useState<Filter>("all");
|
|
39
|
+
const [picked, setPicked] = useState<string | null>(null);
|
|
40
|
+
const [playing, setPlaying] = useState<string | null>(null);
|
|
41
|
+
const [busy, setBusy] = useState(false);
|
|
42
|
+
const [error, setError] = useState<string | null>(null);
|
|
43
|
+
const audio = useRef<HTMLAudioElement | null>(null);
|
|
44
|
+
|
|
45
|
+
const load = (before?: string): void => {
|
|
46
|
+
setLoading(true);
|
|
47
|
+
void generationHistory(before)
|
|
48
|
+
.then(
|
|
49
|
+
(page) => {
|
|
50
|
+
setTakes((held) => (before === undefined ? page.takes : [...(held ?? []), ...page.takes]));
|
|
51
|
+
setNext(page.next);
|
|
52
|
+
},
|
|
53
|
+
(e: unknown) => setFailed(e instanceof Error ? e.message : String(e)),
|
|
54
|
+
)
|
|
55
|
+
.finally(() => setLoading(false));
|
|
56
|
+
};
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
load();
|
|
59
|
+
return () => {
|
|
60
|
+
audio.current?.pause();
|
|
61
|
+
audio.current = null;
|
|
62
|
+
};
|
|
63
|
+
}, []);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (audio.current === null) return;
|
|
66
|
+
audio.current.muted = muted;
|
|
67
|
+
audio.current.volume = volume;
|
|
68
|
+
}, [muted, volume]);
|
|
69
|
+
|
|
70
|
+
const shown = (takes ?? []).filter((t) => filter === "all" || t.kind === filter);
|
|
71
|
+
const selected = shown.find((t) => t.id === picked) ?? shown[0] ?? null;
|
|
72
|
+
|
|
73
|
+
const audition = (take: GenerationTake): void => {
|
|
74
|
+
setPicked(take.id);
|
|
75
|
+
audio.current?.pause();
|
|
76
|
+
if (playing === take.id) {
|
|
77
|
+
audio.current = null;
|
|
78
|
+
setPlaying(null);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const el = new Audio(take.url);
|
|
82
|
+
el.muted = muted;
|
|
83
|
+
el.volume = volume;
|
|
84
|
+
const done = (): void => {
|
|
85
|
+
if (audio.current !== el) return;
|
|
86
|
+
audio.current = null;
|
|
87
|
+
setPlaying(null);
|
|
88
|
+
};
|
|
89
|
+
el.addEventListener("ended", done);
|
|
90
|
+
el.addEventListener("error", done);
|
|
91
|
+
audio.current = el;
|
|
92
|
+
setPlaying(take.id);
|
|
93
|
+
void el.play().catch(done);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const add = (take: GenerationTake): void => {
|
|
97
|
+
setBusy(true);
|
|
98
|
+
setError(null);
|
|
99
|
+
void store
|
|
100
|
+
.placeTake(take, undefined, take.scored === true ? 0 : undefined)
|
|
101
|
+
.then(() => {
|
|
102
|
+
if (store.getState().libraryBrowserOpen) store.setLibraryBrowserOpen(false);
|
|
103
|
+
})
|
|
104
|
+
.catch((e: unknown) => setError(e instanceof Error ? e.message : String(e)))
|
|
105
|
+
.finally(() => setBusy(false));
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const remove = (take: GenerationTake): void => {
|
|
109
|
+
setBusy(true);
|
|
110
|
+
setError(null);
|
|
111
|
+
void deleteGeneration(take.id)
|
|
112
|
+
.then(() => setTakes((held) => (held ?? []).filter((t) => t.id !== take.id)))
|
|
113
|
+
.catch((e: unknown) => setError(e instanceof Error ? e.message : String(e)))
|
|
114
|
+
.finally(() => setBusy(false));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const more =
|
|
118
|
+
next === null ? null : (
|
|
119
|
+
<button type="button" className="btn is-outline" data-testid="history-more" disabled={loading} onClick={() => load(next)}>
|
|
120
|
+
{loading ? "Loading…" : "Load more"}
|
|
121
|
+
</button>
|
|
122
|
+
);
|
|
123
|
+
let body;
|
|
124
|
+
if (takes === null) {
|
|
125
|
+
body = (
|
|
126
|
+
<p className="library-preview-note" role="status">
|
|
127
|
+
{failed ?? "Reading the sound history…"}
|
|
128
|
+
</p>
|
|
129
|
+
);
|
|
130
|
+
} else if (shown.length === 0) {
|
|
131
|
+
body = (
|
|
132
|
+
<>
|
|
133
|
+
<p className="library-preview-note" role="status">
|
|
134
|
+
{takes.length === 0
|
|
135
|
+
? next === null
|
|
136
|
+
? "Nothing generated yet. Sound mode's Generate makes effects, music and voice lines."
|
|
137
|
+
: "None of the newest takes is yours to see. Load more to look further back."
|
|
138
|
+
: "No takes of this kind yet."}
|
|
139
|
+
</p>
|
|
140
|
+
{more}
|
|
141
|
+
</>
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
body = (
|
|
145
|
+
<>
|
|
146
|
+
<ul className="library-sound-list" aria-label="Generated sounds" data-testid="history-list">
|
|
147
|
+
{shown.map((take) => {
|
|
148
|
+
const live = playing === take.id;
|
|
149
|
+
const on = take === selected;
|
|
150
|
+
return (
|
|
151
|
+
<li key={take.id} className={on ? "library-sound-row is-selected" : "library-sound-row"} data-id={take.id}>
|
|
152
|
+
<button type="button" className="library-sound-play" aria-label={live ? `Stop ${take.prompt}` : `Play ${take.prompt}`} onClick={() => audition(take)}>
|
|
153
|
+
{live ? STOP_ICON : PLAY_ICON}
|
|
154
|
+
</button>
|
|
155
|
+
<button type="button" className="library-sound-pick" aria-pressed={on} onClick={() => setPicked(take.id)}>
|
|
156
|
+
<span className="library-sound-name">{take.prompt}</span>
|
|
157
|
+
<span className="stock-sound-license">{KIND_WORDS[take.kind]}</span>
|
|
158
|
+
{take.seconds === null ? null : <span className="library-sound-length">{formatDuration(take.seconds)}</span>}
|
|
159
|
+
</button>
|
|
160
|
+
</li>
|
|
161
|
+
);
|
|
162
|
+
})}
|
|
163
|
+
</ul>
|
|
164
|
+
{more}
|
|
165
|
+
</>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<>
|
|
171
|
+
<section className="library-catalog" aria-label="Generated sounds" data-testid="sound-history">
|
|
172
|
+
<header className="library-catalog-head">
|
|
173
|
+
<div>
|
|
174
|
+
<h2>Generated sounds</h2>
|
|
175
|
+
<p>Generated sounds you can open: every one added to a Project, and the rest for a day.</p>
|
|
176
|
+
</div>
|
|
177
|
+
</header>
|
|
178
|
+
<SegmentedControl
|
|
179
|
+
ariaLabel="Kind of take"
|
|
180
|
+
className="stock-kind"
|
|
181
|
+
value={filter}
|
|
182
|
+
options={[
|
|
183
|
+
{ value: "all", label: "All" },
|
|
184
|
+
{ value: "sfx", label: "Effects" },
|
|
185
|
+
{ value: "music", label: "Music" },
|
|
186
|
+
{ value: "voice", label: "Voice" },
|
|
187
|
+
]}
|
|
188
|
+
onChange={(next) => setFilter(next as Filter)}
|
|
189
|
+
/>
|
|
190
|
+
{body}
|
|
191
|
+
</section>
|
|
192
|
+
<aside className="library-preview is-sounds" aria-label="Preview: generated sound" data-testid="history-preview">
|
|
193
|
+
{selected === null ? (
|
|
194
|
+
<p className="library-preview-empty">Pick a take to preview it here.</p>
|
|
195
|
+
) : (
|
|
196
|
+
<>
|
|
197
|
+
<div className="library-preview-purpose">
|
|
198
|
+
<h2 className="library-preview-name">{selected.prompt}</h2>
|
|
199
|
+
<p className="library-preview-note">
|
|
200
|
+
{KIND_WORDS[selected.kind]}
|
|
201
|
+
{selected.seconds === null ? "" : ` · ${formatDuration(selected.seconds)}`} · {new Date(selected.createdAt).toLocaleDateString()}
|
|
202
|
+
</p>
|
|
203
|
+
</div>
|
|
204
|
+
<div className="library-sound-selected" data-testid="history-selected">
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
className="btn is-primary is-lg"
|
|
208
|
+
data-testid="history-add"
|
|
209
|
+
disabled={!canAdd || busy}
|
|
210
|
+
title={
|
|
211
|
+
!canAdd
|
|
212
|
+
? "Open a Project to add sounds."
|
|
213
|
+
: selected.scored === true
|
|
214
|
+
? "Scored to a film's cuts: added at frame 0, where its sections line up."
|
|
215
|
+
: "Add this take at the playhead."
|
|
216
|
+
}
|
|
217
|
+
onClick={() => add(selected)}
|
|
218
|
+
>
|
|
219
|
+
{ADD_ICON}
|
|
220
|
+
{busy ? "Working…" : selected.scored === true ? "Add at the start" : "Add at playhead"}
|
|
221
|
+
</button>
|
|
222
|
+
<button type="button" className="btn is-outline is-lg" data-testid="history-delete" disabled={busy} onClick={() => remove(selected)}>
|
|
223
|
+
Delete from history
|
|
224
|
+
</button>
|
|
225
|
+
{error === null ? null : (
|
|
226
|
+
<p className="library-preview-error" role="alert" data-testid="history-error">
|
|
227
|
+
{error}
|
|
228
|
+
</p>
|
|
229
|
+
)}
|
|
230
|
+
<p className="library-preview-hint">
|
|
231
|
+
{selected.kept === true
|
|
232
|
+
? "Kept: it stays here until you delete it."
|
|
233
|
+
: "Not added to a Project yet: it leaves the history a day after it was made."}
|
|
234
|
+
</p>
|
|
235
|
+
</div>
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
238
|
+
<footer className="library-preview-meta">
|
|
239
|
+
<span>Sound history</span>
|
|
240
|
+
<span>{takes === null ? "" : `${takes.length}${next === null ? "" : "+"} take${takes.length === 1 ? "" : "s"}`}</span>
|
|
241
|
+
</footer>
|
|
242
|
+
</aside>
|
|
243
|
+
</>
|
|
244
|
+
);
|
|
245
|
+
}
|