feat(core): user service loading state (#10922)
This commit is contained in:
@@ -6,6 +6,8 @@ import type { AffineUserInfo } from './types';
|
|||||||
|
|
||||||
export interface UserService {
|
export interface UserService {
|
||||||
userInfo$(id: string): Signal<AffineUserInfo | null>;
|
userInfo$(id: string): Signal<AffineUserInfo | null>;
|
||||||
|
isLoading$(id: string): Signal<boolean>;
|
||||||
|
error$(id: string): Signal<string | null>; // user friendly error string
|
||||||
revalidateUserInfo(id: string): void;
|
revalidateUserInfo(id: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -346,9 +346,16 @@ function createToolbarMoreMenuConfigV2(baseUrl?: string) {
|
|||||||
const userProvider = cx.std.get(UserProvider);
|
const userProvider = cx.std.get(UserProvider);
|
||||||
userProvider.revalidateUserInfo(createdByUserId);
|
userProvider.revalidateUserInfo(createdByUserId);
|
||||||
const userSignal = userProvider.userInfo$(createdByUserId);
|
const userSignal = userProvider.userInfo$(createdByUserId);
|
||||||
|
const isLoadingSignal = userProvider.isLoading$(createdByUserId);
|
||||||
const name = computed(() => {
|
const name = computed(() => {
|
||||||
const value = userSignal.value;
|
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);
|
const removed = isRemovedUserInfo(value);
|
||||||
if (removed) {
|
if (removed) {
|
||||||
return I18n['Deleted User']();
|
return I18n['Deleted User']();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { PublicUserService } from '@affine/core/modules/cloud';
|
import type { PublicUserService } from '@affine/core/modules/cloud';
|
||||||
|
import { UserFriendlyError } from '@affine/error';
|
||||||
import { UserServiceExtension } from '@blocksuite/affine/shared/services';
|
import { UserServiceExtension } from '@blocksuite/affine/shared/services';
|
||||||
|
|
||||||
export function patchUserExtensions(publicUserService: PublicUserService) {
|
export function patchUserExtensions(publicUserService: PublicUserService) {
|
||||||
@@ -7,6 +8,20 @@ export function patchUserExtensions(publicUserService: PublicUserService) {
|
|||||||
userInfo$(id) {
|
userInfo$(id) {
|
||||||
return publicUserService.publicUser$(id).signal;
|
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) {
|
revalidateUserInfo(id) {
|
||||||
publicUserService.revalidate(id);
|
publicUserService.revalidate(id);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import {
|
|||||||
effect,
|
effect,
|
||||||
fromPromise,
|
fromPromise,
|
||||||
LiveData,
|
LiveData,
|
||||||
|
onComplete,
|
||||||
|
onStart,
|
||||||
Service,
|
Service,
|
||||||
smartRetry,
|
smartRetry,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
@@ -28,13 +30,48 @@ export class PublicUserService extends Service {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
publicUsers$ = new LiveData<Map<string, PublicUserInfo | null>>(new Map());
|
private readonly publicUsers$ = new LiveData<
|
||||||
|
Map<string, PublicUserInfo | null>
|
||||||
|
>(new Map());
|
||||||
|
private readonly isLoadings$ = new LiveData<Map<string, boolean | null>>(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
|
private readonly errors$ = new LiveData<Map<string, any | null>>(new Map());
|
||||||
|
|
||||||
publicUser$(id: string) {
|
publicUser$(id: string) {
|
||||||
return this.publicUsers$.selector(map => map.get(id) ?? null);
|
return this.publicUsers$.selector(map => map.get(id) ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
error$ = new LiveData<any | null>(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(
|
revalidate = effect(
|
||||||
groupBy((id: string) => id),
|
groupBy((id: string) => id),
|
||||||
@@ -58,15 +95,16 @@ export class PublicUserService extends Service {
|
|||||||
smartRetry(),
|
smartRetry(),
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
this.error$.next(error);
|
this.setError(id, error);
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}),
|
}),
|
||||||
mergeMap(user => {
|
mergeMap(user => {
|
||||||
const publicUsers = new Map(this.publicUsers$.value);
|
this.setPublicUser(id, user);
|
||||||
publicUsers.set(user.id, user);
|
this.setError(id, null); // clear error
|
||||||
this.publicUsers$.next(publicUsers);
|
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
})
|
}),
|
||||||
|
onStart(() => this.setLoading(id, true)),
|
||||||
|
onComplete(() => this.setLoading(id, false))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user