Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/types-builder/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = {...}`.
Expand Down
35 changes: 27 additions & 8 deletions packages/vue/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -120,16 +121,21 @@ export type SessionAuthenticated = Omit<Session, 'state' | 'user' | 'account' |
const debug = Debug('session')
debug.log = console.log.bind(console)

function getDefaultTheme (site: FullSiteInfo): Theme {
function getDefaultTheme (site: FullSiteInfo): AppliedTheme {
// see https://www.scottohara.me/blog/2021/10/01/detect-high-contrast-and-dark-modes.html
const preferDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const preferHC = window.matchMedia && window.matchMedia('(forced-colors: active)').matches
const preferDark = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const preferHC = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(forced-colors: active)').matches
if (site.theme.hcDark && preferDark && preferHC) return 'hc-dark'
if (site.theme.hc && preferHC) return 'hc'
if (site.theme.dark && preferDark) return 'dark'
return 'default'
}

function resolveTheme (userTheme: Theme | null, site: FullSiteInfo): AppliedTheme {
if (userTheme && userTheme !== 'system') return userTheme
return getDefaultTheme(site)
}

function jwtDecodeAlive (jwt: string | null): User | undefined {
if (!jwt) return
const decoded = jwtDecode(jwt) as any
Expand Down Expand Up @@ -403,6 +409,9 @@ export async function getSession (initOptions: Partial<SessionOptions>): 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,
Expand All @@ -412,13 +421,13 @@ export async function getSession (initOptions: Partial<SessionOptions>): 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
}
Expand All @@ -433,6 +442,16 @@ export async function getSession (initOptions: Partial<SessionOptions>): 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
Expand Down
24 changes: 20 additions & 4 deletions packages/vuetify/layout-fetch-error.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 = () => {
Expand Down
6 changes: 2 additions & 4 deletions packages/vuetify/personal-menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
</i18n>
Expand Down
21 changes: 12 additions & 9 deletions packages/vuetify/theme-switcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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')"
>
<v-radio :label="t('theme.system')" value="system"></v-radio>
<v-radio :label="t('theme.default')" value="default"></v-radio>
<v-radio v-if="session.fullSite.value?.theme.dark" :label="t('theme.dark')" value="dark"></v-radio>
<v-radio v-if="session.fullSite.value?.theme.hc":label="t('theme.hc')" value="hc"></v-radio>
Expand All @@ -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
</i18n>

<script setup lang="ts">
Expand Down