@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
|
@@ -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
|
+
}
|
|
@@ -182,6 +182,27 @@ export function formatPeriodRange(
|
|
|
182
182
|
return formatRangeWith(range, locale, options);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Day-only range label, "16 – 17 Oct": a comparison menu's trigger names the
|
|
187
|
+
* window it compares with, where the hours would only widen it. A window
|
|
188
|
+
* shorter than a day keeps its times, its day alone telling nothing.
|
|
189
|
+
* `withYear: false` drops the year, as in `formatPeriodRange`.
|
|
190
|
+
*/
|
|
191
|
+
export function formatPeriodDays(
|
|
192
|
+
range: PeriodRange,
|
|
193
|
+
locale: string,
|
|
194
|
+
withYear = true,
|
|
195
|
+
): string {
|
|
196
|
+
const span = range.to.getTime() - range.from.getTime();
|
|
197
|
+
if (span < MS_PER_DAY - MS_PER_HOUR) {
|
|
198
|
+
return formatRangeWith(range, locale, { ...DAY_FORMAT, ...TIME_FORMAT });
|
|
199
|
+
}
|
|
200
|
+
const options: FormatOptions = withYear
|
|
201
|
+
? { ...DAY_FORMAT, year: "numeric" }
|
|
202
|
+
: DAY_FORMAT;
|
|
203
|
+
return formatRangeWith(range, locale, options);
|
|
204
|
+
}
|
|
205
|
+
|
|
185
206
|
export interface PeriodDuration {
|
|
186
207
|
unit: "days" | "hours";
|
|
187
208
|
count: number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PeriodPreset } from "#dms-core/app/composables/period/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Short mono codes, as trails in the preset list and segmented labels: every
|
|
5
|
+
* preset has one, so a row of segments stays as narrow with `all` as without
|
|
6
|
+
* it, where the full label ("All time") widened it. Custom has none: it shows
|
|
7
|
+
* its range. Each is translated under `dms.period.short_presets.<preset>`
|
|
8
|
+
* ("24 h", "7 j" in French); these English codes are the fallback for a
|
|
9
|
+
* locale that lacks one, and the list of presets that have a code.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export const PRESET_SHORT_LABELS: Partial<Record<PeriodPreset, string>> = {
|
|
14
|
+
"last-hour": "1H",
|
|
15
|
+
"last-24h": "24H",
|
|
16
|
+
today: "1D",
|
|
17
|
+
yesterday: "-1D",
|
|
18
|
+
"last-7-days": "7D",
|
|
19
|
+
"last-30-days": "30D",
|
|
20
|
+
"last-90-days": "90D",
|
|
21
|
+
"this-month": "MTD",
|
|
22
|
+
"last-month": "-1M",
|
|
23
|
+
"this-quarter": "QTD",
|
|
24
|
+
"last-quarter": "-1Q",
|
|
25
|
+
ytd: "YTD",
|
|
26
|
+
"last-year": "-1Y",
|
|
27
|
+
all: "ALL",
|
|
28
|
+
};
|