@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,274 @@
|
|
|
1
|
+
// The Version history panel: the right column's occupant while it is open
|
|
2
|
+
// (RightColumn). The open autosave group heads the list as "Current version";
|
|
3
|
+
// the closed groups and the named versions follow under their days, newest
|
|
4
|
+
// first. A click previews a row on the stage, read-only; Restore writes it
|
|
5
|
+
// back as one undo step. A viewer reads and previews, and nothing else.
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { Avatar, useContextMenu, usePrompt } from "@dailies-studio/ui";
|
|
8
|
+
import type { MenuEntry } from "@dailies-studio/ui";
|
|
9
|
+
import type { ShotVersion } from "../../api.js";
|
|
10
|
+
import { useShotEditor, useStore } from "../../state/context.js";
|
|
11
|
+
import { shortcutFor } from "../../state/shortcuts.js";
|
|
12
|
+
import { VIEW_ONLY } from "../../state/workspace.js";
|
|
13
|
+
import {
|
|
14
|
+
authorLine,
|
|
15
|
+
groupByDay,
|
|
16
|
+
personHue,
|
|
17
|
+
personName,
|
|
18
|
+
versionNameError,
|
|
19
|
+
versionSub,
|
|
20
|
+
versionTitle,
|
|
21
|
+
} from "./versionModel.js";
|
|
22
|
+
|
|
23
|
+
export function VersionHistoryPanel() {
|
|
24
|
+
const store = useStore();
|
|
25
|
+
const list = useShotEditor((s) => s.versionList);
|
|
26
|
+
const preview = useShotEditor((s) => s.versionPreview);
|
|
27
|
+
const roleReadOnly = useShotEditor((s) => s.versionPreview?.roleReadOnly ?? s.readOnly);
|
|
28
|
+
const menu = useContextMenu();
|
|
29
|
+
const { prompt, dialog } = usePrompt();
|
|
30
|
+
const people = list?.people ?? {};
|
|
31
|
+
const versions = list?.versions ?? [];
|
|
32
|
+
const head = list?.head ?? null;
|
|
33
|
+
const previewId = preview?.version.id ?? null;
|
|
34
|
+
|
|
35
|
+
const openMenu = (v: ShotVersion, button: HTMLElement): void => {
|
|
36
|
+
const r = button.getBoundingClientRect();
|
|
37
|
+
const locked = roleReadOnly ? { disabled: true, title: VIEW_ONLY } : {};
|
|
38
|
+
const items: MenuEntry[] = [
|
|
39
|
+
{
|
|
40
|
+
kind: "action",
|
|
41
|
+
id: "version.rename",
|
|
42
|
+
label: "Rename…",
|
|
43
|
+
...locked,
|
|
44
|
+
onSelect: () => {
|
|
45
|
+
void prompt({
|
|
46
|
+
title: v.name === undefined ? "Name this version" : "Rename version",
|
|
47
|
+
label: "Name",
|
|
48
|
+
initialValue: v.name ?? "",
|
|
49
|
+
confirmLabel: v.name === undefined ? "Name" : "Rename",
|
|
50
|
+
validate: versionNameError,
|
|
51
|
+
}).then((label) => {
|
|
52
|
+
if (label !== null) void store.renameVersion(v.id, label.trim());
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
kind: "action",
|
|
58
|
+
id: "version.preview",
|
|
59
|
+
label: "Preview",
|
|
60
|
+
onSelect: () => void store.previewVersion(v.id),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
kind: "action",
|
|
64
|
+
id: "version.restore",
|
|
65
|
+
label: "Restore",
|
|
66
|
+
...locked,
|
|
67
|
+
onSelect: () => void store.restoreVersion(v.id),
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
menu.open({ x: r.right, y: r.bottom + 6 }, items);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<section className="versions-panel" data-testid="versions-panel" aria-label="Version history">
|
|
75
|
+
{dialog}
|
|
76
|
+
{menu.menu}
|
|
77
|
+
<header className="versions-head">
|
|
78
|
+
<h2 className="versions-title">Version history</h2>
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
className="btn is-icon"
|
|
82
|
+
data-testid="versions-close"
|
|
83
|
+
aria-label="Close the version history"
|
|
84
|
+
title="Close the version history"
|
|
85
|
+
onClick={() => store.openVersions(false)}
|
|
86
|
+
>
|
|
87
|
+
×
|
|
88
|
+
</button>
|
|
89
|
+
</header>
|
|
90
|
+
{roleReadOnly ? null : <SaveVersion />}
|
|
91
|
+
<div className="versions-list" data-testid="versions-list">
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
className="version-row is-current"
|
|
95
|
+
aria-pressed={previewId === null}
|
|
96
|
+
data-testid="version-current"
|
|
97
|
+
onClick={() => store.exitVersionPreview()}
|
|
98
|
+
>
|
|
99
|
+
<Faces ids={head?.authors ?? []} people={people} />
|
|
100
|
+
<span className="version-text">
|
|
101
|
+
<span className="version-name">Current version</span>
|
|
102
|
+
<span className="version-sub">
|
|
103
|
+
{head === null || head.authors.length === 0
|
|
104
|
+
? "Editing now"
|
|
105
|
+
: `${authorLine(head.authors, people)} · editing now`}
|
|
106
|
+
</span>
|
|
107
|
+
</span>
|
|
108
|
+
</button>
|
|
109
|
+
{list === null ? (
|
|
110
|
+
<p className="versions-note">Loading versions…</p>
|
|
111
|
+
) : versions.length === 0 ? (
|
|
112
|
+
<p className="versions-note">No earlier versions yet. Autosaves gather here as you work.</p>
|
|
113
|
+
) : (
|
|
114
|
+
groupByDay(versions, Date.now()).map((group) => (
|
|
115
|
+
<div key={group.day} className="versions-day" role="group" aria-label={group.day}>
|
|
116
|
+
<h3 className="versions-day-head">{group.day}</h3>
|
|
117
|
+
{group.versions.map((v) => (
|
|
118
|
+
<div key={v.id} className="version-item" data-selected={v.id === previewId ? "" : undefined}>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
className={`version-row${v.name === undefined ? "" : " is-named"}`}
|
|
122
|
+
aria-pressed={v.id === previewId}
|
|
123
|
+
data-testid={`version-row-${v.id}`}
|
|
124
|
+
onClick={() => void store.previewVersion(v.id)}
|
|
125
|
+
>
|
|
126
|
+
<Faces ids={v.authors} people={people} />
|
|
127
|
+
<span className="version-text">
|
|
128
|
+
<span className="version-name">{versionTitle(v, versions)}</span>
|
|
129
|
+
<span className="version-sub">{versionSub(v, people)}</span>
|
|
130
|
+
</span>
|
|
131
|
+
{v.name === undefined ? null : <span className="version-badge">Named</span>}
|
|
132
|
+
</button>
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
className="btn is-icon version-more"
|
|
136
|
+
data-testid={`version-more-${v.id}`}
|
|
137
|
+
aria-label={`More for ${versionTitle(v, versions)}`}
|
|
138
|
+
title="More"
|
|
139
|
+
onClick={(e) => openMenu(v, e.currentTarget)}
|
|
140
|
+
>
|
|
141
|
+
…
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
))
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
{preview === null ? null : (
|
|
150
|
+
<footer className="versions-preview" data-testid="versions-preview">
|
|
151
|
+
<span className="versions-caps">Previewing</span>
|
|
152
|
+
<span className="versions-preview-what">
|
|
153
|
+
{`${preview.version.name === undefined ? versionTitle(preview.version, versions) : `"${preview.version.name}"`} · ${versionSub(preview.version, people)}`}
|
|
154
|
+
</span>
|
|
155
|
+
<div className="versions-preview-verbs">
|
|
156
|
+
<button
|
|
157
|
+
type="button"
|
|
158
|
+
className="btn is-outline"
|
|
159
|
+
data-testid="versions-back"
|
|
160
|
+
onClick={() => store.exitVersionPreview()}
|
|
161
|
+
>
|
|
162
|
+
Back to current
|
|
163
|
+
</button>
|
|
164
|
+
<button
|
|
165
|
+
type="button"
|
|
166
|
+
className="btn is-primary"
|
|
167
|
+
data-testid="versions-restore"
|
|
168
|
+
disabled={roleReadOnly}
|
|
169
|
+
title={roleReadOnly ? VIEW_ONLY : "Restore this version. Undo takes it back."}
|
|
170
|
+
onClick={() => void store.restoreVersion(preview.version.id)}
|
|
171
|
+
>
|
|
172
|
+
Restore
|
|
173
|
+
</button>
|
|
174
|
+
</div>
|
|
175
|
+
</footer>
|
|
176
|
+
)}
|
|
177
|
+
</section>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** ⌘S lands here: the name field, focused. Otherwise one button opens it. */
|
|
182
|
+
function SaveVersion() {
|
|
183
|
+
const store = useStore();
|
|
184
|
+
const ask = useShotEditor((s) => s.versionNameAsk);
|
|
185
|
+
const [label, setLabel] = useState("");
|
|
186
|
+
const [busy, setBusy] = useState(false);
|
|
187
|
+
const field = useRef<HTMLInputElement>(null);
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (!ask) return;
|
|
190
|
+
// A second ⌘S while the field is up selects what is typed, ready to retype.
|
|
191
|
+
field.current?.focus();
|
|
192
|
+
field.current?.select();
|
|
193
|
+
}, [ask]);
|
|
194
|
+
const error = label === "" ? null : versionNameError(label);
|
|
195
|
+
const save = async (): Promise<void> => {
|
|
196
|
+
if (busy || label.trim() === "" || error !== null) return;
|
|
197
|
+
setBusy(true);
|
|
198
|
+
try {
|
|
199
|
+
if (await store.saveVersionNamed(label.trim())) setLabel("");
|
|
200
|
+
} finally {
|
|
201
|
+
setBusy(false);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const combo = shortcutFor("file.save");
|
|
205
|
+
if (!ask) {
|
|
206
|
+
return (
|
|
207
|
+
<div className="versions-save">
|
|
208
|
+
<button
|
|
209
|
+
type="button"
|
|
210
|
+
className="btn is-outline"
|
|
211
|
+
data-testid="versions-save-open"
|
|
212
|
+
title={combo === undefined ? undefined : `Save version (${combo})`}
|
|
213
|
+
onClick={() => store.setVersionNameAsk(true)}
|
|
214
|
+
>
|
|
215
|
+
Save version…
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return (
|
|
221
|
+
<div className="versions-save">
|
|
222
|
+
<label className="versions-caps" htmlFor="versions-save-name">
|
|
223
|
+
Save version
|
|
224
|
+
</label>
|
|
225
|
+
<div className="versions-save-field">
|
|
226
|
+
<input
|
|
227
|
+
ref={field}
|
|
228
|
+
id="versions-save-name"
|
|
229
|
+
className="text-input"
|
|
230
|
+
data-testid="versions-save-name"
|
|
231
|
+
placeholder="Name this version"
|
|
232
|
+
value={label}
|
|
233
|
+
maxLength={80}
|
|
234
|
+
aria-invalid={error !== null}
|
|
235
|
+
onChange={(e) => setLabel(e.currentTarget.value)}
|
|
236
|
+
onKeyDown={(e) => {
|
|
237
|
+
if (e.key === "Enter") {
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
void save();
|
|
240
|
+
} else if (e.key === "Escape") {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
e.stopPropagation();
|
|
243
|
+
store.setVersionNameAsk(false);
|
|
244
|
+
}
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
<button
|
|
248
|
+
type="button"
|
|
249
|
+
className="btn is-primary"
|
|
250
|
+
data-testid="versions-save"
|
|
251
|
+
disabled={busy || label.trim() === "" || error !== null}
|
|
252
|
+
onClick={() => void save()}
|
|
253
|
+
>
|
|
254
|
+
Save
|
|
255
|
+
</button>
|
|
256
|
+
</div>
|
|
257
|
+
<p className="versions-hint">{error ?? "Enter saves. Esc closes the field."}</p>
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** The writers of a row, overlapped, first writer on top. */
|
|
263
|
+
function Faces({ ids, people }: { ids: readonly string[]; people: Readonly<Record<string, string>> }) {
|
|
264
|
+
const shown = ids.slice(0, 3);
|
|
265
|
+
return (
|
|
266
|
+
<span className="version-faces" aria-hidden="true">
|
|
267
|
+
{shown.map((id) => (
|
|
268
|
+
<span key={id} className="version-face" style={{ background: `hsl(${personHue(id)} 45% 45%)` }}>
|
|
269
|
+
<Avatar url={null} label={personName(id, people)} className="version-face-mark" />
|
|
270
|
+
</span>
|
|
271
|
+
))}
|
|
272
|
+
</span>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// What the Version history panel says about each row: who, when, and under
|
|
2
|
+
// which day. Pure, so the words are tested without a DOM.
|
|
3
|
+
import type { ShotVersion, ShotVersionHead } from "../../api.js";
|
|
4
|
+
|
|
5
|
+
/** The CLI's one user (packages/store/src/shotVersions.ts). */
|
|
6
|
+
const LOCAL_USER = "local";
|
|
7
|
+
|
|
8
|
+
/** A person's display name. The CLI's one user is "You"; a hosted id the
|
|
9
|
+
* server could not name (a member who left) is "Former member". */
|
|
10
|
+
export function personName(id: string, people: Readonly<Record<string, string>>): string {
|
|
11
|
+
if (id === LOCAL_USER) return "You";
|
|
12
|
+
return people[id] ?? "Former member";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** "Priya", "Tom, Kanishak", "Tom, Kanishak +2". Empty when no writer was
|
|
16
|
+
* recorded (an agent apply, a save from before authors). */
|
|
17
|
+
export function authorLine(ids: readonly string[], people: Readonly<Record<string, string>>): string {
|
|
18
|
+
const names = ids.map((id) => personName(id, people));
|
|
19
|
+
return names.length <= 2 ? names.join(", ") : `${names.slice(0, 2).join(", ")} +${names.length - 2}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const TIME = new Intl.DateTimeFormat("en-US", {
|
|
23
|
+
hour: "numeric",
|
|
24
|
+
minute: "2-digit",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/** "6:05 PM", or "3:40 to 3:52 PM" for a group that ran across minutes. */
|
|
28
|
+
export function timeSpan(from: number, at: number): string {
|
|
29
|
+
const end = TIME.format(at);
|
|
30
|
+
const start = TIME.format(from);
|
|
31
|
+
if (start === end) return end;
|
|
32
|
+
const period = (s: string): string => s.slice(s.lastIndexOf(" ") + 1);
|
|
33
|
+
return `${period(start) === period(end) ? start.slice(0, start.lastIndexOf(" ")) : start} to ${end}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DAY = new Intl.DateTimeFormat("en-US", {
|
|
37
|
+
weekday: "short",
|
|
38
|
+
month: "short",
|
|
39
|
+
day: "numeric",
|
|
40
|
+
});
|
|
41
|
+
const DAY_YEAR = new Intl.DateTimeFormat("en-US", {
|
|
42
|
+
month: "short",
|
|
43
|
+
day: "numeric",
|
|
44
|
+
year: "numeric",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/** The group header a row sits under: "Today", "Yesterday", "Mon, Oct 6",
|
|
48
|
+
* or "Oct 6, 2025" for another year. Local days. */
|
|
49
|
+
export function dayLabel(at: number, now: number): string {
|
|
50
|
+
const day = new Date(at);
|
|
51
|
+
const today = new Date(now);
|
|
52
|
+
const startOf = (d: Date): number => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
53
|
+
const days = Math.round((startOf(today) - startOf(day)) / 86_400_000);
|
|
54
|
+
if (days === 0) return "Today";
|
|
55
|
+
if (days === 1) return "Yesterday";
|
|
56
|
+
return day.getFullYear() === today.getFullYear() ? DAY.format(day) : DAY_YEAR.format(day);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Rows under their day headers, newest first, as the list arrives. */
|
|
60
|
+
export function groupByDay(versions: readonly ShotVersion[], now: number): { day: string; versions: ShotVersion[] }[] {
|
|
61
|
+
const groups: { day: string; versions: ShotVersion[] }[] = [];
|
|
62
|
+
for (const v of versions) {
|
|
63
|
+
const day = dayLabel(v.at, now);
|
|
64
|
+
const last = groups.at(-1);
|
|
65
|
+
if (last?.day === day) last.versions.push(v);
|
|
66
|
+
else groups.push({ day, versions: [v] });
|
|
67
|
+
}
|
|
68
|
+
return groups;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A row's title: its name, what a restore brought back, or "Autosaved". */
|
|
72
|
+
export function versionTitle(v: ShotVersion, all: readonly ShotVersion[]): string {
|
|
73
|
+
if (v.name !== undefined) return v.name;
|
|
74
|
+
if (v.restoredFrom !== undefined) {
|
|
75
|
+
const from = all.find((r) => r.id === v.restoredFrom);
|
|
76
|
+
return from?.name === undefined ? "Restored an earlier version" : `Restored "${from.name}"`;
|
|
77
|
+
}
|
|
78
|
+
return "Autosaved";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** "Priya · 3:40 to 3:52 PM", or the time alone with no recorded writer. */
|
|
82
|
+
export function versionSub(v: ShotVersionHead, people: Readonly<Record<string, string>>): string {
|
|
83
|
+
const who = authorLine(v.authors, people);
|
|
84
|
+
const when = timeSpan(v.from, v.at);
|
|
85
|
+
return who === "" ? when : `${who} · ${when}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The server's rule (packages/store/src/shotVersions.ts `versionName`):
|
|
89
|
+
* trimmed, 1 to 80 characters, one line. Null when it passes. */
|
|
90
|
+
export function versionNameError(raw: string): string | null {
|
|
91
|
+
const name = raw.trim();
|
|
92
|
+
if (name === "") return "Give the version a name.";
|
|
93
|
+
if (name.length > 80) return "A version name is at most 80 characters.";
|
|
94
|
+
if (/[\u0000-\u001f\u007f]/.test(name)) return "A version name is one line.";
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A stable hue per person, so one face keeps one colour in every row. */
|
|
99
|
+
export function personHue(id: string): number {
|
|
100
|
+
let h = 0;
|
|
101
|
+
for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) >>> 0;
|
|
102
|
+
return h % 360;
|
|
103
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import type { RefObject } from "react";
|
|
3
|
+
|
|
4
|
+
export interface VirtualWindow {
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
before: number;
|
|
8
|
+
after: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function prefixHeights(heights: readonly number[]): number[] {
|
|
12
|
+
const prefix = [0];
|
|
13
|
+
for (const height of heights) prefix.push(prefix[prefix.length - 1]! + Math.max(1, height));
|
|
14
|
+
return prefix;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function lowerBound(values: readonly number[], target: number): number {
|
|
18
|
+
let low = 0;
|
|
19
|
+
let high = values.length;
|
|
20
|
+
while (low < high) {
|
|
21
|
+
const middle = (low + high) >>> 1;
|
|
22
|
+
if (values[middle]! < target) low = middle + 1;
|
|
23
|
+
else high = middle;
|
|
24
|
+
}
|
|
25
|
+
return low;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function upperBound(values: readonly number[], target: number): number {
|
|
29
|
+
let low = 0;
|
|
30
|
+
let high = values.length;
|
|
31
|
+
while (low < high) {
|
|
32
|
+
const middle = (low + high) >>> 1;
|
|
33
|
+
if (values[middle]! <= target) low = middle + 1;
|
|
34
|
+
else high = middle;
|
|
35
|
+
}
|
|
36
|
+
return low;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Variable-height form used where one expanded timeline row contains child
|
|
40
|
+
* lanes. Prefix sums keep scroll updates logarithmic. */
|
|
41
|
+
function variableVirtualWindowFromPrefix(
|
|
42
|
+
prefix: readonly number[],
|
|
43
|
+
viewportStart: number,
|
|
44
|
+
viewportHeight: number,
|
|
45
|
+
overscanPx: number,
|
|
46
|
+
): VirtualWindow {
|
|
47
|
+
const count = prefix.length - 1;
|
|
48
|
+
if (count <= 0) return { start: 0, end: 0, before: 0, after: 0 };
|
|
49
|
+
const total = prefix[prefix.length - 1]!;
|
|
50
|
+
const from = Math.max(0, viewportStart - overscanPx);
|
|
51
|
+
const to = Math.min(total, Math.max(from + 1, viewportStart + viewportHeight + overscanPx));
|
|
52
|
+
const start = Math.min(count - 1, Math.max(0, upperBound(prefix, from) - 1));
|
|
53
|
+
const end = Math.min(count, Math.max(start + 1, lowerBound(prefix, to)));
|
|
54
|
+
return { start, end, before: prefix[start]!, after: total - prefix[end]! };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const INITIAL_ROWS = 24;
|
|
58
|
+
|
|
59
|
+
/** Fixed-height range math shared by the outline and timeline. `end` is
|
|
60
|
+
* exclusive and spacers retain the full scroll geometry. */
|
|
61
|
+
export function fixedVirtualWindow(
|
|
62
|
+
count: number,
|
|
63
|
+
rowHeight: number,
|
|
64
|
+
viewportStart: number,
|
|
65
|
+
viewportHeight: number,
|
|
66
|
+
overscanRows = 8,
|
|
67
|
+
): VirtualWindow {
|
|
68
|
+
if (count <= 0) return { start: 0, end: 0, before: 0, after: 0 };
|
|
69
|
+
const safeHeight = Math.max(1, rowHeight);
|
|
70
|
+
const first = Math.min(count - 1, Math.floor(Math.max(0, viewportStart) / safeHeight));
|
|
71
|
+
const visible = Math.max(1, Math.ceil(Math.max(0, viewportHeight) / safeHeight));
|
|
72
|
+
const start = Math.max(0, first - overscanRows);
|
|
73
|
+
const end = Math.min(count, first + visible + overscanRows);
|
|
74
|
+
return {
|
|
75
|
+
start,
|
|
76
|
+
end,
|
|
77
|
+
before: start * safeHeight,
|
|
78
|
+
after: Math.max(0, (count - end) * safeHeight),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface WindowSpan {
|
|
83
|
+
start: number;
|
|
84
|
+
end: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Merge the viewport with the few rows whose DOM identity is currently
|
|
88
|
+
* load-bearing (a focused, renamed or dragged outline row, the picker's active
|
|
89
|
+
* option). Disjoint spacers keep the list's exact fixed-row geometry, so
|
|
90
|
+
* pinning a row costs one row rather than filling the entire distance between
|
|
91
|
+
* it and the viewport. */
|
|
92
|
+
export function pinnedWindowSpans(
|
|
93
|
+
range: { start: number; end: number },
|
|
94
|
+
count: number,
|
|
95
|
+
pinnedIndexes: readonly number[],
|
|
96
|
+
): WindowSpan[] {
|
|
97
|
+
const windowStart = Math.min(count, Math.max(0, range.start));
|
|
98
|
+
const windowEnd = Math.min(count, Math.max(windowStart, range.end));
|
|
99
|
+
const candidates: WindowSpan[] = [
|
|
100
|
+
{ start: windowStart, end: windowEnd },
|
|
101
|
+
...pinnedIndexes
|
|
102
|
+
.filter((index) => index >= 0 && index < count)
|
|
103
|
+
.map((index) => ({ start: index, end: index + 1 })),
|
|
104
|
+
]
|
|
105
|
+
.filter((span) => span.end > span.start)
|
|
106
|
+
.sort((a, b) => a.start - b.start);
|
|
107
|
+
const merged: WindowSpan[] = [];
|
|
108
|
+
for (const candidate of candidates) {
|
|
109
|
+
const previous = merged[merged.length - 1];
|
|
110
|
+
if (previous === undefined || candidate.start > previous.end) {
|
|
111
|
+
merged.push({ ...candidate });
|
|
112
|
+
} else {
|
|
113
|
+
previous.end = Math.max(previous.end, candidate.end);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return merged;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Internal fixed-row windowing with no dependency. The list keeps its total
|
|
120
|
+
* height through spacers, while scroll and resize only update the mounted
|
|
121
|
+
* slice. Small lists retain their complete DOM for compatibility. */
|
|
122
|
+
export function useFixedVirtualWindow(args: {
|
|
123
|
+
listRef: RefObject<HTMLElement | null>;
|
|
124
|
+
scrollSelector: string;
|
|
125
|
+
count: number;
|
|
126
|
+
rowHeight: number;
|
|
127
|
+
enabled: boolean;
|
|
128
|
+
overscanRows?: number;
|
|
129
|
+
}): VirtualWindow {
|
|
130
|
+
const { listRef, scrollSelector, count, rowHeight, enabled, overscanRows = 8 } = args;
|
|
131
|
+
const [range, setRange] = useState<VirtualWindow>(() =>
|
|
132
|
+
enabled
|
|
133
|
+
? fixedVirtualWindow(count, rowHeight, 0, INITIAL_ROWS * rowHeight, overscanRows)
|
|
134
|
+
: { start: 0, end: count, before: 0, after: 0 },
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
useLayoutEffect(() => {
|
|
138
|
+
if (!enabled) {
|
|
139
|
+
setRange((current) =>
|
|
140
|
+
current.start === 0 && current.end === count && current.before === 0 && current.after === 0
|
|
141
|
+
? current
|
|
142
|
+
: { start: 0, end: count, before: 0, after: 0 },
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const list = listRef.current;
|
|
147
|
+
const scroller = list?.closest(scrollSelector);
|
|
148
|
+
if (!(list instanceof HTMLElement) || !(scroller instanceof HTMLElement)) {
|
|
149
|
+
setRange(fixedVirtualWindow(count, rowHeight, 0, INITIAL_ROWS * rowHeight, overscanRows));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
let lastScrollTop = scroller.scrollTop;
|
|
153
|
+
const measure = (): void => {
|
|
154
|
+
lastScrollTop = scroller.scrollTop;
|
|
155
|
+
const listBox = list.getBoundingClientRect();
|
|
156
|
+
const scrollBox = scroller.getBoundingClientRect();
|
|
157
|
+
const viewportStart = Math.max(0, scrollBox.top - listBox.top);
|
|
158
|
+
const viewportHeight = scroller.clientHeight || INITIAL_ROWS * rowHeight;
|
|
159
|
+
const next = fixedVirtualWindow(count, rowHeight, viewportStart, viewportHeight, overscanRows);
|
|
160
|
+
setRange((current) =>
|
|
161
|
+
current.start === next.start && current.end === next.end &&
|
|
162
|
+
current.before === next.before && current.after === next.after
|
|
163
|
+
? current
|
|
164
|
+
: next,
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
const onScroll = (): void => {
|
|
168
|
+
// The timeline's playhead follow writes `scrollLeft` while playing. A
|
|
169
|
+
// native scroll event does not say which axis moved, so measuring here
|
|
170
|
+
// unconditionally forced two vertical layout reads per followed frame
|
|
171
|
+
// for every mounted window. Vertical range math changes only with the
|
|
172
|
+
// vertical axis; resize/layout changes have their observer below.
|
|
173
|
+
if (scroller.scrollTop === lastScrollTop) return;
|
|
174
|
+
measure();
|
|
175
|
+
};
|
|
176
|
+
measure();
|
|
177
|
+
scroller.addEventListener("scroll", onScroll, { passive: true });
|
|
178
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
|
|
179
|
+
observer?.observe(scroller);
|
|
180
|
+
observer?.observe(list);
|
|
181
|
+
window.addEventListener("resize", measure);
|
|
182
|
+
return () => {
|
|
183
|
+
scroller.removeEventListener("scroll", onScroll);
|
|
184
|
+
observer?.disconnect();
|
|
185
|
+
window.removeEventListener("resize", measure);
|
|
186
|
+
};
|
|
187
|
+
}, [count, enabled, listRef, overscanRows, rowHeight, scrollSelector]);
|
|
188
|
+
|
|
189
|
+
return range;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function useVariableVirtualWindow(args: {
|
|
193
|
+
listRef: RefObject<HTMLElement | null>;
|
|
194
|
+
scrollSelector: string;
|
|
195
|
+
heights: readonly number[];
|
|
196
|
+
enabled: boolean;
|
|
197
|
+
overscanPx?: number;
|
|
198
|
+
}): VirtualWindow {
|
|
199
|
+
const { listRef, scrollSelector, heights, enabled, overscanPx = 224 } = args;
|
|
200
|
+
const prefix = useMemo(() => prefixHeights(heights), [heights]);
|
|
201
|
+
const [range, setRange] = useState<VirtualWindow>(() =>
|
|
202
|
+
enabled
|
|
203
|
+
? variableVirtualWindowFromPrefix(prefix, 0, INITIAL_ROWS * 28, overscanPx)
|
|
204
|
+
: { start: 0, end: heights.length, before: 0, after: 0 },
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// The heights change identity on every document edit (they are derived from
|
|
208
|
+
// the document), and the observer effect below used to list them as a
|
|
209
|
+
// dependency: every rAF frame of a keyframe drag tore down and rebuilt the
|
|
210
|
+
// scroll listener and a two-element `ResizeObserver`, then measured twice.
|
|
211
|
+
// The prefix rides a ref instead, and a change to it re-measures once.
|
|
212
|
+
const prefixRef = useRef(prefix);
|
|
213
|
+
prefixRef.current = prefix;
|
|
214
|
+
const measureRef = useRef<(() => void) | null>(null);
|
|
215
|
+
useLayoutEffect(() => {
|
|
216
|
+
measureRef.current?.();
|
|
217
|
+
}, [prefix]);
|
|
218
|
+
|
|
219
|
+
useLayoutEffect(() => {
|
|
220
|
+
if (!enabled) {
|
|
221
|
+
setRange((current) =>
|
|
222
|
+
current.start === 0 && current.end === heights.length && current.before === 0 && current.after === 0
|
|
223
|
+
? current
|
|
224
|
+
: { start: 0, end: heights.length, before: 0, after: 0 },
|
|
225
|
+
);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const list = listRef.current;
|
|
229
|
+
const scroller = list?.closest(scrollSelector);
|
|
230
|
+
if (!(list instanceof HTMLElement) || !(scroller instanceof HTMLElement)) {
|
|
231
|
+
setRange(variableVirtualWindowFromPrefix(prefixRef.current, 0, INITIAL_ROWS * 28, overscanPx));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
let lastScrollTop = scroller.scrollTop;
|
|
235
|
+
const measure = (): void => {
|
|
236
|
+
lastScrollTop = scroller.scrollTop;
|
|
237
|
+
const listBox = list.getBoundingClientRect();
|
|
238
|
+
const scrollBox = scroller.getBoundingClientRect();
|
|
239
|
+
const viewportStart = Math.max(0, scrollBox.top - listBox.top);
|
|
240
|
+
const viewportHeight = scroller.clientHeight || INITIAL_ROWS * 28;
|
|
241
|
+
const next = variableVirtualWindowFromPrefix(prefixRef.current, viewportStart, viewportHeight, overscanPx);
|
|
242
|
+
setRange((current) =>
|
|
243
|
+
current.start === next.start && current.end === next.end &&
|
|
244
|
+
current.before === next.before && current.after === next.after
|
|
245
|
+
? current
|
|
246
|
+
: next,
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
const onScroll = (): void => {
|
|
250
|
+
// See the fixed-row form above. Horizontal follow must stay a single
|
|
251
|
+
// `scrollLeft` write, never a vertical virtualization measurement.
|
|
252
|
+
if (scroller.scrollTop === lastScrollTop) return;
|
|
253
|
+
measure();
|
|
254
|
+
};
|
|
255
|
+
measureRef.current = measure;
|
|
256
|
+
measure();
|
|
257
|
+
scroller.addEventListener("scroll", onScroll, { passive: true });
|
|
258
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
|
|
259
|
+
observer?.observe(scroller);
|
|
260
|
+
observer?.observe(list);
|
|
261
|
+
window.addEventListener("resize", measure);
|
|
262
|
+
return () => {
|
|
263
|
+
if (measureRef.current === measure) measureRef.current = null;
|
|
264
|
+
scroller.removeEventListener("scroll", onScroll);
|
|
265
|
+
observer?.disconnect();
|
|
266
|
+
window.removeEventListener("resize", measure);
|
|
267
|
+
};
|
|
268
|
+
}, [enabled, heights.length, listRef, overscanPx, scrollSelector]);
|
|
269
|
+
|
|
270
|
+
return range;
|
|
271
|
+
}
|