@antelopejs/dms 0.9.7 → 0.10.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 (414) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +1 -1
  3. package/data/module-store/catalog.json +2 -2
  4. package/dist/attachments/table-view.js +2 -2
  5. package/dist/attachments/table-view.js.map +1 -1
  6. package/dist/automation/actions.d.ts +2 -0
  7. package/dist/automation/actions.js +116 -71
  8. package/dist/automation/actions.js.map +1 -1
  9. package/dist/automation/i18n.d.ts +4 -0
  10. package/dist/automation/i18n.js +19 -0
  11. package/dist/automation/i18n.js.map +1 -0
  12. package/dist/automation/send-notification.d.ts +21 -0
  13. package/dist/automation/send-notification.js +163 -0
  14. package/dist/automation/send-notification.js.map +1 -0
  15. package/dist/automation/triggers.js +19 -25
  16. package/dist/automation/triggers.js.map +1 -1
  17. package/dist/client-origin-cors.d.ts +1 -0
  18. package/dist/client-origin-cors.js +19 -0
  19. package/dist/client-origin-cors.js.map +1 -0
  20. package/dist/config.d.ts +3 -0
  21. package/dist/config.js +5 -3
  22. package/dist/config.js.map +1 -1
  23. package/dist/frontend-module.d.ts +4 -0
  24. package/dist/frontend-module.js +37 -0
  25. package/dist/frontend-module.js.map +1 -0
  26. package/dist/implementations/dms/client-ip.js +1 -1
  27. package/dist/implementations/dms/client-ip.js.map +1 -1
  28. package/dist/implementations/dms/dev-reload.d.ts +5 -0
  29. package/dist/implementations/dms/dev-reload.js +23 -3
  30. package/dist/implementations/dms/dev-reload.js.map +1 -1
  31. package/dist/implementations/dms/frontend-modules.d.ts +2 -4
  32. package/dist/implementations/dms/frontend-modules.js +22 -89
  33. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  34. package/dist/implementations/dms/frontend-ready.d.ts +12 -2
  35. package/dist/implementations/dms/frontend-ready.js +34 -4
  36. package/dist/implementations/dms/frontend-ready.js.map +1 -1
  37. package/dist/implementations/dms/layout-banners.d.ts +2 -1
  38. package/dist/implementations/dms/layout-banners.js +9 -0
  39. package/dist/implementations/dms/layout-banners.js.map +1 -1
  40. package/dist/implementations/dms/manifest.d.ts +1 -2
  41. package/dist/implementations/dms/manifest.js +2 -0
  42. package/dist/implementations/dms/manifest.js.map +1 -1
  43. package/dist/implementations/dms/page.d.ts +4 -3
  44. package/dist/implementations/dms/page.js +49 -18
  45. package/dist/implementations/dms/page.js.map +1 -1
  46. package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
  47. package/dist/implementations/dms/permissions-resolver.js +2 -2
  48. package/dist/implementations/dms/permissions-resolver.js.map +1 -1
  49. package/dist/implementations/dms/realtime.js +1 -0
  50. package/dist/implementations/dms/realtime.js.map +1 -1
  51. package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
  52. package/dist/implementations/dms-base/bulk-selection.js +23 -1
  53. package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
  54. package/dist/implementations/dms-base/filter-tokens.js +5 -3
  55. package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
  56. package/dist/implementations/dms-base/search-route.d.ts +1 -0
  57. package/dist/implementations/dms-base/search-route.js +35 -13
  58. package/dist/implementations/dms-base/search-route.js.map +1 -1
  59. package/dist/implementations/dms-base/table-view.js +11 -6
  60. package/dist/implementations/dms-base/table-view.js.map +1 -1
  61. package/dist/implementations/dms-notifications/index.d.ts +2 -2
  62. package/dist/implementations/dms-notifications/index.js +5 -5
  63. package/dist/implementations/dms-notifications/index.js.map +1 -1
  64. package/dist/index.js +28 -45
  65. package/dist/index.js.map +1 -1
  66. package/dist/pages/modules/index.d.ts +1 -0
  67. package/dist/pages/modules/index.js +1 -0
  68. package/dist/pages/modules/index.js.map +1 -1
  69. package/dist/pages/modules/modules-store.d.ts +9 -0
  70. package/dist/pages/modules/modules-store.js +30 -0
  71. package/dist/pages/modules/modules-store.js.map +1 -0
  72. package/dist/pages/settings/users/account-credentials.js +2 -2
  73. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  74. package/dist/pages/settings/users/account-data-store.js +25 -8
  75. package/dist/pages/settings/users/account-data-store.js.map +1 -1
  76. package/dist/pages/settings/users/invite-extension-routes.js +9 -3
  77. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
  78. package/dist/pages/settings/users/member-management.js +13 -14
  79. package/dist/pages/settings/users/member-management.js.map +1 -1
  80. package/dist/pages/settings/users/profile.d.ts +1 -4
  81. package/dist/pages/settings/users/profile.js +0 -35
  82. package/dist/pages/settings/users/profile.js.map +1 -1
  83. package/dist/pages/settings/users/role-editor-store.js +26 -18
  84. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  85. package/dist/pages/settings/users/role-editor.d.ts +0 -1
  86. package/dist/pages/settings/users/role-editor.js +1 -2
  87. package/dist/pages/settings/users/role-editor.js.map +1 -1
  88. package/dist/pages/settings/users/role-preview.js +1 -2
  89. package/dist/pages/settings/users/role-preview.js.map +1 -1
  90. package/dist/pages/settings/users/roles.js +2 -2
  91. package/dist/pages/settings/users/roles.js.map +1 -1
  92. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  93. package/dist/realtime/broker.d.ts +6 -0
  94. package/dist/realtime/broker.js +8 -1
  95. package/dist/realtime/broker.js.map +1 -1
  96. package/dist/realtime/presence.d.ts +8 -3
  97. package/dist/realtime/presence.js +8 -4
  98. package/dist/realtime/presence.js.map +1 -1
  99. package/dist/realtime/sessions.d.ts +11 -1
  100. package/dist/realtime/sessions.js +21 -3
  101. package/dist/realtime/sessions.js.map +1 -1
  102. package/dist/realtime/table-view.d.ts +2 -2
  103. package/dist/realtime/table-view.js +4 -3
  104. package/dist/realtime/table-view.js.map +1 -1
  105. package/dist/routes/auth/client-ip.d.ts +1 -0
  106. package/dist/routes/auth/client-ip.js +18 -0
  107. package/dist/routes/auth/client-ip.js.map +1 -1
  108. package/dist/routes/auth/oauth/config.d.ts +2 -1
  109. package/dist/routes/auth/oauth/config.js +5 -1
  110. package/dist/routes/auth/oauth/config.js.map +1 -1
  111. package/dist/routes/auth/session-handoff.d.ts +0 -1
  112. package/dist/routes/auth/session-handoff.js +0 -2
  113. package/dist/routes/auth/session-handoff.js.map +1 -1
  114. package/dist/routes/auth.js +3 -2
  115. package/dist/routes/auth.js.map +1 -1
  116. package/dist/routes/realtime.d.ts +13 -1
  117. package/dist/routes/realtime.js +114 -29
  118. package/dist/routes/realtime.js.map +1 -1
  119. package/dist/startup-config.d.ts +2 -0
  120. package/dist/startup-config.js +62 -0
  121. package/dist/startup-config.js.map +1 -0
  122. package/dist/store/interfaces.d.ts +3 -2
  123. package/dist/store/interfaces.js +16 -6
  124. package/dist/store/interfaces.js.map +1 -1
  125. package/dist/store/listing.js +24 -13
  126. package/dist/store/listing.js.map +1 -1
  127. package/dist/store/operations.js +4 -0
  128. package/dist/store/operations.js.map +1 -1
  129. package/dist/store/requirements.js +3 -2
  130. package/dist/store/requirements.js.map +1 -1
  131. package/dist/store/runtime.d.ts +10 -1
  132. package/dist/store/runtime.js +33 -6
  133. package/dist/store/runtime.js.map +1 -1
  134. package/dist/store/types.d.ts +10 -0
  135. package/dist/validation/auth.schema.d.ts +2 -2
  136. package/dist/validation/member-invite.schema.d.ts +2 -10
  137. package/dist/validation/member-invite.schema.js +2 -15
  138. package/dist/validation/member-invite.schema.js.map +1 -1
  139. package/frontend-vue/dms.frontend.ts +24 -5
  140. package/frontend-vue/dms.server.ts +17 -0
  141. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  142. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
  143. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  144. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  145. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  146. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  147. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  148. package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
  149. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  150. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  151. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  152. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  153. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  154. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  155. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
  156. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  157. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  158. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  159. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
  160. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  161. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
  162. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
  163. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  164. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  165. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
  166. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  167. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
  168. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  169. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
  170. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
  171. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  172. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  173. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  174. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  175. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  176. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  177. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  178. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  180. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  181. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  182. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  183. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  184. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  185. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  187. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  188. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  189. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  190. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  191. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  192. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  193. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  194. package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
  195. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
  196. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  197. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
  198. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  199. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  200. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  201. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  202. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
  203. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
  204. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  205. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  206. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  207. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
  208. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
  209. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
  210. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  211. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  212. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  213. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  214. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  215. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  216. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  217. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  218. package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
  219. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
  220. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
  221. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  222. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  223. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  224. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  225. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  226. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  227. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  228. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  229. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  230. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  231. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  232. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  233. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  234. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  235. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  236. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
  237. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  238. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  239. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  240. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  241. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  242. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  243. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  244. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  245. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  246. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  247. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  248. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  249. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  250. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  251. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  252. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  253. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  254. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  255. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  256. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  257. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
  258. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
  259. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  260. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  261. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
  262. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  263. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  264. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  265. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  266. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  267. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  268. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  269. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  270. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  271. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  273. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  274. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  277. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  278. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  279. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  280. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  281. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  282. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  283. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  284. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  285. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  286. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  290. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
  291. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  292. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  294. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  297. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  300. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  301. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  302. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  304. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  305. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
  306. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  307. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  308. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  309. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  310. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  311. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  321. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  322. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  323. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  324. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  325. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  326. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  327. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  328. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  329. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  330. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  331. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  332. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  333. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  334. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  335. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  336. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  337. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  338. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  339. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  340. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  341. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
  342. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  343. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  344. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  345. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  346. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  347. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  348. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  349. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  350. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  351. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  352. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
  353. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  354. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
  355. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  356. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  357. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  358. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  359. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  360. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  361. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  362. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  363. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  364. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  365. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  366. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
  367. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  368. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
  369. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  370. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  371. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  372. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  373. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  374. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  375. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  376. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  377. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  378. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  379. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  380. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
  381. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  382. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
  383. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
  384. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  385. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  386. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  387. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  388. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  389. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  390. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  391. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  392. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  393. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  394. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  395. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  396. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  397. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  398. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  399. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
  400. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
  401. package/frontend-vue/package.json +2 -2
  402. package/package.json +7 -6
  403. package/skills/dms/SKILL.md +1 -1
  404. package/skills/dms-auth/SKILL.md +5 -3
  405. package/skills/dms-best-practices/REFERENCE.md +2 -0
  406. package/skills/dms-best-practices/SKILL.md +4 -0
  407. package/skills/dms-module/SKILL.md +3 -2
  408. package/skills/dms-pages/REFERENCE.md +125 -16
  409. package/skills/dms-pages/SKILL.md +1 -1
  410. package/skills/dms-project/DEV-SERVERS.md +23 -1
  411. package/skills/dms-project/REFERENCE.md +1 -1
  412. package/skills/dms-project/SKILL.md +4 -1
  413. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  414. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -1,5 +1,4 @@
1
1
  <script setup lang="ts">
2
- import type { Ref, VNode } from "vue";
3
2
  import { MONO_CHIP_CLASS } from "#dms-ui/app/build/utils/monoChip";
4
3
  import UButton from "@nuxt/ui/components/Button.vue";
5
4
  import UIcon from "@nuxt/ui/runtime/vue/components/Icon.vue";
@@ -13,8 +12,8 @@ import type { Tone } from "#dms-ui/app/types/tone";
13
12
  import { useKeyboardPlatform } from "#dms-ui/app/composables/global/keyboardPlatform";
14
13
  import DmsChipGroup from "#dms-ui/app/build/components/form/ChipGroup.vue";
15
14
  import ModuleCatalogTile from "../build/components/pages/modules/ModuleCatalogTile.vue";
16
- import ModuleStoreTile from "../build/components/pages/modules/ModuleStoreTile.vue";
17
- import ModuleStoreDependencyPicker from "../build/components/pages/modules/ModuleStoreDependencyPicker.vue";
15
+ import ModuleStoreBanners from "../build/components/pages/modules/ModuleStoreBanners.vue";
16
+ import ModuleTileSkeleton from "../build/components/pages/modules/ModuleTileSkeleton.vue";
18
17
  import { usePageHeaderActions } from "../composables/layout/usePageHeaderActions";
19
18
  import {
20
19
  ENDPOINT_MODULES_LISTING,
@@ -24,7 +23,10 @@ import { useModuleHistory } from "../composables/page/useModuleHistory";
24
23
  import type { ModuleCatalogEntry } from "../types/page";
25
24
  import {
26
25
  MODULE_CATEGORY_ALL,
26
+ MODULE_STORE_PATH,
27
+ MODULE_TILE_GRID_CLASS,
27
28
  filterModules,
29
+ parseModuleSortMode,
28
30
  findModulePageMatch,
29
31
  moduleCategoryOptions,
30
32
  sortModules,
@@ -32,35 +34,19 @@ import {
32
34
  type ModuleSortMode,
33
35
  } from "../build/utils/modules-catalog";
34
36
  import {
35
- type ModuleStoreEntry,
36
- type StoreScope,
37
- FRONTEND_BUILD_COMMAND,
38
- filterByScope,
39
- isAwaitingFrontend,
40
- needsDependencyChoice,
41
37
  sortStoreModules,
38
+ storeOnlyModules,
39
+ storeOpenTarget,
42
40
  toModuleStoreEntry,
41
+ withStoreDetails,
43
42
  } from "../build/utils/module-store-catalog";
44
- import {
45
- type DependencyDraft,
46
- areDependencyDraftsValid,
47
- buildInstallBody,
48
- createDependencyDrafts,
49
- } from "../build/utils/module-store-install";
50
43
  import { useModuleStore } from "../composables/page/useModuleStore";
51
- import {
52
- ConfirmActionError,
53
- useConfirm,
54
- } from "#dms-ui/app/composables/confirm";
55
- import type {
56
- StoreActionBody,
57
- StoreOperationKind,
58
- } from "../types/module-store";
44
+ import { useModuleStoreActions } from "../composables/page/useModuleStoreActions";
59
45
  import DmsSearchInput from "#dms-ui/app/build/components/form/SearchInput.vue";
60
46
 
61
47
  const SKELETON_PLACEHOLDER_COUNT = 6;
62
48
 
63
- const { t } = useI18n();
49
+ const { t, locale } = useI18n();
64
50
  const { processI18n } = useTranslation();
65
51
  const { keyLabel } = useKeyboardPlatform();
66
52
  const isOwner = useIsOwner();
@@ -84,6 +70,15 @@ usePageHeaderActions(() => {
84
70
  t("modules.owner_badge"),
85
71
  ],
86
72
  ),
73
+ h(UButton, {
74
+ label: t("modules.browse_store"),
75
+ icon: "i-ph-storefront",
76
+ to: MODULE_STORE_PATH,
77
+ color: "primary",
78
+ variant: "soft",
79
+ ui: { label: "max-sm:sr-only" },
80
+ "aria-label": t("modules.browse_store"),
81
+ }),
87
82
  h(UButton, {
88
83
  label: t("modules.refresh"),
89
84
  icon: "i-ph-arrows-clockwise",
@@ -113,10 +108,14 @@ if (!isOwner.value) {
113
108
  });
114
109
  }
115
110
 
116
- // Read with the page, which the server renders and hands over: the
117
- // hydration reuses the listing the server rendered (read again, its live
118
- // readouts, an uptime a minute on, would no longer match the markup).
119
- await modulesListing.loadModulesListing();
111
+ // Declared before the installed modules, which take the version, category and
112
+ // source a registration leaves out from it, and the store actions.
113
+ const moduleStore = useModuleStore();
114
+
115
+ // Both listings are read with the page (`useApiData`): a server render waits
116
+ // for them and hands them to the browser, which hydrates on them without
117
+ // reading again. Nothing awaits them here: a client navigation shows the
118
+ // tile skeletons, sized like the tiles, until they answer.
120
119
 
121
120
  const isForbidden = computed(
122
121
  () =>
@@ -135,16 +134,32 @@ const isInitiallyLoading = computed(
135
134
  );
136
135
  const isRetrying = ref(false);
137
136
 
138
- const modules = computed<ModuleCatalogEntry[]>(() =>
139
- (modulesListing.modules.value ?? []).filter((entry) => entry.hasAccess),
137
+ const storeModules = computed(() =>
138
+ sortStoreModules(
139
+ (moduleStore.listing.value?.modules ?? []).map((entry) =>
140
+ toModuleStoreEntry(entry, modulesListing.modules.value ?? []),
141
+ ),
142
+ processI18n,
143
+ locale.value,
144
+ ),
145
+ );
146
+ const storeEntries = computed(
147
+ () => new Map(storeModules.value.map((entry) => [entry.id, entry])),
140
148
  );
141
- const summary = computed(() => summarizeModules(modules.value));
142
149
 
143
- // ── Navigation targets ─────────────────────────────────────────────
144
- function moduleTarget(entry: ModuleCatalogEntry): string {
145
- return moduleHistory.getLast(entry.id) ?? entry.landingSlug;
146
- }
150
+ // The modules that run, and the installed ones that register nothing (paused,
151
+ // not started, or without pages): antelope.config.ts declares them all.
152
+ const modules = computed<ModuleCatalogEntry[]>(() => {
153
+ const registered = withStoreDetails(
154
+ (modulesListing.modules.value ?? []).filter((entry) => entry.hasAccess),
155
+ storeModules.value,
156
+ );
157
+ return [...registered, ...storeOnlyModules(storeModules.value, registered)];
158
+ });
159
+ const summary = computed(() => summarizeModules(modules.value));
147
160
 
161
+ // The history only orders the grid by last use: a tile always opens the
162
+ // module on its landing page, never where it was left.
148
163
  const visitedAt = computed(
149
164
  () =>
150
165
  new Map(
@@ -155,16 +170,19 @@ const visitedAt = computed(
155
170
  ),
156
171
  );
157
172
 
158
- // The installed modules, their skeleton and the store share one grid and
159
- // one search toolbar.
160
- const TILE_GRID_CLASS =
161
- "grid grid-cols-[repeat(auto-fill,minmax(268px,1fr))] gap-4";
162
173
  const TOOLBAR_CLASS = "mb-[18px] flex flex-wrap items-center gap-2.5";
163
174
 
164
175
  // ── Search, category, sort ─────────────────────────────────────────
165
176
  const query = ref("");
166
177
  const category = ref<string>(MODULE_CATEGORY_ALL);
167
- const sortMode = ref<ModuleSortMode>("recent");
178
+ // Remembered per browser in the preferences cookie, so the server renders the
179
+ // grid in the order the browser hydrates it with.
180
+ const SORT_MODE_PREFERENCE_KEY = "modulesPage.sort";
181
+ const { getPreference, setPreference } = usePreferences();
182
+ const sortMode = computed<ModuleSortMode>({
183
+ get: () => parseModuleSortMode(getPreference(SORT_MODE_PREFERENCE_KEY)),
184
+ set: (mode) => setPreference(SORT_MODE_PREFERENCE_KEY, mode),
185
+ });
168
186
 
169
187
  const categoryOptions = computed(() => [
170
188
  {
@@ -183,8 +201,8 @@ watch(categoryOptions, (options) => {
183
201
  });
184
202
 
185
203
  const sortItems = computed(() => [
186
- { value: "recent", label: t("modules.sort.recent") },
187
204
  { value: "name", label: t("modules.sort.name") },
205
+ { value: "recent", label: t("modules.sort.recent") },
188
206
  ]);
189
207
 
190
208
  const visibleModules = computed(() =>
@@ -197,6 +215,7 @@ const visibleModules = computed(() =>
197
215
  sortMode.value,
198
216
  visitedAt.value,
199
217
  processI18n,
218
+ locale.value,
200
219
  ),
201
220
  );
202
221
 
@@ -221,217 +240,22 @@ function clearFilters(): void {
221
240
  }
222
241
 
223
242
  // ── Module store ───────────────────────────────────────────────────
224
- const moduleStore = useModuleStore();
225
- const toast = useToast();
226
- const { confirm } = useConfirm();
227
-
228
243
  onMounted(() => {
229
- if (isOwner.value) void moduleStore.load();
244
+ if (isOwner.value) moduleStore.start();
230
245
  });
231
246
  onBeforeUnmount(() => moduleStore.stop());
232
247
 
233
248
  const storeListing = computed(() => moduleStore.listing.value);
234
- const isStoreLoading = computed(
235
- () => storeListing.value === undefined && !moduleStore.loadingError.value,
236
- );
237
- const storeModules = computed(() =>
238
- sortStoreModules(
239
- (storeListing.value?.modules ?? []).map((entry) =>
240
- toModuleStoreEntry(entry, modulesListing.modules.value ?? []),
241
- ),
242
- processI18n,
243
- ),
244
- );
245
- const storeScope = ref<StoreScope>("all");
246
- const storeQuery = ref("");
247
- const storeCategory = ref<string>(MODULE_CATEGORY_ALL);
248
-
249
- const storeScopeItems = computed(() =>
250
- (["all", "available", "installed"] as const).map((scope) => ({
251
- value: scope,
252
- label: t(`modules.store.scope.${scope}`, {
253
- count: filterByScope(storeModules.value, scope).length,
254
- }),
255
- })),
256
- );
257
- const scopedStoreModules = computed(() =>
258
- filterByScope(storeModules.value, storeScope.value),
259
- );
260
-
261
- const storeCategoryOptions = computed(() => [
262
- {
263
- value: MODULE_CATEGORY_ALL,
264
- label: t("modules.category.all"),
265
- count: scopedStoreModules.value.length,
266
- },
267
- ...moduleCategoryOptions(scopedStoreModules.value, processI18n),
268
- ]);
269
-
270
- watch(storeCategoryOptions, (options) => {
271
- if (!options.some((option) => option.value === storeCategory.value)) {
272
- storeCategory.value = MODULE_CATEGORY_ALL;
273
- }
274
- });
275
-
276
- const visibleStoreModules = computed(() =>
277
- filterModules(scopedStoreModules.value, {
278
- query: storeQuery.value,
279
- category: storeCategory.value,
280
- resolve: processI18n,
281
- }),
282
- );
283
- const trimmedStoreQuery = computed(() => storeQuery.value.trim());
284
-
285
- function clearStoreFilters(): void {
286
- storeQuery.value = "";
287
- storeCategory.value = MODULE_CATEGORY_ALL;
288
- }
289
-
290
- /** Store modules whose interface the running frontend does not match yet. */
291
- const frontendLagging = computed(() =>
292
- storeModules.value.filter(
293
- (entry) =>
294
- isAwaitingFrontend(entry) ||
295
- (entry.frontend === "unknown" &&
296
- moduleStore.awaitingReload.value.has(entry.id)),
297
- ),
249
+ const { runStoreAction, frontendNotice } = useModuleStoreActions(
250
+ moduleStore,
251
+ storeModules,
298
252
  );
299
- const frontendNotice = computed(() => {
300
- const lagging = frontendLagging.value;
301
- if (lagging.length === 0) return null;
302
- const names = lagging.map((entry) => processI18n(entry.title)).join(", ");
303
- const hasReported = lagging.every((entry) => entry.frontend !== "unknown");
304
- const key = !hasReported
305
- ? "unknown"
306
- : storeListing.value?.dev
307
- ? "dev"
308
- : "production";
309
- return { key, names };
310
- });
311
-
312
- /**
313
- * Runs `kind` from a dialog: a refusal is worded as the toast words it and
314
- * shown in the dialog, which stays open.
315
- */
316
- async function runFromDialog(
317
- kind: StoreOperationKind,
318
- entry: ModuleStoreEntry,
319
- body?: StoreActionBody,
320
- ): Promise<void> {
321
- try {
322
- await moduleStore.run(kind, entry, body);
323
- } catch (error) {
324
- throw new ConfirmActionError({
325
- title: t("modules.store.action_failed", {
326
- module: processI18n(entry.title),
327
- }),
328
- description: resolveStoreError(error),
329
- });
330
- }
331
- }
332
-
333
- /** What the owner last picked in a module's install dialog, for a retry. */
334
- const lastDependencyDrafts = new Map<string, DependencyDraft[]>();
335
-
336
- function renderDependencyPicker(
337
- entry: ModuleStoreEntry,
338
- drafts: Ref<DependencyDraft[]>,
339
- showErrors: boolean,
340
- ): VNode {
341
- return h(ModuleStoreDependencyPicker, {
342
- missing: entry.missingInterfaces,
343
- drafts: drafts.value,
344
- showErrors,
345
- onUpdate: (index: number, patch: Partial<DependencyDraft>) => {
346
- drafts.value = drafts.value.map((draft, position) =>
347
- position === index ? { ...draft, ...patch } : draft,
348
- );
349
- },
350
- });
351
- }
352
-
353
- /**
354
- * Asks which modules implement the interfaces the release misses, then
355
- * installs them with it. A refusal shows in the dialog, kept open.
356
- */
357
- async function installWithDependencies(entry: ModuleStoreEntry): Promise<void> {
358
- const title = processI18n(entry.title);
359
- const drafts = ref(
360
- createDependencyDrafts(
361
- entry.missingInterfaces,
362
- lastDependencyDrafts.get(entry.id),
363
- ),
364
- );
365
- const showErrors = ref(false);
366
- await confirm({
367
- title: t("modules.store.install_dialog.title", { module: title }),
368
- description: t("modules.store.install_dialog.description", {
369
- module: title,
370
- }),
371
- confirmLabel: t("modules.store.actions.install"),
372
- confirmIcon: "i-ph-download-simple",
373
- icon: "i-ph-puzzle-piece",
374
- body: () => renderDependencyPicker(entry, drafts, showErrors.value),
375
- validate: () => {
376
- showErrors.value = true;
377
- return areDependencyDraftsValid(drafts.value);
378
- },
379
- onConfirm: async () => {
380
- lastDependencyDrafts.set(entry.id, drafts.value);
381
- await runFromDialog(
382
- "install",
383
- entry,
384
- buildInstallBody(entry.id, drafts.value),
385
- );
386
- },
387
- });
388
- }
389
-
390
- async function runStoreAction(
391
- kind: StoreOperationKind,
392
- entry: ModuleStoreEntry,
393
- ): Promise<void> {
394
- const title = processI18n(entry.title);
395
- if (needsDependencyChoice(entry, kind)) {
396
- await installWithDependencies(entry);
397
- return;
398
- }
399
- if (kind === "uninstall") {
400
- await confirm({
401
- title: t("modules.store.uninstall_confirm.title", { module: title }),
402
- description: t("modules.store.uninstall_confirm.description", {
403
- module: title,
404
- }),
405
- confirmLabel: t("modules.store.actions.uninstall"),
406
- color: "error",
407
- icon: "i-ph-trash",
408
- onConfirm: () => runFromDialog(kind, entry),
409
- });
410
- return;
411
- }
412
- try {
413
- await moduleStore.run(kind, entry);
414
- } catch (error) {
415
- toast.add({
416
- color: "error",
417
- icon: "i-ph-warning-circle",
418
- title: t("modules.store.action_failed", { module: title }),
419
- description: resolveStoreError(error),
420
- });
421
- }
422
- }
423
-
424
- /** What a refused store request carries: the server's error key, if any. */
425
- interface StoreRequestError {
426
- data?: unknown;
427
- message?: string;
428
- }
429
253
 
430
- function resolveStoreError(error: unknown): string {
431
- const details = error as StoreRequestError | undefined;
432
- const key = typeof details?.data === "string" ? details.data : undefined;
433
- if (!key) return details?.message ?? "";
434
- return t(`modules.store.errors.${key.replace("error.", "")}`);
254
+ /** Whether a tile's store actions wait: read-only, or another one runs. */
255
+ function isTileLocked(moduleId: string): boolean {
256
+ const listing = storeListing.value;
257
+ if (!listing?.enabled) return true;
258
+ return listing.busy && storeEntries.value.get(moduleId)?.state !== "busy";
435
259
  }
436
260
 
437
261
  // ── Actions ────────────────────────────────────────────────────────
@@ -544,6 +368,11 @@ const summaryCards = computed<StatGroupItem[]>(() =>
544
368
  :loading="isInitiallyLoading"
545
369
  />
546
370
 
371
+ <ModuleStoreBanners
372
+ :listing="storeListing"
373
+ :frontend-notice="frontendNotice"
374
+ />
375
+
547
376
  <!-- Installed -->
548
377
  <section>
549
378
  <DmsSectionHeader
@@ -582,38 +411,13 @@ const summaryCards = computed<StatGroupItem[]>(() =>
582
411
  <!-- Loading -->
583
412
  <div
584
413
  v-if="isInitiallyLoading"
585
- :class="TILE_GRID_CLASS"
414
+ :class="MODULE_TILE_GRID_CLASS"
586
415
  aria-busy="true"
587
416
  >
588
- <DmsCard
417
+ <ModuleTileSkeleton
589
418
  v-for="placeholder in skeletonItems"
590
419
  :key="placeholder"
591
- :padded="false"
592
- class="flex min-h-[228px] flex-col gap-3 p-4"
593
- >
594
- <div class="flex items-start justify-between">
595
- <USkeleton
596
- :aria-label="t('dms.a11y.loading')"
597
- class="size-9 rounded-[10px]"
598
- />
599
- <USkeleton
600
- :aria-label="t('dms.a11y.loading')"
601
- class="h-5 w-12 rounded-full"
602
- />
603
- </div>
604
- <USkeleton
605
- :aria-label="t('dms.a11y.loading')"
606
- class="h-2.5 w-4/5"
607
- />
608
- <USkeleton
609
- :aria-label="t('dms.a11y.loading')"
610
- class="h-2.5 w-3/5"
611
- />
612
- <USkeleton
613
- :aria-label="t('dms.a11y.loading')"
614
- class="mt-auto h-4 w-2/5"
615
- />
616
- </DmsCard>
420
+ />
617
421
  </div>
618
422
 
619
423
  <!-- Nothing installed -->
@@ -691,214 +495,23 @@ const summaryCards = computed<StatGroupItem[]>(() =>
691
495
  </DmsEmptyState>
692
496
  </DmsCard>
693
497
 
694
- <div v-else :class="TILE_GRID_CLASS">
498
+ <div v-else :class="MODULE_TILE_GRID_CLASS">
695
499
  <ModuleCatalogTile
696
500
  v-for="entry in visibleModules"
697
501
  :key="entry.id"
698
502
  :entry="entry"
699
- :to="moduleTarget(entry)"
503
+ :to="storeOpenTarget(entry, modules)"
504
+ :store-entry="storeEntries.get(entry.id)"
505
+ :is-locked="isTileLocked(entry.id)"
506
+ @action="
507
+ (kind) => {
508
+ const storeEntry = storeEntries.get(entry.id);
509
+ if (storeEntry) void runStoreAction(kind, storeEntry);
510
+ }
511
+ "
700
512
  />
701
513
  </div>
702
514
  </section>
703
-
704
- <!-- Module store: the official modules, installed or not. -->
705
- <section class="mt-10" aria-labelledby="module-store-title">
706
- <DmsSectionHeader
707
- class="mb-3"
708
- :count="isStoreLoading ? undefined : storeModules.length"
709
- :description="t('modules.store.description')"
710
- >
711
- <template #title>
712
- <span id="module-store-title">{{ t("modules.store.title") }}</span>
713
- </template>
714
- <template #trailing>
715
- <span
716
- v-if="storeListing?.frontendVersion"
717
- :class="[
718
- MONO_CHIP_CLASS,
719
- 'text-dimmed border border-(--ui-border) text-[11px] max-sm:hidden',
720
- ]"
721
- :title="
722
- storeListing.frontendVersionSource
723
- ? t(
724
- `modules.store.frontend_version.${storeListing.frontendVersionSource}`,
725
- )
726
- : undefined
727
- "
728
- >
729
- {{
730
- t("modules.store.frontend_version.label", {
731
- version: storeListing.frontendVersion,
732
- })
733
- }}
734
- </span>
735
- <DmsSegmented
736
- v-model="storeScope"
737
- :items="storeScopeItems"
738
- size="sm"
739
- :aria-label="t('modules.store.scope.label')"
740
- />
741
- </template>
742
- </DmsSectionHeader>
743
-
744
- <DmsBanner
745
- v-if="storeListing?.configError"
746
- class="mb-4"
747
- tone="error"
748
- size="sm"
749
- icon="i-ph-file-x"
750
- :title="t('modules.store.config_error')"
751
- :description="storeListing.configError"
752
- />
753
- <DmsBanner
754
- v-else-if="storeListing && !storeListing.enabled"
755
- class="mb-4"
756
- tone="info"
757
- size="sm"
758
- icon="i-ph-lock-simple"
759
- :title="t('modules.store.disabled.title')"
760
- :description="t('modules.store.disabled.description')"
761
- />
762
-
763
- <!-- The interface lags behind the backend: say what follows. -->
764
- <DmsBanner
765
- v-if="frontendNotice"
766
- class="mb-4"
767
- :tone="frontendNotice.key === 'dev' ? 'info' : 'warning'"
768
- size="sm"
769
- :icon="
770
- frontendNotice.key === 'dev'
771
- ? 'i-ph-circle-notch'
772
- : 'i-ph-arrows-clockwise'
773
- "
774
- :title="
775
- t(`modules.store.frontend.${frontendNotice.key}.title`, {
776
- modules: frontendNotice.names,
777
- })
778
- "
779
- >
780
- <template #description>
781
- <i18n-t
782
- :keypath="`modules.store.frontend.${frontendNotice.key}.description`"
783
- scope="global"
784
- tag="span"
785
- >
786
- <template #command>
787
- <code
788
- :class="[
789
- MONO_CHIP_CLASS,
790
- 'bg-elevated text-highlighted text-xs',
791
- ]"
792
- >
793
- {{ FRONTEND_BUILD_COMMAND }}
794
- </code>
795
- </template>
796
- </i18n-t>
797
- </template>
798
- </DmsBanner>
799
-
800
- <div v-if="isStoreLoading" :class="TILE_GRID_CLASS" aria-busy="true">
801
- <DmsCard
802
- v-for="placeholder in skeletonItems"
803
- :key="placeholder"
804
- :padded="false"
805
- class="flex min-h-[228px] flex-col gap-3 p-4"
806
- >
807
- <USkeleton
808
- :aria-label="t('dms.a11y.loading')"
809
- class="size-9 rounded-[10px]"
810
- />
811
- <USkeleton
812
- :aria-label="t('dms.a11y.loading')"
813
- class="mt-auto h-2.5 w-3/5"
814
- />
815
- </DmsCard>
816
- </div>
817
-
818
- <!-- Only when nothing is on screen yet: a follow-up read that fails
819
- while the frontend restarts keeps the tiles and tries again. -->
820
- <DmsBanner
821
- v-else-if="moduleStore.loadingError.value && !storeListing"
822
- tone="error"
823
- size="sm"
824
- icon="i-ph-plugs"
825
- :title="t('modules.store.load_error')"
826
- >
827
- <template #actions>
828
- <UButton
829
- color="error"
830
- size="sm"
831
- icon="i-ph-arrows-clockwise"
832
- :label="t('modules.error.retry')"
833
- @click="moduleStore.load()"
834
- />
835
- </template>
836
- </DmsBanner>
837
-
838
- <template v-else>
839
- <div :class="TOOLBAR_CLASS">
840
- <DmsSearchInput
841
- v-model="storeQuery"
842
- size="sm"
843
- :placeholder="t('modules.store.search_placeholder')"
844
- class="w-[300px] max-md:w-full"
845
- />
846
- <DmsChipGroup
847
- :items="storeCategoryOptions"
848
- :selected="[storeCategory]"
849
- :label="t('modules.store.category_label')"
850
- @pick="storeCategory = $event"
851
- />
852
- </div>
853
-
854
- <DmsCard
855
- v-if="scopedStoreModules.length === 0 && storeScope === 'available'"
856
- :padded="false"
857
- >
858
- <DmsEmptyState
859
- icon="i-ph-check-circle"
860
- :title="t('modules.store.all_installed.title')"
861
- :description="t('modules.store.all_installed.description')"
862
- />
863
- </DmsCard>
864
-
865
- <DmsCard v-else-if="visibleStoreModules.length === 0" :padded="false">
866
- <DmsEmptyState
867
- variant="no-result"
868
- :title="
869
- trimmedStoreQuery
870
- ? t('modules.store.no_result.title', {
871
- query: trimmedStoreQuery,
872
- })
873
- : t('modules.store.no_result.title_filtered')
874
- "
875
- >
876
- <template #actions>
877
- <UButton
878
- color="neutral"
879
- variant="outline"
880
- size="sm"
881
- :label="t('modules.no_result.clear')"
882
- @click="clearStoreFilters"
883
- />
884
- </template>
885
- </DmsEmptyState>
886
- </DmsCard>
887
-
888
- <div v-else :class="TILE_GRID_CLASS">
889
- <ModuleStoreTile
890
- v-for="entry in visibleStoreModules"
891
- :key="entry.id"
892
- :entry="entry"
893
- :is-locked="
894
- !storeListing?.enabled ||
895
- (storeListing?.busy === true && entry.state !== 'busy')
896
- "
897
- @action="runStoreAction($event, entry)"
898
- />
899
- </div>
900
- </template>
901
- </section>
902
515
  </template>
903
516
  </div>
904
517
  </template>
@@ -88,6 +88,9 @@ export default defineDmsMiddleware(async (to, from) => {
88
88
 
89
89
  const moduleEntry = siteLayout.modules.value?.[moduleId];
90
90
 
91
+ // A page of the Modules page itself (the module store), not of a module.
92
+ if (!moduleEntry && siteLayout.findMatchingRoute(to.path)?.metadata) return;
93
+
91
94
  if (!moduleId || !moduleEntry) {
92
95
  throw createDmsError({
93
96
  statusCode: MODULE_NOT_FOUND_STATUS,