From b401012d85e6724c561feac3aed7c21041dc4110 Mon Sep 17 00:00:00 2001 From: EYHN Date: Mon, 17 Mar 2025 09:28:25 +0000 Subject: [PATCH] feat(core): user service loading state (#10922) --- .../src/services/user-service/user-service.ts | 2 + .../extensions/editor-config/toolbar/index.ts | 9 +++- .../core/src/blocksuite/extensions/user.ts | 15 ++++++ .../src/modules/cloud/services/public-user.ts | 52 ++++++++++++++++--- 4 files changed, 70 insertions(+), 8 deletions(-) diff --git a/blocksuite/affine/shared/src/services/user-service/user-service.ts b/blocksuite/affine/shared/src/services/user-service/user-service.ts index 5095f5a4f2..7e06ef3130 100644 --- a/blocksuite/affine/shared/src/services/user-service/user-service.ts +++ b/blocksuite/affine/shared/src/services/user-service/user-service.ts @@ -6,6 +6,8 @@ import type { AffineUserInfo } from './types'; export interface UserService { userInfo$(id: string): Signal; + isLoading$(id: string): Signal; + error$(id: string): Signal; // user friendly error string revalidateUserInfo(id: string): void; } diff --git a/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts b/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts index cec14cba20..dd884604e9 100644 --- a/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts +++ b/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts @@ -346,9 +346,16 @@ function createToolbarMoreMenuConfigV2(baseUrl?: string) { const userProvider = cx.std.get(UserProvider); userProvider.revalidateUserInfo(createdByUserId); const userSignal = userProvider.userInfo$(createdByUserId); + const isLoadingSignal = userProvider.isLoading$(createdByUserId); const name = computed(() => { const value = userSignal.value; - if (!value) return I18n['Unknown User'](); + if (!value) { + if (isLoadingSignal.value) { + // if user info is loading + return ''; + } + return I18n['Unknown User'](); + } const removed = isRemovedUserInfo(value); if (removed) { return I18n['Deleted User'](); diff --git a/packages/frontend/core/src/blocksuite/extensions/user.ts b/packages/frontend/core/src/blocksuite/extensions/user.ts index e8870f769f..5fc45883f8 100644 --- a/packages/frontend/core/src/blocksuite/extensions/user.ts +++ b/packages/frontend/core/src/blocksuite/extensions/user.ts @@ -1,4 +1,5 @@ import type { PublicUserService } from '@affine/core/modules/cloud'; +import { UserFriendlyError } from '@affine/error'; import { UserServiceExtension } from '@blocksuite/affine/shared/services'; export function patchUserExtensions(publicUserService: PublicUserService) { @@ -7,6 +8,20 @@ export function patchUserExtensions(publicUserService: PublicUserService) { userInfo$(id) { return publicUserService.publicUser$(id).signal; }, + // eslint-disable-next-line rxjs/finnish + isLoading$(id) { + return publicUserService.isLoading$(id).signal; + }, + // eslint-disable-next-line rxjs/finnish + error$(id) { + return publicUserService.error$(id).selector(error => { + if (error) { + return UserFriendlyError.fromAny(error).name; + } else { + return null; + } + }).signal; + }, revalidateUserInfo(id) { publicUserService.revalidate(id); }, diff --git a/packages/frontend/core/src/modules/cloud/services/public-user.ts b/packages/frontend/core/src/modules/cloud/services/public-user.ts index b97a1163ba..3060e6f4de 100644 --- a/packages/frontend/core/src/modules/cloud/services/public-user.ts +++ b/packages/frontend/core/src/modules/cloud/services/public-user.ts @@ -2,6 +2,8 @@ import { effect, fromPromise, LiveData, + onComplete, + onStart, Service, smartRetry, } from '@toeverything/infra'; @@ -28,13 +30,48 @@ export class PublicUserService extends Service { super(); } - publicUsers$ = new LiveData>(new Map()); + private readonly publicUsers$ = new LiveData< + Map + >(new Map()); + private readonly isLoadings$ = new LiveData>( + new Map() + ); + private readonly errors$ = new LiveData>(new Map()); publicUser$(id: string) { return this.publicUsers$.selector(map => map.get(id) ?? null); } - error$ = new LiveData(null); + isLoading$(id: string) { + return this.isLoadings$.selector(map => map.get(id) ?? false); + } + + error$(id: string) { + return this.errors$.selector(map => map.get(id)); + } + + private setPublicUser(id: string, userInfo: PublicUserInfo) { + // Reusing the existing publicUsers Map instance instead of creating a new one. + // While this doesn't follow immutability best practices, it reduces memory overhead + // by avoiding the creation of new Map objects for each update. + const publicUsers = this.publicUsers$.value; + publicUsers.set(id, userInfo); + this.publicUsers$.next(publicUsers); + } + + private setLoading(id: string, loading: boolean) { + // Similar to setPublicUser, reusing the existing Map instance to reduce memory overhead + const loadings = this.isLoadings$.value; + loadings.set(id, loading); + this.isLoadings$.next(loadings); + } + + private setError(id: string, error: any | null) { + // Reusing the existing Map instance instead of creating a new one + const errors = this.errors$.value; + errors.set(id, error); + this.errors$.next(errors); + } revalidate = effect( groupBy((id: string) => id), @@ -58,15 +95,16 @@ export class PublicUserService extends Service { smartRetry(), catchError(error => { console.error(error); - this.error$.next(error); + this.setError(id, error); return EMPTY; }), mergeMap(user => { - const publicUsers = new Map(this.publicUsers$.value); - publicUsers.set(user.id, user); - this.publicUsers$.next(publicUsers); + this.setPublicUser(id, user); + this.setError(id, null); // clear error return EMPTY; - }) + }), + onStart(() => this.setLoading(id, true)), + onComplete(() => this.setLoading(id, false)) ) ) )