@antelopejs/dms 0.9.7 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/README.md +1 -1
- package/data/module-store/catalog.json +2 -2
- package/dist/attachments/table-view.js +2 -2
- package/dist/attachments/table-view.js.map +1 -1
- package/dist/automation/actions.d.ts +2 -0
- package/dist/automation/actions.js +116 -71
- package/dist/automation/actions.js.map +1 -1
- package/dist/automation/i18n.d.ts +4 -0
- package/dist/automation/i18n.js +19 -0
- package/dist/automation/i18n.js.map +1 -0
- package/dist/automation/send-notification.d.ts +21 -0
- package/dist/automation/send-notification.js +163 -0
- package/dist/automation/send-notification.js.map +1 -0
- package/dist/automation/triggers.js +19 -25
- package/dist/automation/triggers.js.map +1 -1
- package/dist/client-origin-cors.d.ts +1 -0
- package/dist/client-origin-cors.js +19 -0
- package/dist/client-origin-cors.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +5 -3
- package/dist/config.js.map +1 -1
- package/dist/frontend-module.d.ts +4 -0
- package/dist/frontend-module.js +37 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/implementations/dms/client-ip.js +1 -1
- package/dist/implementations/dms/client-ip.js.map +1 -1
- package/dist/implementations/dms/dev-reload.d.ts +5 -0
- package/dist/implementations/dms/dev-reload.js +23 -3
- package/dist/implementations/dms/dev-reload.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +2 -4
- package/dist/implementations/dms/frontend-modules.js +22 -89
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +12 -2
- package/dist/implementations/dms/frontend-ready.js +34 -4
- package/dist/implementations/dms/frontend-ready.js.map +1 -1
- package/dist/implementations/dms/layout-banners.d.ts +2 -1
- package/dist/implementations/dms/layout-banners.js +9 -0
- package/dist/implementations/dms/layout-banners.js.map +1 -1
- package/dist/implementations/dms/manifest.d.ts +1 -2
- package/dist/implementations/dms/manifest.js +2 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +4 -3
- package/dist/implementations/dms/page.js +49 -18
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
- package/dist/implementations/dms/permissions-resolver.js +2 -2
- package/dist/implementations/dms/permissions-resolver.js.map +1 -1
- package/dist/implementations/dms/realtime.js +1 -0
- package/dist/implementations/dms/realtime.js.map +1 -1
- package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
- package/dist/implementations/dms-base/bulk-selection.js +23 -1
- package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
- package/dist/implementations/dms-base/filter-tokens.js +5 -3
- package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
- package/dist/implementations/dms-base/search-route.d.ts +1 -0
- package/dist/implementations/dms-base/search-route.js +35 -13
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/implementations/dms-base/table-view.js +11 -6
- package/dist/implementations/dms-base/table-view.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +2 -2
- package/dist/implementations/dms-notifications/index.js +5 -5
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/index.js +28 -45
- package/dist/index.js.map +1 -1
- package/dist/pages/modules/index.d.ts +1 -0
- package/dist/pages/modules/index.js +1 -0
- package/dist/pages/modules/index.js.map +1 -1
- package/dist/pages/modules/modules-store.d.ts +9 -0
- package/dist/pages/modules/modules-store.js +30 -0
- package/dist/pages/modules/modules-store.js.map +1 -0
- package/dist/pages/settings/users/account-credentials.js +2 -2
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/account-data-store.js +25 -8
- package/dist/pages/settings/users/account-data-store.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.js +9 -3
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/member-management.js +13 -14
- package/dist/pages/settings/users/member-management.js.map +1 -1
- package/dist/pages/settings/users/profile.d.ts +1 -4
- package/dist/pages/settings/users/profile.js +0 -35
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.js +26 -18
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/role-editor.d.ts +0 -1
- package/dist/pages/settings/users/role-editor.js +1 -2
- package/dist/pages/settings/users/role-editor.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +1 -2
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.js +2 -2
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/realtime/broker.d.ts +6 -0
- package/dist/realtime/broker.js +8 -1
- package/dist/realtime/broker.js.map +1 -1
- package/dist/realtime/presence.d.ts +8 -3
- package/dist/realtime/presence.js +8 -4
- package/dist/realtime/presence.js.map +1 -1
- package/dist/realtime/sessions.d.ts +11 -1
- package/dist/realtime/sessions.js +21 -3
- package/dist/realtime/sessions.js.map +1 -1
- package/dist/realtime/table-view.d.ts +2 -2
- package/dist/realtime/table-view.js +4 -3
- package/dist/realtime/table-view.js.map +1 -1
- package/dist/routes/auth/client-ip.d.ts +1 -0
- package/dist/routes/auth/client-ip.js +18 -0
- package/dist/routes/auth/client-ip.js.map +1 -1
- package/dist/routes/auth/oauth/config.d.ts +2 -1
- package/dist/routes/auth/oauth/config.js +5 -1
- package/dist/routes/auth/oauth/config.js.map +1 -1
- package/dist/routes/auth/session-handoff.d.ts +0 -1
- package/dist/routes/auth/session-handoff.js +0 -2
- package/dist/routes/auth/session-handoff.js.map +1 -1
- package/dist/routes/auth.js +3 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/realtime.d.ts +13 -1
- package/dist/routes/realtime.js +114 -29
- package/dist/routes/realtime.js.map +1 -1
- package/dist/startup-config.d.ts +2 -0
- package/dist/startup-config.js +62 -0
- package/dist/startup-config.js.map +1 -0
- package/dist/store/interfaces.d.ts +3 -2
- package/dist/store/interfaces.js +16 -6
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/listing.js +24 -13
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +4 -0
- package/dist/store/operations.js.map +1 -1
- package/dist/store/requirements.js +3 -2
- package/dist/store/requirements.js.map +1 -1
- package/dist/store/runtime.d.ts +10 -1
- package/dist/store/runtime.js +33 -6
- package/dist/store/runtime.js.map +1 -1
- package/dist/store/types.d.ts +10 -0
- package/dist/validation/auth.schema.d.ts +2 -2
- package/dist/validation/member-invite.schema.d.ts +2 -10
- package/dist/validation/member-invite.schema.js +2 -15
- package/dist/validation/member-invite.schema.js.map +1 -1
- package/frontend-vue/dms.frontend.ts +24 -5
- package/frontend-vue/dms.server.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
- package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
- package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
- package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
- package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
- package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
- package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
- package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
- package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
- package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
- package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
- package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
- package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
- package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
- package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
- package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
- package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
- package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
- package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
- package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
- package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
- package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
- package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
- package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
- package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
- package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
- package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
- package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
- package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
- package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
- package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
- package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
- package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
- package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
- package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
- package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
- package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
- package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
- package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
- package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
- package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
- package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
- package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
- package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
- package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
- package/frontend-vue/package.json +2 -2
- package/package.json +7 -6
- package/skills/dms/SKILL.md +1 -1
- package/skills/dms-auth/SKILL.md +5 -3
- package/skills/dms-best-practices/REFERENCE.md +2 -0
- package/skills/dms-best-practices/SKILL.md +4 -0
- package/skills/dms-module/SKILL.md +3 -2
- package/skills/dms-pages/REFERENCE.md +125 -16
- package/skills/dms-pages/SKILL.md +1 -1
- package/skills/dms-project/DEV-SERVERS.md +23 -1
- package/skills/dms-project/REFERENCE.md +1 -1
- package/skills/dms-project/SKILL.md +4 -1
- package/skills/dms-project/scripts/dms-dev.sh +205 -17
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
/** An input component, with the options its field declares. */
|
|
2
|
+
interface ComponentWithOptions {
|
|
3
|
+
options?: object;
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
/** A field whose input component carries options. */
|
|
2
7
|
interface FieldWithOptions {
|
|
3
|
-
component:
|
|
8
|
+
component: ComponentWithOptions;
|
|
4
9
|
}
|
|
5
10
|
|
|
6
11
|
type Translate = (key: string) => string;
|
|
@@ -23,6 +28,9 @@ const ENTRY_TEXTS = ["label", "description"] as const;
|
|
|
23
28
|
|
|
24
29
|
const isText = (value: unknown): value is string => typeof value === "string";
|
|
25
30
|
|
|
31
|
+
const isOptions = (value: unknown): value is Options =>
|
|
32
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
+
|
|
26
34
|
function translateTexts(
|
|
27
35
|
source: Options,
|
|
28
36
|
keys: readonly string[],
|
|
@@ -36,25 +44,57 @@ function translateTexts(
|
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* its options
|
|
47
|
+
* An entry holding a field of its own (a repeater's column, a key-value
|
|
48
|
+
* editor's value) has the options of that field's component translated too,
|
|
49
|
+
* so a select inside a repeater shows its options in the reader's language.
|
|
42
50
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
function translateNestedField(entry: Options, processI18n: Translate): Options {
|
|
52
|
+
if (!isOptions(entry.component)) return entry;
|
|
53
|
+
return {
|
|
54
|
+
...entry,
|
|
55
|
+
component: translateComponent(entry.component, processI18n),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function translateComponent<C extends ComponentWithOptions>(
|
|
60
|
+
component: C,
|
|
45
61
|
processI18n: Translate,
|
|
46
|
-
):
|
|
47
|
-
const opts =
|
|
48
|
-
if (!opts) return
|
|
62
|
+
): C {
|
|
63
|
+
const opts = component.options as Options | undefined;
|
|
64
|
+
if (!opts) return component;
|
|
49
65
|
|
|
50
66
|
const options = translateTexts(opts, TEXT_OPTIONS, processI18n);
|
|
51
67
|
for (const key of LIST_OPTIONS) {
|
|
52
68
|
const list = opts[key];
|
|
53
69
|
if (!Array.isArray(list)) continue;
|
|
54
70
|
options[key] = list.map((entry: Options) =>
|
|
55
|
-
|
|
71
|
+
translateNestedField(
|
|
72
|
+
translateTexts(entry, ENTRY_TEXTS, processI18n),
|
|
73
|
+
processI18n,
|
|
74
|
+
),
|
|
56
75
|
);
|
|
57
76
|
}
|
|
77
|
+
if (isOptions(opts.value)) {
|
|
78
|
+
options.value = translateNestedField(opts.value, processI18n);
|
|
79
|
+
}
|
|
58
80
|
|
|
59
|
-
return { ...
|
|
81
|
+
return { ...component, options };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A field with the texts of its input component's options translated
|
|
86
|
+
* (`$`-prefixed i18n keys): placeholder, labels, descriptions, and those of
|
|
87
|
+
* its options and columns, the way forms show them. The fields nested in it
|
|
88
|
+
* (a repeater's columns, a key-value editor's value) are translated the same
|
|
89
|
+
* way, at any depth.
|
|
90
|
+
*/
|
|
91
|
+
export function processFieldI18n<F extends FieldWithOptions>(
|
|
92
|
+
field: F,
|
|
93
|
+
processI18n: Translate,
|
|
94
|
+
): F {
|
|
95
|
+
if (!field.component.options) return field;
|
|
96
|
+
return {
|
|
97
|
+
...field,
|
|
98
|
+
component: translateComponent(field.component, processI18n),
|
|
99
|
+
};
|
|
60
100
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How wide a single-line input sits in its row: `"compact"` for a short
|
|
3
|
+
* value, `"full"` to fill the row. Left out, the input's bounds decide.
|
|
4
|
+
*/
|
|
5
|
+
export type InputWidth = "compact" | "full";
|
|
6
|
+
|
|
7
|
+
/** The widest number, in digits, a number input sizes compact by itself. */
|
|
8
|
+
const COMPACT_NUMBER_DIGITS = 6;
|
|
9
|
+
/** The longest `maxLength` a text input sizes compact by itself. */
|
|
10
|
+
const COMPACT_TEXT_LENGTH = 16;
|
|
11
|
+
|
|
12
|
+
interface NumberWidthOptions {
|
|
13
|
+
width?: InputWidth;
|
|
14
|
+
min?: number;
|
|
15
|
+
max?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface TextWidthOptions {
|
|
19
|
+
width?: InputWidth;
|
|
20
|
+
maxLength?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function integerDigits(value: number): number {
|
|
24
|
+
return String(Math.trunc(Math.abs(value))).length;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether a number input takes a compact width: asked for, or left out with
|
|
29
|
+
* a `max` (and `min`) that keep the value within a few digits. Without a
|
|
30
|
+
* `max` nothing bounds the value, so it fills the row.
|
|
31
|
+
*/
|
|
32
|
+
export function isCompactNumberInput({
|
|
33
|
+
width,
|
|
34
|
+
min,
|
|
35
|
+
max,
|
|
36
|
+
}: NumberWidthOptions): boolean {
|
|
37
|
+
if (width) return width === "compact";
|
|
38
|
+
if (max === undefined || !Number.isFinite(max)) return false;
|
|
39
|
+
const bounds = min === undefined ? [max] : [min, max];
|
|
40
|
+
return Math.max(...bounds.map(integerDigits)) <= COMPACT_NUMBER_DIGITS;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether a text input takes a compact width: asked for, or left out with a
|
|
45
|
+
* `maxLength` short enough for a code or an abbreviation.
|
|
46
|
+
*/
|
|
47
|
+
export function isCompactTextInput({
|
|
48
|
+
width,
|
|
49
|
+
maxLength,
|
|
50
|
+
}: TextWidthOptions): boolean {
|
|
51
|
+
if (width) return width === "compact";
|
|
52
|
+
return maxLength !== undefined && maxLength <= COMPACT_TEXT_LENGTH;
|
|
53
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readableNumberFormat } from "#dms-core/app/utils/formatter";
|
|
1
2
|
import type {
|
|
2
3
|
InputNumberAdjustment,
|
|
3
4
|
InputNumberAdjustmentReason,
|
|
@@ -15,7 +16,7 @@ const WHITESPACE = /\s/g;
|
|
|
15
16
|
const NOT_NUMERIC = /[^\d.+-]/g;
|
|
16
17
|
|
|
17
18
|
function separatorsOf(locale: string): { group?: string; decimal: string } {
|
|
18
|
-
const parts =
|
|
19
|
+
const parts = readableNumberFormat(locale).formatToParts(SEPARATOR_SAMPLE);
|
|
19
20
|
return {
|
|
20
21
|
group: parts.find((part) => part.type === "group")?.value,
|
|
21
22
|
decimal: parts.find((part) => part.type === "decimal")?.value ?? ".",
|
|
@@ -23,8 +24,10 @@ function separatorsOf(locale: string): { group?: string; decimal: string } {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
* A number as typed in a field written in `locale` ("1,020", "1 020,5")
|
|
27
|
-
*
|
|
27
|
+
* A number as typed in a field written in `locale` ("1,020", "1 020,5"),
|
|
28
|
+
* its digits grouped by any space (the narrow one the field draws in French,
|
|
29
|
+
* the no-break one the cells write, a typed one): `null` when nothing is
|
|
30
|
+
* typed or it does not read as a number.
|
|
28
31
|
*/
|
|
29
32
|
export function parseTypedNumber(text: string, locale: string): number | null {
|
|
30
33
|
const { group, decimal } = separatorsOf(locale);
|
|
@@ -19,15 +19,29 @@ export function formatRelativeDate(
|
|
|
19
19
|
);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const DAY_MONTH_FORMAT: Intl.DateTimeFormatOptions = {
|
|
23
|
+
month: "short",
|
|
24
|
+
day: "numeric",
|
|
25
|
+
};
|
|
26
|
+
const YEAR_FORMAT: Intl.DateTimeFormatOptions = { year: "numeric" };
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* "Sep 27": day and short month, read against `now`; a date of another year
|
|
30
|
+
* ("Apr 2, 2018") names its year as well.
|
|
31
|
+
*/
|
|
23
32
|
export function formatDayMonth(
|
|
24
33
|
date: string | number | Date,
|
|
25
34
|
locale: string,
|
|
35
|
+
now: number = Date.now(),
|
|
26
36
|
): string {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
const day = new Date(date);
|
|
38
|
+
// Years read in the reader's time zone, as the date is written.
|
|
39
|
+
const year = regionalDateTimeFormat(locale, YEAR_FORMAT);
|
|
40
|
+
const isOtherYear = year.format(day) !== year.format(now);
|
|
41
|
+
return regionalDateTimeFormat(
|
|
42
|
+
locale,
|
|
43
|
+
isOtherYear ? { ...DAY_MONTH_FORMAT, ...YEAR_FORMAT } : DAY_MONTH_FORMAT,
|
|
44
|
+
).format(day);
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
/** First word of a name, for "Sep 27 · by Camille". */
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// `scrollIntoView` scrolls every ancestor that can scroll, including the
|
|
2
|
+
// layout's `overflow: hidden` root: the header and breadcrumb slide out of
|
|
3
|
+
// view and nothing brings them back. These helpers scroll the one container
|
|
4
|
+
// that actually scrolls the element instead.
|
|
5
|
+
|
|
6
|
+
const SCROLLABLE_OVERFLOW = /(auto|scroll)/;
|
|
7
|
+
|
|
8
|
+
/** Where an element lands in its scroll container. */
|
|
9
|
+
export type ScrollWithinBlock = "start" | "center" | "nearest";
|
|
10
|
+
|
|
11
|
+
/** How `scrollWithin` brings an element into view. */
|
|
12
|
+
export interface ScrollWithinOptions {
|
|
13
|
+
block?: ScrollWithinBlock;
|
|
14
|
+
behavior?: ScrollBehavior;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The closest ancestor of `element` the user can scroll (`overflow-y: auto`
|
|
19
|
+
* or `scroll`, with content taller than itself), or `null` when only the
|
|
20
|
+
* window scrolls it.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export function scrollParent(element: HTMLElement): HTMLElement | null {
|
|
25
|
+
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
26
|
+
const { overflowY } = getComputedStyle(node);
|
|
27
|
+
if (
|
|
28
|
+
SCROLLABLE_OVERFLOW.test(overflowY) &&
|
|
29
|
+
node.scrollHeight > node.clientHeight
|
|
30
|
+
) {
|
|
31
|
+
return node;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ScrollViewport {
|
|
38
|
+
top: number;
|
|
39
|
+
height: number;
|
|
40
|
+
scrollTop: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function viewportOf(scroller: HTMLElement | null): ScrollViewport {
|
|
44
|
+
if (scroller) {
|
|
45
|
+
return {
|
|
46
|
+
top: scroller.getBoundingClientRect().top,
|
|
47
|
+
height: scroller.clientHeight,
|
|
48
|
+
scrollTop: scroller.scrollTop,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return { top: 0, height: window.innerHeight, scrollTop: window.scrollY };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function scrollMarginTop(element: HTMLElement): number {
|
|
55
|
+
return Number.parseFloat(getComputedStyle(element).scrollMarginTop) || 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The scroll offset that puts `rect` at `block` in `viewport`. */
|
|
59
|
+
function targetOffset(
|
|
60
|
+
rect: DOMRect,
|
|
61
|
+
margin: number,
|
|
62
|
+
viewport: ScrollViewport,
|
|
63
|
+
block: ScrollWithinBlock,
|
|
64
|
+
): number | null {
|
|
65
|
+
const top = rect.top - viewport.top - margin;
|
|
66
|
+
if (block === "start") return viewport.scrollTop + top;
|
|
67
|
+
if (block === "center") {
|
|
68
|
+
return viewport.scrollTop + top - (viewport.height - rect.height) / 2;
|
|
69
|
+
}
|
|
70
|
+
const bottom = rect.bottom - viewport.top;
|
|
71
|
+
if (top >= 0 && bottom <= viewport.height) return null;
|
|
72
|
+
return top < 0
|
|
73
|
+
? viewport.scrollTop + top
|
|
74
|
+
: viewport.scrollTop + bottom - viewport.height;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Brings `element` into view by scrolling its own scroll container only
|
|
79
|
+
* (the window when nothing closer scrolls it), honouring its
|
|
80
|
+
* `scroll-margin-top`. Unlike `scrollIntoView`, no other ancestor moves.
|
|
81
|
+
*
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
export function scrollWithin(
|
|
85
|
+
element: HTMLElement,
|
|
86
|
+
{ block = "start", behavior = "auto" }: ScrollWithinOptions = {},
|
|
87
|
+
): void {
|
|
88
|
+
const scroller = scrollParent(element);
|
|
89
|
+
const offset = targetOffset(
|
|
90
|
+
element.getBoundingClientRect(),
|
|
91
|
+
block === "start" ? scrollMarginTop(element) : 0,
|
|
92
|
+
viewportOf(scroller),
|
|
93
|
+
block,
|
|
94
|
+
);
|
|
95
|
+
if (offset === null) return;
|
|
96
|
+
const top = Math.max(0, offset);
|
|
97
|
+
if (scroller) scroller.scrollTo({ top, behavior });
|
|
98
|
+
else window.scrollTo({ top, behavior });
|
|
99
|
+
}
|
|
@@ -154,19 +154,32 @@ const FRAGMENT_SEPARATOR = "#";
|
|
|
154
154
|
const PAIR_SEPARATOR = "&";
|
|
155
155
|
const VALUE_SEPARATOR = "=";
|
|
156
156
|
|
|
157
|
-
/** The query of a URL without the pairs whose whole value
|
|
158
|
-
function
|
|
157
|
+
/** The query of a URL without the pairs whose whole value matches `value`. */
|
|
158
|
+
function dropPairs(search: string, value: RegExp): string {
|
|
159
159
|
return search
|
|
160
160
|
.split(PAIR_SEPARATOR)
|
|
161
161
|
.filter((pair) => {
|
|
162
|
-
const
|
|
163
|
-
return (
|
|
164
|
-
!pair.includes(VALUE_SEPARATOR) || !UNRESOLVED_ROUTE_VALUE.test(value)
|
|
165
|
-
);
|
|
162
|
+
const pairValue = pair.slice(pair.indexOf(VALUE_SEPARATOR) + 1);
|
|
163
|
+
return !pair.includes(VALUE_SEPARATOR) || !value.test(pairValue);
|
|
166
164
|
})
|
|
167
165
|
.join(PAIR_SEPARATOR);
|
|
168
166
|
}
|
|
169
167
|
|
|
168
|
+
/**
|
|
169
|
+
* A URL without its `token`s: a query pair whose whole value is one (`value`)
|
|
170
|
+
* is left out, any other is left empty.
|
|
171
|
+
*/
|
|
172
|
+
function stripTokens(url: string, token: RegExp, value: RegExp): string {
|
|
173
|
+
const [beforeFragment = "", ...fragment] = url.split(FRAGMENT_SEPARATOR);
|
|
174
|
+
const [path = "", ...search] = beforeFragment.split(QUERY_SEPARATOR);
|
|
175
|
+
const query = dropPairs(search.join(QUERY_SEPARATOR), value);
|
|
176
|
+
const rebuilt = [
|
|
177
|
+
query ? `${path}${QUERY_SEPARATOR}${query}` : path,
|
|
178
|
+
...fragment,
|
|
179
|
+
].join(FRAGMENT_SEPARATOR);
|
|
180
|
+
return rebuilt.replace(token, "");
|
|
181
|
+
}
|
|
182
|
+
|
|
170
183
|
/**
|
|
171
184
|
* A link's URL with the `{{params.X}}` / `{{query.X}}` tokens of the page it
|
|
172
185
|
* sits on filled, for a button that always leads somewhere: a query pair
|
|
@@ -180,12 +193,21 @@ export function resolveLinkVariables(
|
|
|
180
193
|
): string {
|
|
181
194
|
const filled = replaceUrlVariables(url, context);
|
|
182
195
|
if (!hasUrlVariables(filled)) return filled;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
196
|
+
return stripTokens(filled, UNRESOLVED_ROUTE_TOKEN, UNRESOLVED_ROUTE_VALUE);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// A row field, `{{row.<path>}}`, as `interpolateUrl` fills it.
|
|
200
|
+
const ROW_KEY = String.raw`row\.[^{}\s]+`;
|
|
201
|
+
const ROW_TOKEN = tokenPattern(ROW_KEY);
|
|
202
|
+
const ROW_VALUE = new RegExp(`^${tokenPattern(ROW_KEY, "").source}$`);
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* A URL sent without a row (a bulk action on the selection bar) with its
|
|
206
|
+
* `{{row.X}}` tokens taken out as a link's unresolved page tokens are: a query
|
|
207
|
+
* pair whose whole value is one is left out (`?ids={{row._id}}` gives way to
|
|
208
|
+
* the selection's `ids`), any other is left empty.
|
|
209
|
+
*/
|
|
210
|
+
export function dropRowTokens(url: string): string {
|
|
211
|
+
if (!new RegExp(ROW_TOKEN.source).test(url)) return url;
|
|
212
|
+
return stripTokens(url, ROW_TOKEN, ROW_VALUE);
|
|
191
213
|
}
|
|
@@ -20,6 +20,11 @@ interface Props {
|
|
|
20
20
|
* a role preview veil this block; without it the block is never veiled.
|
|
21
21
|
*/
|
|
22
22
|
layoutPath?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Props handed to this block only, as they are (functions included), over
|
|
25
|
+
* its options: what an action target's container gives its block.
|
|
26
|
+
*/
|
|
27
|
+
rootProps?: Record<string, unknown>;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
const props = defineProps<Props>();
|
|
@@ -108,7 +113,10 @@ provide<GridDeclaredColumns>(GRID_DECLARED_COLUMNS, {
|
|
|
108
113
|
<Component
|
|
109
114
|
:is="component.component"
|
|
110
115
|
:ref="anchorBlock"
|
|
111
|
-
v-bind="
|
|
116
|
+
v-bind="{
|
|
117
|
+
...JSON.parse(JSON.stringify(component.options || {})),
|
|
118
|
+
...rootProps,
|
|
119
|
+
}"
|
|
112
120
|
:page-id="pageId"
|
|
113
121
|
:component-id="componentId"
|
|
114
122
|
:child-count="columnCount"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed,
|
|
2
|
+
import { computed, onMounted, ref } from "vue";
|
|
3
3
|
import DmsSegmented from "../segmented/Segmented.vue";
|
|
4
4
|
import { usePageLive } from "../../composables/live/usePageLive";
|
|
5
|
-
import {
|
|
5
|
+
import { useUpdatedLabel } from "../../build/composables/live/useUpdatedLabel";
|
|
6
6
|
import { useLiveToggleShortcut } from "../../build/composables/live/useLiveToggleShortcut";
|
|
7
7
|
import { HIDDEN_TAB_NOTE } from "../../build/composables/live/pageLiveController";
|
|
8
8
|
import type { ConfirmTranslate } from "../../build/composables/confirm/confirmDialogTexts";
|
|
@@ -10,14 +10,24 @@ import {
|
|
|
10
10
|
missedRowsText,
|
|
11
11
|
showLiveNote,
|
|
12
12
|
} from "../../build/composables/live/liveNotes";
|
|
13
|
+
import {
|
|
14
|
+
type PageToolbarSize,
|
|
15
|
+
usePageToolbar,
|
|
16
|
+
useToolbarLiveToggle,
|
|
17
|
+
} from "../../build/composables/page-toolbar/pageToolbarContext";
|
|
18
|
+
import { PAGE_TOOLBAR_CONTROL_HEIGHT_CLASSES } from "../../build/composables/page-toolbar/toolbarControlHeight";
|
|
13
19
|
import type { DefaultComponentProps } from "../../../../dms-core/app/types/component";
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
// Its ids are optional: it reads the page's live state, not its own, and a
|
|
22
|
+
// component may draw it by hand inside its own `PageToolbar`.
|
|
23
|
+
interface LiveToggleBlockProps extends Partial<DefaultComponentProps> {
|
|
16
24
|
align?: "left" | "center" | "right";
|
|
17
25
|
/** Shows "updated N s ago". */
|
|
18
26
|
showUpdatedAt?: boolean;
|
|
19
27
|
/** `segmented` (Live / Pause segments) or `pill` (a pulsing Live pill). */
|
|
20
28
|
variant?: "segmented" | "pill";
|
|
29
|
+
/** How large it is drawn; the holding `PageToolbar`'s size, else `sm`. */
|
|
30
|
+
size?: PageToolbarSize;
|
|
21
31
|
/** What the paused toggle calls the missed events, given `{count}`. */
|
|
22
32
|
missedLabel?: string;
|
|
23
33
|
}
|
|
@@ -26,12 +36,13 @@ const props = withDefaults(defineProps<LiveToggleBlockProps>(), {
|
|
|
26
36
|
align: "right",
|
|
27
37
|
showUpdatedAt: true,
|
|
28
38
|
variant: "segmented",
|
|
39
|
+
size: undefined,
|
|
29
40
|
missedLabel: undefined,
|
|
30
41
|
});
|
|
31
42
|
|
|
32
43
|
const DIRECT = "direct";
|
|
33
44
|
const PAUSE = "pause";
|
|
34
|
-
const
|
|
45
|
+
const DEFAULT_SIZE: PageToolbarSize = "sm";
|
|
35
46
|
const ALIGN_CLASSES: Record<
|
|
36
47
|
NonNullable<LiveToggleBlockProps["align"]>,
|
|
37
48
|
string
|
|
@@ -41,26 +52,53 @@ const ALIGN_CLASSES: Record<
|
|
|
41
52
|
right: "justify-end",
|
|
42
53
|
};
|
|
43
54
|
const PILL_BUTTON_CLASS =
|
|
44
|
-
"focus-visible:outline-primary flex items-center
|
|
55
|
+
"focus-visible:outline-primary flex h-full items-center rounded-[5px] font-medium transition-colors focus-visible:outline-2";
|
|
45
56
|
const PILL_IDLE_CLASS = "text-muted hover:text-default hover:bg-elevated";
|
|
46
57
|
|
|
58
|
+
/** What each size changes: the texts and the pill's buttons. */
|
|
59
|
+
interface LiveToggleSizeClasses {
|
|
60
|
+
text: string;
|
|
61
|
+
pillButton: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The pill takes the toolbar's one height (its buttons fill it), as the
|
|
65
|
+
// period selector's segments beside it and the Resume button do.
|
|
66
|
+
const SIZE_CLASSES: Record<PageToolbarSize, LiveToggleSizeClasses> = {
|
|
67
|
+
xs: {
|
|
68
|
+
text: "text-[11.5px]",
|
|
69
|
+
pillButton: "gap-1 px-1.5 text-[11px]",
|
|
70
|
+
},
|
|
71
|
+
sm: {
|
|
72
|
+
text: "text-[12.5px]",
|
|
73
|
+
pillButton: "gap-1.5 px-2 text-[12px]",
|
|
74
|
+
},
|
|
75
|
+
md: {
|
|
76
|
+
text: "text-[13px]",
|
|
77
|
+
pillButton: "gap-1.5 px-2.5 text-[13px]",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
47
81
|
const { t } = useI18n();
|
|
48
82
|
const translate = t as ConfirmTranslate;
|
|
49
83
|
const live = usePageLive();
|
|
84
|
+
const toolbar = usePageToolbar();
|
|
85
|
+
useToolbarLiveToggle(toolbar);
|
|
86
|
+
const size = computed(() => props.size ?? toolbar?.size.value ?? DEFAULT_SIZE);
|
|
87
|
+
const sizeClasses = computed(() => SIZE_CLASSES[size.value]);
|
|
88
|
+
const heightClass = computed(
|
|
89
|
+
() => PAGE_TOOLBAR_CONTROL_HEIGHT_CLASSES[size.value],
|
|
90
|
+
);
|
|
50
91
|
|
|
51
|
-
// The
|
|
52
|
-
//
|
|
92
|
+
// The toggle is drawn from the server on, live (the remembered pause applies
|
|
93
|
+
// once mounted), so it takes its place before the page paints. Only the
|
|
94
|
+
// "updated N s ago" text waits for the browser's clock: the server's would
|
|
95
|
+
// not match the hydrating render's.
|
|
53
96
|
const isMounted = ref(false);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (props.showUpdatedAt) {
|
|
60
|
-
ticker = setInterval(() => (now.value = Date.now()), TICK_MS);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
onBeforeUnmount(() => clearInterval(ticker));
|
|
97
|
+
onMounted(() => (isMounted.value = true));
|
|
98
|
+
const updatedText = useUpdatedLabel(
|
|
99
|
+
() => live.lastUpdatedAt.value,
|
|
100
|
+
props.showUpdatedAt,
|
|
101
|
+
);
|
|
64
102
|
|
|
65
103
|
const mode = computed({
|
|
66
104
|
get: () => (live.isPaused.value ? PAUSE : DIRECT),
|
|
@@ -75,15 +113,9 @@ const items = computed(() => [
|
|
|
75
113
|
{ value: PAUSE, label: t("dms.live.pause"), icon: "i-ph-pause" },
|
|
76
114
|
]);
|
|
77
115
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const { unit, count } = formatElapsed(now.value - at);
|
|
82
|
-
const updated = t(`dms.live.updated_${unit}`, { count }, count);
|
|
83
|
-
return live.isPolling.value
|
|
84
|
-
? `${updated} · ${t("dms.live.polling")}`
|
|
85
|
-
: updated;
|
|
86
|
-
});
|
|
116
|
+
// The browser's clock only: the server's text would not match the hydrating
|
|
117
|
+
// render's.
|
|
118
|
+
const updatedLabel = computed(() => (isMounted.value ? updatedText.value : ""));
|
|
87
119
|
|
|
88
120
|
const missedText = computed(() => {
|
|
89
121
|
const count = live.missedCount.value;
|
|
@@ -122,13 +154,14 @@ useLiveToggleShortcut(() => live.toggle());
|
|
|
122
154
|
|
|
123
155
|
<template>
|
|
124
156
|
<div
|
|
125
|
-
v-if="
|
|
157
|
+
v-if="live.isEnabled.value"
|
|
126
158
|
class="dms-live-toggle flex flex-wrap items-center gap-x-2.5 gap-y-1"
|
|
127
159
|
:class="[ALIGN_CLASSES[align], `dms-live-toggle--${variant}`]"
|
|
128
160
|
>
|
|
129
161
|
<span
|
|
130
162
|
v-if="live.isPaused.value"
|
|
131
|
-
class="dms-live-toggle__status text-muted min-w-0
|
|
163
|
+
class="dms-live-toggle__status text-muted min-w-0 tabular-nums"
|
|
164
|
+
:class="sizeClasses.text"
|
|
132
165
|
aria-live="polite"
|
|
133
166
|
>
|
|
134
167
|
{{ pausedText }}
|
|
@@ -136,16 +169,28 @@ useLiveToggleShortcut(() => live.toggle());
|
|
|
136
169
|
</span>
|
|
137
170
|
<span
|
|
138
171
|
v-else-if="showUpdatedAt"
|
|
139
|
-
class="text-muted
|
|
172
|
+
class="text-muted inline-flex items-center gap-1.5 tabular-nums"
|
|
173
|
+
:class="sizeClasses.text"
|
|
140
174
|
aria-live="off"
|
|
141
175
|
>
|
|
142
176
|
{{ updatedLabel }}
|
|
177
|
+
<!-- The polling hint, its box always kept: showing it never widens the
|
|
178
|
+
toggle, which would wrap the toolbar and move the page. -->
|
|
179
|
+
<UIcon
|
|
180
|
+
name="i-ph-wifi-slash"
|
|
181
|
+
class="dms-live-toggle__polling size-3.5 shrink-0"
|
|
182
|
+
:class="!live.isPolling.value && 'invisible'"
|
|
183
|
+
:title="live.isPolling.value ? t('dms.live.polling') : undefined"
|
|
184
|
+
:aria-label="live.isPolling.value ? t('dms.live.polling') : undefined"
|
|
185
|
+
:aria-hidden="!live.isPolling.value || undefined"
|
|
186
|
+
/>
|
|
143
187
|
</span>
|
|
144
188
|
<div
|
|
145
189
|
v-if="variant === 'pill'"
|
|
146
190
|
role="group"
|
|
147
191
|
:aria-label="t('dms.live.aria_label')"
|
|
148
|
-
class="border-default bg-default inline-flex shrink-0 items-
|
|
192
|
+
class="border-default bg-default inline-flex shrink-0 items-stretch gap-0.5 rounded-md border p-0.5"
|
|
193
|
+
:class="heightClass"
|
|
149
194
|
>
|
|
150
195
|
<button
|
|
151
196
|
type="button"
|
|
@@ -153,6 +198,7 @@ useLiveToggleShortcut(() => live.toggle());
|
|
|
153
198
|
:aria-pressed="live.isLive.value"
|
|
154
199
|
:class="[
|
|
155
200
|
PILL_BUTTON_CLASS,
|
|
201
|
+
sizeClasses.pillButton,
|
|
156
202
|
live.isLive.value ? 'bg-success/10 text-success' : PILL_IDLE_CLASS,
|
|
157
203
|
]"
|
|
158
204
|
@click="live.resume()"
|
|
@@ -173,6 +219,7 @@ useLiveToggleShortcut(() => live.toggle());
|
|
|
173
219
|
:aria-pressed="live.isPaused.value"
|
|
174
220
|
:class="[
|
|
175
221
|
PILL_BUTTON_CLASS,
|
|
222
|
+
sizeClasses.pillButton,
|
|
176
223
|
live.isPaused.value
|
|
177
224
|
? 'bg-elevated text-highlighted'
|
|
178
225
|
: PILL_IDLE_CLASS,
|
|
@@ -188,12 +235,13 @@ useLiveToggleShortcut(() => live.toggle());
|
|
|
188
235
|
v-model="mode"
|
|
189
236
|
:items="items"
|
|
190
237
|
:aria-label="t('dms.live.aria_label')"
|
|
191
|
-
size="
|
|
238
|
+
:size="size"
|
|
192
239
|
/>
|
|
193
240
|
<UButton
|
|
194
241
|
v-if="variant === 'pill' && canResume"
|
|
195
242
|
data-action="resume"
|
|
196
|
-
size="
|
|
243
|
+
:size="size"
|
|
244
|
+
:class="heightClass"
|
|
197
245
|
color="neutral"
|
|
198
246
|
variant="soft"
|
|
199
247
|
icon="i-ph-play-fill"
|