@antelopejs/dms 0.9.7 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/README.md +1 -1
- package/data/module-store/catalog.json +2 -2
- package/dist/attachments/table-view.js +2 -2
- package/dist/attachments/table-view.js.map +1 -1
- package/dist/automation/actions.d.ts +2 -0
- package/dist/automation/actions.js +116 -71
- package/dist/automation/actions.js.map +1 -1
- package/dist/automation/i18n.d.ts +4 -0
- package/dist/automation/i18n.js +19 -0
- package/dist/automation/i18n.js.map +1 -0
- package/dist/automation/send-notification.d.ts +21 -0
- package/dist/automation/send-notification.js +163 -0
- package/dist/automation/send-notification.js.map +1 -0
- package/dist/automation/triggers.js +19 -25
- package/dist/automation/triggers.js.map +1 -1
- package/dist/client-origin-cors.d.ts +1 -0
- package/dist/client-origin-cors.js +19 -0
- package/dist/client-origin-cors.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +5 -3
- package/dist/config.js.map +1 -1
- package/dist/frontend-module.d.ts +4 -0
- package/dist/frontend-module.js +37 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/implementations/dms/client-ip.js +1 -1
- package/dist/implementations/dms/client-ip.js.map +1 -1
- package/dist/implementations/dms/dev-reload.d.ts +5 -0
- package/dist/implementations/dms/dev-reload.js +23 -3
- package/dist/implementations/dms/dev-reload.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +2 -4
- package/dist/implementations/dms/frontend-modules.js +22 -89
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +12 -2
- package/dist/implementations/dms/frontend-ready.js +34 -4
- package/dist/implementations/dms/frontend-ready.js.map +1 -1
- package/dist/implementations/dms/layout-banners.d.ts +2 -1
- package/dist/implementations/dms/layout-banners.js +9 -0
- package/dist/implementations/dms/layout-banners.js.map +1 -1
- package/dist/implementations/dms/manifest.d.ts +1 -2
- package/dist/implementations/dms/manifest.js +2 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +4 -3
- package/dist/implementations/dms/page.js +49 -18
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
- package/dist/implementations/dms/permissions-resolver.js +2 -2
- package/dist/implementations/dms/permissions-resolver.js.map +1 -1
- package/dist/implementations/dms/realtime.js +1 -0
- package/dist/implementations/dms/realtime.js.map +1 -1
- package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
- package/dist/implementations/dms-base/bulk-selection.js +23 -1
- package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
- package/dist/implementations/dms-base/filter-tokens.js +5 -3
- package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
- package/dist/implementations/dms-base/search-route.d.ts +1 -0
- package/dist/implementations/dms-base/search-route.js +35 -13
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/implementations/dms-base/table-view.js +11 -6
- package/dist/implementations/dms-base/table-view.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +2 -2
- package/dist/implementations/dms-notifications/index.js +5 -5
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/index.js +28 -45
- package/dist/index.js.map +1 -1
- package/dist/pages/modules/index.d.ts +1 -0
- package/dist/pages/modules/index.js +1 -0
- package/dist/pages/modules/index.js.map +1 -1
- package/dist/pages/modules/modules-store.d.ts +9 -0
- package/dist/pages/modules/modules-store.js +30 -0
- package/dist/pages/modules/modules-store.js.map +1 -0
- package/dist/pages/settings/users/account-credentials.js +2 -2
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/account-data-store.js +25 -8
- package/dist/pages/settings/users/account-data-store.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.js +9 -3
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/member-management.js +13 -14
- package/dist/pages/settings/users/member-management.js.map +1 -1
- package/dist/pages/settings/users/profile.d.ts +1 -4
- package/dist/pages/settings/users/profile.js +0 -35
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.js +26 -18
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/role-editor.d.ts +0 -1
- package/dist/pages/settings/users/role-editor.js +1 -2
- package/dist/pages/settings/users/role-editor.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +1 -2
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.js +2 -2
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/realtime/broker.d.ts +6 -0
- package/dist/realtime/broker.js +8 -1
- package/dist/realtime/broker.js.map +1 -1
- package/dist/realtime/presence.d.ts +8 -3
- package/dist/realtime/presence.js +8 -4
- package/dist/realtime/presence.js.map +1 -1
- package/dist/realtime/sessions.d.ts +11 -1
- package/dist/realtime/sessions.js +21 -3
- package/dist/realtime/sessions.js.map +1 -1
- package/dist/realtime/table-view.d.ts +2 -2
- package/dist/realtime/table-view.js +4 -3
- package/dist/realtime/table-view.js.map +1 -1
- package/dist/routes/auth/client-ip.d.ts +1 -0
- package/dist/routes/auth/client-ip.js +18 -0
- package/dist/routes/auth/client-ip.js.map +1 -1
- package/dist/routes/auth/oauth/config.d.ts +2 -1
- package/dist/routes/auth/oauth/config.js +5 -1
- package/dist/routes/auth/oauth/config.js.map +1 -1
- package/dist/routes/auth/session-handoff.d.ts +0 -1
- package/dist/routes/auth/session-handoff.js +0 -2
- package/dist/routes/auth/session-handoff.js.map +1 -1
- package/dist/routes/auth.js +3 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/realtime.d.ts +13 -1
- package/dist/routes/realtime.js +114 -29
- package/dist/routes/realtime.js.map +1 -1
- package/dist/startup-config.d.ts +2 -0
- package/dist/startup-config.js +62 -0
- package/dist/startup-config.js.map +1 -0
- package/dist/store/interfaces.d.ts +3 -2
- package/dist/store/interfaces.js +16 -6
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/listing.js +24 -13
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +4 -0
- package/dist/store/operations.js.map +1 -1
- package/dist/store/requirements.js +3 -2
- package/dist/store/requirements.js.map +1 -1
- package/dist/store/runtime.d.ts +10 -1
- package/dist/store/runtime.js +33 -6
- package/dist/store/runtime.js.map +1 -1
- package/dist/store/types.d.ts +10 -0
- package/dist/validation/auth.schema.d.ts +2 -2
- package/dist/validation/member-invite.schema.d.ts +2 -10
- package/dist/validation/member-invite.schema.js +2 -15
- package/dist/validation/member-invite.schema.js.map +1 -1
- package/frontend-vue/dms.frontend.ts +24 -5
- package/frontend-vue/dms.server.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
- package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
- package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
- package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
- package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
- package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
- package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
- package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
- package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
- package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
- package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
- package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
- package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
- package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
- package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
- package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
- package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
- package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
- package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
- package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
- package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
- package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
- package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
- package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
- package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
- package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
- package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
- package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
- package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
- package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
- package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
- package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
- package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
- package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
- package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
- package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
- package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
- package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
- package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
- package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
- package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
- package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
- package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
- package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
- package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
- package/frontend-vue/package.json +2 -2
- package/package.json +7 -6
- package/skills/dms/SKILL.md +1 -1
- package/skills/dms-auth/SKILL.md +5 -3
- package/skills/dms-best-practices/REFERENCE.md +2 -0
- package/skills/dms-best-practices/SKILL.md +4 -0
- package/skills/dms-module/SKILL.md +3 -2
- package/skills/dms-pages/REFERENCE.md +125 -16
- package/skills/dms-pages/SKILL.md +1 -1
- package/skills/dms-project/DEV-SERVERS.md +23 -1
- package/skills/dms-project/REFERENCE.md +1 -1
- package/skills/dms-project/SKILL.md +4 -1
- package/skills/dms-project/scripts/dms-dev.sh +205 -17
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
createReconnector,
|
|
3
|
+
joinSharedRealtimeConnection,
|
|
4
|
+
recoverFromError,
|
|
5
|
+
type StreamRecovery,
|
|
6
|
+
} from "../../build/utils/sharedRealtimeConnection";
|
|
7
|
+
import { HTTP_UNAUTHORIZED } from "../../utils/http-status";
|
|
6
8
|
|
|
7
|
-
const
|
|
9
|
+
const REALTIME_CONNECTION_PATH = "/api/realtime/connection";
|
|
10
|
+
const REALTIME_SESSION_PATH = "/api/realtime/session";
|
|
11
|
+
const REALTIME_SESSION_CLOSE_PATH = "/api/realtime/session/close";
|
|
8
12
|
const REALTIME_SUBSCRIBE_PATH_PREFIX = "/api/realtime/subscribe/";
|
|
9
13
|
const REALTIME_UNSUBSCRIBE_PATH = "/api/realtime/unsubscribe";
|
|
10
14
|
const SESSION_HEADER = "x-realtime-session";
|
|
15
|
+
const CONNECTION_HEADER = "x-realtime-connection";
|
|
16
|
+
const CONNECTION_NAME_PREFIX = "dms-realtime";
|
|
11
17
|
const SESSION_STATE_KEY = "dms.realtime.sessionId";
|
|
12
18
|
const MENU_TOPICS_STATE_KEY = "dms.realtime.menuTopics";
|
|
13
|
-
const HELLO_EVENT = "hello";
|
|
14
19
|
const SNAPSHOT_EVENT = "snapshot";
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
20
|
+
/** The server ended this tab's session alone: the tab opens a new one. */
|
|
21
|
+
const SESSION_ENDED_EVENT = "session-ended";
|
|
22
|
+
/** Frames held while a session is being opened, every tab's included. */
|
|
23
|
+
const MAX_BUFFERED_FRAMES = 256;
|
|
18
24
|
|
|
19
25
|
export interface RealtimeEvent {
|
|
20
26
|
topic: string;
|
|
@@ -56,6 +62,13 @@ export interface UserRealtimeApi {
|
|
|
56
62
|
* answered them: an event on one means the page's data moved.
|
|
57
63
|
*/
|
|
58
64
|
pageTopics: Ref<string[]>;
|
|
65
|
+
/**
|
|
66
|
+
* The topic the open connection carries the backend's development reload
|
|
67
|
+
* signal under, as the session's greeting named it: the tab then needs no
|
|
68
|
+
* reload stream of its own. Unset while no session is open, and in
|
|
69
|
+
* production.
|
|
70
|
+
*/
|
|
71
|
+
devReloadTopic: Ref<string | undefined>;
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
interface UserRealtimeState {
|
|
@@ -63,15 +76,45 @@ interface UserRealtimeState {
|
|
|
63
76
|
pageId: Ref<string | undefined>;
|
|
64
77
|
menuTopics: Ref<string[]>;
|
|
65
78
|
pageTopics: Ref<string[]>;
|
|
79
|
+
devReloadTopic: Ref<string | undefined>;
|
|
66
80
|
appliedPageId: Ref<string | null>;
|
|
67
81
|
handlers: Map<string, Set<RealtimeTopicHandler>>;
|
|
68
82
|
snapshots: Map<string, RealtimeSnapshotEvent>;
|
|
69
83
|
reconciling: Promise<void>;
|
|
70
|
-
|
|
84
|
+
connection?: TabConnection;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** This tab's hold on the shared connection. */
|
|
88
|
+
interface TabConnection {
|
|
89
|
+
/** Opens a new session for the tab, in the browser session's workspace. */
|
|
90
|
+
renewSession: () => void;
|
|
91
|
+
cancel: () => void;
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
let singletonApi: UserRealtimeApi | undefined;
|
|
74
95
|
|
|
96
|
+
/** The user field the shared connection is keyed by. */
|
|
97
|
+
interface ConnectionUser {
|
|
98
|
+
_id?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The session fields a realtime session is bound to on the server. */
|
|
102
|
+
interface SessionScopeFields {
|
|
103
|
+
accountId?: string;
|
|
104
|
+
activeTenantId?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The account and workspace a realtime session is opened in. The server binds
|
|
109
|
+
* a session to those of the request that opened it, so a switch of either
|
|
110
|
+
* needs a new session: the old one would keep delivering the previous
|
|
111
|
+
* workspace's events. The connection, keyed by user, stays.
|
|
112
|
+
*/
|
|
113
|
+
const sessionScopeOf = (
|
|
114
|
+
session: SessionScopeFields | null | undefined,
|
|
115
|
+
): string =>
|
|
116
|
+
JSON.stringify([session?.accountId ?? "", session?.activeTenantId ?? ""]);
|
|
117
|
+
|
|
75
118
|
const dispatchToHandlers = (
|
|
76
119
|
state: UserRealtimeState,
|
|
77
120
|
topic: string,
|
|
@@ -105,19 +148,19 @@ const readMenuTopics = (data: object): string[] => {
|
|
|
105
148
|
);
|
|
106
149
|
};
|
|
107
150
|
|
|
151
|
+
/** Takes the session a greeting names, with the menu topics it announces. */
|
|
108
152
|
const handleHello = (state: UserRealtimeState, data: unknown): void => {
|
|
109
153
|
if (!data || typeof data !== "object" || !("sessionId" in data)) return;
|
|
110
154
|
state.sessionId.value = String((data as { sessionId: unknown }).sessionId);
|
|
111
155
|
state.menuTopics.value = readMenuTopics(data);
|
|
156
|
+
const { devReloadTopic } = data as { devReloadTopic?: unknown };
|
|
157
|
+
state.devReloadTopic.value =
|
|
158
|
+
typeof devReloadTopic === "string" ? devReloadTopic : undefined;
|
|
112
159
|
};
|
|
113
160
|
|
|
114
161
|
const handleEvent =
|
|
115
162
|
(state: UserRealtimeState) =>
|
|
116
163
|
(eventName: string, data: unknown): void => {
|
|
117
|
-
if (eventName === HELLO_EVENT) {
|
|
118
|
-
handleHello(state, data);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
164
|
if (isSnapshotEvent(eventName, data)) {
|
|
122
165
|
state.snapshots.set(data.topic, data);
|
|
123
166
|
dispatchToHandlers(state, data.topic, data);
|
|
@@ -128,111 +171,187 @@ const handleEvent =
|
|
|
128
171
|
}
|
|
129
172
|
};
|
|
130
173
|
|
|
131
|
-
interface ConnectionCallbacks {
|
|
174
|
+
interface ConnectionCallbacks extends StreamRecovery {
|
|
132
175
|
onConnected: () => void;
|
|
133
176
|
onDisconnected: () => void;
|
|
134
|
-
refreshSession: () => Promise<boolean>;
|
|
135
|
-
redirectToAuth: () => Promise<void>;
|
|
136
177
|
}
|
|
137
178
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
cancel(): void;
|
|
179
|
+
/** The answer of `POST /api/realtime/session`: a greeting, or `ok: false`. */
|
|
180
|
+
interface SessionGreeting {
|
|
181
|
+
sessionId?: unknown;
|
|
182
|
+
menuTopics?: unknown;
|
|
143
183
|
}
|
|
144
184
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
isCancelled: () => isCancelled,
|
|
151
|
-
schedule: (open) => {
|
|
152
|
-
// A pending reconnect absorbs concurrent triggers: one stream per tab.
|
|
153
|
-
if (isCancelled || timer) return;
|
|
154
|
-
timer = setTimeout(() => {
|
|
155
|
-
timer = undefined;
|
|
156
|
-
open();
|
|
157
|
-
}, delay);
|
|
158
|
-
delay = Math.min(delay * RECONNECT_MULTIPLIER, RECONNECT_MAX_DELAY_MS);
|
|
159
|
-
},
|
|
160
|
-
resetDelay: () => {
|
|
161
|
-
delay = RECONNECT_INITIAL_DELAY_MS;
|
|
162
|
-
},
|
|
163
|
-
cancel: () => {
|
|
164
|
-
isCancelled = true;
|
|
165
|
-
if (timer) clearTimeout(timer);
|
|
166
|
-
timer = undefined;
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
};
|
|
185
|
+
interface BufferedFrame {
|
|
186
|
+
sessionId: string;
|
|
187
|
+
eventName: string;
|
|
188
|
+
data: unknown;
|
|
189
|
+
}
|
|
170
190
|
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return;
|
|
191
|
+
const readGreeting = async (
|
|
192
|
+
response: Response,
|
|
193
|
+
): Promise<SessionGreeting | null> => {
|
|
194
|
+
try {
|
|
195
|
+
return (await response.json()) as SessionGreeting | null;
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
179
198
|
}
|
|
180
|
-
void callbacks.refreshSession().then((isRefreshed) => {
|
|
181
|
-
if (isRefreshed) {
|
|
182
|
-
reconnect();
|
|
183
|
-
} else {
|
|
184
|
-
void callbacks.redirectToAuth();
|
|
185
|
-
}
|
|
186
|
-
}, reconnect);
|
|
187
199
|
};
|
|
188
200
|
|
|
201
|
+
const unauthorizedError = (): Error =>
|
|
202
|
+
Object.assign(new Error("Realtime session refused"), {
|
|
203
|
+
status: HTTP_UNAUTHORIZED,
|
|
204
|
+
});
|
|
205
|
+
|
|
189
206
|
/**
|
|
190
|
-
* Keeps
|
|
191
|
-
*
|
|
192
|
-
*
|
|
207
|
+
* Keeps a session of this tab on the browser's shared realtime connection
|
|
208
|
+
* until cancelled: a new one each time the connection changes, its leader tab
|
|
209
|
+
* having gone or its stream having dropped, and each time it is renewed.
|
|
193
210
|
*/
|
|
194
|
-
const
|
|
195
|
-
url: string,
|
|
211
|
+
const connectTab = (
|
|
196
212
|
state: UserRealtimeState,
|
|
213
|
+
baseUrl: string,
|
|
214
|
+
userKey: string,
|
|
197
215
|
callbacks: ConnectionCallbacks,
|
|
198
|
-
):
|
|
199
|
-
const reconnector = createReconnector();
|
|
216
|
+
): TabConnection => {
|
|
200
217
|
const dispatch = handleEvent(state);
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
onClose: (reason) => {
|
|
218
|
-
if (reason !== "eof" || !retire(handle)) return;
|
|
219
|
-
reconnect();
|
|
218
|
+
const retries = createReconnector();
|
|
219
|
+
const sessionCloseUrl = `${baseUrl}${REALTIME_SESSION_CLOSE_PATH}`;
|
|
220
|
+
let connectionId: string | undefined;
|
|
221
|
+
// Each opening attempt supersedes the previous one, which a renewal may
|
|
222
|
+
// leave in flight on the same connection.
|
|
223
|
+
let attempt = 0;
|
|
224
|
+
// The session's first frames may come before the answer that names it.
|
|
225
|
+
let buffered: BufferedFrame[] | undefined;
|
|
226
|
+
|
|
227
|
+
// The backend ends only a session of the connection the request names.
|
|
228
|
+
const closeStraySession = (forConnection: string, sessionId: string) => {
|
|
229
|
+
void fetch(sessionCloseUrl, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: {
|
|
232
|
+
[CONNECTION_HEADER]: forConnection,
|
|
233
|
+
[SESSION_HEADER]: sessionId,
|
|
220
234
|
},
|
|
221
|
-
|
|
222
|
-
|
|
235
|
+
credentials: "include",
|
|
236
|
+
}).catch(() => {});
|
|
223
237
|
};
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
238
|
+
|
|
239
|
+
const adopt = (forConnection: string, greeting: SessionGreeting) => {
|
|
240
|
+
const sessionId = String(greeting.sessionId);
|
|
241
|
+
handleHello(state, greeting);
|
|
242
|
+
const pending = buffered ?? [];
|
|
243
|
+
buffered = undefined;
|
|
244
|
+
for (const frame of pending) {
|
|
245
|
+
if (frame.sessionId === sessionId) dispatch(frame.eventName, frame.data);
|
|
246
|
+
}
|
|
247
|
+
retries.resetDelay();
|
|
248
|
+
connection.announceSession(forConnection, sessionId);
|
|
249
|
+
callbacks.onConnected();
|
|
230
250
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
251
|
+
|
|
252
|
+
const openSession = async (forConnection: string): Promise<void> => {
|
|
253
|
+
const ownAttempt = ++attempt;
|
|
254
|
+
const isCurrent = () =>
|
|
255
|
+
!retries.isCancelled() &&
|
|
256
|
+
connectionId === forConnection &&
|
|
257
|
+
attempt === ownAttempt;
|
|
258
|
+
const retry = () =>
|
|
259
|
+
retries.schedule(() => {
|
|
260
|
+
if (isCurrent()) void openSession(forConnection);
|
|
261
|
+
});
|
|
262
|
+
buffered = [];
|
|
263
|
+
let response: Response;
|
|
264
|
+
try {
|
|
265
|
+
response = await fetch(`${baseUrl}${REALTIME_SESSION_PATH}`, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
// The dashboard's backend proxy relays this header by name.
|
|
268
|
+
headers: { [CONNECTION_HEADER]: forConnection },
|
|
269
|
+
credentials: "include",
|
|
270
|
+
});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (isCurrent()) recoverFromError(error, retry, callbacks);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const greeting = response.ok ? await readGreeting(response) : null;
|
|
276
|
+
const sessionId = greeting?.sessionId;
|
|
277
|
+
if (!isCurrent()) {
|
|
278
|
+
if (typeof sessionId === "string") {
|
|
279
|
+
closeStraySession(forConnection, sessionId);
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (response.status === HTTP_UNAUTHORIZED) {
|
|
284
|
+
recoverFromError(unauthorizedError(), retry, callbacks);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!response.ok) {
|
|
288
|
+
retry();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// The backend does not know the connection: it went, or this request
|
|
292
|
+
// reached an instance that does not hold it. The leader opens a new one,
|
|
293
|
+
// which this tab opens its session on; the backoff spaces the attempts.
|
|
294
|
+
if (typeof sessionId !== "string") {
|
|
295
|
+
buffered = undefined;
|
|
296
|
+
retries.schedule(() => {
|
|
297
|
+
if (isCurrent()) connection.reopen(forConnection);
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
adopt(forConnection, greeting!);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// A new session on the connection the tab keeps: the backend ended the
|
|
305
|
+
// current one alone, or the tab's account or workspace changed.
|
|
306
|
+
const renewSession = () => {
|
|
307
|
+
if (retries.isCancelled() || !connectionId) return;
|
|
308
|
+
const sessionId = state.sessionId.value;
|
|
309
|
+
if (sessionId) {
|
|
310
|
+
closeStraySession(connectionId, sessionId);
|
|
311
|
+
callbacks.onDisconnected();
|
|
312
|
+
}
|
|
313
|
+
void openSession(connectionId);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const connection = joinSharedRealtimeConnection({
|
|
317
|
+
name: `${CONNECTION_NAME_PREFIX}:${baseUrl}:${userKey}`,
|
|
318
|
+
streamUrl: `${baseUrl}${REALTIME_CONNECTION_PATH}`,
|
|
319
|
+
sessionCloseUrl,
|
|
320
|
+
onConnection: (next) => {
|
|
321
|
+
if (retries.isCancelled()) return;
|
|
322
|
+
if (state.sessionId.value) callbacks.onDisconnected();
|
|
323
|
+
buffered = undefined;
|
|
324
|
+
connectionId = next;
|
|
325
|
+
if (next) void openSession(next);
|
|
326
|
+
},
|
|
327
|
+
onFrame: (sessionId, eventName, data) => {
|
|
328
|
+
// The connection's own frames reach a tab once it has a session on it.
|
|
329
|
+
if (sessionId === undefined) {
|
|
330
|
+
if (state.sessionId.value) dispatch(eventName, data);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (sessionId === state.sessionId.value) {
|
|
334
|
+
if (eventName === SESSION_ENDED_EVENT) {
|
|
335
|
+
renewSession();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
dispatch(eventName, data);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (buffered && buffered.length < MAX_BUFFERED_FRAMES) {
|
|
342
|
+
buffered.push({ sessionId, eventName, data });
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
refreshSession: callbacks.refreshSession,
|
|
346
|
+
redirectToAuth: callbacks.redirectToAuth,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
renewSession,
|
|
351
|
+
cancel: () => {
|
|
352
|
+
retries.cancel();
|
|
353
|
+
connection.leave();
|
|
354
|
+
},
|
|
236
355
|
};
|
|
237
356
|
};
|
|
238
357
|
|
|
@@ -316,6 +435,7 @@ const resetSession = (state: UserRealtimeState): void => {
|
|
|
316
435
|
state.sessionId.value = undefined;
|
|
317
436
|
state.appliedPageId.value = null;
|
|
318
437
|
state.pageTopics.value = [];
|
|
438
|
+
state.devReloadTopic.value = undefined;
|
|
319
439
|
state.snapshots.clear();
|
|
320
440
|
};
|
|
321
441
|
|
|
@@ -328,11 +448,13 @@ const buildApi = (): UserRealtimeApi => {
|
|
|
328
448
|
const appliedPageId = ref<string | null>(null);
|
|
329
449
|
const pageId = ref<string | undefined>(undefined);
|
|
330
450
|
const pageTopics = ref<string[]>([]);
|
|
451
|
+
const devReloadTopic = ref<string | undefined>(undefined);
|
|
331
452
|
const state: UserRealtimeState = {
|
|
332
453
|
sessionId,
|
|
333
454
|
pageId,
|
|
334
455
|
menuTopics,
|
|
335
456
|
pageTopics,
|
|
457
|
+
devReloadTopic,
|
|
336
458
|
appliedPageId,
|
|
337
459
|
handlers: new Map(),
|
|
338
460
|
snapshots: new Map(),
|
|
@@ -340,34 +462,41 @@ const buildApi = (): UserRealtimeApi => {
|
|
|
340
462
|
};
|
|
341
463
|
|
|
342
464
|
const config = useDmsRuntimeConfig();
|
|
343
|
-
const { loggedIn } = useDmsUserSession
|
|
465
|
+
const { loggedIn, user, session } = useDmsUserSession<
|
|
466
|
+
ConnectionUser,
|
|
467
|
+
SessionScopeFields
|
|
468
|
+
>();
|
|
344
469
|
const { refreshSession, redirectToAuth } = useSessionRecovery();
|
|
345
470
|
const baseUrl = config.public.dms.baseURL as string;
|
|
346
471
|
|
|
347
472
|
if (!import.meta.env.SSR) {
|
|
473
|
+
// One connection per user: the tabs of another user (an account
|
|
474
|
+
// switched in one tab) share theirs, and never read this one's events.
|
|
348
475
|
watch(
|
|
349
|
-
() => loggedIn.value,
|
|
350
|
-
(
|
|
351
|
-
state.
|
|
352
|
-
state.
|
|
476
|
+
() => (loggedIn.value ? String(user.value?._id ?? "") : undefined),
|
|
477
|
+
(userKey) => {
|
|
478
|
+
state.connection?.cancel();
|
|
479
|
+
state.connection = undefined;
|
|
353
480
|
resetSession(state);
|
|
354
481
|
state.menuTopics.value = [];
|
|
355
|
-
if (
|
|
356
|
-
state.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
{
|
|
360
|
-
onConnected: () => {
|
|
361
|
-
void reconcilePage(state, baseUrl);
|
|
362
|
-
},
|
|
363
|
-
onDisconnected: () => resetSession(state),
|
|
364
|
-
refreshSession,
|
|
365
|
-
redirectToAuth,
|
|
482
|
+
if (userKey === undefined) return;
|
|
483
|
+
state.connection = connectTab(state, baseUrl, userKey, {
|
|
484
|
+
onConnected: () => {
|
|
485
|
+
void reconcilePage(state, baseUrl);
|
|
366
486
|
},
|
|
367
|
-
|
|
487
|
+
onDisconnected: () => resetSession(state),
|
|
488
|
+
refreshSession,
|
|
489
|
+
redirectToAuth,
|
|
490
|
+
});
|
|
368
491
|
},
|
|
369
492
|
{ immediate: true },
|
|
370
493
|
);
|
|
494
|
+
// A switch of account or workspace within the same user: the tab's session
|
|
495
|
+
// is renewed on the connection it already shares.
|
|
496
|
+
watch(
|
|
497
|
+
() => sessionScopeOf(session?.value),
|
|
498
|
+
() => state.connection?.renewSession(),
|
|
499
|
+
);
|
|
371
500
|
}
|
|
372
501
|
|
|
373
502
|
const subscribe = (topic: string, handler: RealtimeTopicHandler) => {
|
|
@@ -396,7 +525,15 @@ const buildApi = (): UserRealtimeApi => {
|
|
|
396
525
|
await reconcilePage(state, baseUrl);
|
|
397
526
|
};
|
|
398
527
|
|
|
399
|
-
return {
|
|
528
|
+
return {
|
|
529
|
+
subscribe,
|
|
530
|
+
setPage,
|
|
531
|
+
sessionId,
|
|
532
|
+
pageId,
|
|
533
|
+
menuTopics,
|
|
534
|
+
pageTopics,
|
|
535
|
+
devReloadTopic,
|
|
536
|
+
};
|
|
400
537
|
};
|
|
401
538
|
|
|
402
539
|
export function useUserRealtime(): UserRealtimeApi {
|
|
@@ -3,16 +3,11 @@ import {
|
|
|
3
3
|
type ComposedTextTranslate,
|
|
4
4
|
resolveComposedText,
|
|
5
5
|
} from "../../utils/composedText";
|
|
6
|
+
import { getPluginI18n } from "./useTranslation";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
* Writes the texts a block receives — a plain or `$`-prefixed string, or a
|
|
9
|
-
* `ComposedText` from its options or its route — in the reader's language,
|
|
10
|
-
* through {@link resolveComposedText}. Each call reads the active locale, so a
|
|
11
|
-
* text written in a `computed` follows a language switch.
|
|
12
|
-
*/
|
|
13
|
-
export function useComposedText() {
|
|
14
|
-
const { t, locale } = useI18n();
|
|
8
|
+
type ComposedTextI18n = Pick<ReturnType<typeof useI18n>, "t" | "locale">;
|
|
15
9
|
|
|
10
|
+
function composedTextOf({ t, locale }: ComposedTextI18n) {
|
|
16
11
|
// vue-i18n reads a third argument as a plural count only when there is one.
|
|
17
12
|
const translate: ComposedTextTranslate = (key, named, plural) =>
|
|
18
13
|
plural === undefined ? t(key, named) : t(key, named, plural);
|
|
@@ -23,3 +18,21 @@ export function useComposedText() {
|
|
|
23
18
|
|
|
24
19
|
return { processText };
|
|
25
20
|
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Writes the texts a block receives — a plain or `$`-prefixed string, or a
|
|
24
|
+
* `ComposedText` from its options or its route — in the reader's language,
|
|
25
|
+
* through {@link resolveComposedText}. Each call reads the active locale, so a
|
|
26
|
+
* text written in a `computed` follows a language switch.
|
|
27
|
+
*/
|
|
28
|
+
export function useComposedText() {
|
|
29
|
+
return composedTextOf(useI18n());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* {@link useComposedText} outside a component setup — in a data-type
|
|
34
|
+
* formatter, which runs wherever a value is drawn (see `getAppTranslation`).
|
|
35
|
+
*/
|
|
36
|
+
export function getAppComposedText() {
|
|
37
|
+
return composedTextOf(getPluginI18n().i18n);
|
|
38
|
+
}
|
|
@@ -6,9 +6,10 @@ export type I18nTranslate = (
|
|
|
6
6
|
) => string;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* i18n access for DMS app plugins, where `useI18n()` is unavailable: returns
|
|
10
|
-
* global composer plus an {@link I18nTranslate} bound to it. Inside
|
|
11
|
-
* components prefer `useI18n` / {@link useTranslation}
|
|
9
|
+
* i18n access for DMS app plugins, where `useI18n()` is unavailable: returns
|
|
10
|
+
* the global composer plus an {@link I18nTranslate} bound to it. Inside
|
|
11
|
+
* components prefer `useI18n` / {@link useTranslation}; in a data-type
|
|
12
|
+
* formatter, {@link getAppTranslation}.
|
|
12
13
|
*/
|
|
13
14
|
export function getPluginI18n() {
|
|
14
15
|
const { $i18n: i18n } = useDmsApp();
|
|
@@ -88,9 +89,9 @@ export function resolveOptionalI18nKey(
|
|
|
88
89
|
return isUntranslatedKey ? undefined : resolved;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
const { t } = useI18n();
|
|
92
|
+
type ComposerTranslate = ReturnType<typeof useI18n>["t"];
|
|
93
93
|
|
|
94
|
+
function translationOf(t: ComposerTranslate) {
|
|
94
95
|
function processI18n(key: string, params?: Record<string, unknown> | null) {
|
|
95
96
|
return resolveI18nKey(t, key, params);
|
|
96
97
|
}
|
|
@@ -106,4 +107,20 @@ export const useTranslation = () => {
|
|
|
106
107
|
processI18n,
|
|
107
108
|
processApiMessage,
|
|
108
109
|
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const useTranslation = () => translationOf(useI18n().t);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* {@link useTranslation} for code that runs wherever a value is drawn — a
|
|
116
|
+
* data-type formatter, a render function, a `computed` read by a watcher, an
|
|
117
|
+
* export — with no component setup active, where `useI18n()` throws. It reads
|
|
118
|
+
* the application's global composer (the one `useI18n()` returns), and each
|
|
119
|
+
* call reads its active locale, so a text written in a `computed` follows a
|
|
120
|
+
* language switch. `t` is that composer's own, for a key with no `$` prefix
|
|
121
|
+
* (a plural count included).
|
|
122
|
+
*/
|
|
123
|
+
export const getAppTranslation = () => {
|
|
124
|
+
const { t } = getPluginI18n().i18n;
|
|
125
|
+
return { ...translationOf(t), t };
|
|
109
126
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineDmsPlugin } from "#dms/frontend-module";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logs, in development, every error Vue catches before the renderer's own
|
|
5
|
+
* handler takes it. That handler only keeps the error for the error page, so
|
|
6
|
+
* a render that throws showed its bare message there ("ERROR 500 · Must be
|
|
7
|
+
* called at the top of a `setup` function") with no stack in any console.
|
|
8
|
+
*/
|
|
9
|
+
export default defineDmsPlugin(({ vueApp }) => {
|
|
10
|
+
if (!import.meta.env.DEV) return;
|
|
11
|
+
const handleError = vueApp.config.errorHandler;
|
|
12
|
+
vueApp.config.errorHandler = (error, instance, info) => {
|
|
13
|
+
console.error(`[dms] Uncaught error (${info})`, error);
|
|
14
|
+
handleError?.(error, instance, info);
|
|
15
|
+
};
|
|
16
|
+
});
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/** A stored profile picture: the file's resource key. */
|
|
2
|
+
export interface UserAvatar {
|
|
3
|
+
key: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
export interface User {
|
|
2
7
|
_id: string;
|
|
3
8
|
createdAt: Date;
|
|
@@ -6,4 +11,5 @@ export interface User {
|
|
|
6
11
|
name: string;
|
|
7
12
|
language: string;
|
|
8
13
|
isValidated: boolean;
|
|
14
|
+
avatar?: UserAvatar | null;
|
|
9
15
|
}
|
|
@@ -15,7 +15,12 @@ import {
|
|
|
15
15
|
readTranslationKey,
|
|
16
16
|
resolveI18nKey,
|
|
17
17
|
} from "../composables/translation/useTranslation";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
formatDate,
|
|
20
|
+
formatNumber,
|
|
21
|
+
formatRelativeDistance,
|
|
22
|
+
readableNumberFormat,
|
|
23
|
+
} from "./formatter";
|
|
19
24
|
|
|
20
25
|
/** `t` as vue-i18n takes it: the key, the named values, the plural count. */
|
|
21
26
|
export type ComposedTextTranslate = (
|
|
@@ -103,7 +108,7 @@ function formatMoney(
|
|
|
103
108
|
{ locale }: ComposedTextContext,
|
|
104
109
|
): string {
|
|
105
110
|
try {
|
|
106
|
-
const format =
|
|
111
|
+
const format = readableNumberFormat(locale, {
|
|
107
112
|
style: "currency",
|
|
108
113
|
currency: param.currency,
|
|
109
114
|
});
|