@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
package/src/state/context.tsx
CHANGED
|
@@ -26,7 +26,25 @@ export function useStore(): ShotEditorStore {
|
|
|
26
26
|
export function useShotEditor<T>(selector: (s: ShotEditorState) => T): T {
|
|
27
27
|
const store = useStore();
|
|
28
28
|
return useSyncExternalStore(
|
|
29
|
-
store.
|
|
29
|
+
store.subscribeCold,
|
|
30
|
+
() => selector(store.getState()),
|
|
31
|
+
() => selector(store.getState()),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Subscribe to CLOCK-BEARING state.
|
|
37
|
+
*
|
|
38
|
+
* `useShotEditor` is deliberately deaf to a frame-only tick, which is what
|
|
39
|
+
* makes playback affordable for the chrome. A component that paints the
|
|
40
|
+
* playhead itself needs the opposite, and this is it — kept at the smallest
|
|
41
|
+
* leaf that must repaint every tick, never at a panel root, or the saving is
|
|
42
|
+
* handed straight back.
|
|
43
|
+
*/
|
|
44
|
+
export function useShotEditorHot<T>(selector: (s: ShotEditorState) => T): T {
|
|
45
|
+
const store = useStore();
|
|
46
|
+
return useSyncExternalStore(
|
|
47
|
+
store.subscribeHot,
|
|
30
48
|
() => selector(store.getState()),
|
|
31
49
|
() => selector(store.getState()),
|
|
32
50
|
);
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// The editor's per-viewer preferences, remembered across reloads.
|
|
2
|
+
//
|
|
3
|
+
// How the AUTHOR likes to look at the editor: the grid floor, the two
|
|
4
|
+
// diagnostic chips, motion paths, rulers, guides, labels, where the tool
|
|
5
|
+
// footer sits and the playback resolution. Each is a fact about the person,
|
|
6
|
+
// the way the theme is, never about the document, so it lives in
|
|
7
|
+
// localStorage and not in the save. The playback resolution and the Insert
|
|
8
|
+
// drawer's first meeting are facts about this browser too, so they never
|
|
9
|
+
// reach the account copy (`accountPrefs.ts`).
|
|
10
|
+
//
|
|
11
|
+
// ONE KEY HOLDING ONE OBJECT, the way `layoutPrefs` keeps the workspace. A
|
|
12
|
+
// toggle added later is one field here rather than one more key to name,
|
|
13
|
+
// migrate and clear.
|
|
14
|
+
//
|
|
15
|
+
// WHAT IS NOT HERE. The theme stays in `dailies.theme` (the ui package owns it
|
|
16
|
+
// and every surface reads it, not only this editor), and the panel layout stays
|
|
17
|
+
// in `dailies.workspace` (`layoutPrefs.ts`, which has its own clamping rules).
|
|
18
|
+
//
|
|
19
|
+
// Guarded on both sides: private mode and a locked-down storage policy each
|
|
20
|
+
// throw on access, and a preference is never worth an exception. A read that
|
|
21
|
+
// fails answers the defaults, which is the state the chrome had before this
|
|
22
|
+
// existed.
|
|
23
|
+
import { isPlaybackResolution } from "../bridgeTypes.js";
|
|
24
|
+
import type { PlaybackResolution } from "../bridgeTypes.js";
|
|
25
|
+
import type { ShotEditorStore } from "./store.js";
|
|
26
|
+
|
|
27
|
+
/** The one key, exported so the suite asserts against THIS string. */
|
|
28
|
+
export const EDITOR_PREFS_KEY = "dailies.editor";
|
|
29
|
+
|
|
30
|
+
/** The two keys `badgePrefs.ts` used to write, one string each. Read once by
|
|
31
|
+
* `readEditorPrefs`, folded into the object and removed. */
|
|
32
|
+
const LEGACY_BADGE_KEYS = {
|
|
33
|
+
showFpsBadge: "dailies.badge.fps",
|
|
34
|
+
showBackendBadge: "dailies.badge.backend",
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
export interface EditorPrefs {
|
|
38
|
+
/** The selected layer's motion path on the stage. */
|
|
39
|
+
motionPaths: boolean;
|
|
40
|
+
/** A single plain pick moves a paused playhead into the layer's settled
|
|
41
|
+
* window (`ShotEditorState.selectMovesPlayhead`). */
|
|
42
|
+
selectMovesPlayhead: boolean;
|
|
43
|
+
/** Rulers along the canvas edges. */
|
|
44
|
+
rulers: boolean;
|
|
45
|
+
/** Snapping guides while dragging. */
|
|
46
|
+
smartGuides: boolean;
|
|
47
|
+
/** Camera move bar labels on the timeline: fitted to the bar, or off. */
|
|
48
|
+
blockLabels: "fit" | "off";
|
|
49
|
+
/** Inspector field labels: the compact glyph, or the full name. */
|
|
50
|
+
fieldLabels: "compact" | "named";
|
|
51
|
+
/** Where the tool footer sits. */
|
|
52
|
+
toolbarPosition: "bottom" | "top";
|
|
53
|
+
/** The working pane's grid floor (`ShotEditorState.showGrid`). */
|
|
54
|
+
showGrid: boolean;
|
|
55
|
+
/** The canvas bar's frame-rate chip (`ShotEditorState.showFpsBadge`). */
|
|
56
|
+
showFpsBadge: boolean;
|
|
57
|
+
/** The canvas bar's renderer chip (`ShotEditorState.showBackendBadge`). */
|
|
58
|
+
showBackendBadge: boolean;
|
|
59
|
+
/** The author has met the Insert drawer: closed it, or added anything
|
|
60
|
+
* (`ShotEditorState.insertDrawerSeen`). Until then an empty Project opens
|
|
61
|
+
* it on its own, once. */
|
|
62
|
+
insertDrawerSeen: boolean;
|
|
63
|
+
/** What a played frame may render at (`ShotEditorState.playbackResolution`). */
|
|
64
|
+
playbackResolution: PlaybackResolution;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const EDITOR_PREF_DEFAULTS: Readonly<EditorPrefs> = {
|
|
68
|
+
motionPaths: true,
|
|
69
|
+
selectMovesPlayhead: true,
|
|
70
|
+
rulers: false,
|
|
71
|
+
smartGuides: true,
|
|
72
|
+
blockLabels: "fit",
|
|
73
|
+
fieldLabels: "compact",
|
|
74
|
+
toolbarPosition: "bottom",
|
|
75
|
+
showGrid: true,
|
|
76
|
+
showFpsBadge: true,
|
|
77
|
+
showBackendBadge: true,
|
|
78
|
+
insertDrawerSeen: false,
|
|
79
|
+
playbackResolution: "auto",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Each field's own test. A field that fails it falls back to its default and
|
|
83
|
+
* nothing else does: one bad value never costs the good ones. */
|
|
84
|
+
export const EDITOR_PREF_VALID: { [K in keyof EditorPrefs]: (value: unknown) => value is EditorPrefs[K] } = {
|
|
85
|
+
motionPaths: isBoolean,
|
|
86
|
+
selectMovesPlayhead: isBoolean,
|
|
87
|
+
rulers: isBoolean,
|
|
88
|
+
smartGuides: isBoolean,
|
|
89
|
+
blockLabels: (v): v is EditorPrefs["blockLabels"] => v === "fit" || v === "off",
|
|
90
|
+
fieldLabels: (v): v is EditorPrefs["fieldLabels"] => v === "compact" || v === "named",
|
|
91
|
+
toolbarPosition: (v): v is EditorPrefs["toolbarPosition"] => v === "bottom" || v === "top",
|
|
92
|
+
showGrid: isBoolean,
|
|
93
|
+
showFpsBadge: isBoolean,
|
|
94
|
+
showBackendBadge: isBoolean,
|
|
95
|
+
insertDrawerSeen: isBoolean,
|
|
96
|
+
playbackResolution: isPlaybackResolution,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const FIELDS = Object.keys(EDITOR_PREF_DEFAULTS) as readonly (keyof EditorPrefs)[];
|
|
100
|
+
|
|
101
|
+
function isBoolean(value: unknown): value is boolean {
|
|
102
|
+
return typeof value === "boolean";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The saved preferences, validated FIELD BY FIELD over the defaults.
|
|
107
|
+
*
|
|
108
|
+
* A missing key, corrupt JSON or a non-object all answer the defaults; one bad
|
|
109
|
+
* field answers its own default and keeps the rest.
|
|
110
|
+
*
|
|
111
|
+
* THE ONE-TIME MIGRATION lives here, because this is the first thing a boot
|
|
112
|
+
* reads: the old `dailies.badge.*` keys (only an explicit "0" hid a chip) fill
|
|
113
|
+
* the two badge fields when the object has no opinion of its own, the merged
|
|
114
|
+
* object is written, and the old keys are removed so the next boot never reads
|
|
115
|
+
* them again.
|
|
116
|
+
*/
|
|
117
|
+
export function readEditorPrefs(): EditorPrefs {
|
|
118
|
+
const prefs: EditorPrefs = { ...EDITOR_PREF_DEFAULTS };
|
|
119
|
+
let saved: Record<string, unknown> = {};
|
|
120
|
+
try {
|
|
121
|
+
const text = localStorage.getItem(EDITOR_PREFS_KEY);
|
|
122
|
+
if (text !== null) {
|
|
123
|
+
const parsed: unknown = JSON.parse(text);
|
|
124
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed))
|
|
125
|
+
saved = parsed as Record<string, unknown>;
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
/* unreadable or corrupt: the defaults stand */
|
|
129
|
+
}
|
|
130
|
+
for (const field of FIELDS) {
|
|
131
|
+
const value = saved[field];
|
|
132
|
+
if (EDITOR_PREF_VALID[field](value)) (prefs as Record<keyof EditorPrefs, unknown>)[field] = value;
|
|
133
|
+
}
|
|
134
|
+
migrateBadgeKeys(prefs, saved);
|
|
135
|
+
return prefs;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function migrateBadgeKeys(prefs: EditorPrefs, saved: Record<string, unknown>): void {
|
|
139
|
+
try {
|
|
140
|
+
let found = false;
|
|
141
|
+
for (const field of ["showFpsBadge", "showBackendBadge"] as const) {
|
|
142
|
+
const old = localStorage.getItem(LEGACY_BADGE_KEYS[field]);
|
|
143
|
+
if (old === null) continue;
|
|
144
|
+
found = true;
|
|
145
|
+
if (!(field in saved)) prefs[field] = old !== "0";
|
|
146
|
+
}
|
|
147
|
+
if (!found) return;
|
|
148
|
+
writeEditorPrefs(prefs);
|
|
149
|
+
for (const key of Object.values(LEGACY_BADGE_KEYS)) localStorage.removeItem(key);
|
|
150
|
+
} catch {
|
|
151
|
+
/* storage disabled: the migration waits for a boot that can write */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Write the preferences. */
|
|
156
|
+
export function writeEditorPrefs(prefs: EditorPrefs): void {
|
|
157
|
+
try {
|
|
158
|
+
localStorage.setItem(EDITOR_PREFS_KEY, JSON.stringify(prefs));
|
|
159
|
+
} catch {
|
|
160
|
+
/* storage disabled or full: the session still honours the toggle, it just forgets */
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Whether this browser has ever written the preferences. */
|
|
165
|
+
export function hasStoredEditorPrefs(): boolean {
|
|
166
|
+
try {
|
|
167
|
+
return localStorage.getItem(EDITOR_PREFS_KEY) !== null;
|
|
168
|
+
} catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Every field, read off the store's state. `base` stays for a field a later
|
|
174
|
+
* lane adds here before the store owns it. */
|
|
175
|
+
function prefsOf(store: ShotEditorStore, base: EditorPrefs): EditorPrefs {
|
|
176
|
+
const s = store.getState();
|
|
177
|
+
return {
|
|
178
|
+
...base,
|
|
179
|
+
showGrid: s.showGrid,
|
|
180
|
+
showFpsBadge: s.showFpsBadge,
|
|
181
|
+
showBackendBadge: s.showBackendBadge,
|
|
182
|
+
selectMovesPlayhead: s.selectMovesPlayhead,
|
|
183
|
+
insertDrawerSeen: s.insertDrawerSeen,
|
|
184
|
+
motionPaths: s.motionPaths,
|
|
185
|
+
rulers: s.rulers,
|
|
186
|
+
smartGuides: s.smartGuides,
|
|
187
|
+
blockLabels: s.blockLabels,
|
|
188
|
+
fieldLabels: s.fieldLabels,
|
|
189
|
+
toolbarPosition: s.toolbarPosition,
|
|
190
|
+
playbackResolution: s.playbackResolution,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function samePrefs(a: EditorPrefs, b: EditorPrefs): boolean {
|
|
195
|
+
for (const field of FIELDS) if (!Object.is(a[field], b[field])) return false;
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Writes the preferences whenever one of them changes, and returns the
|
|
201
|
+
* unsubscribe.
|
|
202
|
+
*
|
|
203
|
+
* SIGNATURE-GATED, like `persistLayoutPrefs`: the subscription runs on every
|
|
204
|
+
* store notification that is not a transport tick, so it compares a handful
|
|
205
|
+
* of fields and writes only on a real change. On the COLD lane, because no
|
|
206
|
+
* preference is the frame, and a raw one built this object sixty times a
|
|
207
|
+
* second while the transport played. The seed is the store as it stands,
|
|
208
|
+
* which `createShotEditorStore` built from `readEditorPrefs`, so subscribing
|
|
209
|
+
* writes nothing on its own.
|
|
210
|
+
*/
|
|
211
|
+
export function persistEditorPrefs(store: ShotEditorStore): () => void {
|
|
212
|
+
const base = readEditorPrefs();
|
|
213
|
+
let last = prefsOf(store, base);
|
|
214
|
+
return store.subscribeCold(() => {
|
|
215
|
+
const prefs = prefsOf(store, base);
|
|
216
|
+
if (samePrefs(prefs, last)) return;
|
|
217
|
+
last = prefs;
|
|
218
|
+
writeEditorPrefs(prefs);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// What broke for a person in the HOSTED editor, told to the Worker
|
|
2
|
+
// (`POST /api/error-reports`), so the admin panel can show what broke, for
|
|
3
|
+
// whom and since when.
|
|
4
|
+
//
|
|
5
|
+
// Hosted only (`dailiesHost`): the local CLI server has no such route and
|
|
6
|
+
// nobody to tell. NEVER DOCUMENT CONTENT: a code, ids, numbers and the error's
|
|
7
|
+
// own message, never a layer's text, props, code or a file. FIRE AND FORGET:
|
|
8
|
+
// a report never throws, never waits and is never retried. The same report
|
|
9
|
+
// goes at most once per `ERROR_REPORT_WINDOW_MS`, a tab sends at most
|
|
10
|
+
// `ERROR_REPORT_CAP`, and a 429 (rate-limited) or a 401, 403 or 404 (signed
|
|
11
|
+
// out, not hosted) ends sending for the tab.
|
|
12
|
+
import { stageFailuresOf } from "@dailies-studio/shot/doc";
|
|
13
|
+
import { dailiesHost } from "../hostApi.js";
|
|
14
|
+
import { subscribeRuntimeErrors } from "./runtimeErrors.js";
|
|
15
|
+
import { stageHealthReport } from "./stageHealth.js";
|
|
16
|
+
import type { ShotEditorStore } from "./store.js";
|
|
17
|
+
|
|
18
|
+
/** The route's body, as the chrome fills it. */
|
|
19
|
+
export interface ErrorReport {
|
|
20
|
+
/** Stable: `/^[a-z0-9][a-z0-9_.:-]*$/`, at most 80 characters. */
|
|
21
|
+
code: string;
|
|
22
|
+
/** The error's own message; cut to `MESSAGE_MAX` characters. */
|
|
23
|
+
message?: string;
|
|
24
|
+
/** The open Project's document id. */
|
|
25
|
+
projectId?: string;
|
|
26
|
+
/** The Shot (scene) id, when one is involved. */
|
|
27
|
+
shot?: string;
|
|
28
|
+
/** Small facts (a piece kind, a layer id, a frame, a refusal name), at
|
|
29
|
+
* most `DETAIL_MAX` characters of JSON or left out. */
|
|
30
|
+
detail?: Readonly<Record<string, string | number | boolean | null>>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const ERROR_REPORT_WINDOW_MS = 10 * 60_000;
|
|
34
|
+
export const ERROR_REPORT_CAP = 30;
|
|
35
|
+
const MESSAGE_MAX = 300;
|
|
36
|
+
const DETAIL_MAX = 1024;
|
|
37
|
+
/** The answers after which this tab sends nothing more. */
|
|
38
|
+
const STOP_STATUSES = [401, 403, 404, 429];
|
|
39
|
+
|
|
40
|
+
let sent = 0;
|
|
41
|
+
let stopped = false;
|
|
42
|
+
/** Each report's key, and when it was last sent. */
|
|
43
|
+
const lastSent = new Map<string, number>();
|
|
44
|
+
|
|
45
|
+
export function reportError(report: ErrorReport): void {
|
|
46
|
+
if (stopped || sent >= ERROR_REPORT_CAP || dailiesHost() === undefined) return;
|
|
47
|
+
try {
|
|
48
|
+
// Where a failure was first seen is not what failed: two reports that
|
|
49
|
+
// differ only in `frame` are the same report.
|
|
50
|
+
const key = JSON.stringify([report.code, report.projectId, report.shot, { ...report.detail, frame: undefined }]);
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
const last = lastSent.get(key);
|
|
53
|
+
if (last !== undefined && now - last < ERROR_REPORT_WINDOW_MS) return;
|
|
54
|
+
lastSent.set(key, now);
|
|
55
|
+
sent += 1;
|
|
56
|
+
const { message, detail, ...ids } = report;
|
|
57
|
+
const body = {
|
|
58
|
+
...ids,
|
|
59
|
+
...(message === undefined ? {} : { message: message.slice(0, MESSAGE_MAX) }),
|
|
60
|
+
...(detail === undefined || JSON.stringify(detail).length > DETAIL_MAX ? {} : { detail }),
|
|
61
|
+
};
|
|
62
|
+
void fetch("/api/error-reports", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "content-type": "application/json" },
|
|
65
|
+
body: JSON.stringify(body),
|
|
66
|
+
keepalive: true,
|
|
67
|
+
}).then(
|
|
68
|
+
(res) => {
|
|
69
|
+
if (STOP_STATUSES.includes(res.status)) stopped = true;
|
|
70
|
+
},
|
|
71
|
+
() => {},
|
|
72
|
+
);
|
|
73
|
+
} catch {
|
|
74
|
+
// A report must never break what it reports on.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Hosted, report what is wrong on this tab's stage, as an agent's stage check
|
|
80
|
+
* reads it (`stageFailuresOf`): every time the stage's health changes (a new
|
|
81
|
+
* revision drawn, a piece isolated, a crash, a link that failed) or the
|
|
82
|
+
* chrome's error log moves, never per frame. Returns the unsubscribe.
|
|
83
|
+
*/
|
|
84
|
+
export function reportStageFailures(store: ShotEditorStore): () => void {
|
|
85
|
+
if (dailiesHost() === undefined) return () => {};
|
|
86
|
+
const check = (): void => {
|
|
87
|
+
try {
|
|
88
|
+
const state = store.getState();
|
|
89
|
+
const projectId = state.doc === null ? {} : { projectId: state.doc.id };
|
|
90
|
+
// The errors seen at the revision on screen, as the log stamps them
|
|
91
|
+
// (renderEvents.ts `setRuntimeErrorRevision`).
|
|
92
|
+
for (const failure of stageFailuresOf(stageHealthReport(store), state.dirty ? null : state.rev)) {
|
|
93
|
+
const piece = failure.piece;
|
|
94
|
+
reportError(
|
|
95
|
+
piece === undefined
|
|
96
|
+
? { code: "stage.health_failure", message: failure.message, ...projectId, detail: { kind: failure.kind, source: failure.source ?? null } }
|
|
97
|
+
: {
|
|
98
|
+
code: "stage.piece_failure",
|
|
99
|
+
message: piece.message,
|
|
100
|
+
...projectId,
|
|
101
|
+
shot: piece.sceneId,
|
|
102
|
+
detail: { piece: piece.kind, layer: piece.layerId ?? null, field: piece.fieldPath ?? null, frame: piece.firstFrame },
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// The store calls this from its own lanes (attachStage among them).
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const stops = [store.subscribeStageHealth(check), subscribeRuntimeErrors(check)];
|
|
111
|
+
return () => {
|
|
112
|
+
for (const stop of stops) stop();
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Test seam: a fresh tab. */
|
|
117
|
+
export function resetErrorReports(): void {
|
|
118
|
+
sent = 0;
|
|
119
|
+
stopped = false;
|
|
120
|
+
lastSent.clear();
|
|
121
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// The selection fan-out and the typed transform field, as plain functions over
|
|
2
|
+
// the store. They live in state/ rather than beside the inspector hooks so the
|
|
3
|
+
// store's own key verbs (digit opacity) and the in-tab WebMCP tools can run the
|
|
4
|
+
// identical write without importing a React module.
|
|
5
|
+
import type { ShotEditorStore } from "./store.js";
|
|
6
|
+
import {
|
|
7
|
+
containerFrame,
|
|
8
|
+
effectiveTransform,
|
|
9
|
+
fixedTextBox,
|
|
10
|
+
homeOf,
|
|
11
|
+
keyframeMinter,
|
|
12
|
+
lockRefusal,
|
|
13
|
+
lockRefuses,
|
|
14
|
+
patchHome,
|
|
15
|
+
routeShownEdit,
|
|
16
|
+
setEffectiveField,
|
|
17
|
+
} from "@dailies-studio/shot/doc";
|
|
18
|
+
import type { Doc3D, Layer3D, Transform3D, TransformField } from "@dailies-studio/shot/doc";
|
|
19
|
+
import { authoringFrame } from "./viewMode.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The fan-out itself, PURE and over the document it is HANDED.
|
|
23
|
+
*
|
|
24
|
+
* Both write paths run it, which is the point: a typed value and a drag must
|
|
25
|
+
* reach the same layers, refuse the same locked ones and leave the same document
|
|
26
|
+
* identity behind, or the inspector has two answers to "what did that edit do".
|
|
27
|
+
* The commit path hands it `edit`'s document; a scrub capture hands it the
|
|
28
|
+
* gesture's BASE re-derived, which is what stops a drag compounding.
|
|
29
|
+
*
|
|
30
|
+
* IDENTITY WHEN NOTHING CHANGED is load-bearing at both call sites: `edit`
|
|
31
|
+
* records no history entry and schedules no save for a document it gets back
|
|
32
|
+
* unchanged, and a scrub that ends where it started commits nothing at all.
|
|
33
|
+
*/
|
|
34
|
+
export function writeSelection(
|
|
35
|
+
doc: Doc3D,
|
|
36
|
+
containerId: string,
|
|
37
|
+
ids: readonly string[],
|
|
38
|
+
build: (layer: Layer3D) => Layer3D,
|
|
39
|
+
sceneFrame: number,
|
|
40
|
+
): { doc: Doc3D; refusal: string | null } {
|
|
41
|
+
const home = homeOf(doc, containerId);
|
|
42
|
+
if (home === null) return { doc, refusal: null };
|
|
43
|
+
const layersById = { ...home.layersById };
|
|
44
|
+
const mintId = keyframeMinter(doc);
|
|
45
|
+
let changed = false;
|
|
46
|
+
let refusal: string | null = null;
|
|
47
|
+
for (const id of ids) {
|
|
48
|
+
const layer = layersById[id];
|
|
49
|
+
if (layer === undefined) continue;
|
|
50
|
+
// The section builds from the layer as it shows it: an animated field at
|
|
51
|
+
// its value on the playhead. So typing a field back to its stored base is
|
|
52
|
+
// a change from what is on screen, and the router keys it.
|
|
53
|
+
const { from, built, next } = routeShownEdit(layer, build, sceneFrame, doc.fps, mintId);
|
|
54
|
+
if (built === from) continue;
|
|
55
|
+
// A locked layer takes no edit but its own Visible and Locked flags, and
|
|
56
|
+
// says so by name rather than leaving a field that silently did nothing.
|
|
57
|
+
if (lockRefuses(from, built)) {
|
|
58
|
+
refusal ??= lockRefusal(layer);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// ── KEYFRAME-AWARE, ONCE, FOR EVERY SECTION ────────────────────────────
|
|
62
|
+
//
|
|
63
|
+
// `build` is a section's own `layer -> layer` and knows nothing about
|
|
64
|
+
// tracks. `routeLayerEdit` turns the diff it produced into a keyframe on
|
|
65
|
+
// any path this layer animates, which is what makes every colour picker,
|
|
66
|
+
// radius field and shadow slider in the inspector honour a track without
|
|
67
|
+
// one of them being edited. Untracked layers return `after` by reference,
|
|
68
|
+
// so the common case is unchanged.
|
|
69
|
+
if (next === layer) continue;
|
|
70
|
+
layersById[id] = next;
|
|
71
|
+
changed = true;
|
|
72
|
+
}
|
|
73
|
+
return { doc: changed ? patchHome(doc, containerId, { layers: home.layers, layersById }) : doc, refusal };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** One transform field's number, read off a transform. */
|
|
77
|
+
export function readField(t: Transform3D, field: TransformField): number {
|
|
78
|
+
switch (field) {
|
|
79
|
+
case "x":
|
|
80
|
+
return t.position[0];
|
|
81
|
+
case "y":
|
|
82
|
+
return t.position[1];
|
|
83
|
+
case "z":
|
|
84
|
+
return t.position[2];
|
|
85
|
+
case "w":
|
|
86
|
+
return t.size[0];
|
|
87
|
+
case "h":
|
|
88
|
+
return t.size[1];
|
|
89
|
+
case "tiltX":
|
|
90
|
+
return t.tilt[0];
|
|
91
|
+
case "tiltY":
|
|
92
|
+
return t.tilt[1];
|
|
93
|
+
case "roll":
|
|
94
|
+
return t.rollDeg;
|
|
95
|
+
case "opacity":
|
|
96
|
+
return t.opacity;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* `layer` with one transform field typed, for `writeSelection` to route.
|
|
102
|
+
*
|
|
103
|
+
* The typed path and the drag (`useTransformScrub`) both build with this, so a
|
|
104
|
+
* number typed and the same number dragged leave one document. The new
|
|
105
|
+
* transform is a BASE (`setEffectiveField`): `writeSelection`'s
|
|
106
|
+
* `routeLayerEdit` turns a tracked field into a key at the playhead and leaves
|
|
107
|
+
* an untracked one a constant.
|
|
108
|
+
*
|
|
109
|
+
* The SAME LAYER when the value is already there. `routeLayerEdit` hands an
|
|
110
|
+
* untracked layer's edit back as it is, so without this a typed no-op would
|
|
111
|
+
* cost an undo entry.
|
|
112
|
+
*/
|
|
113
|
+
export function typeTransformField(
|
|
114
|
+
layer: Layer3D,
|
|
115
|
+
field: TransformField,
|
|
116
|
+
value: number,
|
|
117
|
+
sceneFrame: number,
|
|
118
|
+
fps: Doc3D["fps"],
|
|
119
|
+
): Layer3D {
|
|
120
|
+
const transform = setEffectiveField(layer, field, value, sceneFrame, fps);
|
|
121
|
+
if (readField(transform, field) === readField(effectiveTransform(layer, sceneFrame, fps), field)) return layer;
|
|
122
|
+
const posed = { ...layer, transform };
|
|
123
|
+
return field === "w" || field === "h" ? fixedTextBox(posed) : posed;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Type a transform field onto `ids`, PURE over the store, returning the refusal.
|
|
128
|
+
*
|
|
129
|
+
* Extracted from the hook below so a caller that is not a React tree — the
|
|
130
|
+
* in-tab WebMCP tools (`state/webmcp.ts`) — runs the identical operation rather
|
|
131
|
+
* than a second one that looks like it. Animate mode needs no fork here: the
|
|
132
|
+
* store's `noteChange` records the edit as keys, for this write and every other.
|
|
133
|
+
*
|
|
134
|
+
* `ids` is the fan-out, so the hook passes the live selection and a tool may
|
|
135
|
+
* pass one layer.
|
|
136
|
+
*/
|
|
137
|
+
export function commitTransformField(
|
|
138
|
+
store: ShotEditorStore,
|
|
139
|
+
field: TransformField,
|
|
140
|
+
value: number,
|
|
141
|
+
ids: readonly string[],
|
|
142
|
+
): string | null {
|
|
143
|
+
// LIVE state at the keystroke, never the display hook's coalesced frame: the
|
|
144
|
+
// compensation arithmetic must target the frame actually painted, or the
|
|
145
|
+
// typed value lands up to 100ms of playhead away from where it reads back.
|
|
146
|
+
const s = store.getState();
|
|
147
|
+
const doc = s.doc;
|
|
148
|
+
// The CONTAINER, and its own clock: with a definition open the write targets
|
|
149
|
+
// its interior at the DEF-LOCAL playhead.
|
|
150
|
+
const container = s.editingDef ?? s.sceneId;
|
|
151
|
+
if (doc === null || container === null) return null;
|
|
152
|
+
const sceneFrame = containerFrame(doc, container, authoringFrame(s));
|
|
153
|
+
let refusal: string | null = null;
|
|
154
|
+
const build = (layer: Layer3D): Layer3D => typeTransformField(layer, field, value, sceneFrame, doc.fps);
|
|
155
|
+
const ran = store.edit((next) => {
|
|
156
|
+
const out = writeSelection(next, container, ids, build, sceneFrame);
|
|
157
|
+
refusal = out.refusal;
|
|
158
|
+
return out.doc;
|
|
159
|
+
});
|
|
160
|
+
return ran ? refusal : store.getState().error;
|
|
161
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// THE FONT PICKER'S RECENT LIST: the families this viewer picked last, newest
|
|
2
|
+
// first, in this browser only (`localStorage`). A convenience, so every read
|
|
3
|
+
// and write is guarded (private mode and a locked-down storage policy throw on
|
|
4
|
+
// access) and a bad entry is dropped field by field, never the whole list.
|
|
5
|
+
//
|
|
6
|
+
// An entry names where the family comes from, not a Project's files: a Google
|
|
7
|
+
// family by its catalog name, a workspace family by its name, so it resolves in
|
|
8
|
+
// any Project of the workspace. A family only one Project holds as an upload is
|
|
9
|
+
// not recorded: it is already listed under In this Project there.
|
|
10
|
+
|
|
11
|
+
/** The one key, exported so the suite asserts against THIS string. */
|
|
12
|
+
export const FONT_RECENTS_KEY = "dailies.fonts.recent";
|
|
13
|
+
|
|
14
|
+
/** How many families the list keeps. */
|
|
15
|
+
export const FONT_RECENTS_MAX = 8;
|
|
16
|
+
|
|
17
|
+
/** A name past this is not a family anyone typed. */
|
|
18
|
+
const FAMILY_MAX = 200;
|
|
19
|
+
|
|
20
|
+
export interface FontRecent {
|
|
21
|
+
kind: "google" | "workspace";
|
|
22
|
+
family: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A family's key: case-folded, runs of spaces as one. The picker's modules
|
|
26
|
+
* share this one copy; it must stay the same as the private copies in
|
|
27
|
+
* state/fonts.ts (`ProjectFontFamily.key`) and fonts/googleFonts.ts. */
|
|
28
|
+
export const fold = (family: string): string => family.toLowerCase().replace(/\s+/gu, " ").trim();
|
|
29
|
+
const sameRecent = (a: FontRecent, b: FontRecent): boolean => a.kind === b.kind && fold(a.family) === fold(b.family);
|
|
30
|
+
|
|
31
|
+
/** One stored entry, or null when any field is not what a write stores. */
|
|
32
|
+
function recentOf(value: unknown): FontRecent | null {
|
|
33
|
+
if (typeof value !== "object" || value === null) return null;
|
|
34
|
+
const { kind, family } = value as Record<string, unknown>;
|
|
35
|
+
if (kind !== "google" && kind !== "workspace") return null;
|
|
36
|
+
if (typeof family !== "string" || family.trim() === "" || family.length > FAMILY_MAX) return null;
|
|
37
|
+
return { kind, family };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The list, newest first: valid entries only, each once, at most the cap. */
|
|
41
|
+
export function readFontRecents(): FontRecent[] {
|
|
42
|
+
let parsed: unknown;
|
|
43
|
+
try {
|
|
44
|
+
const text = localStorage.getItem(FONT_RECENTS_KEY);
|
|
45
|
+
if (text === null) return [];
|
|
46
|
+
parsed = JSON.parse(text);
|
|
47
|
+
} catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
if (!Array.isArray(parsed)) return [];
|
|
51
|
+
const list: FontRecent[] = [];
|
|
52
|
+
for (const value of parsed) {
|
|
53
|
+
const recent = recentOf(value);
|
|
54
|
+
if (recent !== null && !list.some((held) => sameRecent(held, recent))) list.push(recent);
|
|
55
|
+
if (list.length === FONT_RECENTS_MAX) break;
|
|
56
|
+
}
|
|
57
|
+
return list;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Put `recent` first (moving it up if it was listed) and save; answers the
|
|
61
|
+
* new list. A write that fails leaves the session as it was. */
|
|
62
|
+
export function recordFontRecent(recent: FontRecent): FontRecent[] {
|
|
63
|
+
const list = [recent, ...readFontRecents().filter((held) => !sameRecent(held, recent))].slice(0, FONT_RECENTS_MAX);
|
|
64
|
+
try {
|
|
65
|
+
localStorage.setItem(FONT_RECENTS_KEY, JSON.stringify(list));
|
|
66
|
+
} catch {
|
|
67
|
+
// Storage disabled or full: the pick still applied, it is just not remembered.
|
|
68
|
+
}
|
|
69
|
+
return list;
|
|
70
|
+
}
|