@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,240 @@
|
|
|
1
|
+
// ONE ITEM ON THE SKETCH PAPER (Paper SB3), drawn in the DOM at any zoom.
|
|
2
|
+
//
|
|
3
|
+
// Placed in PERCENTAGES of the paper (MiniSketch's `boxStyle`) and sized in
|
|
4
|
+
// `cqw` of it, so a zoom changes the paper's width and nothing here
|
|
5
|
+
// re-renders for it. Back to front as stored: each item is its own element in
|
|
6
|
+
// the sketch's order, an arrow included (one paper-sized SVG each), so the
|
|
7
|
+
// paint order on screen is the order the Layers list and the stage show.
|
|
8
|
+
//
|
|
9
|
+
// ref an image card's picture (`useImageThumb`, the item's fit) with its
|
|
10
|
+
// file name under it; a video's first frame; a component's poster
|
|
11
|
+
// (posterCache.ts, the card preview's own key), else its name tile,
|
|
12
|
+
// tagged "component"; "Missing" for a card that is gone
|
|
13
|
+
// text the words at their role's size, in the sketch ink
|
|
14
|
+
// box light, dark or accent, its label in the corner
|
|
15
|
+
// arrow a line with an open head and its label over the head; the line
|
|
16
|
+
// takes the pointer, the paper around it does not
|
|
17
|
+
//
|
|
18
|
+
// The item only draws and names itself (`data-item`, an aria label). What a
|
|
19
|
+
// press does is the canvas's (SketchCanvas.tsx).
|
|
20
|
+
import { useEffect, useRef } from "react";
|
|
21
|
+
import type { CSSProperties, KeyboardEvent as ReactKeyboardEvent } from "react";
|
|
22
|
+
import { SKETCH_TEXT_MAX, SKETCH_TEXT_SIZE, sketchCardOf } from "@dailies-studio/shot/doc";
|
|
23
|
+
import type { Doc3D, SketchArrow3D, SketchItem3D, SketchRef3D } from "@dailies-studio/shot/doc";
|
|
24
|
+
import { COMPONENT_ICON, kindIcon } from "../../../icons.js";
|
|
25
|
+
import { useShotEditor } from "../../../state/context.js";
|
|
26
|
+
import { useImageThumb } from "../../../state/imageThumbs.js";
|
|
27
|
+
import { closureFor } from "../ComponentStage.js";
|
|
28
|
+
import { arrowPath, boxStyle } from "../MiniSketch.js";
|
|
29
|
+
import { posterKey, usePoster } from "../posterCache.js";
|
|
30
|
+
import { MediaPoster } from "../ReferencePreview.js";
|
|
31
|
+
import { assetUrl } from "../ReferencePreview.js";
|
|
32
|
+
import { componentNameOf, fileNameOf, itemKindOf, itemNameOf } from "./sketchModel.js";
|
|
33
|
+
|
|
34
|
+
/** A box label's size, design px: the stage's (sketch.ts). */
|
|
35
|
+
const LABEL_SIZE = 24;
|
|
36
|
+
|
|
37
|
+
/** `px` design px as a share of the paper's width. */
|
|
38
|
+
export const cqw = (doc: Doc3D, px: number): string => `${(px / doc.size.width) * 100}cqw`;
|
|
39
|
+
|
|
40
|
+
/** What a screen reader hears for an item: its kind, then its name. */
|
|
41
|
+
export function itemLabelOf(doc: Doc3D, item: SketchItem3D): string {
|
|
42
|
+
return `${itemKindOf(doc, item)}: ${itemNameOf(doc, item)}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SketchItemViewProps {
|
|
46
|
+
doc: Doc3D;
|
|
47
|
+
item: SketchItem3D;
|
|
48
|
+
/** The paper's width in CSS px: the picture size a thumbnail asks for. */
|
|
49
|
+
paperPx: number;
|
|
50
|
+
selected: boolean;
|
|
51
|
+
/** Draw only: no focus, no pointer, no label (Play's overlay). */
|
|
52
|
+
inert?: boolean;
|
|
53
|
+
/** The text editor is open on this item. */
|
|
54
|
+
editing?: boolean;
|
|
55
|
+
onEditEnd?: (text: string) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function SketchItemView(props: SketchItemViewProps) {
|
|
59
|
+
const { doc, item } = props;
|
|
60
|
+
if (item.kind === "arrow") return <ArrowView {...props} item={item} />;
|
|
61
|
+
const own = props.inert === true ? {} : interactive(props);
|
|
62
|
+
const style = boxStyle(doc, item.position, item.size);
|
|
63
|
+
if (item.kind === "text") {
|
|
64
|
+
return (
|
|
65
|
+
<div {...own} className={`sketch-item is-text is-${item.role}`} data-align={item.align ?? "start"} style={{ ...style, fontSize: cqw(doc, SKETCH_TEXT_SIZE[item.role]) }}>
|
|
66
|
+
{props.editing === true ? <TextEditor text={item.text} onEnd={props.onEditEnd} /> : item.text}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (item.kind === "box") {
|
|
71
|
+
return (
|
|
72
|
+
<div {...own} className="sketch-item is-box" data-tone={item.tone ?? "light"} style={style}>
|
|
73
|
+
{item.label === undefined ? null : (
|
|
74
|
+
<span className="sketch-item-label" style={{ fontSize: cqw(doc, LABEL_SIZE) }}>
|
|
75
|
+
{item.label}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return <RefView {...props} item={item} own={own} style={style} />;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The attributes every pressable item carries. */
|
|
85
|
+
function interactive(props: SketchItemViewProps) {
|
|
86
|
+
return {
|
|
87
|
+
"data-item": props.item.id,
|
|
88
|
+
"data-testid": `sketch-item-${props.item.id}`,
|
|
89
|
+
"data-selected": props.selected ? "" : undefined,
|
|
90
|
+
tabIndex: 0,
|
|
91
|
+
role: "button",
|
|
92
|
+
"aria-pressed": props.selected,
|
|
93
|
+
"aria-label": itemLabelOf(props.doc, props.item),
|
|
94
|
+
} as const;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function RefView(props: SketchItemViewProps & { item: SketchRef3D; own: object; style: CSSProperties }) {
|
|
98
|
+
const { doc, item } = props;
|
|
99
|
+
const card = sketchCardOf(doc, item);
|
|
100
|
+
const asset = card?.type === "asset" ? doc.assets[card.asset] : undefined;
|
|
101
|
+
const image = asset?.kind === "image" ? asset : undefined;
|
|
102
|
+
const px = (item.size[0] / doc.size.width) * props.paperPx;
|
|
103
|
+
const thumb = useImageThumb(image === undefined ? "" : assetUrl(image.path), image?.hash ?? "", px);
|
|
104
|
+
const fit = item.fit ?? "cover";
|
|
105
|
+
if (image !== undefined) {
|
|
106
|
+
return (
|
|
107
|
+
<div {...props.own} className="sketch-item is-image" style={props.style}>
|
|
108
|
+
{thumb === null ? null : <img src={thumb} alt="" draggable={false} style={{ objectFit: fit }} />}
|
|
109
|
+
{props.inert === true ? null : (
|
|
110
|
+
<span className="sketch-item-caption" aria-hidden="true">
|
|
111
|
+
{kindIcon("image")}
|
|
112
|
+
{fileNameOf(image.path)}
|
|
113
|
+
</span>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (asset?.kind === "video" && card?.type === "asset") {
|
|
119
|
+
const at = card.frame === undefined ? null : (card.frame * doc.fps.den) / doc.fps.num;
|
|
120
|
+
return (
|
|
121
|
+
<div {...props.own} className="sketch-item is-image" style={props.style}>
|
|
122
|
+
<MediaPoster className="sketch-item-media" src={assetUrl(asset.path)} atSeconds={at} />
|
|
123
|
+
{props.inert === true ? null : (
|
|
124
|
+
<span className="sketch-item-caption" aria-hidden="true">
|
|
125
|
+
{kindIcon("video")}
|
|
126
|
+
{fileNameOf(asset.path)}
|
|
127
|
+
</span>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (card?.type === "component") {
|
|
133
|
+
return (
|
|
134
|
+
<div {...props.own} className="sketch-item is-component" style={props.style}>
|
|
135
|
+
<ComponentPicture doc={doc} card={card} />
|
|
136
|
+
{props.inert === true ? null : (
|
|
137
|
+
<span className="sketch-item-tag" aria-hidden="true">
|
|
138
|
+
{COMPONENT_ICON}
|
|
139
|
+
component
|
|
140
|
+
</span>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return (
|
|
146
|
+
<div {...props.own} className="sketch-item is-missing" style={props.style}>
|
|
147
|
+
<span className="sketch-item-label" style={{ fontSize: cqw(doc, LABEL_SIZE) }}>
|
|
148
|
+
Missing
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type ComponentCard = Extract<NonNullable<ReturnType<typeof sketchCardOf>>, { type: "component" }>;
|
|
155
|
+
|
|
156
|
+
/** The card preview's poster when one was captured (the same key
|
|
157
|
+
* `ComponentStage` writes), else the component's name on a tile. */
|
|
158
|
+
function ComponentPicture({ doc, card }: { doc: Doc3D; card: ComponentCard }) {
|
|
159
|
+
const contract = useShotEditor((s) => (card.component === undefined ? undefined : s.contracts[card.component]));
|
|
160
|
+
const studio = card.def !== undefined && doc.components?.[card.def] !== undefined;
|
|
161
|
+
const contentHash = studio ? closureFor(doc, card.def!).hash : (contract?.contractHash ?? card.contractHash ?? "none");
|
|
162
|
+
const poster = usePoster(posterKey(card.component ?? card.def ?? card.id, contentHash, card.props));
|
|
163
|
+
if (poster !== null) return <img className="sketch-item-poster" src={poster} alt="" draggable={false} />;
|
|
164
|
+
return <span className="sketch-item-tile">{componentNameOf(doc, card)}</span>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function ArrowView(props: SketchItemViewProps & { item: SketchArrow3D }) {
|
|
168
|
+
const { doc, item } = props;
|
|
169
|
+
const { width: w, height: h } = doc.size;
|
|
170
|
+
const d = arrowPath(item.from, item.to);
|
|
171
|
+
const own = props.inert === true ? null : interactive(props);
|
|
172
|
+
return (
|
|
173
|
+
<>
|
|
174
|
+
<svg
|
|
175
|
+
className="sketch-item is-arrow"
|
|
176
|
+
data-selected={props.selected ? "" : undefined}
|
|
177
|
+
viewBox={`${-w / 2} ${-h / 2} ${w} ${h}`}
|
|
178
|
+
preserveAspectRatio="none"
|
|
179
|
+
aria-hidden={own === null ? true : undefined}
|
|
180
|
+
>
|
|
181
|
+
{/* THE WIDE STROKE TAKES THE POINTER: a 2px line is a 2px target. */}
|
|
182
|
+
{own === null ? null : <path className="sketch-arrow-hit" d={d} {...own} />}
|
|
183
|
+
<path className="sketch-arrow-line" d={d} />
|
|
184
|
+
</svg>
|
|
185
|
+
{item.label === undefined ? null : (
|
|
186
|
+
<span
|
|
187
|
+
className="sketch-item-label is-arrow"
|
|
188
|
+
aria-hidden="true"
|
|
189
|
+
style={{
|
|
190
|
+
left: `${(item.to[0] / w + 0.5) * 100}%`,
|
|
191
|
+
top: `${((item.to[1] - 40) / h + 0.5) * 100}%`,
|
|
192
|
+
fontSize: cqw(doc, LABEL_SIZE),
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
{item.label}
|
|
196
|
+
</span>
|
|
197
|
+
)}
|
|
198
|
+
</>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The text editor over a text item: Enter, Escape or a click away keeps the
|
|
204
|
+
* words, as the Studio's inline editor does (`InlineTextEditor`) and as design
|
|
205
|
+
* tools do, so a new item's typed words are never thrown away. Shift+Enter
|
|
206
|
+
* breaks a line. Every key stays here, so none reaches the shot board's tool
|
|
207
|
+
* letters or the board's Escape.
|
|
208
|
+
*/
|
|
209
|
+
function TextEditor(props: { text: string; onEnd: ((text: string) => void) | undefined }) {
|
|
210
|
+
const ref = useRef<HTMLTextAreaElement>(null);
|
|
211
|
+
const done = useRef(false);
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
ref.current?.focus();
|
|
214
|
+
ref.current?.select();
|
|
215
|
+
}, []);
|
|
216
|
+
const end = (text: string): void => {
|
|
217
|
+
if (done.current) return;
|
|
218
|
+
done.current = true;
|
|
219
|
+
props.onEnd?.(text);
|
|
220
|
+
};
|
|
221
|
+
const onKeyDown = (event: ReactKeyboardEvent<HTMLTextAreaElement>): void => {
|
|
222
|
+
event.stopPropagation();
|
|
223
|
+
if (event.key === "Escape" || (event.key === "Enter" && !event.shiftKey)) {
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
end(event.currentTarget.value);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
return (
|
|
229
|
+
<textarea
|
|
230
|
+
ref={ref}
|
|
231
|
+
className="sketch-text-editor"
|
|
232
|
+
defaultValue={props.text}
|
|
233
|
+
maxLength={SKETCH_TEXT_MAX}
|
|
234
|
+
aria-label="Text"
|
|
235
|
+
onKeyDown={onKeyDown}
|
|
236
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
237
|
+
onBlur={(event) => end(event.currentTarget.value)}
|
|
238
|
+
/>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// THE SHOT BOARD'S LAYERS (Paper SB3, SB4): the open sketch's items, FRONT
|
|
2
|
+
// FIRST, in 28px rows of icon, name and kind. The left column, 224 wide.
|
|
3
|
+
//
|
|
4
|
+
// A press selects (Shift adds or takes away), the selection is the sketch's
|
|
5
|
+
// own (`sketchSelection`), and a row drags to restack (`restackSketchItem`).
|
|
6
|
+
// The list is a listbox: the arrows walk it and select, Alt+arrow restacks
|
|
7
|
+
// the focused row one step, and Delete reaches the board's own key.
|
|
8
|
+
import { useEffect, useRef } from "react";
|
|
9
|
+
import type { DragEvent as ReactDragEvent, KeyboardEvent as ReactKeyboardEvent, ReactElement } from "react";
|
|
10
|
+
import type { Doc3D, SketchItem3D, StoryboardSketch3D } from "@dailies-studio/shot/doc";
|
|
11
|
+
import { COMPONENT_ICON, kindIcon } from "../../../icons.js";
|
|
12
|
+
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
13
|
+
import { itemKindOf, itemNameOf } from "./sketchModel.js";
|
|
14
|
+
import type { SketchItemKind } from "./sketchModel.js";
|
|
15
|
+
|
|
16
|
+
/** A layer row's own drag type: a restack inside this list only. */
|
|
17
|
+
const LAYER_ROW_MIME = "application/x-dailies-sketch-layer";
|
|
18
|
+
|
|
19
|
+
const KIND_ICON: Record<SketchItemKind, ReactElement> = {
|
|
20
|
+
Title: kindIcon("text"),
|
|
21
|
+
Body: kindIcon("text"),
|
|
22
|
+
Caption: kindIcon("text"),
|
|
23
|
+
Image: kindIcon("image"),
|
|
24
|
+
Video: kindIcon("video"),
|
|
25
|
+
Component: COMPONENT_ICON,
|
|
26
|
+
Box: kindIcon("rect"),
|
|
27
|
+
Arrow: kindIcon("arrow"),
|
|
28
|
+
Missing: kindIcon("rect"),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function SketchLayers({ doc, sketch }: { doc: Doc3D; sketch: StoryboardSketch3D | undefined }) {
|
|
32
|
+
const store = useStore();
|
|
33
|
+
const selection = useShotEditor((s) => s.sketchSelection);
|
|
34
|
+
const readOnly = useShotEditor((s) => s.readOnly);
|
|
35
|
+
const list = useRef<HTMLUListElement>(null);
|
|
36
|
+
const focusId = useRef<string | null>(null);
|
|
37
|
+
const items = sketch?.items ?? [];
|
|
38
|
+
// FRONT FIRST: the stored list is back to front.
|
|
39
|
+
const rows = [...items].reverse();
|
|
40
|
+
const selected = new Set(selection);
|
|
41
|
+
|
|
42
|
+
// THE FOCUS FOLLOWS A WALKED OR RESTACKED ROW.
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const id = focusId.current;
|
|
45
|
+
if (id === null) return;
|
|
46
|
+
focusId.current = null;
|
|
47
|
+
list.current?.querySelector<HTMLElement>(`[data-row="${id}"]`)?.focus();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** A row's back-to-front index, which the store's restack takes. */
|
|
51
|
+
const stackIndex = (row: number): number => items.length - 1 - row;
|
|
52
|
+
|
|
53
|
+
const onKeyDown = (event: ReactKeyboardEvent, item: SketchItem3D, row: number): void => {
|
|
54
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
55
|
+
// Here, and not on the paper: the arrows move items there.
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
event.stopPropagation();
|
|
58
|
+
const to = row + (event.key === "ArrowUp" ? -1 : 1);
|
|
59
|
+
if (to < 0 || to >= rows.length) return;
|
|
60
|
+
if (event.altKey) {
|
|
61
|
+
if (readOnly) return;
|
|
62
|
+
focusId.current = item.id;
|
|
63
|
+
store.restackSketchItem(item.id, stackIndex(to));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const next = rows[to]!;
|
|
67
|
+
focusId.current = next.id;
|
|
68
|
+
store.setSketchSelection(event.shiftKey ? [...selection, next.id] : [next.id]);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const onDrop = (event: ReactDragEvent, row: number): void => {
|
|
72
|
+
const id = event.dataTransfer.getData(LAYER_ROW_MIME);
|
|
73
|
+
if (id === "") return;
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
store.restackSketchItem(id, stackIndex(row));
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<aside className="sketch-layers" aria-label="Layers" data-testid="sketch-layers">
|
|
80
|
+
<header className="panel-head sketch-layers-head">
|
|
81
|
+
<h3 className="caps">Layers</h3>
|
|
82
|
+
<span className="sketch-count">{items.length}</span>
|
|
83
|
+
</header>
|
|
84
|
+
{rows.length === 0 ? (
|
|
85
|
+
<p className="sketch-layers-empty">Nothing on this sketch yet.</p>
|
|
86
|
+
) : (
|
|
87
|
+
<ul ref={list} className="sketch-layer-list" role="listbox" aria-label="Layers" aria-multiselectable="true">
|
|
88
|
+
{rows.map((item, row) => {
|
|
89
|
+
const kind = itemKindOf(doc, item);
|
|
90
|
+
const on = selected.has(item.id);
|
|
91
|
+
return (
|
|
92
|
+
<li
|
|
93
|
+
key={item.id}
|
|
94
|
+
className="sketch-layer"
|
|
95
|
+
role="option"
|
|
96
|
+
aria-selected={on}
|
|
97
|
+
tabIndex={0}
|
|
98
|
+
data-row={item.id}
|
|
99
|
+
data-testid={`sketch-layer-${item.id}`}
|
|
100
|
+
draggable={!readOnly}
|
|
101
|
+
title={readOnly ? undefined : "Drag to restack, or Alt+arrow"}
|
|
102
|
+
onClick={(event) => {
|
|
103
|
+
if (event.shiftKey) store.setSketchSelection(on ? selection.filter((id) => id !== item.id) : [...selection, item.id]);
|
|
104
|
+
else store.setSketchSelection([item.id]);
|
|
105
|
+
}}
|
|
106
|
+
onKeyDown={(event) => onKeyDown(event, item, row)}
|
|
107
|
+
onDragStart={(event) => {
|
|
108
|
+
event.dataTransfer.setData(LAYER_ROW_MIME, item.id);
|
|
109
|
+
event.dataTransfer.effectAllowed = "move";
|
|
110
|
+
}}
|
|
111
|
+
onDragOver={(event) => {
|
|
112
|
+
if (!event.dataTransfer.types.includes(LAYER_ROW_MIME)) return;
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
event.dataTransfer.dropEffect = "move";
|
|
115
|
+
}}
|
|
116
|
+
onDrop={(event) => onDrop(event, row)}
|
|
117
|
+
>
|
|
118
|
+
<span className="sketch-layer-icon" aria-hidden="true">
|
|
119
|
+
{KIND_ICON[kind]}
|
|
120
|
+
</span>
|
|
121
|
+
<span className="sketch-layer-name">{itemNameOf(doc, item)}</span>
|
|
122
|
+
<span className="sketch-layer-kind">{kind}</span>
|
|
123
|
+
</li>
|
|
124
|
+
);
|
|
125
|
+
})}
|
|
126
|
+
</ul>
|
|
127
|
+
)}
|
|
128
|
+
</aside>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// PLAY (Paper SB5): the open sketch live in ONE stage, the paper's size.
|
|
2
|
+
//
|
|
3
|
+
// `sketchStageDoc` turns the sketch into a throwaway one-Shot document (the
|
|
4
|
+
// converter Lay out and the agent's picture share), and `StagePreview` draws
|
|
5
|
+
// it on a second `/shot-stage.html`, looping over the shot's planned length:
|
|
6
|
+
// one WebGL context for the whole sketch, and none until Play is pressed. The
|
|
7
|
+
// stage unmounts on Stop. Components animate; pictures, text and boxes hold.
|
|
8
|
+
//
|
|
9
|
+
// A code component's instance takes the binding a placed one would carry
|
|
10
|
+
// (`contractBinding`, the registry's import-or-snapshot verdict), so its props
|
|
11
|
+
// reach it as the contract names them. Every document change reloads the
|
|
12
|
+
// stage, which is how a prop edit in the inspector shows live.
|
|
13
|
+
//
|
|
14
|
+
// THE OVERLAY is DOM at half strength: the arrows with their labels, and a
|
|
15
|
+
// dashed outline round each box with "<label> · <tone>". A preview fits the
|
|
16
|
+
// frame edge to edge (stage/view.ts `fitView` with no margin), so the overlay
|
|
17
|
+
// covers the whole paper, exactly over the drawn frame.
|
|
18
|
+
// Under it, the progress line and the time chip, read from this preview's own
|
|
19
|
+
// clock.
|
|
20
|
+
import { useEffect, useMemo, useState } from "react";
|
|
21
|
+
import { sketchCodeComponents, sketchStageDoc } from "@dailies-studio/shot/doc";
|
|
22
|
+
import type { Doc3D, StoryboardSketch3D } from "@dailies-studio/shot/doc";
|
|
23
|
+
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
24
|
+
import { clock } from "../../timeline/model.js";
|
|
25
|
+
import { contractBinding } from "../ComponentStage.js";
|
|
26
|
+
import { boxStyle } from "../MiniSketch.js";
|
|
27
|
+
import { StagePreview, loopFrameAt } from "../StagePreview.js";
|
|
28
|
+
import { SketchItemView } from "./SketchItem.js";
|
|
29
|
+
|
|
30
|
+
const TONE_WORD = { light: "light", dark: "dark", accent: "accent" } as const;
|
|
31
|
+
|
|
32
|
+
export function SketchPlay(props: { doc: Doc3D; partId: string; sketch: StoryboardSketch3D; width: number }) {
|
|
33
|
+
const { doc, sketch } = props;
|
|
34
|
+
const store = useStore();
|
|
35
|
+
const contracts = useShotEditor((s) => s.contracts);
|
|
36
|
+
const library = useShotEditor((s) => s.library);
|
|
37
|
+
const libraryLoaded = useShotEditor((s) => s.libraryLoaded);
|
|
38
|
+
const codeKey = sketchCodeComponents(doc, sketch).join("\n");
|
|
39
|
+
|
|
40
|
+
// THE CONTRACTS FIRST, so an instance binds as a placed one would.
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
for (const id of codeKey === "" ? [] : codeKey.split("\n")) void store.loadContract(id);
|
|
43
|
+
if (codeKey !== "" && !libraryLoaded) void store.refreshLibrary();
|
|
44
|
+
}, [codeKey, libraryLoaded, store]);
|
|
45
|
+
|
|
46
|
+
const part = doc.storyboard?.parts.find((p) => p.id === props.partId);
|
|
47
|
+
const length = Math.max(1, part?.durationFrames ?? 1);
|
|
48
|
+
const stageDoc = useMemo(
|
|
49
|
+
() =>
|
|
50
|
+
sketchStageDoc(doc, props.partId, sketch.id, (id) =>
|
|
51
|
+
contractBinding(contracts[id], library.find((row) => row.id === id)?.contractAvailability),
|
|
52
|
+
),
|
|
53
|
+
[doc, props.partId, sketch.id, contracts, library],
|
|
54
|
+
);
|
|
55
|
+
const size = useMemo(() => ({ width: doc.size.width, height: doc.size.height }), [doc.size.width, doc.size.height]);
|
|
56
|
+
|
|
57
|
+
// THE READOUT'S CLOCK, from the moment the stage said it drew: a chrome
|
|
58
|
+
// readout, never the stage's time.
|
|
59
|
+
const [since, setSince] = useState<number | null>(null);
|
|
60
|
+
const [frame, setFrame] = useState(0);
|
|
61
|
+
const fps = doc.fps.num / doc.fps.den;
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (since === null) return;
|
|
64
|
+
let raf = 0;
|
|
65
|
+
const tick = (now: number): void => {
|
|
66
|
+
setFrame(loopFrameAt(now - since, fps, length));
|
|
67
|
+
raf = requestAnimationFrame(tick);
|
|
68
|
+
};
|
|
69
|
+
raf = requestAnimationFrame(tick);
|
|
70
|
+
return () => cancelAnimationFrame(raf);
|
|
71
|
+
}, [since, fps, length]);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="sketch-paper is-playing" data-ground={sketch.ground ?? "paper"} data-testid="sketch-play" style={{ width: props.width, aspectRatio: `${size.width} / ${size.height}` }}>
|
|
75
|
+
<StagePreview
|
|
76
|
+
doc={stageDoc}
|
|
77
|
+
size={size}
|
|
78
|
+
lengthFrames={length}
|
|
79
|
+
slotKey="sketch-play"
|
|
80
|
+
posterKey={null}
|
|
81
|
+
title={`${sketch.label ?? "Sketch"} playing`}
|
|
82
|
+
className="sketch-play-stage"
|
|
83
|
+
onReady={() => setSince((was) => was ?? performance.now())}
|
|
84
|
+
/>
|
|
85
|
+
<div className="sketch-play-overlay" aria-hidden="true">
|
|
86
|
+
{sketch.items.map((item) => {
|
|
87
|
+
if (item.kind === "arrow") return <SketchItemView key={item.id} doc={doc} item={item} paperPx={props.width} selected={false} inert />;
|
|
88
|
+
if (item.kind !== "box") return null;
|
|
89
|
+
return (
|
|
90
|
+
<span key={item.id} className="sketch-play-box" style={boxStyle(doc, item.position, item.size)}>
|
|
91
|
+
{item.label === undefined ? null : (
|
|
92
|
+
<span className="sketch-play-chip">{`${item.label} · ${TONE_WORD[item.tone ?? "light"]}`}</span>
|
|
93
|
+
)}
|
|
94
|
+
</span>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
97
|
+
</div>
|
|
98
|
+
<span className="sketch-play-progress" aria-hidden="true">
|
|
99
|
+
<span style={{ width: `${(frame / length) * 100}%` }} />
|
|
100
|
+
</span>
|
|
101
|
+
<span className="sketch-play-time" data-testid="sketch-play-time">
|
|
102
|
+
<span className="sketch-play-dot" aria-hidden="true" />
|
|
103
|
+
{clock(frame, doc.fps)}
|
|
104
|
+
<span className="sketch-play-total">{`/ ${clock(length, doc.fps)}`}</span>
|
|
105
|
+
</span>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// THE SKETCH STRIP (Paper SB2, SB3): a shot's sketches under the paper, each
|
|
2
|
+
// a thumbnail (`MiniSketch`: the still stage's picture once drawn, the DOM
|
|
3
|
+
// drawing until then) with "1 · Start" and its time, then
|
|
4
|
+
// "+ Sketch", which copies the open one so the next key moment starts from
|
|
5
|
+
// it (the first one is "Start" at 0.00s). Hidden at `SKETCHES_MAX`.
|
|
6
|
+
//
|
|
7
|
+
// A press shows that sketch; a thumb drags to reorder (`moveSketch`); its
|
|
8
|
+
// menu (right press, or the context-menu key) is Rename, Set time, Duplicate
|
|
9
|
+
// and Delete. Rename and Set time edit in place under the thumb, and close on
|
|
10
|
+
// Enter, Escape or any blur: a field that commits only a change used to stay
|
|
11
|
+
// open under an Escape or a click away, with nothing else to close it.
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import type { MouseEvent as ReactMouseEvent } from "react";
|
|
14
|
+
import { SKETCHES_MAX, SKETCH_LABEL_MAX } from "@dailies-studio/shot/doc";
|
|
15
|
+
import type { Doc3D, StoryboardSketch3D } from "@dailies-studio/shot/doc";
|
|
16
|
+
import { TextField, useContextMenu } from "@dailies-studio/ui";
|
|
17
|
+
import { ADD_ICON } from "../../../icons.js";
|
|
18
|
+
import { useShotEditor, useStore } from "../../../state/context.js";
|
|
19
|
+
import { gateForViewer } from "../../../state/menu.js";
|
|
20
|
+
import { formatDuration } from "../../timeline/model.js";
|
|
21
|
+
import { MiniSketch } from "../MiniSketch.js";
|
|
22
|
+
import { useSketchThumbs } from "../StillThumbs.js";
|
|
23
|
+
|
|
24
|
+
/** A thumb's own drag type: a reorder inside one strip. */
|
|
25
|
+
const SKETCH_THUMB_MIME = "application/x-dailies-sketch-thumb";
|
|
26
|
+
/** The thumb's width, CSS px (SB2). */
|
|
27
|
+
export const SKETCH_THUMB_PX = 128;
|
|
28
|
+
|
|
29
|
+
/** What the Set time field takes, said when it takes nothing. */
|
|
30
|
+
export const SKETCH_TIME_REFUSAL = "Type a time in seconds (1.6) or in frames (48f).";
|
|
31
|
+
|
|
32
|
+
/** "0.00s" for a timed sketch, nothing for an untimed one. */
|
|
33
|
+
export function sketchTimeOf(doc: Doc3D, sketch: StoryboardSketch3D): string {
|
|
34
|
+
return sketch.frame === undefined ? "" : formatDuration((sketch.frame * doc.fps.den) / doc.fps.num);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A sketch's typed time back to a frame of its shot: seconds ("1.6", "1.60s")
|
|
39
|
+
* or frames ("48f"), zero included in either (the first sketch is Start, at
|
|
40
|
+
* 0), on the whole frame. Null for anything else: a bare unit, a sign, a word.
|
|
41
|
+
* A sketch is a moment, so unlike a length nothing here has to be above zero.
|
|
42
|
+
*/
|
|
43
|
+
export function parseSketchFrame(text: string, fps: number): number | null {
|
|
44
|
+
const m = /^\s*(\d+(?:\.\d*)?|\.\d+)\s*(s|f)?\s*$/i.exec(text);
|
|
45
|
+
if (m === null) return null;
|
|
46
|
+
const n = Number(m[1]);
|
|
47
|
+
const frames = m[2]?.toLowerCase() === "f" ? n : n * fps;
|
|
48
|
+
return Number.isFinite(frames) ? Math.round(frames) : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function SketchStrip(props: { doc: Doc3D; partId: string; sketches: readonly StoryboardSketch3D[]; current: string | null }) {
|
|
52
|
+
const { doc, sketches } = props;
|
|
53
|
+
const store = useStore();
|
|
54
|
+
const readOnly = useShotEditor((s) => s.readOnly);
|
|
55
|
+
const menu = useContextMenu();
|
|
56
|
+
const [editing, setEditing] = useState<{ id: string; field: "label" | "time" } | null>(null);
|
|
57
|
+
const fps = doc.fps.num / doc.fps.den;
|
|
58
|
+
// Each thumb as the still stage draws it, once ready.
|
|
59
|
+
const pictures = useSketchThumbs(props.partId, sketches.map((sketch) => sketch.id));
|
|
60
|
+
|
|
61
|
+
const openMenu = (event: ReactMouseEvent, sketch: StoryboardSketch3D): void => {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
store.selectSketch(sketch.id);
|
|
64
|
+
const full = sketches.length >= SKETCHES_MAX;
|
|
65
|
+
menu.open(
|
|
66
|
+
{ x: event.clientX, y: event.clientY },
|
|
67
|
+
gateForViewer(store.getState(), [
|
|
68
|
+
{ kind: "action", id: "sketch-rename", label: "Rename", onSelect: () => setEditing({ id: sketch.id, field: "label" }) },
|
|
69
|
+
{ kind: "action", id: "sketch-time", label: "Set time", onSelect: () => setEditing({ id: sketch.id, field: "time" }) },
|
|
70
|
+
{
|
|
71
|
+
kind: "action",
|
|
72
|
+
id: "sketch-duplicate",
|
|
73
|
+
label: "Duplicate",
|
|
74
|
+
disabled: full,
|
|
75
|
+
title: full ? `A shot holds at most ${SKETCHES_MAX} sketches.` : undefined,
|
|
76
|
+
onSelect: () => store.duplicateSketch(sketch.id),
|
|
77
|
+
},
|
|
78
|
+
{ kind: "action", id: "sketch-delete", label: "Delete", danger: true, onSelect: () => store.removeSketch(sketch.id) },
|
|
79
|
+
]),
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const add = (): void => {
|
|
84
|
+
if (props.current !== null) store.duplicateSketch(props.current);
|
|
85
|
+
else store.addSketch(props.partId);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="sketch-strip" role="group" aria-label="Sketches" data-testid="sketch-strip">
|
|
90
|
+
{sketches.map((sketch, at) => {
|
|
91
|
+
const current = sketch.id === props.current;
|
|
92
|
+
const name = `${at + 1} · ${sketch.label ?? `Sketch ${at + 1}`}`;
|
|
93
|
+
const time = sketchTimeOf(doc, sketch);
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
key={sketch.id}
|
|
97
|
+
className="sketch-thumb-cell"
|
|
98
|
+
data-testid={`sketch-thumb-${sketch.id}`}
|
|
99
|
+
draggable={!readOnly && editing === null}
|
|
100
|
+
onDragStart={(event) => {
|
|
101
|
+
event.dataTransfer.setData(SKETCH_THUMB_MIME, sketch.id);
|
|
102
|
+
event.dataTransfer.effectAllowed = "move";
|
|
103
|
+
}}
|
|
104
|
+
onDragOver={(event) => {
|
|
105
|
+
if (!event.dataTransfer.types.includes(SKETCH_THUMB_MIME)) return;
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
event.dataTransfer.dropEffect = "move";
|
|
108
|
+
}}
|
|
109
|
+
onDrop={(event) => {
|
|
110
|
+
const id = event.dataTransfer.getData(SKETCH_THUMB_MIME);
|
|
111
|
+
if (id === "") return;
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
store.moveSketch(id, at);
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
className="sketch-thumb"
|
|
119
|
+
aria-pressed={current}
|
|
120
|
+
aria-label={`${name}${time === "" ? "" : ` at ${time}`}`}
|
|
121
|
+
title="Press to show it. Right press for more."
|
|
122
|
+
onClick={() => store.selectSketch(sketch.id)}
|
|
123
|
+
onContextMenu={(event) => openMenu(event, sketch)}
|
|
124
|
+
>
|
|
125
|
+
<MiniSketch doc={doc} sketch={sketch} width={SKETCH_THUMB_PX} picture={pictures.get(sketch.id) ?? null} />
|
|
126
|
+
</button>
|
|
127
|
+
{/* The editors close on ANY blur, Escape's included (TextField
|
|
128
|
+
cancels by blurring); their own blur commits a change first. */}
|
|
129
|
+
<div className="sketch-thumb-label" {...(editing?.id === sketch.id ? { onBlur: () => setEditing(null) } : {})}>
|
|
130
|
+
{editing?.id === sketch.id && editing.field === "label" ? (
|
|
131
|
+
<TextField
|
|
132
|
+
autoFocus
|
|
133
|
+
value={sketch.label ?? ""}
|
|
134
|
+
ariaLabel="Sketch name"
|
|
135
|
+
onCommit={(text) => {
|
|
136
|
+
const label = text.trim().slice(0, SKETCH_LABEL_MAX).trim();
|
|
137
|
+
if (label !== (sketch.label ?? "")) store.updateSketch(sketch.id, { label: label === "" ? null : label });
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
) : editing?.id === sketch.id && editing.field === "time" ? (
|
|
141
|
+
<TextField
|
|
142
|
+
autoFocus
|
|
143
|
+
value={time}
|
|
144
|
+
placeholder="0.00s"
|
|
145
|
+
ariaLabel="Sketch time"
|
|
146
|
+
onCommit={(text) => {
|
|
147
|
+
// Emptied: the sketch is untimed again.
|
|
148
|
+
if (text.trim() === "") {
|
|
149
|
+
if (sketch.frame !== undefined) store.updateSketch(sketch.id, { frame: null });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const frame = parseSketchFrame(text, fps);
|
|
153
|
+
if (frame === null) store.reportError(SKETCH_TIME_REFUSAL);
|
|
154
|
+
else if (frame !== sketch.frame) store.updateSketch(sketch.id, { frame });
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
) : (
|
|
158
|
+
<>
|
|
159
|
+
<span className={`sketch-thumb-name${current ? " is-current" : ""}`}>{name}</span>
|
|
160
|
+
<span className="sketch-thumb-time">{time}</span>
|
|
161
|
+
</>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
})}
|
|
167
|
+
{sketches.length >= SKETCHES_MAX ? null : (
|
|
168
|
+
<button type="button" className="sketch-add" data-testid="sketch-add" disabled={readOnly} onClick={add}>
|
|
169
|
+
<span className="sketch-add-icon" aria-hidden="true">
|
|
170
|
+
{ADD_ICON}
|
|
171
|
+
</span>
|
|
172
|
+
Sketch
|
|
173
|
+
</button>
|
|
174
|
+
)}
|
|
175
|
+
{menu.menu}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|