feat(user-avatar): users can now edit there image (image is taken from OIDC picture)
This commit is contained in:
@@ -4,9 +4,11 @@
|
||||
* System-users convenience layer.
|
||||
*
|
||||
* Thin wrapper over `addressBook.listContacts(SYSTEM_BOOK_ID)` that
|
||||
* provides a userId → display-name index. Used wherever a grant's
|
||||
* `granted_by` UUID needs to be shown as a human-readable name
|
||||
* (owner tooltips, share dialogs, etc.).
|
||||
* provides a userId → display-name index, a userId → photo-url index,
|
||||
* and a userId → primary-email index.
|
||||
* Used wherever a grant's `granted_by` UUID needs to be shown as a
|
||||
* human-readable name (owner tooltips, share dialogs, etc.), avatar
|
||||
* image (userVignette, user menu), or email (suggestion dropdowns).
|
||||
*
|
||||
* Falls back gracefully when the system address book is disabled
|
||||
* server-side (`OXICLOUD_EXPOSE_SYSTEM_USERS` not set): `isAvailable()`
|
||||
@@ -20,6 +22,12 @@ import { addressBook, SYSTEM_BOOK_ID } from './addressBook.js';
|
||||
/** @type {Map<string, string> | null} userId → display name, built lazily */
|
||||
let _index = null;
|
||||
|
||||
/** @type {Map<string, string | null> | null} userId → photo URL (or null), built lazily */
|
||||
let _photoIndex = null;
|
||||
|
||||
/** @type {Map<string, string | null> | null} userId → primary email (or null), built lazily */
|
||||
let _emailIndex = null;
|
||||
|
||||
/**
|
||||
* Derive the best human-readable name from a contact.
|
||||
* Priority: "First Last" → full_name → primary email → shortened id.
|
||||
@@ -36,7 +44,7 @@ function _nameFor(c) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the index is built (idempotent).
|
||||
* Ensure both indexes are built (idempotent).
|
||||
* After loading contacts from the system address book, the current user
|
||||
* (from localStorage) is injected so owner cells resolve correctly even
|
||||
* when the server-side address book does not include the logged-in user.
|
||||
@@ -46,15 +54,30 @@ async function _ensureIndex() {
|
||||
if (_index !== null) return;
|
||||
const contacts = await addressBook.listContacts(SYSTEM_BOOK_ID);
|
||||
_index = new Map(contacts.map((c) => [c.id, _nameFor(c)]));
|
||||
_photoIndex = new Map(contacts.map((c) => [c.id, c.photo_url ?? null]));
|
||||
_emailIndex = new Map(
|
||||
contacts.map((c) => {
|
||||
const primary = c.email?.find((e) => e.is_primary)?.email ?? c.email?.[0]?.email ?? null;
|
||||
return [c.id, primary];
|
||||
})
|
||||
);
|
||||
|
||||
// Inject the current user if they are not already in the index
|
||||
try {
|
||||
const raw = localStorage.getItem('oxicloud_user');
|
||||
if (raw) {
|
||||
const u = /** @type {{id?:string, display_name?:string, username?:string, email?:string}} */ (JSON.parse(raw));
|
||||
if (u?.id && !_index.has(u.id)) {
|
||||
const name = u.display_name || u.username || u.email || `${u.id.slice(0, 8)}…`;
|
||||
_index.set(u.id, name);
|
||||
const u = /** @type {{id?:string, display_name?:string, username?:string, email?:string, image?:string|null}} */ (JSON.parse(raw));
|
||||
if (u?.id) {
|
||||
if (!_index.has(u.id)) {
|
||||
const name = u.display_name || u.username || u.email || `${u.id.slice(0, 8)}…`;
|
||||
_index.set(u.id, name);
|
||||
}
|
||||
if (!_photoIndex.has(u.id)) {
|
||||
_photoIndex.set(u.id, u.image ?? null);
|
||||
}
|
||||
if (!_emailIndex.has(u.id)) {
|
||||
_emailIndex.set(u.id, u.email ?? null);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -85,6 +108,48 @@ async function getDisplayName(userId) {
|
||||
return _index?.get(userId) ?? `${userId.slice(0, 8)}…`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a user UUID to a photo URL (or null if none set).
|
||||
* Awaits the first load if not yet cached; subsequent calls resolve instantly.
|
||||
*
|
||||
* @param {string} userId
|
||||
* @returns {Promise<string | null>}
|
||||
*/
|
||||
async function getPhoto(userId) {
|
||||
await _ensureIndex();
|
||||
return _photoIndex?.get(userId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a user UUID to their primary email address (or null if unknown).
|
||||
* Awaits the first load if not yet cached; subsequent calls resolve instantly.
|
||||
*
|
||||
* @param {string} userId
|
||||
* @returns {Promise<string | null>}
|
||||
*/
|
||||
async function getEmail(userId) {
|
||||
await _ensureIndex();
|
||||
return _emailIndex?.get(userId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force-refresh the current user's photo entry in the index from localStorage.
|
||||
* Call this after saving a new avatar on the profile page so that existing
|
||||
* vignettes can re-render without a full page reload.
|
||||
*/
|
||||
function refreshCurrentUserPhoto() {
|
||||
try {
|
||||
const raw = localStorage.getItem('oxicloud_user');
|
||||
if (!raw || !_photoIndex) return;
|
||||
const u = /** @type {{id?:string, image?:string|null}} */ (JSON.parse(raw));
|
||||
if (u?.id) {
|
||||
_photoIndex.set(u.id, u.image ?? null);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `false` only after a confirmed 404 from the server (feature
|
||||
* disabled). Returns `true` when status is unknown or the book loaded OK.
|
||||
@@ -94,4 +159,4 @@ function isAvailable() {
|
||||
return addressBook.isSystemAvailable();
|
||||
}
|
||||
|
||||
export const systemUsers = { prefetch, getDisplayName, isAvailable };
|
||||
export const systemUsers = { prefetch, getDisplayName, getPhoto, getEmail, refreshCurrentUserPhoto, isAvailable };
|
||||
|
||||
Reference in New Issue
Block a user