@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/stage/isolation.ts
CHANGED
|
@@ -23,9 +23,21 @@
|
|
|
23
23
|
//
|
|
24
24
|
// The frustum is sized from the subtree's own bounds, measured in the ROOT's
|
|
25
25
|
// frame, so entering a subtree frames it whatever the scene camera was doing.
|
|
26
|
-
import { toLocal, worldStateAt } from "@dailies-studio/shot/doc";
|
|
27
|
-
import type {
|
|
26
|
+
import { effectiveTransform, layerAliveAt, layerChildren, toLocal, worldStateAt } from "@dailies-studio/shot/doc";
|
|
27
|
+
import type { LayerLookup, Mat3, Vec3 } from "@dailies-studio/shot/doc";
|
|
28
28
|
import type { Rational } from "@dailies-studio/engine";
|
|
29
|
+
import type { PaneRect } from "./panes.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Bind-pose box sizes by layer id, as the runtime measured them.
|
|
33
|
+
*
|
|
34
|
+
* A model is contain-fitted INTO its authored box (`applyFit`), so a model whose
|
|
35
|
+
* bind-pose aspect differs from that box occupies only part of it. Framing the
|
|
36
|
+
* box therefore frames empty air on two sides, which is most of why an isolated
|
|
37
|
+
* mesh read small. The stage collects these from `onMeshStats` and they never
|
|
38
|
+
* cross the bridge — this is a viewing decision, not a document one.
|
|
39
|
+
*/
|
|
40
|
+
export type MeshBounds = ReadonlyMap<string, readonly [number, number, number]>;
|
|
29
41
|
|
|
30
42
|
/** How much of the pane is margin around the framed subtree, per side. */
|
|
31
43
|
export const ISOLATION_MARGIN = 0.12;
|
|
@@ -34,6 +46,26 @@ export const ISOLATION_MARGIN = 0.12;
|
|
|
34
46
|
* a real document, and a zero-width ortho camera projects everything to NaN. */
|
|
35
47
|
const MIN_HALF_EXTENT = 1;
|
|
36
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The COMPONENT CONTEXT's frame (unify P2): a definition's canvas is flat at
|
|
51
|
+
* the origin in its own basis, so its head-on frame is a literal — never a
|
|
52
|
+
* derivation off an instance (`isolationFrame` needs a scene and a placed
|
|
53
|
+
* root, and a def-first entry has neither).
|
|
54
|
+
*/
|
|
55
|
+
export function componentFrame(size: readonly [number, number]): IsolationFrame {
|
|
56
|
+
return {
|
|
57
|
+
target: [0, 0, 0],
|
|
58
|
+
rotation: [
|
|
59
|
+
[1, 0, 0],
|
|
60
|
+
[0, 1, 0],
|
|
61
|
+
[0, 0, 1],
|
|
62
|
+
],
|
|
63
|
+
halfWidth: Math.max(MIN_HALF_EXTENT, size[0] / 2),
|
|
64
|
+
halfHeight: Math.max(MIN_HALF_EXTENT, size[1] / 2),
|
|
65
|
+
halfDepth: MIN_HALF_EXTENT,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
37
69
|
export interface IsolationFrame {
|
|
38
70
|
/** Where to look, in THREE world space. */
|
|
39
71
|
target: Vec3;
|
|
@@ -53,58 +85,284 @@ export interface IsolationFrame {
|
|
|
53
85
|
* parent is mid-rotation is looking at where it is now. Returns null when the
|
|
54
86
|
* layer is not in this scene, which is what "the isolation went stale" looks
|
|
55
87
|
* like from here.
|
|
88
|
+
*
|
|
89
|
+
* A bare `LayerLookup` and not a `Scene3D`, because a component DEFINITION is
|
|
90
|
+
* also a layer table and a mesh inside one deserves the same head-on frame a
|
|
91
|
+
* mesh in a scene gets. Nothing here ever read anything but `layersById`.
|
|
56
92
|
*/
|
|
57
93
|
export function isolationFrame(
|
|
58
|
-
scene:
|
|
94
|
+
scene: LayerLookup,
|
|
59
95
|
rootId: string,
|
|
60
96
|
subtree: ReadonlySet<string>,
|
|
61
97
|
sceneFrame: number,
|
|
62
98
|
fps: Rational,
|
|
99
|
+
meshBounds?: MeshBounds,
|
|
100
|
+
): IsolationFrame | null {
|
|
101
|
+
if (scene.layersById[rootId] === undefined) return null;
|
|
102
|
+
const rootPose = worldStateAt(scene, rootId, sceneFrame, fps);
|
|
103
|
+
const bounds = emptyBounds();
|
|
104
|
+
for (const id of subtree) addMember(bounds, scene, rootPose, id, sceneFrame, fps, meshBounds);
|
|
105
|
+
return frameOf(rootPose, bounds);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The most frames `isolationFrameOverLife` resolves the subtree at. */
|
|
109
|
+
export const MAX_LIFE_SAMPLES = 240;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The head-on frame that holds `rootId`'s subtree for its WHOLE life, seen from
|
|
113
|
+
* the root's pose at `anchorFrame`.
|
|
114
|
+
*
|
|
115
|
+
* ── WHY NOT THE FRAME AT THE PLAYHEAD ────────────────────────────────────
|
|
116
|
+
*
|
|
117
|
+
* The frame at one instant is the right size for that instant only. Fitted per
|
|
118
|
+
* frame during playback, the camera re-centred and re-zoomed on every frame, so
|
|
119
|
+
* anything that moved or scaled inside the subtree was cancelled out by the
|
|
120
|
+
* camera chasing it: the picture slid and pulsed while the subject stood still.
|
|
121
|
+
* Isolation is a place the author edits in, so its camera holds still and the
|
|
122
|
+
* motion plays inside it. That needs a box that already contains every pose.
|
|
123
|
+
*
|
|
124
|
+
* The basis is the root's pose at the ANCHOR (the frame the author entered on),
|
|
125
|
+
* not at each sample: a root that rotates over its life would otherwise give a
|
|
126
|
+
* union of boxes measured in different frames, which bounds nothing. Every
|
|
127
|
+
* member at every sample is measured in that one basis.
|
|
128
|
+
*
|
|
129
|
+
* Only what DRAWS is counted: a member outside its lifetime, hidden, or under a
|
|
130
|
+
* parent that is either, paints nothing at that frame, and framing where it
|
|
131
|
+
* would have been is empty margin. Ancestors ABOVE the root are not consulted:
|
|
132
|
+
* when one of them is dead the whole subtree is, and that frame adds nothing.
|
|
133
|
+
* Nothing drawing at any sample falls back to the anchor frame's own box, so a
|
|
134
|
+
* subtree that is never on screen still opens on something.
|
|
135
|
+
*
|
|
136
|
+
* ponytail: past `MAX_LIFE_SAMPLES` frames the life is sampled at an even
|
|
137
|
+
* stride, so an overshoot between two samples can poke past the box. The 12%
|
|
138
|
+
* margin absorbs any ordinary ease. Sample the track keys too if it ever clips.
|
|
139
|
+
*/
|
|
140
|
+
export function isolationFrameOverLife(
|
|
141
|
+
scene: LayerLookup,
|
|
142
|
+
rootId: string,
|
|
143
|
+
anchorFrame: number,
|
|
144
|
+
durationFrames: number,
|
|
145
|
+
fps: Rational,
|
|
146
|
+
meshBounds?: MeshBounds,
|
|
63
147
|
): IsolationFrame | null {
|
|
64
148
|
const root = scene.layersById[rootId];
|
|
65
149
|
if (root === undefined) return null;
|
|
66
|
-
const rootPose = worldStateAt(scene, rootId,
|
|
150
|
+
const rootPose = worldStateAt(scene, rootId, anchorFrame, fps);
|
|
151
|
+
const bounds = emptyBounds();
|
|
152
|
+
const end = Math.max(1, Math.floor(durationFrames));
|
|
153
|
+
const stride = Math.max(1, end / MAX_LIFE_SAMPLES);
|
|
154
|
+
for (let s = 0; s < end; s += stride) {
|
|
155
|
+
const frame = Math.floor(s);
|
|
156
|
+
// Depth-first from the root, carrying "every ancestor down here draws".
|
|
157
|
+
const visit = (id: string): void => {
|
|
158
|
+
const layer = scene.layersById[id];
|
|
159
|
+
if (layer === undefined || !layerAliveAt(layer, frame)) return;
|
|
160
|
+
addMember(bounds, scene, rootPose, id, frame, fps, meshBounds);
|
|
161
|
+
for (const child of layerChildren(layer)) visit(child);
|
|
162
|
+
};
|
|
163
|
+
visit(rootId);
|
|
164
|
+
}
|
|
165
|
+
return Number.isFinite(bounds.minX)
|
|
166
|
+
? frameOf(rootPose, bounds)
|
|
167
|
+
: isolationFrame(scene, rootId, subtreeOf(scene, rootId), anchorFrame, fps, meshBounds);
|
|
168
|
+
}
|
|
67
169
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let minZ = Infinity;
|
|
73
|
-
let maxZ = -Infinity;
|
|
74
|
-
for (const id of subtree) {
|
|
170
|
+
/** `rootId` and every layer under it, through the one child walk. */
|
|
171
|
+
function subtreeOf(scene: LayerLookup, rootId: string): Set<string> {
|
|
172
|
+
const out = new Set<string>();
|
|
173
|
+
const visit = (id: string): void => {
|
|
75
174
|
const layer = scene.layersById[id];
|
|
76
|
-
if (layer === undefined)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
175
|
+
if (layer === undefined || out.has(id)) return;
|
|
176
|
+
out.add(id);
|
|
177
|
+
for (const child of layerChildren(layer)) visit(child);
|
|
178
|
+
};
|
|
179
|
+
visit(rootId);
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface Bounds {
|
|
184
|
+
minX: number;
|
|
185
|
+
maxX: number;
|
|
186
|
+
minY: number;
|
|
187
|
+
maxY: number;
|
|
188
|
+
minZ: number;
|
|
189
|
+
maxZ: number;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function emptyBounds(): Bounds {
|
|
193
|
+
return { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity, minZ: Infinity, maxZ: -Infinity };
|
|
194
|
+
}
|
|
91
195
|
|
|
196
|
+
/** Grow `bounds` by one member's box at one frame, measured in `rootPose`. */
|
|
197
|
+
function addMember(
|
|
198
|
+
bounds: Bounds,
|
|
199
|
+
scene: LayerLookup,
|
|
200
|
+
rootPose: ReturnType<typeof worldStateAt>,
|
|
201
|
+
id: string,
|
|
202
|
+
sceneFrame: number,
|
|
203
|
+
fps: Rational,
|
|
204
|
+
meshBounds: MeshBounds | undefined,
|
|
205
|
+
): void {
|
|
206
|
+
const layer = scene.layersById[id];
|
|
207
|
+
if (layer === undefined) return;
|
|
208
|
+
// Each member's centre in the ROOT's frame, with its own axis-aligned box
|
|
209
|
+
// around it. The box ignores the member's own tilt and roll, the same
|
|
210
|
+
// simplification `groupLayers` makes for the same reason: a rotation-aware
|
|
211
|
+
// hull would frame a rolled child looser than the box the author sees.
|
|
212
|
+
const local = toLocal(rootPose, worldStateAt(scene, id, sceneFrame, fps));
|
|
213
|
+
// The EFFECTIVE size, not the authored one. `worldStateAt` already resolves
|
|
214
|
+
// the chain, so reading `layer.transform.size` beside it framed a box the
|
|
215
|
+
// layer no longer occupies the moment a size track scaled it.
|
|
216
|
+
const [w, h] = effectiveTransform(layer, sceneFrame, fps).size;
|
|
217
|
+
const box = occupiedBox(layer, w, h, meshBounds?.get(id));
|
|
218
|
+
bounds.minX = Math.min(bounds.minX, local.position[0] - box[0] / 2);
|
|
219
|
+
bounds.maxX = Math.max(bounds.maxX, local.position[0] + box[0] / 2);
|
|
220
|
+
bounds.minY = Math.min(bounds.minY, local.position[1] - box[1] / 2);
|
|
221
|
+
bounds.maxY = Math.max(bounds.maxY, local.position[1] + box[1] / 2);
|
|
222
|
+
bounds.minZ = Math.min(bounds.minZ, local.position[2] - box[2] / 2);
|
|
223
|
+
bounds.maxZ = Math.max(bounds.maxZ, local.position[2] + box[2] / 2);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function frameOf(rootPose: ReturnType<typeof worldStateAt>, b: Bounds): IsolationFrame | null {
|
|
227
|
+
if (!Number.isFinite(b.minX)) return null;
|
|
92
228
|
// The bounds' CENTRE, not the root's origin: a group whose children all sit
|
|
93
229
|
// to one side would otherwise be framed off-centre, which is the state the
|
|
94
230
|
// grouping arithmetic exists to make impossible to author but which a
|
|
95
231
|
// hand-written document can still hold.
|
|
96
|
-
const centre: Vec3 = [(minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2];
|
|
97
|
-
const world = worldOf(rootPose, centre);
|
|
98
|
-
|
|
232
|
+
const centre: Vec3 = [(b.minX + b.maxX) / 2, (b.minY + b.maxY) / 2, (b.minZ + b.maxZ) / 2];
|
|
99
233
|
return {
|
|
100
|
-
target:
|
|
234
|
+
target: worldOf(rootPose, centre),
|
|
101
235
|
rotation: rootPose.rotation,
|
|
102
|
-
halfWidth: Math.max(MIN_HALF_EXTENT, (maxX - minX) / 2),
|
|
103
|
-
halfHeight: Math.max(MIN_HALF_EXTENT, (maxY - minY) / 2),
|
|
104
|
-
halfDepth: Math.max(MIN_HALF_EXTENT, (maxZ - minZ) / 2),
|
|
236
|
+
halfWidth: Math.max(MIN_HALF_EXTENT, (b.maxX - b.minX) / 2),
|
|
237
|
+
halfHeight: Math.max(MIN_HALF_EXTENT, (b.maxY - b.minY) / 2),
|
|
238
|
+
halfDepth: Math.max(MIN_HALF_EXTENT, (b.maxZ - b.minZ) / 2),
|
|
105
239
|
};
|
|
106
240
|
}
|
|
107
241
|
|
|
242
|
+
/**
|
|
243
|
+
* THE head-on camera: everything a consumer needs to draw, measure or hand over
|
|
244
|
+
* a derived frame, from one derivation.
|
|
245
|
+
*
|
|
246
|
+
* ── WHY ONE FUNCTION AND NOT THREE CALLERS OF `isolationExtent` ─────────
|
|
247
|
+
*
|
|
248
|
+
* There were three: the camera `StageTools` installs, `paneScreenScale` (which
|
|
249
|
+
* every drag speed and every snap threshold is measured in), and the pose the
|
|
250
|
+
* orbit rig is seeded with on entry. Three derivations of one number is three
|
|
251
|
+
* chances to disagree, and the disagreement is invisible — a drag that runs an
|
|
252
|
+
* order of magnitude fast looks like a bad feel, not like a bug. `pxPerUnit` is
|
|
253
|
+
* that number, answered once.
|
|
254
|
+
*/
|
|
255
|
+
export interface HeadOnCamera {
|
|
256
|
+
/** Orthographic half-extents covering the WHOLE pane, in design px. */
|
|
257
|
+
halfWidth: number;
|
|
258
|
+
halfHeight: number;
|
|
259
|
+
near: number;
|
|
260
|
+
far: number;
|
|
261
|
+
eye: Vec3;
|
|
262
|
+
up: Vec3;
|
|
263
|
+
/** Where the camera looks: the subject, slid so it lands centred in the
|
|
264
|
+
* VISIBLE box rather than in the pane. */
|
|
265
|
+
target: Vec3;
|
|
266
|
+
/** CSS px per design px. The one scale every consumer measures with. */
|
|
267
|
+
pxPerUnit: number;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Frame a subtree head-on inside a pane the chrome is partly covering.
|
|
272
|
+
*
|
|
273
|
+
* The subject is fitted to `visible`; the frustum is then grown to cover `pane`
|
|
274
|
+
* AT THE SAME SCALE, and the target slid so the subject sits in the middle of
|
|
275
|
+
* the hole. So the picture behind the chrome is simply extra: nothing is
|
|
276
|
+
* scaled to fit furniture, and nothing hides under it.
|
|
277
|
+
*
|
|
278
|
+
* Pass the same rect twice for the un-inset behaviour (the tests, and any
|
|
279
|
+
* caller that genuinely owns its whole pane).
|
|
280
|
+
*/
|
|
281
|
+
export function headOnCamera(
|
|
282
|
+
frame: IsolationFrame,
|
|
283
|
+
pane: PaneRect,
|
|
284
|
+
visible: PaneRect,
|
|
285
|
+
): HeadOnCamera {
|
|
286
|
+
const box = visible.width > 0 && visible.height > 0 ? visible : pane;
|
|
287
|
+
const fitted = isolationExtent(frame, box.height === 0 ? 1 : box.width / box.height);
|
|
288
|
+
// The scale the fit implies, and the only place it is decided.
|
|
289
|
+
const pxPerUnit = fitted.halfHeight > 0 ? box.height / (2 * fitted.halfHeight) : 1;
|
|
290
|
+
const halfWidth = pane.width / (2 * pxPerUnit);
|
|
291
|
+
const halfHeight = pane.height / (2 * pxPerUnit);
|
|
292
|
+
|
|
293
|
+
// ── THE SLIDE ────────────────────────────────────────────────────────────
|
|
294
|
+
//
|
|
295
|
+
// A camera looks at the middle of its pane, so the subject lands there. Push
|
|
296
|
+
// the target the OTHER way by however far the visible box's centre is from
|
|
297
|
+
// the pane's, and the subject lands in the visible box's middle instead.
|
|
298
|
+
//
|
|
299
|
+
// The axes are the frame's OWN basis, so this works for a rolled or a
|
|
300
|
+
// face-down subject exactly as it does for a flat one: column 0 is screen
|
|
301
|
+
// right and column 1 is screen up (`worldOf` maps design +x to column 0).
|
|
302
|
+
// Pane y runs DOWN, world up runs UP, which is why the two signs differ.
|
|
303
|
+
const dx = (visible.x + visible.width / 2 - (pane.x + pane.width / 2)) / pxPerUnit;
|
|
304
|
+
const dy = (visible.y + visible.height / 2 - (pane.y + pane.height / 2)) / pxPerUnit;
|
|
305
|
+
const right: Vec3 = [frame.rotation[0][0], frame.rotation[1][0], frame.rotation[2][0]];
|
|
306
|
+
const { eye, up, far } = isolationEye(frame);
|
|
307
|
+
const target: Vec3 = [
|
|
308
|
+
frame.target[0] - right[0] * dx + up[0] * dy,
|
|
309
|
+
frame.target[1] - right[1] * dx + up[1] * dy,
|
|
310
|
+
frame.target[2] - right[2] * dx + up[2] * dy,
|
|
311
|
+
];
|
|
312
|
+
// The eye rides the target. The slide is perpendicular to the view normal, so
|
|
313
|
+
// the depth slab `isolationEye` sized is unchanged by it.
|
|
314
|
+
return {
|
|
315
|
+
halfWidth,
|
|
316
|
+
halfHeight,
|
|
317
|
+
near: 0.1,
|
|
318
|
+
far,
|
|
319
|
+
eye: [
|
|
320
|
+
eye[0] + (target[0] - frame.target[0]),
|
|
321
|
+
eye[1] + (target[1] - frame.target[1]),
|
|
322
|
+
eye[2] + (target[2] - frame.target[2]),
|
|
323
|
+
],
|
|
324
|
+
up,
|
|
325
|
+
target,
|
|
326
|
+
pxPerUnit,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* What a layer actually FILLS of its authored box, as a `[w, h, d]` extent.
|
|
332
|
+
*
|
|
333
|
+
* For everything but a loaded model that is the box itself, flat. A model is a
|
|
334
|
+
* different story: `applyFit` (shot/mesh.tsx) contain-fits the bind-pose box
|
|
335
|
+
* into the authored one, so a tall model in a square box paints a tall sliver
|
|
336
|
+
* and the two side thirds are empty. This mirrors that arithmetic exactly —
|
|
337
|
+
* the same `min(w/bx, h/by)`, the same per-axis `stretch`, the same "z takes
|
|
338
|
+
* the smaller of the two" — so the camera frames the MODEL and not its crate.
|
|
339
|
+
*
|
|
340
|
+
* Depth comes back with it, which is the second thing the old box could not
|
|
341
|
+
* say: a flat subtree used to collapse to `MIN_HALF_EXTENT` and size the ortho
|
|
342
|
+
* slab off a guess.
|
|
343
|
+
*
|
|
344
|
+
* ponytail: a primitive reports no bounds and falls back to its authored box.
|
|
345
|
+
* That is right for authored shapes (a Rect IS its box) and slightly generous
|
|
346
|
+
* for a non-uniformly scaled primitive, whose `primitiveFrameScale` is uniform.
|
|
347
|
+
* Upgrade by consulting the primitive's own params if it ever reads loose.
|
|
348
|
+
*/
|
|
349
|
+
function occupiedBox(
|
|
350
|
+
layer: { kind: string; mesh?: { fit?: "fit" | "stretch" } },
|
|
351
|
+
w: number,
|
|
352
|
+
h: number,
|
|
353
|
+
bind: readonly [number, number, number] | undefined,
|
|
354
|
+
): readonly [number, number, number] {
|
|
355
|
+
if (layer.kind !== "mesh" || bind === undefined) return [w, h, 0];
|
|
356
|
+
const [bx, by, bz] = bind;
|
|
357
|
+
// A degenerate axis is a flat or empty model; `applyFit` leaves it at scale 1
|
|
358
|
+
// rather than dividing by zero, and so must this.
|
|
359
|
+
if (!(bx > 0 && by > 0)) return [w, h, 0];
|
|
360
|
+
const uniform = Math.min(w / bx, h / by);
|
|
361
|
+
const sx = layer.mesh?.fit === "stretch" ? w / bx : uniform;
|
|
362
|
+
const sy = layer.mesh?.fit === "stretch" ? h / by : uniform;
|
|
363
|
+
return [bx * sx, by * sy, bz * Math.min(sx, sy)];
|
|
364
|
+
}
|
|
365
|
+
|
|
108
366
|
/** A point in the root's DESIGN frame, out in three world space. Design +y is
|
|
109
367
|
* down and +z is away, so both flip on the way through the one mapping. */
|
|
110
368
|
function worldOf(pose: { position: Vec3; rotation: Mat3 }, local: Vec3): Vec3 {
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// Chrome's JS Self-Profiling API, exposed only to the explicit local
|
|
2
|
+
// `?perf=profile` diagnostics page. The browser starts sampling in the
|
|
3
|
+
// Profiler constructor, so keeping construction behind both URL and caller
|
|
4
|
+
// gates is what makes normal editor, hosted, and export pages pay zero cost.
|
|
5
|
+
|
|
6
|
+
export const JS_SELF_PROFILE_QUERY_VALUE = "profile";
|
|
7
|
+
export const JS_SELF_PROFILE_SAMPLE_INTERVAL_MS = 5;
|
|
8
|
+
export const JS_SELF_PROFILE_MAX_BUFFER_SIZE = 4_096;
|
|
9
|
+
export const JS_SELF_PROFILE_CAPTURE_VERSION = 1;
|
|
10
|
+
|
|
11
|
+
export interface JsProfilerFrame {
|
|
12
|
+
name: string;
|
|
13
|
+
resourceId?: number;
|
|
14
|
+
line?: number;
|
|
15
|
+
column?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface JsProfilerStack {
|
|
19
|
+
frameId: number;
|
|
20
|
+
parentId?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface JsProfilerSample {
|
|
24
|
+
timestamp: number;
|
|
25
|
+
stackId?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface JsProfilerTrace {
|
|
29
|
+
resources: readonly string[];
|
|
30
|
+
frames: readonly JsProfilerFrame[];
|
|
31
|
+
stacks: readonly JsProfilerStack[];
|
|
32
|
+
samples: readonly JsProfilerSample[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface NativeProfiler extends EventTarget {
|
|
36
|
+
readonly sampleInterval: number;
|
|
37
|
+
readonly stopped: boolean;
|
|
38
|
+
stop(): Promise<JsProfilerTrace>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface NativeProfilerConstructor {
|
|
42
|
+
new (options: { sampleInterval: number; maxBufferSize: number }): NativeProfiler;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type JsSelfProfileRefusalCode =
|
|
46
|
+
| "unsupported"
|
|
47
|
+
| "not-allowed"
|
|
48
|
+
| "already-running"
|
|
49
|
+
| "not-running"
|
|
50
|
+
| "buffer-full"
|
|
51
|
+
| "invalid-trace"
|
|
52
|
+
| "empty-trace"
|
|
53
|
+
| "stop-failed";
|
|
54
|
+
|
|
55
|
+
export interface JsSelfProfileRefusal {
|
|
56
|
+
ok: false;
|
|
57
|
+
code: JsSelfProfileRefusalCode;
|
|
58
|
+
message: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface JsSelfProfileStarted {
|
|
62
|
+
ok: true;
|
|
63
|
+
requestedSampleIntervalMs: number;
|
|
64
|
+
actualSampleIntervalMs: number;
|
|
65
|
+
maxBufferSize: number;
|
|
66
|
+
startedAt: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface JsSelfProfileCapture {
|
|
70
|
+
version: typeof JS_SELF_PROFILE_CAPTURE_VERSION;
|
|
71
|
+
requestedSampleIntervalMs: number;
|
|
72
|
+
actualSampleIntervalMs: number;
|
|
73
|
+
maxBufferSize: number;
|
|
74
|
+
startedAt: number;
|
|
75
|
+
stoppedAt: number;
|
|
76
|
+
sampleCount: number;
|
|
77
|
+
/** Serialized in the profiled realm before Playwright transports it. */
|
|
78
|
+
traceJson: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type JsSelfProfileStartResult = JsSelfProfileStarted | JsSelfProfileRefusal;
|
|
82
|
+
export type JsSelfProfileStopResult =
|
|
83
|
+
| { ok: true; capture: JsSelfProfileCapture }
|
|
84
|
+
| JsSelfProfileRefusal;
|
|
85
|
+
|
|
86
|
+
export interface JsSelfProfilerController {
|
|
87
|
+
status(): {
|
|
88
|
+
enabled: true;
|
|
89
|
+
supported: boolean;
|
|
90
|
+
running: boolean;
|
|
91
|
+
message: string;
|
|
92
|
+
};
|
|
93
|
+
start(): JsSelfProfileStartResult;
|
|
94
|
+
stop(): Promise<JsSelfProfileStopResult>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface JsSelfProfilerEnvironment {
|
|
98
|
+
readonly Profiler?: NativeProfilerConstructor;
|
|
99
|
+
now(): number;
|
|
100
|
+
stringify(value: unknown): string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface ActiveProfile {
|
|
104
|
+
profiler: NativeProfiler;
|
|
105
|
+
started: JsSelfProfileStarted;
|
|
106
|
+
bufferFull: boolean;
|
|
107
|
+
stopPromise: Promise<JsSelfProfileStopResult> | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function isLoopbackHostname(hostname: string): boolean {
|
|
111
|
+
const host = hostname.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
112
|
+
return host === "localhost" || host === "::1" || /^127(?:\.\d{1,3}){3}$/.test(host);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Both conditions are mandatory. A copied hosted URL carrying the query does
|
|
116
|
+
* not install a profiler controller, even if its server accidentally emitted
|
|
117
|
+
* a permissive policy header. */
|
|
118
|
+
export function isLocalJsSelfProfileOptIn(url: string): boolean {
|
|
119
|
+
try {
|
|
120
|
+
const parsed = new URL(url);
|
|
121
|
+
const values = parsed.searchParams.getAll("perf");
|
|
122
|
+
return (
|
|
123
|
+
isLoopbackHostname(parsed.hostname) &&
|
|
124
|
+
values.length === 1 &&
|
|
125
|
+
values[0] === JS_SELF_PROFILE_QUERY_VALUE
|
|
126
|
+
);
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function browserJsSelfProfilerEnvironment(): JsSelfProfilerEnvironment {
|
|
133
|
+
const Profiler = (globalThis as typeof globalThis & { Profiler?: NativeProfilerConstructor })
|
|
134
|
+
.Profiler;
|
|
135
|
+
return {
|
|
136
|
+
...(Profiler === undefined ? {} : { Profiler }),
|
|
137
|
+
now: () => performance.now(),
|
|
138
|
+
stringify: (value) => JSON.stringify(value),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Creating the controller is inert. `start()` is the sole constructor call,
|
|
144
|
+
* and the fixed interval/cap cannot be widened by a console caller or harness
|
|
145
|
+
* argument. One controller can run several sequential captures so fresh,
|
|
146
|
+
* repeat, and post-idle comparisons can reuse one warm page.
|
|
147
|
+
*/
|
|
148
|
+
export function createJsSelfProfilerController(
|
|
149
|
+
env: JsSelfProfilerEnvironment = browserJsSelfProfilerEnvironment(),
|
|
150
|
+
): JsSelfProfilerController {
|
|
151
|
+
let active: ActiveProfile | null = null;
|
|
152
|
+
|
|
153
|
+
const refuse = (code: JsSelfProfileRefusalCode, message: string): JsSelfProfileRefusal => ({
|
|
154
|
+
ok: false,
|
|
155
|
+
code,
|
|
156
|
+
message,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const settle = (session: ActiveProfile): Promise<JsSelfProfileStopResult> => {
|
|
160
|
+
if (session.stopPromise !== null) return session.stopPromise;
|
|
161
|
+
session.stopPromise = (async () => {
|
|
162
|
+
// This is the capture boundary. Awaiting Chrome's stop result and
|
|
163
|
+
// validating/stringifying its trace are post-capture bookkeeping and
|
|
164
|
+
// must not extend the last frame cycle in the artifact.
|
|
165
|
+
const stoppedAt = env.now();
|
|
166
|
+
let trace: JsProfilerTrace;
|
|
167
|
+
try {
|
|
168
|
+
trace = await session.profiler.stop();
|
|
169
|
+
} catch (error) {
|
|
170
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
171
|
+
return refuse(
|
|
172
|
+
session.bufferFull ? "buffer-full" : "stop-failed",
|
|
173
|
+
session.bufferFull
|
|
174
|
+
? `JS self-profile sample buffer filled before capture completed (${detail})`
|
|
175
|
+
: `JS self-profile could not stop: ${detail}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (!validTraceShape(trace)) {
|
|
179
|
+
return refuse("invalid-trace", "Chrome returned a malformed JS self-profile trace");
|
|
180
|
+
}
|
|
181
|
+
if (trace.samples.length > JS_SELF_PROFILE_MAX_BUFFER_SIZE) {
|
|
182
|
+
return refuse(
|
|
183
|
+
"invalid-trace",
|
|
184
|
+
`Chrome returned ${trace.samples.length} samples, above the ${JS_SELF_PROFILE_MAX_BUFFER_SIZE}-sample cap`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (
|
|
188
|
+
session.bufferFull ||
|
|
189
|
+
trace.samples.length === JS_SELF_PROFILE_MAX_BUFFER_SIZE
|
|
190
|
+
) {
|
|
191
|
+
return refuse(
|
|
192
|
+
"buffer-full",
|
|
193
|
+
`JS self-profile reached its ${JS_SELF_PROFILE_MAX_BUFFER_SIZE}-sample cap before the requested frame window completed`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (trace.samples.length === 0) {
|
|
197
|
+
return refuse(
|
|
198
|
+
"empty-trace",
|
|
199
|
+
"Chrome returned no JS samples; refusing to report an unsupported capture as complete",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
let traceJson: string;
|
|
203
|
+
try {
|
|
204
|
+
traceJson = env.stringify(trace);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
207
|
+
return refuse("invalid-trace", `JS self-profile trace is not serializable: ${detail}`);
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
ok: true,
|
|
211
|
+
capture: {
|
|
212
|
+
version: JS_SELF_PROFILE_CAPTURE_VERSION,
|
|
213
|
+
requestedSampleIntervalMs: session.started.requestedSampleIntervalMs,
|
|
214
|
+
actualSampleIntervalMs: session.started.actualSampleIntervalMs,
|
|
215
|
+
maxBufferSize: session.started.maxBufferSize,
|
|
216
|
+
startedAt: session.started.startedAt,
|
|
217
|
+
stoppedAt,
|
|
218
|
+
sampleCount: trace.samples.length,
|
|
219
|
+
traceJson,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
})();
|
|
223
|
+
return session.stopPromise;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
status() {
|
|
228
|
+
const supported = env.Profiler !== undefined;
|
|
229
|
+
return {
|
|
230
|
+
enabled: true,
|
|
231
|
+
supported,
|
|
232
|
+
running: active !== null,
|
|
233
|
+
message: supported
|
|
234
|
+
? active === null
|
|
235
|
+
? "JS self-profiling is ready"
|
|
236
|
+
: "JS self-profiling is running"
|
|
237
|
+
: "Chrome JS Self-Profiling is unavailable; use a Chromium build that exposes window.Profiler",
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
start() {
|
|
241
|
+
if (active !== null) {
|
|
242
|
+
return refuse("already-running", "a JS self-profile capture is already running");
|
|
243
|
+
}
|
|
244
|
+
const Profiler = env.Profiler;
|
|
245
|
+
if (Profiler === undefined) {
|
|
246
|
+
return refuse(
|
|
247
|
+
"unsupported",
|
|
248
|
+
"Chrome JS Self-Profiling is unavailable; use a Chromium build that exposes window.Profiler",
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const startedAt = env.now();
|
|
252
|
+
let profiler: NativeProfiler;
|
|
253
|
+
try {
|
|
254
|
+
profiler = new Profiler({
|
|
255
|
+
sampleInterval: JS_SELF_PROFILE_SAMPLE_INTERVAL_MS,
|
|
256
|
+
maxBufferSize: JS_SELF_PROFILE_MAX_BUFFER_SIZE,
|
|
257
|
+
});
|
|
258
|
+
} catch (error) {
|
|
259
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
260
|
+
const denied =
|
|
261
|
+
(typeof DOMException !== "undefined" &&
|
|
262
|
+
error instanceof DOMException &&
|
|
263
|
+
error.name === "NotAllowedError") ||
|
|
264
|
+
/document policy|not allowed/i.test(detail);
|
|
265
|
+
return refuse(
|
|
266
|
+
denied ? "not-allowed" : "unsupported",
|
|
267
|
+
denied
|
|
268
|
+
? `JS self-profiling was denied; reload the local stage with ?perf=profile and Document-Policy: js-profiling (${detail})`
|
|
269
|
+
: `Chrome JS Self-Profiling could not start: ${detail}`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
const actualSampleIntervalMs = profiler.sampleInterval;
|
|
273
|
+
if (!Number.isFinite(actualSampleIntervalMs) || actualSampleIntervalMs <= 0) {
|
|
274
|
+
void profiler.stop().catch(() => {});
|
|
275
|
+
return refuse(
|
|
276
|
+
"unsupported",
|
|
277
|
+
`Chrome reported an invalid JS self-profile interval (${String(actualSampleIntervalMs)})`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const started: JsSelfProfileStarted = {
|
|
281
|
+
ok: true,
|
|
282
|
+
requestedSampleIntervalMs: JS_SELF_PROFILE_SAMPLE_INTERVAL_MS,
|
|
283
|
+
actualSampleIntervalMs,
|
|
284
|
+
maxBufferSize: JS_SELF_PROFILE_MAX_BUFFER_SIZE,
|
|
285
|
+
startedAt,
|
|
286
|
+
};
|
|
287
|
+
const session: ActiveProfile = {
|
|
288
|
+
profiler,
|
|
289
|
+
started,
|
|
290
|
+
bufferFull: false,
|
|
291
|
+
stopPromise: null,
|
|
292
|
+
};
|
|
293
|
+
profiler.addEventListener(
|
|
294
|
+
"samplebufferfull",
|
|
295
|
+
() => {
|
|
296
|
+
session.bufferFull = true;
|
|
297
|
+
void settle(session);
|
|
298
|
+
},
|
|
299
|
+
{ once: true },
|
|
300
|
+
);
|
|
301
|
+
active = session;
|
|
302
|
+
return started;
|
|
303
|
+
},
|
|
304
|
+
async stop() {
|
|
305
|
+
const session = active;
|
|
306
|
+
if (session === null) {
|
|
307
|
+
return refuse("not-running", "no JS self-profile capture is running");
|
|
308
|
+
}
|
|
309
|
+
const result = await settle(session);
|
|
310
|
+
if (active === session) active = null;
|
|
311
|
+
return result;
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function validTraceShape(trace: unknown): trace is JsProfilerTrace {
|
|
317
|
+
if (trace === null || typeof trace !== "object") return false;
|
|
318
|
+
const row = trace as Partial<JsProfilerTrace>;
|
|
319
|
+
return (
|
|
320
|
+
Array.isArray(row.resources) &&
|
|
321
|
+
Array.isArray(row.frames) &&
|
|
322
|
+
Array.isArray(row.stacks) &&
|
|
323
|
+
Array.isArray(row.samples)
|
|
324
|
+
);
|
|
325
|
+
}
|