@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
|
@@ -1,19 +1,45 @@
|
|
|
1
|
+
import { REDO_ICON, UNDO_ICON } from "../../icons.js";
|
|
1
2
|
import { useShotEditor, useStore } from "../../state/context.js";
|
|
3
|
+
import { shortcutHint } from "../../state/shortcuts.js";
|
|
4
|
+
import { commandBlocker } from "../../state/workspace.js";
|
|
2
5
|
|
|
3
6
|
/** Undo/redo. The enabled state comes from engine's history, never from a
|
|
4
7
|
* count this chrome keeps — a second source of truth for "can I undo" is a
|
|
5
|
-
* button that lies exactly when it matters.
|
|
8
|
+
* button that lies exactly when it matters.
|
|
9
|
+
*
|
|
10
|
+
* THE HINTS COME OFF THE TABLE. They were literal "⌘Z"/"⌘⇧Z", which is wrong
|
|
11
|
+
* on every Windows machine the editor runs on — `formatCombo` spells those
|
|
12
|
+
* Ctrl+Z and Ctrl+Shift+Z. `shortcutFor` is the same lookup the menu
|
|
13
|
+
* accelerators use, so the tooltip and the menu row can no longer disagree,
|
|
14
|
+
* and a rebind moves both. */
|
|
6
15
|
export function HistoryControls() {
|
|
7
16
|
const store = useStore();
|
|
8
17
|
const canUndo = useShotEditor((s) => s.canUndo);
|
|
9
18
|
const canRedo = useShotEditor((s) => s.canRedo);
|
|
19
|
+
const blocked = useShotEditor((s) => commandBlocker(s, "edit.undo"));
|
|
20
|
+
// Under live sync undo is the author's own (Paper page 21, "Undo note").
|
|
21
|
+
const own = useShotEditor((s) => s.syncStatus !== null) ? " · Undo covers only your changes." : "";
|
|
10
22
|
return (
|
|
11
23
|
<div className="history-controls">
|
|
12
|
-
<button
|
|
13
|
-
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
className="btn is-icon"
|
|
27
|
+
aria-label="Undo"
|
|
28
|
+
disabled={!canUndo || blocked !== null}
|
|
29
|
+
title={blocked ?? `Undo${shortcutHint("edit.undo")}${own}`}
|
|
30
|
+
onClick={() => store.undo()}
|
|
31
|
+
>
|
|
32
|
+
{UNDO_ICON}
|
|
14
33
|
</button>
|
|
15
|
-
<button
|
|
16
|
-
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
className="btn is-icon"
|
|
37
|
+
aria-label="Redo"
|
|
38
|
+
disabled={!canRedo || blocked !== null}
|
|
39
|
+
title={blocked ?? `Redo${shortcutHint("edit.redo")}`}
|
|
40
|
+
onClick={() => store.redo()}
|
|
41
|
+
>
|
|
42
|
+
{REDO_ICON}
|
|
17
43
|
</button>
|
|
18
44
|
</div>
|
|
19
45
|
);
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// WHO ELSE IS HERE (lane R7, Paper page 21): one face per person in the
|
|
2
|
+
// Project's live session, in their peer colour, three faces and then "+N".
|
|
3
|
+
// Hovering a face says who and where; clicking one follows them (their Shot,
|
|
4
|
+
// frame and selection) until Escape, an edit of our own, or they leave.
|
|
5
|
+
//
|
|
6
|
+
// The Agent is a mint face while it runs. It is this tab's own agent, so it
|
|
7
|
+
// has no place to follow.
|
|
8
|
+
//
|
|
9
|
+
// People are live sync only: with sync off the presence store is not live and
|
|
10
|
+
// draws no faces. The local CLI is one user, so it never has a face to draw
|
|
11
|
+
// either. An MCP AGENT working on this Project (state/agentActivity.ts) shows
|
|
12
|
+
// on both hosts, sync or not: a mint face per agent, and a status chip naming
|
|
13
|
+
// its step ("Claude: Writing VolumetricClouds.tsx") with Follow when it is
|
|
14
|
+
// writing a file, which opens Code Studio following it. The view never moves
|
|
15
|
+
// on its own from here.
|
|
16
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
17
|
+
import type { ReactNode } from "react";
|
|
18
|
+
import { initialsOf, useToasts } from "@dailies-studio/ui";
|
|
19
|
+
import type { ToastHandle } from "@dailies-studio/ui";
|
|
20
|
+
import type { Doc3D } from "@dailies-studio/shot/doc";
|
|
21
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
22
|
+
import { AGENT_COLOR } from "../../state/presence.js";
|
|
23
|
+
import type { PresencePerson, PresenceState } from "../../state/presence.js";
|
|
24
|
+
import { shotNameOf, shotNumberOf } from "../../state/storyboardModel.js";
|
|
25
|
+
import { agentsIn } from "../../state/agentActivity.js";
|
|
26
|
+
import type { AgentWork } from "../../state/agentActivity.js";
|
|
27
|
+
import { AgentDots, useAgentActivity } from "../agent/AgentDots.js";
|
|
28
|
+
|
|
29
|
+
/** Faces before the "+N". */
|
|
30
|
+
export const PRESENCE_FACES = 3;
|
|
31
|
+
|
|
32
|
+
export function usePresence(): PresenceState {
|
|
33
|
+
const { presence } = useStore();
|
|
34
|
+
return useSyncExternalStore(presence.subscribe, presence.getState);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Where a person is, as the hover card says it: the Shot's number and name,
|
|
38
|
+
* and the first layer they hold. Null for a place this document lacks. */
|
|
39
|
+
export function placeOf(
|
|
40
|
+
doc: Doc3D | null,
|
|
41
|
+
person: Pick<PresencePerson, "scene" | "sel">,
|
|
42
|
+
): { number: string; shot: string; layer: string | null } | null {
|
|
43
|
+
if (doc === null || person.scene === null) return null;
|
|
44
|
+
const scene = doc.scenesById[person.scene];
|
|
45
|
+
if (scene === undefined) return null;
|
|
46
|
+
const number = shotNumberOf(doc, person.scene) ?? String(doc.scenes.indexOf(person.scene) + 1).padStart(2, "0");
|
|
47
|
+
const first = person.sel.find((id) => scene.layersById[id] !== undefined);
|
|
48
|
+
return {
|
|
49
|
+
number,
|
|
50
|
+
shot: shotNameOf(doc, person.scene),
|
|
51
|
+
layer: first === undefined ? null : (scene.layersById[first]?.label ?? null),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const AGENT = "agent";
|
|
56
|
+
|
|
57
|
+
export function PresenceStack(): ReactNode {
|
|
58
|
+
const store = useStore();
|
|
59
|
+
const presence = usePresence();
|
|
60
|
+
const agentRunning = useShotEditor((s) => s.agentRunning);
|
|
61
|
+
const doc = useShotEditor((s) => s.doc);
|
|
62
|
+
const projectName = useShotEditor((s) => s.name);
|
|
63
|
+
const activity = useAgentActivity();
|
|
64
|
+
const mcpAgents = agentsIn(activity, projectName);
|
|
65
|
+
const [card, setCard] = useState<string | null>(null);
|
|
66
|
+
|
|
67
|
+
// Escape stops a follow, from anywhere in the editor.
|
|
68
|
+
const following = presence.following;
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (following === null) return;
|
|
71
|
+
const onKey = (event: KeyboardEvent) => {
|
|
72
|
+
if (event.key !== "Escape") return;
|
|
73
|
+
store.presence.follow(null);
|
|
74
|
+
};
|
|
75
|
+
window.addEventListener("keydown", onKey);
|
|
76
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
77
|
+
}, [following, store]);
|
|
78
|
+
|
|
79
|
+
// A follow that ended on its own says why, once.
|
|
80
|
+
const { toast, node } = useToasts();
|
|
81
|
+
const shown = useRef<ToastHandle | null>(null);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (presence.notice === null) return;
|
|
84
|
+
shown.current?.dismiss();
|
|
85
|
+
shown.current = toast({ message: presence.notice });
|
|
86
|
+
store.presence.dismissNotice();
|
|
87
|
+
}, [presence.notice, store, toast]);
|
|
88
|
+
|
|
89
|
+
const people = presence.live ? presence.people : [];
|
|
90
|
+
const agent = presence.live && agentRunning;
|
|
91
|
+
const total = people.length + (agent ? 1 : 0) + mcpAgents.length;
|
|
92
|
+
if (total === 0) return node;
|
|
93
|
+
|
|
94
|
+
const shownPeople = people.slice(0, Math.max(0, PRESENCE_FACES - (agent ? 1 : 0) - mcpAgents.length));
|
|
95
|
+
const more = people.length - shownPeople.length;
|
|
96
|
+
const followed = people.find((p) => p.user === following) ?? null;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<>
|
|
100
|
+
{node}
|
|
101
|
+
{mcpAgents.length > 0 ? <AgentStatusChip agent={mcpAgents[mcpAgents.length - 1]!} /> : null}
|
|
102
|
+
<div className="presence-stack" data-testid="presence-stack" role="group" aria-label="People here">
|
|
103
|
+
{mcpAgents.map((a) => (
|
|
104
|
+
<span
|
|
105
|
+
key={a.sessionId}
|
|
106
|
+
className="presence-face presence-face-agent"
|
|
107
|
+
data-testid="presence-mcp-agent"
|
|
108
|
+
style={{ background: AGENT_COLOR }}
|
|
109
|
+
tabIndex={0}
|
|
110
|
+
aria-label={`${a.name}, ${a.step}`}
|
|
111
|
+
onPointerEnter={() => setCard(a.sessionId)}
|
|
112
|
+
onPointerLeave={() => setCard(null)}
|
|
113
|
+
onFocus={() => setCard(a.sessionId)}
|
|
114
|
+
onBlur={() => setCard(null)}
|
|
115
|
+
>
|
|
116
|
+
AI
|
|
117
|
+
{card === a.sessionId ? (
|
|
118
|
+
<span className="presence-card" role="tooltip">
|
|
119
|
+
<span className="presence-card-who">
|
|
120
|
+
<span className="presence-card-avatar" style={{ background: AGENT_COLOR }}>
|
|
121
|
+
AI
|
|
122
|
+
</span>
|
|
123
|
+
<span className="presence-card-name">
|
|
124
|
+
<span>{a.name}</span>
|
|
125
|
+
<span className="presence-card-role">Agent over MCP · {a.step}</span>
|
|
126
|
+
</span>
|
|
127
|
+
</span>
|
|
128
|
+
</span>
|
|
129
|
+
) : null}
|
|
130
|
+
</span>
|
|
131
|
+
))}
|
|
132
|
+
{agent ? (
|
|
133
|
+
<span
|
|
134
|
+
className="presence-face presence-face-agent"
|
|
135
|
+
data-testid="presence-agent"
|
|
136
|
+
style={{ background: AGENT_COLOR }}
|
|
137
|
+
tabIndex={0}
|
|
138
|
+
aria-label="Agent, working"
|
|
139
|
+
onPointerEnter={() => setCard(AGENT)}
|
|
140
|
+
onPointerLeave={() => setCard(null)}
|
|
141
|
+
onFocus={() => setCard(AGENT)}
|
|
142
|
+
onBlur={() => setCard(null)}
|
|
143
|
+
>
|
|
144
|
+
AI
|
|
145
|
+
{card === AGENT ? (
|
|
146
|
+
<span className="presence-card" role="tooltip">
|
|
147
|
+
<span className="presence-card-who">
|
|
148
|
+
<span className="presence-card-avatar" style={{ background: AGENT_COLOR }}>
|
|
149
|
+
AI
|
|
150
|
+
</span>
|
|
151
|
+
<span className="presence-card-name">
|
|
152
|
+
<span>Agent</span>
|
|
153
|
+
<span className="presence-card-role">Working in this tab</span>
|
|
154
|
+
</span>
|
|
155
|
+
</span>
|
|
156
|
+
</span>
|
|
157
|
+
) : null}
|
|
158
|
+
</span>
|
|
159
|
+
) : null}
|
|
160
|
+
{shownPeople.map((person) => {
|
|
161
|
+
const place = placeOf(doc, person);
|
|
162
|
+
const isFollowed = person.user === following;
|
|
163
|
+
return (
|
|
164
|
+
<button
|
|
165
|
+
key={person.user}
|
|
166
|
+
type="button"
|
|
167
|
+
className={`presence-face${isFollowed ? " presence-face-followed" : ""}`}
|
|
168
|
+
data-testid="presence-face"
|
|
169
|
+
data-user={person.user}
|
|
170
|
+
// The followed face is ringed in its own colour, outside the
|
|
171
|
+
// bar-coloured border every face has.
|
|
172
|
+
style={{
|
|
173
|
+
background: person.color,
|
|
174
|
+
...(isFollowed ? { boxShadow: `0 0 0 2px ${person.color}` } : {}),
|
|
175
|
+
}}
|
|
176
|
+
aria-pressed={isFollowed}
|
|
177
|
+
aria-label={`${person.name}, ${isFollowed ? "following. Escape stops" : "click to follow"}`}
|
|
178
|
+
onClick={() => store.presence.follow(isFollowed ? null : person.user)}
|
|
179
|
+
onPointerEnter={() => setCard(person.user)}
|
|
180
|
+
onPointerLeave={() => setCard(null)}
|
|
181
|
+
onFocus={() => setCard(person.user)}
|
|
182
|
+
onBlur={() => setCard(null)}
|
|
183
|
+
>
|
|
184
|
+
{person.avatarUrl === null ? initialsOf(person.name) : <img src={person.avatarUrl} alt="" />}
|
|
185
|
+
{card === person.user ? (
|
|
186
|
+
<span className="presence-card" role="tooltip" data-testid="presence-card">
|
|
187
|
+
<span className="presence-card-who">
|
|
188
|
+
<span className="presence-card-avatar" style={{ background: person.color }}>
|
|
189
|
+
{person.avatarUrl === null ? initialsOf(person.name) : <img src={person.avatarUrl} alt="" />}
|
|
190
|
+
</span>
|
|
191
|
+
<span className="presence-card-name">
|
|
192
|
+
<span>{person.name}</span>
|
|
193
|
+
<span className="presence-card-role">
|
|
194
|
+
{person.role === "edit" ? "Editor" : "Viewer"}
|
|
195
|
+
{person.tabs > 1 ? ` · ${person.tabs} tabs` : ""}
|
|
196
|
+
</span>
|
|
197
|
+
</span>
|
|
198
|
+
</span>
|
|
199
|
+
{place === null ? null : (
|
|
200
|
+
<span className="presence-card-where">
|
|
201
|
+
<span className="presence-card-number">{place.number}</span>
|
|
202
|
+
<span className="presence-card-shot">{place.shot}</span>
|
|
203
|
+
{place.layer === null ? null : (
|
|
204
|
+
<>
|
|
205
|
+
<span className="presence-card-slash">/</span>
|
|
206
|
+
<span className="presence-card-swatch" style={{ borderColor: person.color }} />
|
|
207
|
+
<span className="presence-card-layer">{place.layer}</span>
|
|
208
|
+
</>
|
|
209
|
+
)}
|
|
210
|
+
</span>
|
|
211
|
+
)}
|
|
212
|
+
<span className="presence-card-foot">
|
|
213
|
+
<span>{isFollowed ? "Following" : "Click to follow"}</span>
|
|
214
|
+
<span>Esc stops</span>
|
|
215
|
+
</span>
|
|
216
|
+
</span>
|
|
217
|
+
) : null}
|
|
218
|
+
</button>
|
|
219
|
+
);
|
|
220
|
+
})}
|
|
221
|
+
{more > 0 ? (
|
|
222
|
+
<span
|
|
223
|
+
className="presence-face presence-more"
|
|
224
|
+
data-testid="presence-more"
|
|
225
|
+
title={people
|
|
226
|
+
.slice(shownPeople.length)
|
|
227
|
+
.map((p) => p.name)
|
|
228
|
+
.join(", ")}
|
|
229
|
+
>
|
|
230
|
+
+{more}
|
|
231
|
+
</span>
|
|
232
|
+
) : null}
|
|
233
|
+
</div>
|
|
234
|
+
{/* An MCP agent editing the Project as a whole (ops naming no layer):
|
|
235
|
+
the canvas cell's frame in the Agent's colour, the follow frame's
|
|
236
|
+
shape. Its layers, when it names some, are outlined on the stage
|
|
237
|
+
instead (setPeerSelections). */}
|
|
238
|
+
{followed === null && mcpAgents.some((a) => a.busy && a.tool === "shot_batch" && a.layers.length === 0) ? (
|
|
239
|
+
<div
|
|
240
|
+
className="presence-follow-frame is-agent"
|
|
241
|
+
data-testid="agent-frame"
|
|
242
|
+
aria-hidden="true"
|
|
243
|
+
/>
|
|
244
|
+
) : null}
|
|
245
|
+
{followed === null ? null : (
|
|
246
|
+
<>
|
|
247
|
+
<div
|
|
248
|
+
className="presence-follow-frame"
|
|
249
|
+
data-testid="presence-follow-frame"
|
|
250
|
+
aria-hidden="true"
|
|
251
|
+
style={{ borderColor: followed.color }}
|
|
252
|
+
/>
|
|
253
|
+
<div className="presence-follow-pill" data-testid="presence-follow-pill" role="status">
|
|
254
|
+
<span className="presence-follow-dot" style={{ background: followed.color }} />
|
|
255
|
+
Following {followed.name} · Esc to stop
|
|
256
|
+
</div>
|
|
257
|
+
</>
|
|
258
|
+
)}
|
|
259
|
+
</>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* WHAT THE AGENT IS DOING NOW, beside its face: its step, with dots while a
|
|
265
|
+
* call is in flight, and Follow while it is writing a file this tab is not
|
|
266
|
+
* following yet.
|
|
267
|
+
*/
|
|
268
|
+
function AgentStatusChip({ agent }: { agent: AgentWork }): ReactNode {
|
|
269
|
+
const store = useStore();
|
|
270
|
+
const follow = useShotEditor((s) => s.codeFollow);
|
|
271
|
+
const question = useSyncExternalStore(store.agentQuestion.subscribe, store.agentQuestion.getState);
|
|
272
|
+
const docId = useShotEditor((s) => s.doc?.id ?? null);
|
|
273
|
+
const rel = agent.wroteFile;
|
|
274
|
+
// Asking, or waiting on the answer, with this Project's dialog closed:
|
|
275
|
+
// Answer opens it.
|
|
276
|
+
const offerAnswer =
|
|
277
|
+
(agent.tool === "ask_user" || agent.tool === "get_answer") &&
|
|
278
|
+
question.question?.status === "open" &&
|
|
279
|
+
question.question.projectId === docId &&
|
|
280
|
+
!question.open;
|
|
281
|
+
const offerFollow = !offerAnswer && rel !== null && rel !== follow;
|
|
282
|
+
return (
|
|
283
|
+
<span className="agent-status-chip" data-testid="agent-status-chip" role="status">
|
|
284
|
+
{agent.busy ? <AgentDots label={`${agent.name} is working`} /> : <span className="agent-status-dot" aria-hidden="true" />}
|
|
285
|
+
<span className="agent-status-text">
|
|
286
|
+
<span className="agent-status-name">{agent.name}:</span> {agent.step}
|
|
287
|
+
</span>
|
|
288
|
+
{offerAnswer ? (
|
|
289
|
+
<button
|
|
290
|
+
type="button"
|
|
291
|
+
className="agent-status-follow"
|
|
292
|
+
data-testid="agent-status-answer"
|
|
293
|
+
title="Open the agent's question"
|
|
294
|
+
onClick={() => store.agentQuestion.show()}
|
|
295
|
+
>
|
|
296
|
+
Answer
|
|
297
|
+
</button>
|
|
298
|
+
) : null}
|
|
299
|
+
{offerFollow ? (
|
|
300
|
+
<button
|
|
301
|
+
type="button"
|
|
302
|
+
className="agent-status-follow"
|
|
303
|
+
data-testid="agent-status-follow"
|
|
304
|
+
title={`Open ${rel} in Code Studio and follow each write`}
|
|
305
|
+
onClick={() => void store.followAgentFile(rel)}
|
|
306
|
+
>
|
|
307
|
+
Follow
|
|
308
|
+
</button>
|
|
309
|
+
) : null}
|
|
310
|
+
</span>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The people whose place matches `at`, as small dots in their colours after
|
|
315
|
+
* a row's label or a frame's caption (page 21: "Badge lands ●●"). Null with
|
|
316
|
+
* nobody there. */
|
|
317
|
+
export function PeerDots({ at }: { at: (person: PresencePerson) => boolean }): ReactNode {
|
|
318
|
+
const { live, people } = usePresence();
|
|
319
|
+
if (!live) return null;
|
|
320
|
+
const here = people.filter(at);
|
|
321
|
+
if (here.length === 0) return null;
|
|
322
|
+
return (
|
|
323
|
+
<span className="peer-dots" data-testid="peer-dots" title={here.map((p) => p.name).join(", ")}>
|
|
324
|
+
{here.map((p) => (
|
|
325
|
+
<span key={p.user} className="peer-dot" style={{ background: p.color }} />
|
|
326
|
+
))}
|
|
327
|
+
</span>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// The editor has exactly one Project open. Project lifecycle lives at
|
|
2
|
+
// /projects/; this header only names the current one and offers the way back:
|
|
3
|
+
// `Projects › title › shot`, where the root crumb is a link that leaves through
|
|
4
|
+
// `leaveFor` like the app menu's row.
|
|
5
|
+
//
|
|
6
|
+
// ── AND IT NAMES THE LEVEL, NOT JUST THE PROJECT ─────────────────────────
|
|
7
|
+
//
|
|
8
|
+
// The chrome always knew which shot the author was in (`enteredScene`) and
|
|
9
|
+
// never said so up here. The trail that did name it lives in the left rail's
|
|
10
|
+
// head, which is gone the moment the rail is collapsed, and the only other way
|
|
11
|
+
// out was a chip in the timeline dock's head, which is gone with the dock.
|
|
12
|
+
// Collapse both and there was no route back to the project level anywhere on
|
|
13
|
+
// screen. The header never collapses, so the crumb belongs here.
|
|
14
|
+
//
|
|
15
|
+
// A component is not a level of this trail: it opens in the Components
|
|
16
|
+
// workspace, which the workspace switch names, and its canvas bar and left
|
|
17
|
+
// column carry the way back to All components.
|
|
18
|
+
//
|
|
19
|
+
// The crumb is a SWITCHER and not only a back button: "which shot am I in" and
|
|
20
|
+
// "take me to another one" are the same question at this level, and a menu
|
|
21
|
+
// costs the same one click a plain back button would have.
|
|
22
|
+
import { useEffect, useRef, useState, type CSSProperties } from "react";
|
|
23
|
+
import { Popover } from "@dailies-studio/ui";
|
|
24
|
+
import type { Doc3D } from "@dailies-studio/shot/doc";
|
|
25
|
+
import { CHEVRON_DOWN_ICON, CHEVRON_RIGHT_ICON, SCENE_ICON } from "../../icons.js";
|
|
26
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
27
|
+
import { commandBlocker } from "../../state/workspace.js";
|
|
28
|
+
import { leaveFor } from "./leave.js";
|
|
29
|
+
|
|
30
|
+
/** The rows the switcher shows: one per shot, in document order.
|
|
31
|
+
*
|
|
32
|
+
* A free function taking the document rather than a selector, because it
|
|
33
|
+
* ALLOCATES. See the subscription note below: anything this component
|
|
34
|
+
* subscribes to is compared with `Object.is` on every store notification, so a
|
|
35
|
+
* selector shaped like this one would re-render the whole header on each one. */
|
|
36
|
+
function sceneRows(doc: Doc3D | null): ReadonlyArray<{ id: string; name: string }> {
|
|
37
|
+
if (doc === null) return [];
|
|
38
|
+
return doc.scenes.map((id) => ({ id, name: doc.scenesById[id]?.name ?? id }));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const NO_ROWS: ReadonlyArray<{ id: string; name: string }> = [];
|
|
42
|
+
|
|
43
|
+
/** THE LABEL OWNS THE CLIP, not the button.
|
|
44
|
+
*
|
|
45
|
+
* `.project-crumb` is an `inline-flex` with `max-width`, so a bare text child
|
|
46
|
+
* becomes an anonymous flex item. `text-overflow` is not inherited and applies
|
|
47
|
+
* to a block container's own inline content, so it never reaches that item:
|
|
48
|
+
* the button clipped a long shot name mid-glyph instead of ellipsizing it.
|
|
49
|
+
* A real element with `min-width: 0` is a flex item that can shrink, and it
|
|
50
|
+
* carries the ellipsis itself.
|
|
51
|
+
*
|
|
52
|
+
* Inline rather than a class because `styles.css` belongs to another lane this
|
|
53
|
+
* round; move it to `.project-crumb-label` whenever that file is next open. */
|
|
54
|
+
const CRUMB_LABEL: CSSProperties = { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" };
|
|
55
|
+
|
|
56
|
+
export function ProjectTitle() {
|
|
57
|
+
const store = useStore();
|
|
58
|
+
const docId = useShotEditor((state) => state.doc?.id ?? null);
|
|
59
|
+
const title = useShotEditor((state) => state.doc?.title ?? state.name ?? "Untitled Project");
|
|
60
|
+
const open = useShotEditor((state) => state.doc !== null);
|
|
61
|
+
// ── SUBSCRIBE TO STRINGS, NEVER TO `doc` ────────────────────────────────
|
|
62
|
+
//
|
|
63
|
+
// This sits in the header, which must survive playback: `useShotEditor`
|
|
64
|
+
// compares with `Object.is` per notification, so a selector returning a
|
|
65
|
+
// computed object or array re-renders every control up here on each one, and
|
|
66
|
+
// a `doc` subscription re-renders them on each keystroke of any edit. Both
|
|
67
|
+
// labels are resolved INSIDE the selector, so what crosses the boundary is a
|
|
68
|
+
// string that changes only when the words change.
|
|
69
|
+
const enteredScene = useShotEditor((state) => state.enteredScene);
|
|
70
|
+
const shotLabel = useShotEditor((state) =>
|
|
71
|
+
state.enteredScene === null
|
|
72
|
+
? "All shots"
|
|
73
|
+
: (state.doc?.scenesById[state.enteredScene]?.name ?? "Shot"),
|
|
74
|
+
);
|
|
75
|
+
const [draft, setDraft] = useState(title);
|
|
76
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
77
|
+
const anchor = useRef<HTMLButtonElement>(null);
|
|
78
|
+
const cancel = useRef(false);
|
|
79
|
+
|
|
80
|
+
useEffect(() => setDraft(title), [docId, title]);
|
|
81
|
+
|
|
82
|
+
const commit = (): void => {
|
|
83
|
+
if (cancel.current) {
|
|
84
|
+
cancel.current = false;
|
|
85
|
+
setDraft(title);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
store.setProjectTitle(draft);
|
|
89
|
+
const committed = store.getState().doc?.title ?? store.getState().name ?? "Untitled Project";
|
|
90
|
+
if (committed !== draft) setDraft(committed);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// THE LIST IS READ WHEN THE MENU OPENS, not subscribed to. `doc.scenesById`
|
|
94
|
+
// is a fresh object after any edit inside any shot, so subscribing to it
|
|
95
|
+
// would cost the header a render per keystroke of a layer rename, for a list
|
|
96
|
+
// nobody is looking at.
|
|
97
|
+
//
|
|
98
|
+
// THE IDS ARE SUBSCRIBED THOUGH, as one joined string, because the menu can
|
|
99
|
+
// outlive the list it drew. `Popover` intercepts Escape only, so ⌘Z reaches
|
|
100
|
+
// the keymap (`state/keymap.ts`) and an undo can delete a shot with the menu
|
|
101
|
+
// still open; none of the four labels above change when some OTHER shot
|
|
102
|
+
// appears or disappears, so nothing would re-render and the row would stay
|
|
103
|
+
// clickable, reaching `enterSceneAt`'s `scene === undefined` refusal as a
|
|
104
|
+
// dead click. A string still crosses the boundary, so the `Object.is` rule
|
|
105
|
+
// holds and the header re-renders only when the shot list itself changes.
|
|
106
|
+
// A rename while the menu is open still shows the old word until the next
|
|
107
|
+
// render, which is cosmetic and does not make the row lie about where it goes.
|
|
108
|
+
// Joined on a comma, which no generated shot id carries (`sc_<slug>`), so two
|
|
109
|
+
// different lists cannot produce the same key.
|
|
110
|
+
const sceneKey = useShotEditor((state) => state.doc?.scenes.join(",") ?? "");
|
|
111
|
+
const switchBlocked = useShotEditor((state) => commandBlocker(state, "ctx.shot"));
|
|
112
|
+
// The Project level, refused on the board: the row says why, as the app
|
|
113
|
+
// menu's does, rather than closing on a leave that never happens.
|
|
114
|
+
const allBlocked = useShotEditor((state) => commandBlocker(state, "ctx.scene"));
|
|
115
|
+
const rows = menuOpen && sceneKey !== "" ? sceneRows(store.getState().doc) : NO_ROWS;
|
|
116
|
+
|
|
117
|
+
/** The same triple the rail's back crumb runs, in the same order: leave the
|
|
118
|
+
* definition and any isolation before leaving the shot, because each of
|
|
119
|
+
* those is scoped by the one after it. */
|
|
120
|
+
const allShots = (): void => {
|
|
121
|
+
store.exitIsolationAll();
|
|
122
|
+
store.editComponent(null);
|
|
123
|
+
store.leaveScene();
|
|
124
|
+
setMenuOpen(false);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="project-identity">
|
|
129
|
+
{/* THE ROOT CRUMB IS BACK (Paper redesign, 2026-10-04): Projects ›
|
|
130
|
+
Project › Shot, the trail the design system's breadcrumb draws. A
|
|
131
|
+
real link, so a middle click opens the list in a new tab; a plain
|
|
132
|
+
click goes through `leaveFor`, which flushes autosave first and
|
|
133
|
+
stays put when the save fails. */}
|
|
134
|
+
<a
|
|
135
|
+
className="project-home"
|
|
136
|
+
href="/projects/"
|
|
137
|
+
onClick={(event) => {
|
|
138
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
leaveFor(store, "/projects/");
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
Projects
|
|
144
|
+
</a>
|
|
145
|
+
<span className="project-crumb-sep" aria-hidden="true">
|
|
146
|
+
{CHEVRON_RIGHT_ICON}
|
|
147
|
+
</span>
|
|
148
|
+
<input
|
|
149
|
+
className="project-title-input"
|
|
150
|
+
aria-label="Project title"
|
|
151
|
+
value={draft}
|
|
152
|
+
disabled={!open}
|
|
153
|
+
maxLength={80}
|
|
154
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
155
|
+
onBlur={commit}
|
|
156
|
+
onKeyDown={(event) => {
|
|
157
|
+
if (event.key === "Enter") event.currentTarget.blur();
|
|
158
|
+
if (event.key === "Escape") {
|
|
159
|
+
cancel.current = true;
|
|
160
|
+
setDraft(title);
|
|
161
|
+
event.currentTarget.blur();
|
|
162
|
+
}
|
|
163
|
+
}}
|
|
164
|
+
/>
|
|
165
|
+
{!open ? null : (
|
|
166
|
+
<>
|
|
167
|
+
<span className="project-crumb-sep" aria-hidden="true">
|
|
168
|
+
{CHEVRON_RIGHT_ICON}
|
|
169
|
+
</span>
|
|
170
|
+
<button
|
|
171
|
+
ref={anchor}
|
|
172
|
+
type="button"
|
|
173
|
+
className="btn project-crumb is-current"
|
|
174
|
+
data-testid="header-shot-crumb"
|
|
175
|
+
aria-haspopup="menu"
|
|
176
|
+
aria-expanded={menuOpen}
|
|
177
|
+
// A mode shows one object or one component: jumping to a shot
|
|
178
|
+
// from inside it would leave the mode showing the wrong thing.
|
|
179
|
+
disabled={switchBlocked !== null}
|
|
180
|
+
title={switchBlocked ?? "Switch shot"}
|
|
181
|
+
onClick={() => setMenuOpen((was) => !was)}
|
|
182
|
+
>
|
|
183
|
+
<span style={CRUMB_LABEL}>{shotLabel}</span>
|
|
184
|
+
{/* AN SVG, NOT "▾". Every glyph button in this header draws a real
|
|
185
|
+
icon, and header.test.tsx holds that line by asserting the
|
|
186
|
+
toggle group's textContent is empty. A text arrow here would be
|
|
187
|
+
the one character in the bar that reads as content. */}
|
|
188
|
+
{CHEVRON_DOWN_ICON}
|
|
189
|
+
</button>
|
|
190
|
+
<Popover open={menuOpen} anchor={anchor} onClose={() => setMenuOpen(false)}>
|
|
191
|
+
<div className="project-crumb-menu" role="menu">
|
|
192
|
+
<button
|
|
193
|
+
type="button"
|
|
194
|
+
role="menuitem"
|
|
195
|
+
className={`project-crumb-item${enteredScene === null ? " is-current" : ""}`}
|
|
196
|
+
disabled={allBlocked !== null}
|
|
197
|
+
title={allBlocked ?? undefined}
|
|
198
|
+
onClick={allShots}
|
|
199
|
+
>
|
|
200
|
+
All shots
|
|
201
|
+
</button>
|
|
202
|
+
{rows.map((row) => (
|
|
203
|
+
<button
|
|
204
|
+
key={row.id}
|
|
205
|
+
type="button"
|
|
206
|
+
role="menuitem"
|
|
207
|
+
className={`project-crumb-item${row.id === enteredScene ? " is-current" : ""}`}
|
|
208
|
+
onClick={() => {
|
|
209
|
+
// LEAVE THE DEFINITION FIRST, the order `allShots` above
|
|
210
|
+
// and the rail's back crumb both use. `editComponentAt`
|
|
211
|
+
// never writes `enteredScene` and `enterSceneAt` never
|
|
212
|
+
// writes `editingDef`, so the two coexist: without this,
|
|
213
|
+
// picking the shot you are already in is swallowed by
|
|
214
|
+
// `enterSceneAt`'s `sceneId === state.enteredScene` early
|
|
215
|
+
// return and picking another one swaps the shot while the
|
|
216
|
+
// stage keeps rendering the definition.
|
|
217
|
+
store.editComponent(null);
|
|
218
|
+
store.enterScene(row.id);
|
|
219
|
+
setMenuOpen(false);
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
{SCENE_ICON}
|
|
223
|
+
{row.name}
|
|
224
|
+
</button>
|
|
225
|
+
))}
|
|
226
|
+
</div>
|
|
227
|
+
</Popover>
|
|
228
|
+
</>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// "Share" in the top bar, beside Export. HOSTED ONLY: the local CLI has no
|
|
2
|
+
// accounts to share between, so `isCloudWorkspace()` keeps it from asking.
|
|
3
|
+
//
|
|
4
|
+
// The editor keeps no rule about who may share. It asks `/auth/me?project=`
|
|
5
|
+
// (worker/src/auth.ts), which answers this Project's actions from the same
|
|
6
|
+
// grants the dashboard list reads, and draws the button only when they hold
|
|
7
|
+
// `project.share`.
|
|
8
|
+
//
|
|
9
|
+
// The access dialog lives in the dashboard (Members.tsx `ProjectAccessDialog`),
|
|
10
|
+
// so the button opens it there, in a new tab so the editor stays open. `ws`
|
|
11
|
+
// lets the dashboard tell when its session sits in another workspace, rather
|
|
12
|
+
// than opening a same-named project there.
|
|
13
|
+
import { useEffect, useState } from "react";
|
|
14
|
+
import { IconShare } from "@dailies-studio/ui";
|
|
15
|
+
import { useShotEditor } from "../../state/context.js";
|
|
16
|
+
import { isCloudWorkspace } from "../../state/posterCapture.js";
|
|
17
|
+
|
|
18
|
+
interface MeProject {
|
|
19
|
+
signedIn?: boolean;
|
|
20
|
+
workspace?: { id?: string } | null;
|
|
21
|
+
projectActions?: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ShareButton() {
|
|
25
|
+
const name = useShotEditor((s) => s.name);
|
|
26
|
+
const [workspace, setWorkspace] = useState<string | null>(null);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setWorkspace(null);
|
|
29
|
+
if (name === null || !isCloudWorkspace()) return;
|
|
30
|
+
let live = true;
|
|
31
|
+
// A failed probe draws nothing: the dashboard's Share stays the way in.
|
|
32
|
+
void fetch(`/auth/me?project=${encodeURIComponent(`shot/${name}`)}`, { credentials: "same-origin" })
|
|
33
|
+
.then((res) => (res.ok ? (res.json() as Promise<MeProject>) : null))
|
|
34
|
+
.then((me) => {
|
|
35
|
+
const id = me?.workspace?.id;
|
|
36
|
+
if (live && me?.signedIn === true && typeof id === "string" && me.projectActions?.includes("project.share") === true) {
|
|
37
|
+
setWorkspace(id);
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
.catch(() => {});
|
|
41
|
+
return () => {
|
|
42
|
+
live = false;
|
|
43
|
+
};
|
|
44
|
+
}, [name]);
|
|
45
|
+
if (name === null || workspace === null) return null;
|
|
46
|
+
const href = `/projects/?${new URLSearchParams({ share: `shot/${name}`, ws: workspace }).toString()}`;
|
|
47
|
+
return (
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
className="btn header-share"
|
|
51
|
+
aria-label="Share"
|
|
52
|
+
title="Share this project"
|
|
53
|
+
onClick={() => window.open(href, "_blank", "noopener")}
|
|
54
|
+
>
|
|
55
|
+
<IconShare aria-hidden="true" />
|
|
56
|
+
Share
|
|
57
|
+
</button>
|
|
58
|
+
);
|
|
59
|
+
}
|