@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,723 @@
|
|
|
1
|
+
// THE INSERT DRAWER: everything an author can add, in the left column.
|
|
2
|
+
//
|
|
3
|
+
// ── WHERE IT LIVES ──────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// Docked in the left cell, over the outline (or a mode's column), never a
|
|
6
|
+
// modal and never floating over the stage. LeftColumn mounts it directly and
|
|
7
|
+
// marks the rail `is-inserting`, which hides the Files panel and the mode slot
|
|
8
|
+
// without unmounting them, so the outline keeps its scroll and open rows for
|
|
9
|
+
// the trip back. The dock's + (InsertButton), the Scene section's "Shader
|
|
10
|
+
// background…" and the first open of an empty Project all open the same
|
|
11
|
+
// drawer (`openInsertDrawer`).
|
|
12
|
+
//
|
|
13
|
+
// ── NO TABS ─────────────────────────────────────────────────────────────
|
|
14
|
+
//
|
|
15
|
+
// The left column has no tabs (a founder ruling), so the drawer is one search
|
|
16
|
+
// field, a row of jump chips and ONE scrolling list with sticky section heads.
|
|
17
|
+
// A chip scrolls the list; scrolling the list moves the chip.
|
|
18
|
+
//
|
|
19
|
+
// ── WHAT EACH SECTION READS ─────────────────────────────────────────────
|
|
20
|
+
//
|
|
21
|
+
// - Basics: `useAddRows`, the same rows calling the same `run` the Insert
|
|
22
|
+
// button uses. Nothing here knows what a shape or a device is.
|
|
23
|
+
// - Icons: Lucide, imported the first time the section is asked for
|
|
24
|
+
// (`loadIconSet`), so first paint pays nothing for 1850 icons.
|
|
25
|
+
// - Shaders: the registry's TSL effects (`row.effect`), grouped by the
|
|
26
|
+
// catalog's own category, in the order the catalog lists them.
|
|
27
|
+
// - Backgrounds: scene paints written to the Shot's `background` through the
|
|
28
|
+
// inspector's own seam (`patchScene`), and the effects tagged "ground".
|
|
29
|
+
//
|
|
30
|
+
// ── VIEWERS ─────────────────────────────────────────────────────────────
|
|
31
|
+
//
|
|
32
|
+
// Nothing renders for a viewer: the + is hidden too, and the store's set()
|
|
33
|
+
// seam closes the drawer when `readOnly` turns on.
|
|
34
|
+
import { createElement, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
35
|
+
import type { CSSProperties, ReactElement, ReactNode } from "react";
|
|
36
|
+
import { createPortal } from "react-dom";
|
|
37
|
+
import { paintCss } from "@dailies-studio/shot/doc";
|
|
38
|
+
import type { IconNode, Paint } from "@dailies-studio/shot/doc";
|
|
39
|
+
import type { ComponentRow } from "../../api.js";
|
|
40
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
41
|
+
import { INSERT_SECTIONS } from "../../state/types.js";
|
|
42
|
+
import type { InsertSection } from "../../state/types.js";
|
|
43
|
+
import { isSceneContext } from "../../state/viewMode.js";
|
|
44
|
+
import { sceneContextRefusal } from "../../state/keymap.js";
|
|
45
|
+
import { isolationInsertRefusal } from "../../state/isolationTrail.js";
|
|
46
|
+
import { ADD_FAMILIES, promotedBuiltinLabel } from "../../addFamilies.js";
|
|
47
|
+
import {
|
|
48
|
+
ADD_ICON,
|
|
49
|
+
CLOSE_ICON,
|
|
50
|
+
IMAGE_UP_ICON,
|
|
51
|
+
LIBRARY_ICON,
|
|
52
|
+
MORE_ICON,
|
|
53
|
+
SEARCH_ICON,
|
|
54
|
+
addRowIcon,
|
|
55
|
+
effectCategoryIcon,
|
|
56
|
+
} from "../../icons.js";
|
|
57
|
+
import { useAddRows } from "../toolbar/useAddRows.js";
|
|
58
|
+
import type { AddRow } from "../toolbar/useAddRows.js";
|
|
59
|
+
import { useDismiss } from "../toolbar/useDismiss.js";
|
|
60
|
+
import { useFixedVirtualWindow } from "../virtualWindow.js";
|
|
61
|
+
import { patchScene } from "../inspector/scene/SceneSection.js";
|
|
62
|
+
import { loadIconSet, searchIcons } from "./iconSet.js";
|
|
63
|
+
import type { IconEntry } from "./iconSet.js";
|
|
64
|
+
|
|
65
|
+
/** One icon cell (32) and the grid's gap (2). */
|
|
66
|
+
const ICON_PITCH = 34;
|
|
67
|
+
/** The icon grid's own scroller shows this many rows. */
|
|
68
|
+
const ICON_ROWS_SHOWN = 7;
|
|
69
|
+
/** Columns before the grid has been measured (jsdom, the first frame). */
|
|
70
|
+
const ICON_COLS_FALLBACK = 6;
|
|
71
|
+
|
|
72
|
+
/** The effects a Backgrounds list offers: the catalog tags them "ground". */
|
|
73
|
+
const GROUND_TAG = "ground";
|
|
74
|
+
|
|
75
|
+
/** The scene paint presets. Solids first, then the gradients, as drawn. */
|
|
76
|
+
export const SCENE_PAINTS: readonly Paint[] = [
|
|
77
|
+
{ type: "solid", color: "#000000" },
|
|
78
|
+
{ type: "solid", color: "#0a0a0a" },
|
|
79
|
+
{ type: "solid", color: "#0e1a2b" },
|
|
80
|
+
{ type: "solid", color: "#f5f5f5" },
|
|
81
|
+
{ type: "solid", color: "#ff4d1f" },
|
|
82
|
+
{ type: "solid", color: "#0099ff" },
|
|
83
|
+
{ type: "linear-gradient", angleDeg: 135, stops: [{ at: 0, color: "#1b2a4a" }, { at: 1, color: "#0a0a0a" }] },
|
|
84
|
+
{ type: "linear-gradient", angleDeg: 135, stops: [{ at: 0, color: "#ff8a3d" }, { at: 1, color: "#7a2bff" }] },
|
|
85
|
+
{ type: "linear-gradient", angleDeg: 180, stops: [{ at: 0, color: "#f5f5f5" }, { at: 1, color: "#bfc6d1" }] },
|
|
86
|
+
{ type: "radial-gradient", stops: [{ at: 0, color: "#2b3a67" }, { at: 1, color: "#05070d" }] },
|
|
87
|
+
{ type: "radial-gradient", stops: [{ at: 0, color: "#ffe1c2" }, { at: 1, color: "#ff5c69" }] },
|
|
88
|
+
{ type: "linear-gradient", angleDeg: 135, stops: [{ at: 0, color: "#2fd4bf" }, { at: 1, color: "#0e1a2b" }] },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
/** The swatch side the stylesheet gives a swatch (insert-drawer.css). */
|
|
92
|
+
const SWATCH_PX = 32;
|
|
93
|
+
/** The frame before a document is open. */
|
|
94
|
+
const SQUARE_FRAME = { width: 1, height: 1 } as const;
|
|
95
|
+
|
|
96
|
+
/** A scene paint swatch: the Project frame in small, so it shows what the
|
|
97
|
+
* stage draws over that frame. The box is the frame's aspect inside the
|
|
98
|
+
* stylesheet's square, and the paint is `paintCss` over the frame, the
|
|
99
|
+
* stage's own geometry (`paintCss` reads only the aspect). A raw
|
|
100
|
+
* `${angleDeg}deg` read the angle as CSS's, so a 135 preset ran top-left to
|
|
101
|
+
* bottom-right here and the other way on stage, and a square box showed a
|
|
102
|
+
* diagonal at the wrong angle on any frame that is not square. */
|
|
103
|
+
function swatchStyle(paint: Paint, frame: { width: number; height: number }): CSSProperties {
|
|
104
|
+
const { width, height } = frame;
|
|
105
|
+
const wide = width >= height;
|
|
106
|
+
return {
|
|
107
|
+
width: wide ? SWATCH_PX : (SWATCH_PX * width) / height,
|
|
108
|
+
height: wide ? (SWATCH_PX * height) / width : SWATCH_PX,
|
|
109
|
+
background: paintCss(paint, width, height),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function paintName(paint: Paint): string {
|
|
114
|
+
if (paint.type === "solid") return paint.color;
|
|
115
|
+
const stops = paint.stops.map((s) => s.color).join(" to ");
|
|
116
|
+
return `${paint.type === "linear-gradient" ? "Linear" : "Radial"} ${stops}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const samePaint = (a: Paint | undefined, b: Paint): boolean => JSON.stringify(a) === JSON.stringify(b);
|
|
120
|
+
|
|
121
|
+
/** Every word of the query appears in the text. */
|
|
122
|
+
function matches(text: string, query: string): boolean {
|
|
123
|
+
const q = query.trim().toLowerCase();
|
|
124
|
+
if (q === "") return true;
|
|
125
|
+
const hay = text.toLowerCase();
|
|
126
|
+
return q.split(/\s+/).every((word) => hay.includes(word));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A Basics tile's caption: the drawer's own short words for two rows. */
|
|
130
|
+
function captionOf(row: AddRow): string {
|
|
131
|
+
if (row.key === "media") return "Media…";
|
|
132
|
+
if (row.key === "html") return "Html";
|
|
133
|
+
return row.label;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const capitalise = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
|
|
137
|
+
|
|
138
|
+
/** Effect rows grouped by catalog category, in first-appearance order. */
|
|
139
|
+
function byCategory(rows: readonly ComponentRow[]): Array<{ category: string; rows: ComponentRow[] }> {
|
|
140
|
+
const groups = new Map<string, ComponentRow[]>();
|
|
141
|
+
for (const row of rows) {
|
|
142
|
+
const category = row.effect?.category ?? "other";
|
|
143
|
+
const list = groups.get(category);
|
|
144
|
+
if (list === undefined) groups.set(category, [row]);
|
|
145
|
+
else list.push(row);
|
|
146
|
+
}
|
|
147
|
+
return [...groups].map(([category, list]) => ({ category, rows: list }));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function InsertDrawer() {
|
|
151
|
+
const open = useShotEditor((s) => s.insertDrawer !== null && !s.readOnly);
|
|
152
|
+
return open ? <Drawer /> : null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Drawer() {
|
|
156
|
+
const store = useStore();
|
|
157
|
+
const drawer = useShotEditor((s) => s.insertDrawer);
|
|
158
|
+
const above = useShotEditor((s) => s.doc === null || isSceneContext(s));
|
|
159
|
+
const library = useShotEditor((s) => s.library);
|
|
160
|
+
const searchFocus = useShotEditor((s) => s.searchFocus);
|
|
161
|
+
const { rows, busy, fileInput } = useAddRows({ wantLibrary: true, claimImagePick: false });
|
|
162
|
+
const [query, setQuery] = useState("");
|
|
163
|
+
const [active, setActive] = useState<InsertSection>(drawer?.section ?? "Basics");
|
|
164
|
+
const [menu, setMenu] = useState<{ id: string; left: number; top: number } | null>(null);
|
|
165
|
+
const listRef = useRef<HTMLDivElement | null>(null);
|
|
166
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
167
|
+
const sectionRefs = useRef(new Map<InsertSection, HTMLElement>());
|
|
168
|
+
|
|
169
|
+
const contextRefusal = above ? sceneContextRefusal("Insert", "adds") : null;
|
|
170
|
+
// Every tile here adds a layer, which a leaf isolation cannot hold. Scene
|
|
171
|
+
// paint is the exception: it sets the Shot's background, not a layer.
|
|
172
|
+
const leafRefusal = useShotEditor(isolationInsertRefusal);
|
|
173
|
+
const refusal = contextRefusal ?? leafRefusal;
|
|
174
|
+
const searching = query.trim() !== "";
|
|
175
|
+
|
|
176
|
+
// `/` (focusSearch) puts the caret in the drawer's field while it is open.
|
|
177
|
+
// The outline's own field is hidden under it, so its focus is a no-op.
|
|
178
|
+
const firstFocus = useRef(searchFocus);
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (searchFocus !== firstFocus.current) inputRef.current?.focus();
|
|
181
|
+
}, [searchFocus]);
|
|
182
|
+
|
|
183
|
+
// A chip, the inspector's button or an open at a section: scroll to it. Keyed
|
|
184
|
+
// on the drawer OBJECT, which every open makes anew, so asking for the
|
|
185
|
+
// section already shown still scrolls back to its head.
|
|
186
|
+
useLayoutEffect(() => {
|
|
187
|
+
if (drawer === null) return;
|
|
188
|
+
setActive(drawer.section);
|
|
189
|
+
const list = listRef.current;
|
|
190
|
+
const head = sectionRefs.current.get(drawer.section);
|
|
191
|
+
if (list === null || head === undefined) return;
|
|
192
|
+
list.scrollTop = head.offsetTop;
|
|
193
|
+
}, [drawer]);
|
|
194
|
+
|
|
195
|
+
const onScroll = (): void => {
|
|
196
|
+
const list = listRef.current;
|
|
197
|
+
if (list === null) return;
|
|
198
|
+
const atEnd = list.scrollTop + list.clientHeight >= list.scrollHeight - 1;
|
|
199
|
+
let next: InsertSection = "Basics";
|
|
200
|
+
for (const section of INSERT_SECTIONS) {
|
|
201
|
+
const head = sectionRefs.current.get(section);
|
|
202
|
+
if (head !== undefined && (atEnd || head.offsetTop <= list.scrollTop + 1)) next = section;
|
|
203
|
+
}
|
|
204
|
+
setActive(next);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const sectionRef = (section: InsertSection) => (el: HTMLElement | null) => {
|
|
208
|
+
if (el === null) sectionRefs.current.delete(section);
|
|
209
|
+
else sectionRefs.current.set(section, el);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// ── the rows ───────────────────────────────────────────────────────────
|
|
213
|
+
const basics = rows.filter((r) => r.section !== "Effects" && matches(r.label, query));
|
|
214
|
+
const effects = useMemo(
|
|
215
|
+
() => library.filter((r) => r.host === "three" && r.effect !== undefined && promotedBuiltinLabel(r.id) === null),
|
|
216
|
+
[library],
|
|
217
|
+
);
|
|
218
|
+
const effectText = (r: ComponentRow): string => `${r.name} ${r.effect?.category ?? ""} ${(r.effect?.tags ?? []).join(" ")}`;
|
|
219
|
+
const shaders = effects.filter((r) => !(r.effect?.tags.includes(GROUND_TAG) ?? false) && matches(effectText(r), query));
|
|
220
|
+
const grounds = effects.filter((r) => (r.effect?.tags.includes(GROUND_TAG) ?? false) && matches(effectText(r), query));
|
|
221
|
+
const paints = searching ? [] : SCENE_PAINTS;
|
|
222
|
+
|
|
223
|
+
const report = (e: unknown): void => store.reportError(e instanceof Error ? e.message : String(e));
|
|
224
|
+
const addEffect = (id: string): void => void store.addInstanceLayer(id).catch(report);
|
|
225
|
+
const useAsBackground = (id: string): void => void store.useAsBackground(id).catch(report);
|
|
226
|
+
|
|
227
|
+
// The "…" closes the menu on its own press (useDismiss sees a press outside
|
|
228
|
+
// the menu), so the click that follows must not open it straight back.
|
|
229
|
+
const closed = useRef<{ id: string; at: number } | null>(null);
|
|
230
|
+
const openMenu = (id: string, tile: HTMLElement): void => {
|
|
231
|
+
const last = closed.current;
|
|
232
|
+
if (last !== null && last.id === id && performance.now() - last.at < 400) return;
|
|
233
|
+
const box = tile.getBoundingClientRect();
|
|
234
|
+
setMenu({ id, left: box.right + 4, top: Math.max(8, Math.min(box.top, window.innerHeight - 160)) });
|
|
235
|
+
};
|
|
236
|
+
const closeMenu = (): void => {
|
|
237
|
+
if (menu !== null) closed.current = { id: menu.id, at: performance.now() };
|
|
238
|
+
setMenu(null);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const effectTile = (row: ComponentRow) => (
|
|
242
|
+
<ShaderTile
|
|
243
|
+
key={row.id}
|
|
244
|
+
row={row}
|
|
245
|
+
refusal={refusal}
|
|
246
|
+
menuOpen={menu?.id === row.id}
|
|
247
|
+
onAdd={() => addEffect(row.id)}
|
|
248
|
+
onMenu={(tile) => openMenu(row.id, tile)}
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<section className="insert-drawer" aria-label="Insert">
|
|
254
|
+
<header className="insert-head">
|
|
255
|
+
<span className="insert-title">Insert</span>
|
|
256
|
+
<kbd className="insert-hint">Esc</kbd>
|
|
257
|
+
<button
|
|
258
|
+
type="button"
|
|
259
|
+
className="insert-close"
|
|
260
|
+
aria-label="Close Insert"
|
|
261
|
+
title="Close (Esc)"
|
|
262
|
+
onClick={() => store.closeInsertDrawer()}
|
|
263
|
+
>
|
|
264
|
+
{CLOSE_ICON}
|
|
265
|
+
</button>
|
|
266
|
+
</header>
|
|
267
|
+
<div className="insert-search-row">
|
|
268
|
+
<label className={`insert-search${searching ? " has-query" : ""}`}>
|
|
269
|
+
<span className="insert-search-icon" aria-hidden="true">
|
|
270
|
+
{SEARCH_ICON}
|
|
271
|
+
</span>
|
|
272
|
+
<input
|
|
273
|
+
ref={inputRef}
|
|
274
|
+
type="search"
|
|
275
|
+
className="insert-search-input"
|
|
276
|
+
placeholder="Search"
|
|
277
|
+
aria-label="Search everything you can insert"
|
|
278
|
+
value={query}
|
|
279
|
+
onChange={(e) => setQuery(e.currentTarget.value)}
|
|
280
|
+
onKeyDown={(e) => {
|
|
281
|
+
if (e.key !== "Escape") return;
|
|
282
|
+
// The field's Escape: a query clears first, then the drawer
|
|
283
|
+
// closes. Stopped here so no stage lane sees it.
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
e.stopPropagation();
|
|
286
|
+
if (query !== "") setQuery("");
|
|
287
|
+
else store.closeInsertDrawer();
|
|
288
|
+
}}
|
|
289
|
+
/>
|
|
290
|
+
{searching ? (
|
|
291
|
+
<button type="button" className="insert-search-clear" aria-label="Clear search" onClick={() => setQuery("")}>
|
|
292
|
+
{CLOSE_ICON}
|
|
293
|
+
</button>
|
|
294
|
+
) : (
|
|
295
|
+
<kbd className="insert-hint">/</kbd>
|
|
296
|
+
)}
|
|
297
|
+
</label>
|
|
298
|
+
</div>
|
|
299
|
+
<nav className="insert-chips" aria-label="Jump to a section">
|
|
300
|
+
{INSERT_SECTIONS.map((section) => (
|
|
301
|
+
<button
|
|
302
|
+
key={section}
|
|
303
|
+
type="button"
|
|
304
|
+
className={`insert-chip${active === section ? " is-active" : ""}`}
|
|
305
|
+
aria-current={active === section ? "true" : undefined}
|
|
306
|
+
onClick={() => store.openInsertDrawer(section)}
|
|
307
|
+
>
|
|
308
|
+
{section}
|
|
309
|
+
</button>
|
|
310
|
+
))}
|
|
311
|
+
</nav>
|
|
312
|
+
<div ref={listRef} className="insert-list" onScroll={onScroll}>
|
|
313
|
+
<Section name="Basics" count={basics.length} sectionRef={sectionRef("Basics")}>
|
|
314
|
+
{ADD_FAMILIES.map((family) => {
|
|
315
|
+
const members = basics.filter((r) => r.family === family);
|
|
316
|
+
if (members.length === 0) return null;
|
|
317
|
+
return (
|
|
318
|
+
<div key={family} className="insert-family">
|
|
319
|
+
<div className="insert-sub">{family}</div>
|
|
320
|
+
<div className="insert-tiles">
|
|
321
|
+
{members.map((row) => {
|
|
322
|
+
const why =
|
|
323
|
+
refusal ?? (row.disabled === true ? (row.title ?? row.label) : null) ??
|
|
324
|
+
(busy && (row.key === "media" || row.key === "mesh") ? "Waiting for the last file to finish importing" : null);
|
|
325
|
+
return (
|
|
326
|
+
<button
|
|
327
|
+
key={row.key}
|
|
328
|
+
type="button"
|
|
329
|
+
className="insert-tile"
|
|
330
|
+
disabled={why !== null}
|
|
331
|
+
title={why ?? row.title ?? row.label}
|
|
332
|
+
onClick={row.run}
|
|
333
|
+
>
|
|
334
|
+
<span className="insert-tile-icon" aria-hidden="true">
|
|
335
|
+
{addRowIcon(row.key)}
|
|
336
|
+
</span>
|
|
337
|
+
<span className="insert-tile-label">{captionOf(row)}</span>
|
|
338
|
+
</button>
|
|
339
|
+
);
|
|
340
|
+
})}
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
})}
|
|
345
|
+
</Section>
|
|
346
|
+
<IconsSection
|
|
347
|
+
query={query}
|
|
348
|
+
asked={drawer?.section === "Icons"}
|
|
349
|
+
refusal={refusal}
|
|
350
|
+
listRef={listRef}
|
|
351
|
+
sectionRef={sectionRef("Icons")}
|
|
352
|
+
/>
|
|
353
|
+
{/* NOT VIRTUALISED, unlike Icons: the catalog's three-host effects
|
|
354
|
+
are a few dozen tiles, and each thumb is `loading="lazy"`, so the
|
|
355
|
+
browser fetches only the ones scrolled near. A windowed grid is
|
|
356
|
+
for the 1850 icons. Revisit if the registry grows into hundreds. */}
|
|
357
|
+
<Section name="Shaders" count={shaders.length} sectionRef={sectionRef("Shaders")}>
|
|
358
|
+
{byCategory(shaders).map((group) => (
|
|
359
|
+
<div key={group.category} className="insert-family">
|
|
360
|
+
<div className="insert-sub">
|
|
361
|
+
{capitalise(group.category)}
|
|
362
|
+
<span className="insert-count">{group.rows.length}</span>
|
|
363
|
+
</div>
|
|
364
|
+
<div className="insert-tiles">{group.rows.map(effectTile)}</div>
|
|
365
|
+
</div>
|
|
366
|
+
))}
|
|
367
|
+
{searching ? null : (
|
|
368
|
+
<button type="button" className="insert-link" onClick={() => store.setLibraryBrowserOpen(true)}>
|
|
369
|
+
<span className="insert-link-icon" aria-hidden="true">
|
|
370
|
+
{LIBRARY_ICON}
|
|
371
|
+
</span>
|
|
372
|
+
Materials and blueprints are in the Library
|
|
373
|
+
</button>
|
|
374
|
+
)}
|
|
375
|
+
</Section>
|
|
376
|
+
<Section name="Backgrounds" count={paints.length + grounds.length} sectionRef={sectionRef("Backgrounds")}>
|
|
377
|
+
{paints.length === 0 ? null : <ScenePaints refusal={contextRefusal} />}
|
|
378
|
+
{grounds.length === 0 ? null : (
|
|
379
|
+
<div className="insert-family">
|
|
380
|
+
<div className="insert-sub">
|
|
381
|
+
Shader grounds
|
|
382
|
+
<span className="insert-count">{grounds.length}</span>
|
|
383
|
+
</div>
|
|
384
|
+
<div className="insert-tiles">{grounds.map(effectTile)}</div>
|
|
385
|
+
</div>
|
|
386
|
+
)}
|
|
387
|
+
</Section>
|
|
388
|
+
{searching ? (
|
|
389
|
+
<p className="insert-footnote">Search looks in every section. A section with no match stays as one line.</p>
|
|
390
|
+
) : null}
|
|
391
|
+
</div>
|
|
392
|
+
{menu === null ? null : (
|
|
393
|
+
<TileMenu
|
|
394
|
+
left={menu.left}
|
|
395
|
+
top={menu.top}
|
|
396
|
+
onClose={closeMenu}
|
|
397
|
+
onAdd={() => {
|
|
398
|
+
closeMenu();
|
|
399
|
+
addEffect(menu.id);
|
|
400
|
+
}}
|
|
401
|
+
onBackground={() => {
|
|
402
|
+
closeMenu();
|
|
403
|
+
useAsBackground(menu.id);
|
|
404
|
+
}}
|
|
405
|
+
/>
|
|
406
|
+
)}
|
|
407
|
+
{fileInput}
|
|
408
|
+
</section>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function Section(props: {
|
|
413
|
+
name: InsertSection;
|
|
414
|
+
count: number | null;
|
|
415
|
+
sectionRef: (el: HTMLElement | null) => void;
|
|
416
|
+
children?: ReactNode;
|
|
417
|
+
}) {
|
|
418
|
+
return (
|
|
419
|
+
<section ref={props.sectionRef} className="insert-section" aria-label={props.name} data-section={props.name}>
|
|
420
|
+
<div className="insert-section-head">
|
|
421
|
+
<span className="insert-section-label">{props.name}</span>
|
|
422
|
+
{props.count === null ? null : <span className="insert-count">{props.count}</span>}
|
|
423
|
+
</div>
|
|
424
|
+
{props.count === 0 ? null : props.children}
|
|
425
|
+
</section>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** A shader tile: its thumb (or its category's glyph), its name, and a "…"
|
|
430
|
+
* that offers "Use as background". A click adds it to the shot. */
|
|
431
|
+
function ShaderTile(props: {
|
|
432
|
+
row: ComponentRow;
|
|
433
|
+
refusal: string | null;
|
|
434
|
+
menuOpen: boolean;
|
|
435
|
+
onAdd: () => void;
|
|
436
|
+
onMenu: (tile: HTMLElement) => void;
|
|
437
|
+
}) {
|
|
438
|
+
const { row } = props;
|
|
439
|
+
const [broken, setBroken] = useState(false);
|
|
440
|
+
const thumb = row.effect?.thumb;
|
|
441
|
+
return (
|
|
442
|
+
<div className={`insert-shader${props.menuOpen ? " is-menu-open" : ""}`}>
|
|
443
|
+
<button
|
|
444
|
+
type="button"
|
|
445
|
+
className="insert-shader-add"
|
|
446
|
+
disabled={props.refusal !== null}
|
|
447
|
+
title={props.refusal ?? row.name}
|
|
448
|
+
onClick={props.onAdd}
|
|
449
|
+
>
|
|
450
|
+
<span className="insert-thumb">
|
|
451
|
+
{thumb === undefined || broken ? (
|
|
452
|
+
<span className="insert-thumb-glyph" aria-hidden="true">
|
|
453
|
+
{effectCategoryIcon(row.effect?.category ?? "")}
|
|
454
|
+
</span>
|
|
455
|
+
) : (
|
|
456
|
+
<img src={thumb} alt="" loading="lazy" decoding="async" onError={() => setBroken(true)} />
|
|
457
|
+
)}
|
|
458
|
+
</span>
|
|
459
|
+
<span className="insert-shader-label">{row.name}</span>
|
|
460
|
+
</button>
|
|
461
|
+
<button
|
|
462
|
+
type="button"
|
|
463
|
+
className="insert-shader-more"
|
|
464
|
+
aria-label={`More for ${row.name}`}
|
|
465
|
+
aria-haspopup="menu"
|
|
466
|
+
aria-expanded={props.menuOpen}
|
|
467
|
+
disabled={props.refusal !== null}
|
|
468
|
+
onClick={(e) => props.onMenu(e.currentTarget.parentElement ?? e.currentTarget)}
|
|
469
|
+
>
|
|
470
|
+
{MORE_ICON}
|
|
471
|
+
</button>
|
|
472
|
+
</div>
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** The shader tile's menu. Portalled to the body, at the tile's right edge,
|
|
477
|
+
* so the left rail's stacking context cannot paint it under the canvas. */
|
|
478
|
+
function TileMenu(props: {
|
|
479
|
+
left: number;
|
|
480
|
+
top: number;
|
|
481
|
+
onClose: () => void;
|
|
482
|
+
onAdd: () => void;
|
|
483
|
+
onBackground: () => void;
|
|
484
|
+
}) {
|
|
485
|
+
const ref = useRef<HTMLDivElement | null>(null);
|
|
486
|
+
useDismiss(true, ref, props.onClose);
|
|
487
|
+
// The first row takes focus, so Enter adds (the row's own hint says so).
|
|
488
|
+
useLayoutEffect(() => ref.current?.querySelector("button")?.focus(), []);
|
|
489
|
+
return createPortal(
|
|
490
|
+
<div ref={ref} className="insert-menu" role="menu" style={{ left: props.left, top: props.top }}>
|
|
491
|
+
<button type="button" role="menuitem" className="insert-menu-row" onClick={props.onAdd}>
|
|
492
|
+
<span className="insert-menu-icon" aria-hidden="true">
|
|
493
|
+
{ADD_ICON}
|
|
494
|
+
</span>
|
|
495
|
+
<span className="insert-menu-label">Add to shot</span>
|
|
496
|
+
<kbd className="insert-hint">Enter</kbd>
|
|
497
|
+
</button>
|
|
498
|
+
<button type="button" role="menuitem" className="insert-menu-row is-background" onClick={props.onBackground}>
|
|
499
|
+
<span className="insert-menu-icon" aria-hidden="true">
|
|
500
|
+
{IMAGE_UP_ICON}
|
|
501
|
+
</span>
|
|
502
|
+
<span className="insert-menu-label">Use as background</span>
|
|
503
|
+
</button>
|
|
504
|
+
<div className="insert-menu-divider" role="separator" />
|
|
505
|
+
<p className="insert-menu-note">Places it at the back, sized to the frame, locked and named Background. One undo.</p>
|
|
506
|
+
</div>,
|
|
507
|
+
document.body,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** The Shot's background as presets: a click writes `background` through the
|
|
512
|
+
* inspector's seam, so an animated background takes a key at the playhead. */
|
|
513
|
+
function ScenePaints(props: { refusal: string | null }) {
|
|
514
|
+
const store = useStore();
|
|
515
|
+
const sceneId = useShotEditor((s) => s.enteredScene);
|
|
516
|
+
const current = useShotEditor((s) =>
|
|
517
|
+
s.enteredScene === null ? undefined : s.doc?.scenesById[s.enteredScene]?.background,
|
|
518
|
+
);
|
|
519
|
+
const frame = useShotEditor((s) => s.doc?.size) ?? SQUARE_FRAME;
|
|
520
|
+
const why = props.refusal ?? (sceneId === null ? sceneContextRefusal("Scene paint", "sets") : null);
|
|
521
|
+
return (
|
|
522
|
+
<div className="insert-family">
|
|
523
|
+
<div className="insert-sub">
|
|
524
|
+
Scene paint
|
|
525
|
+
<span className="insert-count">solid · gradient</span>
|
|
526
|
+
</div>
|
|
527
|
+
<div className="insert-swatches">
|
|
528
|
+
{SCENE_PAINTS.map((paint) => {
|
|
529
|
+
const on = samePaint(current, paint);
|
|
530
|
+
return (
|
|
531
|
+
<button
|
|
532
|
+
key={paintName(paint)}
|
|
533
|
+
type="button"
|
|
534
|
+
className={`insert-swatch${on ? " is-active" : ""}`}
|
|
535
|
+
style={swatchStyle(paint, frame)}
|
|
536
|
+
aria-pressed={on}
|
|
537
|
+
aria-label={paintName(paint)}
|
|
538
|
+
title={why ?? paintName(paint)}
|
|
539
|
+
disabled={why !== null}
|
|
540
|
+
onClick={() => {
|
|
541
|
+
if (sceneId === null) return;
|
|
542
|
+
store.edit((d) => patchScene(d, sceneId, { background: paint }, store.getState().frame));
|
|
543
|
+
}}
|
|
544
|
+
/>
|
|
545
|
+
);
|
|
546
|
+
})}
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** One Lucide icon, drawn from its nodes with `createElement`: no innerHTML. */
|
|
553
|
+
function IconGlyph(props: { node: readonly IconNode[] }): ReactElement {
|
|
554
|
+
return createElement(
|
|
555
|
+
"svg",
|
|
556
|
+
{
|
|
557
|
+
viewBox: "0 0 24 24",
|
|
558
|
+
fill: "none",
|
|
559
|
+
stroke: "currentColor",
|
|
560
|
+
strokeWidth: 2,
|
|
561
|
+
strokeLinecap: "round",
|
|
562
|
+
strokeLinejoin: "round",
|
|
563
|
+
"aria-hidden": true,
|
|
564
|
+
},
|
|
565
|
+
...props.node.map(([tag, attrs], index) => createElement(tag, { key: index, ...attrs })),
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* The Icons section. Lucide loads the first time the section is ASKED FOR:
|
|
571
|
+
* scrolled into view, jumped to, or searched. Its grid is its own scroller,
|
|
572
|
+
* seven rows tall, and only the rows near its viewport are mounted.
|
|
573
|
+
*/
|
|
574
|
+
function IconsSection(props: {
|
|
575
|
+
query: string;
|
|
576
|
+
asked: boolean;
|
|
577
|
+
refusal: string | null;
|
|
578
|
+
listRef: React.RefObject<HTMLDivElement | null>;
|
|
579
|
+
sectionRef: (el: HTMLElement | null) => void;
|
|
580
|
+
}) {
|
|
581
|
+
const store = useStore();
|
|
582
|
+
const [set, setSet] = useState<readonly IconEntry[] | null>(null);
|
|
583
|
+
const [failed, setFailed] = useState(false);
|
|
584
|
+
const [wanted, setWanted] = useState(false);
|
|
585
|
+
const own = useRef<HTMLElement | null>(null);
|
|
586
|
+
|
|
587
|
+
const want = props.asked || props.query.trim() !== "" || wanted;
|
|
588
|
+
|
|
589
|
+
// Scrolled into view: the list is the root, so a drawer that opens at
|
|
590
|
+
// Basics does not load the set until the author scrolls down to it.
|
|
591
|
+
useEffect(() => {
|
|
592
|
+
const el = own.current;
|
|
593
|
+
const root = props.listRef.current;
|
|
594
|
+
if (wanted || el === null || root === null || typeof IntersectionObserver === "undefined") return;
|
|
595
|
+
const observer = new IntersectionObserver(
|
|
596
|
+
(entries) => {
|
|
597
|
+
if (entries.some((e) => e.isIntersecting)) setWanted(true);
|
|
598
|
+
},
|
|
599
|
+
{ root },
|
|
600
|
+
);
|
|
601
|
+
observer.observe(el);
|
|
602
|
+
return () => observer.disconnect();
|
|
603
|
+
}, [wanted, props.listRef]);
|
|
604
|
+
|
|
605
|
+
const [attempt, setAttempt] = useState(0);
|
|
606
|
+
useEffect(() => {
|
|
607
|
+
if (!want || set !== null) return;
|
|
608
|
+
let live = true;
|
|
609
|
+
setFailed(false);
|
|
610
|
+
loadIconSet().then(
|
|
611
|
+
(loaded) => {
|
|
612
|
+
if (live) setSet(loaded);
|
|
613
|
+
},
|
|
614
|
+
() => {
|
|
615
|
+
if (live) setFailed(true);
|
|
616
|
+
},
|
|
617
|
+
);
|
|
618
|
+
return () => {
|
|
619
|
+
live = false;
|
|
620
|
+
};
|
|
621
|
+
}, [want, set, attempt]);
|
|
622
|
+
|
|
623
|
+
const found = useMemo(() => (set === null ? null : searchIcons(set, props.query)), [set, props.query]);
|
|
624
|
+
|
|
625
|
+
return (
|
|
626
|
+
<section
|
|
627
|
+
ref={(el) => {
|
|
628
|
+
own.current = el;
|
|
629
|
+
props.sectionRef(el);
|
|
630
|
+
}}
|
|
631
|
+
className="insert-section"
|
|
632
|
+
aria-label="Icons"
|
|
633
|
+
data-section="Icons"
|
|
634
|
+
>
|
|
635
|
+
<div className="insert-section-head">
|
|
636
|
+
<span className="insert-section-label">Icons</span>
|
|
637
|
+
{found === null ? null : <span className="insert-count">{found.length}</span>}
|
|
638
|
+
</div>
|
|
639
|
+
{found !== null ? (
|
|
640
|
+
found.length === 0 ? null : (
|
|
641
|
+
<IconGrid icons={found} refusal={props.refusal} onPick={(e) => store.addIconLayer(e.name, e.node)} />
|
|
642
|
+
)
|
|
643
|
+
) : failed ? (
|
|
644
|
+
<div className="insert-status">
|
|
645
|
+
Icons did not load.
|
|
646
|
+
<button type="button" className="insert-retry" onClick={() => setAttempt((n) => n + 1)}>
|
|
647
|
+
Retry
|
|
648
|
+
</button>
|
|
649
|
+
</div>
|
|
650
|
+
) : want ? (
|
|
651
|
+
<div className="insert-status">Loading icons…</div>
|
|
652
|
+
) : null}
|
|
653
|
+
</section>
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function IconGrid(props: { icons: readonly IconEntry[]; refusal: string | null; onPick: (entry: IconEntry) => void }) {
|
|
658
|
+
const scrollRef = useRef<HTMLDivElement | null>(null);
|
|
659
|
+
const gridRef = useRef<HTMLDivElement | null>(null);
|
|
660
|
+
const [cols, setCols] = useState(ICON_COLS_FALLBACK);
|
|
661
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
662
|
+
|
|
663
|
+
// Columns from the scroller's width: a wider rail fits more icons a row.
|
|
664
|
+
useLayoutEffect(() => {
|
|
665
|
+
const el = scrollRef.current;
|
|
666
|
+
if (el === null) return;
|
|
667
|
+
const measure = (): void => {
|
|
668
|
+
const width = el.clientWidth;
|
|
669
|
+
setCols(width > 0 ? Math.max(1, Math.floor((width + 2) / ICON_PITCH)) : ICON_COLS_FALLBACK);
|
|
670
|
+
};
|
|
671
|
+
measure();
|
|
672
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
673
|
+
const observer = new ResizeObserver(measure);
|
|
674
|
+
observer.observe(el);
|
|
675
|
+
return () => observer.disconnect();
|
|
676
|
+
}, []);
|
|
677
|
+
|
|
678
|
+
const rowCount = Math.ceil(props.icons.length / cols);
|
|
679
|
+
const range = useFixedVirtualWindow({
|
|
680
|
+
listRef: gridRef,
|
|
681
|
+
scrollSelector: ".insert-icons",
|
|
682
|
+
count: rowCount,
|
|
683
|
+
rowHeight: ICON_PITCH,
|
|
684
|
+
enabled: true,
|
|
685
|
+
});
|
|
686
|
+
const seenRows = Math.ceil((scrollTop + ICON_ROWS_SHOWN * ICON_PITCH) / ICON_PITCH);
|
|
687
|
+
const below = Math.max(0, props.icons.length - seenRows * cols);
|
|
688
|
+
|
|
689
|
+
const rows: ReactElement[] = [];
|
|
690
|
+
for (let r = range.start; r < range.end; r += 1) {
|
|
691
|
+
rows.push(
|
|
692
|
+
<div key={r} className="insert-icon-row">
|
|
693
|
+
{props.icons.slice(r * cols, r * cols + cols).map((entry) => (
|
|
694
|
+
<button
|
|
695
|
+
key={entry.name}
|
|
696
|
+
type="button"
|
|
697
|
+
className="insert-icon"
|
|
698
|
+
data-name={entry.name}
|
|
699
|
+
aria-label={entry.name}
|
|
700
|
+
title={props.refusal ?? entry.name}
|
|
701
|
+
disabled={props.refusal !== null}
|
|
702
|
+
onClick={() => props.onPick(entry)}
|
|
703
|
+
>
|
|
704
|
+
<IconGlyph node={entry.node} />
|
|
705
|
+
</button>
|
|
706
|
+
))}
|
|
707
|
+
</div>,
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return (
|
|
712
|
+
<>
|
|
713
|
+
<div ref={scrollRef} className="insert-icons" onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}>
|
|
714
|
+
<div ref={gridRef} className="insert-icon-grid">
|
|
715
|
+
<div style={{ height: range.before }} />
|
|
716
|
+
{rows}
|
|
717
|
+
<div style={{ height: range.after }} />
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
{below === 0 ? null : <div className="insert-more">{below} more below</div>}
|
|
721
|
+
</>
|
|
722
|
+
);
|
|
723
|
+
}
|