mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-19 19:16:29 +08:00
feat(core): add use guard hook (#11180)
Previously, we used `useLiveData(guardService.can$())` to get the guard result, but `guardService.can$()` will request the server to revalidate the permission when calling it, will cause additional network requests when re-render. This pr make a new hook `useGuard` to fix this problem. And the side effect in `can$` is moved to `revalidateCan()` to make that the subscribe method is pure
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
toast,
|
||||
Tooltip,
|
||||
} from '@affine/component';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
@@ -274,7 +274,7 @@ export const ExplorerDocNode = ({
|
||||
dropEffect={handleDropEffectOnDoc}
|
||||
data-testid={`explorer-doc-${docId}`}
|
||||
>
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Read">
|
||||
<Guard docId={docId} permission="Doc_Read">
|
||||
{canRead =>
|
||||
canRead
|
||||
? children?.map((child, index) => (
|
||||
@@ -291,7 +291,7 @@ export const ExplorerDocNode = ({
|
||||
))
|
||||
: null
|
||||
}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
</ExplorerTreeNode>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useConfirmModal,
|
||||
} from '@affine/component';
|
||||
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
@@ -175,7 +175,7 @@ export const useExplorerDocNodeOperations = (
|
||||
{
|
||||
index: 99,
|
||||
view: (
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Update">
|
||||
<Guard docId={docId} permission="Doc_Update">
|
||||
{canEdit => (
|
||||
<MenuItem
|
||||
prefixIcon={<LinkedPageIcon />}
|
||||
@@ -185,7 +185,7 @@ export const useExplorerDocNodeOperations = (
|
||||
{t['com.affine.page-operation.add-linked-page']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -239,7 +239,7 @@ export const useExplorerDocNodeOperations = (
|
||||
{
|
||||
index: 10000,
|
||||
view: (
|
||||
<DocPermissionGuard docId={docId} permission="Doc_Trash">
|
||||
<Guard docId={docId} permission="Doc_Trash">
|
||||
{canMoveToTrash => (
|
||||
<MenuItem
|
||||
type={'danger'}
|
||||
@@ -250,7 +250,7 @@ export const useExplorerDocNodeOperations = (
|
||||
{t['com.affine.moveToTrash.title']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
useDropTarget,
|
||||
} from '@affine/component';
|
||||
import { RenameModal } from '@affine/component/rename-modal';
|
||||
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard';
|
||||
import { Guard } from '@affine/core/components/guard';
|
||||
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
||||
import type { DocPermissionActions } from '@affine/core/modules/permissions';
|
||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||
@@ -285,7 +285,7 @@ export const ExplorerTreeNode = ({
|
||||
? {
|
||||
index: 0,
|
||||
view: renameableGuard ? (
|
||||
<DocPermissionGuard
|
||||
<Guard
|
||||
permission={renameableGuard.action}
|
||||
docId={renameableGuard.docId}
|
||||
>
|
||||
@@ -300,7 +300,7 @@ export const ExplorerTreeNode = ({
|
||||
{t['com.affine.menu.rename']()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DocPermissionGuard>
|
||||
</Guard>
|
||||
) : (
|
||||
<MenuItem
|
||||
key={'explorer-tree-rename'}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { AIProvider } from '@affine/core/blocksuite/ai';
|
||||
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
|
||||
import { EditorOutlineViewer } from '@affine/core/blocksuite/outline-viewer';
|
||||
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { PageNotFound } from '@affine/core/desktop/pages/404';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx';
|
||||
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
|
||||
@@ -85,7 +85,6 @@ function DocPeekPreviewEditor({
|
||||
const defaultOpenProperty = useLiveData(editor.defaultOpenProperty$);
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const peekView = useService(PeekViewService).peekView;
|
||||
const guardService = useService(GuardService);
|
||||
const editorElement = useLiveData(editor.editorContainer$);
|
||||
|
||||
const isInTrash = useLiveData(doc.record.trash$);
|
||||
@@ -151,7 +150,7 @@ function DocPeekPreviewEditor({
|
||||
peekView.close();
|
||||
}, [doc, peekView, workbench]);
|
||||
|
||||
const canEdit = useLiveData(guardService.can$('Doc_Update', doc.id));
|
||||
const canEdit = useGuard('Doc_Update', doc.id);
|
||||
|
||||
const readonly = !canEdit || isInTrash;
|
||||
|
||||
@@ -220,8 +219,7 @@ export function DocPeekPreview({
|
||||
!animating
|
||||
);
|
||||
|
||||
const guardService = useService(GuardService);
|
||||
const canAccess = useLiveData(guardService.can$('Doc_Read', docId));
|
||||
const canAccess = useGuard('Doc_Read', docId);
|
||||
|
||||
// if sync engine has been synced and the page is null, show 404 page.
|
||||
if (!doc || !editor || !canAccess) {
|
||||
|
||||
@@ -66,17 +66,6 @@ export class GuardService extends Service {
|
||||
const docId = args[0];
|
||||
return LiveData.from(
|
||||
new Observable(subscriber => {
|
||||
// revalidate permission
|
||||
if (docId) {
|
||||
this.revalidateDocPermission(docId);
|
||||
} else {
|
||||
this.revalidateWorkspacePermission();
|
||||
}
|
||||
// revalidate workspace permission if it's not initialized
|
||||
if (this.isAdmin$.value === null) {
|
||||
this.workspacePermissionService.permission.revalidate();
|
||||
}
|
||||
|
||||
let prev: boolean | undefined = undefined;
|
||||
|
||||
const subscription = combineLatest([
|
||||
@@ -128,6 +117,29 @@ export class GuardService extends Service {
|
||||
return permissions[action as keyof typeof permissions] ?? false;
|
||||
}
|
||||
|
||||
revalidateCan<T extends WorkspacePermissionActions | DocPermissionActions>(
|
||||
_action: T,
|
||||
...args: T extends DocPermissionActions ? [string] : []
|
||||
) {
|
||||
// revalidate workspace permission if it's not initialized
|
||||
if (this.isAdmin$.value === null) {
|
||||
this.workspacePermissionService.permission.revalidate();
|
||||
}
|
||||
|
||||
if (this.isAdmin$.value === true) {
|
||||
// if the user is admin, the permission is always true
|
||||
return;
|
||||
}
|
||||
|
||||
const docId = args[0];
|
||||
// revalidate permission
|
||||
if (docId) {
|
||||
this.revalidateDocPermission(docId);
|
||||
} else {
|
||||
this.revalidateWorkspacePermission();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly revalidateWorkspacePermission = effect(
|
||||
exhaustMapWithTrailing(() =>
|
||||
fromPromise(() => this.guardStore.getWorkspacePermissions()).pipe(
|
||||
|
||||
+3
-7
@@ -8,12 +8,12 @@ import {
|
||||
Tooltip,
|
||||
useConfirmModal,
|
||||
} from '@affine/component';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import {
|
||||
DocGrantedUsersService,
|
||||
type GrantedUser,
|
||||
GuardService,
|
||||
WorkspacePermissionService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { UserFriendlyError } from '@affine/error';
|
||||
@@ -131,7 +131,6 @@ const Options = ({
|
||||
const t = useI18n();
|
||||
const docGrantedUsersService = useService(DocGrantedUsersService);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
const workspacePermissionService = useService(WorkspacePermissionService);
|
||||
const isWorkspaceOwner = useLiveData(
|
||||
workspacePermissionService.permission.isOwner$
|
||||
@@ -140,11 +139,8 @@ const Options = ({
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
|
||||
const canTransferOwner =
|
||||
useLiveData(guardService.can$('Doc_TransferOwner', docService.doc.id)) &&
|
||||
!!isWorkspaceOwner;
|
||||
const canManageUsers = useLiveData(
|
||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||
);
|
||||
useGuard('Doc_TransferOwner', docService.doc.id) && !!isWorkspaceOwner;
|
||||
const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
|
||||
|
||||
const updateUserRole = useCallback(
|
||||
async (userId: string, role: DocRole) => {
|
||||
|
||||
+2
-5
@@ -1,9 +1,9 @@
|
||||
import { Skeleton } from '@affine/component';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import {
|
||||
DocGrantedUsersService,
|
||||
type GrantedUser,
|
||||
GuardService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { ArrowLeftBigIcon } from '@blocksuite/icons/rc';
|
||||
@@ -33,11 +33,8 @@ export const MemberManagement = ({
|
||||
docGrantedUsersService.grantedUserCount$
|
||||
);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
const canManageUsers = useLiveData(
|
||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||
);
|
||||
const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Divider, Skeleton } from '@affine/component';
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { ServerService } from '@affine/core/modules/cloud';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
@@ -64,15 +64,10 @@ export const AFFiNESharePage = (
|
||||
const shareInfoService = useService(ShareInfoService);
|
||||
const serverService = useService(ServerService);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
const canManageUsers = useLiveData(
|
||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||
);
|
||||
const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
|
||||
|
||||
const canPublish = useLiveData(
|
||||
guardService.can$('Doc_Publish', docService.doc.id)
|
||||
);
|
||||
const canPublish = useGuard('Doc_Publish', docService.doc.id);
|
||||
|
||||
useEffect(() => {
|
||||
shareInfoService.shareInfo.revalidate();
|
||||
|
||||
Reference in New Issue
Block a user