@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
|
@@ -90,10 +90,20 @@ export interface BulkActionOutcome {
|
|
|
90
90
|
|
|
91
91
|
// The count each bulk route answers with: delete sends the number of deleted
|
|
92
92
|
// rows (or `{ deleted }` when a row rule filtered out every id), archive and
|
|
93
|
-
// restore send `{ archivedCount }` / `{ restoredCount }
|
|
94
|
-
|
|
93
|
+
// restore send `{ archivedCount }` / `{ restoredCount }`, and a custom bulk
|
|
94
|
+
// route `{ count }`.
|
|
95
|
+
const COUNT_KEYS = [
|
|
96
|
+
"count",
|
|
97
|
+
"deleted",
|
|
98
|
+
"archivedCount",
|
|
99
|
+
"restoredCount",
|
|
100
|
+
] as const;
|
|
95
101
|
|
|
96
|
-
|
|
102
|
+
/**
|
|
103
|
+
* The number of rows a bulk route says it changed: a bare number or one of
|
|
104
|
+
* its count keys, `undefined` when it does not say.
|
|
105
|
+
*/
|
|
106
|
+
export const readBulkCount = (response: unknown): number | undefined => {
|
|
97
107
|
if (typeof response === "number") return response;
|
|
98
108
|
if (!response || typeof response !== "object") return undefined;
|
|
99
109
|
for (const key of COUNT_KEYS) {
|
|
@@ -112,7 +122,7 @@ export function bulkActionOutcome(
|
|
|
112
122
|
response: unknown,
|
|
113
123
|
requested: number,
|
|
114
124
|
): BulkActionOutcome {
|
|
115
|
-
const count =
|
|
125
|
+
const count = readBulkCount(response);
|
|
116
126
|
const processed =
|
|
117
127
|
count === undefined ? requested : Math.min(Math.max(count, 0), requested);
|
|
118
128
|
return { processed, skipped: requested - processed };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CSSProperties } from "vue";
|
|
1
2
|
import { get } from "@nuxt/ui/runtime/utils/index.js";
|
|
2
3
|
import type {
|
|
3
4
|
TableViewCardProps,
|
|
@@ -68,8 +69,132 @@ export function cardFieldColumns(
|
|
|
68
69
|
]);
|
|
69
70
|
return columns
|
|
70
71
|
.filter(
|
|
71
|
-
(column) =>
|
|
72
|
-
column.listable !== false && !skipped.has(column.accessorKey),
|
|
72
|
+
(column) => column.listable !== false && !skipped.has(column.accessorKey),
|
|
73
73
|
)
|
|
74
74
|
.slice(0, FALLBACK_CARD_FIELD_COUNT);
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
/** The id of the data type drawing a name with its avatar and subtitle. */
|
|
78
|
+
const IDENTITY_DISPLAY_TYPE = "identity";
|
|
79
|
+
|
|
80
|
+
/** The database id: a technical key, never worth a line on a card. */
|
|
81
|
+
const DATABASE_ID_KEY = "_id";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* What a built-in card reads off its row besides its title: the subtitle and
|
|
85
|
+
* the avatar the title column's `identity` display declares, so a card shows
|
|
86
|
+
* the row as its grid cell does.
|
|
87
|
+
*/
|
|
88
|
+
export interface CardIdentity {
|
|
89
|
+
/** Row field drawn under the title. */
|
|
90
|
+
subtitleField?: string;
|
|
91
|
+
/** Row field holding the avatar: an image value or a URL. */
|
|
92
|
+
avatarField?: string;
|
|
93
|
+
/** File storage the avatar is read from. */
|
|
94
|
+
storage?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const optionString = (
|
|
98
|
+
options: Record<string, unknown> | undefined,
|
|
99
|
+
key: string,
|
|
100
|
+
): string | undefined =>
|
|
101
|
+
typeof options?.[key] === "string" ? options[key] : undefined;
|
|
102
|
+
|
|
103
|
+
/** The identity the title column (`labelKey`) declares, if it is one. */
|
|
104
|
+
export function cardIdentity(
|
|
105
|
+
columns: TableViewColumn[],
|
|
106
|
+
labelKey: string | undefined,
|
|
107
|
+
): CardIdentity {
|
|
108
|
+
if (!labelKey) return {};
|
|
109
|
+
const display = columns.find(
|
|
110
|
+
(column) => column.accessorKey === labelKey,
|
|
111
|
+
)?.display;
|
|
112
|
+
if (display?.type !== IDENTITY_DISPLAY_TYPE) return {};
|
|
113
|
+
return {
|
|
114
|
+
subtitleField: optionString(display.options, "subtitleField"),
|
|
115
|
+
avatarField: optionString(display.options, "avatarField"),
|
|
116
|
+
storage: optionString(display.options, "storage"),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const hasValue = (value: unknown): boolean =>
|
|
121
|
+
value !== undefined && value !== null && value !== "";
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The line a card draws under its title, as the row holds it: the identity's
|
|
125
|
+
* subtitle, else the row's id when the table names it with a key of its own
|
|
126
|
+
* (`rowIdKey: "code"`) and the title is not it already. The database `_id`
|
|
127
|
+
* is never shown: a UUID or `proj-074` tells the reader nothing.
|
|
128
|
+
*/
|
|
129
|
+
export function cardSubtitle<T>(
|
|
130
|
+
item: T,
|
|
131
|
+
identity: CardIdentity,
|
|
132
|
+
shown: CardShownKeys,
|
|
133
|
+
): unknown {
|
|
134
|
+
const row = item as Record<string, unknown>;
|
|
135
|
+
if (identity.subtitleField) {
|
|
136
|
+
const subtitle: unknown = get(row, identity.subtitleField);
|
|
137
|
+
if (hasValue(subtitle)) return subtitle;
|
|
138
|
+
}
|
|
139
|
+
if (shown.rowIdKey === DATABASE_ID_KEY) return undefined;
|
|
140
|
+
if (!shown.labelKey || !hasValue(get(row, shown.labelKey))) return undefined;
|
|
141
|
+
const id = cardRowId(item, shown.rowIdKey);
|
|
142
|
+
return id || undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The avatar a card draws in place of the title's initials, if any. */
|
|
146
|
+
export function cardAvatar<T>(
|
|
147
|
+
item: T,
|
|
148
|
+
identity: CardIdentity,
|
|
149
|
+
): { key: string } | string | undefined {
|
|
150
|
+
if (!identity.avatarField) return undefined;
|
|
151
|
+
const avatar: unknown = get(
|
|
152
|
+
item as Record<string, unknown>,
|
|
153
|
+
identity.avatarField,
|
|
154
|
+
);
|
|
155
|
+
if (typeof avatar === "string") return avatar || undefined;
|
|
156
|
+
if (avatar && typeof avatar === "object" && "key" in avatar) {
|
|
157
|
+
const { key } = avatar as { key: unknown };
|
|
158
|
+
return typeof key === "string" && key ? { key } : undefined;
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The narrowest a card of the `cards` display gets (backend `card.minWidth`). */
|
|
164
|
+
export const DEFAULT_CARD_MIN_WIDTH = "230px";
|
|
165
|
+
|
|
166
|
+
// The grid's gap and side padding, as `CARD_GRID_CLASS` sets them: the width
|
|
167
|
+
// cap below adds them to the cards' own.
|
|
168
|
+
const CARD_GRID_GAP = "0.75rem";
|
|
169
|
+
const CARD_GRID_PADDING = "36px";
|
|
170
|
+
|
|
171
|
+
// How much wider than `card.minWidth` a card may stretch when the row has room
|
|
172
|
+
// for more cards than the list holds. A full row hardly reaches it: n cards
|
|
173
|
+
// fit, but not n + 1, so each one stays under twice the minimum, give or take
|
|
174
|
+
// a gap.
|
|
175
|
+
const CARD_MAX_WIDTH_FACTOR = 2;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The cards grid: auto-fit tracks of at least `--dms-card-min-width` (the
|
|
179
|
+
* whole row on a narrower screen), so a short list's empty tracks collapse
|
|
180
|
+
* and its cards stretch across the row instead of leaving a hole at its end.
|
|
181
|
+
*/
|
|
182
|
+
export const CARD_GRID_CLASS =
|
|
183
|
+
"grid grid-cols-[repeat(auto-fit,minmax(min(var(--dms-card-min-width),100%),1fr))] gap-3 px-[18px] pt-4 pb-[18px]";
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The style of a cards grid holding `count` cards of at least `minWidth` (a
|
|
187
|
+
* CSS length): the width its tracks start from, and a cap keeping one or two
|
|
188
|
+
* cards from stretching across a whole wide screen.
|
|
189
|
+
*/
|
|
190
|
+
export function cardGridStyle(
|
|
191
|
+
minWidth: string | undefined,
|
|
192
|
+
count: number,
|
|
193
|
+
): CSSProperties {
|
|
194
|
+
const width = minWidth || DEFAULT_CARD_MIN_WIDTH;
|
|
195
|
+
const cards = Math.max(1, count);
|
|
196
|
+
return {
|
|
197
|
+
"--dms-card-min-width": width,
|
|
198
|
+
maxWidth: `calc(${cards} * ${CARD_MAX_WIDTH_FACTOR} * ${width} + ${cards - 1} * ${CARD_GRID_GAP} + ${CARD_GRID_PADDING})`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The text of a cell value, as the browser searches and sorts it. */
|
|
2
|
+
export const textOf = (value: unknown): string =>
|
|
3
|
+
value === null || value === undefined
|
|
4
|
+
? ""
|
|
5
|
+
: typeof value === "object"
|
|
6
|
+
? JSON.stringify(value)
|
|
7
|
+
: String(value);
|
|
8
|
+
|
|
9
|
+
/** Orders two cell values the browser sorts: numbers by value, else as text. */
|
|
10
|
+
export const compareValues = (a: unknown, b: unknown): number => {
|
|
11
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
12
|
+
return textOf(a).localeCompare(textOf(b), undefined, { numeric: true });
|
|
13
|
+
};
|
|
@@ -49,3 +49,31 @@ export function keepListedRows(
|
|
|
49
49
|
Object.entries(expanded).filter(([id]) => listed.has(id)),
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/** A table's open rows, and the listed set (its query) they belong to. */
|
|
54
|
+
export interface ExpandedRowsState {
|
|
55
|
+
expanded: ExpandedRowMap;
|
|
56
|
+
listedKey?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The open rows once the listed rows change: a newly listed set (another
|
|
61
|
+
* `listedKey`: page, filter, search, sort, tab, archive view) opens with
|
|
62
|
+
* `defaultExpanded`; a refresh of the same set keeps what the user opened,
|
|
63
|
+
* minus rows no longer listed. Rows that have not arrived yet (`undefined`,
|
|
64
|
+
* the list still loading, as on a first load) list no set: the default waits
|
|
65
|
+
* for them instead of being spent on an empty table.
|
|
66
|
+
*/
|
|
67
|
+
export function expandedRowsOnListing(
|
|
68
|
+
state: ExpandedRowsState,
|
|
69
|
+
rowIds: string[] | undefined,
|
|
70
|
+
listedKey: string,
|
|
71
|
+
mode?: ExpandedDefault,
|
|
72
|
+
single?: boolean,
|
|
73
|
+
): ExpandedRowsState {
|
|
74
|
+
if (!rowIds) return state;
|
|
75
|
+
if (listedKey === state.listedKey) {
|
|
76
|
+
return { listedKey, expanded: keepListedRows(state.expanded, rowIds) };
|
|
77
|
+
}
|
|
78
|
+
return { listedKey, expanded: defaultExpandedRows(rowIds, mode, single) };
|
|
79
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { get } from "@nuxt/ui/runtime/utils/index.js";
|
|
2
|
+
import type { ColumnSort, SortingState } from "@tanstack/vue-table";
|
|
2
3
|
import {
|
|
3
4
|
regionalDateTimeFormat,
|
|
4
5
|
regionalDayKey,
|
|
@@ -13,6 +14,7 @@ import type {
|
|
|
13
14
|
TableViewGroupedConfig,
|
|
14
15
|
} from "../../../../composables/table-view/types";
|
|
15
16
|
import type { TableFilter } from "../../../components/table/Table.vue";
|
|
17
|
+
import { compareValues } from "./compareValues";
|
|
16
18
|
import { sortValueKind } from "./sortableColumns";
|
|
17
19
|
|
|
18
20
|
/** Key of the group of rows without a value. */
|
|
@@ -146,6 +148,91 @@ export function groupedSorting(
|
|
|
146
148
|
return [{ id: groupByField, desc: own ? !!own.desc : by !== "value" }];
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
/**
|
|
152
|
+
* The sort a group's rows follow on the grouped display. The list route sorts
|
|
153
|
+
* on one field, the grouped column, so the sort picked on another column —
|
|
154
|
+
* by the user or a view, or the hand order of a `reorder` table — orders each
|
|
155
|
+
* group's rows in the browser instead, over the page listed.
|
|
156
|
+
*/
|
|
157
|
+
export function withinGroupSort(
|
|
158
|
+
picked: SortingState,
|
|
159
|
+
grouped: TableViewGroupedConfig,
|
|
160
|
+
): ColumnSort | undefined {
|
|
161
|
+
return picked.find((entry) => entry.id !== grouped.groupByField);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** What decides the order a table view lists its rows in. */
|
|
165
|
+
export interface TableSortingInput {
|
|
166
|
+
/** The order field of a hand-ordered table (`reorder`). */
|
|
167
|
+
reorderField?: string;
|
|
168
|
+
/** The grouped options, while the grouped display shows. */
|
|
169
|
+
grouped?: TableViewGroupedConfig;
|
|
170
|
+
/** The display shown offers the sort picker. */
|
|
171
|
+
canSort: boolean;
|
|
172
|
+
/** The sort the user picked or a view opened with. */
|
|
173
|
+
sorting: SortingState;
|
|
174
|
+
/** The table's default sort. */
|
|
175
|
+
defaultSorting: SortingState;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The order the rows are wanted in: the hand order of a `reorder` table,
|
|
180
|
+
* else the picked sort. The grouped display hides the sort picker but keeps
|
|
181
|
+
* the sort a view opened it with: it orders the rows of each group.
|
|
182
|
+
*/
|
|
183
|
+
export function pickedTableSorting(input: TableSortingInput): SortingState {
|
|
184
|
+
if (input.reorderField) return [{ id: input.reorderField, desc: false }];
|
|
185
|
+
return input.canSort || input.grouped ? input.sorting : input.defaultSorting;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The sort the list route is asked for (the route sorts on one field): on the
|
|
190
|
+
* grouped display, the grouped column, so that a group's rows follow each
|
|
191
|
+
* other — the picked order then sorts each group, see
|
|
192
|
+
* {@link orderGroupedRows}; else the picked order.
|
|
193
|
+
*/
|
|
194
|
+
export function listTableSorting(input: TableSortingInput): SortingState {
|
|
195
|
+
const picked = pickedTableSorting(input);
|
|
196
|
+
if (input.grouped) return groupedSorting(picked, input.grouped);
|
|
197
|
+
return picked;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The rows of a page listed on the grouped column, each group's rows in the
|
|
202
|
+
* order `picked` gives them (see {@link withinGroupSort}). The groups keep
|
|
203
|
+
* the order the list gave them; rows the sort cannot tell apart keep theirs.
|
|
204
|
+
*/
|
|
205
|
+
export function orderGroupedRows<T>(
|
|
206
|
+
rows: T[],
|
|
207
|
+
grouped: TableViewGroupedConfig,
|
|
208
|
+
picked: SortingState,
|
|
209
|
+
locale: string,
|
|
210
|
+
): T[] {
|
|
211
|
+
const sort = withinGroupSort(picked, grouped);
|
|
212
|
+
if (!sort) return rows;
|
|
213
|
+
const by = grouped.by ?? "value";
|
|
214
|
+
const groupOf = new Map<string, number>();
|
|
215
|
+
const entries = rows.map((row, index) => {
|
|
216
|
+
const key = rowGroupKey(
|
|
217
|
+
get(row as Record<string, unknown>, grouped.groupByField),
|
|
218
|
+
by,
|
|
219
|
+
locale,
|
|
220
|
+
);
|
|
221
|
+
if (!groupOf.has(key)) groupOf.set(key, groupOf.size);
|
|
222
|
+
return { row, index, group: groupOf.get(key)! };
|
|
223
|
+
});
|
|
224
|
+
const direction = sort.desc ? -1 : 1;
|
|
225
|
+
const valueOf = (row: T) => get(row as Record<string, unknown>, sort.id);
|
|
226
|
+
return entries
|
|
227
|
+
.sort(
|
|
228
|
+
(a, b) =>
|
|
229
|
+
a.group - b.group ||
|
|
230
|
+
direction * compareValues(valueOf(a.row), valueOf(b.row)) ||
|
|
231
|
+
a.index - b.index,
|
|
232
|
+
)
|
|
233
|
+
.map((entry) => entry.row);
|
|
234
|
+
}
|
|
235
|
+
|
|
149
236
|
/** How a date column picked in the options menu is cut, unless configured. */
|
|
150
237
|
const DEFAULT_DATE_GROUP_BY: TableViewGroupBy = "day";
|
|
151
238
|
|
|
@@ -178,3 +265,29 @@ export function groupedOptionsFor(
|
|
|
178
265
|
grouped.by && grouped.by !== "value" ? grouped.by : DEFAULT_DATE_GROUP_BY;
|
|
179
266
|
return { ...grouped, groupByField: field, by };
|
|
180
267
|
}
|
|
268
|
+
|
|
269
|
+
/** The rows of one group, under its key. */
|
|
270
|
+
export interface RowGroup<T> {
|
|
271
|
+
key: string;
|
|
272
|
+
rows: T[];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The rows filed under their group, each group once, in the order its first
|
|
277
|
+
* row comes. The list is sorted on the grouped column, but rows listed in
|
|
278
|
+
* another order — the previous sort, still shown while the grouped one loads
|
|
279
|
+
* — must not open a group twice: two headers under one key.
|
|
280
|
+
*/
|
|
281
|
+
export function groupRows<T>(
|
|
282
|
+
rows: readonly T[],
|
|
283
|
+
keyOf: (row: T) => string,
|
|
284
|
+
): RowGroup<T>[] {
|
|
285
|
+
const groups = new Map<string, T[]>();
|
|
286
|
+
for (const row of rows) {
|
|
287
|
+
const key = keyOf(row);
|
|
288
|
+
const group = groups.get(key);
|
|
289
|
+
if (group) group.push(row);
|
|
290
|
+
else groups.set(key, [row]);
|
|
291
|
+
}
|
|
292
|
+
return [...groups].map(([key, grouped]) => ({ key, rows: grouped }));
|
|
293
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { nextTick } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
hashText,
|
|
4
|
+
type TableField,
|
|
5
|
+
} from "#dms-core/app/build/composables/user/tablePreferences";
|
|
6
|
+
import type { QueryParamFilters } from "../../../../composables/table-view/types/config";
|
|
7
|
+
import type { TablePreferences } from "../useTablePreferences";
|
|
8
|
+
import { RECORD_URL_KEY } from "./recordLink";
|
|
9
|
+
import { readTableUrlKey, type TableUrlScope, writeTableUrlKey } from "./views";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The kept fields that decide which rows a table lists: a link that names
|
|
13
|
+
* part of that state replaces all of it, not only what it names.
|
|
14
|
+
*/
|
|
15
|
+
const LINKED_STATE_FIELDS: ReadonlySet<TableField> = new Set<TableField>([
|
|
16
|
+
"activeView",
|
|
17
|
+
"activeTab",
|
|
18
|
+
"columnFilters",
|
|
19
|
+
"sorting",
|
|
20
|
+
"pagination",
|
|
21
|
+
"paginationScope",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The listing choices a user makes on a link's table (a view, a tab, a
|
|
26
|
+
* filter, a sort): the first one makes the state shown their own.
|
|
27
|
+
*/
|
|
28
|
+
const USER_CHOICE_FIELDS: ReadonlySet<TableField> = new Set<TableField>([
|
|
29
|
+
"activeView",
|
|
30
|
+
"activeTab",
|
|
31
|
+
"columnFilters",
|
|
32
|
+
"sorting",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const VIEW_URL_KEY = "view";
|
|
36
|
+
const TAB_URL_KEY = "tab";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* What `query` names as the table's view: an id, `""` for no view at all
|
|
40
|
+
* (`?view=`), or `undefined` when it names none.
|
|
41
|
+
*/
|
|
42
|
+
const namedView = (
|
|
43
|
+
query: Record<string, unknown>,
|
|
44
|
+
urlScope: TableUrlScope,
|
|
45
|
+
): string | undefined => {
|
|
46
|
+
const reading = readTableUrlKey(query, urlScope, VIEW_URL_KEY);
|
|
47
|
+
return reading.isCleared ? "" : reading.value;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** What the URL a table arrives on may name, and how it was reached. */
|
|
51
|
+
export interface TableLinkArrival {
|
|
52
|
+
query: Record<string, unknown>;
|
|
53
|
+
urlScope: TableUrlScope;
|
|
54
|
+
queryParamFilters: QueryParamFilters | undefined;
|
|
55
|
+
/** The page was reached through a visit, not loaded or gone back to. */
|
|
56
|
+
isVisit: boolean;
|
|
57
|
+
/** The tab kept from the last visit. */
|
|
58
|
+
rememberedTab: string;
|
|
59
|
+
/** The URL filters the kept state was chosen under (`urlFilterScope`). */
|
|
60
|
+
keptLinkScope: string | undefined;
|
|
61
|
+
/** The link the table last showed, unclaimed by the user (`linkKeyScope`). */
|
|
62
|
+
shownLink: string | undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const namesUrlFilter = (
|
|
66
|
+
query: Record<string, unknown>,
|
|
67
|
+
queryParamFilters: QueryParamFilters | undefined,
|
|
68
|
+
isNamed: (param: string) => boolean,
|
|
69
|
+
): boolean =>
|
|
70
|
+
Object.keys(queryParamFilters ?? {}).some(
|
|
71
|
+
(param) => query[param] !== undefined && isNamed(param),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The `queryParamFilters` values `query` carries, as one key, or `undefined`
|
|
76
|
+
* when it carries none: what a kept state chosen under them is tied to.
|
|
77
|
+
*/
|
|
78
|
+
export function urlFilterScope(
|
|
79
|
+
query: Record<string, unknown>,
|
|
80
|
+
queryParamFilters: QueryParamFilters | undefined,
|
|
81
|
+
): string | undefined {
|
|
82
|
+
const values = Object.keys(queryParamFilters ?? {})
|
|
83
|
+
.filter((param) => query[param] !== undefined)
|
|
84
|
+
.sort()
|
|
85
|
+
.map((param) => [param, query[param]]);
|
|
86
|
+
return values.length > 0 ? hashText(JSON.stringify(values)) : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* What `query` names for the table (its view, tab and row, and the filters
|
|
91
|
+
* declared in `queryParamFilters`), as one key, or `undefined` when it names
|
|
92
|
+
* none of them: a URL spelling a key short or prefixed reads the same.
|
|
93
|
+
*/
|
|
94
|
+
export function linkKeyScope(
|
|
95
|
+
query: Record<string, unknown>,
|
|
96
|
+
urlScope: TableUrlScope,
|
|
97
|
+
queryParamFilters: QueryParamFilters | undefined,
|
|
98
|
+
): string | undefined {
|
|
99
|
+
const named = [
|
|
100
|
+
[VIEW_URL_KEY, namedView(query, urlScope)],
|
|
101
|
+
...[TAB_URL_KEY, RECORD_URL_KEY].map((key) => [
|
|
102
|
+
key,
|
|
103
|
+
readTableUrlKey(query, urlScope, key).value,
|
|
104
|
+
]),
|
|
105
|
+
].filter(([, value]) => value !== undefined);
|
|
106
|
+
const filters = Object.keys(queryParamFilters ?? {})
|
|
107
|
+
.filter((param) => query[param] !== undefined)
|
|
108
|
+
.sort()
|
|
109
|
+
.map((param) => [`?${param}`, query[param]]);
|
|
110
|
+
const values = [...named, ...filters];
|
|
111
|
+
return values.length > 0 ? hashText(JSON.stringify(values)) : undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether the table was reached through a link that names what it lists: a
|
|
116
|
+
* view, a record, a filter declared in `queryParamFilters`, or a tab. Reached
|
|
117
|
+
* through a visit (a link clicked, a notification), it always is, even when
|
|
118
|
+
* what it names is what the user left. Loaded in full (a reload) or gone back
|
|
119
|
+
* to, the URL may be the one the table wrote for the user's own choices: it
|
|
120
|
+
* is a link again when it is the link the table last showed, and when it
|
|
121
|
+
* names a view or a record, or a tab or URL filters other than the kept
|
|
122
|
+
* ones. A server render cannot tell a reload from a URL typed or opened in a
|
|
123
|
+
* new browser tab, so a full load naming only the kept tab reads as one.
|
|
124
|
+
*/
|
|
125
|
+
export function isLinkedArrival(arrival: TableLinkArrival): boolean {
|
|
126
|
+
const { query, urlScope, queryParamFilters } = arrival;
|
|
127
|
+
const linkScope = linkKeyScope(query, urlScope, queryParamFilters);
|
|
128
|
+
if (linkScope === undefined) return false;
|
|
129
|
+
if (arrival.isVisit || linkScope === arrival.shownLink) return true;
|
|
130
|
+
const names = (key: string) => !!readTableUrlKey(query, urlScope, key).value;
|
|
131
|
+
if (namedView(query, urlScope) !== undefined || names(RECORD_URL_KEY)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
const filterScope = urlFilterScope(query, queryParamFilters);
|
|
135
|
+
if (filterScope !== undefined && filterScope !== arrival.keptLinkScope) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
const tab = readTableUrlKey(query, urlScope, TAB_URL_KEY).value;
|
|
139
|
+
return tab !== undefined && tab !== arrival.rememberedTab;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The table's preferences as a link arrival reads them: the listing state
|
|
144
|
+
* (`LINKED_STATE_FIELDS`) reads as its defaults, so the link applies over
|
|
145
|
+
* the table's own default state rather than over the last visit's. The
|
|
146
|
+
* kept state is only replaced once the user makes the state shown their own
|
|
147
|
+
* (`linkFollowingPreferences`).
|
|
148
|
+
*/
|
|
149
|
+
export function linkedArrivalPreferences(
|
|
150
|
+
preferences: TablePreferences,
|
|
151
|
+
): TablePreferences {
|
|
152
|
+
return {
|
|
153
|
+
...preferences,
|
|
154
|
+
get: (field, defaultValue) =>
|
|
155
|
+
LINKED_STATE_FIELDS.has(field)
|
|
156
|
+
? defaultValue
|
|
157
|
+
: preferences.get(field, defaultValue),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** A visit to the page the table is already shown on. */
|
|
162
|
+
export interface TableLinkVisit {
|
|
163
|
+
query: Record<string, unknown>;
|
|
164
|
+
/**
|
|
165
|
+
* A link pushed on the page (one clicked, a notification): it is followed
|
|
166
|
+
* even when it names what the table shows.
|
|
167
|
+
*/
|
|
168
|
+
isLinkClick: boolean;
|
|
169
|
+
/** The query of the visit the table was last reached through. */
|
|
170
|
+
previousQuery: Record<string, unknown>;
|
|
171
|
+
urlScope: TableUrlScope;
|
|
172
|
+
queryParamFilters: QueryParamFilters | undefined;
|
|
173
|
+
/** The view and the tab the table shows. */
|
|
174
|
+
activeView: string | undefined;
|
|
175
|
+
activeTab: string;
|
|
176
|
+
canOpenView: (id: string) => boolean;
|
|
177
|
+
canOpenTab: (id: string) => boolean;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Whether a visit that keeps the table mounted (a link to the page already
|
|
182
|
+
* shown, back and forward) is a link to follow as an arrival would. A link
|
|
183
|
+
* clicked on the page counts whenever it names a view or a tab the table can
|
|
184
|
+
* open, or a URL filter, even what the table already shows: the state around
|
|
185
|
+
* it may hide it. Otherwise (back and forward), the view and the tab are
|
|
186
|
+
* written or kept in place, so they count when they differ from what the
|
|
187
|
+
* table shows; a URL filter when it differs from the last visit. A row named
|
|
188
|
+
* (`?record=`) is opened on the table as it stands, its page included: it is
|
|
189
|
+
* no listing link, and a link naming one counts only for a view, a tab or a
|
|
190
|
+
* URL filter that changes what the table lists. A visit repeating what the
|
|
191
|
+
* table already reads (the one that mounted it) is not a link.
|
|
192
|
+
*/
|
|
193
|
+
export function isLinkedVisit(visit: TableLinkVisit): boolean {
|
|
194
|
+
const { query, previousQuery, urlScope, isLinkClick } = visit;
|
|
195
|
+
const read = (from: Record<string, unknown>, key: string) =>
|
|
196
|
+
readTableUrlKey(from, urlScope, key).value;
|
|
197
|
+
// A link clicked counts even when it names what is already there, unless
|
|
198
|
+
// it opens a row: the table it is opened on stays as it stands.
|
|
199
|
+
const isRowLink = read(query, RECORD_URL_KEY) !== undefined;
|
|
200
|
+
const isNew = (value: unknown, current: unknown) =>
|
|
201
|
+
(isLinkClick && !isRowLink) || value !== current;
|
|
202
|
+
const view = namedView(query, urlScope);
|
|
203
|
+
if (view === "" && isNew(undefined, visit.activeView)) return true;
|
|
204
|
+
if (view && isNew(view, visit.activeView) && visit.canOpenView(view)) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
const tab = read(query, TAB_URL_KEY);
|
|
208
|
+
if (tab && isNew(tab, visit.activeTab) && visit.canOpenTab(tab)) return true;
|
|
209
|
+
return namesUrlFilter(query, visit.queryParamFilters, (param) =>
|
|
210
|
+
isNew(query[param], previousQuery[param]),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** A kept field of the listing state, with its value shown and default. */
|
|
215
|
+
export type KeptListingField = [
|
|
216
|
+
field: TableField,
|
|
217
|
+
value: unknown,
|
|
218
|
+
defaultValue: unknown,
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
/** What `linkFollowingPreferences` reads the table's link state from. */
|
|
222
|
+
export interface LinkFollowingOptions {
|
|
223
|
+
/** The table arrived on a link (`isLinkedArrival`). */
|
|
224
|
+
isLinkedArrival: boolean;
|
|
225
|
+
urlScope: TableUrlScope;
|
|
226
|
+
/** The URL filters the table lists under now (`urlFilterScope`). */
|
|
227
|
+
linkScope: () => string | undefined;
|
|
228
|
+
/** What the URL shown names for the table now (`linkKeyScope`). */
|
|
229
|
+
linkKeys: () => string | undefined;
|
|
230
|
+
/** The listing state shown, as it is kept (`LINKED_STATE_FIELDS`). */
|
|
231
|
+
listingState: () => KeptListingField[];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Preferences that leave a link's listing state unkept until it is the user's. */
|
|
235
|
+
export interface LinkFollowingPreferences extends TablePreferences {
|
|
236
|
+
/**
|
|
237
|
+
* Runs `change`, which opens a link on the table already shown: as on a
|
|
238
|
+
* linked arrival, the state it sets is not kept.
|
|
239
|
+
*/
|
|
240
|
+
followLink: (change: () => void) => Promise<void>;
|
|
241
|
+
/** Called once the table is mounted: what changes from then is the user's. */
|
|
242
|
+
settleArrival: () => Promise<void>;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Wraps the table's preferences so the listing state a link opens (on
|
|
247
|
+
* arrival or on the table already shown) does not overwrite the kept one,
|
|
248
|
+
* which stays for the next plain visit. The table keeps its state from
|
|
249
|
+
* watchers, which run on the next flush: what the link and the arrival set
|
|
250
|
+
* is settled a tick later. The user's first choice from there (a view, a
|
|
251
|
+
* tab, a filter, a sort) makes the state shown theirs: it is kept whole,
|
|
252
|
+
* with the URL filters it was chosen under, and the view the link named
|
|
253
|
+
* leaves the URL, which then reads as a plain visit on reload. Until then,
|
|
254
|
+
* the link shown is kept too (`shownLink`), so a reload shows it again.
|
|
255
|
+
*/
|
|
256
|
+
export function linkFollowingPreferences(
|
|
257
|
+
preferences: TablePreferences,
|
|
258
|
+
options: LinkFollowingOptions,
|
|
259
|
+
): LinkFollowingPreferences {
|
|
260
|
+
let isShowingLink = options.isLinkedArrival;
|
|
261
|
+
let isSettled = false;
|
|
262
|
+
|
|
263
|
+
const keepLinkScope = () => {
|
|
264
|
+
preferences.set("linkScope", options.linkScope(), undefined);
|
|
265
|
+
preferences.set("shownLink", undefined, undefined);
|
|
266
|
+
};
|
|
267
|
+
const keepShownLink = () =>
|
|
268
|
+
preferences.set("shownLink", options.linkKeys(), undefined);
|
|
269
|
+
|
|
270
|
+
const takeOver = () => {
|
|
271
|
+
isShowingLink = false;
|
|
272
|
+
for (const [field, value, defaultValue] of options.listingState()) {
|
|
273
|
+
preferences.set(field, value, defaultValue);
|
|
274
|
+
}
|
|
275
|
+
keepLinkScope();
|
|
276
|
+
writeTableUrlKey(options.urlScope, VIEW_URL_KEY);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
...preferences,
|
|
281
|
+
set: (field, value, defaultValue) => {
|
|
282
|
+
if (!LINKED_STATE_FIELDS.has(field)) {
|
|
283
|
+
preferences.set(field, value, defaultValue);
|
|
284
|
+
} else if (!isShowingLink) {
|
|
285
|
+
preferences.set(field, value, defaultValue);
|
|
286
|
+
keepLinkScope();
|
|
287
|
+
} else if (isSettled && USER_CHOICE_FIELDS.has(field)) {
|
|
288
|
+
takeOver();
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
followLink: async (change) => {
|
|
292
|
+
isShowingLink = true;
|
|
293
|
+
isSettled = false;
|
|
294
|
+
try {
|
|
295
|
+
change();
|
|
296
|
+
await nextTick();
|
|
297
|
+
keepShownLink();
|
|
298
|
+
} finally {
|
|
299
|
+
isSettled = true;
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
settleArrival: async () => {
|
|
303
|
+
await nextTick();
|
|
304
|
+
if (isShowingLink) keepShownLink();
|
|
305
|
+
isSettled = true;
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|