@riducms/admin 0.2.2 → 0.3.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 (352) hide show
  1. package/THIRD_PARTY_NOTICES.md +60 -37
  2. package/package.json +27 -15
  3. package/src/app/admin-core-view-route.svelte +38 -53
  4. package/src/app/admin-extension-route.svelte +19 -0
  5. package/src/app/admin-layout.scss +175 -0
  6. package/src/app/admin-layout.svelte +144 -410
  7. package/src/app/admin-navigation-progress.svelte +11 -77
  8. package/src/app/admin-page-title.svelte +17 -85
  9. package/src/app/admin-page-title.ts +136 -0
  10. package/src/app/admin-route-error.svelte +24 -0
  11. package/src/app/admin-router-provider.svelte +34 -0
  12. package/src/app/admin-router-root.svelte +41 -0
  13. package/src/app/admin-routes.svelte +56 -19
  14. package/src/app/loading-boundary.svelte +3 -1
  15. package/src/app-meta.ts +13 -0
  16. package/src/app.css +138 -496
  17. package/src/app.svelte +60 -26
  18. package/src/components/brand/ridu-logo.svelte +19 -5
  19. package/src/components/data/list-table.scss +63 -0
  20. package/src/components/icons/LICENSE.md +24 -0
  21. package/src/components/icons/chevron.svelte +8 -0
  22. package/src/components/icons/plus.svelte +12 -0
  23. package/src/components/icons/search.svelte +12 -0
  24. package/src/components/icons/x.svelte +8 -0
  25. package/src/components/json-tree/json-tree-node.svelte +61 -67
  26. package/src/components/json-tree/json-viewer.scss +153 -0
  27. package/src/components/json-tree/json-viewer.svelte +57 -96
  28. package/src/components/ui/banner/banner.scss +28 -0
  29. package/src/components/ui/banner/banner.svelte +9 -8
  30. package/src/components/ui/banner/index.ts +1 -5
  31. package/src/components/ui/code-editor/code-editor.scss +20 -0
  32. package/src/components/ui/code-editor/code-editor.svelte +71 -0
  33. package/src/components/ui/code-editor/code-editor.ts +151 -0
  34. package/src/components/ui/code-editor/code-languages.ts +29 -0
  35. package/src/components/ui/collapsible-panel/collapsible-panel.scss +38 -0
  36. package/src/components/ui/collapsible-panel/collapsible-panel.svelte +13 -0
  37. package/src/components/ui/collapsible-panel/index.ts +1 -0
  38. package/src/components/ui/combobox/combobox.scss +163 -0
  39. package/src/components/ui/combobox/index.ts +22 -0
  40. package/src/components/ui/date-value-control/date-value-calendar.svelte +128 -16
  41. package/src/components/ui/date-value-control/date-value-control-loader.scss +26 -0
  42. package/src/components/ui/date-value-control/date-value-control-loader.svelte +35 -13
  43. package/src/components/ui/date-value-control/date-value-control.scss +223 -0
  44. package/src/components/ui/date-value-control/date-value-control.svelte +62 -44
  45. package/src/components/ui/drawer/drawer-depth.ts +8 -0
  46. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -5
  47. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +2 -5
  48. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -2
  49. package/src/components/ui/dropdown-menu/dropdown-menu.scss +62 -0
  50. package/src/components/ui/kbd/kbd.scss +17 -0
  51. package/src/components/ui/kbd/kbd.svelte +3 -10
  52. package/src/components/ui/navigation-drawer/navigation-drawer.scss +40 -0
  53. package/src/components/ui/navigation-drawer/navigation-drawer.svelte +38 -0
  54. package/src/components/ui/popover/popover-content.svelte +3 -5
  55. package/src/components/ui/popover/popover.scss +18 -0
  56. package/src/components/ui/radio-group/index.ts +0 -1
  57. package/src/components/ui/radio-group/radio-card-item.svelte +7 -12
  58. package/src/components/ui/radio-group/radio-group.svelte +2 -2
  59. package/src/components/ui/radio-group/radio.scss +45 -0
  60. package/src/components/ui/select/select-content.svelte +5 -6
  61. package/src/components/ui/select/select-item.svelte +8 -8
  62. package/src/components/ui/select/select-scroll-down-button.svelte +3 -5
  63. package/src/components/ui/select/select-scroll-up-button.svelte +3 -5
  64. package/src/components/ui/select/select-trigger.svelte +6 -7
  65. package/src/components/ui/select/select.scss +126 -0
  66. package/src/components/ui/sheet/sheet-content.svelte +6 -34
  67. package/src/components/ui/sheet/sheet-description.svelte +2 -2
  68. package/src/components/ui/sheet/sheet-overlay.svelte +2 -21
  69. package/src/components/ui/sheet/sheet-title.svelte +2 -2
  70. package/src/components/ui/sheet/sheet.scss +221 -0
  71. package/src/components/ui/skeleton/skeleton.scss +20 -0
  72. package/src/components/ui/skeleton/skeleton.svelte +4 -16
  73. package/src/components/ui/sonner/sonner.scss +121 -0
  74. package/src/components/ui/sonner/sonner.svelte +11 -10
  75. package/src/components/ui/status-indicator/status-indicator.scss +48 -0
  76. package/src/components/ui/status-indicator/status-indicator.svelte +14 -14
  77. package/src/components/ui/tabs/tabs-content.svelte +2 -2
  78. package/src/components/ui/tabs/tabs-list.svelte +7 -7
  79. package/src/components/ui/tabs/tabs-trigger.svelte +2 -8
  80. package/src/components/ui/tabs/tabs.scss +86 -0
  81. package/src/components/ui/tabs/tabs.svelte +2 -2
  82. package/src/components/ui/timezone-picker/timezone-picker.svelte +152 -0
  83. package/src/core/api/admin-client.ts +2 -2
  84. package/src/core/bootstrap/admin-bootstrap.ts +396 -0
  85. package/src/core/bootstrap/admin-prepared-state.ts +213 -0
  86. package/src/core/bootstrap/admin-route-modules.ts +66 -0
  87. package/src/core/clipboard/copy-feedback.svelte.ts +43 -0
  88. package/src/core/forms/embedded-fields.ts +4 -8
  89. package/src/core/forms/field-editor-authoring.ts +8 -0
  90. package/src/core/forms/field-issue-focus.ts +4 -4
  91. package/src/core/forms/field-occurrence.ts +4 -7
  92. package/src/core/forms/form-controller.svelte.ts +14 -21
  93. package/src/core/forms/form-draft-recovery.ts +17 -4
  94. package/src/core/forms/form-issue-correlation.ts +13 -13
  95. package/src/core/forms/form-path.ts +17 -0
  96. package/src/core/forms/form-schema.ts +12 -43
  97. package/src/core/forms/form-validation.ts +7 -46
  98. package/src/core/forms/plugin-field-authoring.ts +6 -2
  99. package/src/core/forms/plugin-field-write.ts +2 -6
  100. package/src/core/i18n/admin-i18n.svelte.ts +18 -0
  101. package/src/core/i18n/format-admin-date-time.ts +19 -0
  102. package/src/core/i18n/humanize-identifier.ts +5 -0
  103. package/src/core/i18n/time-zone-label.ts +20 -0
  104. package/src/core/loaders/admin-view-host.svelte +72 -0
  105. package/src/core/loaders/admin-view-loader.svelte.ts +90 -0
  106. package/src/core/notifications/validation-toast.svelte +2 -2
  107. package/src/core/plugins/field-registry.ts +7 -8
  108. package/src/core/routing/admin-paths.ts +53 -2
  109. package/src/core/routing/admin-scroll.svelte.ts +74 -0
  110. package/src/core/routing/admin-view-selection.ts +78 -0
  111. package/src/core/runtime/admin-runtime.svelte.ts +121 -124
  112. package/src/core/schema/field-default.ts +33 -0
  113. package/src/core/schema/field-localization.ts +15 -0
  114. package/src/core/schema/read-document-path.ts +11 -0
  115. package/src/features/account/account-menu.scss +37 -0
  116. package/src/features/account/account-menu.svelte +115 -0
  117. package/src/features/{auth → account}/account-navigation.svelte +16 -19
  118. package/src/features/{auth → account}/account-route.svelte +206 -49
  119. package/src/features/account/account-security-controller.svelte.ts +157 -0
  120. package/src/features/{auth → account}/account-security-route.svelte +140 -42
  121. package/src/features/api-reference/api-reference-code.svelte +64 -0
  122. package/src/features/api-reference/api-reference-content.svelte +258 -0
  123. package/src/features/api-reference/api-reference-drawer.scss +74 -0
  124. package/src/features/api-reference/api-reference-examples.ts +381 -0
  125. package/src/features/api-reference/api-reference-schema.ts +332 -0
  126. package/src/features/api-reference/api-reference.scss +190 -0
  127. package/src/features/api-reference/api-reference.svelte +74 -0
  128. package/src/features/auth/auth-brand.svelte +5 -3
  129. package/src/features/auth/auth-field.svelte +92 -0
  130. package/src/features/auth/auth-frame.svelte +5 -8
  131. package/src/features/auth/auth.scss +122 -0
  132. package/src/features/auth/create-first-user-controller.svelte.ts +20 -51
  133. package/src/features/auth/create-first-user-route.svelte +83 -122
  134. package/src/features/auth/forgot-password-route.svelte +31 -23
  135. package/src/features/auth/login-route.svelte +68 -116
  136. package/src/features/auth/logout-button.scss +34 -0
  137. package/src/features/auth/logout-button.svelte +52 -0
  138. package/src/features/auth/request-verification-route.svelte +27 -22
  139. package/src/features/auth/reset-password-route.svelte +108 -41
  140. package/src/features/auth/verify-email-route.svelte +49 -15
  141. package/src/features/bulk-edit/bulk-edit-fields.ts +23 -0
  142. package/src/features/bulk-edit/bulk-edit.scss +152 -0
  143. package/src/features/bulk-edit/field-picker.svelte +176 -0
  144. package/src/features/collections/bulk/bulk-actions.svelte +148 -0
  145. package/src/features/collections/bulk/bulk-confirmation.svelte +153 -0
  146. package/src/features/collections/bulk/bulk-editor-loader.svelte +95 -0
  147. package/src/features/collections/bulk/bulk-editor.svelte +186 -0
  148. package/src/features/collections/collection-list-cell-values.ts +144 -0
  149. package/src/features/collections/collection-list-controller.svelte.ts +268 -379
  150. package/src/features/collections/collection-list-preferences-controller.svelte.ts +54 -40
  151. package/src/features/collections/collection-list-query.ts +223 -0
  152. package/src/features/collections/collection-list-results.svelte +205 -329
  153. package/src/features/collections/collection-list-route.svelte +143 -884
  154. package/src/features/collections/collection-list.scss +261 -0
  155. package/src/features/collections/collection-list.svelte.ts +362 -0
  156. package/src/features/collections/controls/column-picker.svelte +110 -0
  157. package/src/features/collections/controls/column-pill.svelte +62 -0
  158. package/src/features/collections/controls/filter-builder.svelte +193 -0
  159. package/src/features/collections/controls/filter-row.svelte +142 -0
  160. package/src/features/collections/controls/list-controls.scss +196 -0
  161. package/src/features/collections/controls/list-options.svelte +203 -0
  162. package/src/features/collections/controls/list-pagination.scss +78 -0
  163. package/src/features/collections/controls/list-pagination.svelte +115 -0
  164. package/src/features/collections/controls/list-toolbar.svelte +132 -0
  165. package/src/features/collections/controls/sort-heading.scss +52 -0
  166. package/src/features/collections/controls/sort-heading.svelte +78 -0
  167. package/src/features/collections/list-columns.ts +43 -0
  168. package/src/features/collections/list-workspace.ts +155 -54
  169. package/src/features/dashboard/dashboard-route.svelte +82 -86
  170. package/src/features/dashboard/dashboard.scss +131 -0
  171. package/src/features/development/development-tools.svelte +20 -36
  172. package/src/features/documents/document-action-bar.svelte +438 -0
  173. package/src/features/documents/document-api-view-loader.svelte +17 -6
  174. package/src/features/documents/document-api-view.scss +120 -0
  175. package/src/features/documents/document-api-view.svelte +169 -454
  176. package/src/features/documents/document-controller.svelte.ts +526 -613
  177. package/src/features/documents/document-credentials.scss +37 -0
  178. package/src/features/documents/document-credentials.svelte +72 -0
  179. package/src/features/documents/{document-route-dirty.ts → document-credentials.ts} +25 -0
  180. package/src/features/documents/document-field-sections.scss +39 -0
  181. package/src/features/documents/document-field-sections.svelte +4 -13
  182. package/src/features/documents/document-loading.scss +24 -0
  183. package/src/features/documents/document-lock-controller.svelte.ts +187 -0
  184. package/src/features/documents/document-operation-dialogs.svelte +110 -0
  185. package/src/features/documents/document-route-loader.svelte +17 -5
  186. package/src/features/documents/document-route.svelte +305 -811
  187. package/src/features/documents/document-schedule.scss +127 -0
  188. package/src/features/documents/document-schedule.svelte +323 -0
  189. package/src/features/documents/document-title.ts +25 -0
  190. package/src/features/documents/document.scss +274 -0
  191. package/src/features/documents/live-preview-panel.scss +117 -0
  192. package/src/features/documents/live-preview-panel.svelte +167 -168
  193. package/src/features/documents/live-preview.ts +2 -10
  194. package/src/features/documents/upload-document-preview-loader.svelte +26 -23
  195. package/src/features/localization/content-locale-switcher.scss +43 -0
  196. package/src/features/localization/content-locale-switcher.svelte +24 -17
  197. package/src/features/navigation/admin-breadcrumbs.scss +56 -0
  198. package/src/features/navigation/admin-breadcrumbs.svelte +102 -37
  199. package/src/features/navigation/admin-command-menu.svelte +38 -15
  200. package/src/features/navigation/admin-navigation.scss +133 -0
  201. package/src/features/navigation/admin-navigation.svelte +169 -0
  202. package/src/features/navigation/breadcrumb-context.svelte.ts +15 -0
  203. package/src/features/navigation/navigation-preference.svelte.ts +70 -0
  204. package/src/features/reference-browser/reference-browser-editor.svelte +180 -0
  205. package/src/features/reference-browser/reference-browser-list.scss +113 -0
  206. package/src/features/reference-browser/reference-browser-list.svelte +370 -0
  207. package/src/features/reference-browser/reference-browser-loader.svelte +26 -8
  208. package/src/features/reference-browser/reference-browser-workflow.svelte.ts +335 -236
  209. package/src/features/reference-browser/reference-browser.scss +217 -0
  210. package/src/features/reference-browser/reference-browser.svelte +154 -797
  211. package/src/features/reference-browser/reference-collection-picker.svelte +116 -0
  212. package/src/features/reference-browser/reference-list-query.ts +35 -0
  213. package/src/features/uploads/bulk-upload-controller.svelte.ts +604 -0
  214. package/src/features/uploads/bulk-upload-edit-all.svelte +92 -0
  215. package/src/features/uploads/bulk-upload-editor.svelte +191 -0
  216. package/src/features/uploads/bulk-upload-file-picker.svelte +70 -0
  217. package/src/features/uploads/bulk-upload-remote.svelte +100 -0
  218. package/src/features/uploads/bulk-upload-route-loader.svelte +54 -0
  219. package/src/features/uploads/bulk-upload-route.svelte +172 -592
  220. package/src/features/uploads/bulk-upload-sidebar.svelte +125 -0
  221. package/src/features/uploads/bulk-upload.scss +526 -0
  222. package/src/features/uploads/bulk-upload.ts +105 -14
  223. package/src/features/uploads/crop-surface.ts +150 -0
  224. package/src/features/uploads/image-editor.svelte +231 -0
  225. package/src/features/uploads/rendition-preview.svelte +64 -0
  226. package/src/features/uploads/upload-control.svelte +340 -0
  227. package/src/features/uploads/upload-document-contracts.ts +30 -0
  228. package/src/features/uploads/upload-draft.svelte.ts +294 -0
  229. package/src/features/uploads/upload-metadata-fields.scss +32 -0
  230. package/src/features/uploads/upload-metadata-fields.svelte +8 -7
  231. package/src/features/uploads/upload-thumbnail.svelte +47 -0
  232. package/src/features/uploads/upload.scss +361 -0
  233. package/src/features/versions/version-comparison.svelte +361 -0
  234. package/src/features/versions/version-diff-fields.svelte +136 -0
  235. package/src/features/versions/version-diff.ts +296 -41
  236. package/src/features/versions/version-history-controller.svelte.ts +311 -0
  237. package/src/features/versions/version-history-route-loader.svelte +32 -0
  238. package/src/features/versions/version-history-route.svelte +157 -522
  239. package/src/features/versions/version-history.scss +444 -0
  240. package/src/features/versions/version-history.ts +45 -0
  241. package/src/features/versions/version-table.svelte +91 -0
  242. package/src/fields/embedded-schema-draft-editor.scss +48 -0
  243. package/src/fields/embedded-schema-draft-editor.svelte +24 -16
  244. package/src/fields/field-collapsible.svelte +14 -8
  245. package/src/fields/field-layout.scss +171 -0
  246. package/src/fields/field-layout.svelte +3 -5
  247. package/src/fields/field-renderer.svelte +17 -29
  248. package/src/fields/field-shell.svelte +1 -1
  249. package/src/fields/field-tab-layout.svelte +25 -19
  250. package/src/fields/join/join-field-controller.svelte.ts +513 -0
  251. package/src/fields/join/join-field-values.ts +111 -0
  252. package/src/fields/join/join-field.scss +189 -0
  253. package/src/fields/join/join-field.svelte +164 -332
  254. package/src/fields/live-validation-feedback.svelte +4 -3
  255. package/src/fields/local-field-editor.svelte +2 -2
  256. package/src/fields/nested/block-name-input.svelte +11 -12
  257. package/src/fields/nested/block-picker.scss +128 -0
  258. package/src/fields/nested/block-picker.svelte +81 -0
  259. package/src/fields/nested/block-placeholder.svg +2 -0
  260. package/src/fields/nested/nested-field.scss +331 -0
  261. package/src/fields/nested/nested-field.svelte +265 -245
  262. package/src/fields/nested/sortable-row.svelte +9 -7
  263. package/src/fields/plugin-field.svelte +4 -6
  264. package/src/fields/point/point-field.svelte +51 -38
  265. package/src/fields/primitive-list/primitive-list-field.svelte +63 -19
  266. package/src/fields/primitive-list/primitive-list.scss +109 -0
  267. package/src/fields/radio/radio-field.svelte +2 -1
  268. package/src/fields/relationship/relationship-field-controller.svelte.ts +81 -41
  269. package/src/fields/relationship/relationship-field.svelte +144 -175
  270. package/src/fields/relationship/relationship-lookup-controller.svelte.ts +58 -30
  271. package/src/fields/relationship/relationship-query.ts +2 -10
  272. package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +36 -62
  273. package/src/fields/relationship/relationship-quick-picker.svelte +178 -227
  274. package/src/fields/relationship/relationship-selection.svelte +120 -0
  275. package/src/fields/relationship/relationship-value.ts +46 -7
  276. package/src/fields/relationship/relationship.scss +182 -0
  277. package/src/fields/scalar/code-field.svelte +101 -0
  278. package/src/fields/scalar/date-control-value.ts +13 -0
  279. package/src/fields/scalar/scalar-field.svelte +25 -67
  280. package/src/fields/select/select-field.svelte +132 -94
  281. package/src/fields/text/slug-field.scss +28 -0
  282. package/src/fields/text/slug-field.svelte +19 -15
  283. package/src/fields/text/text-field.svelte +1 -2
  284. package/src/fields/ui/ui-field.svelte +7 -4
  285. package/src/fields/virtual/virtual-field.svelte +2 -5
  286. package/src/index.ts +1 -2
  287. package/src/routing.ts +2 -0
  288. package/src/app/deferred-redirect.svelte +0 -23
  289. package/src/components/code-block/code-block.svelte +0 -107
  290. package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +0 -50
  291. package/src/components/ui/bulk-action-bar/index.ts +0 -1
  292. package/src/components/ui/button/index.ts +0 -8
  293. package/src/components/ui/calendar/calendar-caption.svelte +0 -72
  294. package/src/components/ui/calendar/calendar-cell.svelte +0 -20
  295. package/src/components/ui/calendar/calendar-day.svelte +0 -27
  296. package/src/components/ui/calendar/calendar-grid-row.svelte +0 -13
  297. package/src/components/ui/calendar/calendar-grid.svelte +0 -17
  298. package/src/components/ui/calendar/calendar-head-cell.svelte +0 -20
  299. package/src/components/ui/calendar/calendar-header.svelte +0 -20
  300. package/src/components/ui/calendar/calendar-heading.svelte +0 -17
  301. package/src/components/ui/calendar/calendar-month-select.svelte +0 -45
  302. package/src/components/ui/calendar/calendar-month.svelte +0 -16
  303. package/src/components/ui/calendar/calendar-months.svelte +0 -20
  304. package/src/components/ui/calendar/calendar-nav.svelte +0 -20
  305. package/src/components/ui/calendar/calendar-next-button.svelte +0 -30
  306. package/src/components/ui/calendar/calendar-prev-button.svelte +0 -30
  307. package/src/components/ui/calendar/calendar-year-select.svelte +0 -44
  308. package/src/components/ui/calendar/calendar.svelte +0 -118
  309. package/src/components/ui/calendar/index.ts +0 -1
  310. package/src/components/ui/checkbox/checkbox.svelte +0 -35
  311. package/src/components/ui/checkbox/index.ts +0 -1
  312. package/src/components/ui/dialog/dialog-close.svelte +0 -11
  313. package/src/components/ui/dialog/dialog-content.svelte +0 -52
  314. package/src/components/ui/dialog/dialog-description.svelte +0 -20
  315. package/src/components/ui/dialog/dialog-footer.svelte +0 -20
  316. package/src/components/ui/dialog/dialog-header.svelte +0 -20
  317. package/src/components/ui/dialog/dialog-overlay.svelte +0 -20
  318. package/src/components/ui/dialog/dialog-title.svelte +0 -17
  319. package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
  320. package/src/components/ui/dialog/dialog.svelte +0 -7
  321. package/src/components/ui/dialog/index.ts +0 -8
  322. package/src/components/ui/empty-state/empty-state.svelte +0 -34
  323. package/src/components/ui/empty-state/index.ts +0 -1
  324. package/src/components/ui/input/index.ts +0 -1
  325. package/src/components/ui/pagination/index.ts +0 -1
  326. package/src/components/ui/pagination/pagination.svelte +0 -88
  327. package/src/components/ui/radio-group/radio-group-item.svelte +0 -17
  328. package/src/components/ui/slider/index.ts +0 -3
  329. package/src/components/ui/slider/slider-content.svelte +0 -59
  330. package/src/components/ui/slider/slider.svelte +0 -35
  331. package/src/components/ui/table/index.ts +0 -6
  332. package/src/components/ui/table/table-body.svelte +0 -20
  333. package/src/components/ui/table/table-cell.svelte +0 -23
  334. package/src/components/ui/table/table-head.svelte +0 -23
  335. package/src/components/ui/table/table-header.svelte +0 -20
  336. package/src/components/ui/table/table-row.svelte +0 -23
  337. package/src/components/ui/table/table.svelte +0 -22
  338. package/src/components/ui/textarea/index.ts +0 -1
  339. package/src/components/ui/tooltip/index.ts +0 -4
  340. package/src/components/ui/tooltip/tooltip-content.svelte +0 -50
  341. package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
  342. package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
  343. package/src/components/ui/tooltip/tooltip.svelte +0 -7
  344. package/src/features/auth/account-menu.svelte +0 -159
  345. package/src/features/auth/account-security-controller.svelte.ts +0 -119
  346. package/src/features/collections/collection-list-bulk-actions.svelte +0 -143
  347. package/src/features/collections/collection-list-bulk-editor.svelte +0 -157
  348. package/src/features/collections/collection-list-filter-popover.svelte +0 -238
  349. package/src/features/collections/collection-list-workspace-toolbar.svelte +0 -409
  350. package/src/features/collections/collection-table-skeleton.svelte +0 -44
  351. package/src/features/documents/document-heading.ts +0 -20
  352. package/src/features/documents/upload-document-preview.svelte +0 -525
@@ -0,0 +1,604 @@
1
+ import type {
2
+ AccessCapabilitiesEnvelope,
3
+ AdminReadResultV1,
4
+ SchemaCollection,
5
+ } from "@riducms/protocol";
6
+ import { RiduError } from "@riducms/sdk";
7
+
8
+ import type { AdminDocument } from "@admin/core/api/admin-client";
9
+ import { FormController, FormValidationError } from "@admin/core/forms/form-controller.svelte";
10
+ import { connectDocumentLiveValidation } from "@admin/core/forms/live-validation.svelte";
11
+ import { initialFormValues } from "@admin/core/forms/form-schema";
12
+ import { invalidFieldLabels } from "@admin/core/forms/form-validation";
13
+ import type { NotificationCenter } from "@admin/core/notifications/notification-center.svelte";
14
+ import type { AdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
15
+ import { initialBulkEditValues } from "@admin/features/bulk-edit/bulk-edit-fields";
16
+ import {
17
+ applyBulkUploadValues,
18
+ bulkUploadBulkEditFields,
19
+ bulkUploadDocumentFields,
20
+ bulkUploadFileIdentity,
21
+ bulkUploadRemoteFilename,
22
+ initialBulkUploadValues,
23
+ } from "@admin/features/uploads/bulk-upload";
24
+ import { UploadDraft } from "@admin/features/uploads/upload-draft.svelte";
25
+
26
+ export type BulkUploadStatus = "queued" | "uploading" | "complete" | "failed" | "uncertain";
27
+
28
+ interface BulkUploadControllerOptions {
29
+ runtime: AdminRuntime;
30
+ notifications: NotificationCenter;
31
+ get slug(): string;
32
+ get locale(): string | undefined;
33
+ get collection(): SchemaCollection | undefined;
34
+ get routeIdentity(): string;
35
+ get preparedAccess(): AdminReadResultV1<AccessCapabilitiesEnvelope> | undefined;
36
+ }
37
+
38
+ export class BulkUploadItem {
39
+ readonly form: FormController;
40
+ readonly upload: UploadDraft;
41
+ status = $state<BulkUploadStatus>("queued");
42
+ error = $state<string>();
43
+ documentID = $state<string>();
44
+ ready: Promise<void>;
45
+ #disposed = false;
46
+
47
+ constructor(
48
+ readonly id: string,
49
+ readonly sourceFile: File,
50
+ private readonly collection: SchemaCollection,
51
+ private readonly runtime: AdminRuntime,
52
+ locale: string | undefined,
53
+ access: AccessCapabilitiesEnvelope | undefined
54
+ ) {
55
+ const fields = bulkUploadDocumentFields(collection.fields);
56
+ const values = initialBulkUploadValues(sourceFile.name, fields, collection.admin.useAsTitle);
57
+ this.form = new FormController(values, runtime.i18n);
58
+ this.form.reset(values, fields);
59
+ connectDocumentLiveValidation(this.form, runtime.client);
60
+ this.form.setResource({ collection: collection.slug });
61
+ this.form.setLocalization(locale);
62
+ this.form.setAccess(access, "create");
63
+ this.upload = new UploadDraft(runtime.client, () => collection.slug, runtime.i18n);
64
+ this.ready = this.prepare();
65
+ }
66
+
67
+ get issueCount() {
68
+ return this.form.issues.length + (this.upload.error === undefined ? 0 : 1);
69
+ }
70
+
71
+ get issueLabels() {
72
+ const labels = invalidFieldLabels(
73
+ bulkUploadDocumentFields(this.collection.fields),
74
+ this.form.issues,
75
+ this.runtime.i18n
76
+ );
77
+ return this.upload.error === undefined ? labels : [this.upload.error, ...labels];
78
+ }
79
+
80
+ get editable() {
81
+ return this.status === "queued" || this.status === "failed";
82
+ }
83
+
84
+ setAccess(access: AccessCapabilitiesEnvelope | undefined) {
85
+ this.form.setAccess(access, "create");
86
+ }
87
+
88
+ dispose() {
89
+ if (this.#disposed) return;
90
+ this.#disposed = true;
91
+ this.form.disposeBindings();
92
+ this.upload.dispose();
93
+ }
94
+
95
+ private async prepare() {
96
+ const settings = this.collection.uploadSettings;
97
+ if (settings === undefined) return;
98
+ await this.upload.select(this.sourceFile, settings);
99
+ if (this.#disposed) return;
100
+ if (this.upload.file !== undefined) return;
101
+ this.status = "failed";
102
+ this.error = this.upload.error ?? this.runtime.i18n.t("uploads:fileOpenFailed");
103
+ }
104
+ }
105
+
106
+ export class BulkUploadController {
107
+ queue = $state.raw<BulkUploadItem[]>([]);
108
+ activeIndex = $state(0);
109
+ running = $state(false);
110
+ accessLoading = $state(true);
111
+ access = $state.raw<AccessCapabilitiesEnvelope>();
112
+ accessError = $state<string>();
113
+ remoteURL = $state("");
114
+ remotePending = $state(false);
115
+ remoteError = $state<string>();
116
+ remoteOutcomeUncertain = $state(false);
117
+ remoteDocumentID = $state<string>();
118
+ bulkEditOpen = $state(false);
119
+ bulkEditPaths = $state.raw<string[]>([]);
120
+ readonly remoteForm: FormController;
121
+ readonly bulkEditForm: FormController;
122
+ #remoteAttemptURL = "";
123
+ #nextID = 0;
124
+ #owner = "";
125
+ #accessKey = "";
126
+ #preparedAccessIdentity = "";
127
+ #queueRequest?: AbortController;
128
+ #remoteRequest?: AbortController;
129
+ #accessRequest?: AbortController;
130
+ #stopRemoteChangeObserver: () => void;
131
+ #disposed = false;
132
+
133
+ constructor(readonly options: BulkUploadControllerOptions) {
134
+ this.remoteForm = new FormController({}, options.runtime.i18n);
135
+ connectDocumentLiveValidation(this.remoteForm, options.runtime.client);
136
+ this.bulkEditForm = new FormController({}, options.runtime.i18n);
137
+ this.#stopRemoteChangeObserver = this.remoteForm.observeChanges(() => {
138
+ if (this.remoteDocumentID !== undefined && this.remoteForm.dirty)
139
+ this.remoteDocumentID = undefined;
140
+ });
141
+
142
+ $effect.pre(() => {
143
+ const owner = `${options.slug}:${options.locale ?? "default"}:${options.runtime.manifestRevision}`;
144
+ if (owner !== this.#owner) this.#activateOwner(owner);
145
+
146
+ const accessKey = `${owner}:${options.routeIdentity}`;
147
+ if (accessKey !== this.#accessKey) {
148
+ const prepared =
149
+ this.#preparedAccessIdentity === options.routeIdentity
150
+ ? undefined
151
+ : options.preparedAccess;
152
+ if (prepared !== undefined) this.#preparedAccessIdentity = options.routeIdentity;
153
+ this.#loadAccess(accessKey, prepared);
154
+ }
155
+ });
156
+
157
+ $effect(() => () => this.dispose());
158
+ }
159
+
160
+ get collection() {
161
+ return this.options.collection;
162
+ }
163
+
164
+ get locale() {
165
+ return this.options.locale;
166
+ }
167
+
168
+ get fields() {
169
+ return bulkUploadDocumentFields(this.collection?.fields ?? []);
170
+ }
171
+
172
+ get editableFields() {
173
+ const form = this.activeItem?.form ?? this.remoteForm;
174
+ return bulkUploadDocumentFields(this.collection?.fields ?? []).filter((field) =>
175
+ form.canRead(field.path)
176
+ );
177
+ }
178
+
179
+ get bulkEditFields() {
180
+ return bulkUploadBulkEditFields(this.collection?.fields ?? []).filter(
181
+ (field) =>
182
+ this.access?.fields[field.path]?.read !== false &&
183
+ this.access?.fields[field.path]?.create !== false
184
+ );
185
+ }
186
+
187
+ get selectedBulkEditFields() {
188
+ const selected = new Set(this.bulkEditPaths);
189
+ return this.bulkEditFields.filter((field) => selected.has(field.path));
190
+ }
191
+
192
+ get activeItem() {
193
+ return this.queue[this.activeIndex];
194
+ }
195
+
196
+ get canCreate() {
197
+ return this.access?.operations.create === true;
198
+ }
199
+
200
+ get uploadEnabled() {
201
+ return (
202
+ this.collection?.capabilities.upload === true && this.collection.uploadSettings !== undefined
203
+ );
204
+ }
205
+
206
+ get pendingCount() {
207
+ return this.queue.filter((item) => item.status === "queued" || item.status === "failed").length;
208
+ }
209
+
210
+ get preparing() {
211
+ return this.draftItems.some((item) => item.upload.busy);
212
+ }
213
+
214
+ get completedCount() {
215
+ return this.queue.filter((item) => item.status === "complete").length;
216
+ }
217
+
218
+ get failedCount() {
219
+ return this.queue.filter((item) => item.status === "failed").length;
220
+ }
221
+
222
+ get uncertainCount() {
223
+ return this.queue.filter((item) => item.status === "uncertain").length;
224
+ }
225
+
226
+ get draftItems() {
227
+ return this.queue.filter((item) => item.status === "queued" || item.status === "failed");
228
+ }
229
+
230
+ get dirty() {
231
+ return (
232
+ this.running ||
233
+ this.remotePending ||
234
+ this.queue.some((item) => item.status !== "complete") ||
235
+ this.remoteURL.trim() !== "" ||
236
+ this.remoteForm.dirty ||
237
+ this.remoteOutcomeUncertain
238
+ );
239
+ }
240
+
241
+ addFiles = async (files: Iterable<File>) => {
242
+ const collection = this.collection;
243
+ if (!collection || !this.uploadEnabled || !this.canCreate || this.running) return;
244
+ const known = new Set(this.queue.map((item) => bulkUploadFileIdentity(item.sourceFile)));
245
+ const additions: BulkUploadItem[] = [];
246
+ for (const file of files) {
247
+ const identity = bulkUploadFileIdentity(file);
248
+ if (known.has(identity)) continue;
249
+ known.add(identity);
250
+ additions.push(
251
+ new BulkUploadItem(
252
+ `upload-${++this.#nextID}`,
253
+ file,
254
+ collection,
255
+ this.options.runtime,
256
+ this.options.locale,
257
+ this.access
258
+ )
259
+ );
260
+ }
261
+ if (additions.length === 0) return;
262
+ const wasEmpty = this.queue.length === 0;
263
+ this.queue = [...this.queue, ...additions];
264
+ if (wasEmpty) this.activeIndex = 0;
265
+ await Promise.all(additions.map((item) => item.ready));
266
+ };
267
+
268
+ remove = (id: string) => {
269
+ if (this.running) return;
270
+ const index = this.queue.findIndex((item) => item.id === id);
271
+ if (index < 0) return;
272
+ const activeIndex = this.activeIndex;
273
+ this.queue[index]?.dispose();
274
+ this.queue = this.queue.filter((item) => item.id !== id);
275
+ this.activeIndex = Math.max(
276
+ 0,
277
+ Math.min(index < activeIndex ? activeIndex - 1 : activeIndex, this.queue.length - 1)
278
+ );
279
+ };
280
+
281
+ select = (index: number) => {
282
+ if (index >= 0 && index < this.queue.length) this.activeIndex = index;
283
+ };
284
+
285
+ previous = () => {
286
+ if (this.queue.length === 0) return;
287
+ this.activeIndex = (this.activeIndex - 1 + this.queue.length) % this.queue.length;
288
+ };
289
+
290
+ next = () => {
291
+ if (this.queue.length === 0) return;
292
+ this.activeIndex = (this.activeIndex + 1) % this.queue.length;
293
+ };
294
+
295
+ saveAll = () => this.#saveItems(this.queue);
296
+
297
+ retry = (item: BulkUploadItem) => {
298
+ if (item.status !== "failed") return Promise.resolve();
299
+ return this.#saveItems([item]);
300
+ };
301
+
302
+ setRemoteURL = (value: string) => {
303
+ if (value !== this.remoteURL) this.remoteDocumentID = undefined;
304
+ this.remoteURL = value;
305
+ if (this.remoteOutcomeUncertain && value.trim() !== this.#remoteAttemptURL) {
306
+ this.remoteOutcomeUncertain = false;
307
+ this.remoteError = undefined;
308
+ }
309
+ };
310
+
311
+ prepareRemoteDefaults = () => {
312
+ const collection = this.collection;
313
+ if (!collection || this.remoteForm.dirty) return;
314
+ this.remoteForm.reset(
315
+ initialBulkUploadValues(
316
+ bulkUploadRemoteFilename(this.remoteURL),
317
+ this.fields,
318
+ collection.admin.useAsTitle
319
+ ),
320
+ this.fields
321
+ );
322
+ this.#configureForm(this.remoteForm);
323
+ };
324
+
325
+ uploadRemote = async () => {
326
+ const collection = this.collection;
327
+ const owner = this.#owner;
328
+ const fields = this.fields;
329
+ const locale = this.options.locale;
330
+ const url = this.remoteURL.trim();
331
+ if (
332
+ !collection ||
333
+ this.remotePending ||
334
+ !this.uploadEnabled ||
335
+ !this.canCreate ||
336
+ url === "" ||
337
+ this.remoteOutcomeUncertain
338
+ )
339
+ return;
340
+ this.prepareRemoteDefaults();
341
+ const request = new AbortController();
342
+ this.#remoteRequest?.abort();
343
+ this.#remoteRequest = request;
344
+ this.remotePending = true;
345
+ this.remoteError = undefined;
346
+ this.remoteOutcomeUncertain = false;
347
+ this.remoteDocumentID = undefined;
348
+ this.#remoteAttemptURL = url;
349
+ try {
350
+ const document = await this.remoteForm.submit(fields, true, (values) =>
351
+ this.options.runtime.client.uploadFromURL(collection.slug, url, {
352
+ data: values,
353
+ signal: request.signal,
354
+ locale,
355
+ })
356
+ );
357
+ if (request.signal.aborted || this.#owner !== owner) return;
358
+ this.remoteURL = "";
359
+ this.remoteForm.reset(initialFormValues(fields), fields);
360
+ this.#configureForm(this.remoteForm);
361
+ this.remoteDocumentID = document.id;
362
+ this.options.runtime.documentsChanged();
363
+ this.options.notifications.success({
364
+ title: this.options.runtime.i18n.t("uploads:remoteAssetUploaded"),
365
+ });
366
+ } catch (cause) {
367
+ if (request.signal.aborted || this.#owner !== owner) return;
368
+ const confirmed =
369
+ cause instanceof FormValidationError || (cause instanceof RiduError && cause.status < 500);
370
+ this.remoteOutcomeUncertain = !confirmed;
371
+ this.remoteError = confirmed
372
+ ? cause instanceof Error
373
+ ? cause.message
374
+ : this.options.runtime.i18n.t("documents:saveFailed")
375
+ : this.options.runtime.i18n.t("uploads:remoteOutcomeNotConfirmed");
376
+ } finally {
377
+ if (this.#remoteRequest === request && this.#owner === owner) {
378
+ this.#remoteRequest = undefined;
379
+ this.remotePending = false;
380
+ }
381
+ }
382
+ };
383
+
384
+ setBulkEditPaths = (paths: string[]) => {
385
+ const allowed = new Set(this.bulkEditFields.map((field) => field.path));
386
+ this.bulkEditPaths = [...new Set(paths.filter((path) => allowed.has(path)))];
387
+ const fields = this.selectedBulkEditFields;
388
+ this.bulkEditForm.reset(initialBulkEditValues(fields, this.bulkEditForm.snapshot()), fields);
389
+ this.#configureForm(this.bulkEditForm);
390
+ };
391
+
392
+ openBulkEdit = () => {
393
+ this.bulkEditPaths = [];
394
+ this.bulkEditForm.reset({}, []);
395
+ this.#configureForm(this.bulkEditForm);
396
+ this.bulkEditOpen = true;
397
+ };
398
+
399
+ applyBulkEdit = () => {
400
+ const fields = this.selectedBulkEditFields;
401
+ const values = this.bulkEditForm.snapshot();
402
+ for (const item of this.draftItems) applyBulkUploadValues(item.form, fields, values);
403
+ this.bulkEditOpen = false;
404
+ };
405
+
406
+ discard = () => {
407
+ this.#queueRequest?.abort();
408
+ this.#remoteRequest?.abort();
409
+ this.#disposeQueue();
410
+ this.queue = [];
411
+ this.activeIndex = 0;
412
+ this.running = false;
413
+ this.remotePending = false;
414
+ this.remoteURL = "";
415
+ this.remoteError = undefined;
416
+ this.remoteOutcomeUncertain = false;
417
+ this.remoteDocumentID = undefined;
418
+ this.#remoteAttemptURL = "";
419
+ this.remoteForm.reset(initialFormValues(this.fields), this.fields);
420
+ this.#configureForm(this.remoteForm);
421
+ this.bulkEditOpen = false;
422
+ this.bulkEditPaths = [];
423
+ this.bulkEditForm.reset({}, []);
424
+ this.#configureForm(this.bulkEditForm);
425
+ };
426
+
427
+ dispose() {
428
+ if (this.#disposed) return;
429
+ this.#disposed = true;
430
+ this.#accessRequest?.abort();
431
+ this.#queueRequest?.abort();
432
+ this.#remoteRequest?.abort();
433
+ this.#disposeQueue();
434
+ this.#stopRemoteChangeObserver();
435
+ this.remoteForm.disposeBindings();
436
+ this.bulkEditForm.disposeBindings();
437
+ }
438
+
439
+ async #saveItems(items: readonly BulkUploadItem[]) {
440
+ if (this.running || !this.uploadEnabled || !this.canCreate) return;
441
+ const collection = this.collection;
442
+ if (!collection) return;
443
+ const owner = this.#owner;
444
+ const fields = this.fields;
445
+ const locale = this.options.locale;
446
+ const candidates = items.filter((item) => item.status === "queued" || item.status === "failed");
447
+ if (candidates.length === 0 || candidates.some((item) => item.upload.busy)) return;
448
+ const request = new AbortController();
449
+ this.#queueRequest?.abort();
450
+ this.#queueRequest = request;
451
+ this.running = true;
452
+
453
+ for (const item of candidates) {
454
+ await item.ready;
455
+ if (request.signal.aborted || this.#owner !== owner) return;
456
+ if (item.upload.file === undefined) {
457
+ item.status = "failed";
458
+ item.error =
459
+ item.upload.error ?? this.options.runtime.i18n.t("uploads:chooseFileBeforeSaving");
460
+ continue;
461
+ }
462
+ item.status = "uploading";
463
+ item.error = undefined;
464
+ try {
465
+ const document = await item.form.submit(fields, true, (values) =>
466
+ this.options.runtime.client.upload(collection.slug, item.upload.file!, {
467
+ filename: item.upload.filename,
468
+ image: item.upload.image,
469
+ data: values,
470
+ signal: request.signal,
471
+ locale,
472
+ })
473
+ );
474
+ if (request.signal.aborted || this.#owner !== owner) return;
475
+ item.status = "complete";
476
+ item.documentID = document.id;
477
+ item.upload.reset(document as AdminDocument);
478
+ this.options.runtime.documentsChanged();
479
+ } catch (cause) {
480
+ if (request.signal.aborted || this.#owner !== owner) return;
481
+ const confirmed =
482
+ cause instanceof FormValidationError ||
483
+ (cause instanceof RiduError && cause.status < 500);
484
+ item.status = confirmed ? "failed" : "uncertain";
485
+ item.error = confirmed
486
+ ? cause instanceof Error
487
+ ? cause.message
488
+ : this.options.runtime.i18n.t("documents:saveFailed")
489
+ : this.options.runtime.i18n.t("uploads:outcomeNotConfirmed");
490
+ }
491
+ }
492
+
493
+ if (request.signal.aborted || this.#owner !== owner) return;
494
+ this.running = false;
495
+ this.#queueRequest = undefined;
496
+ const failures = this.failedCount;
497
+ const uncertain = this.uncertainCount;
498
+ if (failures === 0 && uncertain === 0 && this.completedCount > 0) {
499
+ this.options.notifications.success({
500
+ title: this.options.runtime.i18n.t("uploads:assetsUploaded", {
501
+ count: this.completedCount,
502
+ }),
503
+ });
504
+ } else if (failures > 0 || uncertain > 0) {
505
+ this.options.notifications.error({
506
+ title: this.options.runtime.i18n.t("uploads:needAttention", {
507
+ count: failures + uncertain,
508
+ }),
509
+ message:
510
+ uncertain > 0
511
+ ? this.options.runtime.i18n.t("uploads:unknownOutcomesDescription")
512
+ : this.options.runtime.i18n.t("uploads:correctMetadata"),
513
+ });
514
+ const firstFailure = this.queue.findIndex((item) => item.status === "failed");
515
+ if (firstFailure >= 0) this.activeIndex = firstFailure;
516
+ }
517
+ }
518
+
519
+ #activateOwner(owner: string) {
520
+ this.#owner = owner;
521
+ this.#accessKey = "";
522
+ this.#accessRequest?.abort();
523
+ this.#queueRequest?.abort();
524
+ this.#remoteRequest?.abort();
525
+ this.#accessRequest = undefined;
526
+ this.#queueRequest = undefined;
527
+ this.#remoteRequest = undefined;
528
+ this.#disposeQueue();
529
+ this.queue = [];
530
+ this.activeIndex = 0;
531
+ this.running = false;
532
+ this.access = undefined;
533
+ this.accessLoading = true;
534
+ this.accessError = undefined;
535
+ this.remoteURL = "";
536
+ this.remotePending = false;
537
+ this.remoteError = undefined;
538
+ this.remoteOutcomeUncertain = false;
539
+ this.remoteDocumentID = undefined;
540
+ this.#remoteAttemptURL = "";
541
+ this.remoteForm.reset(initialFormValues(this.fields), this.fields);
542
+ this.#configureForm(this.remoteForm);
543
+ this.bulkEditOpen = false;
544
+ this.bulkEditPaths = [];
545
+ this.bulkEditForm.reset({}, []);
546
+ }
547
+
548
+ #loadAccess(key: string, prepared: AdminReadResultV1<AccessCapabilitiesEnvelope> | undefined) {
549
+ this.#accessKey = key;
550
+ this.#accessRequest?.abort();
551
+ this.#accessRequest = undefined;
552
+ if (prepared !== undefined) {
553
+ this.accessLoading = false;
554
+ this.accessError = prepared.error?.message;
555
+ this.#setAccess(prepared.value);
556
+ return;
557
+ }
558
+ const slug = this.options.slug;
559
+ if (slug === "") {
560
+ this.accessLoading = false;
561
+ return;
562
+ }
563
+ const request = new AbortController();
564
+ this.#accessRequest = request;
565
+ this.accessLoading = true;
566
+ this.accessError = undefined;
567
+ this.#setAccess(undefined);
568
+ this.options.runtime.client
569
+ .collectionAccess(slug, { signal: request.signal, locale: this.options.locale })
570
+ .then((access) => {
571
+ if (!request.signal.aborted) this.#setAccess(access);
572
+ })
573
+ .catch((cause: unknown) => {
574
+ if (!request.signal.aborted)
575
+ this.accessError =
576
+ cause instanceof Error
577
+ ? cause.message
578
+ : this.options.runtime.i18n.t("uploads:accessCheckFailed");
579
+ })
580
+ .finally(() => {
581
+ if (!request.signal.aborted) {
582
+ this.accessLoading = false;
583
+ this.#accessRequest = undefined;
584
+ }
585
+ });
586
+ }
587
+
588
+ #setAccess(access: AccessCapabilitiesEnvelope | undefined) {
589
+ this.access = access;
590
+ for (const item of this.queue) item.setAccess(access);
591
+ this.remoteForm.setAccess(access, "create");
592
+ this.bulkEditForm.setAccess(access, "create");
593
+ }
594
+
595
+ #configureForm(form: FormController) {
596
+ form.setResource({ collection: this.options.slug });
597
+ form.setLocalization(this.options.locale);
598
+ form.setAccess(this.access, "create");
599
+ }
600
+
601
+ #disposeQueue() {
602
+ for (const item of this.queue) item.dispose();
603
+ }
604
+ }
@@ -0,0 +1,92 @@
1
+ <script lang="ts">
2
+ import { Dialog } from "bits-ui";
3
+ import { getAdminI18n } from "@riducms/plugin";
4
+ import { Button } from "@riducms/ui";
5
+ import { untrack } from "svelte";
6
+ import XIcon from "~icons/lucide/x";
7
+
8
+ import FieldPicker from "@admin/features/bulk-edit/field-picker.svelte";
9
+ import DocumentFieldSections from "@admin/features/documents/document-field-sections.svelte";
10
+ import { bulkUploadRenderFields } from "@admin/features/uploads/bulk-upload";
11
+ import type { BulkUploadController } from "@admin/features/uploads/bulk-upload-controller.svelte";
12
+
13
+ let {
14
+ controller,
15
+ contentDirection,
16
+ }: { controller: BulkUploadController; contentDirection: "ltr" | "rtl" } = $props();
17
+ const i18n = getAdminI18n();
18
+ const renderedFields = $derived(
19
+ bulkUploadRenderFields(controller.selectedBulkEditFields, "bulk-upload-edit")
20
+ );
21
+
22
+ $effect(() => {
23
+ const blocked = !controller.bulkEditOpen;
24
+ untrack(() => (controller.bulkEditForm.writeBlocked = blocked));
25
+ return () => {
26
+ untrack(() => (controller.bulkEditForm.writeBlocked = false));
27
+ };
28
+ });
29
+ </script>
30
+
31
+ <Dialog.Root
32
+ open={controller.bulkEditOpen}
33
+ onOpenChange={(open) => {
34
+ controller.bulkEditOpen = open;
35
+ }}
36
+ >
37
+ {#if controller.bulkEditOpen}
38
+ <Dialog.Portal>
39
+ <Dialog.Overlay class="ridu-bulk-edit-overlay" />
40
+ <Dialog.Content class="ridu-bulk-edit-drawer ridu-bulk-edit-drawer--uploads">
41
+ <div class="ridu-bulk-edit-main">
42
+ <header class="ridu-bulk-edit-header">
43
+ <Dialog.Title level={2} class="ridu-bulk-edit-title">
44
+ {i18n.t("uploads:editingAssets", {
45
+ count: controller.draftItems.length,
46
+ formattedCount: i18n.formatNumber(controller.draftItems.length),
47
+ })}
48
+ </Dialog.Title>
49
+ <Dialog.Close
50
+ type="button"
51
+ class="ridu-bulk-edit-close"
52
+ aria-label={i18n.t("general:close")}
53
+ >
54
+ <XIcon />
55
+ </Dialog.Close>
56
+ </header>
57
+ <Dialog.Description class="ridu-bulk-edit-description">
58
+ {i18n.t("uploads:bulkEditDescription")}
59
+ </Dialog.Description>
60
+
61
+ <FieldPicker
62
+ fields={controller.bulkEditFields}
63
+ value={controller.bulkEditPaths}
64
+ onValueChange={controller.setBulkEditPaths}
65
+ />
66
+
67
+ {#if renderedFields.length > 0}
68
+ <div class="ridu-bulk-edit-fields" dir={contentDirection}>
69
+ <DocumentFieldSections
70
+ fields={renderedFields}
71
+ form={controller.bulkEditForm}
72
+ stacked
73
+ />
74
+ </div>
75
+ {/if}
76
+ </div>
77
+
78
+ <aside class="ridu-bulk-edit-sidebar">
79
+ <Button variant="outline" onclick={() => (controller.bulkEditOpen = false)}>
80
+ {i18n.t("general:cancel")}
81
+ </Button>
82
+ <Button
83
+ disabled={controller.selectedBulkEditFields.length === 0}
84
+ onclick={controller.applyBulkEdit}
85
+ >
86
+ {i18n.t("uploads:applyToFiles", { count: controller.draftItems.length })}
87
+ </Button>
88
+ </aside>
89
+ </Dialog.Content>
90
+ </Dialog.Portal>
91
+ {/if}
92
+ </Dialog.Root>