@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,373 @@
|
|
|
1
|
+
// THE CODE STUDIO (C2): one code component's source, beside a live render of
|
|
2
|
+
// what that source produces. It is the Components workspace's Edit tab for a
|
|
3
|
+
// code component; Preview is ComponentsPreview.tsx.
|
|
4
|
+
//
|
|
5
|
+
// It is `ComponentStudio`'s shape and for that file's reasons: the mounted
|
|
6
|
+
// `StagePanel` stays exactly where it is, and one element in the canvas cell is
|
|
7
|
+
// a HOLE the stage renders into. So the picture beside the editor is the real
|
|
8
|
+
// component under the real renderer, at its contract's own defaults, which is
|
|
9
|
+
// the only way "what I am editing" and "what will ship" can be the same thing.
|
|
10
|
+
//
|
|
11
|
+
// ── IN THE DOCKED CELLS ─────────────────────────────────────────────────
|
|
12
|
+
//
|
|
13
|
+
// LEFT the components list (ComponentLibraryRail), as on every tab
|
|
14
|
+
// CENTRE the CODE PANE (file bar, the open buffer, the 48 status footer),
|
|
15
|
+
// then the LIVE PANE (a 40 bar, then the hole), 420 wide
|
|
16
|
+
// RIGHT the component's actions and props (InspectorPanel's code branch)
|
|
17
|
+
// BAR the way back, the breadcrumb, the tab switch, Save and the zoom
|
|
18
|
+
//
|
|
19
|
+
// No transport under the centre and no timeline: the code pane carries its own
|
|
20
|
+
// footer, and the live pane loops on the component clock.
|
|
21
|
+
//
|
|
22
|
+
// ONE SUBTREE, PORTALLED. The buffer feeds both the editor in the canvas cell
|
|
23
|
+
// and the Save in the bar, and the back chevron and the Escape rung share one
|
|
24
|
+
// dirty confirm. The mode portals only while it IS the workspace
|
|
25
|
+
// (`workspaceOf`) on its Edit tab (`componentsTab`), so no two modes can ever
|
|
26
|
+
// fill one cell.
|
|
27
|
+
//
|
|
28
|
+
// ── THE SUBJECT AND THE BUFFER ──────────────────────────────────────────
|
|
29
|
+
//
|
|
30
|
+
// `codeComponent` is what the stage draws; `codeActive` is what the editor
|
|
31
|
+
// shows. The Files toggle in the file bar opens the project's files beside
|
|
32
|
+
// the editor, docked inside the code pane, and a file opened from it moves the
|
|
33
|
+
// buffer and never the subject: reading a helper is not a request to stop
|
|
34
|
+
// looking at the component it feeds.
|
|
35
|
+
//
|
|
36
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
37
|
+
import { useConfirm } from "@dailies-studio/ui";
|
|
38
|
+
import { framesToSeconds } from "@dailies-studio/engine";
|
|
39
|
+
import { CHEVRON_LEFT_ICON } from "../../icons.js";
|
|
40
|
+
import { instanceExportOf } from "@dailies-studio/shot/doc";
|
|
41
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
42
|
+
import { isEditable } from "../../state/keymap.js";
|
|
43
|
+
import { shortcutHint } from "../../state/shortcuts.js";
|
|
44
|
+
import { activeCodeFile } from "../../state/types.js";
|
|
45
|
+
import type { ComponentBuild } from "../../state/types.js";
|
|
46
|
+
import { workspaceOf } from "../../state/workspace.js";
|
|
47
|
+
import { CODE_LIVE_WIDTH, CODE_PANE_MIN } from "../../state/panelSize.js";
|
|
48
|
+
import { ResizeHandle } from "../ResizeHandle.js";
|
|
49
|
+
import { InCell } from "../stage/cellSlots.js";
|
|
50
|
+
import { formatDuration } from "../timeline/model.js";
|
|
51
|
+
import { dirtyCodeFileCount, discardBuffersMessage } from "./codeBufferGuard.js";
|
|
52
|
+
import { CodeFileStrips, CodePane, CodeTabs, TabWriter, useCodeDiagnostics } from "./codeParts.js";
|
|
53
|
+
import { FileTree } from "./FileTree.js";
|
|
54
|
+
import { ComponentLibraryRail } from "../studio/ComponentLibraryRail.js";
|
|
55
|
+
import { ComponentsTabSwitch, StudioZoom } from "../studio/ComponentStudio.js";
|
|
56
|
+
import { ComponentCrumb, ComponentsBack, useLoopLength, useNewPropKeys } from "../studio/componentParts.js";
|
|
57
|
+
|
|
58
|
+
export function CodeStudio() {
|
|
59
|
+
const store = useStore();
|
|
60
|
+
// THIS MODE'S CELLS, and only while it is the workspace on screen, on the
|
|
61
|
+
// Edit tab of Components.
|
|
62
|
+
const open = useShotEditor(
|
|
63
|
+
(s) => workspaceOf(s) === "components" && s.codeComponent !== null && s.componentsTab === "edit",
|
|
64
|
+
);
|
|
65
|
+
const componentId = useShotEditor((s) => s.codeComponent);
|
|
66
|
+
const doc = useShotEditor((s) => s.doc);
|
|
67
|
+
const file = useShotEditor(activeCodeFile);
|
|
68
|
+
const library = useShotEditor((s) => s.library);
|
|
69
|
+
const readOnly = useShotEditor((s) => s.readOnly);
|
|
70
|
+
// ACROSS EVERY BUFFER, not the one on screen. Leaving drops them all, so the
|
|
71
|
+
// question has to be asked about all of them.
|
|
72
|
+
const dirtyCount = useShotEditor((s) => dirtyCodeFileCount(s.codeFiles));
|
|
73
|
+
const build = useShotEditor((s) => (s.codeComponent === null ? undefined : s.componentBuilds[s.codeComponent]));
|
|
74
|
+
const propKeys = useShotEditor((s) => (s.codeComponent === null ? null : s.contracts[s.codeComponent]?.keys));
|
|
75
|
+
const fresh = useNewPropKeys(propKeys === null || propKeys === undefined ? [] : Object.keys(propKeys));
|
|
76
|
+
const { frames, fps } = useLoopLength();
|
|
77
|
+
// The seam between the code and the live pane measures both at the press.
|
|
78
|
+
const studioRef = useRef<HTMLElement | null>(null);
|
|
79
|
+
const liveRef = useRef<HTMLDivElement | null>(null);
|
|
80
|
+
const liveCeiling = (): number | undefined => {
|
|
81
|
+
const width = studioRef.current?.getBoundingClientRect().width ?? 0;
|
|
82
|
+
return width > 0 ? width - CODE_PANE_MIN : undefined;
|
|
83
|
+
};
|
|
84
|
+
const bufferCount = useShotEditor((s) => s.codeOrder.length);
|
|
85
|
+
const [filesOpen, setFilesOpen] = useState(false);
|
|
86
|
+
const { confirm, dialog } = useConfirm();
|
|
87
|
+
|
|
88
|
+
const dirty = file !== null && file.content !== file.savedContent;
|
|
89
|
+
|
|
90
|
+
// LEAVING IS THE ONE PLACE THAT CAN LOSE WORK, so it is the one place that
|
|
91
|
+
// asks. The store's verb never blocks (a store cannot open a dialog), which
|
|
92
|
+
// is why the confirm lives here and every door (Back to Shot, the chevron
|
|
93
|
+
// to All components, Escape) comes through it.
|
|
94
|
+
const guard = useCallback(
|
|
95
|
+
(then: () => void) => {
|
|
96
|
+
if (dirtyCount === 0) {
|
|
97
|
+
then();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
void confirm({
|
|
101
|
+
title: "Unsaved changes",
|
|
102
|
+
message: discardBuffersMessage(dirtyCount),
|
|
103
|
+
confirmLabel: "Discard",
|
|
104
|
+
danger: true,
|
|
105
|
+
}).then((ok) => {
|
|
106
|
+
if (ok) then();
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
[confirm, dirtyCount],
|
|
110
|
+
);
|
|
111
|
+
// ESCAPE: back to the Shot a Shot door opened this from, else out.
|
|
112
|
+
const leave = useCallback(
|
|
113
|
+
() =>
|
|
114
|
+
guard(() =>
|
|
115
|
+
store.getState().componentsOrigin !== null ? void store.backToShot() : store.closeCodeComponent(),
|
|
116
|
+
),
|
|
117
|
+
[guard, store],
|
|
118
|
+
);
|
|
119
|
+
const fromShot = useShotEditor((s) => s.componentsOrigin !== null);
|
|
120
|
+
|
|
121
|
+
// AND AN ESCAPE THE PANEL DID NOT HEAR: one pressed with the stage iframe
|
|
122
|
+
// focused reaches the store's keymap, which asks here rather than closing.
|
|
123
|
+
// Answered once per request, because `leave` changes with the dirty count.
|
|
124
|
+
// A request made while this tab is not up is ComponentsPreview's, and is
|
|
125
|
+
// marked seen here, so swapping to Edit never answers it a second time.
|
|
126
|
+
const backRequest = useShotEditor((s) => s.codeBackRequest);
|
|
127
|
+
const answered = useRef<object | null>(null);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (backRequest === answered.current) return;
|
|
130
|
+
answered.current = backRequest;
|
|
131
|
+
if (!open || backRequest === null) return;
|
|
132
|
+
leave();
|
|
133
|
+
}, [open, backRequest, leave]);
|
|
134
|
+
|
|
135
|
+
// ESCAPE COMES HERE while this mode is open, so a dirty buffer gets the same
|
|
136
|
+
// question the back chevron asks.
|
|
137
|
+
//
|
|
138
|
+
// ON THE DOCUMENT, IN THE BUBBLE PHASE: after everything on screen that owns
|
|
139
|
+
// an Escape of its own, and before the keymap.
|
|
140
|
+
// - A menu, a popover and a live drag take Escape at window capture and
|
|
141
|
+
// stop it, so it never arrives here.
|
|
142
|
+
// - A Dialog stops it in React. CodeMirror marks one it used (closing its
|
|
143
|
+
// search panel, collapsing a selection) with preventDefault.
|
|
144
|
+
// - A field outside the editor (the Project title, a default, the Agent
|
|
145
|
+
// draft) keeps its Escape to revert or cancel.
|
|
146
|
+
// - The keymap listens on the window, bubble phase, so it runs after this,
|
|
147
|
+
// and every other Escape stops here.
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!open) return;
|
|
150
|
+
const onKey = (e: KeyboardEvent): void => {
|
|
151
|
+
if (e.key !== "Escape") return;
|
|
152
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
153
|
+
if (target !== null && isEditable(target) && target.closest(".code-studio-pane") === null) return;
|
|
154
|
+
e.stopPropagation();
|
|
155
|
+
if (e.defaultPrevented || target?.closest(".dialog") != null) return;
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
leave();
|
|
158
|
+
};
|
|
159
|
+
document.addEventListener("keydown", onKey);
|
|
160
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
161
|
+
}, [open, leave]);
|
|
162
|
+
|
|
163
|
+
// THE TYPECHECK of the active buffer (`useCodeDiagnostics`), while this mode
|
|
164
|
+
// is on screen.
|
|
165
|
+
useCodeDiagnostics(open ? (file?.rel ?? null) : null, file?.content ?? null);
|
|
166
|
+
|
|
167
|
+
if (!open || componentId === null) return null;
|
|
168
|
+
|
|
169
|
+
const row = library.find((r) => r.id === componentId);
|
|
170
|
+
const name = row?.name ?? instanceExportOf(componentId);
|
|
171
|
+
const errors = file === null ? 0 : file.diagnostics.filter((d) => d.category === "error").length;
|
|
172
|
+
const tabRel = file?.rel ?? row?.rel ?? null;
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<>
|
|
176
|
+
{/* The confirm portals to <body> itself (house Dialog), so it lands in
|
|
177
|
+
no cell and paints over all of them. */}
|
|
178
|
+
{dialog}
|
|
179
|
+
|
|
180
|
+
<InCell name="bar">
|
|
181
|
+
{fromShot ? (
|
|
182
|
+
<ComponentsBack testId="code-back" guard={guard} />
|
|
183
|
+
) : (
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
className="code-back"
|
|
187
|
+
data-testid="code-back"
|
|
188
|
+
title="All components"
|
|
189
|
+
aria-label="All components"
|
|
190
|
+
onClick={() => guard(() => void store.openComponents())}
|
|
191
|
+
>
|
|
192
|
+
{CHEVRON_LEFT_ICON}
|
|
193
|
+
</button>
|
|
194
|
+
)}
|
|
195
|
+
{/* THE SUBJECT, which is what the stage is drawing, NOT the buffer.
|
|
196
|
+
The file tab in the code pane says the other. */}
|
|
197
|
+
<ComponentCrumb
|
|
198
|
+
sel={{ kind: "code", module: componentId }}
|
|
199
|
+
name={
|
|
200
|
+
<span className="code-studio-name" title={name}>
|
|
201
|
+
{name}
|
|
202
|
+
</span>
|
|
203
|
+
}
|
|
204
|
+
/>
|
|
205
|
+
{dirtyCount > 0 ? (
|
|
206
|
+
<span
|
|
207
|
+
className="code-studio-dirty"
|
|
208
|
+
data-testid="code-dirty"
|
|
209
|
+
title={dirtyCount === 1 ? "One file has unsaved changes" : `${dirtyCount} files have unsaved changes`}
|
|
210
|
+
/>
|
|
211
|
+
) : null}
|
|
212
|
+
<div className="component-studio-tools">
|
|
213
|
+
<ComponentsTabSwitch />
|
|
214
|
+
{/* SAVING IS EXPLICIT. Every save rebuilds, re-extracts the contract
|
|
215
|
+
and records a version, so a keystroke-triggered one would show the
|
|
216
|
+
author a stream of builds of half-typed code. */}
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
className="btn is-primary"
|
|
220
|
+
data-testid="code-save"
|
|
221
|
+
disabled={file === null || !dirty || file.status === "saving" || file.generated !== null}
|
|
222
|
+
title={file !== null && file.generated !== null ? "Generated files are read-only" : `Save${shortcutHint("file.save")}`}
|
|
223
|
+
onClick={() => void store.saveCodeFile()}
|
|
224
|
+
>
|
|
225
|
+
{file?.status === "saving" ? "Saving…" : "Save"}
|
|
226
|
+
</button>
|
|
227
|
+
<StudioZoom />
|
|
228
|
+
</div>
|
|
229
|
+
</InCell>
|
|
230
|
+
|
|
231
|
+
{doc === null ? null : (
|
|
232
|
+
<InCell name="left">
|
|
233
|
+
<ComponentLibraryRail doc={doc} current={{ kind: "code", module: componentId }} />
|
|
234
|
+
</InCell>
|
|
235
|
+
)}
|
|
236
|
+
|
|
237
|
+
<InCell name="centre">
|
|
238
|
+
<section className="code-studio" data-testid="code-studio" aria-label={`Code ${name}`} ref={studioRef}>
|
|
239
|
+
<div className="code-studio-column">
|
|
240
|
+
<div className="code-file-bar" data-testid="code-file-bar">
|
|
241
|
+
<button
|
|
242
|
+
type="button"
|
|
243
|
+
className={`code-files-toggle${filesOpen ? " is-on" : ""}`}
|
|
244
|
+
data-testid="code-files-toggle"
|
|
245
|
+
aria-pressed={filesOpen}
|
|
246
|
+
title={filesOpen ? "Hide the project's files" : "Show the project's files"}
|
|
247
|
+
onClick={() => setFilesOpen((v) => !v)}
|
|
248
|
+
>
|
|
249
|
+
Files
|
|
250
|
+
</button>
|
|
251
|
+
<CodeTabs />
|
|
252
|
+
{/* One buffer: the file tab alone. Two or more: the tab strip. */}
|
|
253
|
+
{tabRel !== null && bufferCount < 2 ? (
|
|
254
|
+
<span className="code-file-tab" data-testid="code-path" title={tabRel}>
|
|
255
|
+
{tabRel.slice(tabRel.lastIndexOf("/") + 1)}
|
|
256
|
+
<TabWriter rel={tabRel} />
|
|
257
|
+
</span>
|
|
258
|
+
) : null}
|
|
259
|
+
<span className="code-file-spacer" />
|
|
260
|
+
<BuildStatus build={build} />
|
|
261
|
+
</div>
|
|
262
|
+
{/* The strips are FILE facts, so they head the column with the
|
|
263
|
+
file in it. */}
|
|
264
|
+
<CodeFileStrips file={file} />
|
|
265
|
+
<div className="code-studio-body">
|
|
266
|
+
{filesOpen ? <FileTree /> : null}
|
|
267
|
+
{file === null ? (
|
|
268
|
+
<p className="code-studio-empty" data-testid="code-no-buffer">
|
|
269
|
+
Pick a file to read it here.
|
|
270
|
+
</p>
|
|
271
|
+
) : (
|
|
272
|
+
<CodePane rel={file.rel} />
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
<div className="code-status" data-testid="code-status">
|
|
276
|
+
{errors > 0 ? (
|
|
277
|
+
<span className="code-status-errors" data-testid="code-errors">
|
|
278
|
+
{errors} {errors === 1 ? "error" : "errors"}
|
|
279
|
+
</span>
|
|
280
|
+
) : (
|
|
281
|
+
<span className="code-status-ok" data-testid="code-problems">
|
|
282
|
+
No problems
|
|
283
|
+
</span>
|
|
284
|
+
)}
|
|
285
|
+
{fresh.length > 0 ? (
|
|
286
|
+
<span className="code-status-note" data-testid="code-new-prop">
|
|
287
|
+
· New prop found: {fresh.join(", ")}
|
|
288
|
+
</span>
|
|
289
|
+
) : null}
|
|
290
|
+
<span className="code-file-spacer" />
|
|
291
|
+
<span className="code-status-verbs">
|
|
292
|
+
<button
|
|
293
|
+
type="button"
|
|
294
|
+
className="code-status-verb"
|
|
295
|
+
data-testid="code-remix"
|
|
296
|
+
disabled={readOnly || row === undefined || row.builtin === true}
|
|
297
|
+
title="Make your own copy of this component. The original stays as it was."
|
|
298
|
+
onClick={() => void store.remixComponent()}
|
|
299
|
+
>
|
|
300
|
+
Remix
|
|
301
|
+
</button>
|
|
302
|
+
<span aria-hidden="true">·</span>
|
|
303
|
+
<button
|
|
304
|
+
type="button"
|
|
305
|
+
className="code-status-verb"
|
|
306
|
+
data-testid="code-history"
|
|
307
|
+
title="Every built version of this component"
|
|
308
|
+
onClick={() => void store.openComponentHistory()}
|
|
309
|
+
>
|
|
310
|
+
History
|
|
311
|
+
</button>
|
|
312
|
+
</span>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
<div className="code-live" data-testid="code-live" ref={liveRef}>
|
|
316
|
+
{/* ON THE SEAM, inside the pane it sizes. The live pane is on the
|
|
317
|
+
RIGHT, so moving the pointer along +x shrinks it. */}
|
|
318
|
+
<ResizeHandle
|
|
319
|
+
axis="x"
|
|
320
|
+
getValue={() => liveRef.current?.getBoundingClientRect().width || store.getState().codeLiveWidth}
|
|
321
|
+
sign={-1}
|
|
322
|
+
onChange={(px) => store.setCodeLiveWidth(px, liveCeiling())}
|
|
323
|
+
onReset={() => store.setCodeLiveWidth(CODE_LIVE_WIDTH.default)}
|
|
324
|
+
className="code-live-resize"
|
|
325
|
+
label="Resize the live preview"
|
|
326
|
+
/>
|
|
327
|
+
<div className="code-live-bar">
|
|
328
|
+
<span className="code-live-title">Live</span>
|
|
329
|
+
<span className="code-live-loop" data-testid="code-live-loop">
|
|
330
|
+
loop {formatDuration(framesToSeconds(frames, fps))}
|
|
331
|
+
</span>
|
|
332
|
+
</div>
|
|
333
|
+
{/* THE COMPONENT ITSELF, and it is a HOLE rather than a component.
|
|
334
|
+
The stage behind this mode renders the registry component
|
|
335
|
+
standalone through a synthetic definition, and `measureInset`
|
|
336
|
+
hands it this element's rect. `aria-hidden` because a screen
|
|
337
|
+
reader has nothing to read in a GL surface this document does
|
|
338
|
+
not own. */}
|
|
339
|
+
<div className="stage-window code-studio-hole" aria-hidden="true" data-testid="code-preview-hole" />
|
|
340
|
+
</div>
|
|
341
|
+
</section>
|
|
342
|
+
</InCell>
|
|
343
|
+
</>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** The file bar's build line: a passing build names its version, a failing
|
|
348
|
+
* one says the last good build is still the one playing. */
|
|
349
|
+
function BuildStatus(props: { build: ComponentBuild | undefined }) {
|
|
350
|
+
const b = props.build;
|
|
351
|
+
if (b === undefined) return null;
|
|
352
|
+
if (b.status === "building") {
|
|
353
|
+
return (
|
|
354
|
+
<span className="code-build is-building" data-testid="code-build-status">
|
|
355
|
+
Building…
|
|
356
|
+
</span>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (b.status === "failed") {
|
|
360
|
+
return (
|
|
361
|
+
<span className="code-build is-failed" data-testid="code-build-status" title={b.message}>
|
|
362
|
+
<span className="code-build-dot" aria-hidden="true" />
|
|
363
|
+
{b.version === undefined ? "Build failed" : `Build failed · v${b.version} still plays`}
|
|
364
|
+
</span>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
return (
|
|
368
|
+
<span className="code-build is-built" data-testid="code-build-status">
|
|
369
|
+
<span className="code-build-dot" aria-hidden="true" />
|
|
370
|
+
{b.version === undefined ? "Built" : `Built v${b.version}`}
|
|
371
|
+
</span>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// THE PROJECT'S FILES, as a tree, docked beside the editor in Code Studio's
|
|
2
|
+
// code pane while the file bar's Files toggle is on (CodeStudio.tsx). A head
|
|
3
|
+
// at the bar height, then the list, which is its one scroller: it wears
|
|
4
|
+
// `.rail-scroll`, the left column's scroller recipe, so it is bounded and
|
|
5
|
+
// themed like the outline.
|
|
6
|
+
//
|
|
7
|
+
// ── WHY THE TREE IS DERIVED AND NEVER STORED ────────────────────────────────
|
|
8
|
+
//
|
|
9
|
+
// `GET /api/files` answers a FLAT list of posix paths, and that is the honest
|
|
10
|
+
// shape: the project's directories are an artifact of where the author put
|
|
11
|
+
// things, not a structure the studio owns. Nesting is a way of DRAWING that
|
|
12
|
+
// list. Deriving it per render means a listing that arrives, changes, or
|
|
13
|
+
// truncates can never disagree with the tree on screen, and there is no second
|
|
14
|
+
// copy to invalidate when `files-changed` lands.
|
|
15
|
+
//
|
|
16
|
+
// The cost is one O(n) build per listing change, on at most `LIST_CAP` = 2000
|
|
17
|
+
// entries. That is nothing next to the class of bug it removes.
|
|
18
|
+
//
|
|
19
|
+
// ponytail: every expanded row is a real DOM node, no virtualisation. Fine to
|
|
20
|
+
// the server's own 2000-file cap; if that cap ever rises, this is the thing to
|
|
21
|
+
// window before anything else here.
|
|
22
|
+
//
|
|
23
|
+
// ── WHAT A ROW KNOWS ────────────────────────────────────────────────────────
|
|
24
|
+
//
|
|
25
|
+
// A file row carries three marks and no more: OPEN (it has a buffer), DIRTY
|
|
26
|
+
// (that buffer is unsaved) and GENERATED (the server will refuse to write it).
|
|
27
|
+
// Everything else an author might want here — which component is in it, whether
|
|
28
|
+
// it drifted — already has a home in the library section of the file panel, and
|
|
29
|
+
// duplicating it would make two places to disagree about one fact.
|
|
30
|
+
import { useMemo, useState } from "react";
|
|
31
|
+
import type { CSSProperties } from "react";
|
|
32
|
+
import type { ProjectFile } from "../../api.js";
|
|
33
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
34
|
+
|
|
35
|
+
/** A directory that starts collapsed. `node_modules` and dotfiles never reach
|
|
36
|
+
* the chrome — the server drops them — so this is only about the folders that
|
|
37
|
+
* are genuinely output rather than source. */
|
|
38
|
+
const COLLAPSED_BY_DEFAULT = new Set(["out", "dist", "build", "tmp", "coverage"]);
|
|
39
|
+
|
|
40
|
+
interface DirNode {
|
|
41
|
+
kind: "dir";
|
|
42
|
+
name: string;
|
|
43
|
+
path: string;
|
|
44
|
+
children: TreeNode[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface FileNode {
|
|
48
|
+
kind: "file";
|
|
49
|
+
name: string;
|
|
50
|
+
path: string;
|
|
51
|
+
generated: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type TreeNode = DirNode | FileNode;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fold the flat listing into directories.
|
|
58
|
+
*
|
|
59
|
+
* Directories sort before files and both sort by name, which is the order every
|
|
60
|
+
* file browser has and therefore the only order that does not have to be
|
|
61
|
+
* learned. The server already sorts its walk, but it interleaves — a flat list
|
|
62
|
+
* cannot express "folders first" — so the comparison is redone here.
|
|
63
|
+
*/
|
|
64
|
+
function buildTree(files: readonly ProjectFile[]): TreeNode[] {
|
|
65
|
+
const root: DirNode = { kind: "dir", name: "", path: "", children: [] };
|
|
66
|
+
const dirs = new Map<string, DirNode>([["", root]]);
|
|
67
|
+
|
|
68
|
+
const dirAt = (path: string): DirNode => {
|
|
69
|
+
const found = dirs.get(path);
|
|
70
|
+
if (found !== undefined) return found;
|
|
71
|
+
const cut = path.lastIndexOf("/");
|
|
72
|
+
const parent = dirAt(cut === -1 ? "" : path.slice(0, cut));
|
|
73
|
+
const node: DirNode = {
|
|
74
|
+
kind: "dir",
|
|
75
|
+
name: path.slice(cut + 1),
|
|
76
|
+
path,
|
|
77
|
+
children: [],
|
|
78
|
+
};
|
|
79
|
+
dirs.set(path, node);
|
|
80
|
+
parent.children.push(node);
|
|
81
|
+
return node;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
const cut = file.path.lastIndexOf("/");
|
|
86
|
+
const parent = dirAt(cut === -1 ? "" : file.path.slice(0, cut));
|
|
87
|
+
parent.children.push({
|
|
88
|
+
kind: "file",
|
|
89
|
+
name: file.path.slice(cut + 1),
|
|
90
|
+
path: file.path,
|
|
91
|
+
generated: file.generated !== undefined,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const sort = (node: DirNode): void => {
|
|
96
|
+
node.children.sort((a, b) => {
|
|
97
|
+
if (a.kind !== b.kind) return a.kind === "dir" ? -1 : 1;
|
|
98
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
99
|
+
});
|
|
100
|
+
for (const child of node.children) if (child.kind === "dir") sort(child);
|
|
101
|
+
};
|
|
102
|
+
sort(root);
|
|
103
|
+
return root.children;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function FileTree() {
|
|
107
|
+
const store = useStore();
|
|
108
|
+
const files = useShotEditor((s) => s.projectFiles);
|
|
109
|
+
const loaded = useShotEditor((s) => s.projectFilesLoaded);
|
|
110
|
+
const truncated = useShotEditor((s) => s.projectFilesTruncated);
|
|
111
|
+
const error = useShotEditor((s) => s.projectFilesError);
|
|
112
|
+
const tree = useMemo(() => buildTree(files), [files]);
|
|
113
|
+
|
|
114
|
+
// COLLAPSE STATE IS THIS PANEL'S, not the store's: it is a posture, it means
|
|
115
|
+
// nothing to any other surface, and it dies with the mode the way a scroll
|
|
116
|
+
// position does.
|
|
117
|
+
const [collapsed, setCollapsed] = useState<ReadonlySet<string>>(new Set());
|
|
118
|
+
const toggle = (path: string): void => {
|
|
119
|
+
setCollapsed((was) => {
|
|
120
|
+
const next = new Set(was);
|
|
121
|
+
if (!next.delete(path)) next.add(path);
|
|
122
|
+
return next;
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div className="code-tree" data-testid="code-tree">
|
|
128
|
+
<div className="code-tree-head">
|
|
129
|
+
<span className="code-tree-title">Files</span>
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
className="btn is-icon"
|
|
133
|
+
data-testid="code-tree-refresh"
|
|
134
|
+
title="Re-read the project's files"
|
|
135
|
+
aria-label="Refresh the file list"
|
|
136
|
+
onClick={() => void store.refreshProjectFiles()}
|
|
137
|
+
>
|
|
138
|
+
⟳
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
{error !== null ? (
|
|
142
|
+
<p className="code-tree-empty" role="alert" data-testid="code-tree-error">
|
|
143
|
+
{error}
|
|
144
|
+
</p>
|
|
145
|
+
) : !loaded ? (
|
|
146
|
+
<p className="code-tree-empty">Reading the project…</p>
|
|
147
|
+
) : tree.length === 0 ? (
|
|
148
|
+
<p className="code-tree-empty">No editable files in this project.</p>
|
|
149
|
+
) : (
|
|
150
|
+
<ul className="code-tree-list rail-scroll" role="tree" aria-label="Project files">
|
|
151
|
+
{tree.map((node) => (
|
|
152
|
+
<TreeRow
|
|
153
|
+
key={node.path}
|
|
154
|
+
node={node}
|
|
155
|
+
depth={0}
|
|
156
|
+
collapsed={collapsed}
|
|
157
|
+
onToggle={toggle}
|
|
158
|
+
/>
|
|
159
|
+
))}
|
|
160
|
+
</ul>
|
|
161
|
+
)}
|
|
162
|
+
{/* SAID, NEVER SWALLOWED. A file browser that quietly omits files teaches
|
|
163
|
+
the author their file does not exist. */}
|
|
164
|
+
{truncated ? (
|
|
165
|
+
<p className="code-tree-truncated" data-testid="code-tree-truncated">
|
|
166
|
+
This project has more files than the listing returns. Some are not shown.
|
|
167
|
+
</p>
|
|
168
|
+
) : null}
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function TreeRow({
|
|
174
|
+
node,
|
|
175
|
+
depth,
|
|
176
|
+
collapsed,
|
|
177
|
+
onToggle,
|
|
178
|
+
}: {
|
|
179
|
+
node: TreeNode;
|
|
180
|
+
depth: number;
|
|
181
|
+
collapsed: ReadonlySet<string>;
|
|
182
|
+
onToggle: (path: string) => void;
|
|
183
|
+
}) {
|
|
184
|
+
if (node.kind === "dir") {
|
|
185
|
+
// XOR AGAINST THE DEFAULT, rather than seeding the set: the set is built
|
|
186
|
+
// before any listing has arrived, so there is nothing to seed it from. The
|
|
187
|
+
// set therefore holds "the author has toggled this", and a name in the
|
|
188
|
+
// collapsed-by-default list is open exactly when it has been toggled.
|
|
189
|
+
// Keyed by full path, so two directories that share a name are independent.
|
|
190
|
+
const defaultCollapsed = COLLAPSED_BY_DEFAULT.has(node.name);
|
|
191
|
+
const isCollapsed = collapsed.has(node.path) !== defaultCollapsed;
|
|
192
|
+
return (
|
|
193
|
+
<li role="none">
|
|
194
|
+
<button
|
|
195
|
+
type="button"
|
|
196
|
+
className="code-tree-row is-dir"
|
|
197
|
+
role="treeitem"
|
|
198
|
+
aria-expanded={!isCollapsed}
|
|
199
|
+
style={{ "--depth": depth } as CSSProperties}
|
|
200
|
+
onClick={() => onToggle(node.path)}
|
|
201
|
+
>
|
|
202
|
+
<span className="code-tree-caret" aria-hidden="true">
|
|
203
|
+
{isCollapsed ? "▸" : "▾"}
|
|
204
|
+
</span>
|
|
205
|
+
<span className="code-tree-name">{node.name}</span>
|
|
206
|
+
</button>
|
|
207
|
+
{isCollapsed ? null : (
|
|
208
|
+
<ul className="code-tree-list" role="group">
|
|
209
|
+
{node.children.map((child) => (
|
|
210
|
+
<TreeRow
|
|
211
|
+
key={child.path}
|
|
212
|
+
node={child}
|
|
213
|
+
depth={depth + 1}
|
|
214
|
+
collapsed={collapsed}
|
|
215
|
+
onToggle={onToggle}
|
|
216
|
+
/>
|
|
217
|
+
))}
|
|
218
|
+
</ul>
|
|
219
|
+
)}
|
|
220
|
+
</li>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return <FileRow node={node} depth={depth} />;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function FileRow({ node, depth }: { node: FileNode; depth: number }) {
|
|
227
|
+
const store = useStore();
|
|
228
|
+
const active = useShotEditor((s) => s.codeActive === node.path);
|
|
229
|
+
const buffer = useShotEditor((s) => s.codeFiles[node.path]);
|
|
230
|
+
const isSubject = useShotEditor(
|
|
231
|
+
(s) => s.library.find((r) => r.id === s.codeComponent)?.rel === node.path,
|
|
232
|
+
);
|
|
233
|
+
const dirty = buffer !== undefined && buffer.content !== buffer.savedContent;
|
|
234
|
+
return (
|
|
235
|
+
<li role="none">
|
|
236
|
+
<button
|
|
237
|
+
type="button"
|
|
238
|
+
className="code-tree-row is-file"
|
|
239
|
+
role="treeitem"
|
|
240
|
+
aria-selected={active}
|
|
241
|
+
data-active={active ? "" : undefined}
|
|
242
|
+
data-open={buffer !== undefined ? "" : undefined}
|
|
243
|
+
data-testid={`code-tree-file-${node.path}`}
|
|
244
|
+
title={node.generated ? `${node.path}: generated, read-only` : node.path}
|
|
245
|
+
style={{ "--depth": depth } as CSSProperties}
|
|
246
|
+
onClick={() => void store.openCodeFile(node.path)}
|
|
247
|
+
>
|
|
248
|
+
<span className="code-tree-name">{node.name}</span>
|
|
249
|
+
{/* THE SUBJECT MARK, so the one file the stage is drawing is findable
|
|
250
|
+
in a tree of two hundred. */}
|
|
251
|
+
{isSubject ? (
|
|
252
|
+
<span className="code-tree-mark is-subject" title="The component in the preview">
|
|
253
|
+
●
|
|
254
|
+
</span>
|
|
255
|
+
) : null}
|
|
256
|
+
{node.generated ? (
|
|
257
|
+
<span className="code-tree-mark is-generated" title="Generated, read-only">
|
|
258
|
+
⌁
|
|
259
|
+
</span>
|
|
260
|
+
) : null}
|
|
261
|
+
{dirty ? <span className="code-tree-mark is-dirty" title="Unsaved changes" /> : null}
|
|
262
|
+
</button>
|
|
263
|
+
</li>
|
|
264
|
+
);
|
|
265
|
+
}
|