@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.
- package/CHANGELOG.md +114 -0
- package/README.md +1 -1
- package/data/module-store/catalog.json +2 -2
- package/dist/attachments/table-view.js +2 -2
- package/dist/attachments/table-view.js.map +1 -1
- package/dist/automation/actions.d.ts +2 -0
- package/dist/automation/actions.js +116 -71
- package/dist/automation/actions.js.map +1 -1
- package/dist/automation/i18n.d.ts +4 -0
- package/dist/automation/i18n.js +19 -0
- package/dist/automation/i18n.js.map +1 -0
- package/dist/automation/send-notification.d.ts +21 -0
- package/dist/automation/send-notification.js +163 -0
- package/dist/automation/send-notification.js.map +1 -0
- package/dist/automation/triggers.js +19 -25
- package/dist/automation/triggers.js.map +1 -1
- package/dist/client-origin-cors.d.ts +1 -0
- package/dist/client-origin-cors.js +19 -0
- package/dist/client-origin-cors.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +5 -3
- package/dist/config.js.map +1 -1
- package/dist/frontend-module.d.ts +4 -0
- package/dist/frontend-module.js +37 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/implementations/dms/client-ip.js +1 -1
- package/dist/implementations/dms/client-ip.js.map +1 -1
- package/dist/implementations/dms/dev-reload.d.ts +5 -0
- package/dist/implementations/dms/dev-reload.js +23 -3
- package/dist/implementations/dms/dev-reload.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +2 -4
- package/dist/implementations/dms/frontend-modules.js +22 -89
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +12 -2
- package/dist/implementations/dms/frontend-ready.js +34 -4
- package/dist/implementations/dms/frontend-ready.js.map +1 -1
- package/dist/implementations/dms/layout-banners.d.ts +2 -1
- package/dist/implementations/dms/layout-banners.js +9 -0
- package/dist/implementations/dms/layout-banners.js.map +1 -1
- package/dist/implementations/dms/manifest.d.ts +1 -2
- package/dist/implementations/dms/manifest.js +2 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +4 -3
- package/dist/implementations/dms/page.js +49 -18
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
- package/dist/implementations/dms/permissions-resolver.js +2 -2
- package/dist/implementations/dms/permissions-resolver.js.map +1 -1
- package/dist/implementations/dms/realtime.js +1 -0
- package/dist/implementations/dms/realtime.js.map +1 -1
- package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
- package/dist/implementations/dms-base/bulk-selection.js +23 -1
- package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
- package/dist/implementations/dms-base/filter-tokens.js +5 -3
- package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
- package/dist/implementations/dms-base/search-route.d.ts +1 -0
- package/dist/implementations/dms-base/search-route.js +35 -13
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/implementations/dms-base/table-view.js +11 -6
- package/dist/implementations/dms-base/table-view.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +2 -2
- package/dist/implementations/dms-notifications/index.js +5 -5
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/index.js +28 -45
- package/dist/index.js.map +1 -1
- package/dist/pages/modules/index.d.ts +1 -0
- package/dist/pages/modules/index.js +1 -0
- package/dist/pages/modules/index.js.map +1 -1
- package/dist/pages/modules/modules-store.d.ts +9 -0
- package/dist/pages/modules/modules-store.js +30 -0
- package/dist/pages/modules/modules-store.js.map +1 -0
- package/dist/pages/settings/users/account-credentials.js +2 -2
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/account-data-store.js +25 -8
- package/dist/pages/settings/users/account-data-store.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.js +9 -3
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/member-management.js +13 -14
- package/dist/pages/settings/users/member-management.js.map +1 -1
- package/dist/pages/settings/users/profile.d.ts +1 -4
- package/dist/pages/settings/users/profile.js +0 -35
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.js +26 -18
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/role-editor.d.ts +0 -1
- package/dist/pages/settings/users/role-editor.js +1 -2
- package/dist/pages/settings/users/role-editor.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +1 -2
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.js +2 -2
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/realtime/broker.d.ts +6 -0
- package/dist/realtime/broker.js +8 -1
- package/dist/realtime/broker.js.map +1 -1
- package/dist/realtime/presence.d.ts +8 -3
- package/dist/realtime/presence.js +8 -4
- package/dist/realtime/presence.js.map +1 -1
- package/dist/realtime/sessions.d.ts +11 -1
- package/dist/realtime/sessions.js +21 -3
- package/dist/realtime/sessions.js.map +1 -1
- package/dist/realtime/table-view.d.ts +2 -2
- package/dist/realtime/table-view.js +4 -3
- package/dist/realtime/table-view.js.map +1 -1
- package/dist/routes/auth/client-ip.d.ts +1 -0
- package/dist/routes/auth/client-ip.js +18 -0
- package/dist/routes/auth/client-ip.js.map +1 -1
- package/dist/routes/auth/oauth/config.d.ts +2 -1
- package/dist/routes/auth/oauth/config.js +5 -1
- package/dist/routes/auth/oauth/config.js.map +1 -1
- package/dist/routes/auth/session-handoff.d.ts +0 -1
- package/dist/routes/auth/session-handoff.js +0 -2
- package/dist/routes/auth/session-handoff.js.map +1 -1
- package/dist/routes/auth.js +3 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/realtime.d.ts +13 -1
- package/dist/routes/realtime.js +114 -29
- package/dist/routes/realtime.js.map +1 -1
- package/dist/startup-config.d.ts +2 -0
- package/dist/startup-config.js +62 -0
- package/dist/startup-config.js.map +1 -0
- package/dist/store/interfaces.d.ts +3 -2
- package/dist/store/interfaces.js +16 -6
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/listing.js +24 -13
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +4 -0
- package/dist/store/operations.js.map +1 -1
- package/dist/store/requirements.js +3 -2
- package/dist/store/requirements.js.map +1 -1
- package/dist/store/runtime.d.ts +10 -1
- package/dist/store/runtime.js +33 -6
- package/dist/store/runtime.js.map +1 -1
- package/dist/store/types.d.ts +10 -0
- package/dist/validation/auth.schema.d.ts +2 -2
- package/dist/validation/member-invite.schema.d.ts +2 -10
- package/dist/validation/member-invite.schema.js +2 -15
- package/dist/validation/member-invite.schema.js.map +1 -1
- package/frontend-vue/dms.frontend.ts +24 -5
- package/frontend-vue/dms.server.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
- package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
- package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
- package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
- package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
- package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
- package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
- package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
- package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
- package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
- package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
- package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
- package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
- package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
- package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
- package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
- package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
- package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
- package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
- package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
- package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
- package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
- package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
- package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
- package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
- package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
- package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
- package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
- package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
- package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
- package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
- package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
- package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
- package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
- package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
- package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
- package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
- package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
- package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
- package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
- package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
- package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
- package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
- package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
- package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
- package/frontend-vue/package.json +2 -2
- package/package.json +7 -6
- package/skills/dms/SKILL.md +1 -1
- package/skills/dms-auth/SKILL.md +5 -3
- package/skills/dms-best-practices/REFERENCE.md +2 -0
- package/skills/dms-best-practices/SKILL.md +4 -0
- package/skills/dms-module/SKILL.md +3 -2
- package/skills/dms-pages/REFERENCE.md +125 -16
- package/skills/dms-pages/SKILL.md +1 -1
- package/skills/dms-project/DEV-SERVERS.md +23 -1
- package/skills/dms-project/REFERENCE.md +1 -1
- package/skills/dms-project/SKILL.md +4 -1
- package/skills/dms-project/scripts/dms-dev.sh +205 -17
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
|
@@ -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
|
|
123
|
+
const byLabel = new Map<string, ModuleCategoryOption>();
|
|
87
124
|
for (const entry of entries) {
|
|
88
|
-
const
|
|
89
|
-
|
|
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 [...
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
112
|
-
moduleCategory(entry) !==
|
|
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)
|
|
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`),
|
|
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) =>
|
|
76
|
+
(banner) =>
|
|
77
|
+
isLayoutBannerInScope(banner, route.path) &&
|
|
78
|
+
(!banner.dismissible || !dismissed.value.includes(banner.key)),
|
|
50
79
|
),
|
|
51
80
|
);
|
|
52
81
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { computed, type ComputedRef } from "vue";
|
|
2
|
+
import { resolveUrlVariables } from "#dms-ui/app/build/utils/urlVariables";
|
|
3
|
+
import type { PageRecord } from "../../utils/pageRecord";
|
|
4
|
+
|
|
5
|
+
/** The record a page shows, as the layout declares it (`record`). */
|
|
6
|
+
export interface PageRecordSource {
|
|
7
|
+
/** `{{params.X}}` and `{{query.X}}` are filled in from the page URL. */
|
|
8
|
+
fetchUrl: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The record the page shows, for the header buttons acting on it: loading
|
|
13
|
+
* until it answers, missing when it does not, or when its URL names a value
|
|
14
|
+
* the page does not carry. Undefined for a page declaring no record.
|
|
15
|
+
*
|
|
16
|
+
* It is read through `useApiData`, keyed on its URL like a block's
|
|
17
|
+
* `fetchUrl`: a block of the page reading the same URL shares the one
|
|
18
|
+
* request, and a server render hands the record to the browser with the page.
|
|
19
|
+
*/
|
|
20
|
+
export function usePageRecord(
|
|
21
|
+
source: () => PageRecordSource | undefined,
|
|
22
|
+
): ComputedRef<PageRecord | undefined> {
|
|
23
|
+
const route = useDmsRoute();
|
|
24
|
+
const siteLayout = useSiteLayout();
|
|
25
|
+
|
|
26
|
+
// Null while the URL names a value the page does not carry.
|
|
27
|
+
const url = computed(() => {
|
|
28
|
+
const declared = source();
|
|
29
|
+
if (!declared) return undefined;
|
|
30
|
+
return (
|
|
31
|
+
resolveUrlVariables(declared.fetchUrl, {
|
|
32
|
+
routeParams: siteLayout.findMatchingRoute(route.path)?.params,
|
|
33
|
+
routeQuery: route.query as Record<string, unknown>,
|
|
34
|
+
}) ?? null
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
// The options a block's read takes (`useChartFetch`): a block never holds
|
|
38
|
+
// a navigation, and neither does the header.
|
|
39
|
+
const { data, error } = useApiData<Record<string, unknown>>(
|
|
40
|
+
() => url.value ?? null,
|
|
41
|
+
{ lazy: true },
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return computed<PageRecord | undefined>(() => {
|
|
45
|
+
if (url.value === undefined) return undefined;
|
|
46
|
+
if (url.value === null || error.value) return { status: "missing" };
|
|
47
|
+
if (data.value) return { status: "loaded", values: data.value };
|
|
48
|
+
return { status: "loading" };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -3,6 +3,8 @@ import type { LayoutHeaderAction } from "../../build/components/layout/PageHeade
|
|
|
3
3
|
import type { PageLayout } from "../../types/page";
|
|
4
4
|
import {
|
|
5
5
|
isSamePageQuery,
|
|
6
|
+
isSameRouteParams,
|
|
7
|
+
fillRouteParams,
|
|
6
8
|
layoutUrlWithQuery,
|
|
7
9
|
pageQueryValues,
|
|
8
10
|
} from "../../utils/pageQuery";
|
|
@@ -15,6 +17,8 @@ interface ServedHeaderActionsOptions {
|
|
|
15
17
|
* declares an `availability` (`headerActionsQuery`).
|
|
16
18
|
*/
|
|
17
19
|
servedQuery: () => Record<string, string> | undefined;
|
|
20
|
+
/** The route parameters (`:id`) their availability was resolved for. */
|
|
21
|
+
servedParams?: () => Record<string, string> | undefined;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
/**
|
|
@@ -22,20 +26,22 @@ interface ServedHeaderActionsOptions {
|
|
|
22
26
|
* `availability` depends on the query (`?workspace=` names the record the
|
|
23
27
|
* page acts on) was resolved for the query the layout was loaded with: when
|
|
24
28
|
* the URL's query moves away from it, the header asks the page layout again
|
|
25
|
-
* for that query and takes its buttons, keeping the last ones meanwhile.
|
|
29
|
+
* for that query and takes its buttons, keeping the last ones meanwhile. The
|
|
30
|
+
* same goes for the route parameters: the layout of `/projects/project/:id`
|
|
31
|
+
* is cached once for the page, whichever record it was first served for.
|
|
26
32
|
* Buttons without an availability never cause a request.
|
|
27
33
|
*/
|
|
28
34
|
export function useServedHeaderActions({
|
|
29
35
|
actions,
|
|
30
36
|
servedQuery,
|
|
37
|
+
servedParams = () => undefined,
|
|
31
38
|
}: ServedHeaderActionsOptions): ComputedRef<LayoutHeaderAction[]> {
|
|
32
39
|
const route = useDmsRoute();
|
|
33
40
|
const siteLayout = useSiteLayout();
|
|
34
41
|
const refreshed = shallowRef<LayoutHeaderAction[] | null>(null);
|
|
35
42
|
let latestRequest = 0;
|
|
36
43
|
|
|
37
|
-
const
|
|
38
|
-
siteLayout.findMatchingRoute(route.path)?.metadata?.layoutUrl;
|
|
44
|
+
const pageRoute = () => siteLayout.findMatchingRoute(route.path);
|
|
39
45
|
|
|
40
46
|
async function askFor(url: string, query: Record<string, string>) {
|
|
41
47
|
const request = ++latestRequest;
|
|
@@ -58,17 +64,29 @@ export function useServedHeaderActions({
|
|
|
58
64
|
function follow() {
|
|
59
65
|
const served = servedQuery();
|
|
60
66
|
const current = pageQueryValues(route.query as Record<string, unknown>);
|
|
61
|
-
const
|
|
62
|
-
|
|
67
|
+
const match = pageRoute();
|
|
68
|
+
const layoutUrl = match?.metadata?.layoutUrl;
|
|
69
|
+
const params = match?.params ?? {};
|
|
70
|
+
const paramsServed = servedParams();
|
|
71
|
+
if (
|
|
72
|
+
!served ||
|
|
73
|
+
!layoutUrl ||
|
|
74
|
+
(isSamePageQuery(served, current) &&
|
|
75
|
+
(!paramsServed || isSameRouteParams(paramsServed, params)))
|
|
76
|
+
) {
|
|
63
77
|
latestRequest++;
|
|
64
78
|
refreshed.value = null;
|
|
65
79
|
return;
|
|
66
80
|
}
|
|
67
|
-
void askFor(
|
|
81
|
+
void askFor(fillRouteParams(layoutUrl, params), current);
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
if (!import.meta.env.SSR) {
|
|
71
|
-
watch(
|
|
85
|
+
watch(
|
|
86
|
+
[() => route.query, () => route.path, servedQuery, servedParams],
|
|
87
|
+
follow,
|
|
88
|
+
{ immediate: true },
|
|
89
|
+
);
|
|
72
90
|
}
|
|
73
91
|
|
|
74
92
|
return computed(() => refreshed.value ?? actions());
|
|
@@ -44,8 +44,9 @@ function normalizeHistory(raw: unknown): StoredHistory {
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Last page visited in each module, kept in a cookie so it survives reloads
|
|
47
|
-
* and the server renders the same
|
|
48
|
-
* the
|
|
47
|
+
* and the server renders the same "recently used" order as the browser. It
|
|
48
|
+
* feeds that order and the command palette's recent modules; it never decides
|
|
49
|
+
* where entering a module lands, which is always the module's landing page.
|
|
49
50
|
*/
|
|
50
51
|
export const useModuleHistory = (): ModuleHistoryApi => {
|
|
51
52
|
const cookie = useDmsCookie<StoredHistory>(MODULE_HISTORY_COOKIE, {
|
|
@@ -16,13 +16,35 @@ const FOLLOW_INTERVAL_MS = 1000;
|
|
|
16
16
|
* follow-up that reads the listing again while something moves. An operation
|
|
17
17
|
* the owner started here reloads the page once the frontend serves its
|
|
18
18
|
* result, so a new module's pages appear without a manual refresh.
|
|
19
|
+
*
|
|
20
|
+
* The first read comes with the page (`useApiData`): a server render waits
|
|
21
|
+
* for it and hands it over, so the tiles render with their store details and
|
|
22
|
+
* never move once mounted.
|
|
19
23
|
*/
|
|
20
24
|
export const useModuleStore = () => {
|
|
21
25
|
// Taken once, in setup: the follow-up reads the listing from a timer,
|
|
22
26
|
// outside any component context.
|
|
23
27
|
const { $authFetch } = useAuthFetch();
|
|
24
|
-
const
|
|
25
|
-
|
|
28
|
+
const isOwner = useIsOwner();
|
|
29
|
+
// Platform owners only: a member's read would be refused.
|
|
30
|
+
const initial = useApiData<StoreListing>(() =>
|
|
31
|
+
isOwner.value ? ENDPOINT_STORE_LISTING : null,
|
|
32
|
+
);
|
|
33
|
+
// The follow-up reads write into the same data, so a later visit shows
|
|
34
|
+
// the latest listing at once.
|
|
35
|
+
const listing = computed<StoreListing | undefined>({
|
|
36
|
+
get: () => initial.data.value ?? undefined,
|
|
37
|
+
set: (value) => {
|
|
38
|
+
initial.data.value = value ?? null;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
/** The last follow-up read's failure; `null` once one succeeds. */
|
|
42
|
+
const followError = ref<unknown>(undefined);
|
|
43
|
+
const loadingError = computed<unknown>(() =>
|
|
44
|
+
followError.value === undefined
|
|
45
|
+
? (initial.error.value ?? null)
|
|
46
|
+
: followError.value,
|
|
47
|
+
);
|
|
26
48
|
/** Modules an operation from this page changed, until the frontend follows. */
|
|
27
49
|
const awaitingReload = ref(new Set<string>());
|
|
28
50
|
let followTimer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -38,9 +60,32 @@ export const useModuleStore = () => {
|
|
|
38
60
|
async function load(): Promise<void> {
|
|
39
61
|
try {
|
|
40
62
|
listing.value = await $authFetch<StoreListing>(ENDPOINT_STORE_LISTING);
|
|
41
|
-
|
|
63
|
+
followError.value = null;
|
|
42
64
|
} catch (error) {
|
|
43
|
-
|
|
65
|
+
followError.value = error;
|
|
66
|
+
}
|
|
67
|
+
reloadWhenServed();
|
|
68
|
+
scheduleFollow();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let isStopped = false;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Starts following once mounted: reads again only when the first read
|
|
75
|
+
* failed, or something moves; a listing already on screen stays as is.
|
|
76
|
+
* A first read still in flight (a client navigation) is waited for.
|
|
77
|
+
*/
|
|
78
|
+
function start(): void {
|
|
79
|
+
isStopped = false;
|
|
80
|
+
if (initial.pending.value) {
|
|
81
|
+
void Promise.resolve(initial).then(() => {
|
|
82
|
+
if (!isStopped) start();
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!listing.value) {
|
|
87
|
+
void load();
|
|
88
|
+
return;
|
|
44
89
|
}
|
|
45
90
|
reloadWhenServed();
|
|
46
91
|
scheduleFollow();
|
|
@@ -93,6 +138,7 @@ export const useModuleStore = () => {
|
|
|
93
138
|
}
|
|
94
139
|
|
|
95
140
|
function stop(): void {
|
|
141
|
+
isStopped = true;
|
|
96
142
|
if (followTimer) clearTimeout(followTimer);
|
|
97
143
|
followTimer = undefined;
|
|
98
144
|
}
|
|
@@ -103,6 +149,7 @@ export const useModuleStore = () => {
|
|
|
103
149
|
isFollowing,
|
|
104
150
|
awaitingReload,
|
|
105
151
|
load,
|
|
152
|
+
start,
|
|
106
153
|
run,
|
|
107
154
|
stop,
|
|
108
155
|
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { Ref, VNode } from "vue";
|
|
2
|
+
import ModuleStoreDependencyPicker from "../../build/components/pages/modules/ModuleStoreDependencyPicker.vue";
|
|
3
|
+
import {
|
|
4
|
+
ConfirmActionError,
|
|
5
|
+
useConfirm,
|
|
6
|
+
} from "#dms-ui/app/composables/confirm";
|
|
7
|
+
import {
|
|
8
|
+
type ModuleStoreEntry,
|
|
9
|
+
isAwaitingFrontend,
|
|
10
|
+
needsDependencyChoice,
|
|
11
|
+
} from "../../build/utils/module-store-catalog";
|
|
12
|
+
import {
|
|
13
|
+
type DependencyDraft,
|
|
14
|
+
areDependencyDraftsValid,
|
|
15
|
+
buildInstallBody,
|
|
16
|
+
createDependencyDrafts,
|
|
17
|
+
} from "../../build/utils/module-store-install";
|
|
18
|
+
import type {
|
|
19
|
+
StoreActionBody,
|
|
20
|
+
StoreOperationKind,
|
|
21
|
+
} from "../../types/module-store";
|
|
22
|
+
import type { useModuleStore } from "./useModuleStore";
|
|
23
|
+
|
|
24
|
+
/** What a refused store request carries: the server's error key, if any. */
|
|
25
|
+
interface StoreRequestError {
|
|
26
|
+
data?: unknown;
|
|
27
|
+
message?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What the frontend banner says while it lags behind the backend. */
|
|
31
|
+
export interface StoreFrontendNotice {
|
|
32
|
+
key: "dev" | "production" | "unknown";
|
|
33
|
+
names: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ModuleStore = ReturnType<typeof useModuleStore>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The store's actions as both Modules views run them: the confirmations,
|
|
40
|
+
* the install dialog that picks the modules for missing interfaces, and a
|
|
41
|
+
* refusal worded from the server's error key. Also what the frontend banner
|
|
42
|
+
* says while the interface lags behind the backend.
|
|
43
|
+
*/
|
|
44
|
+
export const useModuleStoreActions = (
|
|
45
|
+
moduleStore: ModuleStore,
|
|
46
|
+
entries: Ref<ModuleStoreEntry[]>,
|
|
47
|
+
) => {
|
|
48
|
+
const { t } = useI18n();
|
|
49
|
+
const { processI18n } = useTranslation();
|
|
50
|
+
const toast = useToast();
|
|
51
|
+
const { confirm } = useConfirm();
|
|
52
|
+
|
|
53
|
+
function resolveStoreError(error: unknown): string {
|
|
54
|
+
const details = error as StoreRequestError | undefined;
|
|
55
|
+
const key = typeof details?.data === "string" ? details.data : undefined;
|
|
56
|
+
if (!key) return details?.message ?? "";
|
|
57
|
+
return t(`modules.store.errors.${key.replace("error.", "")}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Runs `kind` from a dialog: a refusal is worded as the toast words it and
|
|
62
|
+
* shown in the dialog, which stays open.
|
|
63
|
+
*/
|
|
64
|
+
async function runFromDialog(
|
|
65
|
+
kind: StoreOperationKind,
|
|
66
|
+
entry: ModuleStoreEntry,
|
|
67
|
+
body?: StoreActionBody,
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
try {
|
|
70
|
+
await moduleStore.run(kind, entry, body);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new ConfirmActionError({
|
|
73
|
+
title: t("modules.store.action_failed", {
|
|
74
|
+
module: processI18n(entry.title),
|
|
75
|
+
}),
|
|
76
|
+
description: resolveStoreError(error),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** What the owner last picked in a module's install dialog, for a retry. */
|
|
82
|
+
const lastDependencyDrafts = new Map<string, DependencyDraft[]>();
|
|
83
|
+
|
|
84
|
+
function renderDependencyPicker(
|
|
85
|
+
entry: ModuleStoreEntry,
|
|
86
|
+
drafts: Ref<DependencyDraft[]>,
|
|
87
|
+
showErrors: boolean,
|
|
88
|
+
): VNode {
|
|
89
|
+
return h(ModuleStoreDependencyPicker, {
|
|
90
|
+
missing: entry.missingInterfaces,
|
|
91
|
+
drafts: drafts.value,
|
|
92
|
+
showErrors,
|
|
93
|
+
onUpdate: (index: number, patch: Partial<DependencyDraft>) => {
|
|
94
|
+
drafts.value = drafts.value.map((draft, position) =>
|
|
95
|
+
position === index ? { ...draft, ...patch } : draft,
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Asks which modules implement the interfaces the release misses, then
|
|
103
|
+
* installs them with it. A refusal shows in the dialog, kept open.
|
|
104
|
+
*/
|
|
105
|
+
async function installWithDependencies(
|
|
106
|
+
entry: ModuleStoreEntry,
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
const title = processI18n(entry.title);
|
|
109
|
+
const drafts = ref(
|
|
110
|
+
createDependencyDrafts(
|
|
111
|
+
entry.missingInterfaces,
|
|
112
|
+
lastDependencyDrafts.get(entry.id),
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
const showErrors = ref(false);
|
|
116
|
+
await confirm({
|
|
117
|
+
title: t("modules.store.install_dialog.title", { module: title }),
|
|
118
|
+
description: t("modules.store.install_dialog.description", {
|
|
119
|
+
module: title,
|
|
120
|
+
}),
|
|
121
|
+
confirmLabel: t("modules.store.actions.install"),
|
|
122
|
+
confirmIcon: "i-ph-download-simple",
|
|
123
|
+
icon: "i-ph-puzzle-piece",
|
|
124
|
+
body: () => renderDependencyPicker(entry, drafts, showErrors.value),
|
|
125
|
+
validate: () => {
|
|
126
|
+
showErrors.value = true;
|
|
127
|
+
return areDependencyDraftsValid(drafts.value);
|
|
128
|
+
},
|
|
129
|
+
onConfirm: async () => {
|
|
130
|
+
lastDependencyDrafts.set(entry.id, drafts.value);
|
|
131
|
+
await runFromDialog(
|
|
132
|
+
"install",
|
|
133
|
+
entry,
|
|
134
|
+
buildInstallBody(entry.id, drafts.value),
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function runStoreAction(
|
|
141
|
+
kind: StoreOperationKind,
|
|
142
|
+
entry: ModuleStoreEntry,
|
|
143
|
+
): Promise<void> {
|
|
144
|
+
const title = processI18n(entry.title);
|
|
145
|
+
if (needsDependencyChoice(entry, kind)) {
|
|
146
|
+
await installWithDependencies(entry);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (kind === "uninstall") {
|
|
150
|
+
await confirm({
|
|
151
|
+
title: t("modules.store.uninstall_confirm.title", { module: title }),
|
|
152
|
+
description: t("modules.store.uninstall_confirm.description", {
|
|
153
|
+
module: title,
|
|
154
|
+
}),
|
|
155
|
+
confirmLabel: t("modules.store.actions.uninstall"),
|
|
156
|
+
color: "error",
|
|
157
|
+
icon: "i-ph-trash",
|
|
158
|
+
onConfirm: () => runFromDialog(kind, entry),
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
await moduleStore.run(kind, entry);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
toast.add({
|
|
166
|
+
color: "error",
|
|
167
|
+
icon: "i-ph-warning-circle",
|
|
168
|
+
title: t("modules.store.action_failed", { module: title }),
|
|
169
|
+
description: resolveStoreError(error),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Store modules whose interface the running frontend does not match yet. */
|
|
175
|
+
const frontendNotice = computed<StoreFrontendNotice | null>(() => {
|
|
176
|
+
const lagging = entries.value.filter(
|
|
177
|
+
(entry) =>
|
|
178
|
+
isAwaitingFrontend(entry) ||
|
|
179
|
+
(entry.frontend === "unknown" &&
|
|
180
|
+
moduleStore.awaitingReload.value.has(entry.id)),
|
|
181
|
+
);
|
|
182
|
+
if (lagging.length === 0) return null;
|
|
183
|
+
const names = lagging.map((entry) => processI18n(entry.title)).join(", ");
|
|
184
|
+
const hasReported = lagging.every((entry) => entry.frontend !== "unknown");
|
|
185
|
+
const key = !hasReported
|
|
186
|
+
? "unknown"
|
|
187
|
+
: moduleStore.listing.value?.dev
|
|
188
|
+
? "dev"
|
|
189
|
+
: "production";
|
|
190
|
+
return { key, names };
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return { runStoreAction, frontendNotice };
|
|
194
|
+
};
|