@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,435 @@
|
|
|
1
|
+
// The Library: the whole shipped catalog, and the door that adds any of it to
|
|
2
|
+
// this Project's board. Three columns under the top bar (the rail, the
|
|
3
|
+
// catalog, the selected item's preview) and a status bar across the foot.
|
|
4
|
+
//
|
|
5
|
+
// A DIALOG, NOT A FIFTH MODE, and that is the load-bearing choice. `Dialog`
|
|
6
|
+
// portals to `document.body` at `--z-dialog` 1100, above the whole shell
|
|
7
|
+
// (whose top tier is the overlay, 10), and it brings its own focus trap and
|
|
8
|
+
// its own Escape that stops propagating before the shell's key path sees it.
|
|
9
|
+
// So this surface costs no workspace in `workspaceOf`, no shell class in
|
|
10
|
+
// `App.tsx`, no cell slots, no keymap lane, no `edgesOf` key, no
|
|
11
|
+
// `measureInset` interaction and no URL block. Every one of those is a
|
|
12
|
+
// hand-maintained per-mode entry in this codebase, and a fifth copy of each is
|
|
13
|
+
// more places to forget.
|
|
14
|
+
//
|
|
15
|
+
// It also has to be a dialog to be USABLE. A fifth mode would join the modes'
|
|
16
|
+
// one-at-a-time rule (`workspaceOf`: opening one closes the others), so it
|
|
17
|
+
// could not share the screen with the board, which is precisely where an
|
|
18
|
+
// author decides what to build with. This opens over anything, the board
|
|
19
|
+
// included.
|
|
20
|
+
//
|
|
21
|
+
// IT STARTS BELOW THE TOP BAR, with no scrim and no padding (library.css), so
|
|
22
|
+
// the 64px bar stays on screen and the library reads as the workspace's own
|
|
23
|
+
// page rather than a box floating over it.
|
|
24
|
+
//
|
|
25
|
+
// ADD PLACES A LAYER (D2). Add, Enter and a double click on a component are
|
|
26
|
+
// `dropLibraryOnFrame`: it installs, places it in the open Shot through
|
|
27
|
+
// `placeInShot`, selected, and the Library closes on it, from the board or
|
|
28
|
+
// from Studio alike. PIN TO BOARD is the explicit second action,
|
|
29
|
+
// `addStoryboardLibraryItem`: a component card, or a `library` card for a kind
|
|
30
|
+
// with no layer (whose only action it is), for the whole film. From Studio a
|
|
31
|
+
// pin opens the board first (`openBoardForPin`, F32). A SHOT'S PRIMARY is
|
|
32
|
+
// "Use as new Project" (`claimLibraryShot`): the copy opens in place of this
|
|
33
|
+
// Project and the Library closes on it. It is a press of its own, never Enter
|
|
34
|
+
// or a double click, which still pin a shot: a stray double click must not
|
|
35
|
+
// leave the Project the author is in. There is no shortlist
|
|
36
|
+
// (`resources.library` is legacy, never written). A sound pack adds or pins
|
|
37
|
+
// one sound at a time, from the preview column.
|
|
38
|
+
//
|
|
39
|
+
// STOCK SOUNDS (Openverse) is a rail row with no catalog behind it: it fills
|
|
40
|
+
// the catalog and preview columns with its own search, list and picked sound
|
|
41
|
+
// (`StockSounds`), and adds one sound at a time like a pack.
|
|
42
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
43
|
+
import type { ReactNode } from "react";
|
|
44
|
+
import { Dialog } from "@dailies-studio/ui";
|
|
45
|
+
import { LIBRARY_CATEGORY_ICONS, SEARCH_ICON } from "../../icons.js";
|
|
46
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
47
|
+
import { isEditable } from "../../state/keymap.js";
|
|
48
|
+
import { openBoardForPin } from "../../state/store.js";
|
|
49
|
+
import type { LibraryItem } from "../../api.js";
|
|
50
|
+
import {
|
|
51
|
+
CATEGORY_LABEL,
|
|
52
|
+
CATEGORY_SUBTITLE,
|
|
53
|
+
SORT_LABEL,
|
|
54
|
+
categoryCounts,
|
|
55
|
+
countNoun,
|
|
56
|
+
groupByKind,
|
|
57
|
+
kindOf,
|
|
58
|
+
placesAsLayer,
|
|
59
|
+
onBoardCounts,
|
|
60
|
+
plural,
|
|
61
|
+
shippedRows,
|
|
62
|
+
visibleRows,
|
|
63
|
+
} from "./libraryModel.js";
|
|
64
|
+
import type { LibraryCategory, LibrarySort } from "./libraryModel.js";
|
|
65
|
+
import { LibraryGrid } from "./LibraryGrid.js";
|
|
66
|
+
import type { LibrarySection } from "./LibraryGrid.js";
|
|
67
|
+
import { LibraryExpandedPreview, LibraryPreviewPanel, canExpand } from "./LibraryPreviewPanel.js";
|
|
68
|
+
import { LibraryRail } from "./LibraryRail.js";
|
|
69
|
+
import { lazyPanel } from "../../app/lazyPanel.js";
|
|
70
|
+
|
|
71
|
+
// The two sound catalogs are chunks of their own, fetched the first time
|
|
72
|
+
// their category is picked: most opens of the Library browse components, and
|
|
73
|
+
// neither is a component library. The fallback holds the catalog's place.
|
|
74
|
+
const StockSounds = lazyPanel(
|
|
75
|
+
"Stock sounds",
|
|
76
|
+
() => import("./StockSounds.js").then((m) => m.StockSounds),
|
|
77
|
+
<section className="library-catalog" aria-busy="true" />,
|
|
78
|
+
);
|
|
79
|
+
const SoundHistory = lazyPanel(
|
|
80
|
+
"Generated sounds",
|
|
81
|
+
() => import("./SoundHistory.js").then((m) => m.SoundHistory),
|
|
82
|
+
<section className="library-catalog" aria-busy="true" />,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const NONE: never[] = [];
|
|
86
|
+
|
|
87
|
+
export function LibraryBrowser() {
|
|
88
|
+
const store = useStore();
|
|
89
|
+
const open = useShotEditor((s) => s.libraryBrowserOpen);
|
|
90
|
+
const catalog = useShotEditor((s) => s.libraryCatalog) ?? NONE;
|
|
91
|
+
const loaded = useShotEditor((s) => s.libraryCatalogLoaded);
|
|
92
|
+
const error = useShotEditor((s) => s.libraryCatalogError);
|
|
93
|
+
const problems = useShotEditor((s) => s.libraryCatalogProblems) ?? NONE;
|
|
94
|
+
const references = useShotEditor((s) => s.doc?.resources?.references) ?? NONE;
|
|
95
|
+
const hasDoc = useShotEditor((s) => s.doc !== null);
|
|
96
|
+
// WHERE "BACK" LANDS, as strings (an object selector would re-render this
|
|
97
|
+
// on every edit). Closing returns to whatever is under the library, so the
|
|
98
|
+
// row names that: the entered Shot, else the board when it is up, else the
|
|
99
|
+
// Project's own context.
|
|
100
|
+
const shotName = useShotEditor((s) =>
|
|
101
|
+
s.enteredScene === null ? null : (s.doc?.scenesById[s.enteredScene]?.name ?? "Shot"),
|
|
102
|
+
);
|
|
103
|
+
const boardOpen = useShotEditor((s) => s.storyboardOpen);
|
|
104
|
+
const project = useShotEditor((s) => (s.doc === null ? null : (s.doc.title ?? s.name ?? s.doc.name)));
|
|
105
|
+
const [query, setQuery] = useState("");
|
|
106
|
+
const [category, setCategory] = useState<LibraryCategory>("component");
|
|
107
|
+
const [sort, setSort] = useState<LibrarySort>("curated");
|
|
108
|
+
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
109
|
+
// ONE ADD AT A TIME across the whole surface: an install is a server write,
|
|
110
|
+
// and a double click while one is out would land a second card.
|
|
111
|
+
const [adding, setAdding] = useState<string | null>(null);
|
|
112
|
+
// The last refused add, by item: the shell's error strip is under this
|
|
113
|
+
// dialog, so the preview column prints it under the Add.
|
|
114
|
+
const [addError, setAddError] = useState<{ id: string; message: string } | null>(null);
|
|
115
|
+
// THE EXPANDED PREVIEW, by item id: it shows while that item is still the
|
|
116
|
+
// selection, so picking another card or category closes it with no effect.
|
|
117
|
+
const [expandedId, setExpandedId] = useState<string | null>(null);
|
|
118
|
+
const [retrying, setRetrying] = useState(false);
|
|
119
|
+
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
120
|
+
const searchRef = useRef<HTMLInputElement>(null);
|
|
121
|
+
const detailsRef = useRef<HTMLButtonElement>(null);
|
|
122
|
+
const detailsBoxRef = useRef<HTMLDivElement>(null);
|
|
123
|
+
const detailsId = useId();
|
|
124
|
+
// The Details box floats like a popover, so a press anywhere else closes it.
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!detailsOpen) return;
|
|
127
|
+
const onDown = (event: PointerEvent): void => {
|
|
128
|
+
const target = event.target as Node;
|
|
129
|
+
if (detailsBoxRef.current?.contains(target) || detailsRef.current?.contains(target)) return;
|
|
130
|
+
setDetailsOpen(false);
|
|
131
|
+
};
|
|
132
|
+
window.addEventListener("pointerdown", onDown, true);
|
|
133
|
+
return () => window.removeEventListener("pointerdown", onDown, true);
|
|
134
|
+
}, [detailsOpen]);
|
|
135
|
+
|
|
136
|
+
// "/" FOCUSES SEARCH, the hint the field prints. A window listener rather
|
|
137
|
+
// than a handler on the dialog because the shell's own keys are off while
|
|
138
|
+
// the library is up (store `libraryBrowserOpen`), and never while the
|
|
139
|
+
// author is typing: "/" is a character in every field, the search included.
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (!open) return;
|
|
142
|
+
const onKey = (event: KeyboardEvent): void => {
|
|
143
|
+
if (event.key !== "/" || event.ctrlKey || event.metaKey || event.altKey || isEditable(event.target)) return;
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
searchRef.current?.focus();
|
|
146
|
+
};
|
|
147
|
+
window.addEventListener("keydown", onKey);
|
|
148
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
149
|
+
}, [open]);
|
|
150
|
+
|
|
151
|
+
const shipped = useMemo(() => shippedRows(catalog), [catalog]);
|
|
152
|
+
const counts = useMemo(() => categoryCounts(shipped), [shipped]);
|
|
153
|
+
const rows = useMemo(() => visibleRows(shipped, category, query, sort), [shipped, category, query, sort]);
|
|
154
|
+
const selected = shipped.find((row) => row.id === selectedId) ?? null;
|
|
155
|
+
const onBoard = useMemo(() => onBoardCounts(shipped, references), [shipped, references]);
|
|
156
|
+
const onBoardHere = shipped.filter((row) => onBoard.has(row.id)).length;
|
|
157
|
+
|
|
158
|
+
const close = (): void => {
|
|
159
|
+
setDetailsOpen(false);
|
|
160
|
+
store.setLibraryBrowserOpen(false);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const run = (row: LibraryItem, how: "place" | "claim" | "pin"): void => {
|
|
164
|
+
// A pack adds one sound at a time (the preview column), so Enter or a
|
|
165
|
+
// double click on its card only picks it.
|
|
166
|
+
setSelectedId(row.id);
|
|
167
|
+
if (adding !== null || !hasDoc || kindOf(row) === "sounds") return;
|
|
168
|
+
setAdding(row.id);
|
|
169
|
+
setAddError(null);
|
|
170
|
+
// The store reports a refusal to the shell's strip and resolves; clear
|
|
171
|
+
// the strip first so whatever is there afterwards is this add's.
|
|
172
|
+
store.dismissError();
|
|
173
|
+
let job: Promise<unknown> = Promise.resolve();
|
|
174
|
+
if (how === "place") job = store.dropLibraryOnFrame(row.id);
|
|
175
|
+
else if (how === "claim") job = store.claimLibraryShot(row.id);
|
|
176
|
+
else if (openBoardForPin(store)) job = store.addStoryboardLibraryItem(row.id, null);
|
|
177
|
+
void job.finally(() => {
|
|
178
|
+
setAdding(null);
|
|
179
|
+
const message = store.getState().error;
|
|
180
|
+
if (message !== null) setAddError({ id: row.id, message });
|
|
181
|
+
// The placed layer is selected under this dialog, or the claimed
|
|
182
|
+
// Project opened under it: show it (F32).
|
|
183
|
+
else if (how !== "pin") store.setLibraryBrowserOpen(false);
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
// ADD PLACES what can be a layer (a three component); every other row
|
|
187
|
+
// pins on Enter and a double click (a shot's claim is its button alone).
|
|
188
|
+
const add = (row: LibraryItem): void => run(row, placesAsLayer(row) ? "place" : "pin");
|
|
189
|
+
const pin = (row: LibraryItem): void => run(row, "pin");
|
|
190
|
+
|
|
191
|
+
const retry = (): void => {
|
|
192
|
+
setRetrying(true);
|
|
193
|
+
void store.refreshLibraryCatalog().finally(() => setRetrying(false));
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const expanded = selected !== null && expandedId === selected.id && canExpand(selected);
|
|
197
|
+
const pickCategory = (next: LibraryCategory): void => {
|
|
198
|
+
setExpandedId(null);
|
|
199
|
+
setCategory(next);
|
|
200
|
+
};
|
|
201
|
+
// A CATEGORY ASKED FOR by the door that opened this (`setLibraryBrowserOpen`
|
|
202
|
+
// with one). The store parks it until close, so it applies once per ask.
|
|
203
|
+
const asked = useShotEditor((s) => s.libraryBrowserCategory);
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (open && asked !== null) pickCategory(asked);
|
|
206
|
+
}, [open, asked]);
|
|
207
|
+
|
|
208
|
+
const noun = countNoun(category);
|
|
209
|
+
const kindWords = CATEGORY_LABEL[category].toLowerCase();
|
|
210
|
+
|
|
211
|
+
const catalogBody = (): ReactNode => {
|
|
212
|
+
if (!loaded) {
|
|
213
|
+
if (error === null || retrying) return <LibraryState icon="all" message="Loading the library…" />;
|
|
214
|
+
return (
|
|
215
|
+
<LibraryState
|
|
216
|
+
icon="all"
|
|
217
|
+
message="The library could not be read."
|
|
218
|
+
detail={error}
|
|
219
|
+
action={
|
|
220
|
+
<button type="button" className="btn is-outline" onClick={retry}>
|
|
221
|
+
Retry
|
|
222
|
+
</button>
|
|
223
|
+
}
|
|
224
|
+
/>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (shipped.length === 0) return <LibraryState icon="all" message="This build ships no library items." />;
|
|
228
|
+
if (rows.length === 0) {
|
|
229
|
+
if (query.trim() === "") {
|
|
230
|
+
return <LibraryState icon={category} message={`This build ships no ${kindWords}.`} />;
|
|
231
|
+
}
|
|
232
|
+
return (
|
|
233
|
+
<LibraryState
|
|
234
|
+
icon={category}
|
|
235
|
+
message={`No ${category === "all" ? "items" : kindWords} match ${JSON.stringify(query.trim())}.`}
|
|
236
|
+
action={
|
|
237
|
+
<button
|
|
238
|
+
type="button"
|
|
239
|
+
className="btn is-outline"
|
|
240
|
+
onClick={() => {
|
|
241
|
+
// This button leaves with its state; focus goes where the
|
|
242
|
+
// author types next, not to <body>.
|
|
243
|
+
setQuery("");
|
|
244
|
+
searchRef.current?.focus();
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
Clear search
|
|
248
|
+
</button>
|
|
249
|
+
}
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const sections: LibrarySection[] =
|
|
254
|
+
category === "all"
|
|
255
|
+
? groupByKind(rows).map((s) => ({ heading: CATEGORY_LABEL[s.kind], label: CATEGORY_LABEL[s.kind], rows: s.rows }))
|
|
256
|
+
: [{ label: CATEGORY_LABEL[category], rows }];
|
|
257
|
+
return <LibraryGrid sections={sections} selected={selectedId} onSelect={setSelectedId} onAdd={add} />;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// THE BOARD FIRST: it enters a Shot of its own on the way in, and closing
|
|
261
|
+
// the library lands back on the board, not in Studio inside that Shot.
|
|
262
|
+
let backLabel = `Back to ${project ?? "the editor"}`;
|
|
263
|
+
if (boardOpen) backLabel = "Back to storyboard";
|
|
264
|
+
else if (shotName !== null) backLabel = `Back to ${shotName}`;
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<Dialog open={open} className="library-browser" title="Library" onClose={close}>
|
|
268
|
+
<div className="library-main">
|
|
269
|
+
<LibraryRail
|
|
270
|
+
category={category}
|
|
271
|
+
counts={counts}
|
|
272
|
+
onCategory={pickCategory}
|
|
273
|
+
backLabel={backLabel}
|
|
274
|
+
onBack={close}
|
|
275
|
+
project={project}
|
|
276
|
+
onBoard={onBoardHere}
|
|
277
|
+
/>
|
|
278
|
+
{category === "stock" ? (
|
|
279
|
+
<StockSounds canAdd={hasDoc} />
|
|
280
|
+
) : category === "generated" ? (
|
|
281
|
+
<SoundHistory canAdd={hasDoc} />
|
|
282
|
+
) : expanded && selected !== null ? (
|
|
283
|
+
<LibraryExpandedPreview item={selected} backTo={kindWords} onClose={() => setExpandedId(null)} />
|
|
284
|
+
) : (
|
|
285
|
+
<section className="library-catalog" aria-label={CATEGORY_LABEL[category]}>
|
|
286
|
+
<header className="library-catalog-head">
|
|
287
|
+
<div>
|
|
288
|
+
<h2>{CATEGORY_LABEL[category]}</h2>
|
|
289
|
+
<p>{CATEGORY_SUBTITLE[category]}</p>
|
|
290
|
+
</div>
|
|
291
|
+
<span data-testid="library-count">{plural(rows.length, noun)}</span>
|
|
292
|
+
</header>
|
|
293
|
+
<div className="library-search-row">
|
|
294
|
+
{/* A plain `<input type="search">` and not `TextField`, which
|
|
295
|
+
commits on blur and Enter: this box filters the gallery as you
|
|
296
|
+
type. */}
|
|
297
|
+
<label className="search-field">
|
|
298
|
+
{SEARCH_ICON}
|
|
299
|
+
<input
|
|
300
|
+
ref={searchRef}
|
|
301
|
+
type="search"
|
|
302
|
+
value={query}
|
|
303
|
+
placeholder="Search library"
|
|
304
|
+
aria-label="Search library"
|
|
305
|
+
data-testid="library-search"
|
|
306
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
307
|
+
/>
|
|
308
|
+
<kbd className="kbd" aria-hidden="true">
|
|
309
|
+
/
|
|
310
|
+
</kbd>
|
|
311
|
+
</label>
|
|
312
|
+
<select
|
|
313
|
+
className="select is-quiet"
|
|
314
|
+
aria-label="Sort"
|
|
315
|
+
data-testid="library-sort"
|
|
316
|
+
value={sort}
|
|
317
|
+
onChange={(event) => setSort(event.target.value as LibrarySort)}
|
|
318
|
+
>
|
|
319
|
+
{(Object.keys(SORT_LABEL) as LibrarySort[]).map((s) => (
|
|
320
|
+
<option key={s} value={s}>
|
|
321
|
+
{SORT_LABEL[s]}
|
|
322
|
+
</option>
|
|
323
|
+
))}
|
|
324
|
+
</select>
|
|
325
|
+
</div>
|
|
326
|
+
{catalogBody()}
|
|
327
|
+
</section>
|
|
328
|
+
)}
|
|
329
|
+
{category === "stock" || category === "generated" ? null : (
|
|
330
|
+
<LibraryPreviewPanel
|
|
331
|
+
key={selected?.id}
|
|
332
|
+
item={selected}
|
|
333
|
+
destination="Project storyboard"
|
|
334
|
+
onBoard={selected === null ? 0 : (onBoard.get(selected.id) ?? 0)}
|
|
335
|
+
canAdd={hasDoc}
|
|
336
|
+
adding={adding !== null}
|
|
337
|
+
onAdd={() => {
|
|
338
|
+
if (selected !== null) add(selected);
|
|
339
|
+
}}
|
|
340
|
+
onClaim={() => {
|
|
341
|
+
if (selected !== null) run(selected, "claim");
|
|
342
|
+
}}
|
|
343
|
+
onPin={() => {
|
|
344
|
+
if (selected !== null) pin(selected);
|
|
345
|
+
}}
|
|
346
|
+
error={addError !== null && addError.id === selected?.id ? addError.message : null}
|
|
347
|
+
expanded={expanded}
|
|
348
|
+
onExpand={(open) => setExpandedId(open && selected !== null ? selected.id : null)}
|
|
349
|
+
/>
|
|
350
|
+
)}
|
|
351
|
+
</div>
|
|
352
|
+
{/* THE STATUS BAR replaces the old problems strip. THE REFUSED ITEMS
|
|
353
|
+
are still named: a drifted item is otherwise a card that is simply
|
|
354
|
+
not there, and an author reads that as a build that never shipped it
|
|
355
|
+
rather than one whose manifest needs rebuilding. The count says how
|
|
356
|
+
many; Details says which and why. */}
|
|
357
|
+
{/* THE DETAILS LIST IS INSIDE THE DIALOG, a disclosure the button
|
|
358
|
+
controls, and not a portaled Popover: a portal is a sibling of this
|
|
359
|
+
aria-modal dialog, so screen readers that honour aria-modal never
|
|
360
|
+
reached the list. Escape here closes the list, not the Library. */}
|
|
361
|
+
<footer
|
|
362
|
+
className="library-status"
|
|
363
|
+
data-testid="library-status"
|
|
364
|
+
onKeyDown={(event) => {
|
|
365
|
+
if (event.key !== "Escape" || !detailsOpen) return;
|
|
366
|
+
event.stopPropagation();
|
|
367
|
+
setDetailsOpen(false);
|
|
368
|
+
detailsRef.current?.focus();
|
|
369
|
+
}}
|
|
370
|
+
>
|
|
371
|
+
<span>
|
|
372
|
+
{expanded
|
|
373
|
+
? "Play the preview to see the motion."
|
|
374
|
+
: category === "sounds"
|
|
375
|
+
? "Choose a pack, then preview an individual sound."
|
|
376
|
+
: category === "stock"
|
|
377
|
+
? "Search, then press Enter. Only sounds you may use in a video are listed."
|
|
378
|
+
: category === "generated"
|
|
379
|
+
? "Pick a take to hear it, then add it to the timeline."
|
|
380
|
+
: "Click an item to preview it."}
|
|
381
|
+
</span>
|
|
382
|
+
{problems.length === 0 ? null : (
|
|
383
|
+
<span className="library-status-problems" data-testid="library-browser-problems">
|
|
384
|
+
{plural(problems.length, "item")} unavailable
|
|
385
|
+
<button
|
|
386
|
+
ref={detailsRef}
|
|
387
|
+
type="button"
|
|
388
|
+
className="library-status-details"
|
|
389
|
+
aria-expanded={detailsOpen}
|
|
390
|
+
aria-controls={detailsId}
|
|
391
|
+
onClick={() => setDetailsOpen((v) => !v)}
|
|
392
|
+
>
|
|
393
|
+
Details
|
|
394
|
+
</button>
|
|
395
|
+
</span>
|
|
396
|
+
)}
|
|
397
|
+
{detailsOpen && problems.length > 0 ? (
|
|
398
|
+
<div ref={detailsBoxRef} id={detailsId} className="popover library-problems" role="region" aria-label="Unavailable items">
|
|
399
|
+
<ul data-testid="library-problems-list">
|
|
400
|
+
{problems.map((problem) => (
|
|
401
|
+
<li key={problem.id}>
|
|
402
|
+
<span>{problem.id}</span>
|
|
403
|
+
<span>{problem.error}</span>
|
|
404
|
+
</li>
|
|
405
|
+
))}
|
|
406
|
+
</ul>
|
|
407
|
+
</div>
|
|
408
|
+
) : null}
|
|
409
|
+
</footer>
|
|
410
|
+
</Dialog>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** A catalog with nothing to show: quiet centred copy under the category's
|
|
415
|
+
* icon, and the one action that can change it, when there is one. */
|
|
416
|
+
function LibraryState({
|
|
417
|
+
icon,
|
|
418
|
+
message,
|
|
419
|
+
detail,
|
|
420
|
+
action,
|
|
421
|
+
}: {
|
|
422
|
+
icon: LibraryCategory;
|
|
423
|
+
message: string;
|
|
424
|
+
detail?: string;
|
|
425
|
+
action?: ReactNode;
|
|
426
|
+
}) {
|
|
427
|
+
return (
|
|
428
|
+
<div className="library-state" role="status">
|
|
429
|
+
{LIBRARY_CATEGORY_ICONS[icon]}
|
|
430
|
+
<p>{message}</p>
|
|
431
|
+
{detail === undefined ? null : <p className="library-state-detail">{detail}</p>}
|
|
432
|
+
{action}
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// The Library's gallery: quiet cards, one picked at a time.
|
|
2
|
+
//
|
|
3
|
+
// QUIET CARDS. A card is a picture, a name and one sentence, and nothing to
|
|
4
|
+
// press: no per-card Add. Click picks it and the browser's right column
|
|
5
|
+
// previews it, which is where the Add, the destination and the detail live,
|
|
6
|
+
// once, instead of twenty times. A double click or Enter adds straight away,
|
|
7
|
+
// for the author who already knows.
|
|
8
|
+
//
|
|
9
|
+
// A LISTBOX PER SECTION, ONE ROVING TAB STOP OVER ALL OF THEM. Each section
|
|
10
|
+
// (one in a kind, one per kind under "All items") is a `listbox` of `option`s,
|
|
11
|
+
// labelled by its heading, because a heading may not sit inside a listbox.
|
|
12
|
+
// The arrows walk every option on the gallery in DOM order, across sections,
|
|
13
|
+
// and exactly one option is in the tab order: the picked one, or the first.
|
|
14
|
+
// Selection follows focus, so walking with the arrows previews as it goes,
|
|
15
|
+
// which is the point of a browse surface.
|
|
16
|
+
//
|
|
17
|
+
// THE CARD PICTURE IS `row.thumb`, NEVER `ComponentStage`. A live preview
|
|
18
|
+
// sits behind a module-level `LIVE_CAP = 6` slot budget against the browser's
|
|
19
|
+
// WebGL context ceiling (liveCap.ts), so a twenty-card grid would draw six
|
|
20
|
+
// live and fourteen placeholders, silently and in whatever order they
|
|
21
|
+
// happened to mount. A shipped PNG is the honest picture here, and it is
|
|
22
|
+
// already on the row.
|
|
23
|
+
import { useId, useRef } from "react";
|
|
24
|
+
import type { KeyboardEvent as ReactKeyboardEvent } from "react";
|
|
25
|
+
import { LIBRARY_CATEGORY_ICONS } from "../../icons.js";
|
|
26
|
+
import type { LibraryItem, LibraryItemKind } from "../../api.js";
|
|
27
|
+
import { displayNameOf, kindOf } from "./libraryModel.js";
|
|
28
|
+
|
|
29
|
+
export interface LibrarySection {
|
|
30
|
+
/** The section's kind, for its icon-free small heading; omitted for the one
|
|
31
|
+
* section of a single kind, whose heading is the catalog's own. */
|
|
32
|
+
heading?: string;
|
|
33
|
+
/** The listbox's accessible name when there is no visible heading. */
|
|
34
|
+
label: string;
|
|
35
|
+
rows: readonly LibraryItem[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** How many cards one row of this grid holds. A browser resolves
|
|
39
|
+
* `grid-template-columns` to one px track per column; jsdom returns the
|
|
40
|
+
* declared `repeat(...)` text, which counts as one column. */
|
|
41
|
+
function columnsOf(list: Element | null): number {
|
|
42
|
+
if (list === null) return 1;
|
|
43
|
+
const tracks = getComputedStyle(list).gridTemplateColumns.split(" ").filter((t) => /px$/.test(t));
|
|
44
|
+
return Math.max(1, tracks.length);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function LibraryGrid({
|
|
48
|
+
sections,
|
|
49
|
+
selected,
|
|
50
|
+
onSelect,
|
|
51
|
+
onAdd,
|
|
52
|
+
}: {
|
|
53
|
+
sections: readonly LibrarySection[];
|
|
54
|
+
selected: string | null;
|
|
55
|
+
onSelect: (id: string) => void;
|
|
56
|
+
onAdd: (row: LibraryItem) => void;
|
|
57
|
+
}) {
|
|
58
|
+
const root = useRef<HTMLDivElement>(null);
|
|
59
|
+
const baseId = useId();
|
|
60
|
+
const all = sections.flatMap((s) => s.rows);
|
|
61
|
+
// The tab stop: the picked card when it is on screen, else the first.
|
|
62
|
+
const stop = all.some((row) => row.id === selected) ? selected : (all[0]?.id ?? null);
|
|
63
|
+
|
|
64
|
+
const onKeyDown = (event: ReactKeyboardEvent<HTMLElement>, row: LibraryItem): void => {
|
|
65
|
+
if (event.key === "Enter") {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
onAdd(row);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (event.key === " ") {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
onSelect(row.id);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const options = [...(root.current?.querySelectorAll<HTMLElement>('[role="option"]') ?? [])];
|
|
76
|
+
const at = options.indexOf(event.currentTarget);
|
|
77
|
+
if (at < 0) return;
|
|
78
|
+
// Up and down step by the CURRENT grid's row length. Crossing into a
|
|
79
|
+
// section whose last row is short lands in DOM order rather than in the
|
|
80
|
+
// same column; ponytail: column-exact crossing needs per-card geometry.
|
|
81
|
+
const cols = columnsOf(event.currentTarget.parentElement);
|
|
82
|
+
const next =
|
|
83
|
+
event.key === "ArrowRight"
|
|
84
|
+
? at + 1
|
|
85
|
+
: event.key === "ArrowLeft"
|
|
86
|
+
? at - 1
|
|
87
|
+
: event.key === "ArrowDown"
|
|
88
|
+
? at + cols
|
|
89
|
+
: event.key === "ArrowUp"
|
|
90
|
+
? at - cols
|
|
91
|
+
: event.key === "Home"
|
|
92
|
+
? 0
|
|
93
|
+
: event.key === "End"
|
|
94
|
+
? options.length - 1
|
|
95
|
+
: null;
|
|
96
|
+
if (next === null) return;
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
const target = options[Math.max(0, Math.min(options.length - 1, next))];
|
|
99
|
+
if (target === undefined) return;
|
|
100
|
+
const id = target.dataset.library;
|
|
101
|
+
if (id !== undefined) onSelect(id);
|
|
102
|
+
target.focus();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="library-gallery" ref={root}>
|
|
107
|
+
{sections.map((section, i) => {
|
|
108
|
+
const headingId = `${baseId}-h${i}`;
|
|
109
|
+
return (
|
|
110
|
+
<section key={section.label} className="library-section">
|
|
111
|
+
{section.heading === undefined ? null : (
|
|
112
|
+
<h3 className="library-section-head" id={headingId}>
|
|
113
|
+
{section.heading}
|
|
114
|
+
<span>{section.rows.length}</span>
|
|
115
|
+
</h3>
|
|
116
|
+
)}
|
|
117
|
+
<ul
|
|
118
|
+
className="library-grid"
|
|
119
|
+
role="listbox"
|
|
120
|
+
aria-label={section.heading === undefined ? section.label : undefined}
|
|
121
|
+
aria-labelledby={section.heading === undefined ? undefined : headingId}
|
|
122
|
+
>
|
|
123
|
+
{section.rows.map((row) => (
|
|
124
|
+
<LibraryCard
|
|
125
|
+
key={row.id}
|
|
126
|
+
row={row}
|
|
127
|
+
selected={row.id === selected}
|
|
128
|
+
tabbable={row.id === stop}
|
|
129
|
+
onSelect={() => onSelect(row.id)}
|
|
130
|
+
onAdd={() => onAdd(row)}
|
|
131
|
+
onKeyDown={(event) => onKeyDown(event, row)}
|
|
132
|
+
/>
|
|
133
|
+
))}
|
|
134
|
+
</ul>
|
|
135
|
+
</section>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function LibraryCard({
|
|
143
|
+
row,
|
|
144
|
+
selected,
|
|
145
|
+
tabbable,
|
|
146
|
+
onSelect,
|
|
147
|
+
onAdd,
|
|
148
|
+
onKeyDown,
|
|
149
|
+
}: {
|
|
150
|
+
row: LibraryItem;
|
|
151
|
+
selected: boolean;
|
|
152
|
+
tabbable: boolean;
|
|
153
|
+
onSelect: () => void;
|
|
154
|
+
onAdd: () => void;
|
|
155
|
+
onKeyDown: (event: ReactKeyboardEvent<HTMLElement>) => void;
|
|
156
|
+
}) {
|
|
157
|
+
const kind: LibraryItemKind = kindOf(row);
|
|
158
|
+
const name = displayNameOf(row);
|
|
159
|
+
return (
|
|
160
|
+
<li
|
|
161
|
+
className={`library-card is-${kind}`}
|
|
162
|
+
role="option"
|
|
163
|
+
aria-selected={selected}
|
|
164
|
+
tabIndex={tabbable ? 0 : -1}
|
|
165
|
+
data-library={row.id}
|
|
166
|
+
title={row.id}
|
|
167
|
+
onClick={onSelect}
|
|
168
|
+
onDoubleClick={onAdd}
|
|
169
|
+
onKeyDown={onKeyDown}
|
|
170
|
+
// Not a drag source: the browser is a dialog over the board, and its
|
|
171
|
+
// preview column owns the Add.
|
|
172
|
+
draggable={false}
|
|
173
|
+
>
|
|
174
|
+
{/* The art box reserves its 16:9 BEFORE any image loads, so a late
|
|
175
|
+
thumb never shifts the grid under the pointer mid-click. */}
|
|
176
|
+
<div className="library-card-art">
|
|
177
|
+
{row.thumb === undefined ? (
|
|
178
|
+
<span className="library-card-placeholder" aria-hidden="true">
|
|
179
|
+
{LIBRARY_CATEGORY_ICONS[kind]}
|
|
180
|
+
<span>{name}</span>
|
|
181
|
+
</span>
|
|
182
|
+
) : (
|
|
183
|
+
<img src={row.thumb} alt="" loading="lazy" draggable={false} />
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
<span className="library-card-name">{name}</span>
|
|
187
|
+
{row.description === undefined ? null : <span className="library-card-note">{row.description}</span>}
|
|
188
|
+
</li>
|
|
189
|
+
);
|
|
190
|
+
}
|