@dailies-studio/shot-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// Only for DEVICE layers, and absent otherwise — the same rule MeshSection and
|
|
2
|
+
// ClonerSection follow, for the same reason.
|
|
3
|
+
//
|
|
4
|
+
// ── WHY THIS EXISTS AT ALL ──────────────────────────────────────────────
|
|
5
|
+
//
|
|
6
|
+
// A device could be ADDED (the Add menu picks a preset) and then never touched:
|
|
7
|
+
// its preset, its body colour, its thickness and which child sits on the glass
|
|
8
|
+
// were all reachable from the agent lane and from nowhere in the chrome. That
|
|
9
|
+
// is the exact defect MeshSection's material rows were written to close — "the
|
|
10
|
+
// author could see it and could not touch it" — and schema v12 made it louder,
|
|
11
|
+
// because `device.color` is a KEYFRAMABLE path with no row to hang its diamond
|
|
12
|
+
// on. A trackable property the inspector cannot show is a property the author
|
|
13
|
+
// can only animate by asking an agent to do it.
|
|
14
|
+
//
|
|
15
|
+
// ── THE READOUTS SHOW THE DERIVED VALUE, NOT THE STORED ONE ─────────────
|
|
16
|
+
//
|
|
17
|
+
// `color` and `depthPx` are both OVERRIDES: absent means "the preset's own",
|
|
18
|
+
// which is a real number the stage is drawing and a blank field would hide. So
|
|
19
|
+
// both rows read `deviceSlab`, the same pure function the runtime uses, and an
|
|
20
|
+
// untouched device shows the colour and thickness it actually has. Nothing is
|
|
21
|
+
// stored until a row is edited — baking the preset's value in on mount would
|
|
22
|
+
// freeze it against a later preset change. Mixed is asked of the SAME derived
|
|
23
|
+
// value, per device: two phones of different widths store no depth and still
|
|
24
|
+
// disagree on the one they draw.
|
|
25
|
+
//
|
|
26
|
+
// ponytail: no notch/camera/home-button rows. Those are DECORATIONS and the
|
|
27
|
+
// device header says so: they go in as child layers, and the moment one lives
|
|
28
|
+
// in the preset table the table stops being data.
|
|
29
|
+
import {
|
|
30
|
+
ColorField,
|
|
31
|
+
Combobox,
|
|
32
|
+
InspectorRow,
|
|
33
|
+
InspectorSection,
|
|
34
|
+
NumberField,
|
|
35
|
+
} from "@dailies-studio/ui";
|
|
36
|
+
import { DEVICE_PRESETS, DEVICE_PRESET_IDS, deviceSlab, homeOf } from "@dailies-studio/shot/doc";
|
|
37
|
+
import type { DeviceLayer3D, Layer3D } from "@dailies-studio/shot/doc";
|
|
38
|
+
import { KeyframeDot } from "../KeyframeDot.js";
|
|
39
|
+
import { useShotEditor } from "../../../state/context.js";
|
|
40
|
+
import {
|
|
41
|
+
useInspectorFrame,
|
|
42
|
+
useSelectedLayers,
|
|
43
|
+
useSelectionScrub,
|
|
44
|
+
useSelectionWrite,
|
|
45
|
+
} from "../useDocWrite.js";
|
|
46
|
+
|
|
47
|
+
type DeviceBlock = DeviceLayer3D["device"];
|
|
48
|
+
|
|
49
|
+
/** The Screen combobox's "no child on the glass" entry. The empty string is the
|
|
50
|
+
* one value a layer id can never be, so it needs no second channel. */
|
|
51
|
+
const NO_SCREEN = "";
|
|
52
|
+
|
|
53
|
+
export function DeviceSection() {
|
|
54
|
+
const write = useSelectionWrite();
|
|
55
|
+
const layers = useSelectedLayers();
|
|
56
|
+
const doc = useShotEditor((s) => s.doc);
|
|
57
|
+
const { sceneId } = useInspectorFrame();
|
|
58
|
+
// THE DRAG HALF of the depth, over the same builder its typed value takes.
|
|
59
|
+
const depthScrub = useSelectionScrub((layer, raw) => withDepth(layer, Number(raw)));
|
|
60
|
+
// THE DEVICE LAYERS THEMSELVES, for MeshSection's reason: `layer.device` is an
|
|
61
|
+
// object, so `useSelectionValue` would report every multi-selection as Mixed
|
|
62
|
+
// by identity over values the author can see agree. Mixed is asked per FIELD.
|
|
63
|
+
const devices = layers.filter((l): l is DeviceLayer3D => l.kind === "device");
|
|
64
|
+
const first = devices[0];
|
|
65
|
+
if (first === undefined) return null;
|
|
66
|
+
|
|
67
|
+
const d = first.device;
|
|
68
|
+
const preset = DEVICE_PRESETS[d.preset];
|
|
69
|
+
const slab = deviceSlab(d, first.transform.size);
|
|
70
|
+
/** One device's derived value for a row, read off `deviceSlab` as the row
|
|
71
|
+
* shows it, so Mixed is asked of what the fields display. */
|
|
72
|
+
const shownOn = <T,>(read: (device: DeviceLayer3D) => T): boolean =>
|
|
73
|
+
devices.some((l) => !Object.is(read(l), read(first)));
|
|
74
|
+
const depthOf = (l: DeviceLayer3D): number => deviceSlab(l.device, l.transform.size)?.depthPx ?? l.device.depthPx ?? 0;
|
|
75
|
+
const colorOf = (l: DeviceLayer3D): string =>
|
|
76
|
+
(deviceSlab(l.device, l.transform.size)?.color ?? l.device.color ?? DEVICE_PRESETS[l.device.preset]?.color ?? BODY_FALLBACK).toLowerCase();
|
|
77
|
+
|
|
78
|
+
const patch = (next: (block: DeviceBlock) => DeviceBlock): void =>
|
|
79
|
+
write((layer) => (layer.kind === "device" ? ({ ...layer, device: next(layer.device) } as Layer3D) : layer));
|
|
80
|
+
|
|
81
|
+
const set = <K extends keyof DeviceBlock>(key: K, value: DeviceBlock[K]): void =>
|
|
82
|
+
patch((block) => ({ ...block, [key]: value }));
|
|
83
|
+
|
|
84
|
+
const mixedOn = <T,>(read: (block: DeviceBlock) => T): boolean =>
|
|
85
|
+
devices.some((l) => !Object.is(read(l.device), read(d)));
|
|
86
|
+
|
|
87
|
+
// The child layers this ONE device holds, by label. Resolved through the
|
|
88
|
+
// container so it is right inside a definition too.
|
|
89
|
+
const home = doc === null || sceneId === null ? null : homeOf(doc, sceneId);
|
|
90
|
+
const children = d.children
|
|
91
|
+
.map((id) => ({ value: id, label: home?.layersById[id]?.label ?? id }))
|
|
92
|
+
.filter((row) => home === null || home.layersById[row.value] !== undefined);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<InspectorSection className="insp-object" title="Device" persistKey="shot:device">
|
|
96
|
+
<InspectorRow label="Preset">
|
|
97
|
+
<Combobox
|
|
98
|
+
options={DEVICE_PRESET_IDS.map((id) => ({
|
|
99
|
+
value: id,
|
|
100
|
+
label: id.charAt(0).toUpperCase() + id.slice(1),
|
|
101
|
+
}))}
|
|
102
|
+
// A selection of two presets shows neither: an empty value is how
|
|
103
|
+
// the combobox says Mixed, over its placeholder.
|
|
104
|
+
value={mixedOn((block) => block.preset) ? "" : d.preset}
|
|
105
|
+
ariaLabel="Device preset"
|
|
106
|
+
placeholder="Mixed"
|
|
107
|
+
onChange={(next) => set("preset", next)}
|
|
108
|
+
/>
|
|
109
|
+
</InspectorRow>
|
|
110
|
+
|
|
111
|
+
<BodyColorRow value={slab?.color ?? d.color ?? preset?.color ?? BODY_FALLBACK} mixed={shownOn(colorOf)} />
|
|
112
|
+
|
|
113
|
+
<InspectorRow>
|
|
114
|
+
<NumberField
|
|
115
|
+
// The EFFECTIVE thickness: absent means the preset's ratio times the
|
|
116
|
+
// width, and showing that number is what makes the default legible.
|
|
117
|
+
value={slab?.depthPx ?? d.depthPx ?? 0}
|
|
118
|
+
mixed={shownOn(depthOf)}
|
|
119
|
+
label="Depth"
|
|
120
|
+
ariaLabel="Device depth"
|
|
121
|
+
unit="px"
|
|
122
|
+
min={1}
|
|
123
|
+
step={1}
|
|
124
|
+
onCommit={(next) => write((layer) => withDepth(layer, next))}
|
|
125
|
+
onScrubStart={depthScrub.start}
|
|
126
|
+
onScrub={depthScrub.move}
|
|
127
|
+
onScrubEnd={depthScrub.end}
|
|
128
|
+
/>
|
|
129
|
+
</InspectorRow>
|
|
130
|
+
|
|
131
|
+
{/* ONE DEVICE AT A TIME, and this is a correctness gate rather than a
|
|
132
|
+
courtesy: `screen` must name one of THIS device's own children, so a
|
|
133
|
+
fan-out would write an id the other device has never held and
|
|
134
|
+
`validateShotDoc` refuses the save. */}
|
|
135
|
+
{devices.length > 1 ? (
|
|
136
|
+
<div className="insp-notice">Select one device on its own to choose its screen.</div>
|
|
137
|
+
) : (
|
|
138
|
+
<InspectorRow label="Screen" labelTitle="Which child layer renders on the glass">
|
|
139
|
+
<Combobox
|
|
140
|
+
options={[{ value: NO_SCREEN, label: "None" }, ...children]}
|
|
141
|
+
value={d.screen ?? NO_SCREEN}
|
|
142
|
+
ariaLabel="Device screen child"
|
|
143
|
+
placeholder="Search children"
|
|
144
|
+
onChange={(next) =>
|
|
145
|
+
patch((block) => {
|
|
146
|
+
if (next !== NO_SCREEN) return { ...block, screen: next };
|
|
147
|
+
// Absent, not `null` and not `""`: codegen prints the block by
|
|
148
|
+
// key order, and a present-but-undefined key is a key. The
|
|
149
|
+
// schema accepts null, so this is a house-style choice and not
|
|
150
|
+
// a gate — MeshSection's `clip` row clears the same way.
|
|
151
|
+
const { screen: _drop, ...rest } = block;
|
|
152
|
+
return rest;
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
/>
|
|
156
|
+
</InspectorRow>
|
|
157
|
+
)}
|
|
158
|
+
</InspectorSection>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** A body colour no preset names, the slab's own last resort. */
|
|
163
|
+
const BODY_FALLBACK = "#1d1d1f";
|
|
164
|
+
|
|
165
|
+
/** One device's depth override, the one builder its typed value and its drag
|
|
166
|
+
* both run. */
|
|
167
|
+
function withDepth(layer: Layer3D, next: number): Layer3D {
|
|
168
|
+
return layer.kind === "device" ? ({ ...layer, device: { ...layer.device, depthPx: Math.round(next) } } as Layer3D) : layer;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The body colour. Its own component for `ShapeColorRow`'s reason: `ColorPicker`
|
|
173
|
+
* routes its whole drag through `scrub.move`, so a row without the document
|
|
174
|
+
* bracket is a thumb that moves over a document that never changes. OPAQUE,
|
|
175
|
+
* for `writeBodyColor`'s reason below: the picker offers no alpha the row
|
|
176
|
+
* would refuse.
|
|
177
|
+
*/
|
|
178
|
+
function BodyColorRow({ value, mixed }: { value: string; mixed: boolean }) {
|
|
179
|
+
const write = useSelectionWrite();
|
|
180
|
+
const scrub = useSelectionScrub((layer, raw) => writeBodyColor(layer, raw));
|
|
181
|
+
return (
|
|
182
|
+
<InspectorRow label="Colour" trailing={<KeyframeDot path="device.color" />}>
|
|
183
|
+
<ColorField
|
|
184
|
+
value={value}
|
|
185
|
+
mixed={mixed}
|
|
186
|
+
opaque
|
|
187
|
+
ariaLabel="Body colour"
|
|
188
|
+
scrub={scrub}
|
|
189
|
+
commit={(hex) => write((layer) => writeBodyColor(layer, hex))}
|
|
190
|
+
/>
|
|
191
|
+
</InspectorRow>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* PURE, over the layer it is handed — `writeShapeColor`'s rule and its exact
|
|
197
|
+
* regex.
|
|
198
|
+
*
|
|
199
|
+
* SIX DIGITS, though `validateShotDoc` would take any CSS colour here. The
|
|
200
|
+
* narrower set is the KEYFRAME's: `device.color` is declared `opaqueHex` in the
|
|
201
|
+
* path table, so an 8-digit value typed into an ANIMATED device would be refused
|
|
202
|
+
* by the track gate at the moment it stopped being a constant. A control that
|
|
203
|
+
* accepts a value the same control refuses once a diamond is pressed is worse
|
|
204
|
+
* than one that never offered it.
|
|
205
|
+
*/
|
|
206
|
+
function writeBodyColor(layer: Layer3D, raw: unknown): Layer3D {
|
|
207
|
+
if (layer.kind !== "device") return layer;
|
|
208
|
+
if (typeof raw !== "string" || !/^#[0-9a-fA-F]{6}$/.test(raw)) return layer;
|
|
209
|
+
return { ...layer, device: { ...layer.device, color: raw.toLowerCase() } };
|
|
210
|
+
}
|
|
@@ -1,86 +1,118 @@
|
|
|
1
|
-
// The fill
|
|
2
|
-
// editor, which @dailies-studio/ui has (`GradientValueEditor`) and which wants
|
|
3
|
-
// the gradient adapter the 2D lane wrote — that is a section of its own rather
|
|
4
|
-
// than a control squeezed in here.
|
|
1
|
+
// The fill, as the whole `Paint` union: solid, linear gradient or radial.
|
|
5
2
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
3
|
+
// It was solid-only until the shots.so pass, and not merely incomplete —
|
|
4
|
+
// DESTRUCTIVE. `withSolidFill` was the only writer, so a gradient fill authored
|
|
5
|
+
// by MCP survived being displayed (the swatch showed its first stop) and died on
|
|
6
|
+
// the first commit, and on the `+`/`−` action too. `paintStyle` (raster.ts) has
|
|
7
|
+
// drawn both gradients on a layer since unify Phase 1; only the authoring
|
|
8
|
+
// surface was missing. `PaintRows` is that surface, shared with the scene
|
|
9
|
+
// background's row, which had the identical hole.
|
|
10
|
+
//
|
|
11
|
+
// Nothing here goes through `setEffectiveField`, which is transform only. An
|
|
12
|
+
// edit is a `PaintEdit` run on EACH layer's own fill as painted (its
|
|
13
|
+
// `paint.fill.color` track sampled), so a multi-selection changes one field
|
|
14
|
+
// per layer and never copies the lead's whole paint onto the rest.
|
|
15
|
+
import { IconMinus, IconPlus, InspectorSection, useLocked } from "@dailies-studio/ui";
|
|
16
|
+
import { KeyframeDot } from "../KeyframeDot.js";
|
|
17
|
+
import { PaintRows, type PaintEdit } from "./PaintRows.js";
|
|
10
18
|
import type { Layer3D, Paint } from "@dailies-studio/shot/doc";
|
|
11
|
-
import {
|
|
19
|
+
import { useSelectedLayers, useSelectionScrub, useSelectionValue, useSelectionWrite } from "../useDocWrite.js";
|
|
20
|
+
// The white the DRAW TOOLS open a fill on. Read rather than restated: "+ Fill"
|
|
21
|
+
// here and a dragged rectangle are two doors into the same first paint, and two
|
|
22
|
+
// whites would be a difference nobody chose.
|
|
23
|
+
import { DEFAULT_PAINT_COLOR } from "../../../state/insert.js";
|
|
12
24
|
|
|
13
|
-
const
|
|
25
|
+
const DEFAULT_PAINT: Paint = { type: "solid", color: DEFAULT_PAINT_COLOR };
|
|
14
26
|
|
|
15
27
|
export function FillSection() {
|
|
16
28
|
const write = useSelectionWrite();
|
|
17
29
|
const layers = useSelectedLayers();
|
|
18
|
-
|
|
30
|
+
// The add/remove action is a plain button, so it reads a viewer's lock here.
|
|
31
|
+
const locked = useLocked();
|
|
19
32
|
const present = useSelectionValue((layer) => layer.paint.fill !== undefined);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// ONLY when the whole selection is media. A mixed one keeps the section,
|
|
29
|
-
// because the rects in it still fill and `useSelectionWrite` already skips
|
|
30
|
-
// the layers a write cannot reach.
|
|
33
|
+
const first = layers.find((layer) => layer.paint.fill !== undefined);
|
|
34
|
+
const fill = first?.paint.fill ?? DEFAULT_PAINT;
|
|
35
|
+
// THE DRAG HALF. The picker's SV square, hue bar and alpha bar never call
|
|
36
|
+
// `commit` while the pointer is down: every one of them reports through
|
|
37
|
+
// `scrub.move` (ui/ColorPicker.tsx). With the commit half alone the thumb
|
|
38
|
+
// moved and the document did not, so the only working colour controls were
|
|
39
|
+
// the hex and R/G/B fields.
|
|
31
40
|
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
// It carries a whole PAINT now, so a stop drag and an angle drag ride the same
|
|
42
|
+
// capture the colour bar always did.
|
|
43
|
+
const painted = new Map(layers.map((layer) => [layer.id, layer.paint.fill]));
|
|
44
|
+
const edited = (layer: Layer3D, edit: PaintEdit): Layer3D => {
|
|
45
|
+
const own = painted.get(layer.id) ?? layer.paint.fill;
|
|
46
|
+
return own === undefined ? layer : withFill(layer, edit(own));
|
|
47
|
+
};
|
|
48
|
+
const scrub = useSelectionScrub((layer, raw) => edited(layer, raw as PaintEdit));
|
|
49
|
+
// Over text a fill is the plate behind the words; the glyph colour is
|
|
50
|
+
// `TextFillSection`'s "Fill" (F20).
|
|
51
|
+
const title = layers.every((layer) => layer.kind === "text") ? "Background" : "Fill";
|
|
36
52
|
|
|
53
|
+
// NO KIND GATE. Which selections get a Fill at all — the nine kinds Canvas2D
|
|
54
|
+
// draws, minus media, whose content pixels come from its asset — is declared
|
|
55
|
+
// once in `layout.ts` and answered before this section is mounted.
|
|
37
56
|
return (
|
|
38
57
|
<InspectorSection
|
|
39
|
-
|
|
58
|
+
className="insp-object"
|
|
59
|
+
title={title}
|
|
40
60
|
persistKey="shot:fill"
|
|
41
61
|
action={
|
|
42
62
|
<button
|
|
43
63
|
type="button"
|
|
44
|
-
className="section-action"
|
|
45
|
-
title={present.value === true ? "Remove
|
|
64
|
+
className="btn is-icon insp-section-action"
|
|
65
|
+
title={`${present.value === true ? "Remove" : "Add"} ${title.toLowerCase()}`}
|
|
66
|
+
aria-label={`${present.value === true ? "Remove" : "Add"} ${title.toLowerCase()}`}
|
|
67
|
+
disabled={locked}
|
|
46
68
|
onClick={() =>
|
|
47
69
|
write((layer) =>
|
|
48
70
|
present.value === true
|
|
49
71
|
? { ...layer, paint: withoutFill(layer.paint) }
|
|
50
|
-
:
|
|
72
|
+
: layer.paint.fill === undefined ? withFill(layer, DEFAULT_PAINT) : layer,
|
|
51
73
|
)
|
|
52
74
|
}
|
|
53
75
|
>
|
|
54
|
-
{present.value === true ?
|
|
76
|
+
{present.value === true ? <IconMinus /> : <IconPlus />}
|
|
55
77
|
</button>
|
|
56
78
|
}
|
|
57
79
|
>
|
|
58
80
|
{present.value === true ? (
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
<PaintRows
|
|
82
|
+
// NO `key`. Keying this on the paint would remount the whole control
|
|
83
|
+
// on every scrub tick, which unmounts the ColorPicker mid-drag and
|
|
84
|
+
// drops its pointer capture: the thumb moves and the document does
|
|
85
|
+
// not, which is the exact defect this section was written to fix.
|
|
86
|
+
// Nothing here needs a remount anyway, because the stop rows are
|
|
87
|
+
// derived from `value.stops` on every render.
|
|
88
|
+
value={fill}
|
|
89
|
+
all={layers.flatMap((layer) => (layer.paint.fill === undefined ? [] : [layer.paint.fill]))}
|
|
90
|
+
ariaPrefix="Fill"
|
|
91
|
+
// The heading says Fill; the design draws the type row as a bare
|
|
92
|
+
// chip track. A layer fill has a picture behind it, so its alpha
|
|
93
|
+
// gets the Opacity row a scene's ground does not.
|
|
94
|
+
label={null}
|
|
95
|
+
showOpacity
|
|
96
|
+
// `paint.fill.color` is a colour track and the table refuses it over a
|
|
97
|
+
// gradient, which has no single colour to interpolate. The Opacity
|
|
98
|
+
// box is the same track's alpha byte: its diamond, and no second pin.
|
|
99
|
+
solidTrailing={<KeyframeDot path="paint.fill.color" />}
|
|
100
|
+
opacityTrailing={<KeyframeDot path="paint.fill.color" pin={false} />}
|
|
101
|
+
onScrubStart={scrub.start}
|
|
102
|
+
onScrub={(edit) => scrub.move(edit)}
|
|
103
|
+
onScrubEnd={scrub.end}
|
|
104
|
+
onCommit={(edit) => write((layer) => edited(layer, edit))}
|
|
105
|
+
/>
|
|
69
106
|
) : null}
|
|
70
107
|
</InspectorSection>
|
|
71
108
|
);
|
|
72
109
|
}
|
|
73
110
|
|
|
74
|
-
/**
|
|
75
|
-
*
|
|
76
|
-
* and
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
return paint.type === "solid" ? paint.color : (paint.stops[0]?.color ?? DEFAULT_FILL);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function isMedia(layer: Layer3D): boolean {
|
|
83
|
-
return layer.kind === "image" || layer.kind === "video";
|
|
111
|
+
/** The ONE fill edit, so a drag's move and its release land the same document.
|
|
112
|
+
* Two spellings of this would mean the commit could disagree with the last
|
|
113
|
+
* captured move, and the release would appear to undo the drag. */
|
|
114
|
+
function withFill(layer: Layer3D, paint: Paint): Layer3D {
|
|
115
|
+
return { ...layer, paint: { ...layer.paint, fill: paint } };
|
|
84
116
|
}
|
|
85
117
|
|
|
86
118
|
function withoutFill(paint: { fill?: Paint }): typeof paint {
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// THE FONT ROW'S TRIGGER (plans/FONT-PICKER-PLAN.md "The picker"): one field
|
|
2
|
+
// box showing the family drawn in its own face ("Mixed" when the selected text
|
|
3
|
+
// names different families), the one caret, and the font write in flight.
|
|
4
|
+
//
|
|
5
|
+
// A press, Enter, Space or ArrowDown opens the picker (panels/fonts, a lazy
|
|
6
|
+
// chunk mounted only while open; its chunk and the Google catalog preload as
|
|
7
|
+
// the pointer or focus reaches the field). A request from elsewhere (the
|
|
8
|
+
// Typography note's "Upload font file", `requestFontPicker`) opens it on the
|
|
9
|
+
// asked view. A locked panel reads the family and opens nothing: a pick writes
|
|
10
|
+
// the Project.
|
|
11
|
+
//
|
|
12
|
+
// While a font write runs (`state.fontApply`, which outlives the picker) the
|
|
13
|
+
// box shows its progress in the accessory lane, a level in `--meter-lit` with
|
|
14
|
+
// its percent, or a turning arc while the size is unknown (a dashed ring
|
|
15
|
+
// under reduced motion, never a still arc that reads as a level); a failure
|
|
16
|
+
// shows Retry there instead. Neither changes the row's size.
|
|
17
|
+
//
|
|
18
|
+
// The face the name draws in is the In this Project row's (faceCache.ts):
|
|
19
|
+
// the Project's own file, or the computer's for a machine font, so
|
|
20
|
+
// selecting text fetches nothing from jsDelivr.
|
|
21
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
22
|
+
import type { KeyboardEvent } from "react";
|
|
23
|
+
import { drawsMachineFont } from "@dailies-studio/shot/doc";
|
|
24
|
+
import { IconChevronDown, useLocked } from "@dailies-studio/ui";
|
|
25
|
+
import { FontPicker } from "../../../app/lazySurfaces.js";
|
|
26
|
+
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
27
|
+
import type { FontApplyProgress } from "../../../state/fonts.js";
|
|
28
|
+
import { faceStack, familyFace, usePreviewFace } from "../../fonts/faceCache.js";
|
|
29
|
+
import { useFontPickerRequest } from "../../fonts/pickerRequest.js";
|
|
30
|
+
import type { FontPickerClose, FontPickerView } from "../../fonts/pickerRequest.js";
|
|
31
|
+
import { useSelectedLayers } from "../useDocWrite.js";
|
|
32
|
+
|
|
33
|
+
export { MANAGE_FONTS_HREF } from "../../fonts/pickerRequest.js";
|
|
34
|
+
|
|
35
|
+
export interface FontFieldProps {
|
|
36
|
+
/** The display family: `primaryFontFamily` of the layers' `fontFamily`. */
|
|
37
|
+
value: string;
|
|
38
|
+
/** The selected text layers name different families. */
|
|
39
|
+
mixed: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function FontField({ value, mixed }: FontFieldProps) {
|
|
43
|
+
const store = useStore();
|
|
44
|
+
const locked = useLocked();
|
|
45
|
+
const anchor = useRef<HTMLDivElement>(null);
|
|
46
|
+
const button = useRef<HTMLButtonElement>(null);
|
|
47
|
+
const valueId = useId();
|
|
48
|
+
// The open view, or null while closed.
|
|
49
|
+
const [view, setView] = useState<FontPickerView | null>(null);
|
|
50
|
+
// Set by a close that hands focus back, spent once the picker is gone.
|
|
51
|
+
const refocus = useRef(false);
|
|
52
|
+
const doc = useShotEditor((s) => s.doc);
|
|
53
|
+
const catalog = useShotEditor((s) => s.fontCatalog);
|
|
54
|
+
const workspace = useShotEditor((s) => s.workspaceFonts);
|
|
55
|
+
// A write's progress shows on the field of the text it lands on, not on
|
|
56
|
+
// whatever text is selected while it runs.
|
|
57
|
+
const progress = useShotEditor((s) =>
|
|
58
|
+
s.fontApply !== null && s.fontApply.layerIds.some((id) => s.selection.includes(id)) ? s.fontApply : null,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const face = useMemo(
|
|
62
|
+
() => (mixed || value === "" || doc === null ? null : familyFace(doc, value, catalog, workspace?.fonts ?? null)),
|
|
63
|
+
[mixed, value, doc, catalog, workspace],
|
|
64
|
+
);
|
|
65
|
+
const status = usePreviewFace(face);
|
|
66
|
+
// A fileless layer beside the Project's own copy of its family: the picker
|
|
67
|
+
// lists the copy as current, and picking it still has to write.
|
|
68
|
+
const layers = useSelectedLayers();
|
|
69
|
+
const currentMachine = doc !== null && layers.some((layer) => drawsMachineFont(layer, doc.assets));
|
|
70
|
+
|
|
71
|
+
const warm = (): void => {
|
|
72
|
+
void FontPicker.preload();
|
|
73
|
+
void store.loadFontCatalog();
|
|
74
|
+
};
|
|
75
|
+
const openOn = (next: FontPickerView): void => {
|
|
76
|
+
if (locked) return;
|
|
77
|
+
warm();
|
|
78
|
+
setView(next);
|
|
79
|
+
};
|
|
80
|
+
const close = (how: FontPickerClose): void => {
|
|
81
|
+
// Tab moves on FROM the field: focus has to be here before the browser
|
|
82
|
+
// takes the key.
|
|
83
|
+
if (how === "tab") button.current?.focus();
|
|
84
|
+
refocus.current = how === "refocus";
|
|
85
|
+
setView(null);
|
|
86
|
+
};
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (view !== null || !refocus.current) return;
|
|
89
|
+
refocus.current = false;
|
|
90
|
+
button.current?.focus();
|
|
91
|
+
}, [view]);
|
|
92
|
+
|
|
93
|
+
// A request made before this field mounted is not this field's to answer.
|
|
94
|
+
const request = useFontPickerRequest();
|
|
95
|
+
const answered = useRef(request?.seq ?? 0);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (request === null || request.seq <= answered.current) return;
|
|
98
|
+
answered.current = request.seq;
|
|
99
|
+
openOn(request.view);
|
|
100
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
101
|
+
}, [request]);
|
|
102
|
+
|
|
103
|
+
const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>): void => {
|
|
104
|
+
// Enter and Space press the button; ArrowDown opens it as a list does.
|
|
105
|
+
// The window keymap reads the arrows as a frame step.
|
|
106
|
+
if (e.key !== "ArrowDown" || view !== null) return;
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
e.stopPropagation();
|
|
109
|
+
openOn("list");
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div
|
|
114
|
+
ref={anchor}
|
|
115
|
+
className={`color-field font-field${locked ? " is-readonly" : ""}`}
|
|
116
|
+
onPointerEnter={locked ? undefined : warm}
|
|
117
|
+
onFocus={locked ? undefined : warm}
|
|
118
|
+
>
|
|
119
|
+
<button
|
|
120
|
+
ref={button}
|
|
121
|
+
type="button"
|
|
122
|
+
className="font-field-open"
|
|
123
|
+
aria-label="Font"
|
|
124
|
+
aria-describedby={valueId}
|
|
125
|
+
aria-haspopup="listbox"
|
|
126
|
+
aria-expanded={view !== null}
|
|
127
|
+
disabled={locked}
|
|
128
|
+
onClick={() => (view === null ? openOn("list") : close("refocus"))}
|
|
129
|
+
onKeyDown={onKeyDown}
|
|
130
|
+
>
|
|
131
|
+
<span
|
|
132
|
+
id={valueId}
|
|
133
|
+
className={`font-field-name${mixed ? " is-mixed" : ""}`}
|
|
134
|
+
{...(status === "ready" && face !== null ? { style: { fontFamily: faceStack(face) } } : {})}
|
|
135
|
+
>
|
|
136
|
+
{mixed ? "Mixed" : value}
|
|
137
|
+
</span>
|
|
138
|
+
<IconChevronDown className="field-caret" />
|
|
139
|
+
</button>
|
|
140
|
+
{progress !== null && <FontApplyState progress={progress} locked={locked} onRetry={() => void store.retryFontApply()} />}
|
|
141
|
+
{view !== null && (
|
|
142
|
+
<FontPicker
|
|
143
|
+
anchor={anchor}
|
|
144
|
+
view={view}
|
|
145
|
+
onView={setView}
|
|
146
|
+
current={mixed ? null : value}
|
|
147
|
+
currentMachine={currentMachine}
|
|
148
|
+
onClose={close}
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The font write in flight, in the box's accessory lane. */
|
|
156
|
+
function FontApplyState(props: { progress: FontApplyProgress; locked: boolean; onRetry: () => void }) {
|
|
157
|
+
const { family, phase, loaded, total, failed } = props.progress;
|
|
158
|
+
if (failed !== null) {
|
|
159
|
+
return (
|
|
160
|
+
<span className="field-accessory">
|
|
161
|
+
<button type="button" className="btn font-field-retry" title={failed} disabled={props.locked} onClick={props.onRetry}>
|
|
162
|
+
Retry
|
|
163
|
+
</button>
|
|
164
|
+
</span>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const percent = total !== null && total > 0 ? Math.min(100, Math.round((loaded / total) * 100)) : null;
|
|
168
|
+
const doing = `${phase === "upload" ? "Uploading" : "Downloading"} ${family}`;
|
|
169
|
+
return (
|
|
170
|
+
<span
|
|
171
|
+
className="field-accessory font-field-apply"
|
|
172
|
+
role="progressbar"
|
|
173
|
+
aria-label={doing}
|
|
174
|
+
aria-valuemin={0}
|
|
175
|
+
aria-valuemax={100}
|
|
176
|
+
{...(percent !== null ? { "aria-valuenow": percent } : {})}
|
|
177
|
+
title={percent === null ? doing : `${doing}, ${percent}%`}
|
|
178
|
+
>
|
|
179
|
+
<svg className={`font-field-ring${percent === null ? " is-indeterminate" : ""}`} viewBox="0 0 12 12" aria-hidden="true">
|
|
180
|
+
<circle className="font-field-ring-track" cx="6" cy="6" r="4.5" />
|
|
181
|
+
{/* A size not known yet draws no level: the sheet dashes the ring, or
|
|
182
|
+
turns a short arc where motion is wanted. */}
|
|
183
|
+
<circle
|
|
184
|
+
className="font-field-ring-level"
|
|
185
|
+
cx="6"
|
|
186
|
+
cy="6"
|
|
187
|
+
r="4.5"
|
|
188
|
+
pathLength={100}
|
|
189
|
+
{...(percent === null ? {} : { strokeDasharray: `${percent} 100` })}
|
|
190
|
+
transform="rotate(-90 6 6)"
|
|
191
|
+
/>
|
|
192
|
+
</svg>
|
|
193
|
+
{percent !== null && <span className="font-field-percent">{percent}%</span>}
|
|
194
|
+
</span>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
// this
|
|
1
|
+
// The html layer's snippet. WHICH selections get this section is `layout.ts`'s
|
|
2
|
+
// answer, not this file's — the kind gate that used to open it is gone.
|
|
3
3
|
//
|
|
4
4
|
// TWO FIELDS AND NOT ONE, because the split is a correctness rule rather than a
|
|
5
5
|
// layout preference: a snippet is parsed as XML, where an unescaped `a > b`
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
// granularity here and not a compromise: every commit re-serializes the snippet,
|
|
11
11
|
// re-decodes it through an SVG image and re-rasters the layer, so a per-keystroke
|
|
12
12
|
// commit would put a decode on the critical path of every character typed.
|
|
13
|
+
//
|
|
14
|
+
// Both boxes GROW with their text, so both rows align to their start: the label
|
|
15
|
+
// stays level with the first line instead of centring on a paragraph.
|
|
13
16
|
import { InspectorRow, InspectorSection, TextField } from "@dailies-studio/ui";
|
|
14
17
|
import type { HtmlLayer3D, Layer3D } from "@dailies-studio/shot/doc";
|
|
15
18
|
import { useSelectedLayers, useSelectionValue, useSelectionWrite } from "../useDocWrite.js";
|
|
@@ -17,31 +20,39 @@ import { useSelectedLayers, useSelectionValue, useSelectionWrite } from "../useD
|
|
|
17
20
|
export function HtmlSection() {
|
|
18
21
|
const write = useSelectionWrite();
|
|
19
22
|
const layers = useSelectedLayers();
|
|
20
|
-
const anyHtml = layers.some((l) => l.kind === "html");
|
|
21
23
|
const markup = useSelectionValue((layer) => (layer.kind === "html" ? layer.html.markup : undefined));
|
|
22
24
|
const css = useSelectionValue((layer) => (layer.kind === "html" ? (layer.html.css ?? "") : undefined));
|
|
23
|
-
|
|
25
|
+
// data gate, not a kind gate: see layout.ts. A mixed selection led by a rect
|
|
26
|
+
// has no markup to show, and two fields over nothing is worse than none.
|
|
27
|
+
if (markup.value === undefined) return null;
|
|
28
|
+
// MIXED among the html layers alone: a rect beside one has no markup to
|
|
29
|
+
// disagree with, and the write passes it by.
|
|
30
|
+
const htmls = layers.filter((l): l is HtmlLayer3D => l.kind === "html");
|
|
31
|
+
const markupMixed = htmls.some((l) => l.html.markup !== markup.value);
|
|
32
|
+
const cssMixed = htmls.some((l) => (l.html.css ?? "") !== css.value);
|
|
24
33
|
|
|
25
34
|
const patch = (next: Partial<HtmlLayer3D["html"]>): void =>
|
|
26
|
-
write((layer) =>
|
|
35
|
+
write((layer) =>
|
|
36
|
+
layer.kind === "html" ? ({ ...layer, html: { ...layer.html, ...next } } as Layer3D) : layer,
|
|
37
|
+
);
|
|
27
38
|
|
|
28
39
|
return (
|
|
29
|
-
<InspectorSection title="Html" persistKey="shot:html">
|
|
30
|
-
<InspectorRow label="Markup">
|
|
40
|
+
<InspectorSection className="insp-object" title="Html" persistKey="shot:html">
|
|
41
|
+
<InspectorRow label="Markup" align="start">
|
|
31
42
|
<TextField
|
|
32
|
-
value={markup.value}
|
|
43
|
+
value={markupMixed ? "" : markup.value}
|
|
33
44
|
multiline
|
|
34
45
|
ariaLabel="Html markup"
|
|
35
|
-
placeholder="<div>…</div>"
|
|
46
|
+
placeholder={markupMixed ? "Mixed" : "<div>…</div>"}
|
|
36
47
|
onCommit={(next) => patch({ markup: next })}
|
|
37
48
|
/>
|
|
38
49
|
</InspectorRow>
|
|
39
|
-
<InspectorRow label="CSS">
|
|
50
|
+
<InspectorRow label="CSS" align="start">
|
|
40
51
|
<TextField
|
|
41
|
-
value={css.value ?? ""}
|
|
52
|
+
value={cssMixed ? "" : (css.value ?? "")}
|
|
42
53
|
multiline
|
|
43
54
|
ariaLabel="Html css"
|
|
44
|
-
placeholder=".root { … }"
|
|
55
|
+
placeholder={cssMixed ? "Mixed" : ".root { … }"}
|
|
45
56
|
onCommit={(next) => patch({ css: next })}
|
|
46
57
|
/>
|
|
47
58
|
</InspectorRow>
|