feat(web): avatar thumbnails with original preview, registration avatar, pure-JS crypto fallback for http [CI]
This commit is contained in:
1 parent
283196938e
commit
d66c93fe98
36 files changed
+735
-60
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { create } from 'zustand';
|
||||
import { accountApi } from '@/core-bridge/account';
|
||||
import type { AccountStatus } from '@/core-bridge/account';
|
||||
import type { AvatarChange } from '@/core-bridge/protocol/account';
|
||||
import { useChannels } from './channels';
|
||||
import { useShares } from './shares';
|
||||
import { useWorkspace } from './workspace';
|
||||
@@ -12,7 +13,7 @@ import { loginWithPasskey, passkeyApi } from '@/core-bridge/passkeys';
|
||||
interface AccountState extends AccountStatus {
|
||||
busy: boolean;
|
||||
hydrated: boolean;
|
||||
login: (username: string, password: string, create: boolean, remember?: boolean, displayName?: string) => Promise<void>;
|
||||
login: (username: string, password: string, create: boolean, remember?: boolean, displayName?: string, avatar?: { avatar: string; thumb: string }) => Promise<void>;
|
||||
passkeyLogin: (username: string, remember: boolean) => Promise<void>;
|
||||
unlockPasskey: (password: string, remember: boolean) => Promise<void>;
|
||||
cancelPasskey: () => Promise<void>;
|
||||
@@ -20,7 +21,7 @@ interface AccountState extends AccountStatus {
|
||||
logout: () => Promise<void>;
|
||||
clearSavedLogin: () => Promise<void>;
|
||||
sync: () => Promise<void>;
|
||||
updatePersonal: (displayName: string, avatar?: string) => Promise<void>;
|
||||
updatePersonal: (displayName: string, avatar?: AvatarChange) => Promise<void>;
|
||||
}
|
||||
|
||||
let lastResourceRefresh = 0;
|
||||
@@ -46,12 +47,12 @@ export const useAccount = create<AccountState>()((set, get) => ({
|
||||
catch (error) { set({ busy: false, unlockUsername: undefined, error: error instanceof Error ? error.message : '账号解锁失败' }); throw error; }
|
||||
},
|
||||
cancelPasskey: async () => { await passkeyApi.cancel(); set({ unlockUsername: undefined, error: undefined }); },
|
||||
login: async (username, password, create, remember = true, displayName = '') => {
|
||||
login: async (username, password, create, remember = true, displayName = '', avatar) => {
|
||||
set({ busy: true, error: undefined });
|
||||
try {
|
||||
let status = create ? await accountApi.register(username, password, remember) : await accountApi.login(username, password, remember);
|
||||
// 注册时填的个人名称:账号已经建好,这一步失败不算注册失败,之后可在个人资料里补
|
||||
if (create && displayName.trim()) status = await accountApi.updatePersonal(displayName.trim()).catch(() => status);
|
||||
// 注册时填的个人名称和头像:账号已经建好,这一步失败不算注册失败,之后可在个人资料里补
|
||||
if (create && (displayName.trim() || avatar)) status = await accountApi.updatePersonal(displayName.trim() || username, avatar).catch(() => status);
|
||||
clearViews(); await refreshResources();
|
||||
set({ ...status, busy: false, hydrated: true });
|
||||
} catch (error) {
|
||||
@@ -88,3 +89,20 @@ export const useAccount = create<AccountState>()((set, get) => ({
|
||||
catch (error) { set({ error: error instanceof Error ? error.message : '资料保存失败', busy: false }); throw error; }
|
||||
},
|
||||
}));
|
||||
|
||||
// 头像不跟着账号状态一起传(动图有几 MB,状态每隔几秒就刷新一次):状态里只有 hasAvatar,头像在这里另取,
|
||||
// 并且状态刷新后(personal 被换成没有头像的)按修改时间沿用已经取到的那张
|
||||
let loadingAvatar = '';
|
||||
useAccount.subscribe((state, previous) => {
|
||||
const personal = state.personal;
|
||||
if (!personal?.hasAvatar || personal.avatar || !state.username) return;
|
||||
const known = previous.personal;
|
||||
if (known?.avatar && known.modifiedAt === personal.modifiedAt) { useAccount.setState({ personal: { ...personal, avatar: known.avatar } }); return; }
|
||||
const key = `${state.username}:${personal.modifiedAt}`;
|
||||
if (loadingAvatar === key) return;
|
||||
loadingAvatar = key;
|
||||
void accountApi.avatar(state.username, personal.modifiedAt).then((avatar) => {
|
||||
const now = useAccount.getState().personal;
|
||||
if (avatar && now && now.modifiedAt === personal.modifiedAt && !now.avatar) useAccount.setState({ personal: { ...now, avatar } });
|
||||
}).catch(() => undefined).finally(() => { if (loadingAvatar === key) loadingAvatar = ''; });
|
||||
});
|
||||
@@ -13,7 +13,7 @@ describe('reduce', () => {
|
||||
groups: [{ id: 'group-a', name: '项目', createdAt: 1 }],
|
||||
folders: [{ id: 'folder-a', name: '目录', threadId: 'channel-a' }],
|
||||
locations: [{ itemId: 'a', threadId: 'channel-a', folderId: 'folder-a', authorAccount: 'alice' }] },
|
||||
people: [{ username: 'alice', displayName: '服务端名称', modifiedAt: 1 }] };
|
||||
people: [{ username: 'alice', displayName: '服务端名称', hasAvatar: false, modifiedAt: 1 }] };
|
||||
let state = reduce(initialData, stateEvent);
|
||||
state = reduce(state, { t: 'item', item: { ...text('a', 1), threadId: 'old', author: { username: 'mallory', displayName: '伪造名称' } } });
|
||||
expect(state.byId.a?.threadId).toBe('channel-a'); expect(state.byId.a?.folderId).toBe('folder-a');
|
||||
|
||||
Reference in new issue
Block a user