@antelopejs/dms 0.9.7 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/README.md +1 -1
- package/data/module-store/catalog.json +2 -2
- package/dist/attachments/table-view.js +2 -2
- package/dist/attachments/table-view.js.map +1 -1
- package/dist/automation/actions.d.ts +2 -0
- package/dist/automation/actions.js +116 -71
- package/dist/automation/actions.js.map +1 -1
- package/dist/automation/i18n.d.ts +4 -0
- package/dist/automation/i18n.js +19 -0
- package/dist/automation/i18n.js.map +1 -0
- package/dist/automation/send-notification.d.ts +21 -0
- package/dist/automation/send-notification.js +163 -0
- package/dist/automation/send-notification.js.map +1 -0
- package/dist/automation/triggers.js +19 -25
- package/dist/automation/triggers.js.map +1 -1
- package/dist/client-origin-cors.d.ts +1 -0
- package/dist/client-origin-cors.js +19 -0
- package/dist/client-origin-cors.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +5 -3
- package/dist/config.js.map +1 -1
- package/dist/frontend-module.d.ts +4 -0
- package/dist/frontend-module.js +37 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/implementations/dms/client-ip.js +1 -1
- package/dist/implementations/dms/client-ip.js.map +1 -1
- package/dist/implementations/dms/dev-reload.d.ts +5 -0
- package/dist/implementations/dms/dev-reload.js +23 -3
- package/dist/implementations/dms/dev-reload.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +2 -4
- package/dist/implementations/dms/frontend-modules.js +22 -89
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +12 -2
- package/dist/implementations/dms/frontend-ready.js +34 -4
- package/dist/implementations/dms/frontend-ready.js.map +1 -1
- package/dist/implementations/dms/layout-banners.d.ts +2 -1
- package/dist/implementations/dms/layout-banners.js +9 -0
- package/dist/implementations/dms/layout-banners.js.map +1 -1
- package/dist/implementations/dms/manifest.d.ts +1 -2
- package/dist/implementations/dms/manifest.js +2 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +4 -3
- package/dist/implementations/dms/page.js +49 -18
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
- package/dist/implementations/dms/permissions-resolver.js +2 -2
- package/dist/implementations/dms/permissions-resolver.js.map +1 -1
- package/dist/implementations/dms/realtime.js +1 -0
- package/dist/implementations/dms/realtime.js.map +1 -1
- package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
- package/dist/implementations/dms-base/bulk-selection.js +23 -1
- package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
- package/dist/implementations/dms-base/filter-tokens.js +5 -3
- package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
- package/dist/implementations/dms-base/search-route.d.ts +1 -0
- package/dist/implementations/dms-base/search-route.js +35 -13
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/implementations/dms-base/table-view.js +11 -6
- package/dist/implementations/dms-base/table-view.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +2 -2
- package/dist/implementations/dms-notifications/index.js +5 -5
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/index.js +28 -45
- package/dist/index.js.map +1 -1
- package/dist/pages/modules/index.d.ts +1 -0
- package/dist/pages/modules/index.js +1 -0
- package/dist/pages/modules/index.js.map +1 -1
- package/dist/pages/modules/modules-store.d.ts +9 -0
- package/dist/pages/modules/modules-store.js +30 -0
- package/dist/pages/modules/modules-store.js.map +1 -0
- package/dist/pages/settings/users/account-credentials.js +2 -2
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/account-data-store.js +25 -8
- package/dist/pages/settings/users/account-data-store.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.js +9 -3
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/member-management.js +13 -14
- package/dist/pages/settings/users/member-management.js.map +1 -1
- package/dist/pages/settings/users/profile.d.ts +1 -4
- package/dist/pages/settings/users/profile.js +0 -35
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.js +26 -18
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/role-editor.d.ts +0 -1
- package/dist/pages/settings/users/role-editor.js +1 -2
- package/dist/pages/settings/users/role-editor.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +1 -2
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.js +2 -2
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/realtime/broker.d.ts +6 -0
- package/dist/realtime/broker.js +8 -1
- package/dist/realtime/broker.js.map +1 -1
- package/dist/realtime/presence.d.ts +8 -3
- package/dist/realtime/presence.js +8 -4
- package/dist/realtime/presence.js.map +1 -1
- package/dist/realtime/sessions.d.ts +11 -1
- package/dist/realtime/sessions.js +21 -3
- package/dist/realtime/sessions.js.map +1 -1
- package/dist/realtime/table-view.d.ts +2 -2
- package/dist/realtime/table-view.js +4 -3
- package/dist/realtime/table-view.js.map +1 -1
- package/dist/routes/auth/client-ip.d.ts +1 -0
- package/dist/routes/auth/client-ip.js +18 -0
- package/dist/routes/auth/client-ip.js.map +1 -1
- package/dist/routes/auth/oauth/config.d.ts +2 -1
- package/dist/routes/auth/oauth/config.js +5 -1
- package/dist/routes/auth/oauth/config.js.map +1 -1
- package/dist/routes/auth/session-handoff.d.ts +0 -1
- package/dist/routes/auth/session-handoff.js +0 -2
- package/dist/routes/auth/session-handoff.js.map +1 -1
- package/dist/routes/auth.js +3 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/realtime.d.ts +13 -1
- package/dist/routes/realtime.js +114 -29
- package/dist/routes/realtime.js.map +1 -1
- package/dist/startup-config.d.ts +2 -0
- package/dist/startup-config.js +62 -0
- package/dist/startup-config.js.map +1 -0
- package/dist/store/interfaces.d.ts +3 -2
- package/dist/store/interfaces.js +16 -6
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/listing.js +24 -13
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +4 -0
- package/dist/store/operations.js.map +1 -1
- package/dist/store/requirements.js +3 -2
- package/dist/store/requirements.js.map +1 -1
- package/dist/store/runtime.d.ts +10 -1
- package/dist/store/runtime.js +33 -6
- package/dist/store/runtime.js.map +1 -1
- package/dist/store/types.d.ts +10 -0
- package/dist/validation/auth.schema.d.ts +2 -2
- package/dist/validation/member-invite.schema.d.ts +2 -10
- package/dist/validation/member-invite.schema.js +2 -15
- package/dist/validation/member-invite.schema.js.map +1 -1
- package/frontend-vue/dms.frontend.ts +24 -5
- package/frontend-vue/dms.server.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
- package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
- package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
- package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
- package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
- package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
- package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
- package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
- package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
- package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
- package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
- package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
- package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
- package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
- package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
- package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
- package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
- package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
- package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
- package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
- package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
- package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
- package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
- package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
- package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
- package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
- package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
- package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
- package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
- package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
- package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
- package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
- package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
- package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
- package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
- package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
- package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
- package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
- package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
- package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
- package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
- package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
- package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
- package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
- package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
- package/frontend-vue/package.json +2 -2
- package/package.json +7 -6
- package/skills/dms/SKILL.md +1 -1
- package/skills/dms-auth/SKILL.md +5 -3
- package/skills/dms-best-practices/REFERENCE.md +2 -0
- package/skills/dms-best-practices/SKILL.md +4 -0
- package/skills/dms-module/SKILL.md +3 -2
- package/skills/dms-pages/REFERENCE.md +125 -16
- package/skills/dms-pages/SKILL.md +1 -1
- package/skills/dms-project/DEV-SERVERS.md +23 -1
- package/skills/dms-project/REFERENCE.md +1 -1
- package/skills/dms-project/SKILL.md +4 -1
- package/skills/dms-project/scripts/dms-dev.sh +205 -17
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { onMounted, shallowRef, watch } from "vue";
|
|
2
|
+
import type { DataTypeSelectSource } from "#dms-core/app/composables/data-types/useDataType";
|
|
3
|
+
import { unlabeledRelationIds } from "../data-types/relationValue";
|
|
4
|
+
import {
|
|
5
|
+
relationRowLabel,
|
|
6
|
+
relationRowValue,
|
|
7
|
+
useRelationRows,
|
|
8
|
+
} from "../data-types/relationRows";
|
|
9
|
+
|
|
10
|
+
/** A relation filter of a table: where its rows are read, and its value. */
|
|
11
|
+
export interface RelationFilterSource extends DataTypeSelectSource {
|
|
12
|
+
value: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const labelKey = (searchUrl: string, id: string) => `${searchUrl}\n${id}`;
|
|
16
|
+
|
|
17
|
+
// The plain ids a filter value holds, each once.
|
|
18
|
+
const NO_LABELS: ReadonlySet<string> = new Set();
|
|
19
|
+
const idsOf = (value: unknown) => unlabeledRelationIds(value, NO_LABELS);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The labels of the ids a table's relation filters hold: a chip names the
|
|
23
|
+
* related row ("Acme"), not the raw id it filters on ("cust-060"). Each id is
|
|
24
|
+
* read once from the relation's `select` route, as its picker does; an id the
|
|
25
|
+
* route does not answer (deleted, out of reach) keeps showing as is.
|
|
26
|
+
*/
|
|
27
|
+
export function useRelationFilterLabels(sources: () => RelationFilterSource[]) {
|
|
28
|
+
const { readRelationRows } = useRelationRows();
|
|
29
|
+
const labels = shallowRef<Record<string, string>>({});
|
|
30
|
+
const requested = new Set<string>();
|
|
31
|
+
|
|
32
|
+
async function loadLabels(source: RelationFilterSource, ids: string[]) {
|
|
33
|
+
for (const id of ids) requested.add(labelKey(source.searchUrl, id));
|
|
34
|
+
const rows = await readRelationRows(source, ids);
|
|
35
|
+
const found: Record<string, string> = {};
|
|
36
|
+
for (const row of rows) {
|
|
37
|
+
const id = relationRowValue(source, row);
|
|
38
|
+
const label = relationRowLabel(source, row);
|
|
39
|
+
if (id === undefined || label === undefined) continue;
|
|
40
|
+
found[labelKey(source.searchUrl, String(id))] = String(label);
|
|
41
|
+
}
|
|
42
|
+
if (Object.keys(found).length) {
|
|
43
|
+
labels.value = { ...labels.value, ...found };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const pending = () =>
|
|
48
|
+
sources().flatMap((source) => {
|
|
49
|
+
const ids = idsOf(source.value).filter(
|
|
50
|
+
(id) => !requested.has(labelKey(source.searchUrl, id)),
|
|
51
|
+
);
|
|
52
|
+
return ids.length ? [{ source, ids }] : [];
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// In the browser only: a server render draws the chips with their ids.
|
|
56
|
+
onMounted(() => {
|
|
57
|
+
watch(
|
|
58
|
+
pending,
|
|
59
|
+
(batches) => {
|
|
60
|
+
for (const { source, ids } of batches) void loadLabels(source, ids);
|
|
61
|
+
},
|
|
62
|
+
{ immediate: true },
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/** The labels of a relation filter's ids, in order; an unknown id as is. */
|
|
67
|
+
const labelsOf = (source: RelationFilterSource): string[] =>
|
|
68
|
+
idsOf(source.value).map(
|
|
69
|
+
(id) => labels.value[labelKey(source.searchUrl, id)] ?? id,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return { labelsOf };
|
|
73
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { watch, type Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
/** The attribute naming a body row's id, which the anchor finds it by. */
|
|
4
|
+
export const ROW_ID_ATTRIBUTE = "data-row-id";
|
|
5
|
+
|
|
6
|
+
const SCROLLABLE_OVERFLOW = /(auto|scroll)/;
|
|
7
|
+
|
|
8
|
+
/** The row the reader has at the top of the view, before an update. */
|
|
9
|
+
interface RowAnchor {
|
|
10
|
+
id: string;
|
|
11
|
+
row: Element;
|
|
12
|
+
scroller: HTMLElement;
|
|
13
|
+
/** Where the row stood, in viewport pixels. */
|
|
14
|
+
top: number;
|
|
15
|
+
/** Its position among the rows. */
|
|
16
|
+
index: number;
|
|
17
|
+
previousIds: ReadonlySet<string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The element the table scrolls in: itself, or its nearest scrolled ancestor. */
|
|
21
|
+
function findScroller(start: HTMLElement): HTMLElement | null {
|
|
22
|
+
for (
|
|
23
|
+
let element: HTMLElement | null = start;
|
|
24
|
+
element;
|
|
25
|
+
element = element.parentElement
|
|
26
|
+
) {
|
|
27
|
+
if (
|
|
28
|
+
SCROLLABLE_OVERFLOW.test(getComputedStyle(element).overflowY) &&
|
|
29
|
+
element.scrollHeight > element.clientHeight
|
|
30
|
+
)
|
|
31
|
+
return element;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function captureAnchor(
|
|
37
|
+
root: HTMLElement | null,
|
|
38
|
+
ids: readonly string[],
|
|
39
|
+
): RowAnchor | null {
|
|
40
|
+
if (!root) return null;
|
|
41
|
+
const scroller = findScroller(root);
|
|
42
|
+
// At the top of the list, new rows land in view: what a live list is for.
|
|
43
|
+
if (!scroller || scroller.scrollTop <= 0) return null;
|
|
44
|
+
const viewTop = scroller.getBoundingClientRect().top;
|
|
45
|
+
for (const row of root.querySelectorAll(`tr[${ROW_ID_ATTRIBUTE}]`)) {
|
|
46
|
+
const top = row.getBoundingClientRect().top;
|
|
47
|
+
if (top < viewTop) continue;
|
|
48
|
+
const id = row.getAttribute(ROW_ID_ATTRIBUTE)!;
|
|
49
|
+
return {
|
|
50
|
+
id,
|
|
51
|
+
row,
|
|
52
|
+
scroller,
|
|
53
|
+
top,
|
|
54
|
+
index: ids.indexOf(id),
|
|
55
|
+
previousIds: new Set(ids),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Rows were only added above the anchor (a live list's new rows): a sort, a
|
|
62
|
+
// filter or another page moves the reader on, and is left alone.
|
|
63
|
+
function isPrepend(anchor: RowAnchor, ids: readonly string[]): boolean {
|
|
64
|
+
const index = ids.indexOf(anchor.id);
|
|
65
|
+
if (anchor.index < 0 || index <= anchor.index) return false;
|
|
66
|
+
const added = ids
|
|
67
|
+
.slice(0, index)
|
|
68
|
+
.filter((id) => !anchor.previousIds.has(id)).length;
|
|
69
|
+
return added === index - anchor.index;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Keeps the rows the reader is looking at where they are when rows are added
|
|
74
|
+
* above them (a live list's new rows), by scrolling as far as the rows push
|
|
75
|
+
* them, in the frame they land. The browser's own scroll anchoring does not
|
|
76
|
+
* hold a table's rows. At the top of the list nothing is held: the new rows
|
|
77
|
+
* show, as a live list means them to.
|
|
78
|
+
*
|
|
79
|
+
* @param root The table, whose body rows carry `ROW_ID_ATTRIBUTE`
|
|
80
|
+
* @param rowIds The ids of the body rows, in order
|
|
81
|
+
*/
|
|
82
|
+
export function useRowScrollAnchor(
|
|
83
|
+
root: Readonly<Ref<HTMLElement | null>>,
|
|
84
|
+
rowIds: () => readonly string[],
|
|
85
|
+
): void {
|
|
86
|
+
if (typeof window === "undefined") return;
|
|
87
|
+
let anchor: RowAnchor | null = null;
|
|
88
|
+
watch(
|
|
89
|
+
rowIds,
|
|
90
|
+
(_next, previous) => {
|
|
91
|
+
anchor = captureAnchor(root.value, previous ?? []);
|
|
92
|
+
},
|
|
93
|
+
{ flush: "pre" },
|
|
94
|
+
);
|
|
95
|
+
watch(
|
|
96
|
+
rowIds,
|
|
97
|
+
(next) => {
|
|
98
|
+
const held = anchor;
|
|
99
|
+
anchor = null;
|
|
100
|
+
if (!held || !held.row.isConnected || !isPrepend(held, next)) return;
|
|
101
|
+
const moved = held.row.getBoundingClientRect().top - held.top;
|
|
102
|
+
if (moved !== 0) held.scroller.scrollTop += moved;
|
|
103
|
+
},
|
|
104
|
+
{ flush: "post" },
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -185,6 +185,15 @@ export const useTable = <T extends Data>(props: UseTableProps<T>) => {
|
|
|
185
185
|
},
|
|
186
186
|
);
|
|
187
187
|
|
|
188
|
+
// A display that cannot sort (a hand-ordered table) leaves every header
|
|
189
|
+
// without its sort button, arrow and menu items.
|
|
190
|
+
watch(
|
|
191
|
+
() => props.tableProps.sortingOptions?.enableSorting,
|
|
192
|
+
(enableSorting) => {
|
|
193
|
+
table.setOptions((prev) => ({ ...prev, enableSorting }));
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
|
|
188
197
|
const labeledColumns: ComputedRef<LabeledColumn<T>[]> = computed(() => {
|
|
189
198
|
return table
|
|
190
199
|
.getAllColumns()
|
|
@@ -471,9 +471,8 @@ export const useTableColumns = <T extends Data>(config: ColumnConfig<T>) => {
|
|
|
471
471
|
|
|
472
472
|
const isShowingArchived = () => config.showArchived?.value === true;
|
|
473
473
|
|
|
474
|
-
// The backend serializes `isVisible`; `visible` is the former spelling.
|
|
475
474
|
const isCustomActionInline = (action: CustomRowAction): boolean =>
|
|
476
|
-
action.isVisible === true
|
|
475
|
+
action.isVisible === true;
|
|
477
476
|
|
|
478
477
|
const { confirmAction } = useActionConfirm();
|
|
479
478
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type Translate = (
|
|
2
|
+
key: string,
|
|
3
|
+
named: Record<string, unknown>,
|
|
4
|
+
plural: number,
|
|
5
|
+
) => string;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The caption count while a table lists its archived rows. The default
|
|
9
|
+
* ("Archived: 13", "Archives : 13") agrees with no noun, so it reads right
|
|
10
|
+
* over invoices as over members; a table view's `archivedCountLabel` names
|
|
11
|
+
* its rows instead: a `$` key receiving `{ count }` and pluralized on it, or
|
|
12
|
+
* a text whose `{count}` is replaced.
|
|
13
|
+
*/
|
|
14
|
+
export function formatArchivedCount(
|
|
15
|
+
translate: Translate,
|
|
16
|
+
count: number,
|
|
17
|
+
formattedCount: string,
|
|
18
|
+
label?: string,
|
|
19
|
+
): string {
|
|
20
|
+
if (!label) {
|
|
21
|
+
return translate(
|
|
22
|
+
"dms.table.archived_count",
|
|
23
|
+
{ count: formattedCount },
|
|
24
|
+
count,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
if (label.startsWith("$")) {
|
|
28
|
+
return translate(label.slice(1), { count: formattedCount }, count);
|
|
29
|
+
}
|
|
30
|
+
return label.replaceAll("{count}", formattedCount);
|
|
31
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DataType,
|
|
3
|
+
DataTypeFilterEditorValue,
|
|
4
|
+
} from "#dms-core/app/composables/data-types/useDataType";
|
|
5
|
+
|
|
6
|
+
// A view or tab declares its filter value as text (`TableViewTabFilter.value`),
|
|
7
|
+
// the shape the query sends. The chip's value editor wants the value its
|
|
8
|
+
// column's data type holds there ({@link DataType.filterEditorValue}). The
|
|
9
|
+
// editor reads the converted value; the filter keeps its text until the user
|
|
10
|
+
// edits it, so the query is unchanged.
|
|
11
|
+
|
|
12
|
+
const LIST_SEPARATOR = ",";
|
|
13
|
+
const BETWEEN_MODE = "is_between";
|
|
14
|
+
|
|
15
|
+
/** A number type's filter: the number a numeric text names, else the text. */
|
|
16
|
+
export const numberFilterEditorValue: DataTypeFilterEditorValue = (text) => {
|
|
17
|
+
const parsed = Number(text);
|
|
18
|
+
return text.trim() !== "" && Number.isFinite(parsed) ? parsed : text;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** A date's filter: the two days of a range, a single day as written. */
|
|
22
|
+
export const dateFilterEditorValue: DataTypeFilterEditorValue = (text, mode) =>
|
|
23
|
+
mode === BETWEEN_MODE
|
|
24
|
+
? text.split(LIST_SEPARATOR).map((item) => item.trim())
|
|
25
|
+
: text;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The value a filter's editor reads: a declared text value converted by its
|
|
29
|
+
* column's data type, any other value as it is.
|
|
30
|
+
*/
|
|
31
|
+
export function filterEditorValue(
|
|
32
|
+
dataType: Pick<DataType, "filterEditorValue"> | undefined,
|
|
33
|
+
mode: string,
|
|
34
|
+
value: unknown,
|
|
35
|
+
): unknown {
|
|
36
|
+
if (typeof value !== "string" || !dataType?.filterEditorValue) return value;
|
|
37
|
+
return dataType.filterEditorValue(value, mode);
|
|
38
|
+
}
|
|
@@ -6,6 +6,32 @@ interface FilterColumn {
|
|
|
6
6
|
type?: DataTypeConfig;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
10
|
+
// `{{now}}`, `{{now-7d}}`, `{{now+14d}}`: the tokens the server resolves in a
|
|
11
|
+
// filter value (`filter-tokens.ts`), read here with the same rule.
|
|
12
|
+
const NOW_TOKEN = /\{\{\s*now(?:\s*([+-])\s*(\d+)d)?\s*\}\}/g;
|
|
13
|
+
const BETWEEN_MODE = "is_between";
|
|
14
|
+
const BETWEEN_SEPARATOR = ",";
|
|
15
|
+
|
|
16
|
+
/** A filter value with its `{{now±Nd}}` tokens replaced by that instant. */
|
|
17
|
+
export function resolveNowTokens(value: string, now = Date.now()): string {
|
|
18
|
+
return value.replace(NOW_TOKEN, (_match, sign?: string, days?: string) => {
|
|
19
|
+
const offset = sign ? Number(`${sign}${days}`) * DAY_MS : 0;
|
|
20
|
+
return new Date(now + offset).toISOString();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The value as the formatters read it: tokens resolved, and an `is_between`
|
|
26
|
+
* bound pair written as text (`a,b`, the way the query sends it) split.
|
|
27
|
+
*/
|
|
28
|
+
const readableValue = (filter: TableFilter): unknown => {
|
|
29
|
+
if (typeof filter.value !== "string") return filter.value;
|
|
30
|
+
const value = resolveNowTokens(filter.value);
|
|
31
|
+
if (filter.mode !== BETWEEN_MODE) return value;
|
|
32
|
+
return value.split(BETWEEN_SEPARATOR).map((bound) => bound.trim());
|
|
33
|
+
};
|
|
34
|
+
|
|
9
35
|
export function formatFilterValue(
|
|
10
36
|
filter: TableFilter,
|
|
11
37
|
columns: FilterColumn[],
|
|
@@ -23,9 +49,14 @@ export function formatFilterValue(
|
|
|
23
49
|
const formatter =
|
|
24
50
|
dataType.formatter[filter.mode] || dataType.formatter.default;
|
|
25
51
|
|
|
52
|
+
// A filter's value need not have the column's shape (a tag typed against
|
|
53
|
+
// a tags column, whose formatter draws a list): a formatter that writes
|
|
54
|
+
// nothing for it leaves the value as typed.
|
|
26
55
|
try {
|
|
27
|
-
const result = formatter(filter
|
|
28
|
-
return typeof result === "string"
|
|
56
|
+
const result = formatter(readableValue(filter), locale);
|
|
57
|
+
return typeof result === "string" && result !== ""
|
|
58
|
+
? result
|
|
59
|
+
: String(filter.value);
|
|
29
60
|
} catch {
|
|
30
61
|
return String(filter.value);
|
|
31
62
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Which sides of a scroll area hide content the reader can scroll to. */
|
|
2
|
+
export interface ScrollEdges {
|
|
3
|
+
start: boolean;
|
|
4
|
+
end: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** What {@link horizontalScrollEdges} reads off a scroll area. */
|
|
8
|
+
export interface HorizontalScroll {
|
|
9
|
+
scrollLeft: number;
|
|
10
|
+
scrollWidth: number;
|
|
11
|
+
clientWidth: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Sub-pixel widths leave a fraction of a pixel to scroll at either end.
|
|
15
|
+
const EDGE_TOLERANCE_PX = 1;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The sides of a table wider than its card that still hide columns: a phone
|
|
19
|
+
* shows the first columns, and nothing else would tell that the rest are a
|
|
20
|
+
* swipe away. The scroll position is read as the reader sees it, so a
|
|
21
|
+
* right-to-left page (negative `scrollLeft`) reads the same.
|
|
22
|
+
*/
|
|
23
|
+
export function horizontalScrollEdges(area: HorizontalScroll): ScrollEdges {
|
|
24
|
+
const hidden = area.scrollWidth - area.clientWidth;
|
|
25
|
+
if (hidden <= EDGE_TOLERANCE_PX) return { start: false, end: false };
|
|
26
|
+
const scrolled = Math.abs(area.scrollLeft);
|
|
27
|
+
return {
|
|
28
|
+
start: scrolled > EDGE_TOLERANCE_PX,
|
|
29
|
+
end: scrolled < hidden - EDGE_TOLERANCE_PX,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { get } from "@nuxt/ui/runtime/utils/index.js";
|
|
2
|
+
import { h, type VNodeChild } from "vue";
|
|
3
|
+
import type { TableViewColumn } from "../../../composables/table-view/types/column";
|
|
4
|
+
import { useColumnValueRenderer } from "../data-types/useColumnValueRenderer";
|
|
5
|
+
|
|
6
|
+
type Row = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Draws a row's fields on a card (the cards display, the kanban board) the
|
|
10
|
+
* way the grid draws its cells: through the column's `display`, else its data
|
|
11
|
+
* type, handed the row for what a display reads beside the value (a progress
|
|
12
|
+
* bar's total, a tone, an avatar).
|
|
13
|
+
*/
|
|
14
|
+
export function useCardFields() {
|
|
15
|
+
const { renderColumnValue } = useColumnValueRenderer();
|
|
16
|
+
|
|
17
|
+
const renderField = (column: TableViewColumn, item: Row): VNodeChild => {
|
|
18
|
+
const rendered = renderColumnValue(column, item, "cell");
|
|
19
|
+
if (typeof rendered !== "string" && typeof rendered !== "number") {
|
|
20
|
+
return rendered;
|
|
21
|
+
}
|
|
22
|
+
const value = get(item, column.accessorKey);
|
|
23
|
+
// The fallback of a missing value reads dimmed, as an empty cell does.
|
|
24
|
+
const isMissing = value === null || value === undefined;
|
|
25
|
+
return h(
|
|
26
|
+
"span",
|
|
27
|
+
{ class: isMissing ? "text-dimmed truncate" : "truncate" },
|
|
28
|
+
String(rendered),
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return { renderField };
|
|
33
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { onMounted, type Ref } from "vue";
|
|
2
|
+
import type { TableViewViewStateConfig } from "../../../composables/table-view/types";
|
|
3
|
+
|
|
4
|
+
/** The viewports a table view opens on its `mobileDisplay`: a phone's. */
|
|
5
|
+
export const PHONE_VIEWPORT_QUERY = "(max-width: 767px)";
|
|
6
|
+
|
|
7
|
+
export interface MobileDisplayOptions {
|
|
8
|
+
/** The table view's `mobileDisplay`, if any. */
|
|
9
|
+
mobileDisplay: string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The display the table counts as its default, starting at its resolved
|
|
12
|
+
* `defaultDisplay`: the phone's once applied, so it is not saved as the
|
|
13
|
+
* user's choice, and a view that sets no display opens on it.
|
|
14
|
+
*/
|
|
15
|
+
defaultDisplay: Ref<string>;
|
|
16
|
+
/** The ids of the displays the table view offers. */
|
|
17
|
+
offered: ReadonlySet<string>;
|
|
18
|
+
/** The display shown. */
|
|
19
|
+
display: Ref<string>;
|
|
20
|
+
/**
|
|
21
|
+
* The view open, if any. One that fixes a display keeps it; one that
|
|
22
|
+
* leaves the display out (a `defaultView` showing every row) does not.
|
|
23
|
+
*/
|
|
24
|
+
activeView: Ref<TableViewViewStateConfig | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* The display shown is someone's choice — the user saved one, the default
|
|
27
|
+
* display is the only way in: a phone keeps it.
|
|
28
|
+
*/
|
|
29
|
+
isDisplayChosen: () => boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Opens a phone on the table view's `mobileDisplay` once mounted, the
|
|
34
|
+
* server render having drawn the default display, when nobody chose one.
|
|
35
|
+
* Called after the table's views, so a view resolved once mounted is open.
|
|
36
|
+
*/
|
|
37
|
+
export function useMobileDisplay(options: MobileDisplayOptions): void {
|
|
38
|
+
onMounted(() => {
|
|
39
|
+
const { mobileDisplay, offered } = options;
|
|
40
|
+
if (!mobileDisplay || !offered.has(mobileDisplay)) return;
|
|
41
|
+
if (options.isDisplayChosen()) return;
|
|
42
|
+
const viewDisplay = options.activeView.value?.display;
|
|
43
|
+
if (viewDisplay && offered.has(viewDisplay)) return;
|
|
44
|
+
if (!window.matchMedia?.(PHONE_VIEWPORT_QUERY).matches) return;
|
|
45
|
+
options.defaultDisplay.value = mobileDisplay;
|
|
46
|
+
options.display.value = mobileDisplay;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
import { onBeforeUnmount, onMounted, ref, type Ref } from "vue";
|
|
2
|
+
import { scrollParent } from "../../utils/scrollWithin";
|
|
2
3
|
|
|
3
4
|
const FIRST_ROW_SELECTOR = "tbody tr";
|
|
4
|
-
const SCROLLABLE_OVERFLOW = /(auto|scroll)/;
|
|
5
|
-
|
|
6
|
-
function scrollParent(element: HTMLElement): HTMLElement | null {
|
|
7
|
-
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
8
|
-
const { overflowY } = getComputedStyle(node);
|
|
9
|
-
if (
|
|
10
|
-
SCROLLABLE_OVERFLOW.test(overflowY) &&
|
|
11
|
-
node.scrollHeight > node.clientHeight
|
|
12
|
-
) {
|
|
13
|
-
return node;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
5
|
|
|
19
6
|
/**
|
|
20
7
|
* Whether the first row of the table in `element` is out of view above the
|
|
@@ -29,6 +16,21 @@ export function isFirstRowScrolledPast(element: HTMLElement): boolean {
|
|
|
29
16
|
return row.getBoundingClientRect().bottom <= viewTop;
|
|
30
17
|
}
|
|
31
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Whether the table in `element` is on screen or above it: its top stands
|
|
21
|
+
* above the bottom of the area it scrolls in (its scrolling parent, else the
|
|
22
|
+
* window). Rows added to such a table move rows the user can see, or did.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export function isTableReached(element: HTMLElement): boolean {
|
|
27
|
+
const scroller = scrollParent(element);
|
|
28
|
+
const viewBottom = scroller
|
|
29
|
+
? scroller.getBoundingClientRect().bottom
|
|
30
|
+
: window.innerHeight;
|
|
31
|
+
return element.getBoundingClientRect().top < viewBottom;
|
|
32
|
+
}
|
|
33
|
+
|
|
32
34
|
/**
|
|
33
35
|
* Follows whether the user scrolled the table's first row out of view, on
|
|
34
36
|
* any scroll of the page (read once per frame). Inert when `isEnabled` is
|
|
@@ -40,13 +42,34 @@ export function useScrolledPastTop(
|
|
|
40
42
|
element: () => HTMLElement | null,
|
|
41
43
|
isEnabled: boolean,
|
|
42
44
|
): Ref<boolean> {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
return useScrollMeasure(element, isEnabled, isFirstRowScrolledPast);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Follows whether the table is on screen or scrolled past (`isTableReached`),
|
|
50
|
+
* like `useScrolledPastTop`.
|
|
51
|
+
*
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export function useTableReached(
|
|
55
|
+
element: () => HTMLElement | null,
|
|
56
|
+
isEnabled: boolean,
|
|
57
|
+
): Ref<boolean> {
|
|
58
|
+
return useScrollMeasure(element, isEnabled, isTableReached);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function useScrollMeasure(
|
|
62
|
+
element: () => HTMLElement | null,
|
|
63
|
+
isEnabled: boolean,
|
|
64
|
+
test: (element: HTMLElement) => boolean,
|
|
65
|
+
): Ref<boolean> {
|
|
66
|
+
const isMatching = ref(false);
|
|
67
|
+
if (!isEnabled || typeof window === "undefined") return isMatching;
|
|
45
68
|
let frame: number | null = null;
|
|
46
69
|
const measure = () => {
|
|
47
70
|
frame = null;
|
|
48
71
|
const target = element();
|
|
49
|
-
|
|
72
|
+
isMatching.value = !!target && test(target);
|
|
50
73
|
};
|
|
51
74
|
const onScroll = () => {
|
|
52
75
|
frame ??= requestAnimationFrame(measure);
|
|
@@ -62,5 +85,5 @@ export function useScrolledPastTop(
|
|
|
62
85
|
window.removeEventListener("scroll", onScroll, { capture: true });
|
|
63
86
|
if (frame !== null) cancelAnimationFrame(frame);
|
|
64
87
|
});
|
|
65
|
-
return
|
|
88
|
+
return isMatching;
|
|
66
89
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readableNumberFormat } from "#dms-core/app/utils/formatter";
|
|
1
2
|
import type { ComputedRef } from "vue";
|
|
2
3
|
import type {
|
|
3
4
|
TableViewColumn,
|
|
@@ -69,7 +70,7 @@ export function useTableFooter(options: TableFooterOptions) {
|
|
|
69
70
|
) => {
|
|
70
71
|
if (value === undefined) return undefined;
|
|
71
72
|
if (summary.format) {
|
|
72
|
-
return
|
|
73
|
+
return readableNumberFormat(locale.value, summary.format).format(value);
|
|
73
74
|
}
|
|
74
75
|
const column =
|
|
75
76
|
summary.op === "sum"
|
|
@@ -77,7 +78,7 @@ export function useTableFooter(options: TableFooterOptions) {
|
|
|
77
78
|
: undefined;
|
|
78
79
|
return column
|
|
79
80
|
? renderValue(column, value, undefined)
|
|
80
|
-
:
|
|
81
|
+
: readableNumberFormat(locale.value).format(value);
|
|
81
82
|
};
|
|
82
83
|
|
|
83
84
|
const legendColumn = columns.find(
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { router as inertiaRouter } from "@inertiajs/vue3";
|
|
2
|
+
import { onBeforeUnmount, onMounted } from "vue";
|
|
3
|
+
import type { QueryParamFilters } from "../../../composables/table-view/types/config";
|
|
4
|
+
import { isLinkedVisit } from "./utils/linkedArrival";
|
|
5
|
+
import { queryOfUrl } from "./utils/urlFilters";
|
|
6
|
+
import type { TableUrlScope } from "./utils/views";
|
|
7
|
+
|
|
8
|
+
const URL_BASE = "http://dms.invalid";
|
|
9
|
+
|
|
10
|
+
/** What `useTableLinkedVisit` reads a visit against, and what it calls. */
|
|
11
|
+
export interface TableLinkedVisitOptions {
|
|
12
|
+
route: { path: string; query: Record<string, unknown> };
|
|
13
|
+
urlScope: TableUrlScope;
|
|
14
|
+
queryParamFilters: QueryParamFilters | undefined;
|
|
15
|
+
activeView: () => string | undefined;
|
|
16
|
+
activeTab: () => string;
|
|
17
|
+
canOpenView: (id: string) => boolean;
|
|
18
|
+
canOpenTab: (id: string) => boolean;
|
|
19
|
+
/** Opens the table on what the visit's link names, as an arrival would. */
|
|
20
|
+
follow: (query: Record<string, unknown>) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Follows a link to the page the table is already shown on (a notification,
|
|
25
|
+
* a menu entry, back and forward): Inertia keeps the page mounted, so the
|
|
26
|
+
* table reads the visit's query as an arrival would (`isLinkedVisit`). A
|
|
27
|
+
* link clicked is announced as a client visit pushed, even to the URL shown,
|
|
28
|
+
* which Inertia turns into a replace without a `navigate` event; a page
|
|
29
|
+
* control rebuilding the query replaces it, and is no link. A visit to
|
|
30
|
+
* another page is left alone: the table is about to unmount.
|
|
31
|
+
*/
|
|
32
|
+
export function useTableLinkedVisit(options: TableLinkedVisitOptions): void {
|
|
33
|
+
// The page the table is shown on, as the visit that mounted it reached it.
|
|
34
|
+
const pathname = options.route.path;
|
|
35
|
+
let previousQuery: Record<string, unknown> = { ...options.route.query };
|
|
36
|
+
// A pushed visit announces itself twice when it changes the URL.
|
|
37
|
+
let followedVisitId: string | undefined;
|
|
38
|
+
const stops: Array<() => void> = [];
|
|
39
|
+
|
|
40
|
+
const read = (
|
|
41
|
+
url: string,
|
|
42
|
+
visitId: string | undefined,
|
|
43
|
+
isLinkClick: boolean,
|
|
44
|
+
) => {
|
|
45
|
+
const query = queryOfUrl(url);
|
|
46
|
+
const lastQuery = previousQuery;
|
|
47
|
+
previousQuery = query;
|
|
48
|
+
if (new URL(url, URL_BASE).pathname !== pathname) return;
|
|
49
|
+
if (visitId !== undefined && visitId === followedVisitId) return;
|
|
50
|
+
const isLink = isLinkedVisit({
|
|
51
|
+
query,
|
|
52
|
+
isLinkClick,
|
|
53
|
+
previousQuery: lastQuery,
|
|
54
|
+
urlScope: options.urlScope,
|
|
55
|
+
queryParamFilters: options.queryParamFilters,
|
|
56
|
+
activeView: options.activeView(),
|
|
57
|
+
activeTab: options.activeTab(),
|
|
58
|
+
canOpenView: options.canOpenView,
|
|
59
|
+
canOpenTab: options.canOpenTab,
|
|
60
|
+
});
|
|
61
|
+
if (!isLink) return;
|
|
62
|
+
followedVisitId = visitId;
|
|
63
|
+
options.follow(query);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
onMounted(() => {
|
|
67
|
+
stops.push(
|
|
68
|
+
inertiaRouter.on("navigate", (event) =>
|
|
69
|
+
read(event.detail.page.url, event.detail.visitId, false),
|
|
70
|
+
),
|
|
71
|
+
inertiaRouter.on("clientVisit", (event) => {
|
|
72
|
+
if (event.detail.replace) return;
|
|
73
|
+
read(event.detail.page.url, event.detail.visitId, true);
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
onBeforeUnmount(() => stops.forEach((stop) => stop()));
|
|
78
|
+
}
|