mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-22 12:36:24 +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,10 +5,7 @@ import { Modal, useConfirmModal } from '@affine/component/ui/modal';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import {
|
||||
GuardService,
|
||||
WorkspacePermissionService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { i18nTime, Trans, useI18n } from '@affine/i18n';
|
||||
@@ -35,6 +32,7 @@ import { encodeStateAsUpdate } from 'yjs';
|
||||
import { BlockSuiteEditor } from '../../../blocksuite/block-suite-editor';
|
||||
import { PureEditorModeSwitch } from '../../../blocksuite/block-suite-mode-switch';
|
||||
import { pageHistoryModalAtom } from '../../atoms/page-history';
|
||||
import { useGuard } from '../../guard';
|
||||
import { AffineErrorBoundary } from '../affine-error-boundary';
|
||||
import {
|
||||
historyListGroupByDay,
|
||||
@@ -408,8 +406,6 @@ const PageHistoryManager = ({
|
||||
const workspaceId = docCollection.id;
|
||||
const [activeVersion, setActiveVersion] = useState<string>();
|
||||
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
const pageDocId = useMemo(() => {
|
||||
return docCollection.getDoc(pageId)?.spaceDoc.guid ?? pageId;
|
||||
}, [pageId, docCollection]);
|
||||
@@ -441,7 +437,7 @@ const PageHistoryManager = ({
|
||||
const i18n = useI18n();
|
||||
|
||||
const title = useLiveData(docDisplayMetaService.title$(pageId));
|
||||
const canEdit = useLiveData(guardService.can$('Doc_Update', pageDocId));
|
||||
const canEdit = useGuard('Doc_Update', pageDocId);
|
||||
|
||||
const onConfirmRestore = useCallback(() => {
|
||||
openConfirmModal({
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from '@affine/component';
|
||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@@ -17,6 +16,7 @@ import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { type HTMLProps, useCallback, useState } from 'react';
|
||||
|
||||
import { useGuard } from '../../guard';
|
||||
import { DocPropertyIcon } from '../icons/doc-property-icon';
|
||||
import { EditDocPropertyMenuItems } from '../menu/edit-doc-property';
|
||||
import {
|
||||
@@ -38,13 +38,10 @@ const PropertyItem = ({
|
||||
) => void;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const guardService = useService(GuardService);
|
||||
const workspaceService = useService(WorkspaceService);
|
||||
const docsService = useService(DocsService);
|
||||
const [moreMenuOpen, setMoreMenuOpen] = useState(defaultOpenEditMenu);
|
||||
const canEditPropertyInfo = useLiveData(
|
||||
guardService.can$('Workspace_Properties_Update')
|
||||
);
|
||||
const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
|
||||
|
||||
const typeInfo = isSupportedDocPropertyType(propertyInfo.type)
|
||||
? DocPropertyTypes[propertyInfo.type]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Divider, IconButton, Tooltip } from '@affine/component';
|
||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import track from '@affine/track';
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useGuard } from '../../guard';
|
||||
import { DocPropertyManager } from '../manager';
|
||||
import {
|
||||
DocPropertyTypes,
|
||||
@@ -29,12 +29,9 @@ export const DocPropertySidebar = () => {
|
||||
const [newPropertyId, setNewPropertyId] = useState<string>();
|
||||
|
||||
const docsService = useService(DocsService);
|
||||
const guardService = useService(GuardService);
|
||||
const propertyList = docsService.propertyList;
|
||||
const properties = useLiveData(propertyList.properties$);
|
||||
const canEditPropertyInfo = useLiveData(
|
||||
guardService.can$('Workspace_Properties_Update')
|
||||
);
|
||||
const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
|
||||
const onAddProperty = useCallback(
|
||||
(option: { type: string; name: string }) => {
|
||||
if (!isSupportedDocPropertyType(option.type)) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import type {
|
||||
DatabaseValueCell,
|
||||
} from '@affine/core/modules/doc-info/types';
|
||||
import { DocIntegrationPropertiesTable } from '@affine/core/modules/integration';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { ViewService, WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@@ -32,6 +31,7 @@ import clsx from 'clsx';
|
||||
import type React from 'react';
|
||||
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useGuard } from '../guard';
|
||||
import { DocPropertyIcon } from './icons/doc-property-icon';
|
||||
import { CreatePropertyMenuItems } from './menu/create-doc-property';
|
||||
import { EditDocPropertyMenuItems } from './menu/edit-doc-property';
|
||||
@@ -290,18 +290,13 @@ const DocWorkspacePropertiesTableBody = forwardRef<
|
||||
const workbenchService = useService(WorkbenchService);
|
||||
const viewService = useServiceOptional(ViewService);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
const properties = useLiveData(docsService.propertyList.sortedProperties$);
|
||||
const [addMoreCollapsed, setAddMoreCollapsed] = useState(true);
|
||||
|
||||
const [newPropertyId, setNewPropertyId] = useState<string | null>(null);
|
||||
|
||||
const canEditProperty = useLiveData(
|
||||
guardService.can$('Doc_Update', docService.doc.id)
|
||||
);
|
||||
const canEditPropertyInfo = useLiveData(
|
||||
guardService.can$('Workspace_Properties_Update')
|
||||
);
|
||||
const canEditProperty = useGuard('Doc_Update', docService.doc.id);
|
||||
const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
|
||||
|
||||
const handlePropertyAdded = useCallback(
|
||||
(property: DocCustomPropertyInfo) => {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import {
|
||||
type DocPermissionActions,
|
||||
GuardService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import type React from 'react';
|
||||
|
||||
export const DocPermissionGuard = ({
|
||||
docId,
|
||||
children,
|
||||
permission,
|
||||
}: {
|
||||
docId: string;
|
||||
permission: DocPermissionActions;
|
||||
children: (can: boolean | undefined) => React.ReactNode;
|
||||
}) => {
|
||||
const guardService = useService(GuardService);
|
||||
const can = useLiveData(guardService.can$(permission, docId));
|
||||
|
||||
if (typeof children === 'function') {
|
||||
return children(can);
|
||||
}
|
||||
throw new Error('children must be a function');
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
type DocPermissionActions,
|
||||
type WorkspacePermissionActions,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import type React from 'react';
|
||||
|
||||
import { useGuard } from './use-guard';
|
||||
|
||||
export const Guard = <
|
||||
T extends WorkspacePermissionActions | DocPermissionActions,
|
||||
>(
|
||||
props: {
|
||||
permission: T;
|
||||
children: (can: boolean | undefined) => React.ReactNode;
|
||||
} & (T extends DocPermissionActions ? { docId: string } : {})
|
||||
) => {
|
||||
const { permission, children, ...rest } = props;
|
||||
const docId = 'docId' in rest ? [rest.docId] : [];
|
||||
const can = useGuard(permission, ...(docId as any));
|
||||
|
||||
if (typeof children === 'function') {
|
||||
return children(can);
|
||||
}
|
||||
throw new Error('children must be a function');
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './guard';
|
||||
export * from './use-guard';
|
||||
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
type DocPermissionActions,
|
||||
GuardService,
|
||||
type WorkspacePermissionActions,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
export const useGuard = <
|
||||
T extends WorkspacePermissionActions | DocPermissionActions,
|
||||
>(
|
||||
action: T,
|
||||
...args: T extends DocPermissionActions ? [string] : []
|
||||
) => {
|
||||
const guardService = useService(GuardService);
|
||||
useEffect(() => {
|
||||
guardService.revalidateCan(action, ...args);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [action, guardService, ...args]);
|
||||
|
||||
const livedata$ = useMemo(
|
||||
() => guardService.can$(action, ...args),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[action, guardService, ...args]
|
||||
);
|
||||
|
||||
const can = useLiveData(livedata$);
|
||||
return can;
|
||||
};
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
FavoriteService,
|
||||
} from '@affine/core/modules/favorite';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||
@@ -41,6 +40,7 @@ import { useCallback, useState } from 'react';
|
||||
|
||||
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
|
||||
import type { CollectionService } from '../../modules/collection';
|
||||
import { useGuard } from '../guard';
|
||||
import { IsFavoriteIcon } from '../pure/icons';
|
||||
import { FavoriteTag } from './components/favorite-tag';
|
||||
import * as styles from './list.css';
|
||||
@@ -68,15 +68,13 @@ const PageOperationCellMenuItem = ({
|
||||
workspaceService,
|
||||
compatibleFavoriteItemsAdapter: favAdapter,
|
||||
workbenchService,
|
||||
guardService,
|
||||
} = useServices({
|
||||
WorkspaceService,
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
WorkbenchService,
|
||||
GuardService,
|
||||
});
|
||||
|
||||
const canMoveToTrash = useLiveData(guardService.can$('Doc_Trash', page.id));
|
||||
const canMoveToTrash = useGuard('Doc_Trash', page.id);
|
||||
const currentWorkspace = workspaceService.workspace;
|
||||
const favourite = useLiveData(favAdapter.isFavorite$(page.id, 'doc'));
|
||||
const workbench = workbenchService.workbench;
|
||||
|
||||
Reference in New Issue
Block a user