@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,656 @@
|
|
|
1
|
+
// FONT PICKS, PLANNED. Pure functions over a document, the Google catalog and
|
|
2
|
+
// the workspace font list: the files each target text layer needs for a pick
|
|
3
|
+
// (its face set), the weight and style it then draws at, the stage preview of
|
|
4
|
+
// a pick, what the Typography rows offer for a selection, and the families a
|
|
5
|
+
// Project already holds. The store's verbs (`applyFont`, `setFontWeight`,
|
|
6
|
+
// `setFontStyle`, `previewFont`, `selfHostFont`, `followFontWeights`) fetch
|
|
7
|
+
// and write what these plan, in one edit.
|
|
8
|
+
//
|
|
9
|
+
// The catalog and its subset ranges are lazy chunks, so every function here
|
|
10
|
+
// takes them already loaded, and answers "loading" where it needs one it was
|
|
11
|
+
// not given.
|
|
12
|
+
import { drawsMachineFont, fontStack, primaryFontFamily } from "@dailies-studio/shot/doc";
|
|
13
|
+
import type { Doc3D, Layer3D, ShotAsset } from "@dailies-studio/shot/doc";
|
|
14
|
+
import {
|
|
15
|
+
appliesVariableFile,
|
|
16
|
+
catalogEntryOf,
|
|
17
|
+
catalogFileOf,
|
|
18
|
+
categoryFallback,
|
|
19
|
+
faceWeight,
|
|
20
|
+
findGoogleFont,
|
|
21
|
+
googleFacePlan,
|
|
22
|
+
googleFaceSet,
|
|
23
|
+
googleFontFileName,
|
|
24
|
+
googleStyle,
|
|
25
|
+
nearestWeight,
|
|
26
|
+
subsetsForText,
|
|
27
|
+
} from "../fonts/googleFonts.js";
|
|
28
|
+
import type { GoogleFace, GoogleFontEntry, GoogleFontRanges, GoogleFontStyle } from "../fonts/googleFonts.js";
|
|
29
|
+
import { workspaceFontUrl } from "../api.js";
|
|
30
|
+
import type { WorkspaceFont } from "../api.js";
|
|
31
|
+
import type { ShotFontPreview, ShotFontPreviewFace } from "../bridgeTypes.js";
|
|
32
|
+
import { storedName } from "./storyboardModel.js";
|
|
33
|
+
|
|
34
|
+
export type TextLayer = Extract<Layer3D, { kind: "text" }>;
|
|
35
|
+
|
|
36
|
+
/** What the picker hands the store: one family for the selected text. */
|
|
37
|
+
export type FontPick =
|
|
38
|
+
/** A Google catalog family: its files are copied from jsDelivr, or reused. */
|
|
39
|
+
| { kind: "google"; entry: GoogleFontEntry }
|
|
40
|
+
/** A workspace family: its rows in the workspace font list. */
|
|
41
|
+
| { kind: "workspace"; family: string; fonts: readonly WorkspaceFont[] }
|
|
42
|
+
/** A family whose files the Project already holds: nothing is fetched. */
|
|
43
|
+
| { kind: "project"; family: string; fallback: string; faces: readonly ProjectFace[] };
|
|
44
|
+
|
|
45
|
+
/** One face of a family the Project holds: the asset ids a layer takes. */
|
|
46
|
+
export interface ProjectFace {
|
|
47
|
+
fontAsset: string;
|
|
48
|
+
fontSubsets?: readonly string[];
|
|
49
|
+
/** A static face's weight. Absent for a variable face, or when unknown. */
|
|
50
|
+
weight?: number;
|
|
51
|
+
/** A variable face's `wght` range. */
|
|
52
|
+
axis?: readonly [number, number];
|
|
53
|
+
/** Absent when unknown. */
|
|
54
|
+
italic?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** One file of a face set that may have to be fetched. */
|
|
58
|
+
export interface FaceFile {
|
|
59
|
+
/** Where the bytes download from; also what dedupes it across layers. */
|
|
60
|
+
url: string;
|
|
61
|
+
/** The name it is uploaded as. */
|
|
62
|
+
name: string;
|
|
63
|
+
/** Finds a copy the Project already holds, so nothing is fetched. */
|
|
64
|
+
reuse: (asset: ShotAsset) => boolean;
|
|
65
|
+
/** Absent for a combined or whole file. */
|
|
66
|
+
unicodeRange?: string;
|
|
67
|
+
/** Set for a workspace font, which downloads through its own route. */
|
|
68
|
+
workspace?: WorkspaceFont;
|
|
69
|
+
/** Bytes already in hand (an upload's own file): nothing is downloaded. */
|
|
70
|
+
file?: File | (() => Promise<File>);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A file of a face set: one to fetch or reuse, or a Project asset. */
|
|
74
|
+
export type FaceSource = FaceFile | { asset: string };
|
|
75
|
+
|
|
76
|
+
/** What one target text layer becomes. */
|
|
77
|
+
export interface LayerFontPlan {
|
|
78
|
+
layerId: string;
|
|
79
|
+
/** The face set, `fontAsset` first. Empty keeps the layer's own files. */
|
|
80
|
+
files: readonly FaceSource[];
|
|
81
|
+
/** The `fontFamily` to write; null keeps the layer's. */
|
|
82
|
+
fontFamily: string | null;
|
|
83
|
+
/** The weight the face draws at, written to the layer. */
|
|
84
|
+
weight: number;
|
|
85
|
+
/** The style the face is, written to the layer (upright is no key). */
|
|
86
|
+
style: GoogleFontStyle;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** What a font write came to. `message` says why for refused and failed,
|
|
90
|
+
* and names the locked layers an applied write skipped. */
|
|
91
|
+
export interface FontApplyOutcome {
|
|
92
|
+
status: "applied" | "refused" | "failed" | "superseded";
|
|
93
|
+
message?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The Font field's in-flight apply (`ShotEditorState.fontApply`). */
|
|
97
|
+
export interface FontApplyProgress {
|
|
98
|
+
/** The family being applied, as the Font field names it. While a
|
|
99
|
+
* workspace upload runs, before the server has named the family, the
|
|
100
|
+
* files: "1 font file", "3 font files". */
|
|
101
|
+
family: string;
|
|
102
|
+
/** Downloading the face set's files, or copying them into the Project. */
|
|
103
|
+
phase: "download" | "upload";
|
|
104
|
+
/** Bytes read across the face set's downloads. */
|
|
105
|
+
loaded: number;
|
|
106
|
+
/** Their whole size, or null while any download's size is unknown. */
|
|
107
|
+
total: number | null;
|
|
108
|
+
/** Why it failed, or null while it runs. `retryFontApply` runs the same
|
|
109
|
+
* apply again on the same layers. */
|
|
110
|
+
failed: string | null;
|
|
111
|
+
/** The text layers the write lands on, captured at the call, so the Font
|
|
112
|
+
* field shows it only while one of them is selected. */
|
|
113
|
+
layerIds: readonly string[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A target text layer as an apply reads it at the call. */
|
|
117
|
+
export interface FontTargetLayer {
|
|
118
|
+
id: string;
|
|
119
|
+
weight: number;
|
|
120
|
+
style: GoogleFontStyle;
|
|
121
|
+
content: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const styleOf = (layer: TextLayer): GoogleFontStyle => layer.text.style.fontStyle ?? "normal";
|
|
125
|
+
|
|
126
|
+
export const targetOf = (layer: TextLayer): FontTargetLayer => ({
|
|
127
|
+
id: layer.id,
|
|
128
|
+
weight: layer.text.style.weight,
|
|
129
|
+
style: styleOf(layer),
|
|
130
|
+
content: layer.text.content,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export { fontStack };
|
|
134
|
+
|
|
135
|
+
const clamp = (value: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, value));
|
|
136
|
+
|
|
137
|
+
/** What a file says about its face: the weight it holds, or its `wght`
|
|
138
|
+
* range, and its slant. Each absent when unknown. */
|
|
139
|
+
export interface FaceFacts {
|
|
140
|
+
weight?: number;
|
|
141
|
+
axis?: readonly [number, number];
|
|
142
|
+
italic?: boolean;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The face a layer that wants `weight` and `style` draws from a family's
|
|
147
|
+
* files, the browser's way: a file of that style when the family has one,
|
|
148
|
+
* then the CSS Fonts 4 nearest weight (`nearestWeight`), a variable file
|
|
149
|
+
* counting as every weight on its axis. Answers the drawn weight and style.
|
|
150
|
+
* Files that say nothing about themselves (no weight, axis or italic, such as
|
|
151
|
+
* a font uploaded before its name table was read) keep the layer's own.
|
|
152
|
+
*/
|
|
153
|
+
export function chooseFace<T>(
|
|
154
|
+
faces: readonly T[],
|
|
155
|
+
facts: (face: T) => FaceFacts,
|
|
156
|
+
weight: number,
|
|
157
|
+
style: GoogleFontStyle,
|
|
158
|
+
): { face: T; weight: number; style: GoogleFontStyle } {
|
|
159
|
+
const known = faces.filter((face) => {
|
|
160
|
+
const f = facts(face);
|
|
161
|
+
return f.weight !== undefined || f.axis !== undefined || f.italic !== undefined;
|
|
162
|
+
});
|
|
163
|
+
if (known.length === 0) return { face: faces[0]!, weight, style };
|
|
164
|
+
const styled = known.filter((face) => (facts(face).italic === true) === (style === "italic"));
|
|
165
|
+
const pool = styled.length > 0 ? styled : known;
|
|
166
|
+
const drawn = pool.map((face) => {
|
|
167
|
+
const { axis, weight: own } = facts(face);
|
|
168
|
+
return axis === undefined ? (own ?? 400) : clamp(weight, axis[0], axis[1]);
|
|
169
|
+
});
|
|
170
|
+
const best = nearestWeight({ weights: drawn }, weight);
|
|
171
|
+
const face = pool[drawn.indexOf(best)]!;
|
|
172
|
+
return { face, weight: best, style: facts(face).italic === true ? "italic" : "normal" };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** A workspace font's facts. A variable file's axis wins over its weight
|
|
176
|
+
* class, which only names its default instance. */
|
|
177
|
+
export function workspaceFacts(font: WorkspaceFont): FaceFacts {
|
|
178
|
+
return {
|
|
179
|
+
...(font.wghtAxis !== undefined
|
|
180
|
+
? { axis: [font.wghtAxis[0], font.wghtAxis[2]] as const }
|
|
181
|
+
: font.weight !== undefined
|
|
182
|
+
? { weight: font.weight }
|
|
183
|
+
: {}),
|
|
184
|
+
...(font.italic === undefined ? {} : { italic: font.italic }),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const projectFacts = (face: ProjectFace): FaceFacts => face;
|
|
189
|
+
|
|
190
|
+
const googleFile = (face: GoogleFace): FaceFile => ({
|
|
191
|
+
url: face.url,
|
|
192
|
+
name: face.fileName,
|
|
193
|
+
// A catalog copy by its stored name, which says family, weight, style and
|
|
194
|
+
// subset (`catalogFileOf`).
|
|
195
|
+
reuse: (asset) => asset.kind === "font" && storedName(asset.path) === face.fileName,
|
|
196
|
+
...(face.unicodeRange === undefined ? {} : { unicodeRange: face.unicodeRange }),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A Google family on each layer, keeping the layer's weight and style where
|
|
201
|
+
* the family can draw them (`googleFacePlan`, which the MCP `set_font` op
|
|
202
|
+
* plans with too): a static family takes the nearest weight's face set and
|
|
203
|
+
* writes that weight; a variable one takes its `wght` set and keeps the
|
|
204
|
+
* layer's weight clamped to the axis. A static-only family (CJK) never takes
|
|
205
|
+
* a variable file (`faceWeight`).
|
|
206
|
+
*/
|
|
207
|
+
export function planGooglePick(
|
|
208
|
+
entry: GoogleFontEntry,
|
|
209
|
+
ranges: GoogleFontRanges,
|
|
210
|
+
layers: readonly FontTargetLayer[],
|
|
211
|
+
): LayerFontPlan[] {
|
|
212
|
+
const fontFamily = fontStack(entry.family, categoryFallback(entry.category));
|
|
213
|
+
const sets = new Map<string, FaceFile[]>();
|
|
214
|
+
return layers.map((layer) => {
|
|
215
|
+
const plan = googleFacePlan(entry, ranges, layer.weight, layer.style);
|
|
216
|
+
const key = `${plan.at}:${plan.style}`;
|
|
217
|
+
let files = sets.get(key);
|
|
218
|
+
if (files === undefined) {
|
|
219
|
+
files = plan.faces.map(googleFile);
|
|
220
|
+
sets.set(key, files);
|
|
221
|
+
}
|
|
222
|
+
return { layerId: layer.id, files, fontFamily, weight: plan.weight, style: plan.style };
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Inter's upright variable face set from the chrome's own bundled files
|
|
227
|
+
* (`BUNDLED_INTER_FILES`): the catalog's file names and ranges, so it is the
|
|
228
|
+
* same copy a pick of Inter makes, and the bundle's urls. A subset the
|
|
229
|
+
* bundle lacks is left out. */
|
|
230
|
+
export function bundledInterFaceSet(
|
|
231
|
+
inter: GoogleFontEntry,
|
|
232
|
+
ranges: GoogleFontRanges,
|
|
233
|
+
bundled: Readonly<Record<string, string>>,
|
|
234
|
+
): FaceFile[] {
|
|
235
|
+
return googleFaceSet(inter, ranges, "variable", "normal").flatMap((face) => {
|
|
236
|
+
const url = bundled[face.subset];
|
|
237
|
+
return url === undefined ? [] : [{ ...googleFile(face), url }];
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Whether the Project font file at `path` holds the workspace font `font`'s
|
|
243
|
+
* bytes. Both stores name a file `<hash8>-<safe name>` (`workspaceFontId`,
|
|
244
|
+
* `storedAssetName`), and a Project upload of bytes it already holds answers
|
|
245
|
+
* the name it first stored them under, so a copy's path need not be the id.
|
|
246
|
+
*/
|
|
247
|
+
export const isCopyOf = (path: string, font: WorkspaceFont): boolean => path.slice(0, 9) === font.id.slice(0, 9);
|
|
248
|
+
|
|
249
|
+
const workspaceFile = (font: WorkspaceFont): FaceFile => ({
|
|
250
|
+
url: workspaceFontUrl(font.id),
|
|
251
|
+
name: font.filename,
|
|
252
|
+
reuse: (asset) => asset.kind === "font" && isCopyOf(asset.path, font),
|
|
253
|
+
workspace: font,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/** A workspace family on each layer: the file of the layer's style (when the
|
|
257
|
+
* family has one) nearest its weight, or a variable file covering it. */
|
|
258
|
+
export function planWorkspacePick(
|
|
259
|
+
family: string,
|
|
260
|
+
fonts: readonly WorkspaceFont[],
|
|
261
|
+
layers: readonly FontTargetLayer[],
|
|
262
|
+
): LayerFontPlan[] {
|
|
263
|
+
const fontFamily = fontStack(family);
|
|
264
|
+
return layers.map((layer) => {
|
|
265
|
+
const chosen = chooseFace(fonts, workspaceFacts, layer.weight, layer.style);
|
|
266
|
+
return { layerId: layer.id, files: [workspaceFile(chosen.face)], fontFamily, weight: chosen.weight, style: chosen.style };
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** A family the Project holds, on each layer, from its own files. */
|
|
271
|
+
export function planProjectPick(
|
|
272
|
+
pick: Extract<FontPick, { kind: "project" }>,
|
|
273
|
+
layers: readonly FontTargetLayer[],
|
|
274
|
+
): LayerFontPlan[] {
|
|
275
|
+
const fontFamily = fontStack(pick.family, pick.fallback);
|
|
276
|
+
return layers.map((layer) => {
|
|
277
|
+
const chosen = chooseFace(pick.faces, projectFacts, layer.weight, layer.style);
|
|
278
|
+
const files = [chosen.face.fontAsset, ...(chosen.face.fontSubsets ?? [])].map((asset) => ({ asset }));
|
|
279
|
+
return { layerId: layer.id, files, fontFamily, weight: chosen.weight, style: chosen.style };
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The one dispatcher. A Google pick needs the subset ranges loaded. */
|
|
284
|
+
export function planFontPick(
|
|
285
|
+
pick: FontPick,
|
|
286
|
+
layers: readonly FontTargetLayer[],
|
|
287
|
+
ranges: GoogleFontRanges | null,
|
|
288
|
+
): LayerFontPlan[] {
|
|
289
|
+
switch (pick.kind) {
|
|
290
|
+
case "google":
|
|
291
|
+
if (ranges === null) throw new RangeError("A Google font is planned with the subset ranges loaded.");
|
|
292
|
+
return planGooglePick(pick.entry, ranges, layers);
|
|
293
|
+
case "workspace":
|
|
294
|
+
return planWorkspacePick(pick.family, pick.fonts, layers);
|
|
295
|
+
case "project":
|
|
296
|
+
return planProjectPick(pick, layers);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** The family a pick puts on text, as the Font field names it. */
|
|
301
|
+
export function pickFamily(pick: FontPick): string {
|
|
302
|
+
return pick.kind === "google" ? pick.entry.family : pick.family;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const assetUrl = (path: string): string => `/studio/assets/${encodeURIComponent(path)}`;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* The stage preview of a pick on `layers` (bridge v50). The face is the one
|
|
309
|
+
* the apply would give the FIRST layer (its weight and style rule), and only
|
|
310
|
+
* the layers the apply gives that same face show it: one preview family holds
|
|
311
|
+
* one face, which the stage would draw for any style a layer asks for, so a
|
|
312
|
+
* mixed selection's other layers keep their own font during the hover rather
|
|
313
|
+
* than show a face the apply will not give them. A Google family loads only
|
|
314
|
+
* the subsets those layers' text needs. Null with no layers.
|
|
315
|
+
*/
|
|
316
|
+
export function fontPreviewOf(
|
|
317
|
+
pick: FontPick,
|
|
318
|
+
containerId: string,
|
|
319
|
+
layers: readonly FontTargetLayer[],
|
|
320
|
+
ranges: GoogleFontRanges | null,
|
|
321
|
+
assets: Doc3D["assets"],
|
|
322
|
+
): ShotFontPreview | null {
|
|
323
|
+
const first = layers[0];
|
|
324
|
+
if (first === undefined) return null;
|
|
325
|
+
const sameFace = (key: (layer: FontTargetLayer) => string): FontTargetLayer[] =>
|
|
326
|
+
layers.filter((layer) => key(layer) === key(first));
|
|
327
|
+
if (pick.kind === "google") {
|
|
328
|
+
if (ranges === null) throw new RangeError("A Google font is previewed with the subset ranges loaded.");
|
|
329
|
+
const { entry } = pick;
|
|
330
|
+
const style = googleStyle(entry, first.style);
|
|
331
|
+
const at = faceWeight(entry, first.weight);
|
|
332
|
+
const shown = sameFace((layer) => `${faceWeight(entry, layer.weight)}:${googleStyle(entry, layer.style)}`);
|
|
333
|
+
const wanted = subsetsForText(entry, ranges, shown.map((layer) => layer.content).join("\n"));
|
|
334
|
+
const axis = entry.axes?.["wght"];
|
|
335
|
+
return {
|
|
336
|
+
containerId,
|
|
337
|
+
layerIds: shown.map((layer) => layer.id),
|
|
338
|
+
family: entry.family,
|
|
339
|
+
fallback: categoryFallback(entry.category),
|
|
340
|
+
style,
|
|
341
|
+
weightRange: at === "variable" && axis !== undefined ? [axis[0], axis[1]] : [1, 1000],
|
|
342
|
+
faces: googleFaceSet(entry, ranges, at, style)
|
|
343
|
+
.filter((face) => wanted.includes(face.subset))
|
|
344
|
+
.map((face) => ({ url: face.url, ...(face.unicodeRange === undefined ? {} : { unicodeRange: face.unicodeRange }) })),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (pick.kind === "workspace") {
|
|
348
|
+
const faceOf = (layer: FontTargetLayer) => chooseFace(pick.fonts, workspaceFacts, layer.weight, layer.style);
|
|
349
|
+
const chosen = faceOf(first);
|
|
350
|
+
const axis = workspaceFacts(chosen.face).axis;
|
|
351
|
+
return {
|
|
352
|
+
containerId,
|
|
353
|
+
layerIds: sameFace((layer) => {
|
|
354
|
+
const face = faceOf(layer);
|
|
355
|
+
return `${face.face.id}:${face.style}`;
|
|
356
|
+
}).map((layer) => layer.id),
|
|
357
|
+
family: pick.family,
|
|
358
|
+
fallback: "sans-serif",
|
|
359
|
+
style: chosen.style,
|
|
360
|
+
weightRange: axis === undefined ? [1, 1000] : axis,
|
|
361
|
+
faces: [{ url: workspaceFontUrl(chosen.face.id) }],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const faceOf = (layer: FontTargetLayer) => chooseFace(pick.faces, projectFacts, layer.weight, layer.style);
|
|
365
|
+
const chosen = faceOf(first);
|
|
366
|
+
const faces: ShotFontPreviewFace[] = [];
|
|
367
|
+
for (const id of [chosen.face.fontAsset, ...(chosen.face.fontSubsets ?? [])]) {
|
|
368
|
+
const asset = assets[id];
|
|
369
|
+
if (asset?.kind !== "font") continue;
|
|
370
|
+
faces.push({ url: assetUrl(asset.path), ...(asset.unicodeRange === undefined ? {} : { unicodeRange: asset.unicodeRange }) });
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
containerId,
|
|
374
|
+
layerIds: sameFace((layer) => {
|
|
375
|
+
const face = faceOf(layer);
|
|
376
|
+
return `${face.face.fontAsset}:${face.style}`;
|
|
377
|
+
}).map((layer) => layer.id),
|
|
378
|
+
family: pick.family,
|
|
379
|
+
fallback: pick.fallback,
|
|
380
|
+
style: chosen.style,
|
|
381
|
+
weightRange: chosen.face.axis ?? [1, 1000],
|
|
382
|
+
faces,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ── What the Typography rows offer ─────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
/** What a text layer's family can draw, for the Weight and Italic rows and
|
|
389
|
+
* the machine-font note. */
|
|
390
|
+
export type LayerFontFacts =
|
|
391
|
+
/** Its file is a catalog copy, or it draws a machine font, and the catalog
|
|
392
|
+
* was not handed in yet. */
|
|
393
|
+
| { kind: "loading" }
|
|
394
|
+
/** A Google family. `axis` is set when it applies `wght` files (a number
|
|
395
|
+
* field clamped to it); else `weights` is the named list. */
|
|
396
|
+
| {
|
|
397
|
+
kind: "catalog";
|
|
398
|
+
family: string;
|
|
399
|
+
entry: GoogleFontEntry;
|
|
400
|
+
weights: readonly number[];
|
|
401
|
+
axis: readonly [number, number] | null;
|
|
402
|
+
italic: boolean;
|
|
403
|
+
}
|
|
404
|
+
/** A workspace family, `fonts` its rows. `axis` is the variable file's range
|
|
405
|
+
* for the layer's style when the family has one. */
|
|
406
|
+
| {
|
|
407
|
+
kind: "workspace";
|
|
408
|
+
family: string;
|
|
409
|
+
fonts: readonly WorkspaceFont[];
|
|
410
|
+
weights: readonly number[];
|
|
411
|
+
axis: readonly [number, number] | null;
|
|
412
|
+
italic: boolean;
|
|
413
|
+
}
|
|
414
|
+
/** Another file (an upload, a copy the workspace list no longer has, or
|
|
415
|
+
* the list not handed in): any weight, and italic as the file draws it. */
|
|
416
|
+
| { kind: "file"; family: string }
|
|
417
|
+
/** No file: the computer's font. `selfHost` is the catalog family "Self-host
|
|
418
|
+
* <family>" applies (`selfHostFont`), or null ("Upload font file"). */
|
|
419
|
+
| { kind: "machine"; family: string; selfHost: GoogleFontEntry | null };
|
|
420
|
+
|
|
421
|
+
/** The workspace family a font row belongs to: rows that share its family. */
|
|
422
|
+
export const workspaceFamilyOf = (fonts: readonly WorkspaceFont[], font: WorkspaceFont): WorkspaceFont[] =>
|
|
423
|
+
fonts.filter((f) => f.family === font.family);
|
|
424
|
+
|
|
425
|
+
/** The facts of one text layer. `catalog` and `workspace` are what is loaded,
|
|
426
|
+
* or null. */
|
|
427
|
+
export function fontFactsOf(
|
|
428
|
+
layer: TextLayer,
|
|
429
|
+
assets: Doc3D["assets"],
|
|
430
|
+
catalog: readonly GoogleFontEntry[] | null,
|
|
431
|
+
workspace: readonly WorkspaceFont[] | null,
|
|
432
|
+
): LayerFontFacts {
|
|
433
|
+
const style = layer.text.style;
|
|
434
|
+
const family = primaryFontFamily(style.fontFamily);
|
|
435
|
+
if (drawsMachineFont(layer, assets)) {
|
|
436
|
+
return catalog === null ? { kind: "loading" } : { kind: "machine", family, selfHost: findGoogleFont(catalog, family) ?? null };
|
|
437
|
+
}
|
|
438
|
+
const asset = style.fontAsset === undefined ? undefined : assets[style.fontAsset];
|
|
439
|
+
if (asset === undefined) return { kind: "file", family };
|
|
440
|
+
const own = workspace?.find((font) => isCopyOf(asset.path, font));
|
|
441
|
+
if (own !== undefined && workspace !== null) {
|
|
442
|
+
const fonts = workspaceFamilyOf(workspace, own);
|
|
443
|
+
const italic = (style.fontStyle ?? "normal") === "italic";
|
|
444
|
+
// The weights of the layer's own style when the family has files in it:
|
|
445
|
+
// an italic cut only at 400 offers 400 on italic text, so the menu never
|
|
446
|
+
// names a weight the apply would draw from the upright file.
|
|
447
|
+
const inStyle = fonts.filter((f) => (f.italic === true) === italic);
|
|
448
|
+
const pool = inStyle.length > 0 ? inStyle : fonts;
|
|
449
|
+
const weights = [...new Set(pool.flatMap((f) => (f.wghtAxis === undefined && f.weight !== undefined ? [f.weight] : [])))];
|
|
450
|
+
const variable = pool.find((f) => f.wghtAxis !== undefined) ?? fonts.find((f) => f.wghtAxis !== undefined);
|
|
451
|
+
return {
|
|
452
|
+
kind: "workspace",
|
|
453
|
+
family: own.family,
|
|
454
|
+
fonts,
|
|
455
|
+
weights: weights.sort((a, b) => a - b),
|
|
456
|
+
axis: variable === undefined ? null : workspaceFacts(variable).axis!,
|
|
457
|
+
// Whether the apply draws italic (`chooseFace`): files that say nothing
|
|
458
|
+
// about their slant keep the layer's style, so they can.
|
|
459
|
+
italic: chooseFace(fonts, workspaceFacts, style.weight, "italic").style === "italic",
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
const file = catalogFileOf(asset.path);
|
|
463
|
+
if (file === null) return { kind: "file", family };
|
|
464
|
+
if (catalog === null) return { kind: "loading" };
|
|
465
|
+
const entry = catalogEntryOf(catalog, family, file);
|
|
466
|
+
if (entry === undefined) return { kind: "file", family };
|
|
467
|
+
const axis = entry.axes?.["wght"];
|
|
468
|
+
return {
|
|
469
|
+
kind: "catalog",
|
|
470
|
+
family: entry.family,
|
|
471
|
+
entry,
|
|
472
|
+
weights: entry.weights,
|
|
473
|
+
axis: appliesVariableFile(entry) && axis !== undefined ? [axis[0], axis[1]] : null,
|
|
474
|
+
italic: entry.styles.includes("italic"),
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const fold = (family: string): string => family.toLowerCase().replace(/\s+/gu, " ").trim();
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* The facts the Typography rows render for a selection: the selected text
|
|
482
|
+
* layers' family when they share one, `mixed` when they do not, `loading`
|
|
483
|
+
* while any of them waits on the catalog, null with no text layer selected.
|
|
484
|
+
*/
|
|
485
|
+
export function selectionFontFacts(
|
|
486
|
+
layers: readonly Layer3D[],
|
|
487
|
+
assets: Doc3D["assets"],
|
|
488
|
+
catalog: readonly GoogleFontEntry[] | null,
|
|
489
|
+
workspace: readonly WorkspaceFont[] | null,
|
|
490
|
+
): LayerFontFacts | { kind: "mixed" } | null {
|
|
491
|
+
const facts = layers
|
|
492
|
+
.filter((layer): layer is TextLayer => layer.kind === "text")
|
|
493
|
+
.map((layer) => fontFactsOf(layer, assets, catalog, workspace));
|
|
494
|
+
const first = facts[0];
|
|
495
|
+
if (first === undefined) return null;
|
|
496
|
+
if (facts.some((f) => f.kind === "loading")) return { kind: "loading" };
|
|
497
|
+
const key = (f: LayerFontFacts): string => (f.kind === "loading" ? "" : `${f.kind}:${fold(f.family)}`);
|
|
498
|
+
return facts.every((f) => key(f) === key(first)) ? first : { kind: "mixed" };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ── In this Project ────────────────────────────────────────────────────────
|
|
502
|
+
|
|
503
|
+
/** One family the Project holds files for, listed once. */
|
|
504
|
+
export interface ProjectFontFamily {
|
|
505
|
+
/** `family` case-folded: what makes two sources one row. */
|
|
506
|
+
key: string;
|
|
507
|
+
family: string;
|
|
508
|
+
/** Where its first files came from. */
|
|
509
|
+
source: "catalog" | "workspace" | "file";
|
|
510
|
+
/** Re-applies it from the Project's own files, with no fetch. */
|
|
511
|
+
pick: Extract<FontPick, { kind: "project" }>;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export interface ProjectFonts {
|
|
515
|
+
/** By family name. */
|
|
516
|
+
families: ProjectFontFamily[];
|
|
517
|
+
/** Families text layers name with no file (`drawsMachineFont`), by name,
|
|
518
|
+
* leaving out one the Project also holds files for. */
|
|
519
|
+
machine: { key: string; family: string }[];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** Every text layer in the document: each Shot's, then each component's. */
|
|
523
|
+
function textLayersOf(doc: Doc3D): TextLayer[] {
|
|
524
|
+
return [...Object.values(doc.scenesById), ...Object.values(doc.components ?? {})].flatMap((home) =>
|
|
525
|
+
Object.values(home.layersById).filter((layer): layer is TextLayer => layer.kind === "text"),
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** A file name's stem as a family: `BrandSerif-Regular.woff2` reads
|
|
530
|
+
* "BrandSerif Regular". */
|
|
531
|
+
const stemFamily = (path: string): string =>
|
|
532
|
+
storedName(path).replace(/\.[^.]*$/u, "").replace(/[-_]+/gu, " ").trim() || "Font";
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* The families a Project already holds files for, each once (case-folded):
|
|
536
|
+
* catalog copies grouped by their catalog family (one face per weight and
|
|
537
|
+
* style, its subsets together), workspace copies by the workspace list's
|
|
538
|
+
* family, then every other font asset by the brand row that names it, else
|
|
539
|
+
* its file stem. Each comes with the pick that re-applies it from those
|
|
540
|
+
* files. Without the catalog, a catalog copy's family is the one a text layer
|
|
541
|
+
* names for it, else its file name's.
|
|
542
|
+
*/
|
|
543
|
+
export function projectFontFamilies(
|
|
544
|
+
doc: Doc3D,
|
|
545
|
+
catalog: readonly GoogleFontEntry[] | null,
|
|
546
|
+
workspace: readonly WorkspaceFont[] | null,
|
|
547
|
+
): ProjectFonts {
|
|
548
|
+
const assets = Object.values(doc.assets).filter((asset) => asset.kind === "font");
|
|
549
|
+
// Workspace fonts by the `<hash8>-` their copies share (`isCopyOf`).
|
|
550
|
+
const byHash = new Map((workspace ?? []).map((font) => [font.id.slice(0, 9), font]));
|
|
551
|
+
const workspaceOf = (asset: ShotAsset): WorkspaceFont | undefined => byHash.get(asset.path.slice(0, 9));
|
|
552
|
+
// The catalog by the file-name spelling of its family, read back through
|
|
553
|
+
// the same two helpers that write and parse a catalog file's name.
|
|
554
|
+
const bySafe = new Map(
|
|
555
|
+
(catalog ?? []).map((entry) => [catalogFileOf(googleFontFileName(entry, "variable"))!.safeFamily, entry]),
|
|
556
|
+
);
|
|
557
|
+
const layers = textLayersOf(doc);
|
|
558
|
+
const usedAs = new Map<string, string>();
|
|
559
|
+
for (const layer of layers) {
|
|
560
|
+
const id = layer.text.style.fontAsset;
|
|
561
|
+
if (id !== undefined && !usedAs.has(id)) usedAs.set(id, primaryFontFamily(layer.text.style.fontFamily));
|
|
562
|
+
}
|
|
563
|
+
const branded = new Map<string, string>();
|
|
564
|
+
for (const scope of [doc.resources, ...Object.values(doc.scenesById).map((scene) => scene.resources)]) {
|
|
565
|
+
for (const item of scope?.branding ?? []) {
|
|
566
|
+
if (item.type === "asset" && item.fontFamily !== undefined && !branded.has(item.asset)) {
|
|
567
|
+
branded.set(item.asset, primaryFontFamily(item.fontFamily) || item.fontFamily);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const rows = new Map<string, ProjectFontFamily & { faces: ProjectFace[] }>();
|
|
572
|
+
const add = (family: string, source: ProjectFontFamily["source"], fallback: string, face: ProjectFace): void => {
|
|
573
|
+
const key = fold(family);
|
|
574
|
+
let row = rows.get(key);
|
|
575
|
+
if (row === undefined) {
|
|
576
|
+
const faces: ProjectFace[] = [];
|
|
577
|
+
row = { key, family, source, faces, pick: { kind: "project", family, fallback, faces } };
|
|
578
|
+
rows.set(key, row);
|
|
579
|
+
}
|
|
580
|
+
row.faces.push(face);
|
|
581
|
+
};
|
|
582
|
+
// Catalog copies: one face per (family, weight, style), the default subset
|
|
583
|
+
// first.
|
|
584
|
+
const catalogFaces = new Map<string, { entry: GoogleFontEntry | undefined; family: string; files: ShotAsset[] }>();
|
|
585
|
+
const rest: ShotAsset[] = [];
|
|
586
|
+
for (const asset of assets) {
|
|
587
|
+
const file = workspaceOf(asset) !== undefined ? null : catalogFileOf(asset.path);
|
|
588
|
+
if (file === null) {
|
|
589
|
+
rest.push(asset);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const entry = bySafe.get(file.safeFamily);
|
|
593
|
+
const faceKey = `${file.safeFamily}:${file.weight}:${file.style}`;
|
|
594
|
+
const held = catalogFaces.get(faceKey);
|
|
595
|
+
if (held !== undefined) {
|
|
596
|
+
held.files.push(asset);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const family = entry?.family ?? usedAs.get(asset.id) ?? file.safeFamily.replace(/-/gu, " ");
|
|
600
|
+
catalogFaces.set(faceKey, { entry, family, files: [asset] });
|
|
601
|
+
}
|
|
602
|
+
for (const { entry, family, files } of catalogFaces.values()) {
|
|
603
|
+
const parsed = files.map((asset) => ({ asset, file: catalogFileOf(asset.path)! }));
|
|
604
|
+
const order = entry === undefined ? [] : [entry.defSubset, ...entry.subsets];
|
|
605
|
+
// The file with no subset suffix is the default subset's, catalog or not;
|
|
606
|
+
// a subset the catalog does not list goes last.
|
|
607
|
+
const rank = (subset: string | undefined): number => {
|
|
608
|
+
if (subset === undefined) return -1;
|
|
609
|
+
const at = order.indexOf(subset);
|
|
610
|
+
return at < 0 ? order.length : at;
|
|
611
|
+
};
|
|
612
|
+
parsed.sort((a, b) => rank(a.file.subset) - rank(b.file.subset));
|
|
613
|
+
const [head, ...others] = parsed;
|
|
614
|
+
const { weight, style } = head!.file;
|
|
615
|
+
const axis = entry?.axes?.["wght"];
|
|
616
|
+
add(family, "catalog", entry === undefined ? "sans-serif" : categoryFallback(entry.category), {
|
|
617
|
+
fontAsset: head!.asset.id,
|
|
618
|
+
...(others.length === 0 ? {} : { fontSubsets: others.map((o) => o.asset.id) }),
|
|
619
|
+
...(weight === "variable" ? { axis: axis === undefined ? ([1, 1000] as const) : ([axis[0], axis[1]] as const) } : { weight }),
|
|
620
|
+
italic: style === "italic",
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
for (const asset of rest) {
|
|
624
|
+
const font = workspaceOf(asset);
|
|
625
|
+
if (font !== undefined) add(font.family, "workspace", "sans-serif", { fontAsset: asset.id, ...workspaceFacts(font) });
|
|
626
|
+
}
|
|
627
|
+
for (const asset of rest) {
|
|
628
|
+
if (workspaceOf(asset) !== undefined) continue;
|
|
629
|
+
add(branded.get(asset.id) ?? stemFamily(asset.path), "file", "sans-serif", { fontAsset: asset.id });
|
|
630
|
+
}
|
|
631
|
+
const families = [...rows.values()]
|
|
632
|
+
.map(({ faces: _faces, ...row }) => row)
|
|
633
|
+
.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
634
|
+
const machine = new Map<string, string>();
|
|
635
|
+
for (const layer of layers) {
|
|
636
|
+
if (!drawsMachineFont(layer, doc.assets)) continue;
|
|
637
|
+
const family = primaryFontFamily(layer.text.style.fontFamily);
|
|
638
|
+
const key = fold(family);
|
|
639
|
+
if (key.length > 0 && !rows.has(key) && !machine.has(key)) machine.set(key, family);
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
families,
|
|
643
|
+
machine: [...machine].map(([key, family]) => ({ key, family })).sort((a, b) => (a.key < b.key ? -1 : 1)),
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/** The other character-set files the Project holds for the face whose
|
|
648
|
+
* default file is `assetId`, or undefined when it has none: what a write
|
|
649
|
+
* that sets `fontAsset` to it sets as `fontSubsets`. */
|
|
650
|
+
export function projectSubsetsOf(doc: Doc3D, assetId: string): readonly string[] | undefined {
|
|
651
|
+
for (const family of projectFontFamilies(doc, null, null).families) {
|
|
652
|
+
const face = family.pick.faces.find((f) => f.fontAsset === assetId);
|
|
653
|
+
if (face !== undefined) return face.fontSubsets;
|
|
654
|
+
}
|
|
655
|
+
return undefined;
|
|
656
|
+
}
|