@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,550 @@
|
|
|
1
|
+
// The ONE paint control: solid, linear gradient or radial gradient, writing the
|
|
2
|
+
// shot `Paint` union the rasterizer already reads.
|
|
3
|
+
//
|
|
4
|
+
// ── WHY THIS EXISTS ─────────────────────────────────────────────────────────
|
|
5
|
+
//
|
|
6
|
+
// `BackgroundPlane` has rendered linear and radial gradients for real since the
|
|
7
|
+
// unify overhaul, and `paintStyle` (raster.ts) draws them for a layer fill too.
|
|
8
|
+
// The chrome could not write either. Worse, both surfaces DESTROYED one: the
|
|
9
|
+
// scene's Background row and the layer's Fill row were solid-only pickers whose
|
|
10
|
+
// scrub and commit each wrote `{type: "solid", color}`, so a gradient authored
|
|
11
|
+
// by MCP or by an agent died the first time an author touched the swatch, with
|
|
12
|
+
// no undo affordance naming what was lost.
|
|
13
|
+
//
|
|
14
|
+
// ── WHY NOT `GradientValueEditor` FROM @dailies-studio/ui ───────────────────
|
|
15
|
+
//
|
|
16
|
+
// It exists, it is exported, and it is the wrong shape. It takes contract's
|
|
17
|
+
// `GradientValue` (`{kind: "gradient", stops, space?, hueFixup?}`) and its
|
|
18
|
+
// validator refuses anything whose `kind` is not `"gradient"` AND refuses any
|
|
19
|
+
// extra key, so a shot `Paint` (`{type: "linear-gradient", angleDeg, stops}`)
|
|
20
|
+
// handed to it renders an invalid badge and never opens. Adapting both ways is
|
|
21
|
+
// more code than the rows below, and it would import two knobs that are DEAD
|
|
22
|
+
// here: `space` and `hueFixup` describe an interpolation the shot lane does not
|
|
23
|
+
// perform. `paintStyle` uses canvas `addColorStop`, which is plain sRGB, and
|
|
24
|
+
// nothing in packages/shot reads either field. It also has no angle control, so
|
|
25
|
+
// the one thing a linear gradient most needs would have to be written anyway.
|
|
26
|
+
//
|
|
27
|
+
// So: the stops list is authored directly in the shape the renderer reads, and
|
|
28
|
+
// each stop keeps its own ColorField and At field.
|
|
29
|
+
//
|
|
30
|
+
// ── THE GRADIENT BAR, AND WHY ITS RAMP IS REPAINTED HERE ───────────────────
|
|
31
|
+
//
|
|
32
|
+
// Over the stop rows sits `GradientBar` from @dailies-studio/ui: the design's
|
|
33
|
+
// 28px ramp with one draggable marker per stop. It draws the stops along one
|
|
34
|
+
// axis from 0 to 1, NOT the paint's geometry, so the angle-convention drift
|
|
35
|
+
// that kept a CSS preview of the whole fill out of this file does not arise:
|
|
36
|
+
// there is no angle in it to get wrong.
|
|
37
|
+
//
|
|
38
|
+
// Two things about it are adapted rather than taken whole:
|
|
39
|
+
//
|
|
40
|
+
// ITS RAMP IS OKLCH, THE CANVAS IS sRGB. The bar previews through contract's
|
|
41
|
+
// `gradientPreviewCss`, which interpolates in OKLCH, while `paintStyle`'s
|
|
42
|
+
// `addColorStop` blends plain sRGB. A ramp that brightens through the
|
|
43
|
+
// middle where the picture darkens is a preview lying about the picture,
|
|
44
|
+
// so the wrapper sets `--paint-ramp` to a plain CSS `linear-gradient` (sRGB
|
|
45
|
+
// by default, the canvas's space) and inspector-object.css paints it over
|
|
46
|
+
// the bar's own fill. The bar's markers, hit area and keyboard are its own.
|
|
47
|
+
// ITS VALUES MUST BE CONTRACT-VALID. Its algebra (composite.ts) refuses a
|
|
48
|
+
// stop that is not hex or a list that is not strictly ascending, and a shot
|
|
49
|
+
// `Paint` accepts both (style.ts `paintProblem` asks a stop for an `at` in
|
|
50
|
+
// 0..1 and a colour `colourProblem` passes: any non-empty string but a CSS
|
|
51
|
+
// image, so a named colour is legal). The bar renders only over stops it can edit;
|
|
52
|
+
// the rows below it edit everything, so no paint loses a control.
|
|
53
|
+
//
|
|
54
|
+
// A bar drag is ONE scrub. The bar reports `onChange` per move and `onCommit`
|
|
55
|
+
// at release, and a cancel restores the press stops through `onChange` with no
|
|
56
|
+
// commit at all, so the scrub opens on the first `onChange`, closes with a
|
|
57
|
+
// commit on `onCommit`, and closes WITHOUT one on the gesture's own abort
|
|
58
|
+
// events (Escape, pointercancel, lost capture). A discrete bar edit (a press on
|
|
59
|
+
// the empty ramp, a Delete, an arrow nudge) is `onChange` then `onCommit` in
|
|
60
|
+
// one tick, which is the same bracket, and one undo entry either way.
|
|
61
|
+
//
|
|
62
|
+
// ── STORE-FREE, LIKE EVERY REUSABLE WIDGET IN THIS CHROME ──────────────────
|
|
63
|
+
//
|
|
64
|
+
// A scene background writes through `patchScene`; a layer fill writes through
|
|
65
|
+
// `useSelectionWrite`. Neither is reachable from a shared component, and both
|
|
66
|
+
// have to route through their own edit path so `routeSceneEdit` /
|
|
67
|
+
// `routeLayerEdit` can turn the write into a keyframe when a track is armed.
|
|
68
|
+
// So this takes handlers and holds no document state. What it does keep is view
|
|
69
|
+
// state no document field records: which stop the bar has selected, and the
|
|
70
|
+
// bar's open scrub between its first move and its release.
|
|
71
|
+
import { useEffect, useRef, useState } from "react";
|
|
72
|
+
import type { CSSProperties } from "react";
|
|
73
|
+
import {
|
|
74
|
+
ColorField,
|
|
75
|
+
FieldLabels,
|
|
76
|
+
GradientBar,
|
|
77
|
+
IconMinus,
|
|
78
|
+
IconPlus,
|
|
79
|
+
InspectorRow,
|
|
80
|
+
MIN_GRADIENT_STOPS,
|
|
81
|
+
NumberField,
|
|
82
|
+
SegmentedControl,
|
|
83
|
+
tryParseHex,
|
|
84
|
+
useLocked,
|
|
85
|
+
} from "@dailies-studio/ui";
|
|
86
|
+
import { validateGradient } from "@dailies-studio/contract";
|
|
87
|
+
import type { GradientStop } from "@dailies-studio/contract";
|
|
88
|
+
import { formatHex } from "@dailies-studio/engine";
|
|
89
|
+
import type { Paint } from "@dailies-studio/shot/doc";
|
|
90
|
+
|
|
91
|
+
/** The three paint kinds as data, pinned to the schema's own union so a variant
|
|
92
|
+
* added or renamed there is a compile error here rather than a segment that
|
|
93
|
+
* writes a `type` the rasterizer falls through on. */
|
|
94
|
+
const PAINT_TYPES = ["solid", "linear-gradient", "radial-gradient"] as const satisfies readonly Paint["type"][];
|
|
95
|
+
|
|
96
|
+
const PAINT_LABELS: Record<Paint["type"], string> = {
|
|
97
|
+
solid: "Solid",
|
|
98
|
+
"linear-gradient": "Linear",
|
|
99
|
+
"radial-gradient": "Radial",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** The events that end a bar drag WITHOUT a commit. Escape is read on the
|
|
103
|
+
* window's capture phase because `startDrag` stops its propagation there
|
|
104
|
+
* (after cancelling, so the restore has already arrived); the pointer events
|
|
105
|
+
* in the bubble phase, so the marker's own handler has run first. */
|
|
106
|
+
const ABORT_BUBBLE = ["pointerup", "pointercancel", "lostpointercapture"] as const;
|
|
107
|
+
|
|
108
|
+
/** One control's edit, applied by the caller to EACH paint it writes. A whole
|
|
109
|
+
* paint built from the lead would copy the lead's colour, stops and angle onto
|
|
110
|
+
* every selected layer when the author changed one field. */
|
|
111
|
+
export type PaintEdit = (prev: Paint) => Paint;
|
|
112
|
+
|
|
113
|
+
export interface PaintRowsProps {
|
|
114
|
+
/** The lead paint, for display. */
|
|
115
|
+
value: Paint;
|
|
116
|
+
/** Every paint the rows write to, for the Mixed reads. Defaults to `value`. */
|
|
117
|
+
all?: readonly Paint[];
|
|
118
|
+
/** Every write, as an edit. The caller decides whether that lands on a scene
|
|
119
|
+
* or on a selection, and whether it becomes a keyframe. */
|
|
120
|
+
onCommit: (edit: PaintEdit) => void;
|
|
121
|
+
/** The DRAG half. A ColorPicker's SV square, hue bar and alpha bar never call
|
|
122
|
+
* `commit` while the pointer is down (ui/ColorPicker.tsx), so without these
|
|
123
|
+
* the thumb moves and the document does not. */
|
|
124
|
+
onScrubStart: () => void;
|
|
125
|
+
onScrub: (edit: PaintEdit) => void;
|
|
126
|
+
onScrubEnd: (commit: boolean) => void;
|
|
127
|
+
/** Prefixes every control's accessible name, because two of these can be on
|
|
128
|
+
* screen at once (a scene background and a layer fill). ACCESSIBLE NAMES
|
|
129
|
+
* ONLY: what the type row READS is `label` below. */
|
|
130
|
+
ariaPrefix: string;
|
|
131
|
+
/**
|
|
132
|
+
* The VISIBLE label of the type row. "Type" unless the caller says otherwise,
|
|
133
|
+
* and `null` for none at all.
|
|
134
|
+
*
|
|
135
|
+
* The callers want different words because the row sits under different
|
|
136
|
+
* headings. The shot's rows sit under a section called "Shot" beside a Lens
|
|
137
|
+
* and a Duration, where a bare "Type" offering Solid/Linear/Radial names
|
|
138
|
+
* nothing it is the type OF, so `SceneSection` passes "Background".
|
|
139
|
+
* `FillSection`'s heading already says Fill, and the design draws its type
|
|
140
|
+
* row as a bare chip track with no label, so it passes `null`: labelling it
|
|
141
|
+
* read Fill / Type / Color straight down the rail, three lines for one control.
|
|
142
|
+
*/
|
|
143
|
+
label?: string | null;
|
|
144
|
+
/** The keyframe diamond for the SOLID colour, when the caller has one, in
|
|
145
|
+
* the colour's box. A gradient has no single colour and therefore no
|
|
146
|
+
* diamond: `background.color` and `paint.fill.color` are colour tracks, and
|
|
147
|
+
* the track table refuses them over a gradient for exactly that reason
|
|
148
|
+
* (tracks.ts). */
|
|
149
|
+
solidTrailing?: React.ReactNode;
|
|
150
|
+
/**
|
|
151
|
+
* A solid's OPACITY row, editing the colour's alpha byte. Opt-in, because a
|
|
152
|
+
* scene background is the frame's ground and has nothing behind it for an
|
|
153
|
+
* alpha to show; a layer fill does. It writes the same colour the picker's
|
|
154
|
+
* alpha bar does, so it is one more door onto `color`, not a second field.
|
|
155
|
+
*/
|
|
156
|
+
showOpacity?: boolean;
|
|
157
|
+
/** The Opacity box's accessories: the colour's diamond again (the alpha
|
|
158
|
+
* byte of the same colour, keyed by the same track), without the expose
|
|
159
|
+
* pin the colour's box already carries, because a pin publishes the whole
|
|
160
|
+
* track and one per track is the rule (KeyframeDot). */
|
|
161
|
+
opacityTrailing?: React.ReactNode;
|
|
162
|
+
/** The paint holds no alpha (a scene background is opaque by contract): the
|
|
163
|
+
* pickers hide their alpha bar and every colour they commit is `#rrggbb`. */
|
|
164
|
+
opaque?: boolean;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A colour committed over a selection whose ALPHAS differ, onto one layer's
|
|
169
|
+
* own colour (`lead` is the colour the field shows, the lead layer's).
|
|
170
|
+
*
|
|
171
|
+
* Whether the author meant to set the alpha is read off the alpha byte the
|
|
172
|
+
* field was showing. A commit that left it where it was (a hue or SV drag, a
|
|
173
|
+
* typed six-digit hex, which ColorField fills from that alpha) changed the
|
|
174
|
+
* RGB only, so each layer keeps its own alpha. A commit that moved it (a typed
|
|
175
|
+
* eight-digit hex, the picker's alpha bar or its A field) sets that alpha on
|
|
176
|
+
* every layer: keeping each layer's own there meant typing eight digits, the
|
|
177
|
+
* ColorField's one way to set both, could not set the alpha at all.
|
|
178
|
+
*
|
|
179
|
+
* ponytail: the commit carries a colour and not how it was made, so an
|
|
180
|
+
* eight-digit hex whose alpha equals the lead's reads as untouched and each
|
|
181
|
+
* layer keeps its own. The Opacity (or Alpha) field beside the colour reads
|
|
182
|
+
* Mixed and sets one alpha on all; a commit that says whether its alpha was
|
|
183
|
+
* typed is a ColorField change.
|
|
184
|
+
*/
|
|
185
|
+
export function colorOverMixedAlpha(committed: string, lead: string, own: string): string {
|
|
186
|
+
const next = tryParseHex(committed);
|
|
187
|
+
const mine = tryParseHex(own);
|
|
188
|
+
if (next === null || mine === null) return committed;
|
|
189
|
+
// An unreadable lead is alpha 1, ColorField's own fallback for six digits.
|
|
190
|
+
const shown = tryParseHex(lead)?.alpha ?? 1;
|
|
191
|
+
const byte = (alpha: number): number => Math.round(alpha * 255);
|
|
192
|
+
return byte(next.alpha) === byte(shown) ? formatHex({ ...next, alpha: mine.alpha }) : committed;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Can the bar edit these stops? Its algebra is contract's, so contract says. */
|
|
196
|
+
function barCanEdit(stops: readonly GradientStop[]): boolean {
|
|
197
|
+
return stops.length >= MIN_GRADIENT_STOPS && validateGradient({ kind: "gradient", stops }) === null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The ramp as the CANVAS blends it: plain sRGB, which is a CSS gradient's
|
|
201
|
+
* default space. See the header for why the bar's own OKLCH fill is covered. */
|
|
202
|
+
function srgbRamp(stops: readonly GradientStop[]): string {
|
|
203
|
+
return `linear-gradient(to right, ${stops.map((s) => `${s.color} ${s.at * 100}%`).join(", ")})`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Where "+ Stop" puts a stop: the middle of the WIDEST gap, coloured as the
|
|
208
|
+
* canvas already paints that point, so adding one moves no pixel and the new
|
|
209
|
+
* marker lands where it can be seen and grabbed. Null when the stops are not
|
|
210
|
+
* hex (nothing to blend) or have no gap to split.
|
|
211
|
+
*/
|
|
212
|
+
function splitWidestGap(stops: readonly GradientStop[]): GradientStop[] | null {
|
|
213
|
+
let best = -1;
|
|
214
|
+
for (let i = 0; i + 1 < stops.length; i++) {
|
|
215
|
+
const gap = stops[i + 1]!.at - stops[i]!.at;
|
|
216
|
+
if (gap > 0 && (best < 0 || gap > stops[best + 1]!.at - stops[best]!.at)) best = i;
|
|
217
|
+
}
|
|
218
|
+
if (best < 0) return null;
|
|
219
|
+
const a = tryParseHex(stops[best]!.color);
|
|
220
|
+
const b = tryParseHex(stops[best + 1]!.color);
|
|
221
|
+
if (a === null || b === null) return null;
|
|
222
|
+
const mix = (x: number, y: number): number => (x + y) / 2;
|
|
223
|
+
const stop = {
|
|
224
|
+
at: mix(stops[best]!.at, stops[best + 1]!.at),
|
|
225
|
+
color: formatHex({ r: mix(a.r, b.r), g: mix(a.g, b.g), b: mix(a.b, b.b), alpha: mix(a.alpha, b.alpha) }),
|
|
226
|
+
};
|
|
227
|
+
return [...stops.slice(0, best + 1), stop, ...stops.slice(best + 1)];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function PaintRows(props: PaintRowsProps) {
|
|
231
|
+
const { value, onCommit, onScrub, onScrubStart, onScrubEnd, ariaPrefix, label = "Type" } = props;
|
|
232
|
+
const all = props.all ?? [value];
|
|
233
|
+
const stops = value.type === "solid" ? [] : value.stops;
|
|
234
|
+
const [picked, setPicked] = useState(0);
|
|
235
|
+
const selected = Math.min(picked, Math.max(0, stops.length - 1));
|
|
236
|
+
// A VIEWER'S PANEL LOCKS EVERY VERB THAT WRITES. The widgets read the lock
|
|
237
|
+
// themselves; the stop verbs here are plain buttons, so they read it here.
|
|
238
|
+
const locked = useLocked();
|
|
239
|
+
|
|
240
|
+
// MIXED, per field. A selection whose TYPES differ shows the type row alone:
|
|
241
|
+
// no other row means the same thing on all of them.
|
|
242
|
+
const typeMixed = all.some((p) => p.type !== value.type);
|
|
243
|
+
const solidRgb = value.type === "solid" ? tryParseHex(value.color) : null;
|
|
244
|
+
const rgbOf = (p: Paint): string | null => {
|
|
245
|
+
const rgb = p.type === "solid" ? tryParseHex(p.color) : null;
|
|
246
|
+
return rgb === null ? null : `${rgb.r},${rgb.g},${rgb.b}`;
|
|
247
|
+
};
|
|
248
|
+
const colorMixed = all.some((p) => rgbOf(p) !== rgbOf(value));
|
|
249
|
+
const alphaMixed = all.some((p) => (p.type === "solid" ? tryParseHex(p.color)?.alpha : null) !== solidRgb?.alpha);
|
|
250
|
+
const angleMixed = all.some((p) => p.type !== "linear-gradient" || value.type !== "linear-gradient" || p.angleDeg !== value.angleDeg);
|
|
251
|
+
const stopsKey = JSON.stringify(stops);
|
|
252
|
+
const stopsMixed = all.some((p) => p.type === "solid" || JSON.stringify(p.stops) !== stopsKey);
|
|
253
|
+
|
|
254
|
+
/** A stops edit over each paint's OWN stops. The whole array is written: the
|
|
255
|
+
* renderer reads the array, so a merged single stop would be left behind. */
|
|
256
|
+
const onStops = (edit: (stops: GradientStop[]) => GradientStop[]): PaintEdit =>
|
|
257
|
+
(prev) => (prev.type === "solid" ? prev : { ...prev, stops: edit(prev.stops) });
|
|
258
|
+
|
|
259
|
+
const setStop = (index: number, patch: Partial<GradientStop>): PaintEdit =>
|
|
260
|
+
onStops((own) => own.map((stop, i) => (i === index ? { ...stop, ...patch } : stop)));
|
|
261
|
+
|
|
262
|
+
// THE BAR'S SCRUB, open between its first `onChange` and its commit or
|
|
263
|
+
// abort. See the header. A ref, because the bracket spans pointer events
|
|
264
|
+
// and must not wait on a render.
|
|
265
|
+
const barScrub = useRef<((commit: boolean) => void) | null>(null);
|
|
266
|
+
const live = useRef(props);
|
|
267
|
+
live.current = props;
|
|
268
|
+
useEffect(() => () => barScrub.current?.(false), []);
|
|
269
|
+
|
|
270
|
+
const barChange = (next: GradientStop[]): void => {
|
|
271
|
+
if (barScrub.current === null) {
|
|
272
|
+
const onEscape = (e: KeyboardEvent): void => {
|
|
273
|
+
if (e.key === "Escape") end(false);
|
|
274
|
+
};
|
|
275
|
+
const onAbort = (): void => end(false);
|
|
276
|
+
const end = (commit: boolean): void => {
|
|
277
|
+
window.removeEventListener("keydown", onEscape, true);
|
|
278
|
+
for (const type of ABORT_BUBBLE) window.removeEventListener(type, onAbort);
|
|
279
|
+
barScrub.current = null;
|
|
280
|
+
live.current.onScrubEnd(commit);
|
|
281
|
+
};
|
|
282
|
+
window.addEventListener("keydown", onEscape, true);
|
|
283
|
+
for (const type of ABORT_BUBBLE) window.addEventListener(type, onAbort);
|
|
284
|
+
barScrub.current = end;
|
|
285
|
+
onScrubStart();
|
|
286
|
+
}
|
|
287
|
+
onScrub(onStops(() => next));
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const barCommit = (next: GradientStop[]): void => {
|
|
291
|
+
if (barScrub.current !== null) barScrub.current(true);
|
|
292
|
+
else onCommit(onStops(() => next));
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/** The alpha byte on each paint's OWN colour. */
|
|
296
|
+
const withAlpha = (pct: number): PaintEdit => (prev) => {
|
|
297
|
+
const rgb = prev.type === "solid" ? tryParseHex(prev.color) : null;
|
|
298
|
+
return rgb === null
|
|
299
|
+
? prev
|
|
300
|
+
: { type: "solid", color: formatHex({ ...rgb, alpha: Math.min(1, Math.max(0, pct / 100)) }) };
|
|
301
|
+
};
|
|
302
|
+
/** A colour pick; where opacities differ, each paint keeps its own alpha
|
|
303
|
+
* unless the pick moved the alpha the field shows (`colorOverMixedAlpha`). */
|
|
304
|
+
const solidColor = (color: string): PaintEdit => (prev) =>
|
|
305
|
+
prev.type !== "solid"
|
|
306
|
+
? prev
|
|
307
|
+
: {
|
|
308
|
+
type: "solid",
|
|
309
|
+
color: alphaMixed && value.type === "solid" ? colorOverMixedAlpha(color, value.color, prev.color) : color,
|
|
310
|
+
};
|
|
311
|
+
const withAngle = (angleDeg: number): PaintEdit => (prev) =>
|
|
312
|
+
prev.type === "linear-gradient" ? { ...prev, angleDeg } : prev;
|
|
313
|
+
|
|
314
|
+
// The house segmented control, the one look every switch in the chrome
|
|
315
|
+
// shares (widgets.css): the chosen type is the flat tile, not an accent.
|
|
316
|
+
const typeRow = (
|
|
317
|
+
<SegmentedControl
|
|
318
|
+
ariaLabel={`${ariaPrefix} paint type`}
|
|
319
|
+
// An unmatched value is how a segmented control says Mixed.
|
|
320
|
+
value={typeMixed ? "" : value.type}
|
|
321
|
+
options={PAINT_TYPES.map((type) => ({ value: type, label: PAINT_LABELS[type] }))}
|
|
322
|
+
onChange={(next) => onCommit((prev) => convert(prev, next as Paint["type"]))}
|
|
323
|
+
/>
|
|
324
|
+
);
|
|
325
|
+
const opaque = props.opaque === true;
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<>
|
|
329
|
+
{/* THE LABEL IS THE CALLER'S. "Background" over Solid/Linear/Radial in
|
|
330
|
+
the shot section, none at all under a heading that already names the
|
|
331
|
+
paint. The accessible name keeps its own longer spelling either way,
|
|
332
|
+
which is why it is a second prop and not this one. */}
|
|
333
|
+
{label === null ? <InspectorRow>{typeRow}</InspectorRow> : <InspectorRow label={label}>{typeRow}</InspectorRow>}
|
|
334
|
+
|
|
335
|
+
{typeMixed ? null : (
|
|
336
|
+
<>
|
|
337
|
+
{value.type === "solid" ? (
|
|
338
|
+
<InspectorRow>
|
|
339
|
+
<ColorField
|
|
340
|
+
value={value.color}
|
|
341
|
+
mixed={colorMixed}
|
|
342
|
+
ariaLabel={`${ariaPrefix} colour`}
|
|
343
|
+
{...(opaque ? { opaque: true } : {})}
|
|
344
|
+
trailing={props.solidTrailing}
|
|
345
|
+
scrub={{
|
|
346
|
+
start: onScrubStart,
|
|
347
|
+
move: (color) => onScrub(solidColor(String(color))),
|
|
348
|
+
end: onScrubEnd,
|
|
349
|
+
}}
|
|
350
|
+
commit={(color) => onCommit(solidColor(color))}
|
|
351
|
+
/>
|
|
352
|
+
</InspectorRow>
|
|
353
|
+
) : null}
|
|
354
|
+
|
|
355
|
+
{/* OPACITY IS THE ALPHA BYTE. Only over a colour the engine can read:
|
|
356
|
+
an unparseable string has no byte to set, and a field that wrote one
|
|
357
|
+
would replace the author's colour with a guess. It is keyed by the
|
|
358
|
+
colour's track, so its box carries the colour's diamond. */}
|
|
359
|
+
{value.type === "solid" && props.showOpacity === true && solidRgb !== null ? (
|
|
360
|
+
<InspectorRow>
|
|
361
|
+
<NumberField
|
|
362
|
+
value={Math.round(solidRgb.alpha * 100)}
|
|
363
|
+
mixed={alphaMixed}
|
|
364
|
+
label="Opacity"
|
|
365
|
+
ariaLabel={`${ariaPrefix} opacity`}
|
|
366
|
+
unit="%"
|
|
367
|
+
min={0}
|
|
368
|
+
max={100}
|
|
369
|
+
step={1}
|
|
370
|
+
trailing={props.opacityTrailing}
|
|
371
|
+
onScrubStart={onScrubStart}
|
|
372
|
+
onScrub={(pct) => onScrub(withAlpha(pct))}
|
|
373
|
+
onScrubEnd={onScrubEnd}
|
|
374
|
+
onCommit={(pct) => onCommit(withAlpha(pct))}
|
|
375
|
+
/>
|
|
376
|
+
</InspectorRow>
|
|
377
|
+
) : null}
|
|
378
|
+
|
|
379
|
+
{/* ANGLE IS LINEAR-ONLY. `paintStyle` builds a radial gradient from the
|
|
380
|
+
box centre outward, so there is no direction for an angle to name and
|
|
381
|
+
a field here would write a number nothing reads. */}
|
|
382
|
+
{value.type === "linear-gradient" ? (
|
|
383
|
+
<InspectorRow>
|
|
384
|
+
<NumberField
|
|
385
|
+
value={value.angleDeg}
|
|
386
|
+
mixed={angleMixed}
|
|
387
|
+
label="Angle"
|
|
388
|
+
ariaLabel={`${ariaPrefix} gradient angle`}
|
|
389
|
+
unit="°"
|
|
390
|
+
step={1}
|
|
391
|
+
// NO min/max, and no wrap. The axis is `cos`/`sin` of the angle, so
|
|
392
|
+
// 370 and 10 draw the same picture and clamping would only stop a
|
|
393
|
+
// drag from crossing the seam mid-gesture.
|
|
394
|
+
onScrubStart={onScrubStart}
|
|
395
|
+
onScrub={(angleDeg) => onScrub(withAngle(angleDeg))}
|
|
396
|
+
onScrubEnd={onScrubEnd}
|
|
397
|
+
onCommit={(angleDeg) => onCommit(withAngle(angleDeg))}
|
|
398
|
+
/>
|
|
399
|
+
</InspectorRow>
|
|
400
|
+
) : null}
|
|
401
|
+
|
|
402
|
+
{/* STOPS THAT DIFFER have no one list to show, so the stop controls
|
|
403
|
+
wait until the selection agrees. */}
|
|
404
|
+
{value.type !== "solid" && stopsMixed ? (
|
|
405
|
+
<p className="insp-note">The selected gradients have different stops.</p>
|
|
406
|
+
) : null}
|
|
407
|
+
|
|
408
|
+
{value.type !== "solid" && !stopsMixed && barCanEdit(stops) ? (
|
|
409
|
+
<div className="paint-gradient" style={{ "--paint-ramp": srgbRamp(stops) } as CSSProperties}>
|
|
410
|
+
<GradientBar
|
|
411
|
+
stops={stops}
|
|
412
|
+
selected={selected}
|
|
413
|
+
onSelect={setPicked}
|
|
414
|
+
onChange={barChange}
|
|
415
|
+
onCommit={barCommit}
|
|
416
|
+
ariaLabel={`${ariaPrefix} gradient stops`}
|
|
417
|
+
/>
|
|
418
|
+
</div>
|
|
419
|
+
) : null}
|
|
420
|
+
|
|
421
|
+
{value.type !== "solid" && !stopsMixed
|
|
422
|
+
? stops.map((stop, index) => (
|
|
423
|
+
<InspectorRow
|
|
424
|
+
// Index and not the stop's colour: two stops may share a colour,
|
|
425
|
+
// and a key that changes as the author drags a hue remounts the
|
|
426
|
+
// picker mid-gesture and drops the pointer capture.
|
|
427
|
+
key={index}
|
|
428
|
+
className={`paint-stop${index === selected ? " is-selected" : ""}`}
|
|
429
|
+
label={value.type === "radial-gradient" ? (index === 0 ? "Centre" : "Stop") : "Stop"}
|
|
430
|
+
trailing={
|
|
431
|
+
// Never below the minimum. One stop is a solid written the
|
|
432
|
+
// long way, and `paintStyle` on an EMPTY stops array draws
|
|
433
|
+
// nothing at all, which is a scene that renders as the clear
|
|
434
|
+
// colour with no control on screen explaining why.
|
|
435
|
+
//
|
|
436
|
+
// A VERB IN THE BOX, not a fourth diamond: the house accessory
|
|
437
|
+
// (`.insp-key`) at rest, the danger ink on hover (`is-danger`),
|
|
438
|
+
// and inside the stop's own box, so the row is as wide with it
|
|
439
|
+
// as without it.
|
|
440
|
+
stops.length > MIN_GRADIENT_STOPS ? (
|
|
441
|
+
<button
|
|
442
|
+
type="button"
|
|
443
|
+
className="insp-key is-danger"
|
|
444
|
+
title="Remove stop"
|
|
445
|
+
aria-label={`Remove ${ariaPrefix} stop ${index + 1}`}
|
|
446
|
+
disabled={locked}
|
|
447
|
+
onClick={() => onCommit(onStops((own) => own.filter((_, i) => i !== index)))}
|
|
448
|
+
>
|
|
449
|
+
<IconMinus />
|
|
450
|
+
</button>
|
|
451
|
+
) : undefined
|
|
452
|
+
}
|
|
453
|
+
>
|
|
454
|
+
{/* A FIXED 28px BOX holding two values: the At field keeps its
|
|
455
|
+
in-box prefix in every field-label mode, since a name above
|
|
456
|
+
it would land inside the stop's own box. */}
|
|
457
|
+
<FieldLabels value="compact">
|
|
458
|
+
<ColorField
|
|
459
|
+
value={stop.color}
|
|
460
|
+
ariaLabel={`${ariaPrefix} stop ${index + 1} colour`}
|
|
461
|
+
{...(opaque ? { opaque: true } : {})}
|
|
462
|
+
scrub={{
|
|
463
|
+
start: onScrubStart,
|
|
464
|
+
move: (color) => onScrub(setStop(index, { color: String(color) })),
|
|
465
|
+
end: onScrubEnd,
|
|
466
|
+
}}
|
|
467
|
+
commit={(color) => onCommit(setStop(index, { color }))}
|
|
468
|
+
/>
|
|
469
|
+
<NumberField
|
|
470
|
+
value={stop.at}
|
|
471
|
+
label="At"
|
|
472
|
+
ariaLabel={`${ariaPrefix} stop ${index + 1} position`}
|
|
473
|
+
min={0}
|
|
474
|
+
max={1}
|
|
475
|
+
// A SNAP, not a speed: the drag and the arrow keys land on
|
|
476
|
+
// twenty positions, a ramp an author can put where they
|
|
477
|
+
// meant; 0.01 would be a hundred and land on none of them.
|
|
478
|
+
// It is also the quantum this chrome already picked for its
|
|
479
|
+
// other bounded 0..1 fields, the media crop's and
|
|
480
|
+
// EffectsSection's amounts. The bar above is the fine
|
|
481
|
+
// control.
|
|
482
|
+
step={0.05}
|
|
483
|
+
onScrubStart={onScrubStart}
|
|
484
|
+
onScrub={(at) => onScrub(setStop(index, { at }))}
|
|
485
|
+
onScrubEnd={onScrubEnd}
|
|
486
|
+
onCommit={(at) => onCommit(setStop(index, { at }))}
|
|
487
|
+
/>
|
|
488
|
+
</FieldLabels>
|
|
489
|
+
</InspectorRow>
|
|
490
|
+
))
|
|
491
|
+
: null}
|
|
492
|
+
|
|
493
|
+
{value.type !== "solid" && !stopsMixed ? (
|
|
494
|
+
<InspectorRow className="paint-add-stop">
|
|
495
|
+
<button
|
|
496
|
+
type="button"
|
|
497
|
+
className="btn"
|
|
498
|
+
aria-label={`Add ${ariaPrefix} stop`}
|
|
499
|
+
disabled={locked}
|
|
500
|
+
// Adding a stop moves NO pixel: the author is opening a control,
|
|
501
|
+
// not asking for a change. It splits the widest gap at the colour
|
|
502
|
+
// the canvas already paints there, so the new marker is visible
|
|
503
|
+
// and grabbable on the bar. Over stops the engine cannot blend it
|
|
504
|
+
// falls back to appending at 1 in the last stop's colour, which
|
|
505
|
+
// is equally invisible, and dragging it left is the edit.
|
|
506
|
+
onClick={() => {
|
|
507
|
+
const split = splitWidestGap(stops);
|
|
508
|
+
if (split !== null) {
|
|
509
|
+
setPicked(split.findIndex((s, i) => s !== stops[i]));
|
|
510
|
+
onCommit(onStops(() => split));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
onCommit(onStops((own) => [...own, { at: 1, color: own[own.length - 1]?.color ?? "#ffffff" }]));
|
|
514
|
+
}}
|
|
515
|
+
>
|
|
516
|
+
<IconPlus />
|
|
517
|
+
Stop
|
|
518
|
+
</button>
|
|
519
|
+
</InspectorRow>
|
|
520
|
+
) : null}
|
|
521
|
+
</>
|
|
522
|
+
)}
|
|
523
|
+
</>
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* The same paint as another type, keeping whatever survives the change.
|
|
529
|
+
*
|
|
530
|
+
* A type switch must not be a data-loss event the author cannot see coming, so
|
|
531
|
+
* a solid becomes a ramp that STARTS at its own colour, and a gradient becomes
|
|
532
|
+
* the solid of its first stop (which is what the old solid-only swatch already
|
|
533
|
+
* displayed for one, so the picture the author was looking at is the picture
|
|
534
|
+
* they get). The stops survive a linear/radial switch untouched, because both
|
|
535
|
+
* arms carry the identical array and only the geometry differs.
|
|
536
|
+
*/
|
|
537
|
+
export function convert(paint: Paint, type: Paint["type"]): Paint {
|
|
538
|
+
if (paint.type === type) return paint;
|
|
539
|
+
if (type === "solid") {
|
|
540
|
+
return { type: "solid", color: paint.type === "solid" ? paint.color : (paint.stops[0]?.color ?? "#000000") };
|
|
541
|
+
}
|
|
542
|
+
const stops =
|
|
543
|
+
paint.type === "solid"
|
|
544
|
+
? [{ at: 0, color: paint.color }, { at: 1, color: "#000000" }]
|
|
545
|
+
: paint.stops.map((stop) => ({ ...stop }));
|
|
546
|
+
// `angleDeg` is not preserved across a round trip through radial, because a
|
|
547
|
+
// radial gradient has nowhere to keep it. 90 is a top-to-bottom ramp, which
|
|
548
|
+
// is the one every background in this lane has ever wanted.
|
|
549
|
+
return type === "linear-gradient" ? { type, angleDeg: 90, stops } : { type, stops };
|
|
550
|
+
}
|