@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.
- package/CHANGELOG.md +171 -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 -2
- package/dist/config.js +5 -7
- 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 +38 -94
- 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 +7 -2
- package/dist/implementations/dms/manifest.js +12 -7
- 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 +32 -55
- 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/index.d.ts +0 -1
- package/dist/routes/index.js +0 -1
- package/dist/routes/index.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 +25 -15
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +5 -2
- 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/utils/ensure-default-tenant.d.ts +2 -1
- package/dist/utils/ensure-default-tenant.js +13 -1
- package/dist/utils/ensure-default-tenant.js.map +1 -1
- package/dist/utils/export-jobs.js +2 -1
- package/dist/utils/export-jobs.js.map +1 -1
- 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 +33 -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/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 +25 -5
- package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -5
- package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +61 -26
- 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 +134 -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 +136 -2
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +136 -2
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +0 -2
- package/frontend-vue/layers/dms-layout/app/app.config.ts +0 -1
- package/frontend-vue/layers/dms-layout/app/assets/css/main.css +11 -1
- package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Italic-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Italic-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Variable.woff2 +0 -0
- 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 +6 -2
- 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 -107
- package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +4 -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/github-repository.ts +2 -2
- 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/useServedHeaderActions.ts +49 -11
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +59 -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/usePageRecordDeletion.ts +162 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +6 -2
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +6 -2
- 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 +13 -22
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +11 -10
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +22 -52
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-quick-actions.client.ts +7 -7
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +11 -10
- package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
- package/frontend-vue/layers/dms-layout/app/theme/actions.ts +5 -4
- 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/i18n/locales/layout-en-GB.json +45 -18
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +44 -17
- 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/FormLoadError.vue +5 -1
- 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 +48 -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/actionError.ts +6 -1
- 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 +44 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +123 -27
- 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 +92 -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/formRecordLoad.ts +11 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formValidation.ts +36 -4
- 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 +78 -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 +97 -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 +90 -35
- 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/composables/table-view/utils/writeError.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +67 -29
- 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/htmlGrammar.ts +79 -0
- 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/shellParser.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/utils/streamHighlight.ts +90 -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/code-snippet/CodeSnippet.vue +23 -12
- 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 +119 -36
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +53 -22
- 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 +226 -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 +210 -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/CascaderPath.vue +74 -25
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +48 -44
- 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 +426 -97
- 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 +16 -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 +189 -28
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +29 -12
- 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/cascader.ts +94 -3
- package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +20 -5
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +20 -5
- package/frontend-vue/package.json +6 -2
- package/package.json +11 -10
- 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 +129 -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/dist/hooks/tenant-instances.d.ts +0 -1
- package/dist/hooks/tenant-instances.js +0 -13
- package/dist/hooks/tenant-instances.js.map +0 -1
- package/dist/routes/startup-gate.d.ts +0 -7
- package/dist/routes/startup-gate.js +0 -39
- package/dist/routes/startup-gate.js.map +0 -1
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DataType,
|
|
3
|
+
DataTypeSelectSource,
|
|
4
|
+
} from "#dms-core/app/composables/data-types/useDataType";
|
|
5
|
+
import type { RenderableColumn } from "../data-types/useColumnValueRenderer";
|
|
6
|
+
import { NotFoundRelation } from "../data-types/relationRows";
|
|
7
|
+
|
|
8
|
+
/** A column of a read-only list: a field of its rows. */
|
|
9
|
+
export interface RepeaterTableColumn {
|
|
10
|
+
id: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
component: ComponentInfo;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Where a relation column reads the rows its values name. */
|
|
17
|
+
export interface RelationLabelSource extends DataTypeSelectSource {
|
|
18
|
+
multiple: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The column as the grid's cell renderer reads it: its data type, with the
|
|
23
|
+
* options of its control (a select's items, a relation's key mapping).
|
|
24
|
+
*/
|
|
25
|
+
export function renderableColumn(
|
|
26
|
+
column: RepeaterTableColumn,
|
|
27
|
+
): RenderableColumn {
|
|
28
|
+
const type: DataTypeConfig = {
|
|
29
|
+
id: column.type ?? "",
|
|
30
|
+
compareModes: [],
|
|
31
|
+
filterComponents: {},
|
|
32
|
+
// The column came from the form's JSON: its options are JSON values.
|
|
33
|
+
inputComponent: column.component as ComponentInfo<ComponentOptionsData>,
|
|
34
|
+
};
|
|
35
|
+
return { id: column.id, type };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The rows a relation column names its values from, if its data type reads
|
|
40
|
+
* its labels from a `select` route ({@link DataType.selectSource}).
|
|
41
|
+
*/
|
|
42
|
+
export function relationLabelSource(
|
|
43
|
+
column: RepeaterTableColumn,
|
|
44
|
+
dataType: Pick<DataType, "selectSource"> | undefined,
|
|
45
|
+
): RelationLabelSource | undefined {
|
|
46
|
+
const options = column.component.options as
|
|
47
|
+
| { multiple?: unknown }
|
|
48
|
+
| undefined;
|
|
49
|
+
const source = dataType?.selectSource?.(options);
|
|
50
|
+
return source ? { ...source, multiple: !!options?.multiple } : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The rows a relation column's read answered, by value, and the ids it asked. */
|
|
54
|
+
export interface RelationColumnRows {
|
|
55
|
+
rowsByValue: ReadonlyMap<unknown, Record<string, unknown>>;
|
|
56
|
+
readIds: ReadonlySet<string>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A relation value as its formatter draws it: the related row in place of
|
|
61
|
+
* each id it holds. An id the read asked for and no row answers (deleted, out
|
|
62
|
+
* of reach) is not found; one not asked yet stays as it is, as does a value
|
|
63
|
+
* the server already joined.
|
|
64
|
+
*/
|
|
65
|
+
export function joinRelationValue(
|
|
66
|
+
value: unknown,
|
|
67
|
+
{ rowsByValue, readIds }: RelationColumnRows,
|
|
68
|
+
multiple: boolean,
|
|
69
|
+
): unknown {
|
|
70
|
+
const join = (item: unknown) => {
|
|
71
|
+
const row = rowsByValue.get(item);
|
|
72
|
+
if (row) return row;
|
|
73
|
+
const id = String(item);
|
|
74
|
+
return readIds.has(id) ? new NotFoundRelation(id) : item;
|
|
75
|
+
};
|
|
76
|
+
if (multiple && Array.isArray(value)) return value.map(join);
|
|
77
|
+
return join(value);
|
|
78
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A server error as UForm holds it once placed: on the field, or on the part
|
|
3
|
+
* of it the error names (`address.street`).
|
|
4
|
+
*/
|
|
5
|
+
export interface PlacedServerError {
|
|
6
|
+
name?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** A refused submit's errors a field shows, and the value they were raised against. */
|
|
11
|
+
export interface KeptServerError {
|
|
12
|
+
/** The field's value when the server refused it, as JSON. */
|
|
13
|
+
value: string;
|
|
14
|
+
errors: PlacedServerError[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The value of a field as a kept error compares it. */
|
|
18
|
+
export function serverErrorValue(value: unknown): string {
|
|
19
|
+
return JSON.stringify(value) ?? "";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The fields whose value moved off the one their kept error was raised against. */
|
|
23
|
+
export function changedServerErrorFields(
|
|
24
|
+
kept: ReadonlyMap<string, KeptServerError>,
|
|
25
|
+
state: Readonly<Record<string, unknown>>,
|
|
26
|
+
): string[] {
|
|
27
|
+
return [...kept]
|
|
28
|
+
.filter(([name, entry]) => serverErrorValue(state[name]) !== entry.value)
|
|
29
|
+
.map(([name]) => name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The kept server errors, as UForm's `validate` answers them. UForm runs it
|
|
34
|
+
* with its schema on every validation and replaces a field's errors with what
|
|
35
|
+
* both answer: a number input commits its value as it loses focus, and the
|
|
36
|
+
* input event, landing 300 ms later, re-validates the field after a refused
|
|
37
|
+
* submit placed its error. A field still holding the refused value keeps it.
|
|
38
|
+
* A submit validates without them: the server judges the values again.
|
|
39
|
+
*/
|
|
40
|
+
export function keptServerErrors(
|
|
41
|
+
kept: ReadonlyMap<string, KeptServerError>,
|
|
42
|
+
state: Readonly<Record<string, unknown>>,
|
|
43
|
+
isSubmitting: boolean,
|
|
44
|
+
): PlacedServerError[] {
|
|
45
|
+
if (isSubmitting) return [];
|
|
46
|
+
return [...kept]
|
|
47
|
+
.filter(([name, entry]) => serverErrorValue(state[name]) === entry.value)
|
|
48
|
+
.flatMap(([, entry]) => entry.errors);
|
|
49
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted, watch } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The CSS variable the toaster's bottom offset adds (theme/surfaces.ts): how
|
|
5
|
+
* far above the viewport's bottom edge a bar anchored there reaches.
|
|
6
|
+
*/
|
|
7
|
+
export const TOASTER_CLEARANCE_VAR = "--dms-toaster-clearance";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* How close to the viewport's bottom edge a bar has to end to count as
|
|
11
|
+
* anchored there: the floating save bar floats 16px off it. A bar that ends
|
|
12
|
+
* higher (a short form's bar, a card's footer band mid-page) leaves the
|
|
13
|
+
* toaster's corner free.
|
|
14
|
+
*/
|
|
15
|
+
const BOTTOM_EDGE_REACH = 48;
|
|
16
|
+
|
|
17
|
+
/** The part of a bar's bounding box the clearance reads. */
|
|
18
|
+
export interface BarBox {
|
|
19
|
+
top: number;
|
|
20
|
+
bottom: number;
|
|
21
|
+
height: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The room the toaster leaves above the bars anchored to the bottom of a
|
|
26
|
+
* viewport `viewportHeight` tall: the distance from that edge to the highest
|
|
27
|
+
* such bar's top, 0 when none is.
|
|
28
|
+
*/
|
|
29
|
+
export function toasterClearance(
|
|
30
|
+
boxes: BarBox[],
|
|
31
|
+
viewportHeight: number,
|
|
32
|
+
): number {
|
|
33
|
+
let clearance = 0;
|
|
34
|
+
for (const box of boxes) {
|
|
35
|
+
const isAnchored =
|
|
36
|
+
box.height > 0 &&
|
|
37
|
+
box.top < viewportHeight &&
|
|
38
|
+
box.bottom >= viewportHeight - BOTTOM_EDGE_REACH;
|
|
39
|
+
if (isAnchored)
|
|
40
|
+
clearance = Math.max(clearance, Math.ceil(viewportHeight - box.top));
|
|
41
|
+
}
|
|
42
|
+
return clearance;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type BarElement = () => HTMLElement | null | undefined;
|
|
46
|
+
|
|
47
|
+
// Only mounted, shown bars count, so a server render never adds to it.
|
|
48
|
+
const shownBars = new Map<symbol, BarElement>();
|
|
49
|
+
let frame = 0;
|
|
50
|
+
|
|
51
|
+
function applyClearance(): void {
|
|
52
|
+
frame = 0;
|
|
53
|
+
const boxes = [...shownBars.values()]
|
|
54
|
+
.map((element) => element()?.getBoundingClientRect())
|
|
55
|
+
.filter((box): box is DOMRect => !!box);
|
|
56
|
+
const clearance = toasterClearance(boxes, window.innerHeight);
|
|
57
|
+
const { style } = document.documentElement;
|
|
58
|
+
if (clearance > 0) style.setProperty(TOASTER_CLEARANCE_VAR, `${clearance}px`);
|
|
59
|
+
else style.removeProperty(TOASTER_CLEARANCE_VAR);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// A bar moves with the scroll (a sticky bar reaches the edge, a form's end
|
|
63
|
+
// leaves it), the viewport, its own show transition and a drawer's opening
|
|
64
|
+
// animation: one measure a frame.
|
|
65
|
+
function scheduleClearance(): void {
|
|
66
|
+
if (frame) return;
|
|
67
|
+
frame = requestAnimationFrame(applyClearance);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const LISTENED_EVENTS = [
|
|
71
|
+
"scroll",
|
|
72
|
+
"resize",
|
|
73
|
+
"transitionend",
|
|
74
|
+
"animationend",
|
|
75
|
+
] as const;
|
|
76
|
+
|
|
77
|
+
function listen(isListening: boolean): void {
|
|
78
|
+
const method = isListening ? "addEventListener" : "removeEventListener";
|
|
79
|
+
for (const event of LISTENED_EVENTS)
|
|
80
|
+
window[method](event, scheduleClearance, { capture: true, passive: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function setBarShown(id: symbol, element: BarElement, isShown: boolean): void {
|
|
84
|
+
const wasEmpty = shownBars.size === 0;
|
|
85
|
+
if (isShown) shownBars.set(id, element);
|
|
86
|
+
else shownBars.delete(id);
|
|
87
|
+
if (wasEmpty !== (shownBars.size === 0)) listen(shownBars.size > 0);
|
|
88
|
+
scheduleClearance();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Keeps the toaster clear of the calling bar while `isShown` holds and the
|
|
93
|
+
* bar sits on the bottom edge of the viewport: the toast stack rises above
|
|
94
|
+
* it instead of covering its buttons, and drops back once it hides.
|
|
95
|
+
*/
|
|
96
|
+
export function trackToasterClearance(
|
|
97
|
+
element: BarElement,
|
|
98
|
+
isShown: () => boolean,
|
|
99
|
+
): void {
|
|
100
|
+
const id = Symbol("toaster-clearance");
|
|
101
|
+
let isMounted = false;
|
|
102
|
+
const stop = watch(
|
|
103
|
+
isShown,
|
|
104
|
+
(shown) => {
|
|
105
|
+
if (isMounted) setBarShown(id, element, shown);
|
|
106
|
+
},
|
|
107
|
+
{ flush: "post" },
|
|
108
|
+
);
|
|
109
|
+
onMounted(() => {
|
|
110
|
+
isMounted = true;
|
|
111
|
+
setBarShown(id, element, isShown());
|
|
112
|
+
});
|
|
113
|
+
onBeforeUnmount(() => {
|
|
114
|
+
stop();
|
|
115
|
+
isMounted = false;
|
|
116
|
+
setBarShown(id, element, false);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -54,6 +54,11 @@ export interface InstantFormOptions {
|
|
|
54
54
|
export interface InstantForm extends InstantSave<FormValues> {
|
|
55
55
|
/** The form loaded or was reset: its values are the confirmed ones. */
|
|
56
56
|
start: () => void;
|
|
57
|
+
/**
|
|
58
|
+
* A field the server stored on its own (a rotated secret): its value is the
|
|
59
|
+
* confirmed one, not a change to save again.
|
|
60
|
+
*/
|
|
61
|
+
confirmField: (id: string, value: unknown) => void;
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
const clone = (value: unknown): unknown =>
|
|
@@ -92,6 +97,12 @@ export function useInstantForm(options: InstantFormOptions): InstantForm {
|
|
|
92
97
|
instant.confirm(values);
|
|
93
98
|
}
|
|
94
99
|
|
|
100
|
+
function confirmField(id: string, value: unknown): void {
|
|
101
|
+
const confirmed = clone(value);
|
|
102
|
+
shown.set(id, confirmed);
|
|
103
|
+
instant.confirm({ [id]: confirmed });
|
|
104
|
+
}
|
|
105
|
+
|
|
95
106
|
async function saveField(field: FormField, value: unknown): Promise<void> {
|
|
96
107
|
const check = (checks.get(field.id) ?? 0) + 1;
|
|
97
108
|
checks.set(field.id, check);
|
|
@@ -121,5 +132,5 @@ export function useInstantForm(options: InstantFormOptions): InstantForm {
|
|
|
121
132
|
{ deep: true },
|
|
122
133
|
);
|
|
123
134
|
|
|
124
|
-
return { ...instant, start };
|
|
135
|
+
return { ...instant, start, confirmField };
|
|
125
136
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
computed,
|
|
3
|
+
getCurrentInstance,
|
|
3
4
|
getCurrentScope,
|
|
5
|
+
inject,
|
|
4
6
|
onScopeDispose,
|
|
7
|
+
provide,
|
|
5
8
|
shallowRef,
|
|
6
9
|
type ComputedRef,
|
|
10
|
+
type InjectionKey,
|
|
7
11
|
} from "vue";
|
|
8
12
|
import type {
|
|
9
13
|
LiveSubscription,
|
|
10
14
|
PageLiveController,
|
|
11
15
|
} from "./pageLiveController";
|
|
16
|
+
import { markPageRead } from "./pageReadClock";
|
|
12
17
|
|
|
13
18
|
// Client-only state: the page renderer sets it in the browser, where one app
|
|
14
19
|
// shows one page at a time. The server renders no event and no refetch, so it
|
|
@@ -36,6 +41,34 @@ export function setCurrentPageLive(controller: PageLiveController | null) {
|
|
|
36
41
|
currentController.value = controller;
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
// Server only: whether the page being rendered declares `live`. The browser
|
|
45
|
+
// installs the page's controller before its blocks set up, so they hydrate
|
|
46
|
+
// as on a live page; the server has no controller, and tells them instead.
|
|
47
|
+
const SERVER_PAGE_LIVE_KEY: InjectionKey<() => boolean> = Symbol(
|
|
48
|
+
"dms-server-page-live",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Tells the blocks of the page the server renders whether it declares `live`.
|
|
53
|
+
* The page renderer calls it.
|
|
54
|
+
*
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export function provideServerPageLive(isLive: () => boolean): void {
|
|
58
|
+
provide(SERVER_PAGE_LIVE_KEY, isLive);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* While server-rendering, whether the page declares `live`; `false` in the
|
|
63
|
+
* browser, where `currentPageLive()` answers. Call it in a component setup.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export function isServerPageLive(): boolean {
|
|
68
|
+
if (typeof window !== "undefined" || !getCurrentInstance()) return false;
|
|
69
|
+
return inject(SERVER_PAGE_LIVE_KEY, null)?.() ?? false;
|
|
70
|
+
}
|
|
71
|
+
|
|
39
72
|
/** @internal */
|
|
40
73
|
export interface LiveGate {
|
|
41
74
|
/** An event behind the block arrived: refetch, now or later, or count it. */
|
|
@@ -62,7 +95,7 @@ interface BoundSubscription {
|
|
|
62
95
|
|
|
63
96
|
/**
|
|
64
97
|
* The gate a block's refetch goes through. On a page without `live` it calls
|
|
65
|
-
* `refresh` at once, as before. Bound lazily, on the first event, to the
|
|
98
|
+
* `refresh` at once, as before, and records the read (`pageReadAt`). Bound lazily, on the first event, to the
|
|
66
99
|
* controller of the page on screen: a block of the page layout sets up before
|
|
67
100
|
* the page installs its controller.
|
|
68
101
|
*
|
|
@@ -89,6 +122,11 @@ export function useLiveGate(refresh: () => void): LiveGate {
|
|
|
89
122
|
|
|
90
123
|
if (getCurrentScope()) onScopeDispose(unbind);
|
|
91
124
|
|
|
125
|
+
const refreshUngated = () => {
|
|
126
|
+
markPageRead();
|
|
127
|
+
refresh();
|
|
128
|
+
};
|
|
129
|
+
|
|
92
130
|
const current = () =>
|
|
93
131
|
bound.value?.controller === currentController.value
|
|
94
132
|
? (bound.value?.subscription ?? null)
|
|
@@ -98,14 +136,14 @@ export function useLiveGate(refresh: () => void): LiveGate {
|
|
|
98
136
|
trigger: (event) => {
|
|
99
137
|
const subscription = resolve();
|
|
100
138
|
if (subscription) subscription.trigger(event);
|
|
101
|
-
else
|
|
139
|
+
else refreshUngated();
|
|
102
140
|
},
|
|
103
141
|
missed: computed(() => current()?.missed.value ?? 0),
|
|
104
142
|
lastReadAt: computed(() => current()?.lastReadAt.value ?? 0),
|
|
105
143
|
catchUp: () => {
|
|
106
144
|
const subscription = resolve();
|
|
107
145
|
if (subscription) subscription.catchUp();
|
|
108
|
-
else
|
|
146
|
+
else refreshUngated();
|
|
109
147
|
},
|
|
110
148
|
acknowledge: () => resolve()?.acknowledge(),
|
|
111
149
|
setHeld: (isHeld) => resolve()?.setHeld(isHeld),
|
|
@@ -9,9 +9,15 @@ import { readonly, ref, type Ref } from "vue";
|
|
|
9
9
|
*/
|
|
10
10
|
export class LivePoller {
|
|
11
11
|
private readonly active = ref(false);
|
|
12
|
+
private readonly hasPolled = ref(false);
|
|
12
13
|
private timer: ReturnType<typeof setTimeout> | null = null;
|
|
13
|
-
/**
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The page reads by itself: the stream stayed down for a whole interval and
|
|
16
|
+
* the first poll ran. Not while a page's stream is still connecting (every
|
|
17
|
+
* full load starts "down" until it opens), which would flash the polling
|
|
18
|
+
* hint for a frame.
|
|
19
|
+
*/
|
|
20
|
+
readonly isPolling: Readonly<Ref<boolean>> = readonly(this.hasPolled);
|
|
15
21
|
|
|
16
22
|
constructor(
|
|
17
23
|
private readonly intervalMs: () => number,
|
|
@@ -31,10 +37,12 @@ export class LivePoller {
|
|
|
31
37
|
if (this.timer) clearTimeout(this.timer);
|
|
32
38
|
this.timer = null;
|
|
33
39
|
this.active.value = false;
|
|
40
|
+
this.hasPolled.value = false;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
private schedule(): void {
|
|
37
44
|
this.timer = setTimeout(() => {
|
|
45
|
+
this.hasPolled.value = true;
|
|
38
46
|
this.poll();
|
|
39
47
|
if (this.active.value) this.schedule();
|
|
40
48
|
}, this.intervalMs());
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Ref, readonly, shallowRef } from "vue";
|
|
2
|
+
|
|
3
|
+
// Client-only, like the live controller: the browser shows one page at a
|
|
4
|
+
// time, and a module-level value on the server would be shared between
|
|
5
|
+
// requests.
|
|
6
|
+
const readAt = shallowRef<number | null>(null);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* When the page on screen last read its data (ms since the epoch), on a page
|
|
10
|
+
* with or without `live`: set when the page is shown, each time its blocks
|
|
11
|
+
* read again outside a live gate, and once a `refreshPageBlocks()` is done.
|
|
12
|
+
* `null` before the first page (and on the server). A live page's own clock
|
|
13
|
+
* is its controller's `lastUpdatedAt`.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export const pageReadAt: Readonly<Ref<number | null>> = readonly(readAt);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Records that the page on screen read its data now. Does nothing on the
|
|
21
|
+
* server.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export function markPageRead(): void {
|
|
26
|
+
if (typeof window === "undefined") return;
|
|
27
|
+
readAt.value = Date.now();
|
|
28
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Switches the page between
|
|
1
|
+
/** Switches the page between Live and Pause, while a LiveToggle is on it. */
|
|
2
2
|
export const LIVE_TOGGLE_L_METADATA: ShortcutMetadata = {
|
|
3
3
|
key: ["l"],
|
|
4
4
|
descriptionKey: "$dms.shortcuts.livetoggle.l.description",
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { onMounted, onScopeDispose, watch } from "vue";
|
|
2
2
|
import type { PageLiveOptions } from "../../../types/live";
|
|
3
|
-
import { setCurrentPageLive } from "./currentPageLive";
|
|
3
|
+
import { provideServerPageLive, setCurrentPageLive } from "./currentPageLive";
|
|
4
4
|
import {
|
|
5
5
|
createPageLiveController,
|
|
6
6
|
type PageLiveController,
|
|
7
7
|
} from "./pageLiveController";
|
|
8
|
+
import { markPageRead } from "./pageReadClock";
|
|
8
9
|
|
|
9
10
|
/** The URL query key opening a live page paused: `?live=0`. */
|
|
10
11
|
const LIVE_URL_KEY = "live";
|
|
@@ -69,7 +70,10 @@ export function bindPageLiveEnvironment(
|
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
72
|
* Gives the page on screen its live controller while it declares `live`, and
|
|
72
|
-
* a new one when the renderer moves to another page
|
|
73
|
+
* a new one when the renderer moves to another page; on every page, starts
|
|
74
|
+
* its read clock (`pageReadAt`) as it is shown. A server render has no
|
|
75
|
+
* controller: it only tells the page's blocks whether the page is live, so
|
|
76
|
+
* they draw what the hydrating browser draws.
|
|
73
77
|
*
|
|
74
78
|
* @internal
|
|
75
79
|
*/
|
|
@@ -77,7 +81,13 @@ export function usePageLiveHost(
|
|
|
77
81
|
source: () => PageLiveSource,
|
|
78
82
|
environment: PageLiveEnvironment = {},
|
|
79
83
|
): void {
|
|
80
|
-
if (typeof window === "undefined")
|
|
84
|
+
if (typeof window === "undefined") {
|
|
85
|
+
provideServerPageLive(() => {
|
|
86
|
+
const { pageId, live } = source();
|
|
87
|
+
return Boolean(pageId && live);
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
81
91
|
let controller: PageLiveController | null = null;
|
|
82
92
|
let unbind: (() => void) | null = null;
|
|
83
93
|
// The server renders every page live: the remembered pause applies once
|
|
@@ -116,5 +126,9 @@ export function usePageLiveHost(
|
|
|
116
126
|
{ immediate: true },
|
|
117
127
|
);
|
|
118
128
|
|
|
129
|
+
// The page's blocks read as it is shown: the server's render, or the
|
|
130
|
+
// browser's on a client navigation.
|
|
131
|
+
watch(() => source().pageId, markPageRead, { immediate: true });
|
|
132
|
+
|
|
119
133
|
onScopeDispose(() => install(null));
|
|
120
134
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComputedRef,
|
|
3
|
+
computed,
|
|
4
|
+
onBeforeUnmount,
|
|
5
|
+
onMounted,
|
|
6
|
+
ref,
|
|
7
|
+
} from "vue";
|
|
8
|
+
import { formatElapsed } from "./formatElapsed";
|
|
9
|
+
|
|
10
|
+
const TICK_MS = 1000;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* "Updated 12 s ago" from the instant `at` gives (ms since the epoch), counted
|
|
14
|
+
* every second once mounted; empty while `at` is `null`. The clock is the
|
|
15
|
+
* browser's: render the label once mounted only.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export function useUpdatedLabel(
|
|
20
|
+
at: () => number | null,
|
|
21
|
+
shouldTick = true,
|
|
22
|
+
): ComputedRef<string> {
|
|
23
|
+
const { t } = useI18n();
|
|
24
|
+
const now = ref(Date.now());
|
|
25
|
+
let ticker: ReturnType<typeof setInterval> | undefined;
|
|
26
|
+
onMounted(() => {
|
|
27
|
+
now.value = Date.now();
|
|
28
|
+
if (shouldTick)
|
|
29
|
+
ticker = setInterval(() => (now.value = Date.now()), TICK_MS);
|
|
30
|
+
});
|
|
31
|
+
onBeforeUnmount(() => clearInterval(ticker));
|
|
32
|
+
return computed(() => {
|
|
33
|
+
const instant = at();
|
|
34
|
+
if (instant === null) return "";
|
|
35
|
+
const { unit, count } = formatElapsed(now.value - instant);
|
|
36
|
+
return t(`dms.live.updated_${unit}`, { count }, count);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
type ComputedRef,
|
|
4
|
+
type InjectionKey,
|
|
5
|
+
inject,
|
|
6
|
+
onBeforeUnmount,
|
|
7
|
+
onMounted,
|
|
8
|
+
provide,
|
|
9
|
+
ref,
|
|
10
|
+
} from "vue";
|
|
11
|
+
import type { PageToolbarSize } from "../../../types/page-toolbar";
|
|
12
|
+
|
|
13
|
+
export type { PageToolbarSize };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* What a `PageToolbar` gives the blocks it holds.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export interface PageToolbarContext {
|
|
21
|
+
/** The size its `LiveToggle` and `PeriodSelector` take unless they set one. */
|
|
22
|
+
size: ComputedRef<PageToolbarSize>;
|
|
23
|
+
/** A `LiveToggle` is mounted in it: the toolbar leaves the updated label to it. */
|
|
24
|
+
hasLiveToggle: ComputedRef<boolean>;
|
|
25
|
+
/** Counts a mounted `LiveToggle`; returns the uncount. */
|
|
26
|
+
registerLiveToggle(): () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const PAGE_TOOLBAR_KEY: InjectionKey<PageToolbarContext> =
|
|
30
|
+
Symbol("dms-page-toolbar");
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Provides the context of a `PageToolbar` to the blocks it holds.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function providePageToolbar(
|
|
38
|
+
size: ComputedRef<PageToolbarSize>,
|
|
39
|
+
): PageToolbarContext {
|
|
40
|
+
const liveToggles = ref(0);
|
|
41
|
+
const context: PageToolbarContext = {
|
|
42
|
+
size,
|
|
43
|
+
hasLiveToggle: computed(() => liveToggles.value > 0),
|
|
44
|
+
registerLiveToggle() {
|
|
45
|
+
liveToggles.value += 1;
|
|
46
|
+
return () => {
|
|
47
|
+
liveToggles.value -= 1;
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
provide(PAGE_TOOLBAR_KEY, context);
|
|
52
|
+
return context;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The `PageToolbar` holding the calling block, `null` outside one.
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export function usePageToolbar(): PageToolbarContext | null {
|
|
61
|
+
return inject(PAGE_TOOLBAR_KEY, null);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Tells the toolbar holding the calling `LiveToggle`, if any, that it is
|
|
66
|
+
* mounted there, for as long as it is.
|
|
67
|
+
*
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
export function useToolbarLiveToggle(toolbar: PageToolbarContext | null): void {
|
|
71
|
+
if (!toolbar) return;
|
|
72
|
+
let unregister: (() => void) | undefined;
|
|
73
|
+
onMounted(() => (unregister = toolbar.registerLiveToggle()));
|
|
74
|
+
onBeforeUnmount(() => unregister?.());
|
|
75
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PageToolbarSize } from "./pageToolbarContext";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The one height every control of a toolbar takes at each size -- the live
|
|
5
|
+
* pill, the segmented presets, the comparison trigger, the refresh button,
|
|
6
|
+
* the labels and a leading chip -- so a bar is as tall with live as without.
|
|
7
|
+
* The segmented track's and Nuxt UI's buttons' own steps (24/28/32 px),
|
|
8
|
+
* written out so a theme's padding cannot change them.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export const PAGE_TOOLBAR_CONTROL_HEIGHT_CLASSES: Record<
|
|
13
|
+
PageToolbarSize,
|
|
14
|
+
string
|
|
15
|
+
> = {
|
|
16
|
+
xs: "h-6",
|
|
17
|
+
sm: "h-7",
|
|
18
|
+
md: "h-8",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The height class of a control drawn at `size`, empty for a size the
|
|
23
|
+
* toolbar has no step for (a period selector's `lg`, `xl`).
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function toolbarControlHeightClass(size: string): string {
|
|
28
|
+
return PAGE_TOOLBAR_CONTROL_HEIGHT_CLASSES[size as PageToolbarSize] ?? "";
|
|
29
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { computed, type ComputedRef } from "vue";
|
|
2
|
+
import { usePeriodScopeComparison } from "#dms-core/app/composables/period/usePeriodScope";
|
|
3
|
+
import type { PeriodComparison } from "#dms-core/app/composables/period/types";
|
|
4
|
+
|
|
5
|
+
const COMPARE_LABEL_KEY_PREFIX = "dms.period.compare_labels";
|
|
6
|
+
const COMPARE_LEGEND_KEY_PREFIX = "dms.period.compare_options";
|
|
7
|
+
// A custom window has no name of its own: its legend keeps the generic one.
|
|
8
|
+
const NAMED_LEGEND_COMPARISONS: PeriodComparison[] = [
|
|
9
|
+
"previous-period",
|
|
10
|
+
"previous-year",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export interface PeriodComparisonDisplay {
|
|
14
|
+
/**
|
|
15
|
+
* The scope compares against nothing: the user picked "no comparison", or
|
|
16
|
+
* the period (every period, `all`) has no window to compare against. A
|
|
17
|
+
* card hides its change, its comparison value and its label. `false` while
|
|
18
|
+
* the card has no scope, or its scope is not registered yet: the card then
|
|
19
|
+
* shows what its data carries.
|
|
20
|
+
*/
|
|
21
|
+
isOff: ComputedRef<boolean>;
|
|
22
|
+
/** "vs previous year", following the scope; `null` without one. */
|
|
23
|
+
label: ComputedRef<string | null>;
|
|
24
|
+
/** "Previous year", a chart legend's name for the comparison series. */
|
|
25
|
+
legendLabel: ComputedRef<string | null>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* What a period-bound card shows of its comparison, following the
|
|
30
|
+
* comparison selected on the scope's `PeriodSelector`.
|
|
31
|
+
*/
|
|
32
|
+
export function usePeriodComparisonDisplay(
|
|
33
|
+
periodScope: string | undefined,
|
|
34
|
+
): PeriodComparisonDisplay {
|
|
35
|
+
const { t } = useI18n();
|
|
36
|
+
const comparison = usePeriodScopeComparison(periodScope);
|
|
37
|
+
const activeComparison = computed(() =>
|
|
38
|
+
comparison.value && comparison.value !== "none" ? comparison.value : null,
|
|
39
|
+
);
|
|
40
|
+
return {
|
|
41
|
+
isOff: computed(() => comparison.value === "none"),
|
|
42
|
+
label: computed(() => {
|
|
43
|
+
const active = activeComparison.value;
|
|
44
|
+
return active ? t(`${COMPARE_LABEL_KEY_PREFIX}.${active}`, active) : null;
|
|
45
|
+
}),
|
|
46
|
+
legendLabel: computed(() => {
|
|
47
|
+
const active = activeComparison.value;
|
|
48
|
+
if (!active || !NAMED_LEGEND_COMPARISONS.includes(active)) return null;
|
|
49
|
+
return t(`${COMPARE_LEGEND_KEY_PREFIX}.${active}`, active);
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
}
|