diff --git a/packages/types-builder/build.ts b/packages/types-builder/build.ts index ac17fea..df2f7d0 100755 --- a/packages/types-builder/build.ts +++ b/packages/types-builder/build.ts @@ -359,6 +359,7 @@ const emit = defineEmits(emits) for (const locale of vjsfLocales) { const schemaVjsfOpts = { ...schema['x-vjsf'] } delete schemaVjsfOpts.compName + console.log(` compiledLayout ${locale} options: ${JSON.stringify(schemaVjsfOpts)}`) const otherSchemas = { ...schemas } for (const [key, otherSchema] of Object.entries(schemas)) { if (key === schema.$id) continue @@ -375,7 +376,6 @@ const emit = defineEmits(emits) fullOptions.components[componentInfo.name] = componentInfo } - console.log(` compiledLayout ${locale} options: ${JSON.stringify(schemaVjsfOpts)}`) const compiledLayout = compileLayout(schema, fullOptions) let compiledLayoutCode = await serializeCompiledLayout(compiledLayout) // The serialized code declares `const compiledLayout = {...}`. diff --git a/packages/vue/session.ts b/packages/vue/session.ts index dbe62db..41fd02c 100644 --- a/packages/vue/session.ts +++ b/packages/vue/session.ts @@ -82,7 +82,8 @@ export interface SiteInfo { owner: AccountKeys } -type Theme = 'default' | 'dark' | 'hc' | 'hc-dark' +type AppliedTheme = 'default' | 'dark' | 'hc' | 'hc-dark' +type Theme = AppliedTheme | 'system' export interface Session { state: SessionState @@ -120,16 +121,21 @@ export type SessionAuthenticated = Omit): Promise const setSiteInfo = (siteInfo: any) => { if (siteInfo.theme) { fullSite.value = siteInfo + // an absent cookie is treated as an implicit 'system' choice so the + // theme-switcher radio has a value to bind to. + if (theme.value == null) theme.value = 'system' const partialSite: SiteInfo = { main: siteInfo.main, isAccountMain: siteInfo.isAccountMain, @@ -412,13 +421,13 @@ export async function getSession (initOptions: Partial): Promise authOnlyOtherSite: siteInfo.authOnlyOtherSite, owner: siteInfo.owner } - if (theme.value == null) theme.value = getDefaultTheme(siteInfo) - if (theme.value === 'hc') partialSite.colors = siteInfo.theme.hcColors - if (theme.value === 'dark') { + const applied = resolveTheme(theme.value, siteInfo) + if (applied === 'hc') partialSite.colors = siteInfo.theme.hcColors + if (applied === 'dark') { partialSite.colors = siteInfo.theme.darkColors partialSite.dark = true } - if (theme.value === 'hc-dark') { + if (applied === 'hc-dark') { partialSite.colors = siteInfo.theme.hcDarkColors partialSite.dark = true } @@ -433,6 +442,16 @@ export async function getSession (initOptions: Partial): Promise // @ts-ignore if (!ssr && window.__PUBLIC_SITE_INFO) setSiteInfo(window.__PUBLIC_SITE_INFO) + // re-apply the theme when the OS preference changes while the user is on + // 'system'. Important for mobile devices that switch light/dark over the day. + if (!ssr && typeof window !== 'undefined' && window.matchMedia) { + const onOsPrefChange = () => { + if (theme.value === 'system' && fullSite.value) setSiteInfo(fullSite.value) + } + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', onOsPrefChange) + window.matchMedia('(forced-colors: active)').addEventListener('change', onOsPrefChange) + } + // immediately performs a keepalive, but only on top windows (not iframes or popups) // and only if it was not done very recently (maybe from a refreshed page next to this one) // also run an auto-refresh loop diff --git a/packages/vuetify/layout-fetch-error.vue b/packages/vuetify/layout-fetch-error.vue index f56e0bc..57d10fe 100644 --- a/packages/vuetify/layout-fetch-error.vue +++ b/packages/vuetify/layout-fetch-error.vue @@ -64,7 +64,7 @@ const { error, backTo = '/', backLabel } = defineProps<{ }>() const { t } = useI18n({ useScope: 'local' }) -const { switchOrganization, user } = useSession() +const { switchOrganization, user, account } = useSession() const statusCode = computed(() => error?.statusCode ?? error?.status ?? 500) @@ -99,9 +99,25 @@ const switchOrg = computed(() => { try { owner = JSON.parse(rawOwner) } catch { return null } if (!owner || owner.type !== 'organization' || !owner.id) return null - return user.value.organizations?.find(o => - o.id === owner!.id && (o.department ?? undefined) === (owner!.department ?? undefined) - ) ?? null + const orgs = user.value.organizations ?? [] + const ownerDept = owner.department ?? undefined + const isCurrentAccount = (o: { id: string, department?: string }) => + account.value?.type === 'organization' && + account.value.id === o.id && + (account.value.department ?? undefined) === (o.department ?? undefined) + + // Prefer a membership matching the resource's department exactly... + const exact = orgs.find(o => + o.id === owner!.id && (o.department ?? undefined) === ownerDept && !isCurrentAccount(o) + ) + if (exact) return exact + // ...otherwise fall back to a membership at the organization root: in + // simple-directory's authz model, root access generally grants visibility + // over department-scoped resources. + if (ownerDept) { + return orgs.find(o => o.id === owner!.id && !o.department && !isCurrentAccount(o)) ?? null + } + return null }) const doSwitch = () => { diff --git a/packages/vuetify/personal-menu.vue b/packages/vuetify/personal-menu.vue index 83e0410..508349c 100644 --- a/packages/vuetify/personal-menu.vue +++ b/packages/vuetify/personal-menu.vue @@ -182,9 +182,8 @@ fr: openPersonalMenu: Ouvrez le menu personnel personalAccount: Compte personnel switchAccount: Changer de compte - adminMode: mode admin + adminMode: Mode admin backToAdmin: Revenir à ma session administrateur - darkMode: mode nuit plannedDeletion: La suppression de l'utilisateur {name} et toutes ses informations est programmée le {plannedDeletion}. cancelDeletion: Annuler la suppression de l'utilisateur en: @@ -193,9 +192,8 @@ en: openPersonalMenu: Open personal menu personalAccount: Personal account switchAccount: Switch account - adminMode: admin mode + adminMode: Admin mode backToAdmin: Return to administrator session - darkMode: night mode plannedDeletion: The deletion of the user {name} and all its data is planned on the {plannedDeletion}. cancelDeletion: Cancel the deletion of the user diff --git a/packages/vuetify/theme-switcher.vue b/packages/vuetify/theme-switcher.vue index 16f18cd..b57abb3 100644 --- a/packages/vuetify/theme-switcher.vue +++ b/packages/vuetify/theme-switcher.vue @@ -22,8 +22,9 @@ color="primary" hide-details :label="t('themeSwitch')" - @update:modelValue="value => session.switchTheme(value as 'default' | 'dark' | 'hc' | 'hc-dark')" + @update:modelValue="value => session.switchTheme(value as 'default' | 'dark' | 'hc' | 'hc-dark' | 'system')" > + @@ -39,17 +40,19 @@ fr: themeSwitch: Changer de thème theme: - default: par défaut - dark: sombre - hc: contraste élevé - hcDark: sombre et contraste élevé + system: Système + default: Par défaut + dark: Sombre + hc: Contraste élevé + hcDark: Sombre et contraste élevé en: themeSwitch: Change theme theme: - default: default - dark: dark - hc: high contrast - hcDark: dark and high contrast + system: System + default: Default + dark: Dark + hc: High contrast + hcDark: Dark and high contrast