@jxsuite/studio 2.0.0 → 2.1.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.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-0hzentwq.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-0hzentwq.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-2c188n41.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-97qcgvdn.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-hhj92gcr.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-ygzav7z5.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-n9zmpxjg.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +23711 -35185
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +34 -9
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +270 -262
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -120
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +75 -2
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +113 -0
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qct0gt8f.js +0 -531
  363. package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
  364. package/dist/chunks/files-02zn5y24.js +0 -50
  365. package/dist/chunks/files-02zn5y24.js.map +0 -9
  366. package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
  367. package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
  368. package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
  369. package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
  370. package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
  371. package/dist/chunks/settings-modal-gj0hrah4.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  381. package/dist/chunks/studio-568nx4t5.js +0 -469
  382. package/dist/chunks/studio-568nx4t5.js.map +0 -14
  383. package/dist/chunks/studio-9smfzqhc.js +0 -19
  384. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  385. package/dist/chunks/studio-asvf0j9e.js +0 -429
  386. package/dist/chunks/studio-asvf0j9e.js.map +0 -13
  387. package/dist/chunks/studio-d77qm3pk.js +0 -3427
  388. package/dist/chunks/studio-d77qm3pk.js.map +0 -26
  389. package/dist/chunks/studio-j6gqyjf4.js +0 -1854
  390. package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
  391. package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-rq0vxb19.js +0 -3744
  394. package/dist/chunks/studio-rq0vxb19.js.map +0 -22
  395. package/dist/chunks/studio-wd6ewdvd.js +0 -853
  396. package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
  397. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  398. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  399. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  400. package/dist/chunks/studio-ygzav7z5.js.map +0 -46
  401. package/dist/chunks/studio-zmy7241e.js +0 -399
  402. package/dist/chunks/studio-zmy7241e.js.map +0 -12
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-2c188n41.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-hhj92gcr.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -1,904 +0,0 @@
1
- /// <reference lib="dom" />
2
- /**
3
- * Manage view — project-level file browser with grid and table views.
4
- *
5
- * Displays pages, layouts, components, content, and media in a filterable grid or table. Grid view
6
- * shows live component/page/layout previews and image thumbnails. Includes a "New +" button with
7
- * type-aware entity creation (including content types from project.json).
8
- */
9
-
10
- import { html, render as litRender, nothing } from "lit-html";
11
- import { errorMessage } from "@jxsuite/schema/parse";
12
- import { ref } from "lit-html/directives/ref.js";
13
- import { repeat } from "lit-html/directives/repeat.js";
14
- import { getPlatform } from "../platform";
15
- import { projectState } from "../store";
16
- import { yamlDefault } from "../settings/schema-field-ui";
17
- import type { SchemaProperty } from "../settings/schema-field-ui";
18
- import {
19
- MEDIA_EXTENSIONS,
20
- UPLOAD_ACCEPT,
21
- extensionOf as extOf,
22
- isImage,
23
- uploadAssets,
24
- } from "../files/media-upload";
25
- import { statusMessage } from "../panels/statusbar";
26
- import { componentRegistry } from "../files/components";
27
- import { rectOf } from "../utils/geometry";
28
-
29
- import { renderPopover, showDialog, showPromptDialog } from "../ui/layers";
30
- import { loopbackAssetSrc } from "../canvas/canvas-origin";
31
- import { renderComponentPreview } from "../panels/component-preview";
32
- import { buildScope, renderNode, setSkipServerFunctions } from "@jxsuite/runtime";
33
- import { parseSourceForPath } from "../files/file-ops";
34
- import {
35
- defaultContentFormat,
36
- documentExtensions,
37
- formatByName,
38
- formatForPath,
39
- loadFormats,
40
- } from "../format/format-host";
41
-
42
- import type { ComponentEntry } from "../files/components";
43
- import type { ContentSectionEntry } from "../types";
44
- import type { JxDocument } from "@jxsuite/schema/types";
45
-
46
- // ─── Category definitions ────────────────────────────────────────────────────
47
-
48
- const CATEGORIES = [
49
- { key: "all", label: "All" },
50
- { dir: "pages", key: "pages", label: "Pages" },
51
- { dir: "layouts", key: "layouts", label: "Layouts" },
52
- { dir: "components", key: "components", label: "Components" },
53
- { dir: "content", key: "content", label: "Content" },
54
- { dir: "public", key: "media", label: "Media" },
55
- ];
56
-
57
- // ─── Module state ────────────────────────────────────────────────────────────
58
-
59
- let activeCategory = "all";
60
- let searchQuery = "";
61
- let viewMode: "grid" | "table" = "grid";
62
- let fileCache: {
63
- name: string;
64
- path: string;
65
- type: string;
66
- category: string;
67
- ext: string;
68
- }[] = [];
69
- let loading = false;
70
- /** Track which projectDirs were used for the last load, so we re-scan when they change. */
71
- let lastProjectDirsKey = "";
72
-
73
- // ─── Helpers ─────────────────────────────────────────────────────────────────
74
-
75
- /** Map a file path to a display category. Media files override by extension. */
76
- function categoryFor(dir: string, ext: string) {
77
- if (ext && MEDIA_EXTENSIONS.has(ext)) {
78
- return "Media";
79
- }
80
- if (dir.startsWith("pages")) {
81
- return "Pages";
82
- }
83
- if (dir.startsWith("layouts")) {
84
- return "Layouts";
85
- }
86
- if (dir.startsWith("components")) {
87
- return "Components";
88
- }
89
- if (dir.startsWith("content")) {
90
- return "Content";
91
- }
92
- if (dir.startsWith("public")) {
93
- return "Media";
94
- }
95
- if (dir.startsWith("data")) {
96
- return "Content";
97
- }
98
- if (dir.startsWith("styles")) {
99
- return "Components";
100
- }
101
- return "Other";
102
- }
103
-
104
- /**
105
- * Recursively collect all files under a directory.
106
- *
107
- * @param {string} dir
108
- * @param {ReturnType<typeof getPlatform>} platform
109
- * @returns {Promise<
110
- * { name: string; path: string; type: string; category: string; ext: string }[]
111
- * >}
112
- */
113
- async function collectFiles(
114
- dir: string,
115
- platform: ReturnType<typeof getPlatform>,
116
- ): Promise<{ name: string; path: string; type: string; category: string; ext: string }[]> {
117
- const results: {
118
- name: string;
119
- path: string;
120
- type: string;
121
- category: string;
122
- ext: string;
123
- }[] = [];
124
- try {
125
- const entries = await platform.listDirectory(dir);
126
- for (const entry of entries) {
127
- if (entry.type === "directory") {
128
- const sub = await collectFiles(entry.path, platform);
129
- results.push(...sub);
130
- } else {
131
- const ext = extOf(entry.name);
132
- const category = categoryFor(entry.path, ext);
133
- const type =
134
- category === "Content" ? contentTypeFor(entry.path) || ext || "file" : ext || "file";
135
- results.push({
136
- category,
137
- ext,
138
- name: entry.name,
139
- path: entry.path,
140
- type,
141
- });
142
- }
143
- }
144
- } catch {
145
- // Directory may not exist or be inaccessible
146
- }
147
- return results;
148
- }
149
-
150
- /**
151
- * Match a file path against the project `content` section's source directories to find its content
152
- * type name. Returns the content type name (capitalized) or null if no match.
153
- *
154
- * @param {string} filePath
155
- * @returns {string | null}
156
- */
157
- function contentTypeFor(filePath: string) {
158
- const config = projectState?.projectConfig;
159
- const content = (config?.content ?? {}) as Record<string, ContentSectionEntry>;
160
- for (const [name, d] of Object.entries(content)) {
161
- if (!d.source) {
162
- continue;
163
- }
164
- const prefix = d.source.replace(/^\.\//, "").replace(/\/$/, "");
165
- if (filePath.startsWith(`${prefix}/`) || filePath === prefix) {
166
- return name.charAt(0).toUpperCase() + name.slice(1);
167
- }
168
- }
169
- return null;
170
- }
171
-
172
- // ─── Data loading ────────────────────────────────────────────────────────────
173
-
174
- async function loadFiles() {
175
- if (!projectState) {
176
- return;
177
- }
178
- loading = true;
179
- const platform = getPlatform();
180
- const dirs = projectState.projectDirs || [];
181
- lastProjectDirsKey = dirs.join(",");
182
- const all = await Promise.all(dirs.map((d: string) => collectFiles(d, platform)));
183
- fileCache = all.flat();
184
- fileCache.sort((a, b) => a.path.localeCompare(b.path));
185
- loading = false;
186
- }
187
-
188
- // ─── Filtering ───────────────────────────────────────────────────────────────
189
-
190
- function filteredFiles() {
191
- let files = fileCache;
192
- if (activeCategory !== "all") {
193
- const cat = CATEGORIES.find((c) => c.key === activeCategory);
194
- if (cat && cat.label) {
195
- files = files.filter((f) => f.category === cat.label);
196
- }
197
- }
198
- if (searchQuery) {
199
- const q = searchQuery.toLowerCase();
200
- files = files.filter(
201
- (f) => f.name.toLowerCase().includes(q) || f.path.toLowerCase().includes(q),
202
- );
203
- }
204
- return files;
205
- }
206
-
207
- // ─── Entity types for "New +" button ────────────────────────────────────────
208
-
209
- function getEntityTypes() {
210
- const pageExt = documentExtensions("page")[0] ?? ".json";
211
- const contentExt = defaultContentFormat()?.extensions[0] ?? ".json";
212
- return [
213
- { dir: "pages", ext: pageExt, key: "page", label: "Page" },
214
- { dir: "layouts", ext: ".json", key: "layout", label: "Layout" },
215
- { dir: "components", ext: ".json", key: "component", label: "Component" },
216
- { dir: "content", ext: contentExt, key: "content", label: "Content" },
217
- ];
218
- }
219
-
220
- /**
221
- * Build frontmatter YAML from a content type's schema properties.
222
- *
223
- * @param {string} contentTypeName
224
- * @returns {string}
225
- */
226
- function buildFrontmatterYaml(contentTypeName: string) {
227
- const config = projectState?.projectConfig;
228
- const content = (config?.content ?? {}) as Record<string, ContentSectionEntry>;
229
- const col = content[contentTypeName];
230
- if (!col?.schema?.properties) {
231
- return "title: Untitled\n";
232
- }
233
-
234
- let yaml = "";
235
- const props = col.schema.properties as Record<string, SchemaProperty>;
236
- for (const [field, def] of Object.entries(props)) {
237
- yaml += `${field}: ${yamlDefault(def.type || "", def.format || "")}\n`;
238
- }
239
- return yaml || "title: Untitled\n";
240
- }
241
-
242
- /**
243
- * Get content-type-derived entity types from project config.
244
- *
245
- * @returns {{ key: string; label: string; dir: string; ext: string; contentTypeName: string }[]}
246
- */
247
- function getContentTypeTypes() {
248
- const config = projectState?.projectConfig;
249
- const content = (config?.content ?? {}) as Record<string, ContentSectionEntry>;
250
- return Object.entries(content).map(([name, d]) => {
251
- const dir = d.source ? d.source.replace(/^\.\//, "").replace(/\/$/, "") : name;
252
- return {
253
- contentTypeName: name,
254
- dir,
255
- ext:
256
- d.format === "json"
257
- ? ".json"
258
- : (formatByName(d.format)?.extensions[0] ??
259
- defaultContentFormat()?.extensions[0] ??
260
- ".json"),
261
- key: `contentType:${name}`,
262
- label: name.charAt(0).toUpperCase() + name.slice(1),
263
- };
264
- });
265
- }
266
-
267
- /**
268
- * Handle creation of a new entity.
269
- *
270
- * @param {string} typeKey
271
- * @param {HTMLElement} container
272
- * @param {{ openFile: (path: string) => void }} ctx
273
- */
274
- async function handleNewEntity(
275
- typeKey: string,
276
- _container: HTMLElement,
277
- ctx: { openFile: (path: string) => void },
278
- ) {
279
- const isContentType = typeKey.startsWith("contentType:");
280
- const contentTypeName = isContentType ? typeKey.slice("contentType:".length) : null;
281
- await loadFormats();
282
- const allTypes = [...getEntityTypes(), ...getContentTypeTypes()];
283
- const typeInfo = allTypes.find((t) => t.key === typeKey);
284
- if (!typeInfo) {
285
- return;
286
- }
287
-
288
- const name = await showPromptDialog(`New ${typeInfo.label}`, {
289
- confirmLabel: "Create",
290
- message: `Creating in ${typeInfo.dir}/`,
291
- placeholder: "untitled",
292
- validate: (v) => (v.trim() ? "" : `Enter a ${typeInfo.label.toLowerCase()} name.`),
293
- value: "untitled",
294
- });
295
- if (!name) {
296
- return;
297
- }
298
-
299
- const slug = name
300
- .toLowerCase()
301
- .replaceAll(/\s+/g, "-")
302
- .replaceAll(/[^a-z0-9-]/g, "");
303
- const filePath = `${typeInfo.dir}/${slug}${typeInfo.ext}`;
304
-
305
- let content;
306
- const entityFormat = formatForPath(filePath);
307
- if (entityFormat) {
308
- content = contentTypeName
309
- ? `---\n${buildFrontmatterYaml(contentTypeName)}---\n\n`
310
- : (entityFormat.studio?.newFileTemplate ?? "");
311
- } else {
312
- content = JSON.stringify({ children: [], tagName: "div" }, null, "\t");
313
- }
314
-
315
- const platform = getPlatform();
316
- await platform.writeFile(filePath, content);
317
- invalidateBrowseCache();
318
- ctx.openFile(filePath);
319
- }
320
-
321
- // ─── Upload ─────────────────────────────────────────────────────────────────
322
-
323
- /**
324
- * The directory a drop on the Manage view targets. The view filters by CATEGORY, not directory —
325
- * its cards are files, so there is no folder to drop "into". The active category's own directory is
326
- * the closest thing to a dropped location; "All" (and any category without one) falls back to the
327
- * active document's media directory.
328
- */
329
- export function uploadDirForCategory(category: string): string | undefined {
330
- return CATEGORIES.find((c) => c.key === category)?.dir;
331
- }
332
-
333
- /**
334
- * Handle file uploads — writes binary files into the active category's directory.
335
- *
336
- * @param {FileList | File[]} files
337
- * @param {HTMLElement} container
338
- * @param {{ openFile: (path: string) => void }} ctx
339
- */
340
- async function handleUpload(
341
- files: FileList | File[],
342
- container: HTMLElement,
343
- ctx: { openFile: (path: string) => void },
344
- ) {
345
- const dir = uploadDirForCategory(activeCategory);
346
- await uploadAssets([...files], dir ? { dir } : {});
347
- void renderBrowse(container, ctx);
348
- }
349
-
350
- // ─── Context menu ───────────────────────────────────────────────────────────
351
-
352
- let _browseCtxHandle: ReturnType<typeof renderPopover> | null = null;
353
-
354
- function dismissBrowseContextMenu() {
355
- if (_browseCtxHandle) {
356
- _browseCtxHandle.dismiss();
357
- _browseCtxHandle = null;
358
- }
359
- }
360
-
361
- /**
362
- * @param {MouseEvent} e
363
- * @param {{ name: string; path: string; ext: string }} file
364
- * @param {HTMLElement} container
365
- * @param {{ openFile: (path: string) => void }} ctx
366
- */
367
- function showBrowseContextMenu(
368
- e: MouseEvent,
369
- file: { name: string; path: string; ext: string },
370
- container: HTMLElement,
371
- ctx: { openFile: (path: string) => void },
372
- ) {
373
- e.preventDefault();
374
- e.stopPropagation();
375
- dismissBrowseContextMenu();
376
-
377
- /** @type {{ label: string; action?: () => void; danger?: boolean }[]} */
378
- const items = [
379
- { action: () => ctx.openFile(file.path), label: "Open" },
380
- { label: "\u2014" },
381
- {
382
- action: () => browseRenameFile(file, container, ctx),
383
- label: "Rename\u2026",
384
- },
385
- {
386
- action: () => browseDuplicateFile(file, container, ctx),
387
- label: "Duplicate",
388
- },
389
- { label: "\u2014" },
390
- {
391
- action: () => browseDeleteFile(file, container, ctx),
392
- danger: true,
393
- label: "Delete",
394
- },
395
- ];
396
-
397
- let x = e.clientX,
398
- y = e.clientY;
399
-
400
- _browseCtxHandle = renderPopover(
401
- html`<sp-popover
402
- open
403
- style="position:fixed;left:${x}px;top:${y}px"
404
- ${ref((el) => {
405
- if (!el) {
406
- return;
407
- }
408
- requestAnimationFrame(() => {
409
- const popover = el as HTMLElement;
410
- const menuRect = rectOf(popover);
411
- if (x + menuRect.width > window.innerWidth) {
412
- x = window.innerWidth - menuRect.width - 4;
413
- }
414
- if (y + menuRect.height > window.innerHeight) {
415
- y = window.innerHeight - menuRect.height - 4;
416
- }
417
- popover.style.left = `${x}px`;
418
- popover.style.top = `${y}px`;
419
- });
420
- })}
421
- >
422
- <sp-menu>
423
- ${items.map((item) =>
424
- item.label === "\u2014"
425
- ? html`<sp-menu-divider></sp-menu-divider>`
426
- : html`<sp-menu-item
427
- style=${item.danger ? "color: var(--danger)" : ""}
428
- @click=${() => {
429
- dismissBrowseContextMenu();
430
- void item.action?.();
431
- }}
432
- >${item.label}</sp-menu-item
433
- >`,
434
- )}
435
- </sp-menu>
436
- </sp-popover>`,
437
- {
438
- dismissOnOutsideClick: true,
439
- layer: "dialog",
440
- onDismiss: () => {
441
- _browseCtxHandle = null;
442
- },
443
- },
444
- );
445
- }
446
-
447
- /**
448
- * @param {{ name: string; path: string; ext: string }} file
449
- * @param {HTMLElement} container
450
- * @param {{ openFile: (path: string) => void }} ctx
451
- */
452
- async function browseRenameFile(
453
- file: { name: string; path: string; ext: string },
454
- container: HTMLElement,
455
- ctx: { openFile: (path: string) => void },
456
- ) {
457
- const newName = await showRenameDialog(file.name);
458
- if (!newName || newName === file.name) {
459
- return;
460
- }
461
- const filePath = file.path.replaceAll("\\", "/");
462
- const parentDir = filePath.includes("/") ? filePath.slice(0, filePath.lastIndexOf("/")) : ".";
463
- const newPath = parentDir === "." ? newName : `${parentDir}/${newName}`;
464
- try {
465
- const platform = getPlatform();
466
- await platform.renameFile(file.path, newPath);
467
- invalidateBrowseCache();
468
- void renderBrowse(container, ctx);
469
- statusMessage(`Renamed to ${newName}`);
470
- } catch (error) {
471
- statusMessage(`Error: ${errorMessage(error)}`);
472
- }
473
- }
474
-
475
- /**
476
- * @param {{ name: string; path: string; ext: string }} file
477
- * @param {HTMLElement} container
478
- * @param {{ openFile: (path: string) => void }} ctx
479
- */
480
- async function browseDuplicateFile(
481
- file: { name: string; path: string; ext: string },
482
- container: HTMLElement,
483
- ctx: { openFile: (path: string) => void },
484
- ) {
485
- const filePath = file.path.replaceAll("\\", "/");
486
- const parentDir = filePath.includes("/") ? filePath.slice(0, filePath.lastIndexOf("/")) : ".";
487
- const baseName = file.name.replace(/(\.[^.]+)$/, "");
488
- const ext = file.ext || "";
489
- const copyName = `${baseName}-copy${ext}`;
490
- const copyPath = parentDir === "." ? copyName : `${parentDir}/${copyName}`;
491
- try {
492
- const platform = getPlatform();
493
- const content = await platform.readFile(file.path);
494
- await platform.writeFile(copyPath, content);
495
- invalidateBrowseCache();
496
- void renderBrowse(container, ctx);
497
- statusMessage(`Duplicated as ${copyName}`);
498
- } catch (error) {
499
- statusMessage(`Error: ${errorMessage(error)}`);
500
- }
501
- }
502
-
503
- /**
504
- * @param {{ name: string; path: string; ext: string }} file
505
- * @param {HTMLElement} container
506
- * @param {{ openFile: (path: string) => void }} ctx
507
- */
508
- async function browseDeleteFile(
509
- file: { name: string; path: string; ext: string },
510
- container: HTMLElement,
511
- ctx: { openFile: (path: string) => void },
512
- ) {
513
- const confirmed = await showDeleteDialog(file.name);
514
- if (!confirmed) {
515
- return;
516
- }
517
- try {
518
- const platform = getPlatform();
519
- await platform.deleteFile(file.path);
520
- invalidateBrowseCache();
521
- void renderBrowse(container, ctx);
522
- statusMessage(`Deleted ${file.name}`);
523
- } catch (error) {
524
- statusMessage(`Error: ${errorMessage(error)}`);
525
- }
526
- }
527
-
528
- // ─── Spectrum dialogs ───────────────────────────────────────────────────────
529
-
530
- /**
531
- * @param {string} currentName
532
- * @returns {Promise<string | null>}
533
- */
534
- function showRenameDialog(currentName: string): Promise<string | null> {
535
- return showPromptDialog("Rename", {
536
- confirmLabel: "Rename",
537
- select: "stem",
538
- validate: (v) => (v.trim() ? "" : "Enter a file name."),
539
- value: currentName,
540
- });
541
- }
542
-
543
- /**
544
- * @param {string} fileName
545
- * @returns {Promise<boolean>}
546
- */
547
- function showDeleteDialog(fileName: string) {
548
- return showDialog(
549
- (done) => html`
550
- <sp-dialog-wrapper
551
- open
552
- underlay
553
- headline="Delete File"
554
- confirm-label="Delete"
555
- cancel-label="Cancel"
556
- size="s"
557
- @confirm=${() => done(true)}
558
- @cancel=${() => done(false)}
559
- @close=${() => done(false)}
560
- >
561
- <p>Are you sure you want to delete <strong>${fileName}</strong>? This cannot be undone.</p>
562
- </sp-dialog-wrapper>
563
- `,
564
- );
565
- }
566
-
567
- // ─── Grid view helpers ──────────────────────────────────────────────────────
568
-
569
- const _previewCache = new Map<string, HTMLElement>();
570
-
571
- /**
572
- * Render a live preview for a page or layout file (JSON or Markdown).
573
- *
574
- * @param {string} filePath
575
- * @returns {Promise<HTMLElement | null>}
576
- */
577
- async function renderDocPreview(filePath: string) {
578
- try {
579
- const platform = getPlatform();
580
- const content = await platform.readFile(filePath);
581
- setSkipServerFunctions(true);
582
- await loadFormats();
583
- let doc;
584
- if (formatForPath(filePath)) {
585
- const result = await parseSourceForPath(filePath, content);
586
- doc = result.document as JxDocument;
587
- } else {
588
- doc = JSON.parse(content) as JxDocument;
589
- }
590
- const scope = await buildScope(doc, {}, location.href);
591
- const el = renderNode(doc, scope);
592
- return el instanceof HTMLElement ? el : null;
593
- } catch {
594
- return null;
595
- }
596
- }
597
-
598
- /**
599
- * Load (or retrieve from cache) the preview for a card element.
600
- *
601
- * @param {Element} el — the .element-card-preview div
602
- * @param {{ path: string; category: string }} file
603
- */
604
- async function loadPreview(el: Element, file: { path: string; category: string }) {
605
- // Already populated
606
- if (el.firstElementChild) {
607
- return;
608
- }
609
-
610
- // Component/doc previews instantiate a real runtime custom element that sets img.src verbatim to
611
- // A relative path — not a direct-parent lit literal, so it cannot be pre-rewritten via
612
- // LoopbackAssetSrc at this template layer. The desktop MutationObserver (plus activate()'s initial
613
- // Sweep) still recovers these; the brief stray views:// request is a known cosmetic residual we
614
- // Intentionally do NOT over-engineer with a per-component rewrite.
615
- let preview: HTMLElement | undefined = _previewCache.get(file.path);
616
- if (!preview) {
617
- try {
618
- const comp = componentRegistry.find((c: ComponentEntry) => c.path === file.path);
619
- preview = comp
620
- ? ((await renderComponentPreview(comp)) as HTMLElement | undefined)
621
- : ((await renderDocPreview(file.path)) as HTMLElement | undefined) || undefined;
622
- if (preview) {
623
- _previewCache.set(file.path, /** @type {HTMLElement} */ preview);
624
- }
625
- } catch {
626
- return;
627
- }
628
- }
629
- if (preview) {
630
- el.append(preview);
631
- }
632
- }
633
-
634
- /**
635
- * Render a single grid card.
636
- *
637
- * @param {{ name: string; path: string; type: string; category: string; ext: string }} file
638
- * @param {HTMLElement} container
639
- * @param {{ openFile: (path: string) => void }} ctx
640
- */
641
- function renderCard(
642
- file: {
643
- name: string;
644
- path: string;
645
- type: string;
646
- category: string;
647
- ext: string;
648
- },
649
- container: HTMLElement,
650
- ctx: { openFile: (path: string) => void },
651
- ) {
652
- const isImg = isImage(file.ext);
653
- const needsPreview =
654
- file.category === "Components" ||
655
- file.category === "Pages" ||
656
- file.category === "Layouts" ||
657
- file.category === "Content";
658
-
659
- return html`
660
- <div
661
- class="element-card"
662
- @click=${() => ctx.openFile(file.path)}
663
- @contextmenu=${(e: MouseEvent) => showBrowseContextMenu(e, file, container, ctx)}
664
- >
665
- <div
666
- class="element-card-preview"
667
- ${
668
- needsPreview
669
- ? ref((el: Element | undefined) => {
670
- if (el) {
671
- void loadPreview(el, file);
672
- }
673
- })
674
- : nothing
675
- }
676
- >
677
- ${
678
- isImg
679
- ? html`<img
680
- src=${loopbackAssetSrc(`/${file.path}`)}
681
- style="max-width:100%;max-height:100%;object-fit:contain"
682
- />`
683
- : needsPreview
684
- ? nothing
685
- : html`<sp-icon-document size="xl"></sp-icon-document>`
686
- }
687
- </div>
688
- <div class="element-card-label">${file.name}</div>
689
- </div>
690
- `;
691
- }
692
-
693
- // ─── Render ──────────────────────────────────────────────────────────────────
694
-
695
- /**
696
- * Render the Browse view into the canvas area.
697
- *
698
- * @param {HTMLElement} container — the #canvas-wrap element
699
- * @param {{ openFile: (path: string) => void }} ctx — callbacks from studio.js
700
- */
701
- export async function renderBrowse(
702
- container: HTMLElement,
703
- ctx: { openFile: (path: string) => void },
704
- ) {
705
- // Re-load when projectDirs changed (e.g. project opened after initial render)
706
- const currentKey = (projectState?.projectDirs || []).join(",");
707
- if ((fileCache.length === 0 && !loading) || currentKey !== lastProjectDirsKey) {
708
- await loadFiles();
709
- }
710
-
711
- const files = filteredFiles();
712
-
713
- const contentTypeTypes = getContentTypeTypes();
714
-
715
- const filterBar = html`
716
- <sp-action-group selects="single" size="s" compact>
717
- ${CATEGORIES.map(
718
- (cat) => html`
719
- <sp-action-button
720
- size="s"
721
- ?selected=${activeCategory === cat.key}
722
- @click=${() => {
723
- activeCategory = cat.key;
724
- void renderBrowse(container, ctx);
725
- }}
726
- >
727
- ${cat.label}
728
- </sp-action-button>
729
- `,
730
- )}
731
- </sp-action-group>
732
- <sp-search
733
- size="s"
734
- placeholder="Filter files..."
735
- .value=${searchQuery}
736
- @input=${(e: Event) => {
737
- searchQuery = (e.target as HTMLInputElement).value;
738
- void renderBrowse(container, ctx);
739
- }}
740
- @submit=${(e: Event) => e.preventDefault()}
741
- ></sp-search>
742
- <overlay-trigger placement="bottom-start" triggered-by="click">
743
- <sp-action-button size="s" slot="trigger">
744
- <sp-icon-add slot="icon"></sp-icon-add> New
745
- </sp-action-button>
746
- <sp-popover slot="click-content" tip>
747
- <sp-menu
748
- @change=${(e: Event) =>
749
- handleNewEntity((e.target as HTMLSelectElement).value, container, ctx)}
750
- >
751
- ${getEntityTypes().map(
752
- (t) => html`<sp-menu-item value=${t.key}>${t.label}</sp-menu-item>`,
753
- )}
754
- ${
755
- contentTypeTypes.length > 0
756
- ? html`<sp-menu-divider></sp-menu-divider> ${contentTypeTypes.map(
757
- (t) => html`<sp-menu-item value=${t.key}>${t.label}</sp-menu-item>`,
758
- )}`
759
- : ""
760
- }
761
- </sp-menu>
762
- </sp-popover>
763
- </overlay-trigger>
764
- <sp-action-button
765
- size="s"
766
- @click=${() => {
767
- const input = container.querySelector(".browse-upload-input") as HTMLInputElement;
768
- if (input) {
769
- input.click();
770
- }
771
- }}
772
- >
773
- <sp-icon-upload slot="icon"></sp-icon-upload> Upload
774
- </sp-action-button>
775
- <input
776
- type="file"
777
- multiple
778
- accept=${UPLOAD_ACCEPT}
779
- class="browse-upload-input"
780
- style="display:none"
781
- @change=${(e: Event) => {
782
- const input = e.target as HTMLInputElement;
783
- if (input.files?.length) {
784
- void handleUpload(input.files, container, ctx);
785
- }
786
- input.value = "";
787
- }}
788
- />
789
- <div class="browse-view-switcher">
790
- <sp-action-button
791
- size="s"
792
- ?selected=${viewMode === "grid"}
793
- @click=${() => {
794
- viewMode = "grid";
795
- void renderBrowse(container, ctx);
796
- }}
797
- title="Grid view"
798
- >
799
- <sp-icon-view-grid slot="icon"></sp-icon-view-grid>
800
- </sp-action-button>
801
- <sp-action-button
802
- size="s"
803
- ?selected=${viewMode === "table"}
804
- @click=${() => {
805
- viewMode = "table";
806
- void renderBrowse(container, ctx);
807
- }}
808
- title="Table view"
809
- >
810
- <sp-icon-view-list slot="icon"></sp-icon-view-list>
811
- </sp-action-button>
812
- </div>
813
- `;
814
-
815
- const table = html`
816
- <sp-table size="m" quiet>
817
- <sp-table-head>
818
- <sp-table-head-cell>Name</sp-table-head-cell>
819
- <sp-table-head-cell>Category</sp-table-head-cell>
820
- <sp-table-head-cell>Type</sp-table-head-cell>
821
- <sp-table-head-cell>Path</sp-table-head-cell>
822
- </sp-table-head>
823
- <sp-table-body>
824
- ${
825
- files.length === 0
826
- ? html`<sp-table-row
827
- ><sp-table-cell
828
- >${loading ? "Loading..." : "No files found"}</sp-table-cell
829
- ></sp-table-row
830
- >`
831
- : repeat(
832
- files,
833
- (f) => f.path,
834
- (f) => html`
835
- <sp-table-row
836
- value=${f.path}
837
- class="browse-row"
838
- style=${isImage(f.ext) ? "cursor:default" : ""}
839
- @click=${isImage(f.ext) ? nothing : () => ctx.openFile(f.path)}
840
- @contextmenu=${(e: MouseEvent) => showBrowseContextMenu(e, f, container, ctx)}
841
- >
842
- <sp-table-cell class="browse-name-cell"
843
- >${
844
- isImage(f.ext)
845
- ? html`<img class="browse-thumb" src=${loopbackAssetSrc(`/${f.path}`)} />`
846
- : nothing
847
- }${f.name}</sp-table-cell
848
- >
849
- <sp-table-cell>${f.category}</sp-table-cell>
850
- <sp-table-cell>${f.type}</sp-table-cell>
851
- <sp-table-cell class="browse-path-cell">${f.path}</sp-table-cell>
852
- </sp-table-row>
853
- `,
854
- )
855
- }
856
- </sp-table-body>
857
- </sp-table>
858
- `;
859
-
860
- const grid =
861
- files.length === 0
862
- ? html`<div class="browse-grid-empty">${loading ? "Loading..." : "No files found"}</div>`
863
- : html`<div class="browse-grid">
864
- ${repeat(
865
- files,
866
- (f) => f.path,
867
- (f) => renderCard(f, container, ctx),
868
- )}
869
- </div>`;
870
-
871
- const body = viewMode === "grid" ? grid : html`<div class="browse-table">${table}</div>`;
872
-
873
- const tpl = html`
874
- <div
875
- class="browse-view"
876
- @dragover=${(e: DragEvent) => {
877
- e.preventDefault();
878
- (e.currentTarget as HTMLElement).classList.add("browse-drop-active");
879
- }}
880
- @dragleave=${(e: DragEvent) => {
881
- (e.currentTarget as HTMLElement).classList.remove("browse-drop-active");
882
- }}
883
- @drop=${(e: DragEvent) => {
884
- e.preventDefault();
885
- (e.currentTarget as HTMLElement).classList.remove("browse-drop-active");
886
- const droppedFiles = e.dataTransfer?.files;
887
- if (droppedFiles?.length) {
888
- void handleUpload(droppedFiles, container, ctx);
889
- }
890
- }}
891
- >
892
- <div class="browse-filter-bar">${filterBar}</div>
893
- <div class="browse-body">${body}</div>
894
- </div>
895
- `;
896
-
897
- litRender(tpl, container);
898
- }
899
-
900
- /** Force a data reload on next render (e.g., after file creation/deletion). */
901
- export function invalidateBrowseCache() {
902
- fileCache = [];
903
- _previewCache.clear();
904
- }