@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
|
@@ -1,17 +1,60 @@
|
|
|
1
|
-
// The cinematic post stack,
|
|
1
|
+
// The cinematic post stack, as a list of WHAT IS ON.
|
|
2
2
|
//
|
|
3
3
|
// ABSENT MEANS OFF — there is no `enabled` flag anywhere in `SceneEffects`,
|
|
4
|
-
// because two ways to say "off" is one way to disagree. So
|
|
5
|
-
// DELETES the key rather than setting a boolean, and the shader chain
|
|
6
|
-
// from `postSignature`, which is derived from exactly which keys exist.
|
|
4
|
+
// because two ways to say "off" is one way to disagree. So switching an effect
|
|
5
|
+
// off DELETES the key rather than setting a boolean, and the shader chain
|
|
6
|
+
// rebuilds from `postSignature`, which is derived from exactly which keys exist.
|
|
7
|
+
//
|
|
8
|
+
// THE SECTION USED TO RENDER ALL SIXTEEN, ALWAYS. Sixteen label-plus-toggle
|
|
9
|
+
// rows made this ~630px tall in a 320px panel, so "which effects does this shot
|
|
10
|
+
// have" was a reading task rather than a glance, and the answer was carried by
|
|
11
|
+
// sixteen switch positions the eye has to scan one at a time. Absent means off
|
|
12
|
+
// in the document; it now means absent on screen too, which is the same claim
|
|
13
|
+
// stated once. The rest live behind the header's add menu.
|
|
14
|
+
//
|
|
15
|
+
// ── TWO SECTIONS OVER ONE STACK: BLUR AND POSTPROCESSING ─────────────────
|
|
16
|
+
//
|
|
17
|
+
// `Scene3D.effects` is still ONE object and nothing about the document moved.
|
|
18
|
+
// The rail shows it as two sections because the three blurs are a different
|
|
19
|
+
// kind of decision from the rest. Depth of field, tilt shift and motion blur
|
|
20
|
+
// each say what is SHARP, which is a lens and shutter choice made with the
|
|
21
|
+
// camera; a bloom, a grade or a grain says what the picture is made INTO after
|
|
22
|
+
// it. One list made the author hunt for the defocus among seventeen passes, and
|
|
23
|
+
// the board draws them apart. Each section's add menu offers only its own
|
|
24
|
+
// group, and both run through the one `toggle` seam below, so a removed blur
|
|
25
|
+
// still drops its keyframes exactly as a removed bloom does.
|
|
26
|
+
import { Fragment, useRef, useState } from "react";
|
|
7
27
|
import type { ReactNode } from "react";
|
|
8
|
-
import {
|
|
9
|
-
|
|
28
|
+
import {
|
|
29
|
+
FieldLabels,
|
|
30
|
+
IconClose,
|
|
31
|
+
IconPlus,
|
|
32
|
+
InspectorRow,
|
|
33
|
+
InspectorSection,
|
|
34
|
+
NumberField,
|
|
35
|
+
Popover,
|
|
36
|
+
useCollapsed,
|
|
37
|
+
useLocked,
|
|
38
|
+
} from "@dailies-studio/ui";
|
|
39
|
+
// `sceneTrackablePath` for the numeric limits, so the pill and `set_keyframe`
|
|
40
|
+
// cap at one number rather than two that drift apart.
|
|
41
|
+
import { containerFrame, resolveSceneTracksAt, sceneTrackablePath } from "@dailies-studio/shot/doc";
|
|
42
|
+
import type { Doc3D, SceneEffects } from "@dailies-studio/shot/doc";
|
|
43
|
+
import { SceneKeyframeDot } from "../KeyframeDot.js";
|
|
44
|
+
import { SECTION_ICONS } from "../../../icons.js";
|
|
45
|
+
import { dailiesHost } from "../../../hostApi.js";
|
|
10
46
|
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
47
|
+
import { useInspectorScene, useScrub, writeScene } from "../useDocWrite.js";
|
|
11
48
|
|
|
12
49
|
/** What each effect looks like when first switched on. Values that read as the
|
|
13
50
|
* effect rather than as nothing — a bloom at strength 0 is a toggle that
|
|
14
|
-
* appears not to work, and the author blames the renderer.
|
|
51
|
+
* appears not to work, and the author blames the renderer.
|
|
52
|
+
*
|
|
53
|
+
* ALSO WHAT THE FIELDS BELOW DISPLAY for a param the document has not stored,
|
|
54
|
+
* which is the same number by definition: `toggle` writes this whole object on
|
|
55
|
+
* the way in, so a field showing anything else would be showing a value the
|
|
56
|
+
* shot does not have. The fields restated these as literal `?? 0.6` fallbacks,
|
|
57
|
+
* which is two writers for one number and drifts the day one moves. */
|
|
15
58
|
const DEFAULTS = {
|
|
16
59
|
dof: { fStop: 2.8, bokehScale: 2, quality: "mip" },
|
|
17
60
|
bloom: { strength: 0.6, radius: 0.5, threshold: 0.7 },
|
|
@@ -21,103 +64,711 @@ const DEFAULTS = {
|
|
|
21
64
|
ghost: { amount: 0.25, offset: [0.004, 0.002], count: 3 },
|
|
22
65
|
grade: { slope: [1, 1, 1], offset: [0, 0, 0], power: [1, 1, 1], saturation: 1 },
|
|
23
66
|
edgeFade: { size: 0.12, softness: 0.08 },
|
|
67
|
+
sharpen: { amount: 0.5 },
|
|
68
|
+
tiltShift: { amount: 1, center: 0.5, width: 0.3, softness: 0.15 },
|
|
69
|
+
bleach: { amount: 0.6 },
|
|
70
|
+
glitch: { strength: 0.3, density: 0.3, split: 2 },
|
|
71
|
+
pixelate: { size: 8 },
|
|
72
|
+
crt: { curvature: 0.15, scanlines: 0.35, lineHeight: 3 },
|
|
73
|
+
rays: { strength: 0.6, threshold: 0.6, center: [0.5, 0.35] },
|
|
24
74
|
motionBlur: { shutterDeg: 180, samples: 8 },
|
|
75
|
+
fisheye: { strength: 1, vignette: 0.6 },
|
|
25
76
|
} as const satisfies Record<keyof SceneEffects, unknown>;
|
|
26
77
|
|
|
27
78
|
type EffectKey = keyof typeof DEFAULTS;
|
|
28
79
|
|
|
29
|
-
|
|
80
|
+
/** A vector param's component prefixes, in box order: a vec2's axes, and
|
|
81
|
+
* Grade's channels. Its one diamond's tooltip names them as keying together. */
|
|
82
|
+
const XY = ["X", "Y"] as const;
|
|
83
|
+
const RGB = ["R", "G", "B"] as const;
|
|
84
|
+
|
|
85
|
+
/** The add menus' headings, in menu order, and the only place the names exist.
|
|
86
|
+
* The type is read back off the array so another group is one edit. */
|
|
87
|
+
const GROUPS = ["Blur", "Look", "Lens", "Damage"] as const;
|
|
88
|
+
type EffectGroup = (typeof GROUPS)[number];
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The two sections, and which groups each one owns. Every group belongs to
|
|
92
|
+
* exactly one stack, so every effect has exactly one home and one add item.
|
|
93
|
+
*/
|
|
94
|
+
const STACKS = {
|
|
95
|
+
blur: {
|
|
96
|
+
title: "Blur",
|
|
97
|
+
groups: ["Blur"],
|
|
98
|
+
persistKey: "shot:blur",
|
|
99
|
+
className: "effects-section blur-section",
|
|
100
|
+
addTestId: "blur-add",
|
|
101
|
+
addLabel: "Add blur",
|
|
102
|
+
addTitle: "Add a blur: depth of field, tilt shift or motion blur",
|
|
103
|
+
empty: "No blur yet.",
|
|
104
|
+
},
|
|
105
|
+
// The post stack keeps every name the old single section had (its testid,
|
|
106
|
+
// its aria name, its collapse bit, its empty line), because tests, agents
|
|
107
|
+
// and stored collapse state all address it by them.
|
|
108
|
+
postprocessing: {
|
|
109
|
+
title: "Postprocessing",
|
|
110
|
+
groups: ["Look", "Lens", "Damage"],
|
|
111
|
+
persistKey: "shot:effects",
|
|
112
|
+
className: "effects-section",
|
|
113
|
+
addTestId: "effect-add",
|
|
114
|
+
addLabel: "Add effect",
|
|
115
|
+
addTitle: "Add an effect to the post stack",
|
|
116
|
+
empty: "No effects yet.",
|
|
117
|
+
},
|
|
118
|
+
} as const satisfies Record<
|
|
119
|
+
string,
|
|
120
|
+
{
|
|
121
|
+
title: string;
|
|
122
|
+
groups: readonly EffectGroup[];
|
|
123
|
+
persistKey: string;
|
|
124
|
+
className: string;
|
|
125
|
+
addTestId: string;
|
|
126
|
+
addLabel: string;
|
|
127
|
+
addTitle: string;
|
|
128
|
+
empty: string;
|
|
129
|
+
}
|
|
130
|
+
>;
|
|
131
|
+
type StackId = keyof typeof STACKS;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Every effect's on-screen name and which drawer of the add menu it sits in.
|
|
135
|
+
*
|
|
136
|
+
* `satisfies Record<keyof SceneEffects, …>` IS THE POINT: an effect added to
|
|
137
|
+
* `SceneEffects` without an entry here fails to compile, rather than shipping as
|
|
138
|
+
* a post pass with no way to switch it on. The same guard `DEFAULTS` carries,
|
|
139
|
+
* for the same reason.
|
|
140
|
+
*
|
|
141
|
+
* THE LABELS ARE LOAD-BEARING, character for character: they are the accessible
|
|
142
|
+
* name of both the add item and its remove button, and the inspector tests
|
|
143
|
+
* locate effects by them.
|
|
144
|
+
*
|
|
145
|
+
* THIS ORDER IS THE RENDER ORDER of the enabled list, not document order.
|
|
146
|
+
* Document order is insertion order, so a list sorted by it would reshuffle
|
|
147
|
+
* itself every time an effect is switched on: the row an author just added
|
|
148
|
+
* would land at the bottom and the rows they were reading would move. A fixed
|
|
149
|
+
* table means adding an effect inserts one row and nothing else travels.
|
|
150
|
+
* Grouped in menu order too, so the panel and the menu read the same way down.
|
|
151
|
+
*/
|
|
152
|
+
const EFFECTS = {
|
|
153
|
+
// Blur: what is sharp. Its own section (see the header).
|
|
154
|
+
dof: { label: "Depth of field", group: "Blur" },
|
|
155
|
+
tiltShift: { label: "Tilt shift", group: "Blur" },
|
|
156
|
+
motionBlur: { label: "Motion blur", group: "Blur" },
|
|
157
|
+
// Look: what the picture is graded and glowed into.
|
|
158
|
+
bloom: { label: "Bloom", group: "Look" },
|
|
159
|
+
vignette: { label: "Vignette", group: "Look" },
|
|
160
|
+
grade: { label: "Grade", group: "Look" },
|
|
161
|
+
bleach: { label: "Bleach", group: "Look" },
|
|
162
|
+
sharpen: { label: "Sharpen", group: "Look" },
|
|
163
|
+
// Lens: what a piece of glass in front of the sensor would have done,
|
|
164
|
+
// other than blur it.
|
|
165
|
+
rays: { label: "Light rays", group: "Lens" },
|
|
166
|
+
colorSplit: { label: "Color split", group: "Lens" },
|
|
167
|
+
ghost: { label: "Ghost", group: "Lens" },
|
|
168
|
+
edgeFade: { label: "Edge fade", group: "Lens" },
|
|
169
|
+
fisheye: { label: "Fisheye", group: "Lens" },
|
|
170
|
+
// Damage: the artefacts of a medium, film or signal or screen.
|
|
171
|
+
grain: { label: "Grain", group: "Damage" },
|
|
172
|
+
glitch: { label: "Glitch", group: "Damage" },
|
|
173
|
+
crt: { label: "CRT", group: "Damage" },
|
|
174
|
+
pixelate: { label: "Pixelate", group: "Damage" },
|
|
175
|
+
} satisfies Record<keyof SceneEffects, { label: string; group: EffectGroup }>;
|
|
176
|
+
|
|
177
|
+
const EFFECT_KEYS = Object.keys(EFFECTS) as EffectKey[];
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The HARD limits a param accepts, read from the table that also refuses an
|
|
181
|
+
* agent's `set_keyframe` on the same path.
|
|
182
|
+
*
|
|
183
|
+
* These used to be literals in the fields below, so the two could disagree: the
|
|
184
|
+
* pill capped CRT curvature at 0.49 and `set_keyframe` took 3, which is the
|
|
185
|
+
* picture inverted through itself with the inspector still reading 0.49. Worse
|
|
186
|
+
* on a param that already carries a track, because `routeTrackedEdit` DROPS an
|
|
187
|
+
* edit `trackValueRefusal` rejects, so a field wider than the table is a drag
|
|
188
|
+
* that silently commits nothing.
|
|
189
|
+
*
|
|
190
|
+
* Only the bounds the RENDERER enforces are in that table. A path with none
|
|
191
|
+
* gets nothing from here and keeps the taste ceiling written beside it: bloom
|
|
192
|
+
* stops at 5 because 8 is a look, not because the document refuses it. `step`
|
|
193
|
+
* is local either way. It is the nudge quantum, not a limit.
|
|
194
|
+
*/
|
|
195
|
+
const hardOf = (path: string): { min?: number; max?: number } => {
|
|
196
|
+
const range = sceneTrackablePath(path)?.numberRange;
|
|
197
|
+
if (range === undefined) return {};
|
|
198
|
+
return {
|
|
199
|
+
...(range.min === undefined ? {} : { min: range.min }),
|
|
200
|
+
...(range.max === undefined ? {} : { max: range.max }),
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** Which param a drag is moving. `move` takes `unknown`, so the shape is named
|
|
205
|
+
* once here and the call sites are type-checked against it. A mistyped field
|
|
206
|
+
* would otherwise ride a drag into the document as a key nothing reads. */
|
|
207
|
+
interface ParamEdit {
|
|
208
|
+
key: EffectKey;
|
|
209
|
+
field: string;
|
|
210
|
+
value: number;
|
|
211
|
+
/** One element of a vector field (Grade's R, G or B, a centre's X or Y).
|
|
212
|
+
* Absent for a scalar. */
|
|
213
|
+
index?: number;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* One effect param, written over the document it is HANDED.
|
|
218
|
+
*
|
|
219
|
+
* Both write paths run it: a typed value through `edit`, and a drag through
|
|
220
|
+
* `scrubCapture`, which re-derives from the gesture's BASE. Reading the effect
|
|
221
|
+
* out of the handed document rather than out of the render is what stops a drag
|
|
222
|
+
* compounding: a builder closing over this render's `effects` would re-apply
|
|
223
|
+
* the whole gesture on top of itself and run away from the cursor.
|
|
224
|
+
*
|
|
225
|
+
* IDENTITY WHEN NOTHING CHANGED: `edit` records no history entry for a document
|
|
226
|
+
* it gets back unchanged, and a drag that ends on the value it started from
|
|
227
|
+
* commits nothing.
|
|
228
|
+
*/
|
|
229
|
+
function patchEffect(doc: Doc3D, sceneId: string, edit: ParamEdit, sceneFrame: number): Doc3D {
|
|
230
|
+
// Through `writeScene`, so a param the shot ANIMATES takes a keyframe at the
|
|
231
|
+
// playhead instead of a constant the track is already overriding (P5). Fifteen
|
|
232
|
+
// fields, one seam — this section never learns what a track is.
|
|
233
|
+
return writeScene(
|
|
234
|
+
doc,
|
|
235
|
+
sceneId,
|
|
236
|
+
(scene) => {
|
|
237
|
+
const current = ((scene.effects as Record<string, Record<string, unknown>>)[edit.key] ??
|
|
238
|
+
DEFAULTS[edit.key]) as Record<string, unknown>;
|
|
239
|
+
let value: unknown = edit.value;
|
|
240
|
+
if (edit.index === undefined) {
|
|
241
|
+
if (current[edit.field] === edit.value) return scene;
|
|
242
|
+
} else {
|
|
243
|
+
// ONE ELEMENT OF A VECTOR. The others come from the vector AS PAINTED:
|
|
244
|
+
// a vec track keys the whole value, and the constant under a track is
|
|
245
|
+
// stale, so copying it would snap G and B back while R moves.
|
|
246
|
+
const painted = (resolveSceneTracksAt(scene, sceneFrame, doc.fps).effects as Record<string, Record<string, unknown> | undefined>)[edit.key]?.[edit.field];
|
|
247
|
+
const vec = [...((painted ?? current[edit.field]) as readonly number[])];
|
|
248
|
+
if (vec[edit.index] === edit.value) return scene;
|
|
249
|
+
vec[edit.index] = edit.value;
|
|
250
|
+
value = vec;
|
|
251
|
+
}
|
|
252
|
+
const effects = {
|
|
253
|
+
...scene.effects,
|
|
254
|
+
[edit.key]: { ...current, [edit.field]: value },
|
|
255
|
+
} as SceneEffects;
|
|
256
|
+
return { ...scene, effects };
|
|
257
|
+
},
|
|
258
|
+
sceneFrame,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** The three blurs of `Scene3D.effects`. */
|
|
263
|
+
export function BlurSection() {
|
|
264
|
+
return <EffectsSection stack="blur" />;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Every other pass of `Scene3D.effects`. */
|
|
268
|
+
export function PostprocessingSection() {
|
|
269
|
+
return <EffectsSection stack="postprocessing" />;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function EffectsSection({ stack }: { stack: StackId }) {
|
|
30
273
|
const store = useStore();
|
|
31
274
|
const doc = useShotEditor((s) => s.doc);
|
|
32
275
|
const sceneId = useShotEditor((s) => s.sceneId);
|
|
276
|
+
|
|
277
|
+
// ── THE DRAG HALF ─────────────────────────────────────────────────────
|
|
278
|
+
//
|
|
279
|
+
// DECLARED BEFORE THE EARLY RETURNS, because a hook after a conditional
|
|
280
|
+
// return changes the hook count between renders. ONE bracket for fifteen
|
|
281
|
+
// fields: the widget names the param it is moving, so a new effect costs a
|
|
282
|
+
// `dragOn(...)` spread rather than another hook.
|
|
283
|
+
//
|
|
284
|
+
// Without it these fields commit ONLY on release, so the author drags, sees
|
|
285
|
+
// nothing move on the stage, and concludes the control is dead. The scene is read from the
|
|
286
|
+
// store per capture, the same live read `useSelectionScrub` makes.
|
|
287
|
+
const drag = useScrub((d, raw) => {
|
|
288
|
+
const id = store.getState().sceneId;
|
|
289
|
+
// The frame LIVE at the capture and against the document BEING WRITTEN — a
|
|
290
|
+
// scrub re-derives from its base, so the shot starts here are the base's.
|
|
291
|
+
return id === null
|
|
292
|
+
? d
|
|
293
|
+
: patchEffect(d, id, raw as ParamEdit, containerFrame(d, id, store.getState().frame));
|
|
294
|
+
});
|
|
295
|
+
/** The three props a draggable param needs, bound to one field. */
|
|
296
|
+
const dragOn = (key: EffectKey, field: string, index?: number) => ({
|
|
297
|
+
onScrubStart: drag.start,
|
|
298
|
+
onScrub: (value: number) =>
|
|
299
|
+
drag.move((index === undefined ? { key, field, value } : { key, field, value, index }) satisfies ParamEdit),
|
|
300
|
+
onScrubEnd: drag.end,
|
|
301
|
+
});
|
|
302
|
+
// Same rule as the drag: above the early returns, or the hook count moves
|
|
303
|
+
// when the panel has no shot to describe.
|
|
304
|
+
const addAnchor = useRef<HTMLButtonElement>(null);
|
|
305
|
+
const [addOpen, setAddOpen] = useState(false);
|
|
306
|
+
// The add and remove buttons are this section's own, so they read a
|
|
307
|
+
// viewer's lock themselves: every field below reads it on its own.
|
|
308
|
+
const locked = useLocked();
|
|
309
|
+
// THE STACK AS PAINTED. Most of these params are keyframable, and the constant
|
|
310
|
+
// this used to read is pinned by `routeTrackedEdit` the moment one carries a
|
|
311
|
+
// track — so every animated effect field froze on the value it held when the
|
|
312
|
+
// first diamond was pressed, and dragging it appeared to do nothing at all.
|
|
313
|
+
// Display only: `patchEffect` still writes over the handed document.
|
|
314
|
+
const scene = useInspectorScene();
|
|
315
|
+
// A QUIET FIRST SCREEN (Paper 27, "8 · Empty inspector"): a stack with
|
|
316
|
+
// nothing in it opens collapsed, its header reading "None", so the empty
|
|
317
|
+
// inspector leads with the context's own section. Read once, on mount, and
|
|
318
|
+
// only when the author has never toggled this section (`persistKey`), so a
|
|
319
|
+
// choice they made is kept. Held here rather than inside `InspectorSection`
|
|
320
|
+
// so the add menu can open the section on the row it just created.
|
|
321
|
+
const [collapsed, flipCollapsed] = useCollapsed({
|
|
322
|
+
persistKey: STACKS[stack].persistKey,
|
|
323
|
+
defaultCollapsed: !EFFECT_KEYS.some(
|
|
324
|
+
(key) =>
|
|
325
|
+
(STACKS[stack].groups as readonly EffectGroup[]).includes(EFFECTS[key].group) &&
|
|
326
|
+
scene?.effects?.[key] !== undefined,
|
|
327
|
+
),
|
|
328
|
+
});
|
|
329
|
+
|
|
33
330
|
if (doc === null || sceneId === null) return null;
|
|
34
|
-
const effects =
|
|
331
|
+
const effects = scene?.effects;
|
|
35
332
|
if (effects === undefined) return null;
|
|
36
333
|
|
|
37
334
|
// Block body: `edit` now answers whether it ran, and this caller has no use
|
|
38
335
|
// for the answer — see SceneSection.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
336
|
+
//
|
|
337
|
+
// THE TOGGLE GOES THROUGH `writeScene` TOO, and that is not symmetry for its
|
|
338
|
+
// own sake: switching an effect OFF deletes its key, and a track still
|
|
339
|
+
// pointing at `effects.bloom.strength` is one `sceneTrackRefusal` refuses —
|
|
340
|
+
// so the chrome would autosave a document its own gate rejects. The router's
|
|
341
|
+
// removed-property branch drops those tracks with the effect.
|
|
342
|
+
//
|
|
343
|
+
// THE ONE ON/OFF SEAM, and both new controls are it: the menu item calls it
|
|
344
|
+
// with `true`, the row's remove button with `false`. Neither knows about
|
|
345
|
+
// tracks, which is exactly what stops the remove button growing its own,
|
|
346
|
+
// subtly different, teardown. It edits the HANDED scene's effects: the
|
|
347
|
+
// painted stack above holds sampled values, and writing it back would key
|
|
348
|
+
// every other animated effect field at the playhead.
|
|
46
349
|
const toggle = (key: EffectKey, on: boolean): void => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
350
|
+
store.edit((d) =>
|
|
351
|
+
writeScene(
|
|
352
|
+
d,
|
|
353
|
+
sceneId,
|
|
354
|
+
(scene) => {
|
|
355
|
+
const next: Record<string, unknown> = { ...scene.effects };
|
|
356
|
+
if (on) next[key] = DEFAULTS[key];
|
|
357
|
+
else delete next[key];
|
|
358
|
+
return { ...scene, effects: next as SceneEffects };
|
|
359
|
+
},
|
|
360
|
+
containerFrame(d, sceneId, store.getState().frame),
|
|
361
|
+
),
|
|
362
|
+
);
|
|
51
363
|
};
|
|
52
364
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
365
|
+
// Through the SAME pure op the drag captures with, so typing 0.4 and dragging
|
|
366
|
+
// to 0.4 cannot land different documents.
|
|
367
|
+
const set = (key: EffectKey, field: string, value: number, index?: number): void => {
|
|
368
|
+
const edit: ParamEdit = index === undefined ? { key, field, value } : { key, field, value, index };
|
|
369
|
+
store.edit((d) => patchEffect(d, sceneId, edit, containerFrame(d, sceneId, store.getState().frame)));
|
|
56
370
|
};
|
|
57
371
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<NumberField value={
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
372
|
+
/** A vec2 field as ONE labelled box holding X and Y, with the one diamond
|
|
373
|
+
* its vec2 track row (`effects.<key>.<field>`) has: one track keys both
|
|
374
|
+
* axes, so the box that shows both carries it once, and its tooltip says
|
|
375
|
+
* X and Y key together. */
|
|
376
|
+
const xy = (key: EffectKey, field: string, vec: readonly number[], name: string, what: string, range: { min: number; max: number; step: number }) => (
|
|
377
|
+
<VectorParam name={name} path={`effects.${key}.${field}`} parts={XY}>
|
|
378
|
+
{XY.map((axis, i) => (
|
|
379
|
+
<NumberField key={axis} value={vec[i]!} label={axis} ariaLabel={`${name} ${axis}, ${what}`} {...range} {...dragOn(key, field, i)} onCommit={(v) => set(key, field, v, i)} />
|
|
380
|
+
))}
|
|
381
|
+
</VectorParam>
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
/** One CDL triple of Grade as ONE labelled box holding R, G and B, with the
|
|
385
|
+
* one diamond its vec3 track row (`effects.grade.<field>`) has, whose
|
|
386
|
+
* tooltip says R, G and B key together. */
|
|
387
|
+
const rgb = (field: "offset" | "power" | "slope", name: string, range: { min: number; max: number; step: number }) => (
|
|
388
|
+
<VectorParam name={name} path={`effects.grade.${field}`} keyLabel={name} parts={RGB}>
|
|
389
|
+
{RGB.map((channel, i) => (
|
|
390
|
+
<NumberField key={channel} value={(effects.grade?.[field] ?? DEFAULTS.grade[field])[i]!} label={channel} ariaLabel={`${name} ${channel}, grade ${name.toLowerCase()}`} {...range} {...dragOn("grade", field, i)} onCommit={(v) => set("grade", field, v, i)} />
|
|
391
|
+
))}
|
|
392
|
+
</VectorParam>
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The params of every effect, keyed the same way the rest of this file is.
|
|
397
|
+
*
|
|
398
|
+
* A record and not seventeen components: each entry closes over `effects`,
|
|
399
|
+
* `dragOn` and `set`, so a component per effect would be seventeen prop lists
|
|
400
|
+
* carrying the same three things. Building all seventeen fragments per render
|
|
401
|
+
* is strictly less work than the sixteen mounted rows this replaced, and only
|
|
402
|
+
* the enabled ones are ever handed to React.
|
|
403
|
+
*/
|
|
404
|
+
const params: Record<EffectKey, ReactNode> = {
|
|
405
|
+
dof: (
|
|
406
|
+
<>
|
|
407
|
+
{/* An f-number keeps its fine step: the range is wide but the values
|
|
408
|
+
authors reach for are a third of a stop apart, and Shift (×10) is
|
|
409
|
+
the gear that crosses the whole scale.
|
|
410
|
+
|
|
411
|
+
BOTH DOF FLOORS STAY LOCAL AND STRICTER THAN THE TABLE'S. Its rule
|
|
412
|
+
is `> 0` exclusive and `NumberField` has no exclusive bound, so a
|
|
413
|
+
field taking `min` from it could reach the one value the track
|
|
414
|
+
refuses, which `routeTrackedEdit` would then drop in silence. */}
|
|
415
|
+
<Param><NumberField value={effects.dof?.fStop ?? DEFAULTS.dof.fStop} label="f/" ariaLabel="f/, aperture" title="Animating this costs one uniform write per LAYER per frame: depth of field is a per-layer circle of confusion, not a post pass." min={0.7} max={32} step={0.1} {...dragOn("dof", "fStop")} onCommit={(v) => set("dof", "fStop", v)} trailing={<SceneKeyframeDot path="effects.dof.fStop" />} /></Param>
|
|
416
|
+
{/* ── THE FLOOR IS 0.1, AND 0 IS NOT "NO BLUR" ────────────────────
|
|
417
|
+
`layerLod` reads a non-positive `bokehScale` (and a non-positive
|
|
418
|
+
`fStop`) as 1, so typing 0 here did not remove the blur — it set
|
|
419
|
+
the amount to 1, which is MORE blur than 0.5. The control said
|
|
420
|
+
otherwise, and once this field is keyframable the same lie becomes
|
|
421
|
+
"animate it to zero", which reads as a defocus pull that lands on
|
|
422
|
+
the wrong value. The minimum now matches the renderer's, and the
|
|
423
|
+
title says what to reach for instead. */}
|
|
424
|
+
<Param><NumberField value={effects.dof?.bokehScale ?? DEFAULTS.dof.bokehScale} label="Amount" ariaLabel="Amount, bokeh scale" title="0 is not “no blur”: the renderer reads any value at or below zero as 1. Switch depth of field off instead." min={0.1} max={20} step={0.5} {...dragOn("dof", "bokehScale")} onCommit={(v) => set("dof", "bokehScale", v)} trailing={<SceneKeyframeDot path="effects.dof.bokehScale" />} /></Param>
|
|
425
|
+
</>
|
|
426
|
+
),
|
|
427
|
+
bloom: (
|
|
428
|
+
<>
|
|
429
|
+
<Param><NumberField value={effects.bloom?.strength ?? DEFAULTS.bloom.strength} label="Amount" ariaLabel="Amount, bloom strength" min={0} max={5} step={0.1} {...dragOn("bloom", "strength")} onCommit={(v) => set("bloom", "strength", v)} trailing={<SceneKeyframeDot path="effects.bloom.strength" />} /></Param>
|
|
430
|
+
<Param><NumberField value={effects.bloom?.threshold ?? DEFAULTS.bloom.threshold} label="Cut" ariaLabel="Cut, bloom threshold" min={0} max={2} step={0.05} {...dragOn("bloom", "threshold")} onCommit={(v) => set("bloom", "threshold", v)} trailing={<SceneKeyframeDot path="effects.bloom.threshold" />} /></Param>
|
|
431
|
+
<Param><NumberField value={effects.bloom?.radius ?? DEFAULTS.bloom.radius} label="Radius" ariaLabel="Radius, bloom radius" min={0} max={1} step={0.05} {...dragOn("bloom", "radius")} onCommit={(v) => set("bloom", "radius", v)} trailing={<SceneKeyframeDot path="effects.bloom.radius" />} /></Param>
|
|
432
|
+
</>
|
|
433
|
+
),
|
|
434
|
+
vignette: (
|
|
435
|
+
<>
|
|
436
|
+
<Param><NumberField value={effects.vignette?.amount ?? DEFAULTS.vignette.amount} label="Amount" ariaLabel="Amount, vignette amount" min={0} max={1} step={0.05} {...dragOn("vignette", "amount")} onCommit={(v) => set("vignette", "amount", v)} trailing={<SceneKeyframeDot path="effects.vignette.amount" />} /></Param>
|
|
437
|
+
<Param><NumberField value={effects.vignette?.radius ?? DEFAULTS.vignette.radius} label="Size" ariaLabel="Size, vignette radius" min={0} max={1.5} step={0.05} {...dragOn("vignette", "radius")} onCommit={(v) => set("vignette", "radius", v)} trailing={<SceneKeyframeDot path="effects.vignette.radius" />} /></Param>
|
|
438
|
+
<Param><NumberField value={effects.vignette?.softness ?? DEFAULTS.vignette.softness} label="Soft" ariaLabel="Soft, vignette softness" {...hardOf("effects.vignette.softness")} max={1} step={0.05} {...dragOn("vignette", "softness")} onCommit={(v) => set("vignette", "softness", v)} trailing={<SceneKeyframeDot path="effects.vignette.softness" />} /></Param>
|
|
439
|
+
</>
|
|
440
|
+
),
|
|
441
|
+
grain: (
|
|
442
|
+
<>
|
|
443
|
+
<Param><NumberField value={effects.grain?.amount ?? DEFAULTS.grain.amount} label="Amount" ariaLabel="Amount, grain amount" min={0} max={1} step={0.05} {...dragOn("grain", "amount")} onCommit={(v) => set("grain", "amount", v)} trailing={<SceneKeyframeDot path="effects.grain.amount" />} /></Param>
|
|
444
|
+
{/* The 1px floor is the renderer's and comes from the table; 16 is this
|
|
445
|
+
section's own ceiling on a scale with no upper limit. */}
|
|
446
|
+
<Param><NumberField value={effects.grain?.size ?? DEFAULTS.grain.size} label="Size" ariaLabel="Size, grain size" {...hardOf("effects.grain.size")} max={16} step={0.5} {...dragOn("grain", "size")} onCommit={(v) => set("grain", "size", v)} trailing={<SceneKeyframeDot path="effects.grain.size" />} /></Param>
|
|
447
|
+
</>
|
|
448
|
+
),
|
|
449
|
+
colorSplit: (
|
|
450
|
+
<>
|
|
451
|
+
<Param><NumberField value={effects.colorSplit?.strength ?? DEFAULTS.colorSplit.strength} label="Amount" ariaLabel="Amount, colour split strength" min={0} max={20} step={0.5} {...dragOn("colorSplit", "strength")} onCommit={(v) => set("colorSplit", "strength", v)} trailing={<SceneKeyframeDot path="effects.colorSplit.strength" />} /></Param>
|
|
452
|
+
<Param><NumberField value={effects.colorSplit?.scale ?? DEFAULTS.colorSplit.scale} label="Scale" ariaLabel="Scale, colour split scale" min={0.5} max={2} step={0.01} {...dragOn("colorSplit", "scale")} onCommit={(v) => set("colorSplit", "scale", v)} trailing={<SceneKeyframeDot path="effects.colorSplit.scale" />} /></Param>
|
|
453
|
+
</>
|
|
454
|
+
),
|
|
455
|
+
ghost: (
|
|
456
|
+
<>
|
|
457
|
+
<Param><NumberField value={effects.ghost?.amount ?? DEFAULTS.ghost.amount} label="Amount" ariaLabel="Amount, ghost amount" min={0} max={1} step={0.05} {...dragOn("ghost", "amount")} onCommit={(v) => set("ghost", "amount", v)} trailing={<SceneKeyframeDot path="effects.ghost.amount" />} /></Param>
|
|
81
458
|
{/* A LOOP BOUND, not a uniform — changing it recompiles the chain, which
|
|
82
459
|
is exactly why `postSignature` carries it and the other params do
|
|
83
|
-
not.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
460
|
+
not. Not trackable, so there is no table row to read limits from:
|
|
461
|
+
`ghostCount` clamps to 1..8 and these repeat it. */}
|
|
462
|
+
<Param><NumberField value={effects.ghost?.count ?? DEFAULTS.ghost.count} label="Copies" ariaLabel="Copies, ghost count" min={1} max={8} step={1} {...dragOn("ghost", "count")} onCommit={(v) => set("ghost", "count", v)} /></Param>
|
|
463
|
+
{/* The march between copies, in uv. */}
|
|
464
|
+
{xy("ghost", "offset", effects.ghost?.offset ?? DEFAULTS.ghost.offset, "Shift", "ghost offset", { min: -0.1, max: 0.1, step: 0.001 })}
|
|
465
|
+
</>
|
|
466
|
+
),
|
|
467
|
+
edgeFade: (
|
|
468
|
+
<>
|
|
469
|
+
<Param><NumberField value={effects.edgeFade?.size ?? DEFAULTS.edgeFade.size} label="Size" ariaLabel="Size, edge fade size" min={0} max={0.5} step={0.02} {...dragOn("edgeFade", "size")} onCommit={(v) => set("edgeFade", "size", v)} trailing={<SceneKeyframeDot path="effects.edgeFade.size" />} /></Param>
|
|
470
|
+
<Param><NumberField value={effects.edgeFade?.softness ?? DEFAULTS.edgeFade.softness} label="Soft" ariaLabel="Soft, edge fade softness" {...hardOf("effects.edgeFade.softness")} max={0.5} step={0.02} {...dragOn("edgeFade", "softness")} onCommit={(v) => set("edgeFade", "softness", v)} trailing={<SceneKeyframeDot path="effects.edgeFade.softness" />} /></Param>
|
|
471
|
+
</>
|
|
472
|
+
),
|
|
473
|
+
grade: (
|
|
474
|
+
// ASC CDL: offset is Lift, power is Gamma, slope is Gain.
|
|
475
|
+
<>
|
|
476
|
+
<Param><NumberField value={effects.grade?.saturation ?? DEFAULTS.grade.saturation} label="Sat" ariaLabel="Sat, grade saturation" min={0} max={3} step={0.05} {...dragOn("grade", "saturation")} onCommit={(v) => set("grade", "saturation", v)} trailing={<SceneKeyframeDot path="effects.grade.saturation" />} /></Param>
|
|
477
|
+
{rgb("offset", "Lift", { min: -1, max: 1, step: 0.01 })}
|
|
478
|
+
{rgb("power", "Gamma", { min: 0.1, max: 4, step: 0.05 })}
|
|
479
|
+
{rgb("slope", "Gain", { min: 0, max: 4, step: 0.05 })}
|
|
480
|
+
</>
|
|
481
|
+
),
|
|
482
|
+
sharpen: (
|
|
483
|
+
// 0..1 the right way round, and both ends are the renderer's `clamp01`
|
|
484
|
+
// rather than a taste range, so they come from the table. The node
|
|
485
|
+
// underneath reads 0 as MAXIMUM sharpening and 2 as none; post.ts's
|
|
486
|
+
// `write` inverts it, so this control is the only place the author's
|
|
487
|
+
// direction is stated.
|
|
488
|
+
<Param><NumberField value={effects.sharpen?.amount ?? DEFAULTS.sharpen.amount} label="Amount" ariaLabel="Amount, sharpen amount" {...hardOf("effects.sharpen.amount")} step={0.05} {...dragOn("sharpen", "amount")} onCommit={(v) => set("sharpen", "amount", v)} trailing={<SceneKeyframeDot path="effects.sharpen.amount" />} /></Param>
|
|
489
|
+
),
|
|
490
|
+
bleach: (
|
|
491
|
+
<Param><NumberField value={effects.bleach?.amount ?? DEFAULTS.bleach.amount} label="Amount" ariaLabel="Amount, bleach amount" min={0} max={1} step={0.05} {...dragOn("bleach", "amount")} onCommit={(v) => set("bleach", "amount", v)} trailing={<SceneKeyframeDot path="effects.bleach.amount" />} /></Param>
|
|
492
|
+
),
|
|
493
|
+
tiltShift: (
|
|
494
|
+
<>
|
|
495
|
+
<Param><NumberField value={effects.tiltShift?.amount ?? DEFAULTS.tiltShift.amount} label="Amount" ariaLabel="Amount, tilt shift amount" min={0} max={1} step={0.05} {...dragOn("tiltShift", "amount")} onCommit={(v) => set("tiltShift", "amount", v)} trailing={<SceneKeyframeDot path="effects.tiltShift.amount" />} /></Param>
|
|
496
|
+
{/* Fractions of the frame HEIGHT — 0 is the top edge, 1 the bottom —
|
|
497
|
+
so a focus band means the same thing at any export resolution. */}
|
|
498
|
+
<Param><NumberField value={effects.tiltShift?.center ?? DEFAULTS.tiltShift.center} label="Focus" ariaLabel="Focus, focus band centre" min={0} max={1} step={0.02} {...dragOn("tiltShift", "center")} onCommit={(v) => set("tiltShift", "center", v)} trailing={<SceneKeyframeDot path="effects.tiltShift.center" />} /></Param>
|
|
499
|
+
{/* The 0 floor is the renderer's; a band wider than the frame still
|
|
500
|
+
draws, so the 1 stays this section's. */}
|
|
501
|
+
<Param><NumberField value={effects.tiltShift?.width ?? DEFAULTS.tiltShift.width} label="Width" ariaLabel="Width, focus band width" {...hardOf("effects.tiltShift.width")} max={1} step={0.02} {...dragOn("tiltShift", "width")} onCommit={(v) => set("tiltShift", "width", v)} trailing={<SceneKeyframeDot path="effects.tiltShift.width" />} /></Param>
|
|
502
|
+
<Param><NumberField value={effects.tiltShift?.softness ?? DEFAULTS.tiltShift.softness} label="Soft" ariaLabel="Soft, focus band softness" {...hardOf("effects.tiltShift.softness")} max={1} step={0.02} {...dragOn("tiltShift", "softness")} onCommit={(v) => set("tiltShift", "softness", v)} trailing={<SceneKeyframeDot path="effects.tiltShift.softness" />} /></Param>
|
|
503
|
+
</>
|
|
504
|
+
),
|
|
505
|
+
rays: (
|
|
506
|
+
<>
|
|
507
|
+
<Param><NumberField value={effects.rays?.strength ?? DEFAULTS.rays.strength} label="Amount" ariaLabel="Amount, light ray strength" min={0} max={3} step={0.1} {...dragOn("rays", "strength")} onCommit={(v) => set("rays", "strength", v)} trailing={<SceneKeyframeDot path="effects.rays.strength" />} /></Param>
|
|
508
|
+
{/* Only light ABOVE this streaks. At 0 the whole frame smears toward
|
|
509
|
+
the centre, which reads as a zoom blur rather than as shafts. */}
|
|
510
|
+
<Param><NumberField value={effects.rays?.threshold ?? DEFAULTS.rays.threshold} label="Cut" ariaLabel="Cut, light ray threshold" min={0} max={2} step={0.05} {...dragOn("rays", "threshold")} onCommit={(v) => set("rays", "threshold", v)} trailing={<SceneKeyframeDot path="effects.rays.threshold" />} /></Param>
|
|
511
|
+
{/* Where the shafts radiate from, in screen uv: 0,0 is the top left. */}
|
|
512
|
+
{xy("rays", "center", effects.rays?.center ?? DEFAULTS.rays.center, "Centre", "light ray centre", { min: 0, max: 1, step: 0.02 })}
|
|
513
|
+
</>
|
|
514
|
+
),
|
|
515
|
+
pixelate: (
|
|
516
|
+
// SCREEN pixels per cell, like `grain.size`, so the mosaic does not get
|
|
517
|
+
// four times finer when the export goes from 1080p to 4K. Same split as
|
|
518
|
+
// grain: the 1px floor is the renderer's, the 64 is this section's.
|
|
519
|
+
<Param><NumberField value={effects.pixelate?.size ?? DEFAULTS.pixelate.size} label="Cell" ariaLabel="Cell, pixelate cell size" {...hardOf("effects.pixelate.size")} max={64} step={1} {...dragOn("pixelate", "size")} onCommit={(v) => set("pixelate", "size", v)} trailing={<SceneKeyframeDot path="effects.pixelate.size" />} /></Param>
|
|
520
|
+
),
|
|
521
|
+
glitch: (
|
|
522
|
+
<>
|
|
523
|
+
<Param><NumberField value={effects.glitch?.strength ?? DEFAULTS.glitch.strength} label="Tear" ariaLabel="Tear, glitch strength" min={0} max={1} step={0.02} {...dragOn("glitch", "strength")} onCommit={(v) => set("glitch", "strength", v)} trailing={<SceneKeyframeDot path="effects.glitch.strength" />} /></Param>
|
|
524
|
+
{/* The fraction of bands that tear on any given frame, so `clamp01` in
|
|
525
|
+
`write` is the real limit and the table carries it. */}
|
|
526
|
+
<Param><NumberField value={effects.glitch?.density ?? DEFAULTS.glitch.density} label="Bands" ariaLabel="Bands, glitch density" {...hardOf("effects.glitch.density")} step={0.05} {...dragOn("glitch", "density")} onCommit={(v) => set("glitch", "density", v)} trailing={<SceneKeyframeDot path="effects.glitch.density" />} /></Param>
|
|
527
|
+
<Param><NumberField value={effects.glitch?.split ?? DEFAULTS.glitch.split} label="Split" ariaLabel="Split, glitch channel split" min={0} max={32} step={1} {...dragOn("glitch", "split")} onCommit={(v) => set("glitch", "split", v)} /></Param>
|
|
528
|
+
</>
|
|
529
|
+
),
|
|
530
|
+
crt: (
|
|
531
|
+
<>
|
|
532
|
+
{/* Capped below 0.5 in `write`: past that the barrel's own corner
|
|
533
|
+
compensation goes non-positive and the picture inverts through
|
|
534
|
+
itself. The table says 0.49 so neither this control nor a
|
|
535
|
+
`set_keyframe` can reach it. */}
|
|
536
|
+
<Param><NumberField value={effects.crt?.curvature ?? DEFAULTS.crt.curvature} label="Curve" ariaLabel="Curve, CRT curvature" {...hardOf("effects.crt.curvature")} step={0.01} {...dragOn("crt", "curvature")} onCommit={(v) => set("crt", "curvature", v)} trailing={<SceneKeyframeDot path="effects.crt.curvature" />} /></Param>
|
|
537
|
+
<Param><NumberField value={effects.crt?.scanlines ?? DEFAULTS.crt.scanlines} label="Lines" ariaLabel="Lines, CRT scanline intensity" {...hardOf("effects.crt.scanlines")} step={0.05} {...dragOn("crt", "scanlines")} onCommit={(v) => set("crt", "scanlines", v)} trailing={<SceneKeyframeDot path="effects.crt.scanlines" />} /></Param>
|
|
538
|
+
<Param><NumberField value={effects.crt?.lineHeight ?? DEFAULTS.crt.lineHeight} label="Pitch" ariaLabel="Pitch, CRT scanline pitch" min={1} max={16} step={0.5} {...dragOn("crt", "lineHeight")} onCommit={(v) => set("crt", "lineHeight", v)} /></Param>
|
|
539
|
+
</>
|
|
540
|
+
),
|
|
541
|
+
fisheye: (
|
|
542
|
+
<>
|
|
543
|
+
{/* Both 0..1 from the table. Strength is the lens's curvature (1 is the
|
|
544
|
+
strong measured lens), so keying it 0 -> 1 -> 0 is a punch-through;
|
|
545
|
+
the vignette is scaled by strength, so it arrives with the lens. */}
|
|
546
|
+
<Param><NumberField value={effects.fisheye?.strength ?? DEFAULTS.fisheye.strength} label="Amount" ariaLabel="Amount, fisheye strength" {...hardOf("effects.fisheye.strength")} step={0.05} {...dragOn("fisheye", "strength")} onCommit={(v) => set("fisheye", "strength", v)} trailing={<SceneKeyframeDot path="effects.fisheye.strength" />} /></Param>
|
|
547
|
+
<Param><NumberField value={effects.fisheye?.vignette ?? DEFAULTS.fisheye.vignette} label="Shade" ariaLabel="Shade, fisheye vignette" {...hardOf("effects.fisheye.vignette")} step={0.05} {...dragOn("fisheye", "vignette")} onCommit={(v) => set("fisheye", "vignette", v)} trailing={<SceneKeyframeDot path="effects.fisheye.vignette" />} /></Param>
|
|
548
|
+
</>
|
|
549
|
+
),
|
|
550
|
+
motionBlur: (
|
|
551
|
+
<>
|
|
93
552
|
{/* NOT A SHADER PASS. This feeds the export layer's sub-frame
|
|
94
553
|
accumulation, which is the only formulation compatible with
|
|
95
554
|
out-of-order rendering — see SceneEffects in types.ts. It therefore
|
|
96
|
-
shows in an EXPORT and not in the preview.
|
|
97
|
-
|
|
98
|
-
<NumberField value={effects.motionBlur?.
|
|
99
|
-
|
|
555
|
+
shows in an EXPORT and not in the preview. Not trackable either, so
|
|
556
|
+
these limits are the section's alone. */}
|
|
557
|
+
<Param><NumberField value={effects.motionBlur?.shutterDeg ?? DEFAULTS.motionBlur.shutterDeg} label="Shutter" ariaLabel="Shutter, shutter angle" unit="°" min={0} max={360} step={10} {...dragOn("motionBlur", "shutterDeg")} onCommit={(v) => set("motionBlur", "shutterDeg", v)} /></Param>
|
|
558
|
+
<Param><NumberField value={effects.motionBlur?.samples ?? DEFAULTS.motionBlur.samples} label="Samples" ariaLabel="Samples, motion blur samples" min={2} max={32} step={1} {...dragOn("motionBlur", "samples")} onCommit={(v) => set("motionBlur", "samples", v)} /></Param>
|
|
559
|
+
</>
|
|
560
|
+
),
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
const own = STACKS[stack];
|
|
564
|
+
const groups: readonly EffectGroup[] = own.groups;
|
|
565
|
+
// Motion blur renders only in a studio server export, and the hosted editor
|
|
566
|
+
// has no server, so there it is not offered at all.
|
|
567
|
+
const hosted = dailiesHost() !== undefined;
|
|
568
|
+
const mine = EFFECT_KEYS.filter(
|
|
569
|
+
(key) => groups.includes(EFFECTS[key].group) && !(hosted && key === "motionBlur"),
|
|
570
|
+
);
|
|
571
|
+
const on = mine.filter((key) => effects[key] !== undefined);
|
|
572
|
+
const off = mine.filter((key) => effects[key] === undefined);
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* THE ADD BUTTON IS PASSED AS `action`, NOT AS `onAdd`.
|
|
576
|
+
*
|
|
577
|
+
* `onAdd` renders its own button, and the Popover needs a ref to the element
|
|
578
|
+
* it anchors to. Passing the node is the only way to hold one. It wears the
|
|
579
|
+
* same classes `onAdd` would, so every section's action looks alike, and it
|
|
580
|
+
* keeps `onAdd`'s lock rule by hand: a viewer's panel adds nothing.
|
|
581
|
+
*/
|
|
582
|
+
const addButton = (
|
|
583
|
+
<button
|
|
584
|
+
ref={addAnchor}
|
|
585
|
+
type="button"
|
|
586
|
+
className="btn is-icon insp-section-action"
|
|
587
|
+
data-testid={own.addTestId}
|
|
588
|
+
aria-haspopup="true"
|
|
589
|
+
aria-expanded={addOpen}
|
|
590
|
+
aria-label={own.addLabel}
|
|
591
|
+
title={own.addTitle}
|
|
592
|
+
disabled={locked}
|
|
593
|
+
onClick={(e) => {
|
|
594
|
+
e.stopPropagation();
|
|
595
|
+
if (!locked) setAddOpen((was) => !was);
|
|
596
|
+
}}
|
|
597
|
+
>
|
|
598
|
+
<IconPlus />
|
|
599
|
+
</button>
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
/* Popover and not an inline block: the panel scrolls and clips, and a menu
|
|
603
|
+
that has to fit inside the section is a menu that scrolls the author away
|
|
604
|
+
from the row they opened it from.
|
|
605
|
+
|
|
606
|
+
IT RIDES IN THE `action` SLOT, BESIDE THE BUTTON, NOT IN THE CHILDREN.
|
|
607
|
+
`InspectorSection` renders its children only while expanded, and the
|
|
608
|
+
collapse bit persists to localStorage. As a child the menu was therefore
|
|
609
|
+
unreachable for anyone who had ever collapsed the section, while the
|
|
610
|
+
button that opens it kept rendering in the always-live headrow: a "+" that
|
|
611
|
+
flipped `aria-expanded` and showed nothing. Since the sixteen toggles are
|
|
612
|
+
gone, that is the only way to add an effect. The Popover portals to the document
|
|
613
|
+
body either way, so nothing about where it lands changes. */
|
|
614
|
+
const addMenu = (
|
|
615
|
+
<Popover open={addOpen} anchor={addAnchor} onClose={() => setAddOpen(false)} align="right">
|
|
616
|
+
<div className="effect-add-menu">
|
|
617
|
+
{groups.map((group) => {
|
|
618
|
+
const items = off.filter((key) => EFFECTS[key].group === group);
|
|
619
|
+
// A heading with nothing under it is a group the author has already
|
|
620
|
+
// used up, and it reads as a menu that lost its items.
|
|
621
|
+
if (items.length === 0) return null;
|
|
622
|
+
return (
|
|
623
|
+
<Fragment key={group}>
|
|
624
|
+
{/* A lone group's heading only repeats the section title the
|
|
625
|
+
menu was opened from ("Blur" under Blur), so it is dropped. */}
|
|
626
|
+
{groups.length > 1 ? <div className="effect-add-group">{group}</div> : null}
|
|
627
|
+
{items.map((key) => (
|
|
628
|
+
<button
|
|
629
|
+
key={key}
|
|
630
|
+
type="button"
|
|
631
|
+
className="effect-add-item"
|
|
632
|
+
// EXACTLY THE LABEL. This is the accessible name the
|
|
633
|
+
// inspector tests switch an effect on by, and the name a
|
|
634
|
+
// screen reader reads is the name on the row it creates.
|
|
635
|
+
aria-label={EFFECTS[key].label}
|
|
636
|
+
onClick={() => {
|
|
637
|
+
setAddOpen(false);
|
|
638
|
+
toggle(key, true);
|
|
639
|
+
// The row it adds is drawn only while the section is open.
|
|
640
|
+
if (collapsed) flipCollapsed();
|
|
641
|
+
}}
|
|
642
|
+
>
|
|
643
|
+
{EFFECTS[key].label}
|
|
644
|
+
</button>
|
|
645
|
+
))}
|
|
646
|
+
</Fragment>
|
|
647
|
+
);
|
|
648
|
+
})}
|
|
649
|
+
</div>
|
|
650
|
+
</Popover>
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
return (
|
|
654
|
+
<InspectorSection
|
|
655
|
+
title={own.title}
|
|
656
|
+
icon={SECTION_ICONS[stack]}
|
|
657
|
+
collapsed={collapsed}
|
|
658
|
+
onCollapsedChange={flipCollapsed}
|
|
659
|
+
// Shown only while collapsed. An empty stack says so in one word; a
|
|
660
|
+
// stack with passes in it names them ("Bloom, Cut").
|
|
661
|
+
summary={on.length === 0 ? "None" : on.map((id) => EFFECTS[id].label).join(", ")}
|
|
662
|
+
// The stack's own classes, which tests and the add menu address it by.
|
|
663
|
+
// No section scope: its rows are the house's (widgets.css).
|
|
664
|
+
className={own.className}
|
|
665
|
+
action={
|
|
666
|
+
<>
|
|
667
|
+
{addButton}
|
|
668
|
+
{addMenu}
|
|
669
|
+
</>
|
|
670
|
+
}
|
|
671
|
+
>
|
|
672
|
+
{on.length === 0 ? (
|
|
673
|
+
<p className="insp-empty">{own.empty}</p>
|
|
674
|
+
) : (
|
|
675
|
+
on.map((key) => (
|
|
676
|
+
<div key={key} className="effect-row" data-effect={EFFECTS[key].label}>
|
|
677
|
+
<div className="effect-head">
|
|
678
|
+
<span className="effect-head-label">{EFFECTS[key].label}</span>
|
|
679
|
+
{/* The SAME seam the old toggle's "off" position ran through, so
|
|
680
|
+
the tracks that point into this effect are still dropped with
|
|
681
|
+
it. A remove button with its own delete would be a second
|
|
682
|
+
teardown to keep in step with `sceneTrackRefusal`.
|
|
683
|
+
|
|
684
|
+
The house in-row verb (`.insp-key`): a 16px icon button at full
|
|
685
|
+
strength in the third ink, turning `--danger` only under the
|
|
686
|
+
pointer. It rested at 0.28 opacity, and removing an effect is
|
|
687
|
+
reachable nowhere else, so a quiet control there was nearly a
|
|
688
|
+
missing one. */}
|
|
689
|
+
<button
|
|
690
|
+
type="button"
|
|
691
|
+
className="insp-key is-danger"
|
|
692
|
+
aria-label={`Remove ${EFFECTS[key].label}`}
|
|
693
|
+
title="Switch off and drop any keyframes on its params"
|
|
694
|
+
disabled={locked}
|
|
695
|
+
onClick={() => toggle(key, false)}
|
|
696
|
+
>
|
|
697
|
+
<IconClose />
|
|
698
|
+
</button>
|
|
699
|
+
</div>
|
|
700
|
+
{/* Every param keeps its word INSIDE its box whatever the panel's
|
|
701
|
+
field-label preference. The words are words already ("Amount",
|
|
702
|
+
"Cut", "Soft"), the slots are half a row wide, and a vector box
|
|
703
|
+
holds two or three fields with no room for a name above each.
|
|
704
|
+
Named mode prints a field's accessible name above it, and these
|
|
705
|
+
names start with the visible word ("Amount, bloom strength"),
|
|
706
|
+
so it would print that whole phrase over every box. */}
|
|
707
|
+
<FieldLabels value="compact">
|
|
708
|
+
<div className="effect-params">{params[key]}</div>
|
|
709
|
+
</FieldLabels>
|
|
710
|
+
</div>
|
|
711
|
+
))
|
|
712
|
+
)}
|
|
100
713
|
</InspectorSection>
|
|
101
714
|
);
|
|
102
715
|
}
|
|
103
716
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
717
|
+
/**
|
|
718
|
+
* One scalar param's slot in the two-up wrap.
|
|
719
|
+
*
|
|
720
|
+
* Its keyframe diamond (P5) is INSIDE the field, as the field's `trailing`, so
|
|
721
|
+
* a param the scene table refuses (`ghost.count`, a loop bound the compiled
|
|
722
|
+
* chain is keyed on, and the two motion-blur fields, an export-time
|
|
723
|
+
* accumulation and not a uniform at all) is exactly as wide as one it keys:
|
|
724
|
+
* `.effect-params` wraps two per line, and a field narrowed by a diamond beside
|
|
725
|
+
* it would stop its column lining up with the pairs below.
|
|
726
|
+
*
|
|
727
|
+
* `SceneKeyframeDot` hides itself when the effect is off, which is also when
|
|
728
|
+
* this whole row is unmounted, so the two agree without either asking the
|
|
729
|
+
* other.
|
|
730
|
+
*
|
|
731
|
+
* The slot is a property row (`.insp-row`), half the width of one, so its
|
|
732
|
+
* field takes the inspector's type scale (ui widgets.css sets it inside rows
|
|
733
|
+
* only) like every other field in the panel.
|
|
734
|
+
*/
|
|
735
|
+
function Param({ children }: { children: ReactNode }) {
|
|
736
|
+
return <div className="effect-param insp-row">{children}</div>;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* A vector param (a ghost's shift, a ray centre, Grade's Lift, Gamma and Gain)
|
|
741
|
+
* as ONE labelled box across the whole line: its name, then the components,
|
|
742
|
+
* then the one diamond for the vector, because one track keys every
|
|
743
|
+
* component, and its tooltip names the components that key together. The
|
|
744
|
+
* components carry their one-letter prefixes inside the box, under the
|
|
745
|
+
* params' compact field labels (see the effect rows).
|
|
746
|
+
*/
|
|
747
|
+
function VectorParam({
|
|
748
|
+
name,
|
|
749
|
+
path,
|
|
750
|
+
keyLabel,
|
|
751
|
+
parts,
|
|
108
752
|
children,
|
|
109
753
|
}: {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
754
|
+
name: string;
|
|
755
|
+
path: string;
|
|
756
|
+
/** The diamond's word, where the table's differs from the row's (Grade's
|
|
757
|
+
* table says the CDL's Offset, Power and Slope; the row says Lift, Gamma
|
|
758
|
+
* and Gain). */
|
|
759
|
+
keyLabel?: string;
|
|
760
|
+
/** The components' prefixes, in box order: what keys together. */
|
|
761
|
+
parts: readonly string[];
|
|
113
762
|
children: ReactNode;
|
|
114
763
|
}) {
|
|
115
764
|
return (
|
|
116
|
-
<div className="effect-
|
|
117
|
-
<InspectorRow
|
|
118
|
-
|
|
765
|
+
<div className="effect-param is-vec">
|
|
766
|
+
<InspectorRow
|
|
767
|
+
label={name}
|
|
768
|
+
trailing={<SceneKeyframeDot path={path} parts={parts} {...(keyLabel === undefined ? {} : { label: keyLabel })} />}
|
|
769
|
+
>
|
|
770
|
+
<div className="insp-vec">{children}</div>
|
|
119
771
|
</InspectorRow>
|
|
120
|
-
{on ? <div className="effect-params">{children}</div> : null}
|
|
121
772
|
</div>
|
|
122
773
|
);
|
|
123
774
|
}
|