@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,265 @@
|
|
|
1
|
+
// THE FONT PICKER'S LIST, as one flat array (FontPicker.tsx windows it): the
|
|
2
|
+
// sections in their fixed order, each a head row then its families, a family
|
|
3
|
+
// once, in the highest section that has it (case-folded), every section
|
|
4
|
+
// filtered by the search and the category chip at once, empty sections gone.
|
|
5
|
+
import { isGenericFontFamily } from "@dailies-studio/shot/doc";
|
|
6
|
+
import type { Doc3D } from "@dailies-studio/shot/doc";
|
|
7
|
+
import { appliesVariableFile, findGoogleFont, pickerShows, searchGoogleFonts } from "../../fonts/googleFonts.js";
|
|
8
|
+
import type { GoogleFontCategory, GoogleFontEntry } from "../../fonts/googleFonts.js";
|
|
9
|
+
import type { WorkspaceFont } from "../../api.js";
|
|
10
|
+
import { projectFontFamilies } from "../../state/fonts.js";
|
|
11
|
+
import type { FontPick, ProjectFontFamily } from "../../state/fonts.js";
|
|
12
|
+
import { fold } from "../../state/fontRecents.js";
|
|
13
|
+
import type { FontRecent } from "../../state/fontRecents.js";
|
|
14
|
+
import { googleFace, projectFace, workspaceFace } from "./faceCache.js";
|
|
15
|
+
import type { PreviewFace } from "./faceCache.js";
|
|
16
|
+
|
|
17
|
+
export type PickerSection = "project" | "recent" | "workspace" | "google";
|
|
18
|
+
|
|
19
|
+
export const SECTION_LABELS: Record<PickerSection, string> = {
|
|
20
|
+
project: "In this Project",
|
|
21
|
+
recent: "Recent",
|
|
22
|
+
workspace: "Workspace fonts",
|
|
23
|
+
google: "Google Fonts",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** What a row says after its name. */
|
|
27
|
+
export type FontTag =
|
|
28
|
+
| { kind: "variable" }
|
|
29
|
+
| { kind: "count"; count: number; unit: "weight" | "style" }
|
|
30
|
+
| { kind: "machine" };
|
|
31
|
+
|
|
32
|
+
export interface FontRow {
|
|
33
|
+
type: "font";
|
|
34
|
+
/** Unique in the list: the section and the family's key. */
|
|
35
|
+
id: string;
|
|
36
|
+
/** The family case-folded: what makes two sources one family. */
|
|
37
|
+
key: string;
|
|
38
|
+
family: string;
|
|
39
|
+
section: PickerSection;
|
|
40
|
+
/** What Enter applies and the stage previews. Null for a machine font the
|
|
41
|
+
* catalog does not have: its way in is Upload. */
|
|
42
|
+
pick: FontPick | null;
|
|
43
|
+
/** A family text names with no file (`drawsMachineFont`). */
|
|
44
|
+
machine: boolean;
|
|
45
|
+
/** Null draws the name in Inter. Only a Google row (Recent, Google Fonts)
|
|
46
|
+
* fetches from jsDelivr: a family the Project holds draws from the
|
|
47
|
+
* Project's own file, a machine font from the computer. */
|
|
48
|
+
face: PreviewFace | null;
|
|
49
|
+
tag: FontTag;
|
|
50
|
+
/** Its catalog category, null when the catalog does not know it. */
|
|
51
|
+
category: GoogleFontCategory | null;
|
|
52
|
+
/** What picking it records in Recent: where the family comes from. Null for
|
|
53
|
+
* a file only this Project holds, which In this Project already lists. */
|
|
54
|
+
recent: FontRecent | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface HeadRow {
|
|
58
|
+
type: "head";
|
|
59
|
+
id: string;
|
|
60
|
+
label: string;
|
|
61
|
+
/** Google Fonts says how many families it lists. */
|
|
62
|
+
count?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A line in the list that is not a family: the catalog loading or failed. */
|
|
66
|
+
export interface NoteRow {
|
|
67
|
+
type: "note";
|
|
68
|
+
id: string;
|
|
69
|
+
text: string;
|
|
70
|
+
busy: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type PickerItem = FontRow | HeadRow | NoteRow;
|
|
74
|
+
|
|
75
|
+
export interface PickerInput {
|
|
76
|
+
doc: Doc3D;
|
|
77
|
+
/** Null while it loads. */
|
|
78
|
+
catalog: readonly GoogleFontEntry[] | null;
|
|
79
|
+
/** The catalog's chunk did not load. */
|
|
80
|
+
catalogFailed: boolean;
|
|
81
|
+
/** Null while it loads. */
|
|
82
|
+
workspace: readonly WorkspaceFont[] | null;
|
|
83
|
+
/** The workspace list did not load. */
|
|
84
|
+
workspaceFailed: boolean;
|
|
85
|
+
recents: readonly FontRecent[];
|
|
86
|
+
query: string;
|
|
87
|
+
/** The chip; null is All. */
|
|
88
|
+
category: GoogleFontCategory | null;
|
|
89
|
+
/** The selected text's family, case-folded; null when it is Mixed. */
|
|
90
|
+
current: string | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** How well `name` matches the folded `query`, best 0, or -1 for no match:
|
|
94
|
+
* the tiers `searchGoogleFonts` ranks the catalog by (exact, prefix, a
|
|
95
|
+
* word's prefix, anywhere). */
|
|
96
|
+
function matchTier(name: string, query: string): number {
|
|
97
|
+
const n = fold(name);
|
|
98
|
+
if (n === query) return 0;
|
|
99
|
+
if (n.startsWith(query)) return 1;
|
|
100
|
+
if (n.split(" ").some((word) => word.startsWith(query))) return 2;
|
|
101
|
+
return n.includes(query) ? 3 : -1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const googleTag = (entry: GoogleFontEntry): FontTag =>
|
|
105
|
+
appliesVariableFile(entry) ? { kind: "variable" } : { kind: "count", count: entry.weights.length, unit: "weight" };
|
|
106
|
+
|
|
107
|
+
function googleRow(section: PickerSection, entry: GoogleFontEntry): FontRow {
|
|
108
|
+
const key = fold(entry.family);
|
|
109
|
+
return {
|
|
110
|
+
type: "font",
|
|
111
|
+
id: `${section}:${key}`,
|
|
112
|
+
key,
|
|
113
|
+
family: entry.family,
|
|
114
|
+
section,
|
|
115
|
+
pick: { kind: "google", entry },
|
|
116
|
+
machine: false,
|
|
117
|
+
face: googleFace(entry),
|
|
118
|
+
tag: googleTag(entry),
|
|
119
|
+
category: entry.category,
|
|
120
|
+
recent: { kind: "google", family: entry.family },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function workspaceRow(section: PickerSection, family: string, fonts: readonly WorkspaceFont[]): FontRow {
|
|
125
|
+
const key = fold(family);
|
|
126
|
+
return {
|
|
127
|
+
type: "font",
|
|
128
|
+
id: `${section}:${key}`,
|
|
129
|
+
key,
|
|
130
|
+
family,
|
|
131
|
+
section,
|
|
132
|
+
pick: { kind: "workspace", family, fonts },
|
|
133
|
+
machine: false,
|
|
134
|
+
face: workspaceFace(fonts),
|
|
135
|
+
tag: fonts.some((font) => font.wghtAxis !== undefined)
|
|
136
|
+
? { kind: "variable" }
|
|
137
|
+
: { kind: "count", count: fonts.length, unit: "style" },
|
|
138
|
+
category: null,
|
|
139
|
+
recent: { kind: "workspace", family },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function projectRow(row: ProjectFontFamily, doc: Doc3D, catalog: readonly GoogleFontEntry[] | null): FontRow {
|
|
144
|
+
const entry = row.source === "catalog" && catalog !== null ? findGoogleFont(catalog, row.family) : undefined;
|
|
145
|
+
const faces = row.pick.faces;
|
|
146
|
+
return {
|
|
147
|
+
type: "font",
|
|
148
|
+
id: `project:${row.key}`,
|
|
149
|
+
key: row.key,
|
|
150
|
+
family: row.family,
|
|
151
|
+
section: "project",
|
|
152
|
+
pick: row.pick,
|
|
153
|
+
machine: false,
|
|
154
|
+
face: projectFace(row, doc.assets, catalog),
|
|
155
|
+
tag: entry !== undefined
|
|
156
|
+
? googleTag(entry)
|
|
157
|
+
: faces.some((face) => face.axis !== undefined)
|
|
158
|
+
? { kind: "variable" }
|
|
159
|
+
: { kind: "count", count: faces.length, unit: "style" },
|
|
160
|
+
category: entry?.category ?? null,
|
|
161
|
+
recent: row.source === "file" ? null : { kind: row.source === "catalog" ? "google" : "workspace", family: row.family },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function machineRow(key: string, family: string, catalog: readonly GoogleFontEntry[] | null): FontRow {
|
|
166
|
+
const entry = catalog === null ? undefined : findGoogleFont(catalog, family);
|
|
167
|
+
return {
|
|
168
|
+
type: "font",
|
|
169
|
+
id: `project:${key}`,
|
|
170
|
+
key,
|
|
171
|
+
family,
|
|
172
|
+
section: "project",
|
|
173
|
+
pick: entry === undefined ? null : { kind: "google", entry },
|
|
174
|
+
machine: true,
|
|
175
|
+
// The face the stage draws it in, the computer's own: nothing is fetched.
|
|
176
|
+
face: { family, url: null },
|
|
177
|
+
tag: { kind: "machine" },
|
|
178
|
+
category: entry?.category ?? null,
|
|
179
|
+
recent: entry === undefined ? null : { kind: "google", family: entry.family },
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Workspace font rows by family, in list order. */
|
|
184
|
+
function workspaceFamilies(fonts: readonly WorkspaceFont[]): [string, WorkspaceFont[]][] {
|
|
185
|
+
const families = new Map<string, WorkspaceFont[]>();
|
|
186
|
+
for (const font of fonts) {
|
|
187
|
+
const held = families.get(font.family);
|
|
188
|
+
if (held === undefined) families.set(font.family, [font]);
|
|
189
|
+
else held.push(font);
|
|
190
|
+
}
|
|
191
|
+
return [...families];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function pickerItems(input: PickerInput): PickerItem[] {
|
|
195
|
+
const { doc, catalog, workspace, query, category } = input;
|
|
196
|
+
const q = fold(query);
|
|
197
|
+
// A row the catalog does not know shows under All only.
|
|
198
|
+
const shows = (row: FontRow): boolean =>
|
|
199
|
+
row.category === null ? category === null : pickerShows({ category: row.category }, category);
|
|
200
|
+
const seen = new Set<string>();
|
|
201
|
+
const items: PickerItem[] = [];
|
|
202
|
+
/** A section: its rows filtered and (outside Google, which the catalog
|
|
203
|
+
* search ranks) ranked by the search, each family once. */
|
|
204
|
+
const section = (id: PickerSection, rows: readonly FontRow[]): void => {
|
|
205
|
+
let kept = rows.filter(shows);
|
|
206
|
+
if (q.length > 0 && id !== "google") {
|
|
207
|
+
kept = kept
|
|
208
|
+
.map((row, at) => ({ row, at, tier: matchTier(row.family, q) }))
|
|
209
|
+
.filter((hit) => hit.tier >= 0)
|
|
210
|
+
.sort((a, b) => a.tier - b.tier || a.at - b.at)
|
|
211
|
+
.map((hit) => hit.row);
|
|
212
|
+
}
|
|
213
|
+
const once: FontRow[] = [];
|
|
214
|
+
for (const row of kept) {
|
|
215
|
+
if (seen.has(row.key)) continue;
|
|
216
|
+
seen.add(row.key);
|
|
217
|
+
once.push(row);
|
|
218
|
+
}
|
|
219
|
+
if (once.length === 0) return;
|
|
220
|
+
items.push({ type: "head", id: `head:${id}`, label: SECTION_LABELS[id], ...(id === "google" ? { count: once.length } : {}) }, ...once);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const project = projectFontFamilies(doc, catalog, workspace);
|
|
224
|
+
section("project", [
|
|
225
|
+
...project.families.map((row) => projectRow(row, doc, catalog)),
|
|
226
|
+
// A generic keyword (serif) is no family a pick can write or a file can
|
|
227
|
+
// be, so only the current family's row shows it, the check on it.
|
|
228
|
+
...project.machine
|
|
229
|
+
.filter((m) => m.key === input.current || !isGenericFontFamily(m.family))
|
|
230
|
+
.map((m) => machineRow(m.key, m.family, catalog)),
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
section(
|
|
234
|
+
"recent",
|
|
235
|
+
input.recents.flatMap((recent): FontRow[] => {
|
|
236
|
+
if (recent.kind === "google") {
|
|
237
|
+
const entry = catalog === null ? undefined : findGoogleFont(catalog, recent.family);
|
|
238
|
+
return entry === undefined ? [] : [googleRow("recent", entry)];
|
|
239
|
+
}
|
|
240
|
+
const fonts = (workspace ?? []).filter((font) => fold(font.family) === fold(recent.family));
|
|
241
|
+
return fonts.length === 0 ? [] : [workspaceRow("recent", fonts[0]!.family, fonts)];
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (workspace !== null) {
|
|
246
|
+
section("workspace", workspaceFamilies(workspace).map(([family, fonts]) => workspaceRow("workspace", family, fonts)));
|
|
247
|
+
} else if (input.workspaceFailed) {
|
|
248
|
+
items.push(
|
|
249
|
+
{ type: "head", id: "head:workspace", label: SECTION_LABELS.workspace },
|
|
250
|
+
{ type: "note", id: "note:workspace", text: "Workspace fonts did not load. Open the picker again to retry.", busy: false },
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (catalog !== null) {
|
|
255
|
+
section("google", searchGoogleFonts(catalog, query).map((entry) => googleRow("google", entry)));
|
|
256
|
+
} else {
|
|
257
|
+
items.push(
|
|
258
|
+
{ type: "head", id: "head:google", label: SECTION_LABELS.google },
|
|
259
|
+
input.catalogFailed
|
|
260
|
+
? { type: "note", id: "note:google", text: "Google Fonts did not load. Open the picker again to retry.", busy: false }
|
|
261
|
+
: { type: "note", id: "note:google", text: "Loading Google Fonts…", busy: true },
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return items;
|
|
265
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// A request to open the font picker from somewhere other than its own field:
|
|
2
|
+
// the Typography section's machine-font note opens it on the upload view
|
|
3
|
+
// ("Upload font file"). The Font field listens and opens the picker on the
|
|
4
|
+
// asked view. Kept out of the picker's lazy chunk, so the note can ask
|
|
5
|
+
// before that chunk has loaded.
|
|
6
|
+
import { useSyncExternalStore } from "react";
|
|
7
|
+
|
|
8
|
+
export type FontPickerView = "list" | "upload";
|
|
9
|
+
|
|
10
|
+
/** How the picker closed, which says where focus goes: back to the Font
|
|
11
|
+
* field ("refocus": a pick, Escape in the editor's window), on from the field
|
|
12
|
+
* ("tab": Tab moves to the next control), or where a press outside put it
|
|
13
|
+
* ("stay"; a press or Escape on the stage hands focus to its frame). */
|
|
14
|
+
export type FontPickerClose = "refocus" | "tab" | "stay";
|
|
15
|
+
|
|
16
|
+
/** The dashboard's Fonts tab. The editor host sends `/projects/*` to the main
|
|
17
|
+
* host, so the relative link works on both, and on the local CLI. */
|
|
18
|
+
export const MANAGE_FONTS_HREF = "/projects/?view=settings-fonts";
|
|
19
|
+
|
|
20
|
+
/** One request. `seq` grows with every request, so asking for the same view
|
|
21
|
+
* twice still opens the picker the second time. */
|
|
22
|
+
export interface FontPickerRequest {
|
|
23
|
+
view: FontPickerView;
|
|
24
|
+
seq: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let current: FontPickerRequest | null = null;
|
|
28
|
+
const listeners = new Set<() => void>();
|
|
29
|
+
|
|
30
|
+
/** Ask the Font field to open the picker on `view`. */
|
|
31
|
+
export function requestFontPicker(view: FontPickerView): void {
|
|
32
|
+
current = { view, seq: (current?.seq ?? 0) + 1 };
|
|
33
|
+
for (const listener of listeners) listener();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function subscribe(listener: () => void): () => void {
|
|
37
|
+
listeners.add(listener);
|
|
38
|
+
return () => {
|
|
39
|
+
listeners.delete(listener);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The latest request, or null before the first. */
|
|
44
|
+
export function useFontPickerRequest(): FontPickerRequest | null {
|
|
45
|
+
return useSyncExternalStore(subscribe, () => current, () => null);
|
|
46
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// The signed-in author's initials, at the end of the top bar. HOSTED ONLY: the
|
|
2
|
+
// local CLI has no accounts, so `isCloudWorkspace()` keeps this from asking.
|
|
3
|
+
// The name comes off `/auth/me`, the session probe the editor host answers
|
|
4
|
+
// (worker/src/auth.ts), read once per page. A click opens the profile through
|
|
5
|
+
// `leaveFor`, the same way out the app menu's Profile row takes.
|
|
6
|
+
//
|
|
7
|
+
// A guest in the current workspace also gets a "Guest" note beside it. The
|
|
8
|
+
// role is the server's effective one (`/auth/me` says "guest" for a guest), so
|
|
9
|
+
// the editor keeps no rule of its own about who counts as one.
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
import { IconUser, initialsOf, personInitial } from "@dailies-studio/ui";
|
|
12
|
+
import { useStore } from "../../state/context.js";
|
|
13
|
+
import { isCloudWorkspace } from "../../state/posterCapture.js";
|
|
14
|
+
import { leaveFor } from "./leave.js";
|
|
15
|
+
|
|
16
|
+
/** The signed-in author's name and whether they are a guest here, off
|
|
17
|
+
* `/auth/me`. Null locally, before the probe answers, and when it fails.
|
|
18
|
+
* The Components footer's avatar reads it too. */
|
|
19
|
+
export function useSignedIn(): { name: string; guest: boolean } | null {
|
|
20
|
+
const [name, setName] = useState<string | null>(null);
|
|
21
|
+
const [guest, setGuest] = useState(false);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!isCloudWorkspace()) return;
|
|
24
|
+
let live = true;
|
|
25
|
+
// A failed probe draws nothing: the menu's Profile row is still there.
|
|
26
|
+
void fetch("/auth/me", { credentials: "same-origin" })
|
|
27
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
28
|
+
.then(
|
|
29
|
+
(
|
|
30
|
+
me: {
|
|
31
|
+
signedIn?: boolean;
|
|
32
|
+
login?: string;
|
|
33
|
+
displayName?: string;
|
|
34
|
+
workspace?: { role?: string } | null;
|
|
35
|
+
} | null,
|
|
36
|
+
) => {
|
|
37
|
+
if (!live || me?.signedIn !== true) return;
|
|
38
|
+
setName(me.displayName ?? me.login ?? null);
|
|
39
|
+
setGuest(me.workspace?.role === "guest");
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
.catch(() => {});
|
|
43
|
+
return () => {
|
|
44
|
+
live = false;
|
|
45
|
+
};
|
|
46
|
+
}, []);
|
|
47
|
+
return name === null ? null : { name, guest };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function AccountAvatar() {
|
|
51
|
+
const store = useStore();
|
|
52
|
+
const me = useSignedIn();
|
|
53
|
+
if (me === null) return null;
|
|
54
|
+
const { name, guest } = me;
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
{guest && (
|
|
58
|
+
<span
|
|
59
|
+
className="header-guest"
|
|
60
|
+
title="You see only the projects shared with you, and only the people on those projects."
|
|
61
|
+
>
|
|
62
|
+
Guest
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
className="header-avatar"
|
|
68
|
+
aria-label={`Profile: ${name}`}
|
|
69
|
+
title={name}
|
|
70
|
+
onClick={() => leaveFor(store, "/projects/?view=profile")}
|
|
71
|
+
>
|
|
72
|
+
{personInitial(name) === null ? <IconUser /> : initialsOf(name)}
|
|
73
|
+
</button>
|
|
74
|
+
</>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// The Agent chip, in the top bar before the avatar: whether an outside agent
|
|
2
|
+
// can reach this workspace over MCP, in two words, and the connect rows under
|
|
3
|
+
// it. It replaced the left column's 64px MCP band.
|
|
4
|
+
//
|
|
5
|
+
// The word is HEALTH ONLY (`mcpWord`): "MCP ready", "Read only", "MCP off",
|
|
6
|
+
// "No access". A hosted role without `mcp.connect` wears the off tone.
|
|
7
|
+
// `/api/health` cannot see a client, so the chip never says "connected".
|
|
8
|
+
//
|
|
9
|
+
// One probe (`useMcpHealth`), held here and handed to the details, so the chip
|
|
10
|
+
// and its popover cannot disagree. The popover is portaled to <body>, so the
|
|
11
|
+
// header's `inert` under the Library does not reach it: it closes when the
|
|
12
|
+
// Library opens instead. The Popover's outside-pointerdown ignores its anchor,
|
|
13
|
+
// so a second press on the chip closes it through this toggle.
|
|
14
|
+
import { useEffect, useRef, useState } from "react";
|
|
15
|
+
import { PanelBoundary } from "@dailies-studio/ui";
|
|
16
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
17
|
+
import { ADMINS_ONLY_NOTE, McpPopover, adminsOnly, mcpWord, useMcpHealth } from "../files/McpSection.js";
|
|
18
|
+
import { agentHandoffPrompt } from "./agentHandoff.js";
|
|
19
|
+
|
|
20
|
+
export function AgentChip() {
|
|
21
|
+
const name = useShotEditor((s) => s.name);
|
|
22
|
+
if (name === null) return null;
|
|
23
|
+
return (
|
|
24
|
+
<PanelBoundary label="Agent chip">
|
|
25
|
+
<Chip name={name} />
|
|
26
|
+
</PanelBoundary>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Chip({ name }: { name: string }) {
|
|
31
|
+
const store = useStore();
|
|
32
|
+
const title = useShotEditor((s) => s.doc?.title ?? s.name);
|
|
33
|
+
const libraryOpen = useShotEditor((s) => s.libraryBrowserOpen);
|
|
34
|
+
const { health, retry } = useMcpHealth(name);
|
|
35
|
+
const [open, setOpen] = useState(false);
|
|
36
|
+
const chip = useRef<HTMLButtonElement>(null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (libraryOpen) setOpen(false);
|
|
40
|
+
}, [libraryOpen]);
|
|
41
|
+
|
|
42
|
+
const word = mcpWord(health);
|
|
43
|
+
const tone =
|
|
44
|
+
health.kind !== "ready"
|
|
45
|
+
? "is-unknown"
|
|
46
|
+
: !health.mounted || !health.allowed
|
|
47
|
+
? "is-off"
|
|
48
|
+
: health.write ? "is-write" : "is-read";
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<button
|
|
52
|
+
ref={chip}
|
|
53
|
+
type="button"
|
|
54
|
+
className={`btn agent-chip ${tone}`}
|
|
55
|
+
data-testid="agent-chip"
|
|
56
|
+
aria-haspopup="dialog"
|
|
57
|
+
aria-expanded={open}
|
|
58
|
+
title={adminsOnly(health) ? ADMINS_ONLY_NOTE : "Connect your AI over MCP"}
|
|
59
|
+
onClick={() => setOpen((value) => !value)}
|
|
60
|
+
>
|
|
61
|
+
<span className="agent-chip-dot" aria-hidden="true" />
|
|
62
|
+
<span className="agent-chip-label">Agent</span>
|
|
63
|
+
<span className="agent-chip-word">{word}</span>
|
|
64
|
+
</button>
|
|
65
|
+
<McpPopover
|
|
66
|
+
anchor={chip}
|
|
67
|
+
open={open}
|
|
68
|
+
onClose={() => setOpen(false)}
|
|
69
|
+
health={health}
|
|
70
|
+
onRetry={retry}
|
|
71
|
+
projectName={name}
|
|
72
|
+
projectTitle={title}
|
|
73
|
+
handoff={() => agentHandoffPrompt(store.getState())}
|
|
74
|
+
/>
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// THE APP MENU'S BUTTON: the brand mark in the top-left, and every verb this
|
|
2
|
+
// chrome has behind it.
|
|
3
|
+
//
|
|
4
|
+
// ── WHY THE TOP-LEFT ─────────────────────────────────────────────────────
|
|
5
|
+
//
|
|
6
|
+
// The brand button leads the Project identity, in the corner every desktop app
|
|
7
|
+
// reserves for its menu.
|
|
8
|
+
//
|
|
9
|
+
// ── AND WHY THIS FILE IS SO SHORT ────────────────────────────────────────
|
|
10
|
+
//
|
|
11
|
+
// The tree is DATA (`appMenuEntries`) and the framework is shared
|
|
12
|
+
// (`useContextMenu`, which already portals to <body>, clamps and flips against
|
|
13
|
+
// the viewport, nests submenus, drives the arrows, closes on Escape and an
|
|
14
|
+
// outside press, and restores focus). What is left is: a button, a rect to open
|
|
15
|
+
// at, and the four things a menu row cannot do by itself.
|
|
16
|
+
//
|
|
17
|
+
// ── THOSE FOUR THINGS ────────────────────────────────────────────────────
|
|
18
|
+
//
|
|
19
|
+
// A file dialog can only be opened by a click on a real `<input>` (the store is
|
|
20
|
+
// not one, which is why `imagePick` exists), and a name has to be ASKED for. So
|
|
21
|
+
// this file owns one hidden input and one `usePrompt`, and hands the menu
|
|
22
|
+
// callbacks, using the same `onRename` hatch `shotMenuEntries` established. Project
|
|
23
|
+
// lifecycle is deliberately absent here; `/projects/` owns it.
|
|
24
|
+
//
|
|
25
|
+
// The input is HERE and not borrowed from `AddMenu`: that one lives inside
|
|
26
|
+
// `ToolDockBar`, which UNMOUNTS ENTIRELY in the scene context, so it is not in
|
|
27
|
+
// the document at exactly the moment a global row would click it.
|
|
28
|
+
import { useEffect, useRef, useState } from "react";
|
|
29
|
+
import { DailiesMark, useConfirm, usePrompt, useContextMenu, useToasts } from "@dailies-studio/ui";
|
|
30
|
+
import { homeOf } from "@dailies-studio/shot/doc";
|
|
31
|
+
import { useStudioTheme } from "@dailies-studio/ui/theme";
|
|
32
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
33
|
+
import { appMenuEntries } from "../../state/menu.js";
|
|
34
|
+
import type { AppMenuHooks } from "../../state/menu.js";
|
|
35
|
+
import { shortcutFor } from "../../state/shortcuts.js";
|
|
36
|
+
import { CHEVRON_DOWN_ICON } from "../../icons.js";
|
|
37
|
+
import { HelpDialog } from "../help/HelpSection.js";
|
|
38
|
+
import { PreferencesDialog } from "../../app/lazySurfaces.js";
|
|
39
|
+
import { leaveFor } from "./leave.js";
|
|
40
|
+
|
|
41
|
+
export function AppMenu() {
|
|
42
|
+
const store = useStore();
|
|
43
|
+
const { preference: themePreference, setPreference: setThemePreference } = useStudioTheme();
|
|
44
|
+
const { open, menu } = useContextMenu();
|
|
45
|
+
const { prompt, dialog } = usePrompt();
|
|
46
|
+
const { confirm, dialog: confirmDialog } = useConfirm();
|
|
47
|
+
const { toast, node: toasts } = useToasts();
|
|
48
|
+
const [helpOpen, setHelpOpen] = useState(false);
|
|
49
|
+
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
50
|
+
const audioRef = useRef<HTMLInputElement | null>(null);
|
|
51
|
+
// ⌘K's mailbox. The ONE subscription in this component, and it is to an object
|
|
52
|
+
// identity rather than to any state the tree reads — see `appMenu` in types.ts
|
|
53
|
+
// and `StageMenu`, which is the same shape for the same reason.
|
|
54
|
+
const request = useShotEditor((s) => s.appMenu);
|
|
55
|
+
// `menu` is null while closed, so the button's `aria-expanded` is DERIVED from
|
|
56
|
+
// it. No second open bit: `useContextMenu` owns the three ways a menu is
|
|
57
|
+
// dismissed (Escape, an outside press, an action firing) and reports none of
|
|
58
|
+
// them, so a mirrored boolean here could only ever be the stale one.
|
|
59
|
+
const isOpen = menu !== null;
|
|
60
|
+
|
|
61
|
+
// THE TREE IS READ WITH `getState()` AT OPEN TIME — StageMenu's rule — because
|
|
62
|
+
// a menu built from subscribed slices would need one subscription per predicate
|
|
63
|
+
// in it, in the header, which re-renders every control up there on each
|
|
64
|
+
// notification.
|
|
65
|
+
const report = (e: unknown): void =>
|
|
66
|
+
store.reportError(e instanceof Error ? e.message : String(e));
|
|
67
|
+
|
|
68
|
+
const hooks: AppMenuHooks = {
|
|
69
|
+
themePreference,
|
|
70
|
+
onThemePreference: setThemePreference,
|
|
71
|
+
onRegenerate: () => {
|
|
72
|
+
void confirm({
|
|
73
|
+
title: "Regenerate the component",
|
|
74
|
+
message:
|
|
75
|
+
"This overwrites the generated file with what the document says, discarding any hand " +
|
|
76
|
+
"edits in it. This cannot be undone.",
|
|
77
|
+
confirmLabel: "Regenerate",
|
|
78
|
+
danger: true,
|
|
79
|
+
}).then((yes) => {
|
|
80
|
+
if (yes) void store.regenerateShot();
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
onCopyPath: (path) => {
|
|
84
|
+
// `navigator.clipboard` is absent over plain http on a non-localhost host,
|
|
85
|
+
// which the hosted lane is not but a LAN dev server can be, so the failure
|
|
86
|
+
// path says the path instead of silently doing nothing. A success leaves
|
|
87
|
+
// the error strip alone: it may hold an unrelated message.
|
|
88
|
+
const fail = (): void => store.reportError(`Copy failed. The generated file is at ${path}`);
|
|
89
|
+
if (navigator.clipboard === undefined) return fail();
|
|
90
|
+
void navigator.clipboard.writeText(path).then(
|
|
91
|
+
() => void toast({ message: "Path copied", severity: "success" }),
|
|
92
|
+
fail,
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
onRenameScene: (sceneId) => {
|
|
96
|
+
const scene = store.getState().doc?.scenesById[sceneId];
|
|
97
|
+
void prompt({
|
|
98
|
+
title: "Rename shot",
|
|
99
|
+
label: "Name",
|
|
100
|
+
initialValue: scene?.name ?? "",
|
|
101
|
+
}).then((name) => {
|
|
102
|
+
if (name !== null && name.trim() !== "") store.renameScene(sceneId, name.trim());
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
onRenameLayer: (layerId) => {
|
|
106
|
+
// The container `store.renameLayer` writes to (`layerContainer()`).
|
|
107
|
+
const s = store.getState();
|
|
108
|
+
const container = s.editingDef ?? s.sceneId;
|
|
109
|
+
const layer =
|
|
110
|
+
s.doc === null || container === null ? undefined : homeOf(s.doc, container)?.layersById[layerId];
|
|
111
|
+
void prompt({ title: "Rename layer", label: "Name", initialValue: layer?.label ?? "" }).then((name) => {
|
|
112
|
+
if (name !== null && name.trim() !== "") store.renameLayer(layerId, name.trim());
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
onRenameComponent: (defId) => {
|
|
116
|
+
const def = store.getState().doc?.components?.[defId];
|
|
117
|
+
void prompt({
|
|
118
|
+
title: "Rename component",
|
|
119
|
+
label: "Name",
|
|
120
|
+
initialValue: def?.name ?? "",
|
|
121
|
+
}).then((name) => {
|
|
122
|
+
if (name !== null && name.trim() !== "") store.renameComponent(defId, name.trim());
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
onImportAudio: () => audioRef.current?.click(),
|
|
126
|
+
onOpenHelp: () => setHelpOpen(true),
|
|
127
|
+
// One way out for every row, shared with the `Projects` crumb and the
|
|
128
|
+
// avatar: see `leaveFor` on why it flushes first and stays on a failure.
|
|
129
|
+
onNavigate: (href) => leaveFor(store, href),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const show = (): void => {
|
|
133
|
+
const el = buttonRef.current;
|
|
134
|
+
if (el === null) return;
|
|
135
|
+
// A POINT FROM THE BUTTON'S RECT, not a new anchoring API on the menu.
|
|
136
|
+
// `Popover` already owns anchor-to-element and has no keyboard model;
|
|
137
|
+
// composing the two would be a third floating primitive for two lines of
|
|
138
|
+
// arithmetic. The gap matches the header's own 12px inset feel.
|
|
139
|
+
const r = el.getBoundingClientRect();
|
|
140
|
+
open({ x: r.left, y: r.bottom + 6 }, appMenuEntries(store, store.getState(), hooks), {
|
|
141
|
+
// ONE SURFACE, not a menu plus a palette. Framer's list has a search field
|
|
142
|
+
// at the top AND a "Quick Actions ⌘K" row below it; here ⌘K opens this and
|
|
143
|
+
// the field is already focused, so the second row would point at itself.
|
|
144
|
+
search: "Type to search…",
|
|
145
|
+
// Paper's workspace menu keeps both 16px lanes on every row of every
|
|
146
|
+
// level, so File's labels line up with the root's when it opens beside it.
|
|
147
|
+
lanes: "always",
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// ⌘K, arriving through the store because the window's keydown listener cannot
|
|
152
|
+
// reach a popover. Keyed on the mailbox OBJECT, so a second press re-opens.
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (request === null) return;
|
|
155
|
+
show();
|
|
156
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `show` closes over
|
|
157
|
+
// fresh hooks/state every render by design; the mailbox identity is the trigger.
|
|
158
|
+
}, [request]);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
<button
|
|
163
|
+
ref={buttonRef}
|
|
164
|
+
type="button"
|
|
165
|
+
// `.btn` first: hover, disabled and the `.is-active` accent-soft fill
|
|
166
|
+
// are the chrome's one button recipe, not a second one written here.
|
|
167
|
+
className={`btn app-menu-trigger${isOpen ? " is-active" : ""}`}
|
|
168
|
+
aria-haspopup="menu"
|
|
169
|
+
aria-expanded={isOpen}
|
|
170
|
+
aria-label="Menu"
|
|
171
|
+
// From the shortcut table, not typed here — `ZoomControls` typed its own
|
|
172
|
+
// hint and it drifted.
|
|
173
|
+
title={`Menu (${shortcutFor("app.menu") ?? ""})`}
|
|
174
|
+
data-testid="app-menu-trigger"
|
|
175
|
+
onClick={show}
|
|
176
|
+
>
|
|
177
|
+
{/* The Dailies mark in `currentColor`, the one brand element the
|
|
178
|
+
chrome draws (Paper's workspace trigger). */}
|
|
179
|
+
<DailiesMark className="app-menu-mark" />
|
|
180
|
+
<span className="app-menu-caret" aria-hidden="true">
|
|
181
|
+
{CHEVRON_DOWN_ICON}
|
|
182
|
+
</span>
|
|
183
|
+
</button>
|
|
184
|
+
{menu}
|
|
185
|
+
<input
|
|
186
|
+
ref={audioRef}
|
|
187
|
+
type="file"
|
|
188
|
+
accept="audio/*"
|
|
189
|
+
hidden
|
|
190
|
+
onChange={(e) => {
|
|
191
|
+
const file = e.target.files?.[0] ?? null;
|
|
192
|
+
// Clear FIRST: picking the same file twice must fire again, and an
|
|
193
|
+
// unchanged value fires no event at all.
|
|
194
|
+
e.target.value = "";
|
|
195
|
+
if (file === null) return;
|
|
196
|
+
// At the PLAYHEAD and into the entered shot when there is one, which
|
|
197
|
+
// is `importAudio`'s own convention — a master bed otherwise.
|
|
198
|
+
const s = store.getState();
|
|
199
|
+
void store
|
|
200
|
+
.importAudio(file, s.frame, s.enteredScene ?? undefined)
|
|
201
|
+
.catch(report);
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
{dialog}
|
|
205
|
+
{confirmDialog}
|
|
206
|
+
{toasts}
|
|
207
|
+
<HelpDialog open={helpOpen} onClose={() => setHelpOpen(false)} />
|
|
208
|
+
{/* Opened by `prefsOpen` (the menu row, ⌘, and `prefs.open`), and
|
|
209
|
+
fetched at its first open (app/lazySurfaces.tsx). */}
|
|
210
|
+
<PreferencesDialog />
|
|
211
|
+
</>
|
|
212
|
+
);
|
|
213
|
+
}
|