Skip to main content

useUserPreferences

Reads and writes how a person likes the product, in Nextcloud's own per-user preferences.

Nine of this cluster's candidates are one preference each: a landing page, pinned navigation, a date display, the view each list reopens in, a row order. None of them belongs in a component store. A preference kept in a component store is gone on the next page, invisible on another device, and nowhere an administrator can look for it.

Usage​

import { useUserPreferences, USER_PREFERENCE_KEYS } from '@conduction/nextcloud-vue'

const prefs = useUserPreferences('dossiq', { personalisation: manifest.personalisation })

const landing = await prefs.read(USER_PREFERENCE_KEYS.landingPage, 'Cases')
await prefs.write(USER_PREFERENCE_KEYS.dateDisplay, 'relative')

It addresses the app's own preferences endpoint, the same one useWalkthrough and useSupportDialog use:

GET  /apps/{appId}/api/preferences/{key}   ->  { value }
PUT /apps/{appId}/api/preferences/{key} { value }

Values are stored as JSON, so a list or a map survives the round trip. A per-browser mirror in localStorage answers before the GET does and carries the preference through a missing endpoint, an unauthenticated session or an offline browser.

Options​

OptionTypeDescription
personalisationObjectThe manifest's personalisation block: the app defaults and the instance switch.
administeredObjectAdministered values, by key.
httpObjectAn axios-shaped client. For tests.
storageObjectA localStorage-shaped store. For tests.

What it returns​

KeyDescription
read(key, fallback?)The value, resolved across all three layers.
write(key, value)Stores it. Returns false when the instance switched personal customisation off.
resolve(key, personal?)Resolves without reading the server.
valuesRef of everything read so far, by key.
loadingRef<boolean>.
personalAppliesComputedRef<boolean>. false when an administrator switched the personal layer off.
disabledReasonComputedRef<string>. What to tell the person when it is off.

Three layers, one order​

App default, administered value, personal value. The person wins, then the administrator, then what the product ships.

false and 0 are choices, not absences. Only undefined, null and '' fall through to the layer below. Reading false as unset is how a person who switched something off gets it switched back on.

When customisation is switched off​

An instance can turn the personal layer off. Then personalApplies is false, write refuses, and read does not even fetch the personal value, so a stale one cannot leak through.

Render the options as unavailable, with disabledReason beside them. Accepting a setting and then not applying it is the behaviour the switch exists to replace.

The keys​

ConstantKeyHolds
USER_PREFERENCE_KEYS.landingPagecn_landing_pagePage id the app opens on.
USER_PREFERENCE_KEYS.pinnedMenucn_pinned_menuMenu ids this person pinned.
USER_PREFERENCE_KEYS.dateDisplaycn_date_displayabsolute or relative.
USER_PREFERENCE_KEYS.lastUsedViewcn_last_used_viewRecord type to the view mode last used on it.
USER_PREFERENCE_KEYS.menuOrdercn_menu_orderMenu ids in this person's own order.

Declaring the defaults​

{
"personalisation": {
"enabled": true,
"landingPage": "Queue",
"dateDisplay": "absolute",
"rememberLastView": true,
"pinnedMenu": ["Cases", "Queue"]
}
}

Next​

Render a date the way this person reads dates with formatDateForDisplay, which never lets a relative date hide the date.