97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
import { reactive } from 'vue';
|
|
import type { OrgUserDto } from '@mento/shared';
|
|
import { api } from '@/lib/api';
|
|
import { useAuthStore } from '@/stores/auth';
|
|
|
|
export const DEFAULT_USER_AVATAR = '/brand/user-avatar.png';
|
|
|
|
/** userId → آیا عکس دارد (از فهرست کاربران سازمان) */
|
|
const hasAvatarById = reactive<Record<string, boolean>>({});
|
|
/** userId → object URL عکس */
|
|
const urlById = reactive<Record<string, string>>({});
|
|
const pending = new Map<string, Promise<void>>();
|
|
let usersLoad: Promise<void> | null = null;
|
|
let usersScope: string | null = null;
|
|
|
|
function resetCache() {
|
|
for (const url of Object.values(urlById)) URL.revokeObjectURL(url);
|
|
for (const key of Object.keys(urlById)) delete urlById[key];
|
|
for (const key of Object.keys(hasAvatarById)) delete hasAvatarById[key];
|
|
pending.clear();
|
|
usersLoad = null;
|
|
}
|
|
|
|
function ensureScope() {
|
|
const auth = useAuthStore();
|
|
const scope = auth.user ? `${auth.user.id}:${auth.user.organizationId}` : null;
|
|
if (scope !== usersScope) {
|
|
resetCache();
|
|
usersScope = scope;
|
|
}
|
|
return auth;
|
|
}
|
|
|
|
function loadUserFlags(): Promise<void> {
|
|
if (usersLoad) return usersLoad;
|
|
usersLoad = api
|
|
.get<OrgUserDto[]>('/users')
|
|
.then(({ data }) => {
|
|
for (const u of data ?? []) hasAvatarById[u.id] = Boolean(u.hasAvatar);
|
|
})
|
|
.catch(() => undefined);
|
|
return usersLoad;
|
|
}
|
|
|
|
function fetchAvatar(userId: string): Promise<void> {
|
|
const existing = pending.get(userId);
|
|
if (existing) return existing;
|
|
const job = api
|
|
.get<Blob>(`/users/${encodeURIComponent(userId)}/avatar`, { responseType: 'blob' })
|
|
.then(({ data }) => {
|
|
if (!data || data.size === 0 || data.type.includes('json')) return;
|
|
urlById[userId] = URL.createObjectURL(data);
|
|
})
|
|
.catch(() => {
|
|
hasAvatarById[userId] = false;
|
|
});
|
|
pending.set(userId, job);
|
|
return job;
|
|
}
|
|
|
|
/** اطلاع از وضعیت عکس کاربرانی که API برگردانده (مثلاً اعضای گفتگو). */
|
|
export function primeUserAvatar(userId: string, hasAvatar: boolean | undefined) {
|
|
ensureScope();
|
|
if (hasAvatar === undefined) return;
|
|
hasAvatarById[userId] = hasAvatar;
|
|
}
|
|
|
|
/** آدرس عکس پروفایل کاربر؛ تا بارگذاری نشده عکس پیشفرض برمیگردد. */
|
|
export function userAvatarSrc(userId: string | null | undefined): string {
|
|
if (!userId) return DEFAULT_USER_AVATAR;
|
|
const auth = ensureScope();
|
|
if (auth.user?.id === userId) return auth.avatarSrc;
|
|
|
|
const url = urlById[userId];
|
|
if (url) return url;
|
|
|
|
const flag = hasAvatarById[userId];
|
|
if (flag === undefined) {
|
|
void loadUserFlags().then(() => {
|
|
if (hasAvatarById[userId]) void fetchAvatar(userId);
|
|
});
|
|
} else if (flag) {
|
|
void fetchAvatar(userId);
|
|
}
|
|
return DEFAULT_USER_AVATAR;
|
|
}
|
|
|
|
/** بعد از تغییر عکس یک کاربر (مثلاً در تنظیمات تیم) کش را تازه کن. */
|
|
export function invalidateUserAvatar(userId: string) {
|
|
const url = urlById[userId];
|
|
if (url) URL.revokeObjectURL(url);
|
|
delete urlById[userId];
|
|
delete hasAvatarById[userId];
|
|
pending.delete(userId);
|
|
usersLoad = null;
|
|
}
|