Просмотр исходного кода

refactor hooks for page abilities

Yuki Takei 6 месяцев назад
Родитель
Сommit
cf28913f4a

+ 6 - 6
apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -42,14 +42,14 @@ import { usePageDeleteModalActions } from '~/states/ui/modal/page-delete';
 import { usePageDuplicateModalActions, type IPageForPageDuplicateModal } from '~/states/ui/modal/page-duplicate';
 import { usePresentationModalActions } from '~/states/ui/modal/page-presentation';
 import { usePageRenameModalActions } from '~/states/ui/modal/page-rename';
+import {
+  useIsAbleToShowPageManagement,
+  useIsAbleToChangeEditorMode,
+} from '~/states/ui/page-abilities';
 import {
   useSWRxPageInfo,
 } from '~/stores/page';
 import { mutatePageTree, mutateRecentlyUpdated } from '~/stores/page-listing';
-import {
-  useIsAbleToShowPageManagement,
-  useIsAbleToChangeEditorMode,
-} from '~/stores/ui';
 
 import { NotAvailable } from '../NotAvailable';
 import { Skeleton } from '../Skeleton';
@@ -279,8 +279,8 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
   const shouldExpandContent = useShouldExpandContent(currentPage);
 
-  const { data: isAbleToShowPageManagement } = useIsAbleToShowPageManagement();
-  const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
+  const isAbleToShowPageManagement = useIsAbleToShowPageManagement();
+  const isAbleToChangeEditorMode = useIsAbleToChangeEditorMode();
   const [isDeviceLargerThanMd] = useDeviceLargerThanMd();
 
   const { open: openDuplicateModal } = usePageDuplicateModalActions();

+ 0 - 2
apps/app/src/client/components/PageEditor/EditorNavbarBottom/OptionsSelector.tsx

@@ -15,8 +15,6 @@ import {
 import { isIndentSizeForcedAtom } from '~/states/server-configurations';
 import { useDeviceLargerThanMd } from '~/states/ui/device';
 import { useEditorSettings, useCurrentIndentSize } from '~/stores/editor';
-import {
-} from '~/stores/ui';
 
 type RadioListItemProps = {
   onClick: () => void,

+ 2 - 2
apps/app/src/client/components/PageSideContents/PageSideContents.tsx

@@ -16,8 +16,8 @@ import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
 import { showPageSideAuthorsAtom } from '~/states/server-configurations';
 import { useDescendantsPageListModalActions } from '~/states/ui/modal/descendants-page-list';
 import { useTagEditModalActions } from '~/states/ui/modal/tag-edit';
+import { useIsAbleToShowTagLabel } from '~/states/ui/page-abilities';
 import { useSWRxPageInfo, useSWRxTagsInfo } from '~/stores/page';
-import { useIsAbleToShowTagLabel } from '~/stores/ui';
 
 import { ContentLinkButtons } from '../ContentLinkButtons';
 import { PageTagsSkeleton } from '../PageTags';
@@ -48,7 +48,7 @@ const Tags = (props: TagsProps): JSX.Element => {
 
   const { data: tagsInfoData } = useSWRxTagsInfo(pageId, { suspense: true });
 
-  const { data: showTagLabel } = useIsAbleToShowTagLabel();
+  const showTagLabel = useIsAbleToShowTagLabel();
   const isGuestUser = useIsGuestUser();
   const isReadOnlyUser = useIsReadOnlyUser();
   const { open: openTagEditModal } = useTagEditModalActions();

+ 0 - 2
apps/app/src/client/components/Sidebar/Sidebar.tsx

@@ -19,8 +19,6 @@ import {
   useCurrentProductNavWidth,
   useSetSidebarScrollerRef,
 } from '~/states/ui/sidebar';
-import {
-} from '~/stores/ui';
 
 import { DrawerToggler } from '../Common/DrawerToggler';
 

+ 3 - 3
apps/app/src/components/PageView/PageAlerts/TrashPageAlert.tsx

@@ -10,9 +10,9 @@ import {
 } from '~/states/page';
 import { usePageDeleteModalActions } from '~/states/ui/modal/page-delete';
 import { usePutBackPageModalActions } from '~/states/ui/modal/put-back-page';
+import { useIsAbleToShowTrashPageManagementButtons } from '~/states/ui/page-abilities';
 import { useSWRxPageInfo } from '~/stores/page';
 import { mutateRecentlyUpdated } from '~/stores/page-listing';
-import { useIsAbleToShowTrashPageManagementButtons } from '~/stores/ui';
 
 
 const onDeletedHandler = (pathOrPathsToDelete) => {
@@ -27,7 +27,7 @@ export const TrashPageAlert = (): JSX.Element => {
   const { t } = useTranslation();
   const router = useRouter();
 
-  const { data: isAbleToShowTrashPageManagementButtons } = useIsAbleToShowTrashPageManagementButtons();
+  const isAbleToShowTrashPageManagementButtons = useIsAbleToShowTrashPageManagementButtons();
   const pageData = useCurrentPageData();
   const isTrashPage = useIsTrashPage();
   const pageId = pageData?._id;
@@ -50,7 +50,7 @@ export const TrashPageAlert = (): JSX.Element => {
     if (isEmptyPage) {
       return;
     }
-    const putBackedHandler = async() => {
+    const putBackedHandler = async () => {
       if (currentPagePath == null) {
         return;
       }

+ 9 - 0
apps/app/src/states/context.ts

@@ -76,3 +76,12 @@ const growiDocumentationUrlAtom = atom((get) => {
 
 export const useGrowiDocumentationUrl = () =>
   useAtomValue(growiDocumentationUrlAtom);
+
+/**
+ * Internal atoms for derived atom usage (special naming convention)
+ * These atoms are exposed only for creating derived atoms in other modules
+ */
+export const _atomsForDerivedAbilities = {
+  isReadOnlyUserAtom,
+  isSharedUserAtom,
+} as const;

+ 8 - 0
apps/app/src/states/global/global.ts

@@ -162,3 +162,11 @@ export const _atomsForAdminPagesHydration = {
   growiCloudUriAtom,
   growiAppIdForGrowiCloudAtom,
 };
+
+/**
+ * Internal atoms for derived atom usage (special naming convention)
+ * These atoms are exposed only for creating derived atoms in other modules
+ */
+export const _atomsForDerivedAbilities = {
+  currentUserAtom,
+} as const;

+ 1 - 0
apps/app/src/states/page/index.ts

@@ -5,6 +5,7 @@
  * hiding internal implementation details while exposing only the necessary hooks.
  */
 
+export { _atomsForDerivedAbilities } from './internal-atoms';
 export * from './hooks';
 export { useCurrentPageLoading } from './use-current-page-loading';
 // Data fetching hooks

+ 13 - 0
apps/app/src/states/page/internal-atoms.ts

@@ -140,3 +140,16 @@ export const setRemoteRevisionDataAtom = atom(
  * Atom for redirect from path
  */
 export const redirectFromAtom = atom<string | undefined>(undefined);
+
+/**
+ * Internal atoms for derived atom usage (special naming convention)
+ * These atoms are exposed only for creating derived atoms in other modules
+ */
+export const _atomsForDerivedAbilities = {
+  pageNotFoundAtom,
+  currentPagePathAtom,
+  isIdenticalPathAtom,
+  shareLinkIdAtom,
+  currentPageIdAtom,
+  isTrashPageAtom,
+} as const;

+ 8 - 0
apps/app/src/states/ui/editor/atoms.ts

@@ -50,3 +50,11 @@ export const editingMarkdownAtom = atom<string>('');
 export const selectedGrantAtom = atom<IPageSelectedGrant | null>({
   grant: PageGrant.GRANT_PUBLIC,
 });
+
+/**
+ * Internal atoms for derived atom usage (special naming convention)
+ * These atoms are exposed only for creating derived atoms in other modules
+ */
+export const _atomsForDerivedAbilities = {
+  editorModeAtom,
+} as const;

+ 1 - 1
apps/app/src/states/ui/editor/index.ts

@@ -1,6 +1,6 @@
 // Export only the essential public API
 
-export { editingMarkdownAtom, selectedGrantAtom } from './atoms';
+export { editingMarkdownAtom, selectedGrantAtom, _atomsForDerivedAbilities } from './atoms';
 export {
   useEditingMarkdown,
   useEditorMode,

+ 130 - 0
apps/app/src/states/ui/page-abilities.ts

@@ -0,0 +1,130 @@
+import { pagePathUtils } from '@growi/core/dist/utils';
+import { atom, useAtomValue } from 'jotai';
+
+import { EditorMode } from '~/states/ui/editor';
+import { useIsSharedUser } from '~/states/context';
+import {
+  useIsEditable, usePageNotFound, useCurrentPagePath, useCurrentPageId,
+} from '~/states/page';
+
+// Import internal atoms with special naming
+import { _atomsForDerivedAbilities as pageAtoms } from '~/states/page';
+import { _atomsForDerivedAbilities as editorAtoms } from '~/states/ui/editor';
+import { _atomsForDerivedAbilities as globalAtoms } from '~/states/global';
+import { _atomsForDerivedAbilities as contextAtoms } from '~/states/context';
+
+const { isTrashTopPage, isUsersTopPage } = pagePathUtils;
+
+// Derived atom for TagLabel display ability
+const isAbleToShowTagLabelAtom = atom((get) => {
+  const isNotFound = get(pageAtoms.pageNotFoundAtom);
+  const currentPagePath = get(pageAtoms.currentPagePathAtom);
+  const isIdenticalPath = get(pageAtoms.isIdenticalPathAtom);
+  const shareLinkId = get(pageAtoms.shareLinkIdAtom);
+  const editorMode = get(editorAtoms.editorModeAtom);
+
+  // Return false if any dependency is undefined
+  if ([currentPagePath, isIdenticalPath, isNotFound, editorMode].some(v => v === undefined)) {
+    return false;
+  }
+
+  const isViewMode = editorMode === EditorMode.View;
+
+  // "/trash" page does not exist on page collection and unable to add tags
+  return !isUsersTopPage(currentPagePath!) && !isTrashTopPage(currentPagePath!)
+    && shareLinkId == null && !isIdenticalPath && !(isViewMode && isNotFound);
+});
+
+/**
+ * True if the current user can see TagLabel
+ */
+export const useIsAbleToShowTagLabel = (): boolean => {
+  return useAtomValue(isAbleToShowTagLabelAtom);
+};
+
+// Derived atom for TrashPageManagementButtons display ability
+const isAbleToShowTrashPageManagementButtonsAtom = atom((get) => {
+  const currentUser = get(globalAtoms.currentUserAtom);
+  const currentPageId = get(pageAtoms.currentPageIdAtom);
+  const isNotFound = get(pageAtoms.pageNotFoundAtom);
+  const isTrashPage = get(pageAtoms.isTrashPageAtom);
+  const isReadOnlyUser = get(contextAtoms.isReadOnlyUserAtom);
+
+  // Return false if any dependency is undefined
+  if ([currentUser, currentPageId, isNotFound, isReadOnlyUser, isTrashPage].some(v => v === undefined)) {
+    return false;
+  }
+
+  const isCurrentUserExist = currentUser != null;
+  const isPageExist = currentPageId != null && isNotFound === false;
+  const isTrashPageCondition = isPageExist && isTrashPage === true;
+  const isReadOnlyUserCondition = isPageExist && isReadOnlyUser === true;
+
+  return isTrashPageCondition && isCurrentUserExist && !isReadOnlyUserCondition;
+});
+
+/**
+ * True if the current user can see TrashPageManagementButtons
+ */
+export const useIsAbleToShowTrashPageManagementButtons = (): boolean => {
+  return useAtomValue(isAbleToShowTrashPageManagementButtonsAtom);
+};
+
+// Derived atom for PageManagement display ability
+const isAbleToShowPageManagementAtom = atom((get) => {
+  const currentPageId = get(pageAtoms.currentPageIdAtom);
+  const isNotFound = get(pageAtoms.pageNotFoundAtom);
+  const isTrashPage = get(pageAtoms.isTrashPageAtom);
+  const isSharedUser = get(contextAtoms.isSharedUserAtom);
+
+  const pageId = currentPageId;
+
+  // Return false if any dependency is undefined
+  if ([pageId, isTrashPage, isSharedUser, isNotFound].some(v => v === undefined)) {
+    return false;
+  }
+
+  const isPageExist = (pageId != null) && isNotFound === false;
+  const isEmptyPage = (pageId != null) && isNotFound === true;
+  const isTrashPageCondition = isPageExist && isTrashPage === true;
+  const isSharedUserCondition = isPageExist && isSharedUser === true;
+
+  return (isPageExist && !isTrashPageCondition && !isSharedUserCondition) || isEmptyPage;
+});
+
+/**
+ * True if the current user can see PageManagement
+ */
+export const useIsAbleToShowPageManagement = (): boolean => {
+  return useAtomValue(isAbleToShowPageManagementAtom);
+};
+
+/**
+ * True if the current user can change editor mode
+ */
+export const useIsAbleToChangeEditorMode = (): boolean => {
+  const isEditable = useIsEditable();
+  const isSharedUser = useIsSharedUser();
+
+  const includesUndefined = [isEditable, isSharedUser].some(v => v === undefined);
+  if (includesUndefined) return false;
+
+  return !!isEditable && !isSharedUser;
+};
+
+/**
+ * True if the current user can see PageAuthors
+ */
+export const useIsAbleToShowPageAuthors = (): boolean => {
+  const pageId = useCurrentPageId();
+  const isNotFound = usePageNotFound();
+  const pagePath = useCurrentPagePath();
+
+  const includesUndefined = [pageId, pagePath, isNotFound].some(v => v === undefined);
+  if (includesUndefined) return false;
+
+  const isPageExist = (pageId != null) && !isNotFound;
+  const isUsersTopPagePath = pagePath != null && isUsersTopPage(pagePath);
+
+  return isPageExist && !isUsersTopPagePath;
+};

+ 0 - 119
apps/app/src/stores/ui.tsx

@@ -1,119 +0,0 @@
-import { pagePathUtils } from '@growi/core/dist/utils';
-import {
-  type SWRResponse,
-} from 'swr';
-import useSWRImmutable from 'swr/immutable';
-
-import {
-  useIsReadOnlyUser, useIsSharedUser,
-} from '~/states/context';
-import { useCurrentUser } from '~/states/global';
-import {
-  useIsEditable, useIsIdenticalPath, usePageNotFound, useCurrentPagePath, useIsTrashPage, useCurrentPageId,
-} from '~/states/page';
-import { useShareLinkId } from '~/states/page/hooks';
-import { EditorMode, useEditorMode } from '~/states/ui/editor';
-import loggerFactory from '~/utils/logger';
-
-const { isTrashTopPage, isUsersTopPage } = pagePathUtils;
-
-const logger = loggerFactory('growi:stores:ui');
-
-
-/** **********************************************************
- *                          SWR Hooks
- *                Determined value by context
- *********************************************************** */
-
-export const useIsAbleToShowTrashPageManagementButtons = (): SWRResponse<boolean, Error> => {
-  const key = 'isAbleToShowTrashPageManagementButtons';
-
-  const _currentUser = useCurrentUser();
-  const isCurrentUserExist = _currentUser != null;
-
-  const _currentPageId = useCurrentPageId();
-  const _isNotFound = usePageNotFound();
-  const _isTrashPage = useIsTrashPage();
-  const _isReadOnlyUser = useIsReadOnlyUser();
-  const isPageExist = _currentPageId != null && _isNotFound === false;
-  const isTrashPage = isPageExist && _isTrashPage === true;
-  const isReadOnlyUser = isPageExist && _isReadOnlyUser === true;
-
-  const includesUndefined = [_currentUser, _currentPageId, _isNotFound, _isReadOnlyUser, _isTrashPage].some(v => v === undefined);
-
-  return useSWRImmutable(
-    includesUndefined ? null : [key, isTrashPage, isCurrentUserExist, isReadOnlyUser],
-    ([, isTrashPage, isCurrentUserExist, isReadOnlyUser]) => isTrashPage && isCurrentUserExist && !isReadOnlyUser,
-  );
-};
-
-export const useIsAbleToShowPageManagement = (): SWRResponse<boolean, Error> => {
-  const key = 'isAbleToShowPageManagement';
-  const currentPageId = useCurrentPageId();
-  const isNotFound = usePageNotFound();
-  const _isTrashPage = useIsTrashPage();
-  const _isSharedUser = useIsSharedUser();
-
-  const pageId = currentPageId;
-  const includesUndefined = [pageId, _isTrashPage, _isSharedUser, isNotFound].some(v => v === undefined);
-  const isPageExist = (pageId != null) && isNotFound === false;
-  const isEmptyPage = (pageId != null) && isNotFound === true;
-  const isTrashPage = isPageExist && _isTrashPage === true;
-  const isSharedUser = isPageExist && _isSharedUser === true;
-
-  return useSWRImmutable(
-    includesUndefined ? null : [key, pageId, isPageExist, isEmptyPage, isTrashPage, isSharedUser],
-    ([, , isPageExist, isEmptyPage, isTrashPage, isSharedUser]) => (isPageExist && !isTrashPage && !isSharedUser) || isEmptyPage,
-  );
-};
-
-export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
-  const key = 'isAbleToShowTagLabel';
-  const pageId = useCurrentPageId();
-  const isNotFound = usePageNotFound();
-  const currentPagePath = useCurrentPagePath();
-  const isIdenticalPath = useIsIdenticalPath();
-  const { editorMode } = useEditorMode();
-  const shareLinkId = useShareLinkId();
-
-
-  const includesUndefined = [currentPagePath, isIdenticalPath, isNotFound, editorMode].some(v => v === undefined);
-
-  const isViewMode = editorMode === EditorMode.View;
-
-  return useSWRImmutable(
-    includesUndefined ? null : [key, pageId, currentPagePath, isIdenticalPath, isNotFound, editorMode, shareLinkId],
-    // "/trash" page does not exist on page collection and unable to add tags
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    () => !isUsersTopPage(currentPagePath!) && !isTrashTopPage(currentPagePath!) && shareLinkId == null && !isIdenticalPath && !(isViewMode && isNotFound),
-  );
-};
-
-export const useIsAbleToChangeEditorMode = (): SWRResponse<boolean, Error> => {
-  const key = 'isAbleToChangeEditorMode';
-  const isEditable = useIsEditable();
-  const isSharedUser = useIsSharedUser();
-
-  const includesUndefined = [isEditable, isSharedUser].some(v => v === undefined);
-
-  return useSWRImmutable(
-    includesUndefined ? null : [key, isEditable, isSharedUser],
-    () => !!isEditable && !isSharedUser,
-  );
-};
-
-export const useIsAbleToShowPageAuthors = (): SWRResponse<boolean, Error> => {
-  const key = 'isAbleToShowPageAuthors';
-  const pageId = useCurrentPageId();
-  const isNotFound = usePageNotFound();
-  const pagePath = useCurrentPagePath();
-
-  const includesUndefined = [pageId, pagePath, isNotFound].some(v => v === undefined);
-  const isPageExist = (pageId != null) && !isNotFound;
-  const isUsersTopPagePath = pagePath != null && isUsersTopPage(pagePath);
-
-  return useSWRImmutable(
-    includesUndefined ? null : [key, pageId, pagePath, isNotFound],
-    () => isPageExist && !isUsersTopPagePath,
-  );
-};