@antelopejs/dms 0.9.8 → 0.11.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 (451) hide show
  1. package/CHANGELOG.md +171 -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 -2
  21. package/dist/config.js +5 -7
  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 +38 -94
  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 +7 -2
  41. package/dist/implementations/dms/manifest.js +12 -7
  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 +32 -55
  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/index.d.ts +0 -1
  117. package/dist/routes/index.js +0 -1
  118. package/dist/routes/index.js.map +1 -1
  119. package/dist/routes/realtime.d.ts +13 -1
  120. package/dist/routes/realtime.js +114 -29
  121. package/dist/routes/realtime.js.map +1 -1
  122. package/dist/startup-config.d.ts +2 -0
  123. package/dist/startup-config.js +62 -0
  124. package/dist/startup-config.js.map +1 -0
  125. package/dist/store/interfaces.d.ts +3 -2
  126. package/dist/store/interfaces.js +16 -6
  127. package/dist/store/interfaces.js.map +1 -1
  128. package/dist/store/listing.js +25 -15
  129. package/dist/store/listing.js.map +1 -1
  130. package/dist/store/operations.js +5 -2
  131. package/dist/store/operations.js.map +1 -1
  132. package/dist/store/requirements.js +3 -2
  133. package/dist/store/requirements.js.map +1 -1
  134. package/dist/store/runtime.d.ts +10 -1
  135. package/dist/store/runtime.js +33 -6
  136. package/dist/store/runtime.js.map +1 -1
  137. package/dist/store/types.d.ts +10 -0
  138. package/dist/utils/ensure-default-tenant.d.ts +2 -1
  139. package/dist/utils/ensure-default-tenant.js +13 -1
  140. package/dist/utils/ensure-default-tenant.js.map +1 -1
  141. package/dist/utils/export-jobs.js +2 -1
  142. package/dist/utils/export-jobs.js.map +1 -1
  143. package/dist/validation/auth.schema.d.ts +2 -2
  144. package/dist/validation/member-invite.schema.d.ts +2 -10
  145. package/dist/validation/member-invite.schema.js +2 -15
  146. package/dist/validation/member-invite.schema.js.map +1 -1
  147. package/frontend-vue/dms.frontend.ts +24 -5
  148. package/frontend-vue/dms.server.ts +17 -0
  149. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  150. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +33 -0
  151. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  152. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  153. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  154. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  155. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  156. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  157. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  158. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  159. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  160. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  161. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  162. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +25 -5
  163. package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -5
  164. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +61 -26
  165. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  166. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  167. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  168. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +134 -35
  169. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  170. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +136 -2
  171. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +136 -2
  172. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +0 -2
  173. package/frontend-vue/layers/dms-layout/app/app.config.ts +0 -1
  174. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +11 -1
  175. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Italic-Variable.woff2 +0 -0
  176. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Variable.woff2 +0 -0
  177. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Italic-Variable.woff2 +0 -0
  178. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Variable.woff2 +0 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  180. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  181. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +6 -2
  182. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  183. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -107
  184. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  185. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +4 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  187. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  188. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  189. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  190. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  191. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  192. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  193. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  194. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  195. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  196. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  197. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  198. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  199. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  200. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  201. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  202. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  203. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  204. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  205. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  206. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +2 -2
  207. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  208. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  209. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  210. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +49 -11
  211. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  212. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +59 -4
  213. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  214. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  215. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  216. package/frontend-vue/layers/dms-layout/app/composables/page/usePageRecordDeletion.ts +162 -0
  217. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  218. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +6 -2
  219. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +6 -2
  220. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  221. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  222. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  223. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +13 -22
  224. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +11 -10
  225. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +22 -52
  226. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-quick-actions.client.ts +7 -7
  227. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +11 -10
  228. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  229. package/frontend-vue/layers/dms-layout/app/theme/actions.ts +5 -4
  230. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  231. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  232. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  233. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  234. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  235. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  236. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  237. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +45 -18
  238. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +44 -17
  239. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  240. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  241. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  242. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  243. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  244. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  245. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  246. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  247. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  248. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  249. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  250. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +5 -1
  251. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  252. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  253. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  254. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  255. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +48 -1
  256. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  257. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  258. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  259. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  260. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  261. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  262. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  263. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  264. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  265. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  266. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  267. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  268. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  269. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  270. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +6 -1
  271. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  273. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  274. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  277. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +44 -0
  278. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +123 -27
  279. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  280. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  281. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +92 -0
  282. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  283. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  284. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  285. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  286. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  290. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  291. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +11 -5
  292. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/form/formValidation.ts +36 -4
  294. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +78 -0
  297. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  300. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  301. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  302. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  304. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  305. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  306. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  307. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  308. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  309. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  310. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  311. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +97 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  321. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  322. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  323. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  324. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  325. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  326. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  327. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +90 -35
  328. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  329. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  330. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  331. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  332. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  333. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  334. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  335. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  336. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  337. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  338. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  339. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  340. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  341. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  342. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  343. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/writeError.ts +21 -0
  344. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  345. package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +67 -29
  346. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  347. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  348. package/frontend-vue/layers/dms-ui/app/build/utils/htmlGrammar.ts +79 -0
  349. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  350. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  351. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  352. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  353. package/frontend-vue/layers/dms-ui/app/build/utils/shellParser.ts +1 -1
  354. package/frontend-vue/layers/dms-ui/app/build/utils/streamHighlight.ts +90 -0
  355. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  356. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  357. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  358. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  359. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  360. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  361. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  362. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  363. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  364. package/frontend-vue/layers/dms-ui/app/components/code-snippet/CodeSnippet.vue +23 -12
  365. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  366. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  367. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  368. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  369. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +119 -36
  370. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +53 -22
  371. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  372. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  373. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  374. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  375. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  376. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  377. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  378. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  379. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  380. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  381. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +226 -65
  382. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  383. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +210 -0
  384. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  385. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  386. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  387. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  388. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  389. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  390. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  391. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  392. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  393. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  394. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  395. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +74 -25
  396. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +48 -44
  397. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  398. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +426 -97
  399. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  400. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  401. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  402. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  403. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  404. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  405. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  406. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  407. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  408. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  409. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  410. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +16 -0
  411. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  412. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +189 -28
  413. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +29 -12
  414. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  415. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  416. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  417. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  418. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  419. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  420. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  421. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  422. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  423. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  424. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  425. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  426. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  427. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  428. package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +94 -3
  429. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  430. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +20 -5
  431. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +20 -5
  432. package/frontend-vue/package.json +6 -2
  433. package/package.json +11 -10
  434. package/skills/dms/SKILL.md +1 -1
  435. package/skills/dms-auth/SKILL.md +5 -3
  436. package/skills/dms-best-practices/REFERENCE.md +2 -0
  437. package/skills/dms-best-practices/SKILL.md +4 -0
  438. package/skills/dms-module/SKILL.md +3 -2
  439. package/skills/dms-pages/REFERENCE.md +129 -16
  440. package/skills/dms-pages/SKILL.md +1 -1
  441. package/skills/dms-project/DEV-SERVERS.md +23 -1
  442. package/skills/dms-project/REFERENCE.md +1 -1
  443. package/skills/dms-project/SKILL.md +4 -1
  444. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  445. package/dist/hooks/tenant-instances.d.ts +0 -1
  446. package/dist/hooks/tenant-instances.js +0 -13
  447. package/dist/hooks/tenant-instances.js.map +0 -1
  448. package/dist/routes/startup-gate.d.ts +0 -7
  449. package/dist/routes/startup-gate.js +0 -39
  450. package/dist/routes/startup-gate.js.map +0 -1
  451. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -1,12 +1,17 @@
1
1
  import type { Tone } from "#dms-ui/app/types/tone";
2
2
  import type { StatusPillDot } from "#dms-ui/app/components/status-pill/StatusPill.vue";
3
- import type { ModuleCatalogEntry } from "../../types/page";
3
+ import { type ModuleCatalogEntry, MODULE_URL_PREFIX } from "../../types/page";
4
4
  import type {
5
+ ModuleSourceKind,
5
6
  StoreListingEntry,
6
7
  StoreModuleState,
7
8
  StoreOperationKind,
8
9
  } from "../../types/module-store";
9
- import type { DisplayResolver } from "./modules-catalog";
10
+ import {
11
+ type DisplayResolver,
12
+ compareDisplayNames,
13
+ moduleStatus,
14
+ } from "./modules-catalog";
10
15
 
11
16
  /** A store module as the Modules page filters and draws it. */
12
17
  export type ModuleStoreEntry = StoreListingEntry & {
@@ -17,13 +22,19 @@ export type ModuleStoreEntry = StoreListingEntry & {
17
22
  /** Which store modules the store section shows. */
18
23
  export type StoreScope = "all" | "available" | "installed";
19
24
 
20
- /** What a store tile's pill says. */
21
- export interface StoreTileStatus {
22
- tone: Tone;
23
- /** i18n key under `modules.store.state`. */
24
- labelKey: string;
25
- dot?: StatusPillDot;
26
- }
25
+ /**
26
+ * The scopes in the order the store's switch shows them. The store is for
27
+ * discovering, so it opens on what can still be installed; "all" comes last,
28
+ * the widest view.
29
+ */
30
+ export const STORE_SCOPES: readonly StoreScope[] = [
31
+ "available",
32
+ "installed",
33
+ "all",
34
+ ];
35
+
36
+ /** The scope the store opens on. */
37
+ export const DEFAULT_STORE_SCOPE: StoreScope = "available";
27
38
 
28
39
  /** One action a store tile offers. */
29
40
  export interface StoreAction {
@@ -42,18 +53,18 @@ export interface StoreAction {
42
53
 
43
54
  const CATEGORY_PREFIX = "$modules.store.category";
44
55
 
56
+ /** The icon a tile's footer names a module's source with. */
57
+ export const MODULE_SOURCE_ICONS: Record<ModuleSourceKind, string> = {
58
+ npm: "i-ph-package",
59
+ github: "i-ph-github-logo",
60
+ gitlab: "i-ph-gitlab-logo",
61
+ git: "i-ph-git-branch",
62
+ local: "i-ph-folder-simple",
63
+ };
64
+
45
65
  /** The command that turns a production frontend into one serving new layers. */
46
66
  export const FRONTEND_BUILD_COMMAND = "ajs dms build";
47
67
 
48
- const STATUSES: Record<StoreModuleState, StoreTileStatus> = {
49
- available: { tone: "neutral", labelKey: "available", dot: "none" },
50
- busy: { tone: "info", labelKey: "busy", dot: "live" },
51
- active: { tone: "success", labelKey: "active" },
52
- disabled: { tone: "neutral", labelKey: "disabled" },
53
- error: { tone: "error", labelKey: "error" },
54
- pending: { tone: "warning", labelKey: "pending" },
55
- };
56
-
57
68
  const ACTIONS: Record<StoreOperationKind, StoreAction> = {
58
69
  install: {
59
70
  kind: "install",
@@ -85,21 +96,85 @@ const INSTALLED_STATES = new Set<StoreModuleState>([
85
96
  "pending",
86
97
  ]);
87
98
 
99
+ /**
100
+ * The category a module is filed under, the same on its installed tile and
101
+ * its store tile: the one its registration names, else the store catalog's.
102
+ */
103
+ export function resolveCatalogCategory(
104
+ registered: Pick<ModuleCatalogEntry, "catalogCategory"> | undefined,
105
+ entry: Pick<StoreListingEntry, "category">,
106
+ ): string {
107
+ return (
108
+ registered?.catalogCategory?.trim() ||
109
+ `${CATEGORY_PREFIX}.${entry.category}`
110
+ );
111
+ }
112
+
88
113
  export function toModuleStoreEntry(
89
114
  entry: StoreListingEntry,
90
- registeredModules: readonly Pick<ModuleCatalogEntry, "id" | "title">[] = [],
115
+ registeredModules: readonly Pick<
116
+ ModuleCatalogEntry,
117
+ "id" | "title" | "catalogCategory"
118
+ >[] = [],
91
119
  ): ModuleStoreEntry {
120
+ const registered = registeredModules.find((module) => module.id === entry.id);
92
121
  return {
93
122
  ...entry,
94
123
  // The registration also names Installed and navigation. Keep the catalog
95
124
  // fallback for unloaded modules, whose translations may not be served.
96
- title:
97
- registeredModules.find((module) => module.id === entry.id)?.title ??
98
- entry.title,
99
- catalogCategory: `${CATEGORY_PREFIX}.${entry.category}`,
125
+ title: registered?.title ?? entry.title,
126
+ catalogCategory: resolveCatalogCategory(registered, entry),
100
127
  };
101
128
  }
102
129
 
130
+ /** A version as written, not a range: what an installed tile prints. */
131
+ const PLAIN_VERSION = /^\d+\.\d+\.\d+/;
132
+
133
+ /**
134
+ * Installed modules with the version, category and source their registration
135
+ * leaves out, read from the store listing of the same id. A module that registers
136
+ * neither still reads like the others on its tile and in the filters.
137
+ */
138
+ export function withStoreDetails<T extends ModuleCatalogEntry>(
139
+ modules: readonly T[],
140
+ store: readonly StoreListingEntry[],
141
+ ): T[] {
142
+ const byId = new Map(store.map((entry) => [entry.id, entry]));
143
+ return modules.map((module) => {
144
+ const entry = byId.get(module.id);
145
+ if (!entry) return module;
146
+ const installed = entry.installedVersion;
147
+ return {
148
+ ...module,
149
+ version:
150
+ module.version ??
151
+ (installed && PLAIN_VERSION.test(installed) ? installed : undefined),
152
+ catalogCategory: resolveCatalogCategory(module, entry),
153
+ source: entry.source,
154
+ };
155
+ });
156
+ }
157
+
158
+ /**
159
+ * Where a store tile's Open button leads: the landing page of the installed
160
+ * module of the same id, when the caller may open it and it has pages (a
161
+ * module without one lands on its own address, which has nothing to show).
162
+ */
163
+ export function storeOpenTarget(
164
+ entry: Pick<StoreListingEntry, "id">,
165
+ installedModules: readonly Pick<
166
+ ModuleCatalogEntry,
167
+ "id" | "hasAccess" | "landingSlug"
168
+ >[],
169
+ ): string | undefined {
170
+ const module = installedModules.find(
171
+ (candidate) => candidate.id === entry.id,
172
+ );
173
+ if (!module?.hasAccess || !module.landingSlug) return undefined;
174
+ const ownAddress = `${MODULE_URL_PREFIX}/${entry.id}`;
175
+ return module.landingSlug === ownAddress ? undefined : module.landingSlug;
176
+ }
177
+
103
178
  /** Whether the module is installed, from the store or by the config. */
104
179
  export function isInstalled(entry: StoreListingEntry): boolean {
105
180
  if (INSTALLED_STATES.has(entry.state)) return true;
@@ -115,29 +190,26 @@ export function filterByScope<T extends StoreListingEntry>(
115
190
  return entries.filter((entry) => isInstalled(entry) === wantsInstalled);
116
191
  }
117
192
 
118
- /** The store modules, alphabetical by their shown name. */
193
+ /** The store modules, alphabetical by their shown name in `locale`'s order. */
119
194
  export function sortStoreModules<T extends StoreListingEntry>(
120
195
  entries: readonly T[],
121
196
  resolve: DisplayResolver,
197
+ locale?: string,
122
198
  ): T[] {
199
+ const compare = compareDisplayNames(locale);
123
200
  return [...entries].sort((a, b) =>
124
- resolve(a.title).localeCompare(resolve(b.title)),
201
+ compare(resolve(a.title), resolve(b.title)),
125
202
  );
126
203
  }
127
204
 
128
- export function storeTileStatus(entry: StoreListingEntry): StoreTileStatus {
129
- if (entry.state === "active" && entry.updateAvailable) {
130
- return { tone: "warning", labelKey: "update_available" };
131
- }
132
- return STATUSES[entry.state];
133
- }
134
-
135
205
  /**
136
206
  * What the tile offers, the main action first. A module in error offers to
137
207
  * try its last step again: installing it when the file does not hold it,
138
208
  * applying the file otherwise.
139
209
  */
140
210
  export function storeActions(entry: StoreListingEntry): StoreAction[] {
211
+ // The developer's working copy is theirs to change in antelope.config.ts.
212
+ if (entry.isWorkingCopy) return [];
141
213
  const installed = entry.installedVersion !== undefined;
142
214
  const byState: Record<StoreModuleState, StoreOperationKind[]> = {
143
215
  available: ["install"],
@@ -166,6 +238,7 @@ export type StoreActionBlock =
166
238
  | "incompatible"
167
239
  | "frontend"
168
240
  | "interfaces"
241
+ | "used_by_project"
169
242
  | "dependents";
170
243
 
171
244
  /** Actions that load a new release of the module. */
@@ -197,10 +270,55 @@ export function storeActionBlock(
197
270
  kind: StoreOperationKind,
198
271
  ): StoreActionBlock | undefined {
199
272
  if (RELEASE_ACTIONS.has(kind)) return releaseBlock(entry, kind);
200
- if (TAKE_DOWN_ACTIONS.has(kind) && entry.dependents.length > 0) {
201
- return "dependents";
202
- }
203
- return undefined;
273
+ if (!TAKE_DOWN_ACTIONS.has(kind)) return undefined;
274
+ // The project cannot be removed first: only a change to its code frees it.
275
+ if (entry.usedByProject) return "used_by_project";
276
+ return entry.dependents.length > 0 ? "dependents" : undefined;
277
+ }
278
+
279
+ /** Translates an i18n key with its named parameters. */
280
+ export type StoreTranslate = (
281
+ key: string,
282
+ params?: Record<string, string>,
283
+ ) => string;
284
+
285
+ const LIST_SEPARATOR = ", ";
286
+
287
+ const BLOCK_MESSAGES: Record<
288
+ StoreActionBlock,
289
+ (entry: StoreListingEntry, t: StoreTranslate) => string
290
+ > = {
291
+ no_release: (_entry, t) => t("modules.store.no_release"),
292
+ incompatible: (entry, t) =>
293
+ t("modules.store.incompatible", {
294
+ range: entry.requiredInterfaceRange ?? "",
295
+ }),
296
+ frontend: (entry, t) =>
297
+ t("modules.store.frontend_requirement.needs", {
298
+ range: entry.frontendRequirement?.range ?? "",
299
+ running: entry.frontendRequirement?.running ?? "",
300
+ }),
301
+ interfaces: (entry, t) =>
302
+ t("modules.store.requires_first", {
303
+ interfaces: entry.missingInterfaces
304
+ .map((missing) => missing.interfacePackage)
305
+ .join(LIST_SEPARATOR),
306
+ }),
307
+ used_by_project: (_entry, t) => t("modules.store.used_by_project"),
308
+ dependents: (entry, t) =>
309
+ t("modules.store.used_by", {
310
+ modules: entry.dependents.join(LIST_SEPARATOR),
311
+ }),
312
+ };
313
+
314
+ /** Why `kind` cannot run on the module, worded for its tile, if it cannot. */
315
+ export function storeBlockMessage(
316
+ entry: StoreListingEntry,
317
+ kind: StoreOperationKind,
318
+ t: StoreTranslate,
319
+ ): string | undefined {
320
+ const block = storeActionBlock(entry, kind);
321
+ return block ? BLOCK_MESSAGES[block](entry, t) : undefined;
204
322
  }
205
323
 
206
324
  /** Whether `kind` first asks which modules implement the missing interfaces. */
@@ -210,3 +328,81 @@ export function needsDependencyChoice(
210
328
  ): boolean {
211
329
  return PICKS_DEPENDENCIES.has(kind) && entry.missingInterfaces.length > 0;
212
330
  }
331
+
332
+ /** What an installed module's pill says. */
333
+ export interface InstalledTileStatus {
334
+ tone: Tone;
335
+ /** Full i18n key of the label. */
336
+ labelKey: string;
337
+ dot?: StatusPillDot;
338
+ }
339
+
340
+ /** Store states an installed module's pill says before anything else. */
341
+ const STORE_STATE_PILLS: Partial<
342
+ Record<StoreModuleState, InstalledTileStatus>
343
+ > = {
344
+ busy: { tone: "info", labelKey: "modules.store.state.busy", dot: "live" },
345
+ error: { tone: "error", labelKey: "modules.store.state.error" },
346
+ disabled: { tone: "warning", labelKey: "modules.store.state.disabled" },
347
+ pending: { tone: "warning", labelKey: "modules.store.state.pending" },
348
+ };
349
+
350
+ const MODULE_STATUS_PILLS: Record<
351
+ ReturnType<typeof moduleStatus>,
352
+ InstalledTileStatus
353
+ > = {
354
+ live: { tone: "success", labelKey: "modules.status.live" },
355
+ beta: { tone: "secondary", labelKey: "modules.status.beta" },
356
+ update: { tone: "warning", labelKey: "modules.status.update" },
357
+ attention: { tone: "error", labelKey: "modules.status.attention" },
358
+ };
359
+
360
+ /**
361
+ * The pill of an installed module: what the store says of it first (an
362
+ * operation, an error, paused, not applied, a frontend to restart, an
363
+ * update), then what the module reports of itself, Active otherwise.
364
+ */
365
+ export function installedTileStatus(
366
+ module: ModuleCatalogEntry,
367
+ entry: StoreListingEntry | undefined,
368
+ ): InstalledTileStatus {
369
+ const storeStatus = entry && STORE_STATE_PILLS[entry.state];
370
+ if (storeStatus) return storeStatus;
371
+ if (entry && isAwaitingFrontend(entry)) {
372
+ return { tone: "warning", labelKey: "modules.status.restart_required" };
373
+ }
374
+ if (entry?.updateAvailable) {
375
+ return { tone: "warning", labelKey: "modules.status.update" };
376
+ }
377
+ return MODULE_STATUS_PILLS[moduleStatus(module)];
378
+ }
379
+
380
+ /**
381
+ * Installed store modules that register no module of their own, as the
382
+ * Installed grid lists them: a paused module, one that did not start, or one
383
+ * without pages. antelope.config.ts still declares them, so they stay listed
384
+ * and managed there, with what the store knows of them.
385
+ */
386
+ export function storeOnlyModules(
387
+ store: readonly ModuleStoreEntry[],
388
+ registered: readonly Pick<ModuleCatalogEntry, "id">[],
389
+ ): ModuleCatalogEntry[] {
390
+ const registeredIds = new Set(registered.map((module) => module.id));
391
+ return store
392
+ .filter((entry) => isInstalled(entry) && !registeredIds.has(entry.id))
393
+ .map((entry) => ({
394
+ id: entry.id,
395
+ title: entry.title,
396
+ description: entry.description,
397
+ icon: entry.icon,
398
+ hasAccess: true,
399
+ // Nothing to open: the module serves no page.
400
+ landingSlug: "",
401
+ catalogCategory: entry.catalogCategory,
402
+ version:
403
+ entry.installedVersion && PLAIN_VERSION.test(entry.installedVersion)
404
+ ? entry.installedVersion
405
+ : undefined,
406
+ source: entry.source,
407
+ }));
408
+ }
@@ -1,9 +1,30 @@
1
1
  import type {
2
2
  ModuleCatalogEntry,
3
3
  ModuleStatus,
4
+ ModuleWithAccess,
4
5
  PageInfo,
5
6
  SiteLayout,
7
+ SiteLayoutTree,
6
8
  } from "../../types/page";
9
+ import { resolveModuleLandingSlug } from "../../utils/moduleLanding";
10
+
11
+ /** The Modules page, which lists and manages the installed modules. */
12
+ export const MODULES_PAGE_PATH = "/modules";
13
+ /** The module store, its own view under the Modules page. */
14
+ export const MODULE_STORE_PATH = `${MODULES_PAGE_PATH}/store`;
15
+
16
+ /**
17
+ * The grid of both module views, installed and store: three columns on a
18
+ * laptop, so a tile's description, readout and footer line have room. The
19
+ * min() keeps one column from overflowing a phone.
20
+ */
21
+ export const MODULE_TILE_GRID_CLASS =
22
+ "grid grid-cols-[repeat(auto-fill,minmax(min(340px,100%),1fr))] gap-4";
23
+
24
+ /** Where an installed module's tile is, the one that manages it. */
25
+ export function installedModuleLink(moduleId: string): string {
26
+ return `${MODULES_PAGE_PATH}#module-${moduleId}`;
27
+ }
7
28
 
8
29
  /** Category of the modules that declare none. */
9
30
  export const MODULE_CATEGORY_OTHER = "$modules.category_other";
@@ -12,6 +33,18 @@ export const MODULE_CATEGORY_ALL = "";
12
33
 
13
34
  export type ModuleSortMode = "recent" | "name";
14
35
 
36
+ /** The order the installed modules open in until the user picks another. */
37
+ export const DEFAULT_MODULE_SORT_MODE: ModuleSortMode = "name";
38
+
39
+ const MODULE_SORT_MODES: readonly ModuleSortMode[] = ["name", "recent"];
40
+
41
+ /** A remembered sort mode, or the default when it is missing or unknown. */
42
+ export function parseModuleSortMode(value: unknown): ModuleSortMode {
43
+ return (
44
+ MODULE_SORT_MODES.find((mode) => mode === value) ?? DEFAULT_MODULE_SORT_MODE
45
+ );
46
+ }
47
+
15
48
  /** Turns a display string (possibly a `$i18n.key`) into the shown text. */
16
49
  export type DisplayResolver = (value: string) => string;
17
50
 
@@ -78,23 +111,28 @@ export function summarizeModules(
78
111
  /**
79
112
  * Category filter options: the categories present, alphabetical, with
80
113
  * "Other" last. The "All" option is the caller's to add.
114
+ *
115
+ * Categories are told apart by their shown name: two modules that each name
116
+ * "Content" through their own i18n key share one option, whose value is the
117
+ * first key met.
81
118
  */
82
119
  export function moduleCategoryOptions(
83
120
  entries: readonly FilterableModule[],
84
121
  resolve: DisplayResolver,
85
122
  ): ModuleCategoryOption[] {
86
- const counts = new Map<string, number>();
123
+ const byLabel = new Map<string, ModuleCategoryOption>();
87
124
  for (const entry of entries) {
88
- const category = moduleCategory(entry);
89
- counts.set(category, (counts.get(category) ?? 0) + 1);
125
+ const value = moduleCategory(entry);
126
+ const label = resolve(value);
127
+ const option = byLabel.get(label);
128
+ if (option) option.count += 1;
129
+ else byLabel.set(label, { value, label, count: 1 });
90
130
  }
91
- return [...counts.entries()]
92
- .map(([value, count]) => ({ value, label: resolve(value), count }))
93
- .sort((a, b) => {
94
- if (a.value === MODULE_CATEGORY_OTHER) return 1;
95
- if (b.value === MODULE_CATEGORY_OTHER) return -1;
96
- return a.label.localeCompare(b.label);
97
- });
131
+ return [...byLabel.values()].sort((a, b) => {
132
+ if (a.value === MODULE_CATEGORY_OTHER) return 1;
133
+ if (b.value === MODULE_CATEGORY_OTHER) return -1;
134
+ return a.label.localeCompare(b.label);
135
+ });
98
136
  }
99
137
 
100
138
  function normalizeQuery(query: string): string {
@@ -106,10 +144,14 @@ export function filterModules<T extends FilterableModule>(
106
144
  options: { query: string; category: string; resolve: DisplayResolver },
107
145
  ): T[] {
108
146
  const query = normalizeQuery(options.query);
147
+ const category =
148
+ options.category === MODULE_CATEGORY_ALL
149
+ ? undefined
150
+ : options.resolve(options.category);
109
151
  return entries.filter((entry) => {
110
152
  if (
111
- options.category !== MODULE_CATEGORY_ALL &&
112
- moduleCategory(entry) !== options.category
153
+ category !== undefined &&
154
+ options.resolve(moduleCategory(entry)) !== category
113
155
  ) {
114
156
  return false;
115
157
  }
@@ -126,18 +168,36 @@ export function filterModules<T extends FilterableModule>(
126
168
  });
127
169
  }
128
170
 
171
+ /**
172
+ * Compares shown names as `locale` orders them: accents and case only break
173
+ * ties ("Mailing", "Marketing", "Médiathèque"), and digits read as numbers.
174
+ * An unknown locale falls back to the runtime's own.
175
+ */
176
+ export function compareDisplayNames(
177
+ locale?: string,
178
+ ): (a: string, b: string) => number {
179
+ const options: Intl.CollatorOptions = { numeric: true };
180
+ try {
181
+ return new Intl.Collator(locale, options).compare;
182
+ } catch {
183
+ return new Intl.Collator(undefined, options).compare;
184
+ }
185
+ }
186
+
129
187
  /**
130
188
  * `recent`: most recently opened first, never-opened modules after them by
131
- * name. `name`: alphabetical.
189
+ * name. `name`: alphabetical by shown name, in `locale`'s order.
132
190
  */
133
191
  export function sortModules(
134
192
  entries: readonly ModuleCatalogEntry[],
135
193
  mode: ModuleSortMode,
136
194
  visitedAt: ReadonlyMap<string, number>,
137
195
  resolve: DisplayResolver,
196
+ locale?: string,
138
197
  ): ModuleCatalogEntry[] {
198
+ const compare = compareDisplayNames(locale);
139
199
  const byName = (a: ModuleCatalogEntry, b: ModuleCatalogEntry) =>
140
- resolve(a.title).localeCompare(resolve(b.title));
200
+ compare(resolve(a.title), resolve(b.title));
141
201
  return [...entries].sort((a, b) => {
142
202
  if (mode === "recent") {
143
203
  const delta = (visitedAt.get(b.id) ?? -1) - (visitedAt.get(a.id) ?? -1);
@@ -197,3 +257,51 @@ export function findModulePageMatch(
197
257
  }
198
258
  return null;
199
259
  }
260
+
261
+ /** A visited module as the command palette's recent modules list it. */
262
+ export interface RecentModule {
263
+ moduleId: string;
264
+ /** Display name, possibly a `$i18n.key`. */
265
+ title: string;
266
+ icon: string;
267
+ /** The module's landing page, never the page it was left on. */
268
+ to: string;
269
+ }
270
+
271
+ /** A visit of the module history, oldest first as it keeps them. */
272
+ export interface ModuleVisit {
273
+ moduleId: string;
274
+ }
275
+
276
+ /**
277
+ * The visited modules the caller may still open, most recent first, each
278
+ * leading to its landing page: every way into a module lands there. A module
279
+ * with no page the caller may open is left out.
280
+ */
281
+ export function recentModules(
282
+ visits: readonly ModuleVisit[],
283
+ modules: Readonly<Record<string, ModuleWithAccess>> | undefined,
284
+ tree: SiteLayoutTree | undefined,
285
+ ): RecentModule[] {
286
+ return visits
287
+ .slice()
288
+ .reverse()
289
+ .flatMap((visit) => {
290
+ const module = modules?.[visit.moduleId];
291
+ if (!module?.hasAccess) return [];
292
+ const to = resolveModuleLandingSlug(
293
+ tree,
294
+ visit.moduleId,
295
+ module.landingPage,
296
+ );
297
+ if (!to) return [];
298
+ return [
299
+ {
300
+ moduleId: visit.moduleId,
301
+ title: module.title,
302
+ icon: module.icon,
303
+ to,
304
+ },
305
+ ];
306
+ });
307
+ }
@@ -27,11 +27,37 @@ export const resolveLayoutBannerPresentation = (
27
27
  return { ...presentation, icon: banner.icon ?? presentation.icon };
28
28
  };
29
29
 
30
+ /**
31
+ * Whether a path is in the module area: the modules page, the module store
32
+ * and every module's own pages, all under `MODULE_URL_PREFIX`.
33
+ */
34
+ export const isModuleAreaPath = (path: string): boolean =>
35
+ path === MODULE_URL_PREFIX || path.startsWith(`${MODULE_URL_PREFIX}/`);
36
+
37
+ const SCOPE_MATCHERS: Record<
38
+ LayoutBannerScope,
39
+ (isModuleArea: boolean) => boolean
40
+ > = {
41
+ app: (isModuleArea) => !isModuleArea,
42
+ modules: (isModuleArea) => isModuleArea,
43
+ everywhere: () => true,
44
+ };
45
+
46
+ /** Whether a banner's scope, as the backend serves it, covers a page. */
47
+ export const isLayoutBannerInScope = (
48
+ banner: LayoutBanner,
49
+ path: string,
50
+ ): boolean => SCOPE_MATCHERS[banner.scope](isModuleAreaPath(path));
51
+
30
52
  /**
31
53
  * The global banners rendered under the dashboard header, as resolved by the
32
54
  * server for this request (see `RegisterLayoutBanner` in
33
- * `@antelopejs/interface-dms/layout-banners`), minus the ones the user
34
- * dismissed.
55
+ * `@antelopejs/interface-dms/layout-banners`), keeping the ones whose scope
56
+ * covers the current route, minus the ones the user dismissed.
57
+ *
58
+ * The site layout is shared by every page, so the scope is applied here
59
+ * against the route: on the server-rendered page and again on each
60
+ * client-side navigation, without re-fetching the layout.
35
61
  *
36
62
  * Dismissals are kept in a session cookie rather than in local storage so the
37
63
  * server-rendered page already leaves a dismissed banner out, instead of
@@ -39,6 +65,7 @@ export const resolveLayoutBannerPresentation = (
39
65
  */
40
66
  export const useLayoutBanners = () => {
41
67
  const { siteLayout } = useSiteLayout();
68
+ const route = useDmsRoute();
42
69
  const dismissed = useDmsCookie<string[]>(DISMISSED_BANNERS_COOKIE, {
43
70
  default: () => [],
44
71
  sameSite: "lax",
@@ -46,7 +73,9 @@ export const useLayoutBanners = () => {
46
73
 
47
74
  const banners = computed<LayoutBanner[]>(() =>
48
75
  (siteLayout.value?.banners ?? []).filter(
49
- (banner) => !banner.dismissible || !dismissed.value.includes(banner.key),
76
+ (banner) =>
77
+ isLayoutBannerInScope(banner, route.path) &&
78
+ (!banner.dismissible || !dismissed.value.includes(banner.key)),
50
79
  ),
51
80
  );
52
81