@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,656 @@
|
|
|
1
|
+
// THE BOARD'S PROJECT ASSETS: everything this Project holds, in the order an
|
|
2
|
+
// author reaches for it. A drop zone, the pictures, the other files, the
|
|
3
|
+
// components, the colors, and the brand's rules in a fold.
|
|
4
|
+
//
|
|
5
|
+
// The rows are the shared resource parts (`resourceParts`), and the verbs
|
|
6
|
+
// are the store's, so no panel can disagree on what a row is or what Remove
|
|
7
|
+
// takes out. The board's right column shows it (`boardRightPanel` "assets"),
|
|
8
|
+
// opened from the board's + menu.
|
|
9
|
+
//
|
|
10
|
+
// ── THE BOARD NEVER WRITES INTO A SHOT ───────────────────────────────────
|
|
11
|
+
//
|
|
12
|
+
// The board plans and the Studio makes, so on the board "Use in shot" is
|
|
13
|
+
// refused with `placeInShotRefusal`'s sentence. On a shot board a picture's
|
|
14
|
+
// button is "Add to sketch" instead (`addSketchItemFromAsset`).
|
|
15
|
+
//
|
|
16
|
+
// ── A FILE DRAGS ONLY WHERE A TARGET IS ON SCREEN ───────────────────────
|
|
17
|
+
//
|
|
18
|
+
// `assetDropTargetShown`: the board's canvas and its shot cards, which pin
|
|
19
|
+
// the file as a card. A font never drags: it is applied to type, never placed.
|
|
20
|
+
//
|
|
21
|
+
// ── A LINK IS A PIN, A MEDIA LINK IS REFUSED ────────────────────────────
|
|
22
|
+
//
|
|
23
|
+
// A page address dropped or pasted on the zone becomes a Whole film link card
|
|
24
|
+
// (`addLinkReference`, lane null). Links are never fetched, so an address
|
|
25
|
+
// that names a media file is refused by name: its bytes would have to come
|
|
26
|
+
// from somewhere, and the only honest source is the author's own download.
|
|
27
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
28
|
+
import type { ClipboardEvent, DragEvent, ReactNode } from "react";
|
|
29
|
+
import { effectiveResources, planAssetRemoval, soundtrackOwner } from "@dailies-studio/shot/doc";
|
|
30
|
+
import type { Doc3D, ScopedBrandItem3D, ShotAsset } from "@dailies-studio/shot/doc";
|
|
31
|
+
import { loadPeaks } from "@dailies-studio/timeline";
|
|
32
|
+
import { formatBytes } from "../../api.js";
|
|
33
|
+
import { SEARCH_ICON } from "../../icons.js";
|
|
34
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
35
|
+
import { useImageThumb } from "../../state/imageThumbs.js";
|
|
36
|
+
import { projectComponents } from "../../state/projectComponents.js";
|
|
37
|
+
import { assetPlaceTarget, placeInShotRefusal } from "../../state/store.js";
|
|
38
|
+
import { inShotContext } from "../../state/viewMode.js";
|
|
39
|
+
import { DOM_NOT_SUPPORTED, shotBoardOf } from "../../state/workspace.js";
|
|
40
|
+
import { formatDuration } from "../timeline/model.js";
|
|
41
|
+
import { STORYBOARD_MIME, urlFromTransfer } from "../storyboard/storyboardDrop.js";
|
|
42
|
+
import { ASSET_DROP_COPY, ASSET_MIME, assetDropTargetShown, writeAssetDrag } from "./assetDrag.js";
|
|
43
|
+
import {
|
|
44
|
+
BrandRow,
|
|
45
|
+
NameOf,
|
|
46
|
+
ResourceForm,
|
|
47
|
+
ResourceLane,
|
|
48
|
+
UPLOAD_ACCEPT,
|
|
49
|
+
actionForResource,
|
|
50
|
+
assetGlyph,
|
|
51
|
+
assetUsedInComposition,
|
|
52
|
+
fileNameOf,
|
|
53
|
+
selectedLayers,
|
|
54
|
+
} from "./resourceParts.js";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The board's Project assets panel in the right column: a head that names it
|
|
58
|
+
* and closes it, a search, and the tab under one scroller. A viewer reads
|
|
59
|
+
* every row locked.
|
|
60
|
+
*/
|
|
61
|
+
export function BoardAssets() {
|
|
62
|
+
const store = useStore();
|
|
63
|
+
const readOnly = useShotEditor((s) => s.readOnly);
|
|
64
|
+
const [query, setQuery] = useState("");
|
|
65
|
+
return (
|
|
66
|
+
<section className="board-assets" data-testid="board-assets" aria-label="Project assets">
|
|
67
|
+
<header className="board-assets-head">
|
|
68
|
+
<h2 className="board-assets-title">Project assets</h2>
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
className="btn is-icon"
|
|
72
|
+
data-testid="board-assets-close"
|
|
73
|
+
aria-label="Close Project assets"
|
|
74
|
+
title="Close Project assets"
|
|
75
|
+
onClick={() => store.setBoardAssetsOpen(false)}
|
|
76
|
+
>
|
|
77
|
+
×
|
|
78
|
+
</button>
|
|
79
|
+
</header>
|
|
80
|
+
<div className="board-assets-search">
|
|
81
|
+
<label className="search-field">
|
|
82
|
+
{SEARCH_ICON}
|
|
83
|
+
<input
|
|
84
|
+
type="search"
|
|
85
|
+
value={query}
|
|
86
|
+
placeholder="Search files"
|
|
87
|
+
aria-label="Search files"
|
|
88
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
89
|
+
/>
|
|
90
|
+
</label>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="rail-scroll">
|
|
93
|
+
<AssetsTab query={query} readOnly={readOnly} />
|
|
94
|
+
</div>
|
|
95
|
+
</section>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function AssetsTab({
|
|
100
|
+
readOnly = false,
|
|
101
|
+
query = "",
|
|
102
|
+
}: {
|
|
103
|
+
readOnly?: boolean;
|
|
104
|
+
/** The rail's search box. Filters every lane by the name the author sees. */
|
|
105
|
+
query?: string;
|
|
106
|
+
}) {
|
|
107
|
+
const store = useStore();
|
|
108
|
+
const doc = useShotEditor((state) => state.doc);
|
|
109
|
+
const name = useShotEditor((state) => state.name);
|
|
110
|
+
const usage = useShotEditor((state) => state.assetUsage);
|
|
111
|
+
const library = useShotEditor((state) => state.library);
|
|
112
|
+
const libraryLoaded = useShotEditor((state) => state.libraryLoaded);
|
|
113
|
+
const homes = useShotEditor((state) => state.componentHomes);
|
|
114
|
+
// The Shot this tab works on. The board enters none, so a Shot the Studio
|
|
115
|
+
// left open behind it is not one: no "This shot only" lane, and no Apply
|
|
116
|
+
// onto layers selected there.
|
|
117
|
+
const enteredScene = useShotEditor((state) => (state.storyboardOpen ? null : state.enteredScene));
|
|
118
|
+
const editingDef = useShotEditor((state) => state.editingDef);
|
|
119
|
+
const selection = useShotEditor((state) => state.selection);
|
|
120
|
+
const dropShown = useShotEditor(assetDropTargetShown);
|
|
121
|
+
// The target's NAME, a string or null, so the selector allocates nothing.
|
|
122
|
+
const targetName = useShotEditor((state) => {
|
|
123
|
+
const id = assetPlaceTarget(state);
|
|
124
|
+
return id === null ? null : (state.doc?.scenesById[id]?.name ?? null);
|
|
125
|
+
});
|
|
126
|
+
// Why Use would refuse. One hidden copy that every Use points at, since the
|
|
127
|
+
// reason is the tab's state, not a row's.
|
|
128
|
+
const refusal = useShotEditor(placeInShotRefusal);
|
|
129
|
+
// A sound joins the Shot's audio, so a leaf isolation does not refuse it.
|
|
130
|
+
// It is either `refusal` or null, so the one hidden copy still describes it.
|
|
131
|
+
const soundRefusal = useShotEditor((state) => placeInShotRefusal(state, "sound"));
|
|
132
|
+
const refusalId = useId();
|
|
133
|
+
// A shot board's pictures go on its sketch.
|
|
134
|
+
const sketching = useShotEditor((state) => shotBoardOf(state) !== null);
|
|
135
|
+
const [form, setForm] = useState<"color" | "guidance" | null>(null);
|
|
136
|
+
|
|
137
|
+
// The meter is server truth, refreshed on entry rather than trusted stale.
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (name !== null) void store.refreshAssetUsage();
|
|
140
|
+
}, [name, store]);
|
|
141
|
+
// The registry rows the Components lane lists are the Project's own
|
|
142
|
+
// (`projectComponents`), so the registry has to be read once.
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (!libraryLoaded) void store.refreshLibrary();
|
|
145
|
+
}, [libraryLoaded, store]);
|
|
146
|
+
// And which Project holds which file (`componentHomes`), or a folder-mate
|
|
147
|
+
// another Project lists or uses folds in here as this Project's.
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (homes === null) void store.refreshWorkspaceComponents();
|
|
150
|
+
}, [homes, store]);
|
|
151
|
+
|
|
152
|
+
if (doc === null) return null;
|
|
153
|
+
const needle = query.trim().toLowerCase();
|
|
154
|
+
const matches = (text: string): boolean => needle === "" || text.toLowerCase().includes(needle);
|
|
155
|
+
const report = (error: unknown): void => store.reportError(error instanceof Error ? error.message : String(error));
|
|
156
|
+
|
|
157
|
+
const assets = Object.values(doc.assets);
|
|
158
|
+
const unused = new Set(planAssetRemoval(doc).remove);
|
|
159
|
+
const sizeByHash = new Map((usage?.assets ?? []).map((a) => [a.hash, a.size]));
|
|
160
|
+
// Gone from storage. The stage's notice offers Relink and Remove.
|
|
161
|
+
const missing = new Set((usage?.missing ?? []).map((row) => row.id));
|
|
162
|
+
const shownAssets = assets.filter((asset) => matches(fileNameOf(asset)));
|
|
163
|
+
const images = shownAssets.filter((asset) => asset.kind === "image");
|
|
164
|
+
const files = shownAssets.filter((asset) => asset.kind !== "image");
|
|
165
|
+
|
|
166
|
+
// The Project's rows, whatever Shot is entered, and the entered Shot's own
|
|
167
|
+
// in their own lane.
|
|
168
|
+
const project = effectiveResources(doc, null).branding;
|
|
169
|
+
const shot = enteredScene === null ? undefined : doc.scenesById[enteredScene];
|
|
170
|
+
const shotBranding =
|
|
171
|
+
shot === undefined ? [] : effectiveResources(doc, enteredScene).branding.filter((item) => item.scope === "shot");
|
|
172
|
+
const shadowedKeys = new Set(shotBranding.map((item) => item.key));
|
|
173
|
+
const brandAssets = new Set(
|
|
174
|
+
[...project, ...shotBranding].flatMap((item) => (item.type === "asset" ? [item.asset] : [])),
|
|
175
|
+
);
|
|
176
|
+
const colors = project.filter((item) => item.type === "color" && matches(item.label));
|
|
177
|
+
const rules = project.filter((item) => item.type !== "color" && matches(item.label));
|
|
178
|
+
const shotRows = shotBranding.filter((item) => matches(item.label));
|
|
179
|
+
|
|
180
|
+
const definitions = Object.values(doc.components ?? {}).filter((def) => matches(def.name));
|
|
181
|
+
const registry = projectComponents(doc, library, homes).project.filter((row) => matches(row.name));
|
|
182
|
+
|
|
183
|
+
const inShot = inShotContext({ enteredScene, editingDef });
|
|
184
|
+
const selected = selectedLayers(doc, editingDef ?? enteredScene, selection);
|
|
185
|
+
|
|
186
|
+
/** Not on a soundtrack whose video stays: that link would name a file that
|
|
187
|
+
* is gone. It leaves with its video. */
|
|
188
|
+
const removable = (asset: ShotAsset): boolean =>
|
|
189
|
+
!readOnly && unused.has(asset.id) && soundtrackOwner(doc, asset.id) === null;
|
|
190
|
+
const removeButton = (asset: ShotAsset): ReactNode => (
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
className="assets-tab-remove"
|
|
194
|
+
aria-label={`Remove ${asset.path} from this Project`}
|
|
195
|
+
title="Remove from this Project"
|
|
196
|
+
onClick={() => store.removeUnusedAssets([asset.id])}
|
|
197
|
+
>
|
|
198
|
+
×
|
|
199
|
+
</button>
|
|
200
|
+
);
|
|
201
|
+
/** "Use in shot", or a refusal that says why: `aria-disabled` rather than
|
|
202
|
+
* `disabled`, so Tab still reaches it and the click reports the sentence. */
|
|
203
|
+
const useButton = (
|
|
204
|
+
label: string,
|
|
205
|
+
onUse: () => void,
|
|
206
|
+
reason: string | null = refusal,
|
|
207
|
+
reasonId: string = refusalId,
|
|
208
|
+
): ReactNode =>
|
|
209
|
+
readOnly ? null : (
|
|
210
|
+
<button
|
|
211
|
+
type="button"
|
|
212
|
+
className="btn assets-tab-use"
|
|
213
|
+
aria-label={`Use in shot: ${label}${targetName === null ? "" : `, ${targetName}`}`}
|
|
214
|
+
aria-disabled={reason === null ? undefined : "true"}
|
|
215
|
+
aria-describedby={reason === null ? undefined : reasonId}
|
|
216
|
+
title={reason ?? `Use in ${targetName}`}
|
|
217
|
+
onClick={() => {
|
|
218
|
+
if (reason !== null && reason !== refusal) {
|
|
219
|
+
store.reportError(reason);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
onUse();
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
Use in shot
|
|
226
|
+
</button>
|
|
227
|
+
);
|
|
228
|
+
/** "Add to sketch" on a shot board, for an image or a video; else Use. */
|
|
229
|
+
const useOrSketch = (asset: ShotAsset, reason: string | null = refusal): ReactNode =>
|
|
230
|
+
sketching && (asset.kind === "image" || asset.kind === "video") ? (
|
|
231
|
+
readOnly ? null : (
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
className="btn assets-tab-use"
|
|
235
|
+
aria-label={`Add to sketch: ${fileNameOf(asset)}`}
|
|
236
|
+
onClick={() => void store.addSketchItemFromAsset(asset.id)}
|
|
237
|
+
>
|
|
238
|
+
Add to sketch
|
|
239
|
+
</button>
|
|
240
|
+
)
|
|
241
|
+
) : (
|
|
242
|
+
useButton(fileNameOf(asset), () => void store.useAssetInShot(asset.id), reason)
|
|
243
|
+
);
|
|
244
|
+
const dragProps = (asset: ShotAsset) => {
|
|
245
|
+
// READ-ONLY IS NOT DRAGGABLE, a font never is, and neither is a file
|
|
246
|
+
// with no drop target on screen.
|
|
247
|
+
const draggable = !readOnly && dropShown && asset.kind !== "font";
|
|
248
|
+
return {
|
|
249
|
+
draggable,
|
|
250
|
+
...(draggable
|
|
251
|
+
? {
|
|
252
|
+
title: ASSET_DROP_COPY,
|
|
253
|
+
onDragStart: (event: DragEvent<HTMLElement>) => {
|
|
254
|
+
if (event.dataTransfer !== null) writeAssetDrag(event.dataTransfer, { asset: asset.id });
|
|
255
|
+
},
|
|
256
|
+
}
|
|
257
|
+
: {}),
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const brandRow = (item: ScopedBrandItem3D) => {
|
|
262
|
+
const asset = item.type === "asset" ? doc.assets[item.asset] : undefined;
|
|
263
|
+
return (
|
|
264
|
+
<BrandRow
|
|
265
|
+
key={item.id}
|
|
266
|
+
item={item}
|
|
267
|
+
{...(asset === undefined ? {} : { asset })}
|
|
268
|
+
used={asset === undefined ? false : assetUsedInComposition(doc, asset.id)}
|
|
269
|
+
readOnly={readOnly}
|
|
270
|
+
canOverride={item.scope === "project" && shot !== undefined && !shadowedKeys.has(item.key)}
|
|
271
|
+
shadowed={item.scope === "project" && shadowedKeys.has(item.key)}
|
|
272
|
+
action={actionForResource(item, asset, selected, inShot)}
|
|
273
|
+
onUse={() => void store.useBrandResource(item.id).catch(report)}
|
|
274
|
+
onOverride={() => store.overrideBrandResource(item.id)}
|
|
275
|
+
onRemove={() => store.removeResource("branding", item.id, item.scope)}
|
|
276
|
+
/>
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<div className={`assets-tab resource-stack${readOnly ? " is-readonly" : ""}`} data-testid="assets-tab">
|
|
282
|
+
{/* `hidden` keeps it off screen, and `aria-describedby` still reads it. */}
|
|
283
|
+
{refusal === null ? null : (
|
|
284
|
+
<span id={refusalId} hidden>
|
|
285
|
+
{refusal}
|
|
286
|
+
</span>
|
|
287
|
+
)}
|
|
288
|
+
{readOnly ? null : <AssetsDropZone />}
|
|
289
|
+
|
|
290
|
+
<div className="assets-tab-head">
|
|
291
|
+
{usage === null ? null : (
|
|
292
|
+
<span className="assets-tab-meter" data-testid="assets-tab-meter">
|
|
293
|
+
{formatBytes(usage.limitBytes - usage.remainingBytes)} / {formatBytes(usage.limitBytes)}
|
|
294
|
+
</span>
|
|
295
|
+
)}
|
|
296
|
+
{readOnly || unused.size === 0 ? null : (
|
|
297
|
+
<button
|
|
298
|
+
type="button"
|
|
299
|
+
title="Take the files nothing in this Project uses out of it. Undo brings them back."
|
|
300
|
+
onClick={() => store.removeUnusedAssets()}
|
|
301
|
+
>
|
|
302
|
+
Remove unused ({unused.size})
|
|
303
|
+
</button>
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<ResourceLane title="Images" count={images.length} readOnly={false} actions={null}>
|
|
308
|
+
{images.length === 0 ? (
|
|
309
|
+
<div className="resource-empty">{needle === "" ? "No images yet." : "No image names match this search."}</div>
|
|
310
|
+
) : (
|
|
311
|
+
<ul className="assets-tab-images" data-testid="assets-tab-images">
|
|
312
|
+
{images.map((asset) => (
|
|
313
|
+
<li key={asset.id} className="assets-tab-image" data-asset={asset.id} {...dragProps(asset)}>
|
|
314
|
+
<span className="assets-tab-thumb">
|
|
315
|
+
{/* Not draggable itself, so the drag is the TILE's and carries
|
|
316
|
+
the registry id rather than the image's URL. */}
|
|
317
|
+
<AssetThumb asset={asset} />
|
|
318
|
+
{brandAssets.has(asset.id) ? <span className="assets-tab-chip is-brand">Brand</span> : null}
|
|
319
|
+
{unused.has(asset.id) ? <span className="assets-tab-chip">Unused</span> : null}
|
|
320
|
+
{missing.has(asset.id) ? <span className="assets-tab-chip">File missing</span> : null}
|
|
321
|
+
{removable(asset) ? removeButton(asset) : null}
|
|
322
|
+
</span>
|
|
323
|
+
<NameOf asset={asset} bytes={sizeByHash.get(asset.hash)} />
|
|
324
|
+
{useOrSketch(asset)}
|
|
325
|
+
</li>
|
|
326
|
+
))}
|
|
327
|
+
</ul>
|
|
328
|
+
)}
|
|
329
|
+
</ResourceLane>
|
|
330
|
+
|
|
331
|
+
<ResourceLane title="Files" count={files.length} readOnly={false} actions={null}>
|
|
332
|
+
{files.length === 0 ? (
|
|
333
|
+
<div className="resource-empty">{needle === "" ? "No other files yet." : "No file names match this search."}</div>
|
|
334
|
+
) : (
|
|
335
|
+
<ul className="resource-list" data-testid="assets-tab-files">
|
|
336
|
+
{files.map((asset) => (
|
|
337
|
+
<li key={asset.id} className="resource-row assets-tab-file" data-asset={asset.id} {...dragProps(asset)}>
|
|
338
|
+
<div className="resource-asset-row">
|
|
339
|
+
<span className="resource-thumb is-kind" aria-hidden="true">
|
|
340
|
+
{assetGlyph(asset)}
|
|
341
|
+
</span>
|
|
342
|
+
<NameOf asset={asset} />
|
|
343
|
+
<FileMeta doc={doc} asset={asset} />
|
|
344
|
+
{brandAssets.has(asset.id) ? <span className="resource-badge">Brand</span> : null}
|
|
345
|
+
{missing.has(asset.id) ? <span className="resource-badge">File missing</span> : null}
|
|
346
|
+
{unused.has(asset.id) ? (
|
|
347
|
+
<span className="resource-badge">Unused</span>
|
|
348
|
+
) : assetUsedInComposition(doc, asset.id) ? (
|
|
349
|
+
<span className="resource-used">Used</span>
|
|
350
|
+
) : null}
|
|
351
|
+
{sizeByHash.has(asset.hash) ? (
|
|
352
|
+
<span className="asset-size">{formatBytes(sizeByHash.get(asset.hash)!)}</span>
|
|
353
|
+
) : null}
|
|
354
|
+
{asset.kind === "font" ? null : useOrSketch(asset, asset.kind === "audio" ? soundRefusal : refusal)}
|
|
355
|
+
{removable(asset) ? removeButton(asset) : null}
|
|
356
|
+
</div>
|
|
357
|
+
</li>
|
|
358
|
+
))}
|
|
359
|
+
</ul>
|
|
360
|
+
)}
|
|
361
|
+
</ResourceLane>
|
|
362
|
+
|
|
363
|
+
<ResourceLane
|
|
364
|
+
title="Components"
|
|
365
|
+
count={definitions.length + registry.length}
|
|
366
|
+
readOnly={false}
|
|
367
|
+
actions={
|
|
368
|
+
readOnly ? null : (
|
|
369
|
+
<div className="resource-add-actions">
|
|
370
|
+
<button type="button" onClick={() => store.setLibraryBrowserOpen(true, "component")}>
|
|
371
|
+
+ From Library
|
|
372
|
+
</button>
|
|
373
|
+
</div>
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
>
|
|
377
|
+
{definitions.length + registry.length === 0 ? (
|
|
378
|
+
<div className="resource-empty">
|
|
379
|
+
{needle === "" ? "No components in this Project yet." : "No component names match this search."}
|
|
380
|
+
</div>
|
|
381
|
+
) : (
|
|
382
|
+
<ul className="resource-list" data-testid="assets-tab-components">
|
|
383
|
+
{definitions.map((def) => (
|
|
384
|
+
<li key={def.id} className="resource-row" data-component={def.id}>
|
|
385
|
+
<div className="resource-asset-row">
|
|
386
|
+
<span className="resource-thumb is-kind" aria-hidden="true">
|
|
387
|
+
{"</>"}
|
|
388
|
+
</span>
|
|
389
|
+
<span className="resource-label">{def.name}</span>
|
|
390
|
+
<span className="resource-badge">Project</span>
|
|
391
|
+
{useButton(def.name, () => void store.useComponentInShot(def.id))}
|
|
392
|
+
</div>
|
|
393
|
+
</li>
|
|
394
|
+
))}
|
|
395
|
+
{registry.map((row) => (
|
|
396
|
+
<li key={row.id} className="resource-row" data-component={row.id}>
|
|
397
|
+
<div className="resource-asset-row">
|
|
398
|
+
<span className="resource-thumb is-kind" aria-hidden="true">
|
|
399
|
+
{"</>"}
|
|
400
|
+
</span>
|
|
401
|
+
<span className="resource-label" title={row.rel}>
|
|
402
|
+
{row.name}
|
|
403
|
+
</span>
|
|
404
|
+
<span className="resource-badge">{row.host === "three" ? "3D" : "2D"}</span>
|
|
405
|
+
{row.host === "three" ? (
|
|
406
|
+
useButton(row.name, () => void store.useComponentInShot(row.id))
|
|
407
|
+
) : (
|
|
408
|
+
<>
|
|
409
|
+
<span id={`${refusalId}-${row.id}`} hidden>
|
|
410
|
+
{DOM_NOT_SUPPORTED}
|
|
411
|
+
</span>
|
|
412
|
+
{useButton(
|
|
413
|
+
row.name,
|
|
414
|
+
() => void store.useComponentInShot(row.id),
|
|
415
|
+
DOM_NOT_SUPPORTED,
|
|
416
|
+
`${refusalId}-${row.id}`,
|
|
417
|
+
)}
|
|
418
|
+
</>
|
|
419
|
+
)}
|
|
420
|
+
</div>
|
|
421
|
+
</li>
|
|
422
|
+
))}
|
|
423
|
+
</ul>
|
|
424
|
+
)}
|
|
425
|
+
</ResourceLane>
|
|
426
|
+
|
|
427
|
+
<ResourceLane title="Colors" count={colors.length} readOnly={false} actions={null}>
|
|
428
|
+
<ul className="assets-tab-swatches" data-testid="assets-tab-colors">
|
|
429
|
+
{colors.map((item) =>
|
|
430
|
+
item.type !== "color" ? null : (
|
|
431
|
+
<li key={item.id} className="assets-tab-swatch" data-resource={item.id} title={`${item.label} ${item.value}`}>
|
|
432
|
+
<span className="assets-tab-swatch-chip" style={{ backgroundColor: item.value }} aria-hidden="true" />
|
|
433
|
+
<span className="assets-tab-swatch-label">{item.label}</span>
|
|
434
|
+
{readOnly ? null : (
|
|
435
|
+
<button
|
|
436
|
+
type="button"
|
|
437
|
+
className="assets-tab-remove"
|
|
438
|
+
aria-label={`Remove ${item.label}`}
|
|
439
|
+
onClick={() => store.removeResource("branding", item.id, item.scope)}
|
|
440
|
+
>
|
|
441
|
+
×
|
|
442
|
+
</button>
|
|
443
|
+
)}
|
|
444
|
+
</li>
|
|
445
|
+
),
|
|
446
|
+
)}
|
|
447
|
+
{readOnly ? null : (
|
|
448
|
+
<li>
|
|
449
|
+
<button
|
|
450
|
+
type="button"
|
|
451
|
+
className="assets-tab-swatch-add"
|
|
452
|
+
aria-label="Add a color"
|
|
453
|
+
aria-expanded={form === "color"}
|
|
454
|
+
onClick={() => setForm(form === "color" ? null : "color")}
|
|
455
|
+
>
|
|
456
|
+
+
|
|
457
|
+
</button>
|
|
458
|
+
</li>
|
|
459
|
+
)}
|
|
460
|
+
</ul>
|
|
461
|
+
{form === "color" ? (
|
|
462
|
+
<ResourceForm
|
|
463
|
+
kind="color"
|
|
464
|
+
onCancel={() => setForm(null)}
|
|
465
|
+
onSubmit={(label, value) => {
|
|
466
|
+
store.addBrandColor(label, value, "project");
|
|
467
|
+
setForm(null);
|
|
468
|
+
}}
|
|
469
|
+
/>
|
|
470
|
+
) : null}
|
|
471
|
+
</ResourceLane>
|
|
472
|
+
|
|
473
|
+
<ResourceLane
|
|
474
|
+
title="Brand rules"
|
|
475
|
+
count={rules.length}
|
|
476
|
+
readOnly={readOnly}
|
|
477
|
+
foldable
|
|
478
|
+
actions={
|
|
479
|
+
readOnly ? null : (
|
|
480
|
+
<div className="resource-add-actions">
|
|
481
|
+
<button type="button" onClick={() => setForm(form === "guidance" ? null : "guidance")}>
|
|
482
|
+
+ Rule
|
|
483
|
+
</button>
|
|
484
|
+
</div>
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
>
|
|
488
|
+
{form === "guidance" ? (
|
|
489
|
+
<ResourceForm
|
|
490
|
+
kind="guidance"
|
|
491
|
+
onCancel={() => setForm(null)}
|
|
492
|
+
onSubmit={(label, value) => {
|
|
493
|
+
store.addBrandGuidance(label, value, "project");
|
|
494
|
+
setForm(null);
|
|
495
|
+
}}
|
|
496
|
+
/>
|
|
497
|
+
) : null}
|
|
498
|
+
{rules.length === 0 ? (
|
|
499
|
+
<div className="resource-empty">No brand rules yet.</div>
|
|
500
|
+
) : (
|
|
501
|
+
<ul className="resource-list" data-testid="assets-tab-rules">
|
|
502
|
+
{rules.map(brandRow)}
|
|
503
|
+
</ul>
|
|
504
|
+
)}
|
|
505
|
+
</ResourceLane>
|
|
506
|
+
|
|
507
|
+
{/* ONLY WHEN THERE IS SOMETHING IN IT. */}
|
|
508
|
+
{shot === undefined || shotRows.length === 0 ? null : (
|
|
509
|
+
<ResourceLane title="This shot only" count={shotRows.length} meta={shot.name} readOnly={readOnly} actions={null}>
|
|
510
|
+
<ul className="resource-list" data-testid="assets-tab-shot-only">
|
|
511
|
+
{shotRows.map(brandRow)}
|
|
512
|
+
</ul>
|
|
513
|
+
</ResourceLane>
|
|
514
|
+
)}
|
|
515
|
+
</div>
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* The dashed zone at the top of the tab: a picker for many files, a drop of
|
|
521
|
+
* many files, and a paste. Files upload one at a time, in order, each its own
|
|
522
|
+
* undo entry (`addAsset`). With no files, a page address is a Whole film link
|
|
523
|
+
* card. Our own drags (an asset, a board card) are not taken.
|
|
524
|
+
*/
|
|
525
|
+
export function AssetsDropZone() {
|
|
526
|
+
const store = useStore();
|
|
527
|
+
const picker = useRef<HTMLInputElement | null>(null);
|
|
528
|
+
const [uploading, setUploading] = useState<string | null>(null);
|
|
529
|
+
const [over, setOver] = useState(false);
|
|
530
|
+
|
|
531
|
+
const upload = async (picked: readonly File[]): Promise<void> => {
|
|
532
|
+
for (const file of picked) {
|
|
533
|
+
setUploading(file.name);
|
|
534
|
+
try {
|
|
535
|
+
await store.addAsset(file);
|
|
536
|
+
} catch (error) {
|
|
537
|
+
store.reportError(error instanceof Error ? error.message : String(error));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
setUploading(null);
|
|
541
|
+
};
|
|
542
|
+
const take = (transfer: DataTransfer): boolean => {
|
|
543
|
+
const types = Array.from(transfer.types ?? []);
|
|
544
|
+
if (types.includes(ASSET_MIME) || types.includes(STORYBOARD_MIME)) return false;
|
|
545
|
+
const picked = Array.from(transfer.files ?? []);
|
|
546
|
+
if (picked.length > 0) {
|
|
547
|
+
void upload(picked);
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
const url = urlFromTransfer(transfer);
|
|
551
|
+
if (url === null) return false;
|
|
552
|
+
store.addLinkReference(url, null, { select: false });
|
|
553
|
+
return true;
|
|
554
|
+
};
|
|
555
|
+
const accepts = (event: DragEvent<HTMLElement>): boolean => {
|
|
556
|
+
const types = Array.from(event.dataTransfer?.types ?? []);
|
|
557
|
+
if (types.includes(ASSET_MIME) || types.includes(STORYBOARD_MIME)) return false;
|
|
558
|
+
return types.includes("Files") || types.includes("text/uri-list") || types.includes("text/plain");
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
return (
|
|
562
|
+
<div
|
|
563
|
+
className={`assets-drop-zone${over ? " is-over" : ""}`}
|
|
564
|
+
data-testid="assets-drop-zone"
|
|
565
|
+
onDragOver={(event) => {
|
|
566
|
+
if (!accepts(event)) return;
|
|
567
|
+
event.preventDefault();
|
|
568
|
+
event.dataTransfer.dropEffect = "copy";
|
|
569
|
+
setOver(true);
|
|
570
|
+
}}
|
|
571
|
+
onDragLeave={() => setOver(false)}
|
|
572
|
+
onDrop={(event) => {
|
|
573
|
+
setOver(false);
|
|
574
|
+
if (!accepts(event)) return;
|
|
575
|
+
event.preventDefault();
|
|
576
|
+
// A drop is on purpose, so one that adds nothing says why. A paste
|
|
577
|
+
// that adds nothing stays quiet: it falls through to the field.
|
|
578
|
+
if (!take(event.dataTransfer)) store.reportError("Drop files here, or an https link.");
|
|
579
|
+
}}
|
|
580
|
+
onPaste={(event: ClipboardEvent<HTMLElement>) => {
|
|
581
|
+
if (take(event.clipboardData)) event.preventDefault();
|
|
582
|
+
}}
|
|
583
|
+
>
|
|
584
|
+
<input
|
|
585
|
+
ref={picker}
|
|
586
|
+
type="file"
|
|
587
|
+
hidden
|
|
588
|
+
multiple
|
|
589
|
+
accept={UPLOAD_ACCEPT}
|
|
590
|
+
data-testid="assets-drop-input"
|
|
591
|
+
onChange={(event) => {
|
|
592
|
+
const picked = Array.from(event.target.files ?? []);
|
|
593
|
+
event.target.value = "";
|
|
594
|
+
void upload(picked);
|
|
595
|
+
}}
|
|
596
|
+
/>
|
|
597
|
+
<button type="button" onClick={() => picker.current?.click()} disabled={uploading !== null}>
|
|
598
|
+
Add files
|
|
599
|
+
</button>
|
|
600
|
+
<span className="assets-drop-zone-hint">or drop files, or paste a link</span>
|
|
601
|
+
{uploading === null ? null : <span className="resource-pending">Uploading {uploading}…</span>}
|
|
602
|
+
</div>
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** A 100px tile's picture from the shared thumbnail cache (`useImageThumb`),
|
|
607
|
+
* never the full-size original decoded for a tile. */
|
|
608
|
+
function AssetThumb({ asset }: { asset: ShotAsset }) {
|
|
609
|
+
const thumb = useImageThumb(`/studio/assets/${encodeURIComponent(asset.path)}`, asset.hash, 100);
|
|
610
|
+
return thumb === null ? null : <img src={thumb} alt="" loading="lazy" draggable={false} />;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* What a file row says after its name: a video's or a sound's length
|
|
615
|
+
* (`formatDuration`), a font's family, a model's format.
|
|
616
|
+
*
|
|
617
|
+
* A length is the probe's (`media.durationSeconds`, written at import), else
|
|
618
|
+
* for a sound the decode the timeline shares (`loadPeaks`, one per path). Its
|
|
619
|
+
* 0 is "could not decode", which is not a length, so it prints nothing.
|
|
620
|
+
*/
|
|
621
|
+
function FileMeta({ doc, asset }: { doc: Doc3D; asset: ShotAsset }) {
|
|
622
|
+
const probed = asset.media?.durationSeconds;
|
|
623
|
+
const [decoded, setDecoded] = useState<number | null>(null);
|
|
624
|
+
useEffect(() => {
|
|
625
|
+
if (asset.kind !== "audio" || probed !== undefined) return;
|
|
626
|
+
let alive = true;
|
|
627
|
+
loadPeaks(asset)
|
|
628
|
+
.then((data) => {
|
|
629
|
+
if (alive && data.sourceSeconds > 0) setDecoded(data.sourceSeconds);
|
|
630
|
+
})
|
|
631
|
+
.catch(() => {});
|
|
632
|
+
return () => {
|
|
633
|
+
alive = false;
|
|
634
|
+
};
|
|
635
|
+
}, [asset, probed]);
|
|
636
|
+
|
|
637
|
+
let text: string | null = null;
|
|
638
|
+
if (asset.kind === "video" || asset.kind === "audio") {
|
|
639
|
+
const seconds = probed ?? decoded;
|
|
640
|
+
text = seconds === null ? null : formatDuration(seconds);
|
|
641
|
+
} else if (asset.kind === "font") {
|
|
642
|
+
text = fontFamilyOf(doc, asset);
|
|
643
|
+
} else if (asset.kind === "model") {
|
|
644
|
+
text = /\.([a-z0-9]+)$/i.exec(asset.path)?.[1]?.toUpperCase() ?? null;
|
|
645
|
+
}
|
|
646
|
+
return text === null ? null : <span className="assets-tab-meta">{text}</span>;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/** A brand row's family for this font, else the file's stem. */
|
|
650
|
+
function fontFamilyOf(doc: Doc3D, asset: ShotAsset): string {
|
|
651
|
+
const named = effectiveResources(doc, null).branding.find(
|
|
652
|
+
(item) => item.type === "asset" && item.asset === asset.id && item.fontFamily !== undefined,
|
|
653
|
+
);
|
|
654
|
+
if (named?.type === "asset" && named.fontFamily !== undefined) return named.fontFamily;
|
|
655
|
+
return fileNameOf(asset).replace(/\.[^.]+$/, "");
|
|
656
|
+
}
|