auricui-skill 0.0.0-stage → 0.1.2

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.
Files changed (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -2
  3. package/bin/install.mjs +66 -0
  4. package/package.json +29 -4
  5. package/skill/SKILL.md +64 -0
  6. package/skill/agents/openai.yaml +4 -0
  7. package/skill/assets/examples/ApprovalCard.tsx +8 -0
  8. package/skill/assets/examples/BasicApp.tsx +38 -0
  9. package/skill/assets/examples/BriefingCard.tsx +8 -0
  10. package/skill/assets/examples/ChatControls.tsx +49 -0
  11. package/skill/assets/examples/ChatPanel.tsx +73 -0
  12. package/skill/assets/examples/Checkbox.tsx +13 -0
  13. package/skill/assets/examples/Combobox.tsx +14 -0
  14. package/skill/assets/examples/Drawer.tsx +10 -0
  15. package/skill/assets/examples/MemoryCard.tsx +9 -0
  16. package/skill/assets/examples/MessageList.tsx +8 -0
  17. package/skill/assets/examples/Theme.tsx +14 -0
  18. package/skill/assets/examples/ToolCall.tsx +8 -0
  19. package/skill/assets/examples/Tree.tsx +15 -0
  20. package/skill/assets/examples/examples.css +11 -0
  21. package/skill/assets/library/auricui-0.1.0-preview.1.tgz +0 -0
  22. package/skill/assets/library/auricui-0.1.0-preview.3.tgz +0 -0
  23. package/skill/assets/library/auricui-0.1.0-preview.4.tgz +0 -0
  24. package/skill/assets/upstream/LICENSE +21 -0
  25. package/skill/assets/upstream/apps/desktop/chat/src/App.tsx +1099 -0
  26. package/skill/assets/upstream/apps/desktop/chat/src/app-bridge.ts +154 -0
  27. package/skill/assets/upstream/apps/desktop/chat/src/chat.css +1812 -0
  28. package/skill/assets/upstream/apps/desktop/chat/src/components/AddSkill.tsx +223 -0
  29. package/skill/assets/upstream/apps/desktop/chat/src/components/AgentActivityRow.tsx +716 -0
  30. package/skill/assets/upstream/apps/desktop/chat/src/components/AgentIcons.tsx +46 -0
  31. package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentCard.tsx +34 -0
  32. package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentPreview.tsx +331 -0
  33. package/skill/assets/upstream/apps/desktop/chat/src/components/BalanceNotice.tsx +22 -0
  34. package/skill/assets/upstream/apps/desktop/chat/src/components/BrandLogo.tsx +35 -0
  35. package/skill/assets/upstream/apps/desktop/chat/src/components/ComposerCard.tsx +182 -0
  36. package/skill/assets/upstream/apps/desktop/chat/src/components/ConversationSessionBar.tsx +292 -0
  37. package/skill/assets/upstream/apps/desktop/chat/src/components/CopyDiagnostics.tsx +24 -0
  38. package/skill/assets/upstream/apps/desktop/chat/src/components/Divider.tsx +47 -0
  39. package/skill/assets/upstream/apps/desktop/chat/src/components/FileChangesList.tsx +119 -0
  40. package/skill/assets/upstream/apps/desktop/chat/src/components/Markdown.tsx +198 -0
  41. package/skill/assets/upstream/apps/desktop/chat/src/components/ModelPopover.tsx +280 -0
  42. package/skill/assets/upstream/apps/desktop/chat/src/components/NoticeCard.tsx +87 -0
  43. package/skill/assets/upstream/apps/desktop/chat/src/components/Popover.tsx +195 -0
  44. package/skill/assets/upstream/apps/desktop/chat/src/components/PreviewFrame.tsx +129 -0
  45. package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectConversation.tsx +852 -0
  46. package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectStarters.tsx +118 -0
  47. package/skill/assets/upstream/apps/desktop/chat/src/components/Tooltip.tsx +191 -0
  48. package/skill/assets/upstream/apps/desktop/chat/src/components/TurnLog.tsx +583 -0
  49. package/skill/assets/upstream/apps/desktop/chat/src/components/TurnQuestionCard.tsx +133 -0
  50. package/skill/assets/upstream/apps/desktop/chat/src/components/dock-pane-bar.ts +14 -0
  51. package/skill/assets/upstream/apps/desktop/chat/src/components/project-version.ts +37 -0
  52. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/MentionMenuPlugin.tsx +372 -0
  53. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptEditor.tsx +476 -0
  54. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptPill.tsx +399 -0
  55. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptReferenceNode.tsx +164 -0
  56. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/index.ts +31 -0
  57. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/mention-menu.ts +206 -0
  58. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/prompt-text.ts +171 -0
  59. package/skill/assets/upstream/apps/desktop/chat/src/components/useStickToBottom.ts +320 -0
  60. package/skill/assets/upstream/apps/desktop/chat/src/components/useTicker.ts +55 -0
  61. package/skill/assets/upstream/apps/desktop/chat/src/i18n/index.ts +42 -0
  62. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/en-US.ts +408 -0
  63. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/es.ts +407 -0
  64. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ja.ts +407 -0
  65. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ko.ts +403 -0
  66. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/words.ts +6 -0
  67. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/zh-CN.ts +407 -0
  68. package/skill/assets/upstream/apps/desktop/chat/src/i18n/lookup.ts +12 -0
  69. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-activity-runs.ts +154 -0
  70. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-menu-rows.ts +41 -0
  71. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-refs.ts +105 -0
  72. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-transcript.ts +440 -0
  73. package/skill/assets/upstream/apps/desktop/chat/src/lib/asset-lists.ts +75 -0
  74. package/skill/assets/upstream/apps/desktop/chat/src/lib/attachment-place.ts +17 -0
  75. package/skill/assets/upstream/apps/desktop/chat/src/lib/balance.ts +16 -0
  76. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-layer.ts +10 -0
  77. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-menu.ts +18 -0
  78. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-store.ts +101 -0
  79. package/skill/assets/upstream/apps/desktop/chat/src/lib/composer-attachments.ts +189 -0
  80. package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-attachments.ts +178 -0
  81. package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-bridge.ts +291 -0
  82. package/skill/assets/upstream/apps/desktop/chat/src/lib/developer.ts +39 -0
  83. package/skill/assets/upstream/apps/desktop/chat/src/lib/esc-stop.ts +23 -0
  84. package/skill/assets/upstream/apps/desktop/chat/src/lib/file-changes.ts +32 -0
  85. package/skill/assets/upstream/apps/desktop/chat/src/lib/live-activity.ts +20 -0
  86. package/skill/assets/upstream/apps/desktop/chat/src/lib/local-agent-run.ts +1174 -0
  87. package/skill/assets/upstream/apps/desktop/chat/src/lib/local-sessions.ts +25 -0
  88. package/skill/assets/upstream/apps/desktop/chat/src/lib/model-calls.ts +87 -0
  89. package/skill/assets/upstream/apps/desktop/chat/src/lib/pretty-json.ts +12 -0
  90. package/skill/assets/upstream/apps/desktop/chat/src/lib/preview-top.ts +15 -0
  91. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-resources.ts +66 -0
  92. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-sessions.ts +66 -0
  93. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-versions.ts +116 -0
  94. package/skill/assets/upstream/apps/desktop/chat/src/lib/ref-pictures.ts +119 -0
  95. package/skill/assets/upstream/apps/desktop/chat/src/lib/relative-time.ts +46 -0
  96. package/skill/assets/upstream/apps/desktop/chat/src/lib/skill-pages.ts +29 -0
  97. package/skill/assets/upstream/apps/desktop/chat/src/lib/step-file.ts +23 -0
  98. package/skill/assets/upstream/apps/desktop/chat/src/lib/structural-share.ts +52 -0
  99. package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-film.ts +44 -0
  100. package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-transfer.ts +40 -0
  101. package/skill/assets/upstream/apps/desktop/chat/src/lib/unprompted.ts +27 -0
  102. package/skill/assets/upstream/apps/desktop/chat/src/lib/upload-limits.ts +53 -0
  103. package/skill/assets/upstream/apps/desktop/chat/src/mount.tsx +19 -0
  104. package/skill/assets/upstream/apps/desktop/chat/src/state/local-agent-store.ts +296 -0
  105. package/skill/assets/upstream/apps/desktop/chat/src/studio-refs.ts +307 -0
  106. package/skill/assets/upstream/apps/desktop/package.json +51 -0
  107. package/skill/assets/upstream/apps/desktop/shell/bridge.ts +415 -0
  108. package/skill/assets/upstream/apps/desktop/shell/index.html +27 -0
  109. package/skill/assets/upstream/apps/desktop/shell/main.tsx +17 -0
  110. package/skill/assets/upstream/apps/desktop/shell/shell.css +17 -0
  111. package/skill/assets/upstream/apps/desktop/shell/vite.config.ts +65 -0
  112. package/skill/assets/upstream/apps/desktop/src/agent-definitions.mjs +31 -0
  113. package/skill/assets/upstream/packages/openfilm/LICENSE +21 -0
  114. package/skill/assets/upstream/packages/openfilm/package.json +88 -0
  115. package/skill/assets/upstream/packages/openfilm/src/film-doc.d.mts +125 -0
  116. package/skill/assets/upstream/packages/openfilm/src/film-doc.mjs +986 -0
  117. package/skill/assets/upstream/packages/openfilm/src/overrides.mjs +73 -0
  118. package/skill/assets/upstream/packages/openfilm/src/timeline.mjs +1280 -0
  119. package/skill/assets/upstream/packages/openfilm/studio/server/bridge.js +209 -0
  120. package/skill/assets/upstream/packages/openfilm/studio/server/film-subtitle.mjs +785 -0
  121. package/skill/assets/upstream/packages/openfilm/studio/server/ops.mjs +403 -0
  122. package/skill/assets/upstream/packages/openfilm/studio/server/stage.js +1649 -0
  123. package/skill/assets/upstream/packages/openfilm/studio/server/track-layout.mjs +43 -0
  124. package/skill/assets/upstream/packages/openfilm/studio/ui/src/App.tsx +156 -0
  125. package/skill/assets/upstream/packages/openfilm/studio/ui/src/api.ts +328 -0
  126. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/AudioMeters.tsx +188 -0
  127. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockThumbs.tsx +84 -0
  128. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockWaveform.tsx +201 -0
  129. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLoadingScreen.tsx +56 -0
  130. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLogo.tsx +25 -0
  131. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Card.tsx +89 -0
  132. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ColorPicker.tsx +204 -0
  133. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ConfirmDialog.tsx +96 -0
  134. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ContextMenu.tsx +422 -0
  135. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/DesktopDialog.tsx +271 -0
  136. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ExamplesGallery.tsx +372 -0
  137. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmFullscreenBar.tsx +147 -0
  138. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmInspector.tsx +2310 -0
  139. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmStageSelect.tsx +3296 -0
  140. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FocusNav.tsx +28 -0
  141. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FontFamilyPicker.tsx +293 -0
  142. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FramePicker.tsx +69 -0
  143. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/InspectorPanel.tsx +25 -0
  144. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Launcher.tsx +38 -0
  145. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LayersPanel.tsx +277 -0
  146. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LiveTime.tsx +101 -0
  147. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPool.tsx +1413 -0
  148. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPreview.tsx +188 -0
  149. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MenuSelect.tsx +161 -0
  150. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MgOutline.tsx +24 -0
  151. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/OperationHistory.tsx +184 -0
  152. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PaneDivider.tsx +292 -0
  153. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PasteAttrsDialog.tsx +69 -0
  154. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PlaybackMenu.tsx +110 -0
  155. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Popover.tsx +159 -0
  156. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectCover.tsx +60 -0
  157. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectHistoryControls.tsx +748 -0
  158. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectLayoutMenu.tsx +82 -0
  159. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectMenu.tsx +111 -0
  160. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectSources.tsx +587 -0
  161. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectView.tsx +3222 -0
  162. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsLibrary.tsx +567 -0
  163. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsView.tsx +1593 -0
  164. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PropertiesSection.tsx +418 -0
  165. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ResourceInspector.tsx +100 -0
  166. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SelectionToolbar.tsx +157 -0
  167. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SkillsLibrary.tsx +504 -0
  168. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SourcePreview.tsx +464 -0
  169. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SpendAsks.tsx +88 -0
  170. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageMaskOverlay.tsx +299 -0
  171. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageTransformFrame.tsx +246 -0
  172. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.module.css +46 -0
  173. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.tsx +884 -0
  174. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleOverlay.tsx +148 -0
  175. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleRow.tsx +419 -0
  176. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TabStrip.tsx +188 -0
  177. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Timeline.tsx +4911 -0
  178. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TimelineMarkers.tsx +329 -0
  179. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Toast.tsx +55 -0
  180. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Tooltip.tsx +193 -0
  181. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/UpdateButton.tsx +104 -0
  182. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ViewerBar.tsx +98 -0
  183. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/WhatsNew.tsx +147 -0
  184. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/dock-pane-bar.ts +16 -0
  185. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportCenter.tsx +1502 -0
  186. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportTray.tsx +284 -0
  187. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/film-stage-hit.ts +18 -0
  188. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-fields.tsx +863 -0
  189. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-look.tsx +1357 -0
  190. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/controls-bar.tsx +397 -0
  191. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/transport.tsx +294 -0
  192. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/replace-drop.ts +20 -0
  193. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/AgentsSection.tsx +613 -0
  194. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/CustomServices.tsx +104 -0
  195. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/DiagnosticsGroup.tsx +202 -0
  196. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ModelsSection.tsx +179 -0
  197. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProviderMark.tsx +24 -0
  198. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProvidersSection.tsx +177 -0
  199. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsModal.tsx +511 -0
  200. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsSelect.tsx +84 -0
  201. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ShortcutsSection.tsx +291 -0
  202. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/StorageGroup.tsx +119 -0
  203. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/theme.ts +47 -0
  204. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ui.tsx +139 -0
  205. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/use-settings.ts +10 -0
  206. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/stage-menu.tsx +283 -0
  207. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/timeline-head-extras.tsx +91 -0
  208. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/typing-target.ts +45 -0
  209. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-editor-tools.tsx +408 -0
  210. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-page-changes.ts +48 -0
  211. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-project-history.ts +113 -0
  212. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/useBlockDrag.ts +639 -0
  213. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/Preview.tsx +426 -0
  214. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/player.ts +558 -0
  215. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/preview-frames.ts +36 -0
  216. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/steps.ts +36 -0
  217. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/undo.ts +156 -0
  218. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-film.ts +212 -0
  219. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-preview-renders.ts +89 -0
  220. package/skill/assets/upstream/packages/openfilm/studio/ui/src/globals.css +559 -0
  221. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/en.ts +1901 -0
  222. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/es.ts +1900 -0
  223. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/index.ts +130 -0
  224. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ja.ts +1900 -0
  225. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ko.ts +1896 -0
  226. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/words.ts +6 -0
  227. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/zh-CN.ts +1901 -0
  228. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/agent-row.ts +21 -0
  229. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/asset-timeline.ts +469 -0
  230. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/audio-levels.ts +60 -0
  231. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/batch-map.ts +28 -0
  232. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/browser.ts +10 -0
  233. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/canvas-fit.ts +116 -0
  234. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/chat-marks.ts +114 -0
  235. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-attrs.ts +102 -0
  236. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-fade.ts +70 -0
  237. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-look.ts +520 -0
  238. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-mask.ts +536 -0
  239. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-replace.ts +60 -0
  240. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/component-props.ts +128 -0
  241. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/context-length.ts +12 -0
  242. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/desktop-build.ts +44 -0
  243. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/diagnostics.ts +68 -0
  244. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/double-press.ts +22 -0
  245. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/dropped-overrides.ts +139 -0
  246. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-jobs.ts +191 -0
  247. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-spec.ts +261 -0
  248. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/field-input.ts +155 -0
  249. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-clip-edits.ts +82 -0
  250. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-frame.ts +134 -0
  251. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-overrides.ts +107 -0
  252. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-shape.ts +172 -0
  253. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film.ts +80 -0
  254. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-menu.ts +236 -0
  255. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-styles.ts +129 -0
  256. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/freeze-frame.ts +49 -0
  257. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/host.ts +438 -0
  258. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/inspect-scrub.ts +26 -0
  259. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap-store.ts +74 -0
  260. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap.ts +529 -0
  261. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/layers-tree.ts +133 -0
  262. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/look.ts +83 -0
  263. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/loudness.ts +58 -0
  264. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/markers.ts +152 -0
  265. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-bar.ts +58 -0
  266. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-folders.ts +45 -0
  267. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-place.ts +102 -0
  268. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-queue.ts +107 -0
  269. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-selection.ts +64 -0
  270. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/menu-position.ts +77 -0
  271. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mg-transform.ts +111 -0
  272. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mixed.ts +3 -0
  273. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/page-changes.ts +45 -0
  274. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/picture-files.ts +19 -0
  275. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-prefs.ts +46 -0
  276. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-renders.ts +71 -0
  277. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-delete.ts +20 -0
  278. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-folders.ts +144 -0
  279. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-history.ts +188 -0
  280. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-sources.ts +283 -0
  281. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/projects-page.ts +40 -0
  282. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/provider-marks.ts +35 -0
  283. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/providers.ts +163 -0
  284. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/resource-drag.ts +131 -0
  285. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/retry-image.ts +17 -0
  286. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved-insert.ts +40 -0
  287. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved.ts +125 -0
  288. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/selection-toolbar.ts +158 -0
  289. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/shortcut-hint.ts +21 -0
  290. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/similar-layers.ts +46 -0
  291. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/solo.ts +19 -0
  292. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-clip.ts +60 -0
  293. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-crop.ts +232 -0
  294. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-gesture.ts +277 -0
  295. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-layers.ts +71 -0
  296. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-mask.ts +176 -0
  297. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-measure.ts +53 -0
  298. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-types.ts +155 -0
  299. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-refs.ts +248 -0
  300. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-version.ts +41 -0
  301. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitle-cues.ts +238 -0
  302. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitles.ts +263 -0
  303. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/tab-menu.ts +6 -0
  304. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/template-controls.ts +131 -0
  305. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/theme-changes.ts +31 -0
  306. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cache.ts +63 -0
  307. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cell.ts +8 -0
  308. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-frame.ts +76 -0
  309. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-pick.ts +42 -0
  310. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timecode.ts +161 -0
  311. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-clipboard.ts +279 -0
  312. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-drag.ts +589 -0
  313. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-edit.ts +1138 -0
  314. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-keys.ts +63 -0
  315. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-layout.ts +1038 -0
  316. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-nav.ts +160 -0
  317. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-prefs.ts +73 -0
  318. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-split.ts +40 -0
  319. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-view.ts +105 -0
  320. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/title.ts +39 -0
  321. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-heights.ts +55 -0
  322. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-lock.ts +27 -0
  323. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/transitions.ts +459 -0
  324. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/type-metrics.ts +83 -0
  325. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-film-subtitles.ts +111 -0
  326. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-fonts.ts +181 -0
  327. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-loaded-thumbs.ts +91 -0
  328. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-edits.ts +134 -0
  329. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-font.ts +51 -0
  330. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-style.ts +68 -0
  331. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/waveform-columns.ts +48 -0
  332. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/web-update.ts +41 -0
  333. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/whats-new.ts +401 -0
  334. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/workspace-resources.ts +373 -0
  335. package/skill/assets/upstream/packages/openfilm/studio/ui/src/main.tsx +10 -0
  336. package/skill/assets/upstream/packages/openfilm/studio/ui/vite.config.ts +12 -0
  337. package/skill/assets/upstream/packages/shared/package.json +45 -0
  338. package/skill/assets/upstream/packages/shared/src/agent-activity.ts +263 -0
  339. package/skill/assets/upstream/packages/shared/src/contract.ts +285 -0
  340. package/skill/assets/upstream/packages/shared/src/index.ts +17 -0
  341. package/skill/assets/upstream/packages/shared/src/markdown-blocks.ts +252 -0
  342. package/skill/assets/upstream/packages/shared/src/modality.ts +4 -0
  343. package/skill/assets/upstream/packages/shared/src/prompt-reference.ts +113 -0
  344. package/skill/assets/upstream/packages/shared/src/title.ts +76 -0
  345. package/skill/assets/upstream/packages/shared/src/turn.ts +68 -0
  346. package/skill/assets/upstream/packages/shared/src/video.ts +26 -0
  347. package/skill/references/agent-components.md +33 -0
  348. package/skill/references/api-index.json +2693 -0
  349. package/skill/references/chat-components.md +65 -0
  350. package/skill/references/components.md +23 -0
  351. package/skill/references/core-components.md +44 -0
  352. package/skill/references/dependencies.md +22 -0
  353. package/skill/references/esm-package.md +48 -0
  354. package/skill/references/integration.md +43 -0
  355. package/skill/references/library-manifest.json +12 -0
  356. package/skill/references/source-manifest.json +325 -0
  357. package/skill/references/studio-components.md +24 -0
  358. package/skill/references/theme-motion.md +49 -0
  359. package/skill/references/troubleshooting.md +27 -0
  360. package/skill/references/verification.md +13 -0
  361. package/skill/scripts/check_dependencies.py +109 -0
  362. package/skill/scripts/inspect_api.py +60 -0
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The keys in use on this page: the scheme and custom schemes from Studio's prefs (`keys`, lib/keymap), read once and
3
+ * again whenever the page comes back into view (Settings may have changed them in another window), and the keymap
4
+ * they make. The key handlers read it as each key is pressed (`keyAction`); hints and Settings re-render on a change.
5
+ */
6
+ import { useMemo, useSyncExternalStore } from 'react';
7
+ import { api } from '@/api';
8
+ import { actionOf, DEFAULT_KEY_PREFS, hintOf, keyPrefsOf, keymapOf, type ActionId, type KeyPrefs, type Keymap } from './keymap.ts';
9
+ import type { KeyPress } from './timeline-keys.ts';
10
+
11
+ /** ⌘ is the modifier on a Mac, Ctrl elsewhere (as lib/shortcut-hint). */
12
+ export const MAC = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform);
13
+
14
+ let prefs: KeyPrefs = DEFAULT_KEY_PREFS;
15
+ let keymap: Keymap = keymapOf(prefs, MAC);
16
+ const listeners = new Set<() => void>();
17
+ let loaded = false;
18
+
19
+ function set(next: KeyPrefs): void {
20
+ if (JSON.stringify(next) === JSON.stringify(prefs)) return;
21
+ prefs = next;
22
+ keymap = keymapOf(next, MAC);
23
+ for (const fn of listeners) fn();
24
+ }
25
+
26
+ function load(): void {
27
+ void api.prefs().then((p) => set(keyPrefsOf(p.keys)), () => { /* Studio not reachable: the keys as they are */ });
28
+ }
29
+
30
+ function ensure(): void {
31
+ if (loaded || typeof window === 'undefined') return;
32
+ loaded = true;
33
+ load();
34
+ window.addEventListener('focus', load);
35
+ }
36
+
37
+ /** The keys in use. */
38
+ export function currentKeymap(): Keymap {
39
+ ensure();
40
+ return keymap;
41
+ }
42
+
43
+ /** The action a key press is, with the keys in use; null when it is none. */
44
+ export const keyAction = (e: KeyPress): ActionId | null => actionOf(e, currentKeymap(), MAC);
45
+
46
+ /** The schemes as kept. */
47
+ export function currentKeyPrefs(): KeyPrefs {
48
+ ensure();
49
+ return prefs;
50
+ }
51
+
52
+ /** Keep the schemes (Settings' Save): in use at once here, and in Studio's prefs for every window and next time. */
53
+ export async function saveKeyPrefs(next: KeyPrefs): Promise<void> {
54
+ set(next);
55
+ await api.setPrefs({ keys: next });
56
+ }
57
+
58
+ function subscribe(fn: () => void): () => void {
59
+ ensure();
60
+ listeners.add(fn);
61
+ return () => { listeners.delete(fn); };
62
+ }
63
+
64
+ /** The schemes as kept, kept up to date. */
65
+ export const useKeyPrefs = (): KeyPrefs => useSyncExternalStore(subscribe, currentKeyPrefs, () => DEFAULT_KEY_PREFS);
66
+
67
+ /** The keys in use, kept up to date. */
68
+ export const useKeymap = (): Keymap => useSyncExternalStore(subscribe, currentKeymap, currentKeymap);
69
+
70
+ /** A hint for an action (its first key, ⌘B on a Mac and Ctrl+B elsewhere), with the keys in use; undefined: it has none. */
71
+ export function useKeyHint(): (id: ActionId) => string | undefined {
72
+ const map = useKeymap();
73
+ return useMemo(() => (id: ActionId) => hintOf(map, id, MAC), [map]);
74
+ }
@@ -0,0 +1,529 @@
1
+ /**
2
+ * The editor's keys, in one table: every action has an id, a tab in Settings › Keyboard shortcuts, and its default
3
+ * keys. The key handlers (`actionOf`), Settings' list and the hints in menus and tooltips (`hintOf`) all read it.
4
+ *
5
+ * A scheme gives the actions their keys: OpenFilm's own (the defaults), Premiere Pro's and Final Cut Pro X's (from each
6
+ * app's default keyboard shortcuts, mapped onto the actions Studio has), or one of three custom ones. A built-in scheme
7
+ * is read-only; a custom one starts as a copy of a scheme and can change any key.
8
+ *
9
+ * A key combination is a string: its modifiers in a fixed order, then the key, joined by "+" (`mod+shift+b`). `mod` is
10
+ * ⌘ on a Mac and Ctrl elsewhere; `ctrl` is the Control key itself, which off a Mac is the same key as `mod`. Letters
11
+ * are read by the letter they type (so they follow the keyboard's layout), or by where they sit on a US keyboard on a
12
+ * layout without Latin letters; digits and punctuation by where they sit.
13
+ */
14
+ import { letterOf, type KeyPress } from './timeline-keys.ts';
15
+
16
+ export type KeyTab = 'timeline' | 'player' | 'basic' | 'other';
17
+ export const KEY_TABS: readonly KeyTab[] = ['timeline', 'player', 'basic', 'other'];
18
+
19
+ /** The actions, in the order Settings lists them. */
20
+ export const ACTIONS = [
21
+ /* the timeline */
22
+ { id: 'split', tab: 'timeline', keys: ['mod+b'] },
23
+ { id: 'split-all', tab: 'timeline', keys: ['mod+shift+b', 'mod+k'] },
24
+ { id: 'select-mode', tab: 'timeline', keys: ['a', 'v'] },
25
+ { id: 'split-mode', tab: 'timeline', keys: ['b'] },
26
+ { id: 'ripple-start', tab: 'timeline', keys: ['q'] },
27
+ { id: 'ripple-end', tab: 'timeline', keys: ['w'] },
28
+ { id: 'select-left', tab: 'timeline', keys: ['['] },
29
+ { id: 'select-right', tab: 'timeline', keys: [']'] },
30
+ { id: 'nudge-back', tab: 'timeline', keys: ['alt+ArrowLeft'] },
31
+ { id: 'nudge-forward', tab: 'timeline', keys: ['alt+ArrowRight'] },
32
+ { id: 'toggle-magnet', tab: 'timeline', keys: ['p', 'shift+n'] },
33
+ { id: 'toggle-snap', tab: 'timeline', keys: ['n'] },
34
+ { id: 'toggle-linkage', tab: 'timeline', keys: ['`', 'shift+`'] },
35
+ { id: 'toggle-preview-axis', tab: 'timeline', keys: ['s'] },
36
+ { id: 'zoom-in', tab: 'timeline', keys: ['mod++', 'mod+='] },
37
+ { id: 'zoom-out', tab: 'timeline', keys: ['mod+-'] },
38
+ { id: 'zoom-reset', tab: 'timeline', keys: ['mod+0'] },
39
+ { id: 'zoom-fit', tab: 'timeline', keys: ['shift+z'] },
40
+ { id: 'add-marker', tab: 'timeline', keys: ['m'] },
41
+ { id: 'next-marker', tab: 'timeline', keys: ['shift+m'] },
42
+ { id: 'prev-marker', tab: 'timeline', keys: ['alt+m', 'alt+shift+m'] },
43
+ { id: 'speed', tab: 'timeline', keys: ['mod+r'] },
44
+ { id: 'detach-audio', tab: 'timeline', keys: ['ctrl+shift+s'] },
45
+ /* the player */
46
+ { id: 'play-pause', tab: 'player', keys: ['Space'] },
47
+ { id: 'play-range', tab: 'player', keys: ['shift+Space'] },
48
+ { id: 'loop', tab: 'player', keys: ['shift+l'] },
49
+ { id: 'shuttle-back', tab: 'player', keys: ['j'] },
50
+ { id: 'shuttle-stop', tab: 'player', keys: ['k'] },
51
+ { id: 'shuttle-forward', tab: 'player', keys: ['l'] },
52
+ { id: 'frame-back', tab: 'player', keys: ['ArrowLeft'] },
53
+ { id: 'frame-forward', tab: 'player', keys: ['ArrowRight'] },
54
+ { id: 'jump-back', tab: 'player', keys: ['shift+ArrowLeft'] },
55
+ { id: 'jump-forward', tab: 'player', keys: ['shift+ArrowRight'] },
56
+ { id: 'prev-cut', tab: 'player', keys: ['ArrowUp'] },
57
+ { id: 'next-cut', tab: 'player', keys: ['ArrowDown'] },
58
+ { id: 'start', tab: 'player', keys: ['Home'] },
59
+ { id: 'end', tab: 'player', keys: ['End'] },
60
+ { id: 'mark-in', tab: 'player', keys: ['i'] },
61
+ { id: 'mark-out', tab: 'player', keys: ['o'] },
62
+ { id: 'clear-range', tab: 'player', keys: ['x', 'alt+x'] },
63
+ { id: 'range-clip', tab: 'player', keys: ['shift+x'] },
64
+ { id: 'go-in', tab: 'player', keys: ['shift+i'] },
65
+ { id: 'go-out', tab: 'player', keys: ['shift+o'] },
66
+ { id: 'insert-source', tab: 'player', keys: [','] },
67
+ { id: 'overwrite-source', tab: 'player', keys: ['.'] },
68
+ /* the basics */
69
+ { id: 'undo', tab: 'basic', keys: ['mod+z'] },
70
+ { id: 'redo', tab: 'basic', keys: ['mod+shift+z'] },
71
+ { id: 'cut', tab: 'basic', keys: ['mod+x'] },
72
+ { id: 'copy', tab: 'basic', keys: ['mod+c'] },
73
+ { id: 'paste', tab: 'basic', keys: ['mod+v'] },
74
+ { id: 'duplicate', tab: 'basic', keys: ['mod+d'] },
75
+ { id: 'remove', tab: 'basic', keys: ['Backspace', 'Delete'] },
76
+ { id: 'ripple-delete', tab: 'basic', keys: ['shift+Backspace', 'shift+Delete'] },
77
+ { id: 'select-all', tab: 'basic', keys: ['mod+a'] },
78
+ { id: 'copy-attrs', tab: 'basic', keys: ['mod+shift+c'] },
79
+ { id: 'paste-attrs', tab: 'basic', keys: ['mod+shift+v'] },
80
+ /* the rest */
81
+ { id: 'refer', tab: 'other', keys: ['mod+l'] },
82
+ ] as const satisfies readonly { id: string; tab: KeyTab; keys: readonly string[] }[];
83
+
84
+ export type ActionId = (typeof ACTIONS)[number]['id'];
85
+ export type Action = { id: ActionId; tab: KeyTab; keys: readonly string[] };
86
+ export const ACTION_IDS: readonly ActionId[] = ACTIONS.map((a) => a.id);
87
+ const KNOWN = new Set<string>(ACTION_IDS);
88
+
89
+ /**
90
+ * Keys that do one thing wherever they are and are not set by a scheme: listed under Other, as Settings has always
91
+ * listed them. `keys` as combinations; a modifier held during a drag is the modifier alone (`alt+`).
92
+ */
93
+ export const FIXED_KEYS: readonly { label: string; keys: readonly string[] }[] = [
94
+ { label: 'deselect', keys: ['Escape'] },
95
+ { label: 'addMedia', keys: ['Enter'] },
96
+ { label: 'splitModeAll', keys: ['shift+'] },
97
+ { label: 'editModeSwap', keys: ['alt+'] },
98
+ { label: 'copyDrag', keys: ['alt+'] },
99
+ { label: 'nudge', keys: ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'] },
100
+ { label: 'nudgeFast', keys: ['shift+ArrowUp', 'shift+ArrowDown', 'shift+ArrowLeft', 'shift+ArrowRight'] },
101
+ { label: 'editText', keys: ['Enter'] },
102
+ { label: 'commitText', keys: ['mod+Enter'] },
103
+ { label: 'cancelText', keys: ['Escape'] },
104
+ ];
105
+
106
+ /* ── schemes ── */
107
+
108
+ export type BuiltInScheme = 'openfilm' | 'premiere' | 'fcp';
109
+ export type CustomSlot = 'custom1' | 'custom2' | 'custom3';
110
+ export type SchemeId = BuiltInScheme | CustomSlot;
111
+ export const BUILT_IN_SCHEMES: readonly BuiltInScheme[] = ['openfilm', 'premiere', 'fcp'];
112
+ export const CUSTOM_SLOTS: readonly CustomSlot[] = ['custom1', 'custom2', 'custom3'];
113
+ export const isBuiltIn = (id: SchemeId): id is BuiltInScheme => (BUILT_IN_SCHEMES as readonly string[]).includes(id);
114
+
115
+ /** An action's keys in a scheme: the same everywhere, or one list for a Mac and another for Windows and Linux. */
116
+ type Binding = readonly string[] | { mac: readonly string[]; other: readonly string[] };
117
+
118
+ /**
119
+ * Premiere Pro's default keyboard shortcuts (Adobe's "Default keyboard shortcuts" list), where Studio has the action.
120
+ * Where Windows and macOS differ, both are given.
121
+ */
122
+ const PREMIERE: Partial<Record<ActionId, Binding>> = {
123
+ 'play-pause': ['Space'],
124
+ /* Play In to Out */
125
+ 'play-range': { mac: ['alt+k'], other: ['mod+shift+Space'] },
126
+ 'shuttle-back': ['j'],
127
+ 'shuttle-stop': ['k'],
128
+ 'shuttle-forward': ['l'],
129
+ 'frame-back': ['ArrowLeft'],
130
+ 'frame-forward': ['ArrowRight'],
131
+ 'jump-back': ['shift+ArrowLeft'],
132
+ 'jump-forward': ['shift+ArrowRight'],
133
+ /* Go to Previous / Next Edit Point */
134
+ 'prev-cut': ['ArrowUp'],
135
+ 'next-cut': ['ArrowDown'],
136
+ start: ['Home'],
137
+ end: ['End'],
138
+ 'mark-in': ['i'],
139
+ 'mark-out': ['o'],
140
+ 'clear-range': { mac: ['alt+x'], other: ['mod+shift+x'] },
141
+ /* Mark Selection */
142
+ 'range-clip': ['/'],
143
+ 'go-in': ['shift+i'],
144
+ 'go-out': ['shift+o'],
145
+ 'insert-source': [','],
146
+ 'overwrite-source': ['.'],
147
+ /* Add Edit, Add Edit to All Tracks */
148
+ split: ['mod+k'],
149
+ 'split-all': ['mod+shift+k'],
150
+ /* the Selection and Razor tools */
151
+ 'select-mode': ['v'],
152
+ 'split-mode': ['c'],
153
+ /* Ripple Trim Previous / Next Edit to Playhead */
154
+ 'ripple-start': ['q'],
155
+ 'ripple-end': ['w'],
156
+ /* Nudge Clip Selection Left / Right One Frame */
157
+ 'nudge-back': { mac: ['mod+ArrowLeft'], other: ['alt+ArrowLeft'] },
158
+ 'nudge-forward': { mac: ['mod+ArrowRight'], other: ['alt+ArrowRight'] },
159
+ /* Snap in Timeline */
160
+ 'toggle-snap': ['s'],
161
+ 'zoom-in': ['='],
162
+ 'zoom-out': ['-'],
163
+ /* Zoom to Sequence */
164
+ 'zoom-fit': ['\\'],
165
+ 'add-marker': ['m'],
166
+ 'next-marker': ['shift+m'],
167
+ 'prev-marker': ['mod+shift+m'],
168
+ /* Speed/Duration */
169
+ speed: ['mod+r'],
170
+ undo: ['mod+z'],
171
+ redo: ['mod+shift+z'],
172
+ cut: ['mod+x'],
173
+ copy: ['mod+c'],
174
+ paste: ['mod+v'],
175
+ duplicate: ['mod+shift+/'],
176
+ /* Clear */
177
+ remove: ['Backspace', 'Delete'],
178
+ 'ripple-delete': ['shift+Delete', 'alt+Backspace'],
179
+ 'select-all': ['mod+a'],
180
+ 'paste-attrs': ['mod+alt+v'],
181
+ };
182
+
183
+ /** Final Cut Pro's default keyboard shortcuts (Apple's "Keyboard shortcuts in Final Cut Pro"), where Studio has the action. */
184
+ const FINAL_CUT: Partial<Record<ActionId, Binding>> = {
185
+ 'play-pause': ['Space'],
186
+ /* Play Selection */
187
+ 'play-range': ['/'],
188
+ /* Loop Playback */
189
+ loop: ['mod+l'],
190
+ 'shuttle-back': ['j'],
191
+ 'shuttle-stop': ['k'],
192
+ 'shuttle-forward': ['l'],
193
+ 'frame-back': ['ArrowLeft'],
194
+ 'frame-forward': ['ArrowRight'],
195
+ 'jump-back': ['shift+ArrowLeft'],
196
+ 'jump-forward': ['shift+ArrowRight'],
197
+ /* Go to Previous / Next Edit */
198
+ 'prev-cut': ['ArrowUp', ';'],
199
+ 'next-cut': ['ArrowDown', "'"],
200
+ start: ['Home'],
201
+ end: ['End'],
202
+ /* Set Range Start / End, Clear Selected Ranges, Select Clip Range, Go to Range Start / End */
203
+ 'mark-in': ['i'],
204
+ 'mark-out': ['o'],
205
+ 'clear-range': ['alt+x'],
206
+ 'range-clip': ['x'],
207
+ 'go-in': ['shift+i'],
208
+ 'go-out': ['shift+o'],
209
+ 'insert-source': ['w'],
210
+ 'overwrite-source': ['d'],
211
+ /* Blade, Blade All */
212
+ split: ['mod+b'],
213
+ 'split-all': ['mod+shift+b'],
214
+ /* the Select and Blade tools */
215
+ 'select-mode': ['a'],
216
+ 'split-mode': ['b'],
217
+ /* Trim Start, Trim End */
218
+ 'ripple-start': ['alt+['],
219
+ 'ripple-end': ['alt+]'],
220
+ /* Nudge Left / Right */
221
+ 'nudge-back': [','],
222
+ 'nudge-forward': ['.'],
223
+ /* Snapping, Skimming */
224
+ 'toggle-snap': ['n'],
225
+ 'toggle-preview-axis': ['s'],
226
+ 'zoom-in': ['mod++', 'mod+='],
227
+ 'zoom-out': ['mod+-'],
228
+ 'zoom-fit': ['shift+z'],
229
+ 'add-marker': ['m'],
230
+ 'next-marker': ["ctrl+'"],
231
+ 'prev-marker': ['ctrl+;'],
232
+ /* Show/Hide Retime Editor */
233
+ speed: ['mod+r'],
234
+ 'detach-audio': ['ctrl+shift+s'],
235
+ undo: ['mod+z'],
236
+ redo: ['mod+shift+z'],
237
+ cut: ['mod+x'],
238
+ copy: ['mod+c'],
239
+ paste: ['mod+v'],
240
+ duplicate: ['mod+d'],
241
+ /* Delete closes the gap; Replace with Gap leaves one */
242
+ 'ripple-delete': ['Backspace', 'Delete'],
243
+ remove: ['shift+Backspace', 'shift+Delete'],
244
+ 'select-all': ['mod+a'],
245
+ 'paste-attrs': ['mod+shift+v'],
246
+ };
247
+
248
+ const SCHEMES: Record<BuiltInScheme, Partial<Record<ActionId, Binding>>> = { openfilm: {}, premiere: PREMIERE, fcp: FINAL_CUT };
249
+
250
+ /** Every action's keys. */
251
+ export type Keymap = Record<ActionId, readonly string[]>;
252
+
253
+ /* ── key combinations ── */
254
+
255
+ const MODIFIERS = ['ctrl', 'mod', 'alt', 'shift'] as const;
256
+ type Modifier = (typeof MODIFIERS)[number];
257
+
258
+ /** A combination's modifiers and key (`shift+` is ⇧ alone, held during a drag); null when it is not one. */
259
+ export function parseCombo(combo: string): { mods: Set<Modifier>; key: string } | null {
260
+ const m = /^((?:(?:ctrl|mod|alt|shift)\+)*)(.*)$/.exec(combo);
261
+ if (!m) return null;
262
+ const mods = new Set(m[1]!.split('+').filter(Boolean) as Modifier[]);
263
+ return { mods, key: m[2]! };
264
+ }
265
+
266
+ const comboOfParts = (mods: Set<Modifier>, key: string): string => [...MODIFIERS.filter((x) => mods.has(x)), key].join('+');
267
+
268
+ /** A combination as this platform presses it: off a Mac, Control is the modifier, so `ctrl` is `mod`. */
269
+ export function platformCombo(combo: string, mac: boolean): string {
270
+ const parsed = parseCombo(combo);
271
+ if (!parsed) return combo;
272
+ if (!mac && parsed.mods.delete('ctrl')) parsed.mods.add('mod');
273
+ return comboOfParts(parsed.mods, parsed.key);
274
+ }
275
+
276
+ const NAMED = /^(?:Arrow(?:Left|Right|Up|Down)|Home|End|PageUp|PageDown|Backspace|Delete|Enter|Escape|Tab|Space|Insert|F(?:[1-9]|1[0-9]))$/;
277
+
278
+ /** A combination a key can be set to: modifiers in order, then a single character or a named key. */
279
+ export function isCombo(combo: unknown): combo is string {
280
+ if (typeof combo !== 'string' || combo.length > 40) return false;
281
+ const parsed = parseCombo(combo);
282
+ if (!parsed || comboOfParts(parsed.mods, parsed.key) !== combo) return false;
283
+ return (parsed.key.length === 1 && parsed.key !== ' ' && parsed.key === parsed.key.toLowerCase()) || NAMED.test(parsed.key);
284
+ }
285
+
286
+ /* punctuation read by where it sits (US keyboard), and what its key types with ⇧ */
287
+ const CODE_KEYS: Record<string, string> = {
288
+ Backquote: '`', Minus: '-', Equal: '=', BracketLeft: '[', BracketRight: ']', Backslash: '\\', Semicolon: ';', Quote: "'",
289
+ Comma: ',', Period: '.', Slash: '/',
290
+ Digit0: '0', Digit1: '1', Digit2: '2', Digit3: '3', Digit4: '4', Digit5: '5', Digit6: '6', Digit7: '7', Digit8: '8', Digit9: '9',
291
+ };
292
+ const PUNCTUATION = new Set(Object.values(CODE_KEYS).filter((k) => !/\d/.test(k)));
293
+ const SHIFTED: Record<string, string> = {
294
+ '~': '`', _: '-', '{': '[', '}': ']', '|': '\\', ':': ';', '"': "'", '<': ',', '>': '.', '?': '/',
295
+ '!': '1', '@': '2', '#': '3', $: '4', '%': '5', '^': '6', '&': '7', '*': '8', '(': '9', ')': '0',
296
+ };
297
+ const MODIFIER_KEYS = new Set(['Shift', 'Control', 'Alt', 'Meta', 'CapsLock', 'OS', 'Fn', 'Hyper', 'Super', 'AltGraph']);
298
+
299
+ /** The key of a press, as combinations name it. */
300
+ function keyName(e: Pick<KeyPress, 'key' | 'code' | 'shiftKey' | 'altKey'>): string {
301
+ if (e.key === ' ' || e.code === 'Space') return 'Space';
302
+ const letter = letterOf(e);
303
+ if (/^[a-z]$/.test(letter)) return letter;
304
+ if (e.key === '+') return '+';
305
+ /* punctuation the layout types where it is, unshifted: read as typed */
306
+ if (e.key.length === 1 && !e.shiftKey && !e.altKey && PUNCTUATION.has(e.key)) return e.key;
307
+ const byCode = CODE_KEYS[e.code ?? ''];
308
+ if (byCode) return byCode;
309
+ if (e.key.length === 1) return SHIFTED[e.key] ?? e.key.toLowerCase();
310
+ return e.key === 'Esc' ? 'Escape' : e.key;
311
+ }
312
+
313
+ /**
314
+ * The combination a key press is, or null (a modifier alone, or the other platform's modifier: the Windows key is no
315
+ * shortcut off a Mac). `+` is the same with or without ⇧ (⌘+ is ⌘⇧= on a US keyboard).
316
+ */
317
+ export function comboOf(e: KeyPress, mac: boolean): string | null {
318
+ if (MODIFIER_KEYS.has(e.key) || e.key === 'Dead' || e.key === 'Unidentified' || !e.key) return null;
319
+ if (!mac && e.metaKey) return null;
320
+ const key = keyName(e);
321
+ const mods = new Set<Modifier>();
322
+ if (mac && e.ctrlKey) mods.add('ctrl');
323
+ if (mac ? e.metaKey : e.ctrlKey) mods.add('mod');
324
+ if (e.altKey) mods.add('alt');
325
+ if (e.shiftKey && key !== '+') mods.add('shift');
326
+ return comboOfParts(mods, key);
327
+ }
328
+
329
+ /* ── keymaps ── */
330
+
331
+ const bindingFor = (b: Binding, mac: boolean): readonly string[] => ('mac' in b ? (mac ? b.mac : b.other) : b);
332
+
333
+ /**
334
+ * Every action's keys under `base`, then `own` (an action's keys; missing: base's). An action without its own keys
335
+ * keeps base's, less any key the actions given their own already use: no key does two things.
336
+ */
337
+ function layered(base: (id: ActionId) => readonly string[], own: Partial<Record<ActionId, readonly string[]>>, mac: boolean): Keymap {
338
+ const map = {} as Record<ActionId, readonly string[]>;
339
+ const taken = new Set<string>();
340
+ for (const id of ACTION_IDS) {
341
+ const keys = own[id];
342
+ if (!keys) continue;
343
+ map[id] = keys;
344
+ for (const combo of keys) taken.add(platformCombo(combo, mac));
345
+ }
346
+ for (const id of ACTION_IDS) {
347
+ if (map[id]) continue;
348
+ const keys = base(id).filter((combo) => !taken.has(platformCombo(combo, mac)));
349
+ map[id] = keys;
350
+ for (const combo of keys) taken.add(platformCombo(combo, mac));
351
+ }
352
+ return map;
353
+ }
354
+
355
+ const DEFAULTS = Object.fromEntries(ACTIONS.map((a) => [a.id, a.keys])) as unknown as Keymap;
356
+
357
+ /**
358
+ * A built-in scheme's keys on this platform. Actions the scheme's app has take its keys; the rest keep OpenFilm's,
359
+ * unless the app uses that key for another of Studio's actions.
360
+ */
361
+ export function schemeKeymap(id: BuiltInScheme, mac: boolean): Keymap {
362
+ const own = SCHEMES[id];
363
+ const keys = Object.fromEntries(Object.entries(own).map(([action, b]) => [action, bindingFor(b, mac)])) as Partial<Record<ActionId, readonly string[]>>;
364
+ return layered((action) => DEFAULTS[action], keys, mac);
365
+ }
366
+
367
+ /** A custom scheme: the scheme it was copied from (whose keys fill in actions it has no word on), and its keys. */
368
+ export interface CustomScheme { base: BuiltInScheme; keys: Partial<Record<ActionId, readonly string[]>> }
369
+
370
+ /** A custom scheme's keys. An action added after it was made takes its base's keys, unless one of them is used. */
371
+ export function customKeymap(custom: CustomScheme, mac: boolean): Keymap {
372
+ const base = schemeKeymap(custom.base, mac);
373
+ return layered((id) => base[id], custom.keys, mac);
374
+ }
375
+
376
+ /* ── finding, showing and changing keys ── */
377
+
378
+ const indexes = new WeakMap<Keymap, Map<string, ActionId>>();
379
+ const indexOf = (keymap: Keymap, mac: boolean): Map<string, ActionId> => {
380
+ let index = indexes.get(keymap);
381
+ if (!index) {
382
+ index = new Map();
383
+ for (const id of ACTION_IDS) for (const combo of keymap[id]) {
384
+ const at = platformCombo(combo, mac);
385
+ if (!index.has(at)) index.set(at, id);
386
+ }
387
+ indexes.set(keymap, index);
388
+ }
389
+ return index;
390
+ };
391
+
392
+ /** The action a key press is in `keymap`, or null. */
393
+ export function actionOf(e: KeyPress, keymap: Keymap, mac: boolean): ActionId | null {
394
+ const combo = comboOf(e, mac);
395
+ return combo ? indexOf(keymap, mac).get(combo) ?? null : null;
396
+ }
397
+
398
+ /** Whether a press is one of an action's keys. */
399
+ export const pressIs = (e: KeyPress, id: ActionId, keymap: Keymap, mac: boolean): boolean => actionOf(e, keymap, mac) === id;
400
+
401
+ /** The other action `combo` already does in `keymap` (not `id` itself), or null. */
402
+ export function conflictOf(keymap: Keymap, id: ActionId, combo: string, mac: boolean): ActionId | null {
403
+ const at = platformCombo(combo, mac);
404
+ for (const other of ACTION_IDS) {
405
+ if (other !== id && keymap[other].some((c) => platformCombo(c, mac) === at)) return other;
406
+ }
407
+ return null;
408
+ }
409
+
410
+ /** `id` set to `combo` alone; any other action that had it loses it. */
411
+ export function assignKey(keymap: Keymap, id: ActionId, combo: string, mac: boolean): Keymap {
412
+ const at = platformCombo(combo, mac);
413
+ const next = { ...keymap } as Record<ActionId, readonly string[]>;
414
+ for (const other of ACTION_IDS) {
415
+ if (other !== id && next[other].some((c) => platformCombo(c, mac) === at)) next[other] = next[other].filter((c) => platformCombo(c, mac) !== at);
416
+ }
417
+ next[id] = [combo];
418
+ return next;
419
+ }
420
+
421
+ const KEY_LABELS: Record<string, [mac: string, other: string]> = {
422
+ ArrowLeft: ['←', '←'], ArrowRight: ['→', '→'], ArrowUp: ['↑', '↑'], ArrowDown: ['↓', '↓'],
423
+ Backspace: ['⌫', 'Backspace'], Delete: ['⌦', 'Delete'], Escape: ['Esc', 'Esc'], Enter: ['Enter', 'Enter'],
424
+ Space: ['Space', 'Space'], PageUp: ['Page Up', 'Page Up'], PageDown: ['Page Down', 'Page Down'], Tab: ['Tab', 'Tab'],
425
+ '-': ['−', '−'], '`': ['~', '~'],
426
+ };
427
+ const MOD_LABELS: Record<Modifier, [mac: string, other: string]> = { ctrl: ['⌃', 'Ctrl'], mod: ['⌘', 'Ctrl'], alt: ['⌥', 'Alt'], shift: ['⇧', 'Shift'] };
428
+
429
+ /** A combination as key caps on this platform: its modifiers, then its key (none for a modifier held alone). */
430
+ export function comboCaps(combo: string, mac: boolean): string[] {
431
+ const parsed = parseCombo(platformCombo(combo, mac));
432
+ if (!parsed) return [combo];
433
+ const pick = (labels: [string, string]) => labels[mac ? 0 : 1];
434
+ const mods = MODIFIERS.filter((m) => parsed.mods.has(m)).map((m) => pick(MOD_LABELS[m]));
435
+ if (!parsed.key) return mods;
436
+ const known = KEY_LABELS[parsed.key];
437
+ return [...mods, known ? pick(known) : parsed.key.length === 1 ? parsed.key.toUpperCase() : parsed.key];
438
+ }
439
+
440
+ /** A combination as one hint: ⌘⇧B on a Mac, Ctrl+Shift+B elsewhere (as lib/shortcut-hint writes them). */
441
+ export const comboText = (combo: string, mac: boolean): string => comboCaps(combo, mac).join(mac ? '' : '+');
442
+
443
+ /** An action's first key as a hint, or undefined when it has none. */
444
+ export function hintOf(keymap: Keymap, id: ActionId, mac: boolean): string | undefined {
445
+ const combo = keymap[id][0];
446
+ return combo ? comboText(combo, mac) : undefined;
447
+ }
448
+
449
+ /* ── what is kept ── */
450
+
451
+ /** The scheme in use and the custom ones, as Studio's prefs keep them (`keys`). */
452
+ export interface KeyPrefs { scheme: SchemeId; custom: Partial<Record<CustomSlot, CustomScheme>> }
453
+ export const DEFAULT_KEY_PREFS: KeyPrefs = { scheme: 'openfilm', custom: {} };
454
+
455
+ /** What is kept, each part checked: an unknown scheme is OpenFilm's; unknown actions and keys that are none are left out. */
456
+ export function keyPrefsOf(raw: unknown): KeyPrefs {
457
+ const p = raw && typeof raw === 'object' ? raw as { scheme?: unknown; custom?: unknown } : {};
458
+ const custom: KeyPrefs['custom'] = {};
459
+ const given = p.custom && typeof p.custom === 'object' ? p.custom as Record<string, unknown> : {};
460
+ for (const slot of CUSTOM_SLOTS) {
461
+ const one = given[slot] as { base?: unknown; keys?: unknown } | undefined;
462
+ if (!one || typeof one !== 'object') continue;
463
+ const base = (BUILT_IN_SCHEMES as readonly unknown[]).includes(one.base) ? one.base as BuiltInScheme : 'openfilm';
464
+ const keys: CustomScheme['keys'] = {};
465
+ if (one.keys && typeof one.keys === 'object') {
466
+ for (const [id, list] of Object.entries(one.keys as Record<string, unknown>)) {
467
+ if (KNOWN.has(id) && Array.isArray(list)) keys[id as ActionId] = list.filter(isCombo).slice(0, 8);
468
+ }
469
+ }
470
+ custom[slot] = { base, keys };
471
+ }
472
+ const scheme = ([...BUILT_IN_SCHEMES, ...CUSTOM_SLOTS] as readonly unknown[]).includes(p.scheme) ? p.scheme as SchemeId : 'openfilm';
473
+ return { scheme: isBuiltIn(scheme) || custom[scheme] ? scheme : 'openfilm', custom };
474
+ }
475
+
476
+ /** The keys of a scheme (an empty custom slot: OpenFilm's). */
477
+ export function keymapFor(prefs: KeyPrefs, id: SchemeId, mac: boolean): Keymap {
478
+ if (isBuiltIn(id)) return schemeKeymap(id, mac);
479
+ const custom = prefs.custom[id];
480
+ return custom ? customKeymap(custom, mac) : schemeKeymap('openfilm', mac);
481
+ }
482
+
483
+ /** The keys in use. */
484
+ export const keymapOf = (prefs: KeyPrefs, mac: boolean): Keymap => keymapFor(prefs, prefs.scheme, mac);
485
+
486
+ /** A copy of scheme `from`, as a custom scheme: all its keys, and the built-in scheme it comes from. */
487
+ export function copyOf(prefs: KeyPrefs, from: SchemeId, mac: boolean): CustomScheme {
488
+ const keymap = keymapFor(prefs, from, mac);
489
+ const base = isBuiltIn(from) ? from : prefs.custom[from]?.base ?? 'openfilm';
490
+ return { base, keys: Object.fromEntries(ACTION_IDS.map((id) => [id, [...keymap[id]]])) };
491
+ }
492
+
493
+ /** The first custom slot nothing is kept in, or null. */
494
+ export const freeSlot = (prefs: KeyPrefs): CustomSlot | null => CUSTOM_SLOTS.find((slot) => !prefs.custom[slot]) ?? null;
495
+
496
+ /**
497
+ * Ready to change a key: a custom scheme is changed itself; a built-in one is first copied into a free custom slot,
498
+ * which becomes the scheme (`copiedTo` says which). Null: every custom slot is taken, so there is none to copy into.
499
+ */
500
+ export function editable(prefs: KeyPrefs, mac: boolean): { prefs: KeyPrefs; slot: CustomSlot; copiedTo: CustomSlot | null } | null {
501
+ if (!isBuiltIn(prefs.scheme)) {
502
+ const slot = prefs.scheme;
503
+ if (prefs.custom[slot]) return { prefs, slot, copiedTo: null };
504
+ return { prefs: { ...prefs, custom: { ...prefs.custom, [slot]: copyOf(prefs, 'openfilm', mac) } }, slot, copiedTo: null };
505
+ }
506
+ const slot = freeSlot(prefs);
507
+ if (!slot) return null;
508
+ return { prefs: { scheme: slot, custom: { ...prefs.custom, [slot]: copyOf(prefs, prefs.scheme, mac) } }, slot, copiedTo: slot };
509
+ }
510
+
511
+ /** A custom scheme set back to the keys of the scheme it was copied from. */
512
+ export function restoreDefaults(prefs: KeyPrefs, slot: CustomSlot, mac: boolean): KeyPrefs {
513
+ const base = prefs.custom[slot]?.base ?? 'openfilm';
514
+ return { ...prefs, custom: { ...prefs.custom, [slot]: copyOf(prefs, base, mac) } };
515
+ }
516
+
517
+ /** A custom scheme with one action's keys changed (`combo` alone; whatever had it loses it). */
518
+ export function withKey(prefs: KeyPrefs, slot: CustomSlot, id: ActionId, combo: string, mac: boolean): KeyPrefs {
519
+ const custom = prefs.custom[slot] ?? copyOf(prefs, 'openfilm', mac);
520
+ const keys = assignKey(customKeymap(custom, mac), id, combo, mac);
521
+ return { ...prefs, custom: { ...prefs.custom, [slot]: { base: custom.base, keys: Object.fromEntries(ACTION_IDS.map((a) => [a, [...keys[a]]])) } } };
522
+ }
523
+
524
+ /** A custom scheme with one action's keys taken away. */
525
+ export function withoutKeys(prefs: KeyPrefs, slot: CustomSlot, id: ActionId, mac: boolean): KeyPrefs {
526
+ const custom = prefs.custom[slot] ?? copyOf(prefs, 'openfilm', mac);
527
+ const keys = { ...customKeymap(custom, mac), [id]: [] };
528
+ return { ...prefs, custom: { ...prefs.custom, [slot]: { base: custom.base, keys: Object.fromEntries(ACTION_IDS.map((a) => [a, [...keys[a]]])) } } };
529
+ }