@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,390 @@
|
|
|
1
|
+
// The Library's Stock sounds: free sounds from Openverse (Freesound effects,
|
|
2
|
+
// Jamendo music), searched from this browser, auditioned, then added to the
|
|
3
|
+
// open Shot or pinned to the board. It fills the catalog column AND the
|
|
4
|
+
// preview column, so the search, the list and the picked sound share one
|
|
5
|
+
// piece of state and nothing about them reaches the store until an add.
|
|
6
|
+
//
|
|
7
|
+
// ONLY SOUNDS A VIDEO MAY USE are listed: CC0, the public domain and CC BY
|
|
8
|
+
// (`OPENVERSE_LICENSES`). The search asks for those, the list drops anything
|
|
9
|
+
// else, and the host refuses anything else again at the add, so the credit a
|
|
10
|
+
// CC BY sound owes is always recorded where every export reads it.
|
|
11
|
+
//
|
|
12
|
+
// ONE AUDITION AT A TIME through one `Audio` element, following the tab's
|
|
13
|
+
// monitor settings, as the pack panel does (`LibrarySoundPanel`).
|
|
14
|
+
import { useEffect, useRef, useState } from "react";
|
|
15
|
+
import type { FormEvent } from "react";
|
|
16
|
+
import { SegmentedControl } from "@dailies-studio/ui";
|
|
17
|
+
import {
|
|
18
|
+
openverseSiteOf,
|
|
19
|
+
creditLineOf,
|
|
20
|
+
creditRequired,
|
|
21
|
+
licenseLabel,
|
|
22
|
+
openverseSourceOf,
|
|
23
|
+
} from "@dailies-studio/shot/doc";
|
|
24
|
+
import type { OpenverseRow, OpenverseSoundKind } from "@dailies-studio/shot/doc";
|
|
25
|
+
import { openverseStatus } from "../../api.js";
|
|
26
|
+
import { ADD_ICON, PLAY_ICON, SEARCH_ICON, STOP_ICON } from "../../icons.js";
|
|
27
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
28
|
+
import { openBoardForPin, placeInShotRefusal } from "../../state/store.js";
|
|
29
|
+
import { formatDuration } from "../timeline/model.js";
|
|
30
|
+
import { SoundWave } from "./LibrarySoundPanel.js";
|
|
31
|
+
import { STOCK_LENGTHS, StockSearchError, lengthLabel, searchStock, withinLength } from "./stockSearch.js";
|
|
32
|
+
|
|
33
|
+
type Status = { kind: "idle" } | { kind: "loading" } | { kind: "error"; message: string } | { kind: "done" };
|
|
34
|
+
|
|
35
|
+
const KIND_WORDS: Record<OpenverseSoundKind, string> = { sfx: "Effects", music: "Music" };
|
|
36
|
+
|
|
37
|
+
/** Why this host cannot add a stock sound: a hosted deployment with no
|
|
38
|
+
* registered Openverse app. Searching and listening still work. */
|
|
39
|
+
const NOT_SET_UP = "Adding stock sounds is not set up on this server yet. You can still search and listen.";
|
|
40
|
+
|
|
41
|
+
export function StockSounds({ canAdd }: { canAdd: boolean }) {
|
|
42
|
+
const store = useStore();
|
|
43
|
+
const muted = useShotEditor((s) => s.previewMuted);
|
|
44
|
+
const volume = useShotEditor((s) => s.previewVolume);
|
|
45
|
+
const placeRefusal = useShotEditor((s) => placeInShotRefusal(s, "sound"));
|
|
46
|
+
const boardOpen = useShotEditor((s) => s.storyboardOpen);
|
|
47
|
+
const [draft, setDraft] = useState("");
|
|
48
|
+
const [kind, setKind] = useState<OpenverseSoundKind>("sfx");
|
|
49
|
+
const [maxSeconds, setMaxSeconds] = useState<number | null>(null);
|
|
50
|
+
const [searched, setSearched] = useState<{ query: string; kind: OpenverseSoundKind } | null>(null);
|
|
51
|
+
const [rows, setRows] = useState<OpenverseRow[]>([]);
|
|
52
|
+
const [page, setPage] = useState({ page: 0, pageCount: 0 });
|
|
53
|
+
const [status, setStatus] = useState<Status>({ kind: "idle" });
|
|
54
|
+
const [picked, setPicked] = useState<string | null>(null);
|
|
55
|
+
const [playing, setPlaying] = useState<string | null>(null);
|
|
56
|
+
const [busy, setBusy] = useState(false);
|
|
57
|
+
const [error, setError] = useState<string | null>(null);
|
|
58
|
+
const audio = useRef<HTMLAudioElement | null>(null);
|
|
59
|
+
// The newest search wins: an older answer that lands late is dropped.
|
|
60
|
+
const ticket = useRef(0);
|
|
61
|
+
// Whether this host can add one. Unknown until asked, and a failed ask
|
|
62
|
+
// leaves the buttons on: the add itself then says what went wrong.
|
|
63
|
+
const [settable, setSettable] = useState(true);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
let alive = true;
|
|
66
|
+
void openverseStatus().then(
|
|
67
|
+
(status) => {
|
|
68
|
+
if (alive) setSettable(status.imports);
|
|
69
|
+
},
|
|
70
|
+
() => {},
|
|
71
|
+
);
|
|
72
|
+
return () => {
|
|
73
|
+
alive = false;
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
useEffect(
|
|
78
|
+
() => () => {
|
|
79
|
+
audio.current?.pause();
|
|
80
|
+
audio.current = null;
|
|
81
|
+
},
|
|
82
|
+
[],
|
|
83
|
+
);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (audio.current === null) return;
|
|
86
|
+
audio.current.muted = muted;
|
|
87
|
+
audio.current.volume = volume;
|
|
88
|
+
}, [muted, volume]);
|
|
89
|
+
|
|
90
|
+
const run = (query: string, forKind: OpenverseSoundKind, nextPage: number): void => {
|
|
91
|
+
const mine = ++ticket.current;
|
|
92
|
+
setStatus({ kind: "loading" });
|
|
93
|
+
void searchStock(query, forKind, nextPage)
|
|
94
|
+
.then((answer) => {
|
|
95
|
+
if (mine !== ticket.current) return;
|
|
96
|
+
setRows((held) => (nextPage === 1 ? answer.rows : [...held, ...answer.rows.filter((r) => !held.some((h) => h.id === r.id))]));
|
|
97
|
+
setPage({ page: answer.page, pageCount: answer.pageCount });
|
|
98
|
+
setStatus({ kind: "done" });
|
|
99
|
+
})
|
|
100
|
+
.catch((e: unknown) => {
|
|
101
|
+
if (mine !== ticket.current) return;
|
|
102
|
+
setStatus({ kind: "error", message: e instanceof StockSearchError ? e.message : "The search failed. Try again." });
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const submit = (event?: FormEvent): void => {
|
|
107
|
+
event?.preventDefault();
|
|
108
|
+
const query = draft.trim();
|
|
109
|
+
if (query === "") return;
|
|
110
|
+
setSearched({ query, kind });
|
|
111
|
+
setPicked(null);
|
|
112
|
+
run(query, kind, 1);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const pickKind = (next: OpenverseSoundKind): void => {
|
|
116
|
+
setKind(next);
|
|
117
|
+
setMaxSeconds(null);
|
|
118
|
+
// A kind is a different site, so a standing search is asked again there.
|
|
119
|
+
if (searched !== null) {
|
|
120
|
+
setSearched({ query: searched.query, kind: next });
|
|
121
|
+
setPicked(null);
|
|
122
|
+
run(searched.query, next, 1);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const shown = withinLength(rows, maxSeconds);
|
|
127
|
+
const selected = shown.find((row) => row.id === picked) ?? shown[0] ?? null;
|
|
128
|
+
|
|
129
|
+
const stop = (): void => {
|
|
130
|
+
audio.current?.pause();
|
|
131
|
+
audio.current = null;
|
|
132
|
+
setPlaying(null);
|
|
133
|
+
};
|
|
134
|
+
const audition = (row: OpenverseRow): void => {
|
|
135
|
+
setPicked(row.id);
|
|
136
|
+
if (playing === row.id) {
|
|
137
|
+
stop();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
audio.current?.pause();
|
|
141
|
+
const el = new Audio(row.url);
|
|
142
|
+
el.muted = muted;
|
|
143
|
+
el.volume = volume;
|
|
144
|
+
const done = (): void => {
|
|
145
|
+
if (audio.current !== el) return;
|
|
146
|
+
audio.current = null;
|
|
147
|
+
setPlaying(null);
|
|
148
|
+
};
|
|
149
|
+
el.addEventListener("ended", done);
|
|
150
|
+
el.addEventListener("error", done);
|
|
151
|
+
audio.current = el;
|
|
152
|
+
setPlaying(row.id);
|
|
153
|
+
void el.play().catch(done);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const act = (row: OpenverseRow, mode: "place" | "pin"): void => {
|
|
157
|
+
if (mode === "pin" && !openBoardForPin(store)) return;
|
|
158
|
+
setBusy(true);
|
|
159
|
+
setError(null);
|
|
160
|
+
store.dismissError();
|
|
161
|
+
void store
|
|
162
|
+
.addStockSound(row.id, mode)
|
|
163
|
+
.then(() => {
|
|
164
|
+
const message = store.getState().error;
|
|
165
|
+
if (message !== null) setError(message);
|
|
166
|
+
else if (mode === "place" && store.getState().libraryBrowserOpen) store.setLibraryBrowserOpen(false);
|
|
167
|
+
})
|
|
168
|
+
.finally(() => setBusy(false));
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Further pages, whenever there are any: a page whose rows all fall
|
|
172
|
+
// outside the length, or the policy, is not the end of the search.
|
|
173
|
+
const more =
|
|
174
|
+
searched !== null && page.page < page.pageCount ? (
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
className="btn is-outline"
|
|
178
|
+
data-testid="stock-more"
|
|
179
|
+
disabled={status.kind === "loading"}
|
|
180
|
+
onClick={() => run(searched.query, searched.kind, page.page + 1)}
|
|
181
|
+
>
|
|
182
|
+
{status.kind === "loading" ? "Loading…" : "Load more"}
|
|
183
|
+
</button>
|
|
184
|
+
) : null;
|
|
185
|
+
let body;
|
|
186
|
+
if (status.kind === "idle") {
|
|
187
|
+
body = (
|
|
188
|
+
<p className="library-preview-note" role="status">
|
|
189
|
+
Search Freesound effects or Jamendo music. Only sounds you may use in a video are listed: CC0, public domain and CC BY.
|
|
190
|
+
</p>
|
|
191
|
+
);
|
|
192
|
+
} else if (status.kind === "loading" && rows.length === 0) {
|
|
193
|
+
body = <p className="library-preview-note" role="status">Searching Openverse…</p>;
|
|
194
|
+
} else if (status.kind === "error" && rows.length === 0) {
|
|
195
|
+
body = (
|
|
196
|
+
<p className="library-preview-error" role="alert" data-testid="stock-error">
|
|
197
|
+
{status.message}
|
|
198
|
+
</p>
|
|
199
|
+
);
|
|
200
|
+
} else if (shown.length === 0) {
|
|
201
|
+
body = (
|
|
202
|
+
<>
|
|
203
|
+
<p className="library-preview-note" role="status">
|
|
204
|
+
{rows.length === 0
|
|
205
|
+
? more === null
|
|
206
|
+
? `No free ${KIND_WORDS[kind].toLowerCase()} match ${JSON.stringify(searched?.query ?? "")}. Try other words.`
|
|
207
|
+
: "Nothing on this page may be used in a video. Load more to look further."
|
|
208
|
+
: `None of these is ${lengthLabel(maxSeconds).toLowerCase()}.`}
|
|
209
|
+
</p>
|
|
210
|
+
{more}
|
|
211
|
+
</>
|
|
212
|
+
);
|
|
213
|
+
} else {
|
|
214
|
+
body = (
|
|
215
|
+
<>
|
|
216
|
+
<ul className="library-sound-list" aria-label="Stock sounds" data-testid="stock-list">
|
|
217
|
+
{shown.map((row) => {
|
|
218
|
+
const live = playing === row.id;
|
|
219
|
+
const on = row === selected;
|
|
220
|
+
return (
|
|
221
|
+
<li key={row.id} className={on ? "library-sound-row is-selected" : "library-sound-row"} data-id={row.id}>
|
|
222
|
+
<button type="button" className="library-sound-play" aria-label={live ? `Stop ${row.title}` : `Play ${row.title}`} onClick={() => audition(row)}>
|
|
223
|
+
{live ? STOP_ICON : PLAY_ICON}
|
|
224
|
+
</button>
|
|
225
|
+
<button type="button" className="library-sound-pick" aria-pressed={on} onClick={() => setPicked(row.id)}>
|
|
226
|
+
<span className="library-sound-name">
|
|
227
|
+
{row.title}
|
|
228
|
+
{row.creator === null ? null : <span className="stock-sound-creator"> · {row.creator}</span>}
|
|
229
|
+
</span>
|
|
230
|
+
<span className="stock-sound-license">{licenseLabel(row.license, "")}</span>
|
|
231
|
+
<span className="library-sound-length">{formatDuration((row.durationMs ?? 0) / 1000)}</span>
|
|
232
|
+
</button>
|
|
233
|
+
</li>
|
|
234
|
+
);
|
|
235
|
+
})}
|
|
236
|
+
</ul>
|
|
237
|
+
{status.kind === "error" ? (
|
|
238
|
+
<p className="library-preview-error" role="alert">
|
|
239
|
+
{status.message}
|
|
240
|
+
</p>
|
|
241
|
+
) : null}
|
|
242
|
+
{more}
|
|
243
|
+
</>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const source = selected === null ? null : openverseSourceOf(selected);
|
|
248
|
+
const site = selected === null ? null : openverseSiteOf(selected.source);
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<>
|
|
252
|
+
<section className="library-catalog" aria-label="Stock sounds" data-testid="stock-sounds">
|
|
253
|
+
<header className="library-catalog-head">
|
|
254
|
+
<div>
|
|
255
|
+
<h2>Stock sounds</h2>
|
|
256
|
+
<p>Free sounds from Openverse, safe to use in a video.</p>
|
|
257
|
+
</div>
|
|
258
|
+
</header>
|
|
259
|
+
<form className="library-search-row" role="search" onSubmit={submit}>
|
|
260
|
+
{/* Searched on Enter, never per key: each search spends the
|
|
261
|
+
person's own Openverse allowance. */}
|
|
262
|
+
<label className="search-field">
|
|
263
|
+
{SEARCH_ICON}
|
|
264
|
+
<input
|
|
265
|
+
type="search"
|
|
266
|
+
value={draft}
|
|
267
|
+
placeholder={kind === "sfx" ? "Search effects, then press Enter" : "Search music, then press Enter"}
|
|
268
|
+
aria-label="Search stock sounds"
|
|
269
|
+
data-testid="stock-search"
|
|
270
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
271
|
+
/>
|
|
272
|
+
</label>
|
|
273
|
+
<select
|
|
274
|
+
className="select is-quiet"
|
|
275
|
+
aria-label="Length"
|
|
276
|
+
data-testid="stock-length"
|
|
277
|
+
value={maxSeconds === null ? "" : String(maxSeconds)}
|
|
278
|
+
onChange={(event) => setMaxSeconds(event.target.value === "" ? null : Number(event.target.value))}
|
|
279
|
+
>
|
|
280
|
+
{STOCK_LENGTHS[kind].map((s) => (
|
|
281
|
+
<option key={s ?? "any"} value={s === null ? "" : String(s)}>
|
|
282
|
+
{lengthLabel(s)}
|
|
283
|
+
</option>
|
|
284
|
+
))}
|
|
285
|
+
</select>
|
|
286
|
+
</form>
|
|
287
|
+
<SegmentedControl
|
|
288
|
+
ariaLabel="Kind of sound"
|
|
289
|
+
className="stock-kind"
|
|
290
|
+
value={kind}
|
|
291
|
+
options={[
|
|
292
|
+
{ value: "sfx", label: "Effects", testId: "stock-kind-sfx" },
|
|
293
|
+
{ value: "music", label: "Music", testId: "stock-kind-music" },
|
|
294
|
+
]}
|
|
295
|
+
onChange={(next) => pickKind(next as OpenverseSoundKind)}
|
|
296
|
+
/>
|
|
297
|
+
{body}
|
|
298
|
+
</section>
|
|
299
|
+
<aside className="library-preview is-sounds" aria-label="Preview: stock sound" data-testid="stock-preview">
|
|
300
|
+
{selected === null || source === null ? (
|
|
301
|
+
<p className="library-preview-empty">Pick a sound to preview it here.</p>
|
|
302
|
+
) : (
|
|
303
|
+
<>
|
|
304
|
+
<div className="library-preview-purpose">
|
|
305
|
+
<h2 className="library-preview-name">{selected.title}</h2>
|
|
306
|
+
<p className="library-preview-note">
|
|
307
|
+
{selected.creator === null ? "" : `by ${selected.creator} · `}
|
|
308
|
+
{site?.name ?? selected.source} · {formatDuration((selected.durationMs ?? 0) / 1000)}
|
|
309
|
+
</p>
|
|
310
|
+
</div>
|
|
311
|
+
<div className="library-sound-selected" data-testid="stock-selected">
|
|
312
|
+
{/* Jamendo's files carry no CORS header, so their envelope
|
|
313
|
+
cannot be read here: the well stays empty, the audition
|
|
314
|
+
still plays. */}
|
|
315
|
+
{/* Drawn once the person picks or plays the row, never for the
|
|
316
|
+
one shown first: drawing it fetches the file from Freesound. */}
|
|
317
|
+
{selected.source === "freesound" && (picked === selected.id || playing === selected.id) ? (
|
|
318
|
+
<SoundWave url={selected.url} />
|
|
319
|
+
) : null}
|
|
320
|
+
<p className="stock-sound-terms" data-testid="stock-license">
|
|
321
|
+
<strong>{licenseLabel(source.license, source.licenseVersion)}</strong>
|
|
322
|
+
{creditRequired(source.license)
|
|
323
|
+
? " · Credit required. Dailies keeps the line and puts it in your export."
|
|
324
|
+
: " · No credit needed."}
|
|
325
|
+
</p>
|
|
326
|
+
<p className="stock-sound-credit" data-testid="stock-credit">
|
|
327
|
+
{creditLineOf(source)}
|
|
328
|
+
</p>
|
|
329
|
+
<a className="stock-sound-link" href={selected.landingUrl} target="_blank" rel="noopener noreferrer">
|
|
330
|
+
Open on {site?.name ?? selected.source}
|
|
331
|
+
</a>
|
|
332
|
+
<button
|
|
333
|
+
type="button"
|
|
334
|
+
className="btn is-primary is-lg"
|
|
335
|
+
data-testid="stock-add"
|
|
336
|
+
disabled={!canAdd || busy || !settable}
|
|
337
|
+
aria-disabled={placeRefusal !== null || undefined}
|
|
338
|
+
title={
|
|
339
|
+
!settable
|
|
340
|
+
? NOT_SET_UP
|
|
341
|
+
: !canAdd
|
|
342
|
+
? "Open a Project to add sounds."
|
|
343
|
+
: (placeRefusal ?? "Add this sound at the playhead of the open Shot.")
|
|
344
|
+
}
|
|
345
|
+
onClick={() => {
|
|
346
|
+
if (placeRefusal === null) act(selected, "place");
|
|
347
|
+
}}
|
|
348
|
+
>
|
|
349
|
+
{ADD_ICON}
|
|
350
|
+
{busy ? "Adding…" : "Add to shot"}
|
|
351
|
+
</button>
|
|
352
|
+
<button
|
|
353
|
+
type="button"
|
|
354
|
+
className="btn is-outline is-lg"
|
|
355
|
+
data-testid="stock-pin"
|
|
356
|
+
disabled={!canAdd || busy || !settable}
|
|
357
|
+
title={
|
|
358
|
+
!settable
|
|
359
|
+
? NOT_SET_UP
|
|
360
|
+
: canAdd
|
|
361
|
+
? `Add this sound and pin it as a card the agent reads.${boardOpen ? "" : " Opens the storyboard."}`
|
|
362
|
+
: "Open a Project to add sounds."
|
|
363
|
+
}
|
|
364
|
+
onClick={() => act(selected, "pin")}
|
|
365
|
+
>
|
|
366
|
+
Pin to board
|
|
367
|
+
</button>
|
|
368
|
+
{error === null ? null : (
|
|
369
|
+
<p className="library-preview-error" role="alert" data-testid="stock-add-error">
|
|
370
|
+
{error}
|
|
371
|
+
</p>
|
|
372
|
+
)}
|
|
373
|
+
<p className="library-preview-hint" data-testid="stock-hint">
|
|
374
|
+
{!settable
|
|
375
|
+
? NOT_SET_UP
|
|
376
|
+
: selected.source === "freesound"
|
|
377
|
+
? "Freesound shares a preview-quality MP3, which is what is added."
|
|
378
|
+
: "Add places it in the open Shot. Pin saves it for later."}
|
|
379
|
+
</p>
|
|
380
|
+
</div>
|
|
381
|
+
</>
|
|
382
|
+
)}
|
|
383
|
+
<footer className="library-preview-meta">
|
|
384
|
+
<span>Openverse</span>
|
|
385
|
+
<span>{KIND_WORDS[kind]}</span>
|
|
386
|
+
</footer>
|
|
387
|
+
</aside>
|
|
388
|
+
</>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// What the Library browse surface shows, as pure functions over catalog rows:
|
|
2
|
+
// which rows, in which order, under which heading, and the words around them.
|
|
3
|
+
// No React and no store, so every rule here is one unit test away.
|
|
4
|
+
import { libraryDisplayName } from "@dailies-studio/shot/doc";
|
|
5
|
+
import type { ReferenceItem3D } from "@dailies-studio/shot/doc";
|
|
6
|
+
import type { LibraryItem, LibraryItemKind } from "../../api.js";
|
|
7
|
+
import { matchesLibraryItem } from "../../state/storyboardModel.js";
|
|
8
|
+
|
|
9
|
+
/** A rail row: one catalog kind, every kind at once, or Stock sounds, which
|
|
10
|
+
* is no catalog kind: Openverse's free sounds, searched from the browser
|
|
11
|
+
* (`StockSounds`). */
|
|
12
|
+
export type LibraryCategory = "all" | LibraryItemKind | "stock" | "generated";
|
|
13
|
+
|
|
14
|
+
/** The rail, top to bottom. Components lead because a row with no `kind` is
|
|
15
|
+
* one (the registry's own default) and it is the kind an author reaches for
|
|
16
|
+
* first; the rest follow the order the library skill introduces them. */
|
|
17
|
+
export const LIBRARY_KINDS: readonly LibraryItemKind[] = ["component", "blueprint", "material", "shot", "images", "sounds"];
|
|
18
|
+
export const LIBRARY_CATEGORIES: readonly LibraryCategory[] = ["all", ...LIBRARY_KINDS, "stock", "generated"];
|
|
19
|
+
|
|
20
|
+
export const CATEGORY_LABEL: Record<LibraryCategory, string> = {
|
|
21
|
+
all: "All items",
|
|
22
|
+
component: "Components",
|
|
23
|
+
blueprint: "Blueprints",
|
|
24
|
+
material: "Materials",
|
|
25
|
+
shot: "Shots",
|
|
26
|
+
images: "Images",
|
|
27
|
+
sounds: "Sounds",
|
|
28
|
+
stock: "Stock sounds",
|
|
29
|
+
generated: "Generated sounds",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** The sentence under each catalog heading. Components and Sounds are Paper's
|
|
33
|
+
* own words; the rest are written to the same pattern: what the kind is for,
|
|
34
|
+
* in one line, with no product jargon. */
|
|
35
|
+
export const CATEGORY_SUBTITLE: Record<LibraryCategory, string> = {
|
|
36
|
+
all: "Everything this build ships, one kind at a time.",
|
|
37
|
+
component: "Ready-made pieces of motion. Preview one, then make it yours.",
|
|
38
|
+
blueprint: "Step-by-step builds to follow inside a shot.",
|
|
39
|
+
material: "Surfaces that give a model its look.",
|
|
40
|
+
shot: "Finished shots to learn from or build on.",
|
|
41
|
+
images: "Picture packs to bring into this Project.",
|
|
42
|
+
sounds: "Find the sound that makes a moment land.",
|
|
43
|
+
stock: "Free sounds from Openverse, safe to use in a video.",
|
|
44
|
+
generated: "Generated sounds you can open: every one added to a Project, and the rest for a day.",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** A sound pack is counted as a pack, not an item: what the author picks
|
|
48
|
+
* there is one sound out of it, so "12 items" would promise twelve sounds. */
|
|
49
|
+
export const countNoun = (category: LibraryCategory): "item" | "pack" => (category === "sounds" ? "pack" : "item");
|
|
50
|
+
|
|
51
|
+
/** "1 item", "2 items", "0 items". */
|
|
52
|
+
export function plural(n: number, noun: string): string {
|
|
53
|
+
return `${n} ${n === 1 ? noun : `${noun}s`}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const kindOf = (row: LibraryItem): LibraryItemKind => row.kind ?? "component";
|
|
57
|
+
|
|
58
|
+
/** Whether Add places this row as a layer: a component, and only a three
|
|
59
|
+
* host can be an instance layer (`instanceHostRefusal`). The rest pin. */
|
|
60
|
+
export const placesAsLayer = (row: LibraryItem): boolean => kindOf(row) === "component" && row.host === "three";
|
|
61
|
+
|
|
62
|
+
/** The name a card prints. ONE helper for the whole surface, shared with the
|
|
63
|
+
* CLI (`libraryDisplayName` in @dailies-studio/shot/doc): the manifest's
|
|
64
|
+
* `displayName` when the server sends one, else the code name in sentence
|
|
65
|
+
* case (`SelectionLoop` -> "Selection loop"). */
|
|
66
|
+
export const displayNameOf = (row: LibraryItem): string => libraryDisplayName(row);
|
|
67
|
+
|
|
68
|
+
export type LibrarySort = "curated" | "name";
|
|
69
|
+
|
|
70
|
+
export const SORT_LABEL: Record<LibrarySort, string> = { curated: "Curated", name: "Name" };
|
|
71
|
+
|
|
72
|
+
/** The search box's rule: every field the old cards printed and the id
|
|
73
|
+
* (`matchesLibraryItem`), plus the printed name, which is no longer `name`. */
|
|
74
|
+
export function matchesQuery(row: LibraryItem, query: string): boolean {
|
|
75
|
+
const q = query.trim().toLowerCase();
|
|
76
|
+
return q === "" || matchesLibraryItem(row, q) || displayNameOf(row).toLowerCase().includes(q);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The rows one category shows, searched and sorted. "Curated" is the
|
|
81
|
+
* catalog's own order, which is the order the build ships them in and the
|
|
82
|
+
* only order anyone chose; "Name" sorts by the PRINTED name, so the list
|
|
83
|
+
* reads alphabetically on screen rather than by code name.
|
|
84
|
+
*/
|
|
85
|
+
export function visibleRows(
|
|
86
|
+
rows: readonly LibraryItem[],
|
|
87
|
+
category: LibraryCategory,
|
|
88
|
+
query: string,
|
|
89
|
+
sort: LibrarySort,
|
|
90
|
+
): LibraryItem[] {
|
|
91
|
+
const out = rows.filter((row) => (category === "all" || kindOf(row) === category) && matchesQuery(row, query));
|
|
92
|
+
if (sort === "name") {
|
|
93
|
+
out.sort((a, b) => displayNameOf(a).localeCompare(displayNameOf(b), undefined, { sensitivity: "base" }));
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** "All items" as sections, one per kind in rail order, empty kinds left
|
|
99
|
+
* out so a search does not print a column of empty headings. */
|
|
100
|
+
export function groupByKind(rows: readonly LibraryItem[]): Array<{ kind: LibraryItemKind; rows: LibraryItem[] }> {
|
|
101
|
+
return LIBRARY_KINDS.map((kind) => ({ kind, rows: rows.filter((row) => kindOf(row) === kind) })).filter(
|
|
102
|
+
(section) => section.rows.length > 0,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Rows per rail category, "all" included. Stock sounds has no count: its
|
|
107
|
+
* rows are a search away, never a list this build holds. */
|
|
108
|
+
export function categoryCounts(rows: readonly LibraryItem[]): Map<LibraryCategory, number> {
|
|
109
|
+
const out = new Map<LibraryCategory, number>(["all" as const, ...LIBRARY_KINDS].map((c) => [c, 0]));
|
|
110
|
+
for (const row of rows) {
|
|
111
|
+
const k = kindOf(row);
|
|
112
|
+
out.set(k, (out.get(k) ?? 0) + 1);
|
|
113
|
+
}
|
|
114
|
+
out.set("all", rows.length);
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The rows the Library shows: `source === "shipped"` and NOT `!== "builtin"`.
|
|
120
|
+
* The catalog concatenates three lists and the third is `shared`: a component
|
|
121
|
+
* vendored from a library this build does not ship, which carries no kind, no
|
|
122
|
+
* tags, no thumb and no docs. A shared row has nothing to draw on a card and
|
|
123
|
+
* nothing to pick, and the loose filter would have put every one of them in
|
|
124
|
+
* front of the author. ONE filter for the browser and the shot board's
|
|
125
|
+
* component picker (its Dailies Library section).
|
|
126
|
+
*/
|
|
127
|
+
export function shippedRows(catalog: readonly LibraryItem[]): LibraryItem[] {
|
|
128
|
+
return catalog.filter((row) => row.source === "shipped");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* HOW MANY CARDS ON THE BOARD CITE EACH ROW, by item id. A component card
|
|
133
|
+
* names the registry id the install made (`installedAs`), every other kind
|
|
134
|
+
* names the item itself.
|
|
135
|
+
*/
|
|
136
|
+
export function onBoardCounts(
|
|
137
|
+
shipped: readonly LibraryItem[],
|
|
138
|
+
references: readonly ReferenceItem3D[],
|
|
139
|
+
): Map<string, number> {
|
|
140
|
+
const byComponent = new Map(shipped.flatMap((row) => (row.installedAs === undefined ? [] : [[row.installedAs, row.id]])));
|
|
141
|
+
const out = new Map<string, number>();
|
|
142
|
+
for (const ref of references) {
|
|
143
|
+
const id = ref.type === "library" ? ref.library : ref.type === "component" && ref.component !== undefined ? byComponent.get(ref.component) : undefined;
|
|
144
|
+
if (id !== undefined) out.set(id, (out.get(id) ?? 0) + 1);
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// The Library's Stock sounds search, run from this browser straight to
|
|
2
|
+
// Openverse, the way openverse.org itself runs: each person spends their own
|
|
3
|
+
// anonymous allowance (20 searches a minute, 200 a day), so one busy
|
|
4
|
+
// workspace never uses up another's. The editor sends nothing but the words
|
|
5
|
+
// typed: no cookie (`credentials: "omit"`) and no page address
|
|
6
|
+
// (`referrerPolicy: "no-referrer"`).
|
|
7
|
+
//
|
|
8
|
+
// Adding a sound never trusts this answer. The add names the row's id, and
|
|
9
|
+
// the host reads the row again itself before it stores a byte or records a
|
|
10
|
+
// license (`/api/openverse/add`).
|
|
11
|
+
import { fitsSeconds, openverseRowRefusal, openverseSearchUrl, parseOpenverseRow } from "@dailies-studio/shot/doc";
|
|
12
|
+
import type { OpenverseRow, OpenverseSoundKind } from "@dailies-studio/shot/doc";
|
|
13
|
+
|
|
14
|
+
export interface StockPage {
|
|
15
|
+
rows: OpenverseRow[];
|
|
16
|
+
page: number;
|
|
17
|
+
pageCount: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A search that did not answer, in words for the person. */
|
|
21
|
+
export class StockSearchError extends Error {}
|
|
22
|
+
|
|
23
|
+
/** One page of a search: only rows the license policy takes. Searching costs
|
|
24
|
+
* the person's allowance, so the view calls this on Enter, never per key. */
|
|
25
|
+
export async function searchStock(
|
|
26
|
+
query: string,
|
|
27
|
+
kind: OpenverseSoundKind,
|
|
28
|
+
page: number,
|
|
29
|
+
fetchImpl: typeof fetch = (input, init) => fetch(input, init),
|
|
30
|
+
): Promise<StockPage> {
|
|
31
|
+
let res: Response;
|
|
32
|
+
try {
|
|
33
|
+
res = await fetchImpl(openverseSearchUrl({ query, kind, page }), {
|
|
34
|
+
credentials: "omit",
|
|
35
|
+
referrerPolicy: "no-referrer",
|
|
36
|
+
headers: { accept: "application/json" },
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
throw new StockSearchError("Openverse could not be reached. Check your connection and try again.");
|
|
40
|
+
}
|
|
41
|
+
if (res.status === 429) {
|
|
42
|
+
const wait = Number(res.headers.get("retry-after"));
|
|
43
|
+
throw new StockSearchError(
|
|
44
|
+
`Openverse's search limit was reached. Try again in ${Number.isFinite(wait) && wait > 0 ? `${Math.ceil(wait)} seconds` : "a minute"}.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (!res.ok) throw new StockSearchError(`Openverse answered ${res.status}. Try again in a minute.`);
|
|
48
|
+
const body = (await res.json().catch(() => null)) as { results?: unknown; page_count?: unknown } | null;
|
|
49
|
+
if (body === null || !Array.isArray(body.results)) throw new StockSearchError("Openverse's answer could not be read.");
|
|
50
|
+
return {
|
|
51
|
+
rows: body.results.flatMap((raw) => {
|
|
52
|
+
const row = parseOpenverseRow(raw);
|
|
53
|
+
return row !== null && openverseRowRefusal(row) === null ? [row] : [];
|
|
54
|
+
}),
|
|
55
|
+
page,
|
|
56
|
+
pageCount: typeof body.page_count === "number" ? body.page_count : page,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The length limits offered per kind, in seconds; null is no limit. A short
|
|
61
|
+
* effect and a long bed want different steps. */
|
|
62
|
+
export const STOCK_LENGTHS: Record<OpenverseSoundKind, readonly (number | null)[]> = {
|
|
63
|
+
sfx: [null, 1, 3, 10, 30],
|
|
64
|
+
music: [null, 30, 60, 180],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** The rows within the length limit. Filtered here, not asked of Openverse,
|
|
68
|
+
* whose own length filter answers rows far outside its bands. */
|
|
69
|
+
export function withinLength(rows: readonly OpenverseRow[], maxSeconds: number | null): OpenverseRow[] {
|
|
70
|
+
return rows.filter((row) => fitsSeconds(row, maxSeconds ?? undefined));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** "Up to 3s", "Any length". */
|
|
74
|
+
export function lengthLabel(seconds: number | null): string {
|
|
75
|
+
if (seconds === null) return "Any length";
|
|
76
|
+
return seconds < 60 ? `Up to ${seconds}s` : `Up to ${seconds / 60} min`;
|
|
77
|
+
}
|