@antelopejs/dms 0.9.8 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +171 -0
- package/README.md +1 -1
- package/data/module-store/catalog.json +2 -2
- package/dist/attachments/table-view.js +2 -2
- package/dist/attachments/table-view.js.map +1 -1
- package/dist/automation/actions.d.ts +2 -0
- package/dist/automation/actions.js +116 -71
- package/dist/automation/actions.js.map +1 -1
- package/dist/automation/i18n.d.ts +4 -0
- package/dist/automation/i18n.js +19 -0
- package/dist/automation/i18n.js.map +1 -0
- package/dist/automation/send-notification.d.ts +21 -0
- package/dist/automation/send-notification.js +163 -0
- package/dist/automation/send-notification.js.map +1 -0
- package/dist/automation/triggers.js +19 -25
- package/dist/automation/triggers.js.map +1 -1
- package/dist/client-origin-cors.d.ts +1 -0
- package/dist/client-origin-cors.js +19 -0
- package/dist/client-origin-cors.js.map +1 -0
- package/dist/config.d.ts +3 -2
- package/dist/config.js +5 -7
- package/dist/config.js.map +1 -1
- package/dist/frontend-module.d.ts +4 -0
- package/dist/frontend-module.js +37 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/implementations/dms/client-ip.js +1 -1
- package/dist/implementations/dms/client-ip.js.map +1 -1
- package/dist/implementations/dms/dev-reload.d.ts +5 -0
- package/dist/implementations/dms/dev-reload.js +23 -3
- package/dist/implementations/dms/dev-reload.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +2 -4
- package/dist/implementations/dms/frontend-modules.js +38 -94
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +12 -2
- package/dist/implementations/dms/frontend-ready.js +34 -4
- package/dist/implementations/dms/frontend-ready.js.map +1 -1
- package/dist/implementations/dms/layout-banners.d.ts +2 -1
- package/dist/implementations/dms/layout-banners.js +9 -0
- package/dist/implementations/dms/layout-banners.js.map +1 -1
- package/dist/implementations/dms/manifest.d.ts +7 -2
- package/dist/implementations/dms/manifest.js +12 -7
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +4 -3
- package/dist/implementations/dms/page.js +49 -18
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
- package/dist/implementations/dms/permissions-resolver.js +2 -2
- package/dist/implementations/dms/permissions-resolver.js.map +1 -1
- package/dist/implementations/dms/realtime.js +1 -0
- package/dist/implementations/dms/realtime.js.map +1 -1
- package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
- package/dist/implementations/dms-base/bulk-selection.js +23 -1
- package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
- package/dist/implementations/dms-base/filter-tokens.js +5 -3
- package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
- package/dist/implementations/dms-base/search-route.d.ts +1 -0
- package/dist/implementations/dms-base/search-route.js +35 -13
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/implementations/dms-base/table-view.js +11 -6
- package/dist/implementations/dms-base/table-view.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +2 -2
- package/dist/implementations/dms-notifications/index.js +5 -5
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/index.js +32 -55
- package/dist/index.js.map +1 -1
- package/dist/pages/modules/index.d.ts +1 -0
- package/dist/pages/modules/index.js +1 -0
- package/dist/pages/modules/index.js.map +1 -1
- package/dist/pages/modules/modules-store.d.ts +9 -0
- package/dist/pages/modules/modules-store.js +30 -0
- package/dist/pages/modules/modules-store.js.map +1 -0
- package/dist/pages/settings/users/account-credentials.js +2 -2
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/account-data-store.js +25 -8
- package/dist/pages/settings/users/account-data-store.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.js +9 -3
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/member-management.js +13 -14
- package/dist/pages/settings/users/member-management.js.map +1 -1
- package/dist/pages/settings/users/profile.d.ts +1 -4
- package/dist/pages/settings/users/profile.js +0 -35
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.js +26 -18
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/role-editor.d.ts +0 -1
- package/dist/pages/settings/users/role-editor.js +1 -2
- package/dist/pages/settings/users/role-editor.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +1 -2
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.js +2 -2
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/realtime/broker.d.ts +6 -0
- package/dist/realtime/broker.js +8 -1
- package/dist/realtime/broker.js.map +1 -1
- package/dist/realtime/presence.d.ts +8 -3
- package/dist/realtime/presence.js +8 -4
- package/dist/realtime/presence.js.map +1 -1
- package/dist/realtime/sessions.d.ts +11 -1
- package/dist/realtime/sessions.js +21 -3
- package/dist/realtime/sessions.js.map +1 -1
- package/dist/realtime/table-view.d.ts +2 -2
- package/dist/realtime/table-view.js +4 -3
- package/dist/realtime/table-view.js.map +1 -1
- package/dist/routes/auth/client-ip.d.ts +1 -0
- package/dist/routes/auth/client-ip.js +18 -0
- package/dist/routes/auth/client-ip.js.map +1 -1
- package/dist/routes/auth/oauth/config.d.ts +2 -1
- package/dist/routes/auth/oauth/config.js +5 -1
- package/dist/routes/auth/oauth/config.js.map +1 -1
- package/dist/routes/auth/session-handoff.d.ts +0 -1
- package/dist/routes/auth/session-handoff.js +0 -2
- package/dist/routes/auth/session-handoff.js.map +1 -1
- package/dist/routes/auth.js +3 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/index.d.ts +0 -1
- package/dist/routes/index.js +0 -1
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/realtime.d.ts +13 -1
- package/dist/routes/realtime.js +114 -29
- package/dist/routes/realtime.js.map +1 -1
- package/dist/startup-config.d.ts +2 -0
- package/dist/startup-config.js +62 -0
- package/dist/startup-config.js.map +1 -0
- package/dist/store/interfaces.d.ts +3 -2
- package/dist/store/interfaces.js +16 -6
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/listing.js +25 -15
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +5 -2
- package/dist/store/operations.js.map +1 -1
- package/dist/store/requirements.js +3 -2
- package/dist/store/requirements.js.map +1 -1
- package/dist/store/runtime.d.ts +10 -1
- package/dist/store/runtime.js +33 -6
- package/dist/store/runtime.js.map +1 -1
- package/dist/store/types.d.ts +10 -0
- package/dist/utils/ensure-default-tenant.d.ts +2 -1
- package/dist/utils/ensure-default-tenant.js +13 -1
- package/dist/utils/ensure-default-tenant.js.map +1 -1
- package/dist/utils/export-jobs.js +2 -1
- package/dist/utils/export-jobs.js.map +1 -1
- package/dist/validation/auth.schema.d.ts +2 -2
- package/dist/validation/member-invite.schema.d.ts +2 -10
- package/dist/validation/member-invite.schema.js +2 -15
- package/dist/validation/member-invite.schema.js.map +1 -1
- package/frontend-vue/dms.frontend.ts +24 -5
- package/frontend-vue/dms.server.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
- package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +33 -0
- package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
- package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
- package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
- package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
- package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
- package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
- package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +25 -5
- package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -5
- package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +61 -26
- package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
- package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
- package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
- package/frontend-vue/layers/dms-core/app/utils/formatter.ts +134 -35
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +136 -2
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +136 -2
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +0 -2
- package/frontend-vue/layers/dms-layout/app/app.config.ts +0 -1
- package/frontend-vue/layers/dms-layout/app/assets/css/main.css +11 -1
- package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Italic-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Italic-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +6 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -107
- package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +4 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
- package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +2 -2
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
- package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
- package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +49 -11
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +59 -4
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageRecordDeletion.ts +162 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +6 -2
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +6 -2
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +13 -22
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +11 -10
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +22 -52
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-quick-actions.client.ts +7 -7
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +11 -10
- package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
- package/frontend-vue/layers/dms-layout/app/theme/actions.ts +5 -4
- package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
- package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
- package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +45 -18
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +44 -17
- package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +5 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
- package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
- package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +48 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
- package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
- package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +6 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +44 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +123 -27
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +92 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +11 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formValidation.ts +36 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +78 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +97 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +90 -35
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/writeError.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +67 -29
- package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
- package/frontend-vue/layers/dms-ui/app/build/utils/htmlGrammar.ts +79 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
- package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/shellParser.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/utils/streamHighlight.ts +90 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
- package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
- package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
- package/frontend-vue/layers/dms-ui/app/components/code-snippet/CodeSnippet.vue +23 -12
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
- package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +119 -36
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +53 -22
- package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +226 -65
- package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
- package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +210 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
- package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
- package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
- package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +74 -25
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +48 -44
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +426 -97
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
- package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
- package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +16 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +189 -28
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +29 -12
- package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
- package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
- package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
- package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
- package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +94 -3
- package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +20 -5
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +20 -5
- package/frontend-vue/package.json +6 -2
- package/package.json +11 -10
- package/skills/dms/SKILL.md +1 -1
- package/skills/dms-auth/SKILL.md +5 -3
- package/skills/dms-best-practices/REFERENCE.md +2 -0
- package/skills/dms-best-practices/SKILL.md +4 -0
- package/skills/dms-module/SKILL.md +3 -2
- package/skills/dms-pages/REFERENCE.md +129 -16
- package/skills/dms-pages/SKILL.md +1 -1
- package/skills/dms-project/DEV-SERVERS.md +23 -1
- package/skills/dms-project/REFERENCE.md +1 -1
- package/skills/dms-project/SKILL.md +4 -1
- package/skills/dms-project/scripts/dms-dev.sh +205 -17
- package/dist/hooks/tenant-instances.d.ts +0 -1
- package/dist/hooks/tenant-instances.js +0 -13
- package/dist/hooks/tenant-instances.js.map +0 -1
- package/dist/routes/startup-gate.d.ts +0 -7
- package/dist/routes/startup-gate.js +0 -39
- package/dist/routes/startup-gate.js.map +0 -1
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { nextTick, reactive } from "vue";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type I18nTranslate,
|
|
4
|
+
resolveApiMessage,
|
|
5
|
+
} from "./translation/useTranslation";
|
|
3
6
|
import { resolveApiErrorMessage } from "./useApiError";
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -116,24 +119,23 @@ const FIELD_ERROR_KEYS = {
|
|
|
116
119
|
/** Issue types whose size is a count (characters, items), not a value. */
|
|
117
120
|
const COUNTED_TYPES = new Set(["string", "array", "set"]);
|
|
118
121
|
|
|
119
|
-
// The data API (`@antelopejs/interface-data-api`)
|
|
120
|
-
//
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
// The codes the data API (`@antelopejs/interface-data-api`) refuses a body
|
|
123
|
+
// with, `{ code, fields }`: the message each field shows.
|
|
124
|
+
const DATA_API_CODES: Readonly<Record<string, string>> = {
|
|
125
|
+
missing_mandatory_fields: FIELD_ERROR_KEYS.required,
|
|
126
|
+
invalid_field_types: FIELD_ERROR_KEYS.invalid,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// The same, naming the field: "Loss reason is required".
|
|
130
|
+
const NAMED_FIELD_ERROR_KEYS: Readonly<Record<string, string>> = {
|
|
131
|
+
[FIELD_ERROR_KEYS.required]: "dms.field_errors.named_required",
|
|
132
|
+
[FIELD_ERROR_KEYS.invalid]: "dms.field_errors.named_invalid",
|
|
133
|
+
};
|
|
131
134
|
|
|
132
135
|
// `name: String must contain…`: one validation issue prefixed with its field.
|
|
133
136
|
const FIELD_PREFIXED_MESSAGE = /^([\w.]+):\s+(.+)$/s;
|
|
134
137
|
// `error.invalid_2fa_code`, `page.settings.roles.error.name_taken`.
|
|
135
138
|
const BARE_I18N_KEY = /^[a-z0-9_]+(\.[a-z0-9_]+)+$/i;
|
|
136
|
-
const LIST_SEPARATOR = /\s*,\s*/;
|
|
137
139
|
|
|
138
140
|
/** The HTTP status of a fetch error; undefined for a network failure. */
|
|
139
141
|
export function apiErrorStatus(error: unknown): number | undefined {
|
|
@@ -265,16 +267,19 @@ function fromFieldObject(body: Record<string, unknown>): ApiFieldError[] {
|
|
|
265
267
|
];
|
|
266
268
|
}
|
|
267
269
|
|
|
270
|
+
/** `{ code, fields }`, the data API's refusal of a body; undefined otherwise. */
|
|
271
|
+
function fromCodedFields(
|
|
272
|
+
body: Record<string, unknown>,
|
|
273
|
+
): ApiFieldError[] | undefined {
|
|
274
|
+
const { code, fields } = body;
|
|
275
|
+
const message = typeof code === "string" ? DATA_API_CODES[code] : undefined;
|
|
276
|
+
if (!message || !Array.isArray(fields)) return undefined;
|
|
277
|
+
return fields
|
|
278
|
+
.filter((field): field is string => typeof field === "string")
|
|
279
|
+
.map((field) => ({ field, message }));
|
|
280
|
+
}
|
|
281
|
+
|
|
268
282
|
function fromText(text: string): ApiFieldError[] {
|
|
269
|
-
for (const { pattern, message } of DATA_API_MESSAGES) {
|
|
270
|
-
const match = pattern.exec(text);
|
|
271
|
-
if (match) {
|
|
272
|
-
return match[1]!
|
|
273
|
-
.split(LIST_SEPARATOR)
|
|
274
|
-
.filter(Boolean)
|
|
275
|
-
.map((field) => ({ field, message }));
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
283
|
const prefixed = FIELD_PREFIXED_MESSAGE.exec(text);
|
|
279
284
|
if (prefixed) {
|
|
280
285
|
const [, field, message] = prefixed;
|
|
@@ -320,7 +325,7 @@ function fromBody(body: unknown): ApiFieldError[] {
|
|
|
320
325
|
const record = body as Record<string, unknown>;
|
|
321
326
|
if (isIssueList(record.issues)) return fromIssues(record.issues);
|
|
322
327
|
if (isIssueList(record.errors)) return fromIssues(record.errors);
|
|
323
|
-
return fromFieldObject(record);
|
|
328
|
+
return fromCodedFields(record) ?? fromFieldObject(record);
|
|
324
329
|
}
|
|
325
330
|
|
|
326
331
|
function isClientError(status: number | undefined): status is number {
|
|
@@ -337,8 +342,8 @@ function isClientError(status: number | undefined): status is number {
|
|
|
337
342
|
*
|
|
338
343
|
* Read, in order: a code the form maps to a field (`codes`, for any 4xx), a
|
|
339
344
|
* zod issue list (what `assertValidation` answers), a `{ field, message }`
|
|
340
|
-
* body (with optional `values` and `params`), the data API's
|
|
341
|
-
*
|
|
345
|
+
* body (with optional `values` and `params`), the data API's
|
|
346
|
+
* `{ code, fields }` and a `field: message` text. A network failure, a 403, a 429
|
|
342
347
|
* or a 5xx never resolves to a field.
|
|
343
348
|
*
|
|
344
349
|
* @returns The field errors, one per field (the first issue wins), and
|
|
@@ -371,6 +376,36 @@ export function resolveFieldErrors<F extends string>(
|
|
|
371
376
|
return { fields, hasUnmatched: hasUnmatched || fields.length === 0 };
|
|
372
377
|
}
|
|
373
378
|
|
|
379
|
+
/**
|
|
380
|
+
* The data API's refusal of a body (`{ code, fields }`) as one sentence per
|
|
381
|
+
* field naming it, translated: "Loss reason is required." Undefined for any
|
|
382
|
+
* other error. `labelOf` gives a field's label, translated; the field's id
|
|
383
|
+
* stands in for one it does not know.
|
|
384
|
+
*/
|
|
385
|
+
export function describeFieldErrors(
|
|
386
|
+
error: unknown,
|
|
387
|
+
translate: I18nTranslate,
|
|
388
|
+
labelOf: (field: string) => string | undefined,
|
|
389
|
+
): string | undefined {
|
|
390
|
+
const status = apiErrorStatus(error);
|
|
391
|
+
if (status === undefined || !VALIDATION_STATUSES.has(status)) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
const body = bodyOf(error);
|
|
395
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
const entries = fromCodedFields(body as Record<string, unknown>);
|
|
399
|
+
if (!entries?.length) return undefined;
|
|
400
|
+
return entries
|
|
401
|
+
.map(({ field, message }) =>
|
|
402
|
+
translate(NAMED_FIELD_ERROR_KEYS[message]!, {
|
|
403
|
+
field: labelOf(field) ?? field,
|
|
404
|
+
}),
|
|
405
|
+
)
|
|
406
|
+
.join(" ");
|
|
407
|
+
}
|
|
408
|
+
|
|
374
409
|
/**
|
|
375
410
|
* The message of an error that belongs to no field, for its toast: the API's
|
|
376
411
|
* message key, the first validation issue's (or a generic one), else the
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineDmsPlugin } from "#dms/frontend-module";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logs, in development, every error Vue catches before the renderer's own
|
|
5
|
+
* handler takes it. That handler only keeps the error for the error page, so
|
|
6
|
+
* a render that throws showed its bare message there ("ERROR 500 · Must be
|
|
7
|
+
* called at the top of a `setup` function") with no stack in any console.
|
|
8
|
+
*/
|
|
9
|
+
export default defineDmsPlugin(({ vueApp }) => {
|
|
10
|
+
if (!import.meta.env.DEV) return;
|
|
11
|
+
const handleError = vueApp.config.errorHandler;
|
|
12
|
+
vueApp.config.errorHandler = (error, instance, info) => {
|
|
13
|
+
console.error(`[dms] Uncaught error (${info})`, error);
|
|
14
|
+
handleError?.(error, instance, info);
|
|
15
|
+
};
|
|
16
|
+
});
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/** A stored profile picture: the file's resource key. */
|
|
2
|
+
export interface UserAvatar {
|
|
3
|
+
key: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
export interface User {
|
|
2
7
|
_id: string;
|
|
3
8
|
createdAt: Date;
|
|
@@ -6,4 +11,5 @@ export interface User {
|
|
|
6
11
|
name: string;
|
|
7
12
|
language: string;
|
|
8
13
|
isValidated: boolean;
|
|
14
|
+
avatar?: UserAvatar | null;
|
|
9
15
|
}
|
|
@@ -15,7 +15,12 @@ import {
|
|
|
15
15
|
readTranslationKey,
|
|
16
16
|
resolveI18nKey,
|
|
17
17
|
} from "../composables/translation/useTranslation";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
formatDate,
|
|
20
|
+
formatNumber,
|
|
21
|
+
formatRelativeDistance,
|
|
22
|
+
readableNumberFormat,
|
|
23
|
+
} from "./formatter";
|
|
19
24
|
|
|
20
25
|
/** `t` as vue-i18n takes it: the key, the named values, the plural count. */
|
|
21
26
|
export type ComposedTextTranslate = (
|
|
@@ -103,7 +108,7 @@ function formatMoney(
|
|
|
103
108
|
{ locale }: ComposedTextContext,
|
|
104
109
|
): string {
|
|
105
110
|
try {
|
|
106
|
-
const format =
|
|
111
|
+
const format = readableNumberFormat(locale, {
|
|
107
112
|
style: "currency",
|
|
108
113
|
currency: param.currency,
|
|
109
114
|
});
|
|
@@ -20,8 +20,6 @@ const TIME_UNITS = {
|
|
|
20
20
|
ms: 1,
|
|
21
21
|
} as const;
|
|
22
22
|
|
|
23
|
-
const TIME_UNIT_ORDER = ["y", "M", "w", "d", "h", "m", "s", "ms"] as const;
|
|
24
|
-
|
|
25
23
|
type TranslateFunction = (
|
|
26
24
|
key: string,
|
|
27
25
|
params?: Record<string, unknown>,
|
|
@@ -230,8 +228,23 @@ export function formatDateTime(
|
|
|
230
228
|
}
|
|
231
229
|
|
|
232
230
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
231
|
+
* `new Intl.NumberFormat(locale, options)` the way the interface writes a
|
|
232
|
+
* figure: a four-digit one grouped like a longer one ("1 234" next to
|
|
233
|
+
* "12 345", where Spanish or Polish would leave "1234" by default).
|
|
234
|
+
* `options.useGrouping` still decides. Throws as `Intl.NumberFormat` does on
|
|
235
|
+
* options it refuses.
|
|
236
|
+
*/
|
|
237
|
+
export function readableNumberFormat(
|
|
238
|
+
locale?: string,
|
|
239
|
+
options?: Intl.NumberFormatOptions,
|
|
240
|
+
): Intl.NumberFormat {
|
|
241
|
+
return new Intl.NumberFormat(locale, { useGrouping: true, ...options });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* A number written for `locale` (see {@link readableNumberFormat}); a value
|
|
246
|
+
* that is not a number is returned as is. Options Intl refuses (an unknown
|
|
247
|
+
* currency) write the bare number.
|
|
235
248
|
*/
|
|
236
249
|
export function formatNumber(
|
|
237
250
|
value: number,
|
|
@@ -250,7 +263,7 @@ export function formatNumber(
|
|
|
250
263
|
): unknown {
|
|
251
264
|
if (!isNumber(value)) return value;
|
|
252
265
|
try {
|
|
253
|
-
return
|
|
266
|
+
return readableNumberFormat(locale, options).format(value);
|
|
254
267
|
} catch {
|
|
255
268
|
return String(value);
|
|
256
269
|
}
|
|
@@ -312,16 +325,19 @@ const TIME_SPAN_UNITS: Record<string, keyof typeof TIME_UNITS> = {
|
|
|
312
325
|
w: "w",
|
|
313
326
|
d: "d",
|
|
314
327
|
h: "h",
|
|
328
|
+
min: "m",
|
|
315
329
|
m: "m",
|
|
316
330
|
s: "s",
|
|
317
331
|
ms: "ms",
|
|
318
332
|
};
|
|
319
333
|
|
|
320
|
-
// `01:30`, `26:00:30`: hours, then minutes and seconds under 60.
|
|
334
|
+
// `01:30`, `8:15`, `26:00:30`: hours, then minutes and seconds under 60.
|
|
321
335
|
const CLOCK_SPAN = /^(\d+):([0-5]\d)(?::([0-5]\d))?$/;
|
|
322
|
-
// `
|
|
323
|
-
const
|
|
324
|
-
|
|
336
|
+
// `8h15`, `8 h 05`: hours, then bare minutes, the way a time is often written.
|
|
337
|
+
const HOUR_MINUTE_SPAN = /^(\d+)\s*h\s*([0-5]?\d)$/i;
|
|
338
|
+
// `1h30m`, `1h 30m`, `7 h 30 min`, `1h:30m` (older displays), `1.5h`.
|
|
339
|
+
const UNIT_SPAN = /^(?:\d+(?:[.,]\d+)?\s*(?:ms|mo|min|[Mmwydhs])[\s:]*)+$/;
|
|
340
|
+
const UNIT_SPAN_PART = /(\d+(?:[.,]\d+)?)\s*(ms|mo|min|[Mmwydhs])/g;
|
|
325
341
|
|
|
326
342
|
function readClockSpan(text: string): number | undefined {
|
|
327
343
|
const match = CLOCK_SPAN.exec(text);
|
|
@@ -334,24 +350,38 @@ function readClockSpan(text: string): number | undefined {
|
|
|
334
350
|
);
|
|
335
351
|
}
|
|
336
352
|
|
|
353
|
+
function readHourMinuteSpan(text: string): number | undefined {
|
|
354
|
+
const match = HOUR_MINUTE_SPAN.exec(text);
|
|
355
|
+
if (!match) return undefined;
|
|
356
|
+
const [, hours, minutes] = match;
|
|
357
|
+
return Number(hours) * TIME_UNITS.h + Number(minutes) * TIME_UNITS.m;
|
|
358
|
+
}
|
|
359
|
+
|
|
337
360
|
function readUnitSpan(text: string): number | undefined {
|
|
338
361
|
if (!UNIT_SPAN.test(text)) return undefined;
|
|
339
362
|
let total = 0;
|
|
340
363
|
for (const [, amount, unit] of text.matchAll(UNIT_SPAN_PART)) {
|
|
341
|
-
|
|
364
|
+
const value = Number.parseFloat(amount!.replace(",", "."));
|
|
365
|
+
total += value * TIME_UNITS[TIME_SPAN_UNITS[unit!]!];
|
|
342
366
|
}
|
|
343
367
|
return total;
|
|
344
368
|
}
|
|
345
369
|
|
|
346
370
|
/**
|
|
347
|
-
* A duration typed in, in milliseconds: a clock duration (`01:30`,
|
|
348
|
-
* `26:00:30`)
|
|
349
|
-
*
|
|
371
|
+
* A duration typed in, in milliseconds: a clock duration (`01:30`, `8:15`,
|
|
372
|
+
* `26:00:30`), hours then minutes (`8h15`, `8h`) or units (`1h30m`,
|
|
373
|
+
* `1h 30m`, `7 h 30 min`, `1.5h`), a `-` before any of them for a negative
|
|
374
|
+
* one, or `undefined` for a text that is neither. It reads all
|
|
375
|
+
* {@link formatDuration} writes.
|
|
350
376
|
*/
|
|
351
377
|
export function readTimeSpan(value: string): number | undefined {
|
|
352
|
-
const
|
|
378
|
+
const trimmed = value.trim();
|
|
379
|
+
const isNegative = trimmed.startsWith("-");
|
|
380
|
+
const text = isNegative ? trimmed.slice(1).trimStart() : trimmed;
|
|
353
381
|
if (!text) return undefined;
|
|
354
|
-
|
|
382
|
+
const span =
|
|
383
|
+
readClockSpan(text) ?? readHourMinuteSpan(text) ?? readUnitSpan(text);
|
|
384
|
+
return span !== undefined && isNegative ? -span : span;
|
|
355
385
|
}
|
|
356
386
|
|
|
357
387
|
export function parseTimeSpan(value: string | number): number {
|
|
@@ -359,30 +389,99 @@ export function parseTimeSpan(value: string | number): number {
|
|
|
359
389
|
return readTimeSpan(value) ?? 0;
|
|
360
390
|
}
|
|
361
391
|
|
|
362
|
-
|
|
363
|
-
|
|
392
|
+
// Durations count in hours at most: a day or a month is a calendar span, not
|
|
393
|
+
// a number of hours.
|
|
394
|
+
const DURATION_UNITS = [
|
|
395
|
+
{ symbol: "h", ms: TIME_UNITS.h, fractionDigits: 0 },
|
|
396
|
+
{ symbol: "min", ms: TIME_UNITS.m, fractionDigits: 0 },
|
|
397
|
+
// The seconds carry the milliseconds, so nothing is rounded away.
|
|
398
|
+
{ symbol: "s", ms: TIME_UNITS.s, fractionDigits: 3 },
|
|
399
|
+
] as const;
|
|
400
|
+
const MS_FRACTION_DIGITS = 3;
|
|
364
401
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
402
|
+
/**
|
|
403
|
+
* How exact a written duration is: `exact` (the default) keeps every
|
|
404
|
+
* millisecond, so a field shows the value it holds and {@link readTimeSpan}
|
|
405
|
+
* reads it back; `rounded` keeps two units at most, the smaller one rounded
|
|
406
|
+
* ("2.1 s", "4 min 12 s", "7 h 30 min"), for a cell read at a glance.
|
|
407
|
+
*/
|
|
408
|
+
export type DurationPrecision = "exact" | "rounded";
|
|
409
|
+
|
|
410
|
+
export interface DurationFormatOptions {
|
|
411
|
+
precision?: DurationPrecision;
|
|
412
|
+
}
|
|
370
413
|
|
|
371
|
-
|
|
372
|
-
if (totalMs === 0) return ZERO_MS;
|
|
414
|
+
const ROUNDED_SECONDS_FRACTION_DIGITS = 1;
|
|
373
415
|
|
|
416
|
+
function durationFigure(
|
|
417
|
+
locale: string,
|
|
418
|
+
value: number,
|
|
419
|
+
symbol: string,
|
|
420
|
+
fractionDigits: number,
|
|
421
|
+
): string {
|
|
422
|
+
const figure = readableNumberFormat(locale, {
|
|
423
|
+
maximumFractionDigits: fractionDigits,
|
|
424
|
+
useGrouping: false,
|
|
425
|
+
numberingSystem: "latn",
|
|
426
|
+
}).format(value);
|
|
427
|
+
return `${figure} ${symbol}`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function exactDuration(ms: number, locale: string): string {
|
|
431
|
+
let remaining = ms;
|
|
432
|
+
if (remaining < TIME_UNITS.s) {
|
|
433
|
+
return durationFigure(locale, remaining, "ms", MS_FRACTION_DIGITS);
|
|
434
|
+
}
|
|
374
435
|
const parts: string[] = [];
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
parts.push(
|
|
382
|
-
remaining %= unitValue;
|
|
383
|
-
}
|
|
436
|
+
for (const { symbol, ms: unitMs, fractionDigits } of DURATION_UNITS) {
|
|
437
|
+
const amount = fractionDigits
|
|
438
|
+
? remaining / unitMs
|
|
439
|
+
: Math.floor(remaining / unitMs);
|
|
440
|
+
remaining -= amount * unitMs;
|
|
441
|
+
if (amount)
|
|
442
|
+
parts.push(durationFigure(locale, amount, symbol, fractionDigits));
|
|
384
443
|
}
|
|
444
|
+
return parts.join(" ");
|
|
445
|
+
}
|
|
385
446
|
|
|
386
|
-
|
|
387
|
-
|
|
447
|
+
function roundedDuration(ms: number, locale: string): string {
|
|
448
|
+
if (ms < TIME_UNITS.s) return durationFigure(locale, Math.round(ms), "ms", 0);
|
|
449
|
+
if (ms < TIME_UNITS.m) {
|
|
450
|
+
return durationFigure(
|
|
451
|
+
locale,
|
|
452
|
+
ms / TIME_UNITS.s,
|
|
453
|
+
"s",
|
|
454
|
+
ROUNDED_SECONDS_FRACTION_DIGITS,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
const [major, majorSymbol, minor, minorSymbol] =
|
|
458
|
+
ms < TIME_UNITS.h
|
|
459
|
+
? ([TIME_UNITS.m, "min", TIME_UNITS.s, "s"] as const)
|
|
460
|
+
: ([TIME_UNITS.h, "h", TIME_UNITS.m, "min"] as const);
|
|
461
|
+
// Rounded as a whole first, so 1 min 59.7 s reads "2 min", not "1 min 60 s".
|
|
462
|
+
const total = Math.round(ms / minor) * minor;
|
|
463
|
+
const whole = Math.floor(total / major);
|
|
464
|
+
const rest = (total - whole * major) / minor;
|
|
465
|
+
const head = durationFigure(locale, whole, majorSymbol, 0);
|
|
466
|
+
return rest
|
|
467
|
+
? `${head} ${durationFigure(locale, rest, minorSymbol, 0)}`
|
|
468
|
+
: head;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* A duration in milliseconds the way people write it, its figures in the
|
|
473
|
+
* reader's locale: "910 ms", "2.149 s", "4 min 12 s", "7 h 30 min". The one
|
|
474
|
+
* writer of a duration, for a field and a cell alike. Exact by default (hours
|
|
475
|
+
* and minutes whole, the seconds carrying the milliseconds), its figures
|
|
476
|
+
* neither grouped nor in another script, so {@link readTimeSpan} reads back
|
|
477
|
+
* the very value it wrote; `precision: "rounded"` for a cell.
|
|
478
|
+
*/
|
|
479
|
+
export function formatDuration(
|
|
480
|
+
ms: number,
|
|
481
|
+
locale: string,
|
|
482
|
+
{ precision = "exact" }: DurationFormatOptions = {},
|
|
483
|
+
): string {
|
|
484
|
+
const sign = ms < 0 ? "-" : "";
|
|
485
|
+
const write = precision === "rounded" ? roundedDuration : exactDuration;
|
|
486
|
+
return sign + write(Math.abs(ms), locale);
|
|
388
487
|
}
|
|
@@ -12,8 +12,25 @@ const WHOLE_ROW_TOKEN = new RegExp(`^\\s*${ROW_TOKEN.source}\\s*$`);
|
|
|
12
12
|
export const namesRowField = (template: string): boolean =>
|
|
13
13
|
new RegExp(ROW_TOKEN.source).test(template);
|
|
14
14
|
|
|
15
|
+
// A relation field is listed as the related row (a join): it stands for that
|
|
16
|
+
// row's id, as the field stores it, rather than "[object Object]". A dotted
|
|
17
|
+
// path (`{{row.customer.name}}`) still reaches into the row.
|
|
18
|
+
const RELATED_ROW_ID_KEYS = ["_id", "id"] as const;
|
|
19
|
+
|
|
20
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
21
|
+
Object.prototype.toString.call(value) === "[object Object]";
|
|
22
|
+
|
|
23
|
+
const relatedRowId = (value: Record<string, unknown>): unknown =>
|
|
24
|
+
RELATED_ROW_ID_KEYS.map((key) => value[key]).find(
|
|
25
|
+
(id) => id !== undefined && id !== null,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const scalarOf = (value: unknown): unknown =>
|
|
29
|
+
isPlainObject(value) ? relatedRowId(value) : value;
|
|
30
|
+
|
|
15
31
|
const rowValue = (data: Record<string, unknown>, key: string): string => {
|
|
16
|
-
const
|
|
32
|
+
const field: unknown = get(data, key);
|
|
33
|
+
const value = Array.isArray(field) ? field.map(scalarOf) : scalarOf(field);
|
|
17
34
|
return value === undefined || value === null ? "" : String(value);
|
|
18
35
|
};
|
|
19
36
|
|
|
@@ -408,6 +408,8 @@
|
|
|
408
408
|
"too_short": "This value is too short.",
|
|
409
409
|
"too_small": "This value is too small.",
|
|
410
410
|
"too_large": "This value is too large.",
|
|
411
|
+
"named_required": "{field} is required.",
|
|
412
|
+
"named_invalid": "{field} is not valid.",
|
|
411
413
|
"with_values": "{message}: {values}",
|
|
412
414
|
"code_incomplete": "Enter every digit of the code.",
|
|
413
415
|
"password_rules": "The password doesn’t meet every rule.",
|
|
@@ -438,7 +440,8 @@
|
|
|
438
440
|
"chart": {
|
|
439
441
|
"no_data": "No data",
|
|
440
442
|
"legend_period": "Period",
|
|
441
|
-
"legend_comparison": "Comparison"
|
|
443
|
+
"legend_comparison": "Comparison",
|
|
444
|
+
"versus_value": "vs {value}"
|
|
442
445
|
},
|
|
443
446
|
"period": {
|
|
444
447
|
"aria_label": "Period selector",
|
|
@@ -463,12 +466,33 @@
|
|
|
463
466
|
"all": "All time",
|
|
464
467
|
"custom": "Custom"
|
|
465
468
|
},
|
|
469
|
+
"short_presets": {
|
|
470
|
+
"last-hour": "1H",
|
|
471
|
+
"last-24h": "24H",
|
|
472
|
+
"today": "1D",
|
|
473
|
+
"yesterday": "-1D",
|
|
474
|
+
"last-7-days": "7D",
|
|
475
|
+
"last-30-days": "30D",
|
|
476
|
+
"last-90-days": "90D",
|
|
477
|
+
"this-month": "MTD",
|
|
478
|
+
"last-month": "-1M",
|
|
479
|
+
"this-quarter": "QTD",
|
|
480
|
+
"last-quarter": "-1Q",
|
|
481
|
+
"ytd": "YTD",
|
|
482
|
+
"last-year": "-1Y",
|
|
483
|
+
"all": "ALL"
|
|
484
|
+
},
|
|
466
485
|
"comparisons": {
|
|
467
486
|
"none": "No comparison",
|
|
468
487
|
"previous-period": "vs previous period",
|
|
469
488
|
"previous-year": "vs previous year",
|
|
470
489
|
"custom": "Custom"
|
|
471
490
|
},
|
|
491
|
+
"compare_labels": {
|
|
492
|
+
"previous-period": "vs previous period",
|
|
493
|
+
"previous-year": "vs previous year",
|
|
494
|
+
"custom": "vs comparison period"
|
|
495
|
+
},
|
|
472
496
|
"compare_options": {
|
|
473
497
|
"none": "No comparison",
|
|
474
498
|
"previous-period": "Previous period",
|
|
@@ -494,7 +518,117 @@
|
|
|
494
518
|
},
|
|
495
519
|
"a11y": {
|
|
496
520
|
"breadcrumb": "Breadcrumb",
|
|
497
|
-
"
|
|
521
|
+
"close": "Close",
|
|
522
|
+
"loading": "Loading",
|
|
523
|
+
"show_options": "Show options"
|
|
524
|
+
},
|
|
525
|
+
"automation": {
|
|
526
|
+
"triggers": {
|
|
527
|
+
"memberAdded": {
|
|
528
|
+
"name": "Member added",
|
|
529
|
+
"description": "Fires when a user becomes a member of a workspace"
|
|
530
|
+
},
|
|
531
|
+
"memberRemoved": {
|
|
532
|
+
"name": "Member removed",
|
|
533
|
+
"description": "Fires when members are removed from a workspace"
|
|
534
|
+
},
|
|
535
|
+
"inviteCreated": {
|
|
536
|
+
"name": "Invite created",
|
|
537
|
+
"description": "Fires when a user invite is created on a workspace"
|
|
538
|
+
},
|
|
539
|
+
"userRegistered": {
|
|
540
|
+
"name": "User registered",
|
|
541
|
+
"description": "Fires when a new user completes signup"
|
|
542
|
+
},
|
|
543
|
+
"recordCreated": {
|
|
544
|
+
"name": "Record created",
|
|
545
|
+
"description": "Fires when rows are created in a TableView-backed table"
|
|
546
|
+
},
|
|
547
|
+
"recordUpdated": {
|
|
548
|
+
"name": "Record updated",
|
|
549
|
+
"description": "Fires when rows are updated in a TableView-backed table"
|
|
550
|
+
},
|
|
551
|
+
"recordDeleted": {
|
|
552
|
+
"name": "Record deleted",
|
|
553
|
+
"description": "Fires when rows are deleted in a TableView-backed table"
|
|
554
|
+
},
|
|
555
|
+
"record": {
|
|
556
|
+
"fields": {
|
|
557
|
+
"location": "Data controller location to watch, e.g. \"/api/tables/members\". Leave empty to fire for every table."
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
"actions": {
|
|
562
|
+
"sendEmail": {
|
|
563
|
+
"name": "Send email",
|
|
564
|
+
"description": "Send an HTML email through the configured email provider, written out or rendered from a registered HTML template",
|
|
565
|
+
"fields": {
|
|
566
|
+
"template": "Name of a registered HTML template to render instead of the HTML",
|
|
567
|
+
"props": "Props the template is rendered with",
|
|
568
|
+
"language": "Language the template is rendered in; defaults to the recipient's when the address belongs to a user"
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
"sendNotification": {
|
|
572
|
+
"name": "Send notification",
|
|
573
|
+
"description": "Send an in-app notification to specific users and/or every user holding one of the given roles",
|
|
574
|
+
"fields": {
|
|
575
|
+
"userId": "One recipient, e.g. a trigger's user ID",
|
|
576
|
+
"tenantId": "Workspace whose members receive the role notification; leave empty for the default workspace",
|
|
577
|
+
"title": "Literal text, or a \"$\" translation key",
|
|
578
|
+
"description": "Literal text, or a \"$\" translation key",
|
|
579
|
+
"params": "Interpolation params of the title and description translations",
|
|
580
|
+
"idempotencyKey": "Stable event key, e.g. a trigger's delivery ID: a replayed event is delivered once"
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
"inviteUser": {
|
|
584
|
+
"name": "Invite user",
|
|
585
|
+
"description": "Invite an email address to a workspace: existing users are added as members directly, otherwise an invite is created (and its signup email sent unless Send the email is off)",
|
|
586
|
+
"fields": {
|
|
587
|
+
"tenantId": "Workspace to invite into; defaults to the default workspace",
|
|
588
|
+
"language": "Language of the invitation email and of the account it creates; defaults to en",
|
|
589
|
+
"inviterName": "Who the invitation email says it comes from"
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
"fields": {
|
|
594
|
+
"actorId": "Actor ID",
|
|
595
|
+
"actorName": "Actor name",
|
|
596
|
+
"asTenantOwner": "Workspace owner",
|
|
597
|
+
"at": "Date",
|
|
598
|
+
"deliveryId": "Delivery ID",
|
|
599
|
+
"description": "Description",
|
|
600
|
+
"email": "Email",
|
|
601
|
+
"error": "Error",
|
|
602
|
+
"firstname": "First name",
|
|
603
|
+
"html": "HTML",
|
|
604
|
+
"icon": "Icon",
|
|
605
|
+
"idempotencyKey": "Replay key",
|
|
606
|
+
"ids": "Record IDs",
|
|
607
|
+
"inviteId": "Invite ID",
|
|
608
|
+
"inviterName": "Inviter name",
|
|
609
|
+
"isTenantOwner": "Workspace owner",
|
|
610
|
+
"kind": "Result",
|
|
611
|
+
"language": "Language",
|
|
612
|
+
"lastname": "Last name",
|
|
613
|
+
"linkTo": "Link",
|
|
614
|
+
"location": "Table",
|
|
615
|
+
"messageId": "Message ID",
|
|
616
|
+
"name": "Name",
|
|
617
|
+
"ok": "Sent",
|
|
618
|
+
"params": "Parameters",
|
|
619
|
+
"props": "Template props",
|
|
620
|
+
"roleIds": "Roles",
|
|
621
|
+
"sendEmail": "Send the email",
|
|
622
|
+
"subject": "Subject",
|
|
623
|
+
"success": "Success",
|
|
624
|
+
"template": "Template",
|
|
625
|
+
"tenantId": "Workspace",
|
|
626
|
+
"title": "Title",
|
|
627
|
+
"to": "To",
|
|
628
|
+
"tone": "Tone",
|
|
629
|
+
"userId": "User",
|
|
630
|
+
"userIds": "Users"
|
|
631
|
+
}
|
|
498
632
|
}
|
|
499
633
|
}
|
|
500
634
|
}
|