@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,8 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatDuration,
|
|
3
|
+
readableNumberFormat,
|
|
4
|
+
} from "#dms-core/app/utils/formatter";
|
|
1
5
|
import { defineAsyncComponent, h } from "vue";
|
|
2
6
|
import type { DataType } from "#dms-core/app/composables/data-types/useDataType";
|
|
3
7
|
import StatusPill from "../../../components/status-pill/StatusPill.vue";
|
|
4
8
|
import MonoCell from "../../components/table/MonoCell.vue";
|
|
5
|
-
import {
|
|
9
|
+
import { getAppComposedText } from "../../../../../dms-core/app/composables/translation/useComposedText";
|
|
6
10
|
import {
|
|
7
11
|
readRowField,
|
|
8
12
|
readRowTone,
|
|
@@ -11,6 +15,7 @@ import {
|
|
|
11
15
|
toneClass,
|
|
12
16
|
type Row,
|
|
13
17
|
} from "./cellHelpers";
|
|
18
|
+
import { getAppTranslation } from "../../../../../dms-core/app/composables/translation/useTranslation";
|
|
14
19
|
|
|
15
20
|
const Sparkline = defineAsyncComponent(
|
|
16
21
|
() => import("../../../components/chart/internal/Sparkline.vue"),
|
|
@@ -50,8 +55,8 @@ function statusPillTone(opts: StatusPillOptions, key: string, row: Row) {
|
|
|
50
55
|
* `subTone`, else the pill's.
|
|
51
56
|
*/
|
|
52
57
|
function renderStatusPill(value: unknown, options: unknown, row: Row) {
|
|
53
|
-
const { processI18n } =
|
|
54
|
-
const { processText } =
|
|
58
|
+
const { processI18n } = getAppTranslation();
|
|
59
|
+
const { processText } = getAppComposedText();
|
|
55
60
|
const opts = (options ?? {}) as StatusPillOptions;
|
|
56
61
|
const key = String(value);
|
|
57
62
|
const item = opts.typeOptions?.items?.find(
|
|
@@ -175,10 +180,6 @@ function renderSparkline(options: unknown, row: Row) {
|
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
const MS_PER_SECOND = 1000;
|
|
178
|
-
const SECONDS_PER_MINUTE = 60;
|
|
179
|
-
const MINUTES_PER_HOUR = 60;
|
|
180
|
-
const MS_PER_MINUTE = MS_PER_SECOND * SECONDS_PER_MINUTE;
|
|
181
|
-
const MS_PER_HOUR = MS_PER_MINUTE * MINUTES_PER_HOUR;
|
|
182
183
|
const DURATION_UNIT_MS: Record<string, number> = { ms: 1, s: MS_PER_SECOND };
|
|
183
184
|
|
|
184
185
|
// SI symbols read the same in every language; the figure follows the locale.
|
|
@@ -188,39 +189,16 @@ const withSymbol = (
|
|
|
188
189
|
symbol: string,
|
|
189
190
|
maximumFractionDigits = 0,
|
|
190
191
|
): string =>
|
|
191
|
-
`${
|
|
192
|
+
`${readableNumberFormat(locale, { maximumFractionDigits }).format(value)} ${symbol}`;
|
|
192
193
|
|
|
193
194
|
/** A duration always in milliseconds, its digits grouped: "2,149 ms". */
|
|
194
195
|
export function formatMilliseconds(ms: number, locale: string): string {
|
|
195
|
-
const figure =
|
|
196
|
+
const figure = readableNumberFormat(locale, {
|
|
196
197
|
maximumFractionDigits: 0,
|
|
197
|
-
// `true` groups a four-digit figure too, where a locale's default (es,
|
|
198
|
-
// pl) would leave "2149" against "12.149" further down the column.
|
|
199
|
-
useGrouping: true,
|
|
200
198
|
}).format(ms);
|
|
201
199
|
return `${figure} ms`;
|
|
202
200
|
}
|
|
203
201
|
|
|
204
|
-
/**
|
|
205
|
-
* A duration in its most readable unit: "910 ms", "1.8 s", "4 min 12 s",
|
|
206
|
-
* "2 h 5 min".
|
|
207
|
-
*/
|
|
208
|
-
export function formatDuration(ms: number, locale: string): string {
|
|
209
|
-
const absolute = Math.abs(ms);
|
|
210
|
-
if (absolute < MS_PER_SECOND) return withSymbol(locale, Math.round(ms), "ms");
|
|
211
|
-
if (absolute < MS_PER_MINUTE) {
|
|
212
|
-
return withSymbol(locale, ms / MS_PER_SECOND, "s", 1);
|
|
213
|
-
}
|
|
214
|
-
const [major, majorSymbol, minor, minorSymbol] =
|
|
215
|
-
absolute < MS_PER_HOUR
|
|
216
|
-
? ([MS_PER_MINUTE, "min", MS_PER_SECOND, "s"] as const)
|
|
217
|
-
: ([MS_PER_HOUR, "h", MS_PER_MINUTE, "min"] as const);
|
|
218
|
-
const whole = Math.trunc(ms / major);
|
|
219
|
-
const rest = Math.round((ms - whole * major) / minor);
|
|
220
|
-
const head = withSymbol(locale, whole, majorSymbol);
|
|
221
|
-
return rest === 0 ? head : `${head} ${withSymbol(locale, rest, minorSymbol)}`;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
202
|
const BYTE_SYMBOLS = ["B", "kB", "MB", "GB", "TB"];
|
|
225
203
|
const BYTE_STEP = 1000;
|
|
226
204
|
|
|
@@ -253,8 +231,12 @@ interface DurationOptions {
|
|
|
253
231
|
toneField?: string;
|
|
254
232
|
}
|
|
255
233
|
|
|
256
|
-
|
|
257
|
-
|
|
234
|
+
type DurationWriter = (ms: number, locale: string) => string;
|
|
235
|
+
|
|
236
|
+
// A cell is read at a glance: the readable duration is rounded there.
|
|
237
|
+
const DURATION_FORMATS: Record<string, DurationWriter> = {
|
|
238
|
+
readable: (ms, locale) =>
|
|
239
|
+
formatDuration(ms, locale, { precision: "rounded" }),
|
|
258
240
|
ms: formatMilliseconds,
|
|
259
241
|
};
|
|
260
242
|
|
|
@@ -270,7 +252,8 @@ function renderDuration(
|
|
|
270
252
|
) {
|
|
271
253
|
const opts = (options ?? {}) as DurationOptions;
|
|
272
254
|
const ms = numberOf(value) * (DURATION_UNIT_MS[opts.unit ?? "ms"] ?? 1);
|
|
273
|
-
const format =
|
|
255
|
+
const format =
|
|
256
|
+
DURATION_FORMATS[opts.format ?? "readable"] ?? DURATION_FORMATS.readable!;
|
|
274
257
|
return figure(
|
|
275
258
|
format(ms, locale),
|
|
276
259
|
rowToneTextClass(row, opts.toneField, FIGURE_TONE_CLASS),
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { DataTypeFormatter } from "#dms-core/app/composables/data-types/useDataType";
|
|
2
|
+
import { getAppTranslation } from "../../../../../dms-core/app/composables/translation/useTranslation";
|
|
3
|
+
|
|
4
|
+
/** A formatter writing a number for a locale, with Intl's options. */
|
|
5
|
+
type NumberFormatter = (
|
|
6
|
+
value: unknown,
|
|
7
|
+
locale: string,
|
|
8
|
+
options?: Intl.NumberFormatOptions,
|
|
9
|
+
) => unknown;
|
|
10
|
+
|
|
11
|
+
/** The options of a numeric field a cell reads besides Intl's. */
|
|
12
|
+
interface NumberSuffixOptions {
|
|
13
|
+
/** Unit after the value ("days"): an i18n key with `$`, or a literal. */
|
|
14
|
+
suffix?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Keeps the unit on the line of its number when a cell wraps.
|
|
18
|
+
const UNIT_SEPARATOR = " ";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A numeric data type's formatter writing the field's unit after the value,
|
|
22
|
+
* as its form input does ("12.5 kWp"), wherever the type is drawn: table
|
|
23
|
+
* cells, expanded rows, cards, read-only fields. A value that is not a
|
|
24
|
+
* number is left as the formatter returns it.
|
|
25
|
+
*
|
|
26
|
+
* A formatter also runs where no component setup is active (a footer sum, a
|
|
27
|
+
* group header, an export), so the unit is translated through the
|
|
28
|
+
* application's composer, never through a composable.
|
|
29
|
+
*/
|
|
30
|
+
export function withNumberSuffix(format: NumberFormatter): DataTypeFormatter {
|
|
31
|
+
return (value, locale, options) => {
|
|
32
|
+
const formatted = format(
|
|
33
|
+
value,
|
|
34
|
+
locale,
|
|
35
|
+
options as Intl.NumberFormatOptions | undefined,
|
|
36
|
+
);
|
|
37
|
+
const suffix = (options as NumberSuffixOptions | undefined)?.suffix;
|
|
38
|
+
if (!suffix || typeof value !== "number" || typeof formatted !== "string") {
|
|
39
|
+
return formatted;
|
|
40
|
+
}
|
|
41
|
+
const unit = getAppTranslation().processI18n(suffix);
|
|
42
|
+
return `${formatted}${UNIT_SEPARATOR}${unit}`;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -7,6 +7,7 @@ import UIcon from "@nuxt/ui/runtime/vue/components/Icon.vue";
|
|
|
7
7
|
import ULink from "@nuxt/ui/components/Link.vue";
|
|
8
8
|
import { buildRelationBadges } from "./relationBadges";
|
|
9
9
|
import { mapRelationBeforeState, sameRelationValue } from "./relationValue";
|
|
10
|
+
import { NotFoundRelation, relationSelectSource } from "./relationRows";
|
|
10
11
|
import {
|
|
11
12
|
isSet,
|
|
12
13
|
readRowField,
|
|
@@ -17,8 +18,15 @@ import {
|
|
|
17
18
|
toneClass,
|
|
18
19
|
} from "./cellHelpers";
|
|
19
20
|
import { registerMetricCellTypes } from "./metricCells";
|
|
21
|
+
import {
|
|
22
|
+
dateFilterEditorValue,
|
|
23
|
+
numberFilterEditorValue,
|
|
24
|
+
} from "../table/utils/filterEditorValue";
|
|
25
|
+
import { formatDuration } from "#dms-core/app/utils/formatter";
|
|
26
|
+
import { withNumberSuffix } from "./numberSuffix";
|
|
20
27
|
import { registerTwoLineCellType } from "./twoLineCell";
|
|
21
|
-
import {
|
|
28
|
+
import { formatRecordLabel } from "../table-view/utils/formTexts";
|
|
29
|
+
import { getAppComposedText } from "../../../../../dms-core/app/composables/translation/useComposedText";
|
|
22
30
|
import StatusPill, {
|
|
23
31
|
type StatusPillVariant,
|
|
24
32
|
} from "../../../components/status-pill/StatusPill.vue";
|
|
@@ -29,6 +37,8 @@ import {
|
|
|
29
37
|
formatDayMonth,
|
|
30
38
|
formatRelativeDate,
|
|
31
39
|
} from "../../utils/relativeDate";
|
|
40
|
+
import { serverRenderNow } from "#dms-core/app/build/composables/renderNow";
|
|
41
|
+
import { getAppTranslation } from "../../../../../dms-core/app/composables/translation/useTranslation";
|
|
32
42
|
|
|
33
43
|
const FilePreview = defineAsyncComponent(
|
|
34
44
|
() => import("../../../components/table-view/FilePreview.vue"),
|
|
@@ -141,11 +151,57 @@ function createLinkRenderer(options: LinkRendererOptions) {
|
|
|
141
151
|
};
|
|
142
152
|
}
|
|
143
153
|
|
|
154
|
+
/** A `UrlType`'s options its read-only value reads. */
|
|
155
|
+
interface UrlOptions {
|
|
156
|
+
preview?: "avatar" | "image";
|
|
157
|
+
/**
|
|
158
|
+
* The label of the field shown, handed by the form's read-only display:
|
|
159
|
+
* the image's name when its row has none.
|
|
160
|
+
*/
|
|
161
|
+
fieldLabel?: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const URL_IMAGE_CLASS =
|
|
165
|
+
"border-default h-16 w-auto max-w-full rounded-md border object-cover";
|
|
166
|
+
|
|
167
|
+
// The fields that name a row, read in order for its image's text.
|
|
168
|
+
const ROW_NAME_FIELDS = ["name", "label", "title"];
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* What an image stands for to a reader who cannot see it: the name of the
|
|
172
|
+
* row it belongs to (a team member's photo is that member), or else the
|
|
173
|
+
* field's label.
|
|
174
|
+
*/
|
|
175
|
+
function imageAlt(row: Row, locale: string, options: UrlOptions): string {
|
|
176
|
+
for (const field of ROW_NAME_FIELDS) {
|
|
177
|
+
const name = formatRecordLabel(row?.[field], locale);
|
|
178
|
+
if (name) return name;
|
|
179
|
+
}
|
|
180
|
+
return options.fieldLabel ?? "";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* A URL drawn as the image it points to, when its type asks for it
|
|
185
|
+
* (`preview`): a remote photo reads as a picture, not as its address.
|
|
186
|
+
*/
|
|
187
|
+
function renderUrl(value: unknown, locale: string, options: unknown, row: Row) {
|
|
188
|
+
const opts = (options ?? {}) as UrlOptions;
|
|
189
|
+
if (!opts.preview || !isString(value) || !value) return undefined;
|
|
190
|
+
const alt = imageAlt(row, locale, opts);
|
|
191
|
+
if (opts.preview === "avatar") return h(UAvatar, { src: value, alt });
|
|
192
|
+
return h("img", {
|
|
193
|
+
src: value,
|
|
194
|
+
alt,
|
|
195
|
+
loading: "lazy",
|
|
196
|
+
class: URL_IMAGE_CLASS,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
144
200
|
const convertToBoolean = (value: unknown): boolean =>
|
|
145
201
|
parse(String(value)) === true;
|
|
146
202
|
|
|
147
203
|
const getBooleanLabel = (value: unknown): string => {
|
|
148
|
-
const { processI18n } =
|
|
204
|
+
const { processI18n } = getAppTranslation();
|
|
149
205
|
return convertToBoolean(value)
|
|
150
206
|
? processI18n("$dms.table.filter.boolean.checked")
|
|
151
207
|
: processI18n("$dms.table.filter.boolean.unchecked");
|
|
@@ -160,7 +216,7 @@ function renderBoolean(value: unknown) {
|
|
|
160
216
|
}
|
|
161
217
|
|
|
162
218
|
function formatDateBetween(value: unknown, locale: string): string {
|
|
163
|
-
const { t } =
|
|
219
|
+
const { t } = getAppTranslation();
|
|
164
220
|
const [rawStart, rawEnd] = Array.isArray(value)
|
|
165
221
|
? value
|
|
166
222
|
: [
|
|
@@ -174,7 +230,7 @@ function formatDateBetween(value: unknown, locale: string): string {
|
|
|
174
230
|
|
|
175
231
|
// v2 .status pill, tinted with the configured online/offline color.
|
|
176
232
|
function renderStatus(value: unknown, options: unknown) {
|
|
177
|
-
const { processI18n } =
|
|
233
|
+
const { processI18n } = getAppTranslation();
|
|
178
234
|
const opts = options as StatusOptions | undefined;
|
|
179
235
|
const isOnline = Boolean(value);
|
|
180
236
|
const onlineLabel = opts?.onlineLabel || "$common.status.online";
|
|
@@ -194,7 +250,7 @@ function renderStatusPill(label: string, color: string) {
|
|
|
194
250
|
}
|
|
195
251
|
|
|
196
252
|
function renderSelectItem(item: SelectItem) {
|
|
197
|
-
const { processI18n } =
|
|
253
|
+
const { processI18n } = getAppTranslation();
|
|
198
254
|
const label = processI18n(item.label);
|
|
199
255
|
if (!item.icon && !item.iconColor && !item.textColor) return label;
|
|
200
256
|
|
|
@@ -253,7 +309,7 @@ function renderSelect(value: unknown, options?: unknown) {
|
|
|
253
309
|
function formatAddress(value: unknown): string {
|
|
254
310
|
if (!value || typeof value !== "object") return " ";
|
|
255
311
|
|
|
256
|
-
const { processI18n } =
|
|
312
|
+
const { processI18n } = getAppTranslation();
|
|
257
313
|
const addr = value as Record<string, string>;
|
|
258
314
|
const countryName = addr.countryCode
|
|
259
315
|
? processI18n(`$country.${addr.countryCode}`)
|
|
@@ -282,12 +338,31 @@ function renderRelationBadge(label: string, title?: string) {
|
|
|
282
338
|
});
|
|
283
339
|
}
|
|
284
340
|
|
|
341
|
+
// An id its `select` route did not answer: "Not found", the id on hover.
|
|
342
|
+
function renderNotFoundRelation(relation: NotFoundRelation) {
|
|
343
|
+
const { t } = getAppTranslation();
|
|
344
|
+
return h(
|
|
345
|
+
"span",
|
|
346
|
+
{ class: "text-muted", title: relation.id },
|
|
347
|
+
t("dms.form.relation.not_found"),
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
285
351
|
function renderRelationBadges(values: unknown[], labelKey: string) {
|
|
286
|
-
const { processI18n } =
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
renderRelationBadge(processI18n(label)),
|
|
352
|
+
const { processI18n, t } = getAppTranslation();
|
|
353
|
+
const notFound = values.filter(
|
|
354
|
+
(item): item is NotFoundRelation => item instanceof NotFoundRelation,
|
|
290
355
|
);
|
|
356
|
+
const { visible, hidden } = buildRelationBadges(
|
|
357
|
+
values.filter((item) => !(item instanceof NotFoundRelation)),
|
|
358
|
+
labelKey,
|
|
359
|
+
);
|
|
360
|
+
const badges = [
|
|
361
|
+
...visible.map((label) => renderRelationBadge(processI18n(label))),
|
|
362
|
+
...notFound.map((relation) =>
|
|
363
|
+
renderRelationBadge(t("dms.form.relation.not_found"), relation.id),
|
|
364
|
+
),
|
|
365
|
+
];
|
|
291
366
|
if (hidden.length > 0) {
|
|
292
367
|
const hiddenLabels = hidden.map((label) => processI18n(label)).join(", ");
|
|
293
368
|
badges.push(renderRelationBadge(`+${hidden.length}`, hiddenLabels));
|
|
@@ -303,6 +378,7 @@ function renderRelation(value: unknown, options: unknown) {
|
|
|
303
378
|
return renderRelationBadges(value, labelKey);
|
|
304
379
|
}
|
|
305
380
|
|
|
381
|
+
if (value instanceof NotFoundRelation) return renderNotFoundRelation(value);
|
|
306
382
|
if (!value || !isObject(value)) return value;
|
|
307
383
|
|
|
308
384
|
const valueObject = value as Record<string, string>;
|
|
@@ -354,7 +430,7 @@ function renderImage(value: unknown, options: unknown) {
|
|
|
354
430
|
}
|
|
355
431
|
|
|
356
432
|
function renderFile(value: unknown, options: unknown) {
|
|
357
|
-
const { t } =
|
|
433
|
+
const { t } = getAppTranslation();
|
|
358
434
|
const opts = options as FileOptions | undefined;
|
|
359
435
|
|
|
360
436
|
if (opts?.multiple && Array.isArray(value)) {
|
|
@@ -408,8 +484,8 @@ const matchesBadge = (badge: IdentityBadgeOption, value: unknown): boolean =>
|
|
|
408
484
|
* `emptyIcon` stand for a row without a value.
|
|
409
485
|
*/
|
|
410
486
|
function renderIdentity(value: unknown, options: unknown, row: Row) {
|
|
411
|
-
const { processI18n } =
|
|
412
|
-
const { processText } =
|
|
487
|
+
const { processI18n } = getAppTranslation();
|
|
488
|
+
const { processText } = getAppComposedText();
|
|
413
489
|
const opts = (options ?? {}) as IdentityOptions;
|
|
414
490
|
const badges = (opts.badges ?? [])
|
|
415
491
|
.filter((badge) => matchesBadge(badge, readRowField(row, badge.field)))
|
|
@@ -526,7 +602,7 @@ function renderPill(pill: PillItem) {
|
|
|
526
602
|
* a relation could not resolve (a deleted row) is skipped.
|
|
527
603
|
*/
|
|
528
604
|
function renderPills(value: unknown, options: unknown, row: Row) {
|
|
529
|
-
const { processI18n } =
|
|
605
|
+
const { processI18n } = getAppTranslation();
|
|
530
606
|
const opts = (options ?? {}) as PillsOptions;
|
|
531
607
|
if (opts.exclusive && isSet(readRowField(row, opts.exclusive.field))) {
|
|
532
608
|
return h("span", { class: [PILL_CLASS, PILL_FILLED_CLASS] }, [
|
|
@@ -605,11 +681,14 @@ function renderRelativeDate(
|
|
|
605
681
|
options: unknown,
|
|
606
682
|
row: Row,
|
|
607
683
|
) {
|
|
608
|
-
const { processI18n } =
|
|
684
|
+
const { processI18n } = getAppTranslation();
|
|
609
685
|
const opts = (options ?? {}) as RelativeDateOptions;
|
|
610
686
|
const date = new Date(value as string);
|
|
611
687
|
if (Number.isNaN(date.getTime())) return String(value);
|
|
612
|
-
|
|
688
|
+
// Measured from the server render's instant while hydrating it: "3 min ago"
|
|
689
|
+
// written at 12:00:59 stays so in a browser hydrating at 12:01:01.
|
|
690
|
+
const now = serverRenderNow() ?? Date.now();
|
|
691
|
+
const deltaMs = date.getTime() - now;
|
|
613
692
|
const isPast = deltaMs < 0;
|
|
614
693
|
const title = formatDate(date, locale, RELATIVE_TITLE_FORMAT) ?? undefined;
|
|
615
694
|
|
|
@@ -624,8 +703,8 @@ function renderRelativeDate(
|
|
|
624
703
|
const style = (isPast ? opts.pastStyle : undefined) ?? opts.style;
|
|
625
704
|
let text =
|
|
626
705
|
style === "day"
|
|
627
|
-
? formatDayMonth(date, locale)
|
|
628
|
-
: formatRelativeDate(date, locale);
|
|
706
|
+
? formatDayMonth(date, locale, now)
|
|
707
|
+
: formatRelativeDate(date, locale, now);
|
|
629
708
|
const isSoon = !isPast && !!opts.soonWithinMs && deltaMs < opts.soonWithinMs;
|
|
630
709
|
const tone = isPast
|
|
631
710
|
? (opts.pastTone ?? opts.tone)
|
|
@@ -644,7 +723,7 @@ function renderRelativeDate(
|
|
|
644
723
|
}
|
|
645
724
|
|
|
646
725
|
function renderMissingDate(options: unknown) {
|
|
647
|
-
const { processI18n } =
|
|
726
|
+
const { processI18n } = getAppTranslation();
|
|
648
727
|
const opts = (options ?? {}) as RelativeDateOptions;
|
|
649
728
|
if (!opts.emptyLabel) return "-";
|
|
650
729
|
return h(
|
|
@@ -670,7 +749,7 @@ interface IndicatorOptions {
|
|
|
670
749
|
* on for a set flag or a non-empty list.
|
|
671
750
|
*/
|
|
672
751
|
function renderIndicator(value: unknown, options: unknown) {
|
|
673
|
-
const { processI18n } =
|
|
752
|
+
const { processI18n } = getAppTranslation();
|
|
674
753
|
const opts = (options ?? {}) as IndicatorOptions;
|
|
675
754
|
const isOn = isSet(value);
|
|
676
755
|
const icon = isOn ? opts.onIcon : opts.offIcon;
|
|
@@ -768,7 +847,8 @@ const registerPrimitiveTypes = (
|
|
|
768
847
|
) => {
|
|
769
848
|
registerDataType({
|
|
770
849
|
id: "number",
|
|
771
|
-
formatter: { default: formatNumber
|
|
850
|
+
formatter: { default: withNumberSuffix(formatNumber) },
|
|
851
|
+
filterEditorValue: numberFilterEditorValue,
|
|
772
852
|
});
|
|
773
853
|
registerDataType({
|
|
774
854
|
id: "string",
|
|
@@ -776,15 +856,24 @@ const registerPrimitiveTypes = (
|
|
|
776
856
|
});
|
|
777
857
|
registerDataType({
|
|
778
858
|
id: "price",
|
|
779
|
-
formatter: { default: formatPrice
|
|
859
|
+
formatter: { default: withNumberSuffix(formatPrice) },
|
|
860
|
+
filterEditorValue: numberFilterEditorValue,
|
|
780
861
|
});
|
|
781
862
|
registerDataType({
|
|
782
863
|
id: "percentage",
|
|
783
|
-
formatter: { default: formatPercentage
|
|
864
|
+
formatter: { default: withNumberSuffix(formatPercentage) },
|
|
865
|
+
filterEditorValue: numberFilterEditorValue,
|
|
784
866
|
});
|
|
785
867
|
registerDataType({
|
|
786
868
|
id: "string_time",
|
|
787
|
-
|
|
869
|
+
// Written as the field writes it ("7 h 30 min"), in the reader's locale,
|
|
870
|
+
// rounded as every duration cell is: the field keeps the exact value.
|
|
871
|
+
formatter: {
|
|
872
|
+
default: (value: unknown, locale: string) =>
|
|
873
|
+
isNumber(value)
|
|
874
|
+
? formatDuration(Number(value), locale, { precision: "rounded" })
|
|
875
|
+
: value,
|
|
876
|
+
},
|
|
788
877
|
});
|
|
789
878
|
};
|
|
790
879
|
|
|
@@ -801,17 +890,18 @@ const registerDateType = (registerDataType: (dataType: DataType) => void) => {
|
|
|
801
890
|
id: "date",
|
|
802
891
|
formatter: {
|
|
803
892
|
default: (value: unknown, locale: string) => {
|
|
804
|
-
const { t } =
|
|
893
|
+
const { t } = getAppTranslation();
|
|
805
894
|
return formatDate(value, locale) ?? t("dms.date.undefined");
|
|
806
895
|
},
|
|
807
896
|
cell: (value: unknown, locale: string) => {
|
|
808
|
-
const { t } =
|
|
897
|
+
const { t } = getAppTranslation();
|
|
809
898
|
return (
|
|
810
899
|
formatDate(value, locale, CELL_DATE_FORMAT) ?? t("dms.date.undefined")
|
|
811
900
|
);
|
|
812
901
|
},
|
|
813
902
|
is_between: formatDateBetween,
|
|
814
903
|
},
|
|
904
|
+
filterEditorValue: dateFilterEditorValue,
|
|
815
905
|
});
|
|
816
906
|
};
|
|
817
907
|
|
|
@@ -875,9 +965,13 @@ const registerLinkTypes = (registerDataType: (dataType: DataType) => void) => {
|
|
|
875
965
|
id: "email",
|
|
876
966
|
formatter: { default: createLinkRenderer({ hrefPrefix: "mailto:" }) },
|
|
877
967
|
});
|
|
968
|
+
const renderLink = createLinkRenderer({ target: "_blank" });
|
|
878
969
|
registerDataType({
|
|
879
970
|
id: "url",
|
|
880
|
-
formatter: {
|
|
971
|
+
formatter: {
|
|
972
|
+
default: (value: unknown, locale: string, options: unknown, row?: Row) =>
|
|
973
|
+
renderUrl(value, locale, options, row) ?? renderLink(value, locale),
|
|
974
|
+
},
|
|
881
975
|
});
|
|
882
976
|
};
|
|
883
977
|
|
|
@@ -892,6 +986,7 @@ const registerRelationType = (
|
|
|
892
986
|
},
|
|
893
987
|
beforeStateMapper: mapRelationBeforeState,
|
|
894
988
|
isSameValue: sameRelationValue,
|
|
989
|
+
selectSource: relationSelectSource,
|
|
895
990
|
});
|
|
896
991
|
};
|
|
897
992
|
|
|
@@ -906,6 +1001,7 @@ const registerCascaderRelationType = (
|
|
|
906
1001
|
},
|
|
907
1002
|
beforeStateMapper: mapRelationBeforeState,
|
|
908
1003
|
isSameValue: sameRelationValue,
|
|
1004
|
+
selectSource: relationSelectSource,
|
|
909
1005
|
});
|
|
910
1006
|
};
|
|
911
1007
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { h } from "vue";
|
|
2
2
|
import type { DataType } from "#dms-core/app/composables/data-types/useDataType";
|
|
3
|
+
import { getAppTranslation } from "../../../../../dms-core/app/composables/translation/useTranslation";
|
|
3
4
|
|
|
4
5
|
// How the field types made of several values read in a table cell or a
|
|
5
6
|
// read-only form: a count, a masked secret, the first line of the code,
|
|
@@ -19,7 +20,7 @@ const countOf = (value: unknown): number => {
|
|
|
19
20
|
|
|
20
21
|
function renderCount(key: string) {
|
|
21
22
|
return (value: unknown) => {
|
|
22
|
-
const { t } =
|
|
23
|
+
const { t } = getAppTranslation();
|
|
23
24
|
const count = countOf(value);
|
|
24
25
|
return t(key, { count }, count);
|
|
25
26
|
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { isEmptyFormValue, sameFormValue } from "../unsaved-changes/formValue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A relation picker narrowed by another field of its form: the related
|
|
5
|
+
* table's `field` must equal the form's value of `from`.
|
|
6
|
+
*/
|
|
7
|
+
export interface RelationFilterBy {
|
|
8
|
+
field: string;
|
|
9
|
+
from: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The compare mode the search route matches the related `field` with. */
|
|
13
|
+
const FILTER_MODE = "is";
|
|
14
|
+
|
|
15
|
+
type FilterValue = string | number | boolean;
|
|
16
|
+
|
|
17
|
+
function isFilterValue(value: unknown): value is FilterValue {
|
|
18
|
+
if (typeof value === "string") return value !== "";
|
|
19
|
+
return typeof value === "number" || typeof value === "boolean";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The query narrowing the picker's search: the related table's filter
|
|
24
|
+
* syntax (`filter_<field>=is:<value>`), the one its funnel sends. Nothing
|
|
25
|
+
* while the source field holds no single value: every row is offered.
|
|
26
|
+
*/
|
|
27
|
+
export function relationFilterParams(
|
|
28
|
+
filterBy: RelationFilterBy | undefined,
|
|
29
|
+
sourceValue: unknown,
|
|
30
|
+
): Record<string, string> {
|
|
31
|
+
if (!filterBy || !isFilterValue(sourceValue)) return {};
|
|
32
|
+
return { [`filter_${filterBy.field}`]: `${FILTER_MODE}:${sourceValue}` };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The source field and the pick, before and after a change of either. */
|
|
36
|
+
export interface RelationPickChange {
|
|
37
|
+
source: unknown;
|
|
38
|
+
pick: unknown;
|
|
39
|
+
previousSource: unknown;
|
|
40
|
+
previousPick: unknown;
|
|
41
|
+
/** The source value the current pick was made (or loaded) with. */
|
|
42
|
+
pickSource: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Whether to clear the pick, and the source value the pick now goes with. */
|
|
46
|
+
export interface RelationPickOutcome {
|
|
47
|
+
shouldClear: boolean;
|
|
48
|
+
pickSource: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Whether a picker holds a value. */
|
|
52
|
+
function hasRelationPick(value: unknown): boolean {
|
|
53
|
+
return !isEmptyFormValue(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether a change of the source field voids the pick: made for another of
|
|
58
|
+
* its values. A pick changing at the same time (a record loading both
|
|
59
|
+
* values) or after goes with the current source; going back to the value the
|
|
60
|
+
* pick was made with keeps it.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveRelationPick(
|
|
63
|
+
change: RelationPickChange,
|
|
64
|
+
): RelationPickOutcome {
|
|
65
|
+
const isPickChange = !sameFormValue(change.pick, change.previousPick);
|
|
66
|
+
if (isPickChange || !hasRelationPick(change.pick)) {
|
|
67
|
+
return { shouldClear: false, pickSource: change.source };
|
|
68
|
+
}
|
|
69
|
+
const isSourceChange = !sameFormValue(change.source, change.previousSource);
|
|
70
|
+
const isPickSource = sameFormValue(change.source, change.pickSource);
|
|
71
|
+
return {
|
|
72
|
+
shouldClear: isSourceChange && !isPickSource,
|
|
73
|
+
pickSource: change.pickSource,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { DataTypeSelectSource } from "#dms-core/app/composables/data-types/useDataType";
|
|
2
|
+
import { pageRequestUrl } from "../page/pageUrlValues";
|
|
3
|
+
import { relationIdsQuery } from "./relationValue";
|
|
4
|
+
|
|
5
|
+
/** The keys a `select` route's row carries its label and value under by default. */
|
|
6
|
+
export const RELATION_LABEL_KEY = "label";
|
|
7
|
+
export const RELATION_VALUE_KEY = "value";
|
|
8
|
+
|
|
9
|
+
type RelationRow = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
interface RelationSelectResponse {
|
|
12
|
+
results?: RelationRow[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface RelationSourceOptions {
|
|
16
|
+
searchUrl?: unknown;
|
|
17
|
+
keyMapping?: DataTypeSelectSource["keyMapping"];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The `select` route a relation's control reads its rows from, and their key
|
|
22
|
+
* mapping: the relation data type's {@link DataTypeSelectSource}.
|
|
23
|
+
*/
|
|
24
|
+
export function relationSelectSource(
|
|
25
|
+
options: unknown,
|
|
26
|
+
): DataTypeSelectSource | undefined {
|
|
27
|
+
const opts = options as RelationSourceOptions | undefined;
|
|
28
|
+
if (typeof opts?.searchUrl !== "string") return undefined;
|
|
29
|
+
return { searchUrl: opts.searchUrl, keyMapping: opts.keyMapping };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The value a row of the source is picked by. */
|
|
33
|
+
export function relationRowValue(
|
|
34
|
+
source: DataTypeSelectSource,
|
|
35
|
+
row: RelationRow,
|
|
36
|
+
): unknown {
|
|
37
|
+
return row[source.keyMapping?.value || RELATION_VALUE_KEY];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The label a row of the source is named by. */
|
|
41
|
+
export function relationRowLabel(
|
|
42
|
+
source: DataTypeSelectSource,
|
|
43
|
+
row: RelationRow,
|
|
44
|
+
): unknown {
|
|
45
|
+
return row[source.keyMapping?.label || RELATION_LABEL_KEY];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* An id a relation's `select` route did not answer: a deleted row, or one out
|
|
50
|
+
* of the reader's reach — it cannot tell which, so it is shown as "Not
|
|
51
|
+
* found", never as "Deleted", with the id itself in its tooltip.
|
|
52
|
+
*/
|
|
53
|
+
export class NotFoundRelation {
|
|
54
|
+
constructor(readonly id: string) {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The ids of a read that its rows do not answer. */
|
|
58
|
+
export function notFoundRelationIds(
|
|
59
|
+
source: DataTypeSelectSource,
|
|
60
|
+
ids: string[],
|
|
61
|
+
rows: RelationRow[],
|
|
62
|
+
): string[] {
|
|
63
|
+
const answered = new Set(
|
|
64
|
+
rows.map((row) => String(relationRowValue(source, row))),
|
|
65
|
+
);
|
|
66
|
+
return ids.filter((id) => !answered.has(id));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Reads the rows of a relation's ids from its `select` route, narrowed with
|
|
71
|
+
* `?ids=` to those rows alone (matched on the mapped value key), whatever the
|
|
72
|
+
* route's page size. The one way the picker, the filter chips and the
|
|
73
|
+
* read-only lists name the ids they hold. A failed read answers no rows: the
|
|
74
|
+
* ids then show as not found, as a row out of the reader's reach does.
|
|
75
|
+
*/
|
|
76
|
+
export function useRelationRows() {
|
|
77
|
+
const { $authFetch } = useAuthFetch();
|
|
78
|
+
|
|
79
|
+
async function readRelationRows(
|
|
80
|
+
source: DataTypeSelectSource,
|
|
81
|
+
ids: string[],
|
|
82
|
+
): Promise<RelationRow[]> {
|
|
83
|
+
const url = pageRequestUrl(source.searchUrl);
|
|
84
|
+
if (!url || ids.length === 0) return [];
|
|
85
|
+
const response = await $authFetch<RelationSelectResponse>(url, {
|
|
86
|
+
params: relationIdsQuery(ids, source.keyMapping),
|
|
87
|
+
}).catch(() => undefined);
|
|
88
|
+
return response?.results ?? [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { readRelationRows };
|
|
92
|
+
}
|