@antelopejs/dms 0.8.3 → 0.9.1
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 +36 -0
- package/data/module-store/catalog.json +71 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/implementations/dms/frontend-backend-proxies.d.ts +3 -0
- package/dist/implementations/dms/frontend-backend-proxies.js +39 -0
- package/dist/implementations/dms/frontend-backend-proxies.js.map +1 -0
- package/dist/implementations/dms/frontend-bootstrap.d.ts +1 -0
- package/dist/implementations/dms/frontend-bootstrap.js +13 -0
- package/dist/implementations/dms/frontend-bootstrap.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +6 -2
- package/dist/implementations/dms/frontend-modules.js +37 -8
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +8 -0
- package/dist/implementations/dms/frontend-ready.js +34 -0
- package/dist/implementations/dms/frontend-ready.js.map +1 -0
- package/dist/implementations/dms/manifest.d.ts +7 -0
- package/dist/implementations/dms/manifest.js +13 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/menu-categories.d.ts +16 -0
- package/dist/implementations/dms/menu-categories.js +30 -0
- package/dist/implementations/dms/menu-categories.js.map +1 -0
- package/dist/implementations/dms/nav-badges.d.ts +0 -8
- package/dist/implementations/dms/nav-badges.js +0 -16
- package/dist/implementations/dms/nav-badges.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +13 -5
- package/dist/implementations/dms/page.js +72 -41
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms-html-render/index.js +5 -2
- package/dist/implementations/dms-html-render/index.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +4 -3
- package/dist/implementations/dms-notifications/index.js +14 -6
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/pages/settings/settings-index.js +3 -2
- package/dist/pages/settings/settings-index.js.map +1 -1
- package/dist/pages/settings/users/invites.js +2 -2
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/member-change-notifications.js +17 -6
- package/dist/pages/settings/users/member-change-notifications.js.map +1 -1
- package/dist/pages/settings/users/member-invite-form.d.ts +3 -2
- package/dist/pages/settings/users/member-invite-form.js +7 -4
- package/dist/pages/settings/users/member-invite-form.js.map +1 -1
- package/dist/pages/settings/users/member-roles-field.js +2 -1
- package/dist/pages/settings/users/member-roles-field.js.map +1 -1
- package/dist/pages/settings/users/members.js +4 -1
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.d.ts +6 -0
- package/dist/pages/settings/users/notifications.js +23 -15
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/profile.js +1 -1
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/region-form.d.ts +1 -1
- package/dist/pages/settings/users/region-form.js +2 -1
- package/dist/pages/settings/users/region-form.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.d.ts +3 -1
- package/dist/pages/settings/users/role-editor-store.js +17 -4
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/roles.js +1 -0
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/realtime/registry.d.ts +1 -0
- package/dist/realtime/registry.js +5 -0
- package/dist/realtime/registry.js.map +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/realtime.d.ts +5 -1
- package/dist/routes/realtime.js +16 -9
- package/dist/routes/realtime.js.map +1 -1
- package/dist/routes/store.d.ts +21 -0
- package/dist/routes/store.js +130 -0
- package/dist/routes/store.js.map +1 -0
- package/dist/store/activity.d.ts +2 -0
- package/dist/store/activity.js +12 -0
- package/dist/store/activity.js.map +1 -0
- package/dist/store/cache.d.ts +8 -0
- package/dist/store/cache.js +29 -0
- package/dist/store/cache.js.map +1 -0
- package/dist/store/catalog.d.ts +4 -0
- package/dist/store/catalog.js +69 -0
- package/dist/store/catalog.js.map +1 -0
- package/dist/store/compatibility.d.ts +3 -0
- package/dist/store/compatibility.js +45 -0
- package/dist/store/compatibility.js.map +1 -0
- package/dist/store/declarations.d.ts +18 -0
- package/dist/store/declarations.js +116 -0
- package/dist/store/declarations.js.map +1 -0
- package/dist/store/frontend-compatibility.d.ts +18 -0
- package/dist/store/frontend-compatibility.js +93 -0
- package/dist/store/frontend-compatibility.js.map +1 -0
- package/dist/store/interfaces.d.ts +6 -0
- package/dist/store/interfaces.js +126 -0
- package/dist/store/interfaces.js.map +1 -0
- package/dist/store/listing.d.ts +2 -0
- package/dist/store/listing.js +180 -0
- package/dist/store/listing.js.map +1 -0
- package/dist/store/operations.d.ts +8 -0
- package/dist/store/operations.js +254 -0
- package/dist/store/operations.js.map +1 -0
- package/dist/store/registry.d.ts +4 -0
- package/dist/store/registry.js +61 -0
- package/dist/store/registry.js.map +1 -0
- package/dist/store/requirements.d.ts +14 -0
- package/dist/store/requirements.js +81 -0
- package/dist/store/requirements.js.map +1 -0
- package/dist/store/runtime.d.ts +11 -0
- package/dist/store/runtime.js +78 -0
- package/dist/store/runtime.js.map +1 -0
- package/dist/store/tarball.d.ts +2 -0
- package/dist/store/tarball.js +80 -0
- package/dist/store/tarball.js.map +1 -0
- package/dist/store/types.d.ts +98 -0
- package/dist/store/types.js +3 -0
- package/dist/store/types.js.map +1 -0
- package/dist/test/integration/account-deletion.test.js +2 -2
- package/dist/test/integration/account-deletion.test.js.map +1 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js +2 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js.map +1 -1
- package/dist/test/integration/auth/current-password.test.js +2 -1
- package/dist/test/integration/auth/current-password.test.js.map +1 -1
- package/dist/test/integration/auth/session-revocation.test.js +2 -1
- package/dist/test/integration/auth/session-revocation.test.js.map +1 -1
- package/dist/test/integration/auth/sign-in-country.test.js +2 -1
- package/dist/test/integration/auth/sign-in-country.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-codes.test.js +2 -1
- package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-password.test.js +3 -2
- package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
- package/dist/test/integration/email-change.test.js +2 -1
- package/dist/test/integration/email-change.test.js.map +1 -1
- package/dist/test/integration/files/component-files.test.js +2 -2
- package/dist/test/integration/files/component-files.test.js.map +1 -1
- package/dist/test/integration/member-settings-access.test.js +8 -7
- package/dist/test/integration/member-settings-access.test.js.map +1 -1
- package/dist/test/integration/menu-access-events.test.d.ts +1 -0
- package/dist/test/integration/menu-access-events.test.js +148 -0
- package/dist/test/integration/menu-access-events.test.js.map +1 -0
- package/dist/test/integration/notification-inbox.test.js +4 -1
- package/dist/test/integration/notification-inbox.test.js.map +1 -1
- package/dist/test/integration/notification-locked-subjects.test.js +3 -2
- package/dist/test/integration/notification-locked-subjects.test.js.map +1 -1
- package/dist/test/integration/notification-unread-badge.test.js +51 -5
- package/dist/test/integration/notification-unread-badge.test.js.map +1 -1
- package/dist/test/integration/page-layout-route-guard.test.d.ts +1 -0
- package/dist/test/integration/page-layout-route-guard.test.js +56 -0
- package/dist/test/integration/page-layout-route-guard.test.js.map +1 -0
- package/dist/test/integration/permission-warnings.test.js +3 -2
- package/dist/test/integration/permission-warnings.test.js.map +1 -1
- package/dist/test/integration/removed-member-notifications.test.js +3 -2
- package/dist/test/integration/removed-member-notifications.test.js.map +1 -1
- package/dist/test/integration/role-delete-reassign.test.js +2 -1
- package/dist/test/integration/role-delete-reassign.test.js.map +1 -1
- package/dist/test/integration/role-permission-ancestors.test.js +2 -1
- package/dist/test/integration/role-permission-ancestors.test.js.map +1 -1
- package/dist/test/unit/frontend-bootstrap.test.js +13 -0
- package/dist/test/unit/frontend-bootstrap.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js +14 -5
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js +66 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js +26 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/nav-badges.test.js +23 -9
- package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +22 -4
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js +12 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js.map +1 -1
- package/dist/test/unit/pages/settings/role-delete-confirm.test.js.map +1 -1
- package/dist/test/unit/realtime/menu-topics.test.d.ts +1 -0
- package/dist/test/unit/realtime/menu-topics.test.js +26 -0
- package/dist/test/unit/realtime/menu-topics.test.js.map +1 -0
- package/dist/test/unit/store/store.test.d.ts +1 -0
- package/dist/test/unit/store/store.test.js +127 -0
- package/dist/test/unit/store/store.test.js.map +1 -0
- package/dist/validation/store.schema.d.ts +39 -0
- package/dist/validation/store.schema.js +27 -0
- package/dist/validation/store.schema.js.map +1 -0
- package/frontend-vue/deferred-plugins.ts +3 -4
- package/frontend-vue/dms.email.ts +5 -1
- package/frontend-vue/dms.frontend.ts +15 -5
- package/frontend-vue/dms.server.ts +74 -0
- package/frontend-vue/layers/dms-auth/app/composables/useLogout.ts +1 -1
- package/frontend-vue/layers/dms-auth/app/composables/usePostLoginRedirect.ts +16 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/2fa.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/backup.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/oauth-complete.vue +26 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/recover.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +22 -25
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/validate.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -7
- package/frontend-vue/layers/dms-auth/app/plugins/permissions-preload.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +135 -0
- package/frontend-vue/layers/dms-auth/server/actions.ts +251 -0
- package/frontend-vue/layers/dms-auth/server/oauth.ts +260 -0
- package/frontend-vue/layers/dms-auth/server/session.ts +158 -0
- package/frontend-vue/layers/dms-core/app/build/utils/asyncDataScope.ts +47 -0
- package/frontend-vue/layers/dms-core/app/build/utils/permission-preview.ts +6 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +148 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useAuthFetch.ts +7 -20
- package/frontend-vue/layers/dms-core/app/composables/auth/useCurrentUser.ts +2 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +18 -5
- package/frontend-vue/layers/dms-core/app/composables/auth/usePermissions.ts +35 -6
- package/frontend-vue/layers/dms-core/app/composables/auth/useSessionRecovery.ts +1 -1
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +20 -14
- package/frontend-vue/layers/dms-core/app/composables/period/periodClock.ts +121 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +42 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodTimeZone.ts +31 -0
- package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +48 -19
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +33 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +78 -25
- package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +3 -0
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +36 -6
- package/frontend-vue/layers/dms-core/app/composables/user/useUserRegionalPreferences.ts +1 -1
- package/frontend-vue/layers/dms-core/app/plugins/async-data-scope.ts +26 -0
- package/frontend-vue/layers/dms-core/app/utils/pageRoutes.ts +22 -0
- package/frontend-vue/layers/dms-core/app/utils/regional.ts +36 -3
- package/frontend-vue/layers/dms-core/app/utils/trailingDeduper.ts +10 -1
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +48 -4
- package/frontend-vue/layers/dms-layout/app/app.config.ts +42 -23
- package/frontend-vue/layers/dms-layout/app/assets/css/main.css +72 -20
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +0 -3
- package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +9 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreDependencyPicker.vue +136 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +98 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +247 -29
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +19 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +13 -12
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +17 -7
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +36 -33
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +1 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +5 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +35 -28
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder.ts +7 -6
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useVisitNavBadges.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationCatalog.ts +25 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationPreferences.ts +44 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/security/useSessionHandoff.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/preferencesSummary.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfileAvatar.ts +1 -18
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +19 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts +6 -3
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +13 -18
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +186 -120
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-install.ts +146 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +8 -6
- package/frontend-vue/layers/dms-layout/app/build/utils/publicErrorMessage.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -11
- package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +3 -13
- package/frontend-vue/layers/dms-layout/app/composables/general/useSystemState.ts +7 -11
- package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +103 -22
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +109 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useModulesListing.ts +25 -102
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +0 -10
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +177 -96
- package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +341 -62
- package/frontend-vue/layers/dms-layout/app/emails/components/EmailLayout.vue +31 -11
- package/frontend-vue/layers/dms-layout/app/error.vue +5 -2
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/middleware/page-leave-guard.global.ts +2 -2
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +12 -9
- package/frontend-vue/layers/dms-layout/app/plugins/dms-menu-sync.client.ts +31 -3
- package/frontend-vue/layers/dms-layout/app/plugins/favicon.ts +58 -0
- package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +15 -7
- package/frontend-vue/layers/dms-layout/app/plugins/profile-sync.client.ts +5 -1
- package/frontend-vue/layers/dms-layout/app/plugins/regional-preferences.ts +10 -4
- package/frontend-vue/layers/dms-layout/app/plugins/time-zone-cookie.client.ts +12 -0
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +134 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +13 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +107 -9
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +107 -9
- package/frontend-vue/layers/dms-layout/server/html-render.ts +58 -0
- package/frontend-vue/layers/dms-layout/server/render-token.ts +95 -0
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/useOnboarding.ts +25 -21
- package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +1 -1
- package/frontend-vue/layers/dms-onboarding/app/plugins/onboarding-preload.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +4 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/files/useFileMetadata.ts +162 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +23 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageDataRefresh.ts +101 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +33 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +19 -16
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +40 -20
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +6 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +42 -14
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +19 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +5 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +9 -9
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +20 -18
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +26 -132
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +40 -105
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +2 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -4
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +9 -13
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/FilePreview.vue +12 -24
- package/frontend-vue/layers/dms-ui/app/components/table-view/ImagePreview.vue +17 -29
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +7 -3
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +20 -8
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +8 -11
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +146 -182
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLiveRefresh.ts +8 -2
- package/frontend-vue/layers/dms-ui/app/composables/useFileReadUrls.ts +11 -99
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +32 -3
- package/frontend-vue/package.json +3 -3
- package/frontend-vue/pnpm-lock.yaml +16 -22
- package/frontend-vue/server/private-options.ts +27 -0
- package/frontend-vue/tests/async-data-scope.test.ts +121 -0
- package/frontend-vue/tests/auth-middleware-error.test.ts +3 -3
- package/frontend-vue/tests/banner-block-fetch.test.ts +3 -0
- package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +3 -0
- package/frontend-vue/tests/block-items-period-realtime.test.ts +3 -0
- package/frontend-vue/tests/block-not-found.test.ts +16 -2
- package/frontend-vue/tests/block-page-refresh.test.ts +3 -0
- package/frontend-vue/tests/block-route-params.test.ts +3 -0
- package/frontend-vue/tests/branding-defaults.test.ts +34 -0
- package/frontend-vue/tests/builtin-emails.test.ts +23 -2
- package/frontend-vue/tests/chart-live-pause.test.ts +4 -1
- package/frontend-vue/tests/chart-value-propagation.test.ts +4 -1
- package/frontend-vue/tests/composed-text-blocks.test.ts +3 -0
- package/frontend-vue/tests/email-layout.test.ts +66 -4
- package/frontend-vue/tests/error-page-message.test.ts +25 -2
- package/frontend-vue/tests/favicon.test.ts +131 -0
- package/frontend-vue/tests/file-fields-ssr.test.ts +145 -0
- package/frontend-vue/tests/file-read-urls.test.ts +145 -34
- package/frontend-vue/tests/file-unavailable.test.ts +5 -1
- package/frontend-vue/tests/fixtures/dataEngine.ts +44 -0
- package/frontend-vue/tests/form-load-error.test.ts +55 -1
- package/frontend-vue/tests/helpers/api-data-runtime.ts +77 -0
- package/frontend-vue/tests/image-hydration-ids.test.ts +21 -16
- package/frontend-vue/tests/image-reference-preservation.test.ts +59 -53
- package/frontend-vue/tests/input-number-adjust.test.ts +14 -0
- package/frontend-vue/tests/key-value-list-copy.test.ts +3 -0
- package/frontend-vue/tests/member-role-picker.test.ts +4 -2
- package/frontend-vue/tests/menu-sync-reconnect.test.ts +47 -0
- package/frontend-vue/tests/metadata-retry.test.ts +5 -1
- package/frontend-vue/tests/module-routing.test.ts +2 -5
- package/frontend-vue/tests/module-store-catalog.test.ts +270 -51
- package/frontend-vue/tests/modules-catalog.test.ts +17 -4
- package/frontend-vue/tests/modules-listing-transfer.test.ts +95 -0
- package/frontend-vue/tests/multi-account-validate.test.ts +23 -1
- package/frontend-vue/tests/nav-badge-tones.test.ts +66 -3
- package/frontend-vue/tests/notification-counts-from-writes.test.ts +223 -0
- package/frontend-vue/tests/notification-counts-transfer.test.ts +88 -0
- package/frontend-vue/tests/notification-inbox-display.test.ts +57 -5
- package/frontend-vue/tests/notification-popover-errors.test.ts +21 -1
- package/frontend-vue/tests/oauth-complete-handoff.test.ts +209 -0
- package/frontend-vue/tests/onboarding-middleware.test.ts +1 -1
- package/frontend-vue/tests/onboarding-transfer.test.ts +106 -0
- package/frontend-vue/tests/page-data-refresh.test.ts +141 -0
- package/frontend-vue/tests/page-fill-height.test.ts +16 -3
- package/frontend-vue/tests/page-no-visible-blocks.test.ts +12 -2
- package/frontend-vue/tests/page-realtime-remount.test.ts +1 -1
- package/frontend-vue/tests/page-record-label.test.ts +0 -7
- package/frontend-vue/tests/page-validation-redirect.test.ts +12 -2
- package/frontend-vue/tests/period-hydration-now.test.ts +185 -0
- package/frontend-vue/tests/period-live-pause.test.ts +3 -0
- package/frontend-vue/tests/period-reader-zone.test.ts +137 -0
- package/frontend-vue/tests/period-scope-first-fetch.test.ts +17 -1
- package/frontend-vue/tests/period-ssr.test.ts +195 -0
- package/frontend-vue/tests/permission-preview-partial.test.ts +0 -2
- package/frontend-vue/tests/permissions-transfer.test.ts +194 -0
- package/frontend-vue/tests/post-login-redirect.test.ts +10 -5
- package/frontend-vue/tests/profile-personal-info-ssr.test.ts +150 -0
- package/frontend-vue/tests/profile-preferences-summary.test.ts +15 -11
- package/frontend-vue/tests/realtime-reconnect.test.ts +22 -3
- package/frontend-vue/tests/roles-name-errors.test.ts +1 -1
- package/frontend-vue/tests/row-side-panel.test.ts +1 -1
- package/frontend-vue/tests/security-sessions-load-error.test.ts +3 -1
- package/frontend-vue/tests/server-routes-vs-pages.test.ts +26 -0
- package/frontend-vue/tests/server-routes.test.ts +582 -0
- package/frontend-vue/tests/session-handoff.test.ts +1 -1
- package/frontend-vue/tests/settings-reads-ssr.test.ts +232 -0
- package/frontend-vue/tests/side-panels.test.ts +8 -1
- package/frontend-vue/tests/sign-in-exit-history.test.ts +219 -0
- package/frontend-vue/tests/signup-invitation-link.test.ts +56 -8
- package/frontend-vue/tests/site-layout-categories.test.ts +42 -0
- package/frontend-vue/tests/site-layout-hydration.test.ts +3 -3
- package/frontend-vue/tests/site-layout-rendered.test.ts +103 -0
- package/frontend-vue/tests/site-layout-resync.test.ts +87 -0
- package/frontend-vue/tests/ssr-guard-preload.test.ts +188 -0
- package/frontend-vue/tests/table-cell-tones.test.ts +36 -1
- package/frontend-vue/tests/table-data-ssr.test.ts +195 -0
- package/frontend-vue/tests/table-list-data-requests.test.ts +154 -0
- package/frontend-vue/tests/table-retry-loading.test.ts +37 -0
- package/frontend-vue/tests/table-source-navigation.test.ts +0 -1
- package/frontend-vue/tests/table-source-period-scope.test.ts +0 -1
- package/frontend-vue/tests/table-view-views.test.ts +1 -1
- package/frontend-vue/tests/top-list-card-actions.test.ts +3 -0
- package/frontend-vue/tests/top-list-card-period-badge-ssr.test.ts +212 -0
- package/frontend-vue/tests/top-list-card-period-badge.test.ts +79 -0
- package/frontend-vue/tests/top-list-card-value-suffix.test.ts +3 -0
- package/frontend-vue/tests/trailing-deduper.test.ts +22 -0
- package/frontend-vue/tests/use-api-data-update.test.ts +119 -0
- package/frontend-vue/tests/use-api-data.test.ts +410 -0
- package/frontend-vue/tests/user-language.test.ts +8 -5
- package/frontend-vue/tests/widget-label-i18n.test.ts +4 -1
- package/frontend-vue/vitest.config.ts +6 -0
- package/package.json +2 -2
- package/skills/dms/SKILL.md +2 -1
- package/skills/dms-module/SKILL.md +1 -1
- package/skills/dms-pages/SKILL.md +4 -1
- package/frontend-vue/layers/dms-core/app/build/composables/page/usePageRecordLabel.ts +0 -9
- package/frontend-vue/layers/dms-layout/app/composables/page/usePrefetch.ts +0 -21
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useControlError.ts +0 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useServerRenderedAsyncData.ts +0 -23
- package/frontend-vue/tests/control-error-path.test.ts +0 -11
- package/frontend-vue/tests/modules-listing-per-request.test.ts +0 -104
- package/frontend-vue/tests/page-layout-background-refresh.test.ts +0 -321
|
@@ -8,22 +8,39 @@ import {
|
|
|
8
8
|
type PeriodPreset,
|
|
9
9
|
type PeriodRange,
|
|
10
10
|
} from "./types";
|
|
11
|
+
import { readerClock } from "./periodClock";
|
|
12
|
+
|
|
13
|
+
// Resolved in the reader's calendar (`readerClock`): the browser's zone, on
|
|
14
|
+
// the server as in the browser.
|
|
15
|
+
function atTime(
|
|
16
|
+
date: Date,
|
|
17
|
+
hours: number,
|
|
18
|
+
minutes: number,
|
|
19
|
+
seconds: number,
|
|
20
|
+
milliseconds: number,
|
|
21
|
+
): Date {
|
|
22
|
+
const clock = readerClock();
|
|
23
|
+
return clock.instant({
|
|
24
|
+
...clock.wall(date),
|
|
25
|
+
hours,
|
|
26
|
+
minutes,
|
|
27
|
+
seconds,
|
|
28
|
+
milliseconds,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
11
31
|
|
|
12
32
|
function startOfDay(date: Date): Date {
|
|
13
|
-
|
|
14
|
-
next.setHours(0, 0, 0, 0);
|
|
15
|
-
return next;
|
|
33
|
+
return atTime(date, 0, 0, 0, 0);
|
|
16
34
|
}
|
|
17
35
|
|
|
18
36
|
function endOfDay(date: Date): Date {
|
|
19
|
-
|
|
20
|
-
|
|
37
|
+
return atTime(
|
|
38
|
+
date,
|
|
21
39
|
END_OF_DAY_HOURS,
|
|
22
40
|
END_OF_DAY_MINUTES,
|
|
23
41
|
END_OF_DAY_SECONDS,
|
|
24
42
|
END_OF_DAY_MS,
|
|
25
43
|
);
|
|
26
|
-
return next;
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
function addDays(date: Date, amount: number): Date {
|
|
@@ -34,35 +51,47 @@ function addHours(date: Date, amount: number): Date {
|
|
|
34
51
|
return new Date(date.getTime() + amount * MS_PER_HOUR);
|
|
35
52
|
}
|
|
36
53
|
|
|
54
|
+
/** Midnight of a day given as year, month and day; they roll over as `Date`'s. */
|
|
55
|
+
function dayStart(year: number, month: number, day: number): Date {
|
|
56
|
+
return readerClock().instant({
|
|
57
|
+
year,
|
|
58
|
+
month,
|
|
59
|
+
day,
|
|
60
|
+
hours: 0,
|
|
61
|
+
minutes: 0,
|
|
62
|
+
seconds: 0,
|
|
63
|
+
milliseconds: 0,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
37
67
|
function startOfMonth(date: Date, monthOffset = 0): Date {
|
|
38
|
-
|
|
68
|
+
const { year, month } = readerClock().wall(date);
|
|
69
|
+
return dayStart(year, month + monthOffset, 1);
|
|
39
70
|
}
|
|
40
71
|
|
|
41
72
|
function endOfMonth(date: Date, monthOffset = 0): Date {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
date.getMonth() + monthOffset + 1,
|
|
45
|
-
0,
|
|
46
|
-
);
|
|
47
|
-
return endOfDay(next);
|
|
73
|
+
const { year, month } = readerClock().wall(date);
|
|
74
|
+
return endOfDay(dayStart(year, month + monthOffset + 1, 0));
|
|
48
75
|
}
|
|
49
76
|
|
|
50
77
|
function startOfQuarter(date: Date, quarterOffset = 0): Date {
|
|
51
|
-
const
|
|
52
|
-
|
|
78
|
+
const { year, month } = readerClock().wall(date);
|
|
79
|
+
const quarter = Math.floor(month / 3) + quarterOffset;
|
|
80
|
+
return dayStart(year, quarter * 3, 1);
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
function endOfQuarter(date: Date, quarterOffset = 0): Date {
|
|
56
|
-
const
|
|
57
|
-
|
|
84
|
+
const { year, month } = readerClock().wall(date);
|
|
85
|
+
const quarter = Math.floor(month / 3) + quarterOffset;
|
|
86
|
+
return endOfDay(dayStart(year, quarter * 3 + 3, 0));
|
|
58
87
|
}
|
|
59
88
|
|
|
60
89
|
function startOfYear(date: Date, yearOffset = 0): Date {
|
|
61
|
-
return
|
|
90
|
+
return dayStart(readerClock().wall(date).year + yearOffset, 0, 1);
|
|
62
91
|
}
|
|
63
92
|
|
|
64
93
|
function endOfYear(date: Date, yearOffset = 0): Date {
|
|
65
|
-
return endOfDay(
|
|
94
|
+
return endOfDay(dayStart(readerClock().wall(date).year + yearOffset, 11, 31));
|
|
66
95
|
}
|
|
67
96
|
|
|
68
97
|
const PRESET_RANGE_RESOLVERS: Record<
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
computed,
|
|
3
3
|
getCurrentScope,
|
|
4
|
+
onMounted,
|
|
4
5
|
onScopeDispose,
|
|
5
6
|
ref,
|
|
7
|
+
shallowRef,
|
|
6
8
|
watch,
|
|
7
9
|
type ComputedRef,
|
|
8
10
|
type Ref,
|
|
@@ -23,6 +25,7 @@ import {
|
|
|
23
25
|
resolvePresetRange,
|
|
24
26
|
} from "./presetResolvers";
|
|
25
27
|
import { resolveComparisonRange } from "./compareResolvers";
|
|
28
|
+
import { periodRenderNow } from "./periodRenderNow";
|
|
26
29
|
|
|
27
30
|
export interface UsePeriodOptions {
|
|
28
31
|
defaultPreset?: PeriodPreset;
|
|
@@ -47,6 +50,11 @@ export interface UsePeriodReturn {
|
|
|
47
50
|
presets: ComputedRef<PeriodPreset[]>;
|
|
48
51
|
comparisons: ComputedRef<PeriodComparison[]>;
|
|
49
52
|
state: ComputedRef<PeriodState>;
|
|
53
|
+
/**
|
|
54
|
+
* The instant `state` was resolved from: what a label relative to now
|
|
55
|
+
* reads, so it holds still with the period (see `periodRenderNow`).
|
|
56
|
+
*/
|
|
57
|
+
resolvedAt: ComputedRef<Date>;
|
|
50
58
|
setPreset: (preset: PeriodPreset) => void;
|
|
51
59
|
setComparison: (comparison: PeriodComparison) => void;
|
|
52
60
|
setCustomRange: (range: PeriodRange | null) => void;
|
|
@@ -135,8 +143,7 @@ interface PeriodRefs {
|
|
|
135
143
|
customCompareRange: Ref<PeriodRange | null>;
|
|
136
144
|
}
|
|
137
145
|
|
|
138
|
-
function buildState(refs: PeriodRefs): PeriodState {
|
|
139
|
-
const now = new Date();
|
|
146
|
+
function buildState(refs: PeriodRefs, now: Date): PeriodState {
|
|
140
147
|
const fallback = refs.customRange.value ?? defaultFallbackRange(now);
|
|
141
148
|
const range = resolvePresetRange(refs.preset.value, now, fallback);
|
|
142
149
|
const compareRange = resolveComparisonRange(
|
|
@@ -176,6 +183,9 @@ function makeSetters(refs: PeriodRefs) {
|
|
|
176
183
|
* Reactive period state machine behind `PeriodSelector` — presets,
|
|
177
184
|
* comparisons and custom ranges resolved into a publishable `state`.
|
|
178
185
|
*
|
|
186
|
+
* A server render and the browser hydrating it resolve from the same instant
|
|
187
|
+
* (`periodRenderNow`), so both render the same bounds.
|
|
188
|
+
*
|
|
179
189
|
* Relative presets (`last-hour`, `last-24h`) re-resolve every minute, and
|
|
180
190
|
* calendar presets (`today`, `last-7-days`, `this-month`...) at each local
|
|
181
191
|
* midnight, so a page left open overnight moves on to the new day.
|
|
@@ -201,10 +211,28 @@ export function usePeriod(options: UsePeriodOptions = {}): UsePeriodReturn {
|
|
|
201
211
|
() => options.comparisons ?? DEFAULT_COMPARISONS,
|
|
202
212
|
);
|
|
203
213
|
const presetClock = usePresetClock(refs.preset, options.holdClock);
|
|
204
|
-
|
|
214
|
+
// The server render's instant, until the clock ticks or the period changes
|
|
215
|
+
// once on screen: what the setup applies (the URL's period) the server
|
|
216
|
+
// applied as well.
|
|
217
|
+
const renderNow = shallowRef(periodRenderNow());
|
|
218
|
+
if (renderNow.value) {
|
|
219
|
+
onMounted(() => {
|
|
220
|
+
watch(
|
|
221
|
+
[presetClock, ...Object.values(refs)],
|
|
222
|
+
() => {
|
|
223
|
+
renderNow.value = null;
|
|
224
|
+
},
|
|
225
|
+
{ flush: "sync" },
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const resolution = computed(() => {
|
|
205
230
|
void presetClock.value;
|
|
206
|
-
|
|
231
|
+
const now = renderNow.value ?? new Date();
|
|
232
|
+
return { now, state: buildState(refs, now) };
|
|
207
233
|
});
|
|
234
|
+
const state = computed<PeriodState>(() => resolution.value.state);
|
|
235
|
+
const resolvedAt = computed<Date>(() => resolution.value.now);
|
|
208
236
|
const setters = makeSetters(refs);
|
|
209
237
|
|
|
210
238
|
return {
|
|
@@ -212,6 +240,7 @@ export function usePeriod(options: UsePeriodOptions = {}): UsePeriodReturn {
|
|
|
212
240
|
presets,
|
|
213
241
|
comparisons,
|
|
214
242
|
state,
|
|
243
|
+
resolvedAt,
|
|
215
244
|
...setters,
|
|
216
245
|
};
|
|
217
246
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { computed, ref, type ComputedRef } from "vue";
|
|
1
|
+
import { computed, markRaw, ref, type ComputedRef, type Ref } from "vue";
|
|
2
|
+
import { resolvesPeriodsAsBrowser } from "./periodTimeZone";
|
|
2
3
|
import type { PeriodState } from "./types";
|
|
3
4
|
|
|
4
5
|
interface PeriodScopeRegistration {
|
|
@@ -6,65 +7,117 @@ interface PeriodScopeRegistration {
|
|
|
6
7
|
refCount: number;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
interface PeriodScopeRegistry {
|
|
11
|
+
scopes: Map<string, PeriodScopeRegistration>;
|
|
12
|
+
version: Ref<number>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const PERIOD_SCOPES_STATE_KEY = "dms-period-scopes";
|
|
11
16
|
|
|
12
|
-
function
|
|
13
|
-
|
|
17
|
+
function createRegistry(): PeriodScopeRegistry {
|
|
18
|
+
return markRaw({ scopes: new Map(), version: ref(0) });
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
// A browser tab runs one application, whose scopes every caller shares; a
|
|
22
|
+
// server renders many requests at once, each with its own scopes.
|
|
23
|
+
const browserRegistry = createRegistry();
|
|
24
|
+
|
|
25
|
+
function usePeriodScopeRegistry(): PeriodScopeRegistry {
|
|
26
|
+
if (typeof window !== "undefined") return browserRegistry;
|
|
27
|
+
return useDmsState(PERIOD_SCOPES_STATE_KEY, createRegistry).value;
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
export function registerPeriodScope(
|
|
19
31
|
id: string,
|
|
20
32
|
state: ComputedRef<PeriodState>,
|
|
21
33
|
): () => void {
|
|
22
|
-
|
|
23
|
-
const existing =
|
|
34
|
+
const registry = usePeriodScopeRegistry();
|
|
35
|
+
const existing = registry.scopes.get(id);
|
|
24
36
|
if (existing) {
|
|
25
37
|
existing.state = state;
|
|
26
38
|
existing.refCount += 1;
|
|
27
39
|
} else {
|
|
28
|
-
|
|
40
|
+
registry.scopes.set(id, { state, refCount: 1 });
|
|
29
41
|
}
|
|
30
|
-
|
|
42
|
+
registry.version.value += 1;
|
|
31
43
|
return () => {
|
|
32
|
-
const entry =
|
|
44
|
+
const entry = registry.scopes.get(id);
|
|
33
45
|
if (!entry) return;
|
|
34
46
|
entry.refCount -= 1;
|
|
35
|
-
if (entry.refCount <= 0)
|
|
36
|
-
|
|
47
|
+
if (entry.refCount <= 0) registry.scopes.delete(id);
|
|
48
|
+
registry.version.value += 1;
|
|
37
49
|
};
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
const warnedMissingScopes = new Set<string>();
|
|
41
53
|
const MISSING_SCOPE_WARN_DELAY_MS = 250;
|
|
42
54
|
|
|
43
|
-
function deferMissingScopeWarn(
|
|
55
|
+
function deferMissingScopeWarn(
|
|
56
|
+
registry: PeriodScopeRegistry,
|
|
57
|
+
id: string,
|
|
58
|
+
): void {
|
|
44
59
|
if (warnedMissingScopes.has(id)) return;
|
|
45
60
|
warnedMissingScopes.add(id);
|
|
46
61
|
if (typeof window === "undefined") return;
|
|
47
62
|
setTimeout(() => {
|
|
48
|
-
if (
|
|
63
|
+
if (registry.scopes.has(id)) return;
|
|
49
64
|
console.warn(
|
|
50
65
|
`[dms] periodScope "${id}" is not registered. The consumer will run without period filtering. Add a PeriodSelector with id="${id}" to bind it.`,
|
|
51
66
|
);
|
|
52
67
|
}, MISSING_SCOPE_WARN_DELAY_MS);
|
|
53
68
|
}
|
|
54
69
|
|
|
70
|
+
function readPeriodScope(
|
|
71
|
+
registry: PeriodScopeRegistry,
|
|
72
|
+
id: string,
|
|
73
|
+
): PeriodState | null {
|
|
74
|
+
void registry.version.value;
|
|
75
|
+
const entry = registry.scopes.get(id);
|
|
76
|
+
if (!entry) {
|
|
77
|
+
deferMissingScopeWarn(registry, id);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return entry.state.value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The state of a period scope, `null` while no selector registers it.
|
|
85
|
+
*
|
|
86
|
+
* A server render reads it once, as the caller sets up: a selector that sets
|
|
87
|
+
* up later (further down the page) leaves the caller to the browser, whose
|
|
88
|
+
* first render must match the HTML. It also answers `null` for a period the
|
|
89
|
+
* browser could resolve to other bounds: before the browser's cookie names
|
|
90
|
+
* its time zone. A relative preset resolves from the instant the server
|
|
91
|
+
* hands the browser (`periodRenderNow`), so both read the same bounds.
|
|
92
|
+
*/
|
|
55
93
|
export function usePeriodScope(
|
|
56
94
|
id: string | undefined,
|
|
57
95
|
): ComputedRef<PeriodState | null> {
|
|
58
|
-
return computed(() =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
96
|
+
if (!id) return computed(() => null);
|
|
97
|
+
const registry = usePeriodScopeRegistry();
|
|
98
|
+
if (typeof window !== "undefined")
|
|
99
|
+
return computed(() => readPeriodScope(registry, id));
|
|
100
|
+
const state = readPeriodScope(registry, id);
|
|
101
|
+
const resolvesAsBrowser = state !== null && resolvesPeriodsAsBrowser();
|
|
102
|
+
return computed(() => (resolvesAsBrowser ? state : null));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The preset of a period scope, `null` while no selector registers it: what
|
|
107
|
+
* a card's period badge names. A server render reads it once, as the caller
|
|
108
|
+
* sets up, like `usePeriodScope`, but whatever bounds the browser would
|
|
109
|
+
* resolve the period to: a preset is the same name on both sides, so the
|
|
110
|
+
* server prints the badge the browser hydrates.
|
|
111
|
+
*/
|
|
112
|
+
export function usePeriodScopePreset(
|
|
113
|
+
id: string | undefined,
|
|
114
|
+
): ComputedRef<PeriodState["preset"] | null> {
|
|
115
|
+
if (!id) return computed(() => null);
|
|
116
|
+
const registry = usePeriodScopeRegistry();
|
|
117
|
+
if (typeof window !== "undefined")
|
|
118
|
+
return computed(() => readPeriodScope(registry, id)?.preset ?? null);
|
|
119
|
+
const preset = readPeriodScope(registry, id)?.preset ?? null;
|
|
120
|
+
return computed(() => preset);
|
|
68
121
|
}
|
|
69
122
|
|
|
70
123
|
/**
|
|
@@ -10,6 +10,8 @@ export interface PageRealtimeApi {
|
|
|
10
10
|
subscribe(topic: string, handler: RealtimeTopicHandler): () => void;
|
|
11
11
|
sessionId: Ref<string | undefined>;
|
|
12
12
|
pageId: Ref<string | undefined>;
|
|
13
|
+
/** The page's realtime topics, once the session is subscribed to it. */
|
|
14
|
+
pageTopics: Ref<string[]>;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
interface PageClaim {
|
|
@@ -70,6 +72,7 @@ export function usePageRealtime(
|
|
|
70
72
|
subscribe: user.subscribe,
|
|
71
73
|
sessionId: user.sessionId,
|
|
72
74
|
pageId: resolvedPageId,
|
|
75
|
+
pageTopics: user.pageTopics,
|
|
73
76
|
};
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -51,12 +51,18 @@ export interface UserRealtimeApi {
|
|
|
51
51
|
* announced in the greeting.
|
|
52
52
|
*/
|
|
53
53
|
menuTopics: Ref<string[]>;
|
|
54
|
+
/**
|
|
55
|
+
* The realtime topics of the page this session is on, as its subscription
|
|
56
|
+
* answered them: an event on one means the page's data moved.
|
|
57
|
+
*/
|
|
58
|
+
pageTopics: Ref<string[]>;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
interface UserRealtimeState {
|
|
57
62
|
sessionId: Ref<string | undefined>;
|
|
58
63
|
pageId: Ref<string | undefined>;
|
|
59
64
|
menuTopics: Ref<string[]>;
|
|
65
|
+
pageTopics: Ref<string[]>;
|
|
60
66
|
appliedPageId: Ref<string | null>;
|
|
61
67
|
handlers: Map<string, Set<RealtimeTopicHandler>>;
|
|
62
68
|
snapshots: Map<string, RealtimeSnapshotEvent>;
|
|
@@ -230,22 +236,42 @@ const connectWithBackoff = (
|
|
|
230
236
|
};
|
|
231
237
|
};
|
|
232
238
|
|
|
239
|
+
interface PageSubscriptionAnswer {
|
|
240
|
+
topics?: unknown;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** The topics a page subscription answered, none when it answered none. */
|
|
244
|
+
const readPageTopics = async (response: Response): Promise<string[]> => {
|
|
245
|
+
let answer: PageSubscriptionAnswer | null;
|
|
246
|
+
try {
|
|
247
|
+
answer = (await response.json()) as PageSubscriptionAnswer | null;
|
|
248
|
+
} catch {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
const topics = answer?.topics;
|
|
252
|
+
return Array.isArray(topics)
|
|
253
|
+
? topics.filter((topic): topic is string => typeof topic === "string")
|
|
254
|
+
: [];
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/** Subscribes the session to `pageId`, or to no page; returns its topics. */
|
|
233
258
|
const callPageEndpoint = async (
|
|
234
259
|
state: UserRealtimeState,
|
|
235
260
|
pageId: string | null,
|
|
236
261
|
baseUrl: string,
|
|
237
|
-
): Promise<
|
|
262
|
+
): Promise<string[]> => {
|
|
238
263
|
const sessionId = state.sessionId.value;
|
|
239
|
-
if (!sessionId) return;
|
|
264
|
+
if (!sessionId) return [];
|
|
240
265
|
const headers: Record<string, string> = { [SESSION_HEADER]: sessionId };
|
|
241
266
|
const url = pageId
|
|
242
267
|
? `${baseUrl}${REALTIME_SUBSCRIBE_PATH_PREFIX}${pageId}`
|
|
243
268
|
: `${baseUrl}${REALTIME_UNSUBSCRIBE_PATH}`;
|
|
244
|
-
await fetch(url, {
|
|
269
|
+
const response = await fetch(url, {
|
|
245
270
|
method: "POST",
|
|
246
271
|
headers,
|
|
247
272
|
credentials: "include",
|
|
248
273
|
});
|
|
274
|
+
return pageId && response.ok ? readPageTopics(response) : [];
|
|
249
275
|
};
|
|
250
276
|
|
|
251
277
|
const applyDesiredPage = async (
|
|
@@ -256,11 +282,12 @@ const applyDesiredPage = async (
|
|
|
256
282
|
const sessionId = state.sessionId.value;
|
|
257
283
|
if (state.appliedPageId.value === desired) return;
|
|
258
284
|
if (!sessionId) return;
|
|
259
|
-
await callPageEndpoint(state, desired, baseUrl);
|
|
285
|
+
const topics = await callPageEndpoint(state, desired, baseUrl);
|
|
260
286
|
// A reconnect during the call replaced the session: the page is applied to
|
|
261
287
|
// the dead one, and the new one still needs it.
|
|
262
288
|
if (state.sessionId.value !== sessionId) return;
|
|
263
289
|
state.appliedPageId.value = desired;
|
|
290
|
+
state.pageTopics.value = topics;
|
|
264
291
|
};
|
|
265
292
|
|
|
266
293
|
/**
|
|
@@ -288,6 +315,7 @@ const reconcilePage = (
|
|
|
288
315
|
const resetSession = (state: UserRealtimeState): void => {
|
|
289
316
|
state.sessionId.value = undefined;
|
|
290
317
|
state.appliedPageId.value = null;
|
|
318
|
+
state.pageTopics.value = [];
|
|
291
319
|
state.snapshots.clear();
|
|
292
320
|
};
|
|
293
321
|
|
|
@@ -299,10 +327,12 @@ const buildApi = (): UserRealtimeApi => {
|
|
|
299
327
|
const menuTopics = useDmsState<string[]>(MENU_TOPICS_STATE_KEY, () => []);
|
|
300
328
|
const appliedPageId = ref<string | null>(null);
|
|
301
329
|
const pageId = ref<string | undefined>(undefined);
|
|
330
|
+
const pageTopics = ref<string[]>([]);
|
|
302
331
|
const state: UserRealtimeState = {
|
|
303
332
|
sessionId,
|
|
304
333
|
pageId,
|
|
305
334
|
menuTopics,
|
|
335
|
+
pageTopics,
|
|
306
336
|
appliedPageId,
|
|
307
337
|
handlers: new Map(),
|
|
308
338
|
snapshots: new Map(),
|
|
@@ -310,7 +340,7 @@ const buildApi = (): UserRealtimeApi => {
|
|
|
310
340
|
};
|
|
311
341
|
|
|
312
342
|
const config = useDmsRuntimeConfig();
|
|
313
|
-
const { loggedIn } =
|
|
343
|
+
const { loggedIn } = useDmsUserSession();
|
|
314
344
|
const { refreshSession, redirectToAuth } = useSessionRecovery();
|
|
315
345
|
const baseUrl = config.public.dms.baseURL as string;
|
|
316
346
|
|
|
@@ -366,7 +396,7 @@ const buildApi = (): UserRealtimeApi => {
|
|
|
366
396
|
await reconcilePage(state, baseUrl);
|
|
367
397
|
};
|
|
368
398
|
|
|
369
|
-
return { subscribe, setPage, sessionId, pageId, menuTopics };
|
|
399
|
+
return { subscribe, setPage, sessionId, pageId, menuTopics, pageTopics };
|
|
370
400
|
};
|
|
371
401
|
|
|
372
402
|
export function useUserRealtime(): UserRealtimeApi {
|
|
@@ -27,7 +27,7 @@ function localeTimeFormat(locale: string): TimeFormatPreference {
|
|
|
27
27
|
* the values themselves, such as a calendar's `weekStartsOn`.
|
|
28
28
|
*/
|
|
29
29
|
export function useUserRegionalPreferences() {
|
|
30
|
-
const { user } =
|
|
30
|
+
const { user } = useDmsUserSession<RegionalPreferenceFields>();
|
|
31
31
|
const { locale } = useI18n();
|
|
32
32
|
|
|
33
33
|
const preferences = computed<RegionalPreferences>(() =>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { watch } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
defineDmsPlugin,
|
|
4
|
+
setDmsAsyncDataScope,
|
|
5
|
+
useDmsUserSession,
|
|
6
|
+
} from "#dms/frontend-module";
|
|
7
|
+
import {
|
|
8
|
+
asyncDataScopeOf,
|
|
9
|
+
type ScopedSession,
|
|
10
|
+
} from "../build/utils/asyncDataScope";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Keeps every `useApiData` / `useDmsAsyncData` key in the scope of the
|
|
14
|
+
* session's account and workspace, on both sides before the page renders:
|
|
15
|
+
* the browser finds the data the server transferred under the same scope,
|
|
16
|
+
* and a switch of account or workspace drops the data of the previous one
|
|
17
|
+
* instead of showing it under the new session.
|
|
18
|
+
*/
|
|
19
|
+
export default defineDmsPlugin(() => {
|
|
20
|
+
const { session } = useDmsUserSession<unknown, ScopedSession>();
|
|
21
|
+
const scope = () => asyncDataScopeOf(session.value);
|
|
22
|
+
setDmsAsyncDataScope(scope());
|
|
23
|
+
// One app per request on a server: its session never changes there.
|
|
24
|
+
if (typeof window === "undefined") return;
|
|
25
|
+
watch(scope, (next) => setDmsAsyncDataScope(next));
|
|
26
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Mirror of PAGE_ROUTE_PREFIX and pageRoutePath from
|
|
2
|
+
// @antelopejs/interface-dms/page.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Where the backend serves the routes a page controller declares, and its
|
|
6
|
+
* layout: under the DMS's own prefix, which the frontend server proxies. The
|
|
7
|
+
* page's own path is the page, never a backend route.
|
|
8
|
+
*/
|
|
9
|
+
export const PAGE_ROUTE_PREFIX = "/dms/pages";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The backend path of a page's route, as the browser calls it:
|
|
13
|
+
* `pageRoutePath("/settings/user/security", "/sessions")` is
|
|
14
|
+
* `/dms/pages/settings/user/security/sessions`.
|
|
15
|
+
*
|
|
16
|
+
* @param pageSlug The page's path, such as `/settings/user/security`
|
|
17
|
+
* @param route The route below the page; none for the page's own route
|
|
18
|
+
*/
|
|
19
|
+
export function pageRoutePath(pageSlug: string, route = ""): string {
|
|
20
|
+
const path = `${PAGE_ROUTE_PREFIX}/${pageSlug}/${route}`.replace(/\/+/g, "/");
|
|
21
|
+
return path.endsWith("/") ? path.slice(0, -1) : path;
|
|
22
|
+
}
|
|
@@ -37,6 +37,7 @@ export interface RegionalOptionsSettings {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
type PreferencesSource = () => RegionalPreferences;
|
|
40
|
+
type ReaderTimeZoneSource = () => string | null | undefined;
|
|
40
41
|
|
|
41
42
|
const WEEK_STARTS: readonly number[] = [0, 1, 6];
|
|
42
43
|
const TIME_FORMATS: readonly string[] = ["h23", "h12"];
|
|
@@ -73,6 +74,7 @@ const NUMERIC_DATE: Intl.DateTimeFormatOptions = {
|
|
|
73
74
|
|
|
74
75
|
const knownTimeZones = new Map<string, boolean>();
|
|
75
76
|
let preferencesSource: PreferencesSource | undefined;
|
|
77
|
+
let readerTimeZoneSource: ReaderTimeZoneSource | undefined;
|
|
76
78
|
|
|
77
79
|
/** Whether the runtime knows `timeZone` (an unknown one would throw in Intl). */
|
|
78
80
|
export function isKnownTimeZone(timeZone: string): boolean {
|
|
@@ -125,6 +127,35 @@ export function setRegionalPreferencesSource(source: PreferencesSource): void {
|
|
|
125
127
|
preferencesSource = source;
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Tells a server render where to read the zone of the browser it renders for
|
|
132
|
+
* (its `dms-time-zone` cookie). Called on every read, so it resolves the
|
|
133
|
+
* request being rendered rather than a value captured once.
|
|
134
|
+
*/
|
|
135
|
+
export function setReaderTimeZoneSource(source: ReaderTimeZoneSource): void {
|
|
136
|
+
readerTimeZoneSource = source;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The time zone of the browser the page is for, when it is not the zone the
|
|
141
|
+
* code runs in: `undefined` in the browser, where it is; on a server, the zone
|
|
142
|
+
* the browser's cookie names, if this runtime knows it. Dates computed or
|
|
143
|
+
* written for the browser use it, so the server renders what the browser
|
|
144
|
+
* renders.
|
|
145
|
+
*/
|
|
146
|
+
export function readerTimeZone(): string | undefined {
|
|
147
|
+
if (typeof window !== "undefined") return undefined;
|
|
148
|
+
let zone: string | null | undefined;
|
|
149
|
+
try {
|
|
150
|
+
zone = readerTimeZoneSource?.();
|
|
151
|
+
} catch {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
return zone && zone !== browserTimeZone() && isKnownTimeZone(zone)
|
|
155
|
+
? zone
|
|
156
|
+
: undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
128
159
|
/** The current user's preferences; none outside an application. */
|
|
129
160
|
export function currentRegionalPreferences(): RegionalPreferences {
|
|
130
161
|
try {
|
|
@@ -195,8 +226,10 @@ export function withRegionalOptions(
|
|
|
195
226
|
let result = preferences.dateFormat
|
|
196
227
|
? DATE_FORMAT_REWRITES[preferences.dateFormat](options)
|
|
197
228
|
: { ...options };
|
|
198
|
-
|
|
199
|
-
|
|
229
|
+
const timeZone =
|
|
230
|
+
(!settings.keepLocalZone && preferences.timeZone) || readerTimeZone();
|
|
231
|
+
if (timeZone && !result.timeZone) {
|
|
232
|
+
result = { ...result, timeZone };
|
|
200
233
|
}
|
|
201
234
|
const statesClock =
|
|
202
235
|
result.hour12 !== undefined || result.hourCycle !== undefined;
|
|
@@ -238,7 +271,7 @@ export function regionalDayNumber(
|
|
|
238
271
|
preferences: RegionalPreferences = currentRegionalPreferences(),
|
|
239
272
|
): number {
|
|
240
273
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
241
|
-
timeZone: preferences.timeZone,
|
|
274
|
+
timeZone: preferences.timeZone ?? readerTimeZone(),
|
|
242
275
|
year: "numeric",
|
|
243
276
|
month: "numeric",
|
|
244
277
|
day: "numeric",
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export interface TrailingDeduper {
|
|
2
2
|
run(task: () => Promise<void>): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Joins the run in flight, or starts one: for a caller that needs a result
|
|
5
|
+
* rather than a fresh one, such as a first load, which a run started
|
|
6
|
+
* before it answers as well as its own would.
|
|
7
|
+
*/
|
|
8
|
+
join(task: () => Promise<void>): Promise<void>;
|
|
3
9
|
}
|
|
4
10
|
|
|
5
11
|
/**
|
|
@@ -38,5 +44,8 @@ export function createTrailingDeduper(): TrailingDeduper {
|
|
|
38
44
|
return trailing;
|
|
39
45
|
};
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
const join = (task: () => Promise<void>): Promise<void> =>
|
|
48
|
+
inFlight ?? run(task);
|
|
49
|
+
|
|
50
|
+
return { run, join };
|
|
42
51
|
}
|