ItemsTree.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import React, { useEffect, useCallback, type JSX } from 'react';
  2. import path from 'path';
  3. import type { IPageToDeleteWithMeta } from '@growi/core';
  4. import { useGlobalSocket } from '@growi/core/dist/swr';
  5. import { useTranslation } from 'next-i18next';
  6. import { useRouter } from 'next/router';
  7. import { toastError, toastSuccess } from '~/client/util/toastr';
  8. import type { IPageForItem } from '~/interfaces/page';
  9. import type { OnDuplicatedFunction, OnDeletedFunction } from '~/interfaces/ui';
  10. import type { UpdateDescCountData, UpdateDescCountRawData } from '~/interfaces/websocket';
  11. import { SocketEventName } from '~/interfaces/websocket';
  12. import { useCurrentPagePath, useFetchCurrentPage } from '~/states/page';
  13. import type { IPageForPageDuplicateModal } from '~/stores/modal';
  14. import { usePageDuplicateModal, usePageDeleteModal } from '~/stores/modal';
  15. import { mutateAllPageInfo } from '~/stores/page';
  16. import {
  17. useSWRxRootPage, mutatePageTree, mutatePageList,
  18. } from '~/stores/page-listing';
  19. import { mutateSearching } from '~/stores/search';
  20. import { usePageTreeDescCountMap } from '~/stores/ui';
  21. import loggerFactory from '~/utils/logger';
  22. import { ItemNode, type TreeItemProps } from '../TreeItem';
  23. import ItemsTreeContentSkeleton from './ItemsTreeContentSkeleton';
  24. import styles from './ItemsTree.module.scss';
  25. const moduleClass = styles['items-tree'] ?? '';
  26. const logger = loggerFactory('growi:cli:ItemsTree');
  27. type ItemsTreeProps = {
  28. isEnableActions: boolean
  29. isReadOnlyUser: boolean
  30. isWipPageShown?: boolean
  31. targetPath: string
  32. targetPathOrId?: string,
  33. CustomTreeItem: React.FunctionComponent<TreeItemProps>
  34. onClickTreeItem?: (page: IPageForItem) => void;
  35. }
  36. /*
  37. * ItemsTree
  38. */
  39. export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
  40. const {
  41. targetPath, targetPathOrId, isEnableActions, isReadOnlyUser, isWipPageShown, CustomTreeItem, onClickTreeItem,
  42. } = props;
  43. const { t } = useTranslation();
  44. const router = useRouter();
  45. const { data: rootPageResult, error } = useSWRxRootPage({ suspense: true });
  46. const [currentPagePath] = useCurrentPagePath();
  47. const { open: openDuplicateModal } = usePageDuplicateModal();
  48. const { open: openDeleteModal } = usePageDeleteModal();
  49. const { data: socket } = useGlobalSocket();
  50. const { data: ptDescCountMap, update: updatePtDescCountMap } = usePageTreeDescCountMap();
  51. // for mutation
  52. const { fetchCurrentPage } = useFetchCurrentPage();
  53. useEffect(() => {
  54. if (socket == null) {
  55. return;
  56. }
  57. socket.on(SocketEventName.UpdateDescCount, (data: UpdateDescCountRawData) => {
  58. // save to global state
  59. const newData: UpdateDescCountData = new Map(Object.entries(data));
  60. updatePtDescCountMap(newData);
  61. });
  62. return () => { socket.off(SocketEventName.UpdateDescCount) };
  63. }, [socket, ptDescCountMap, updatePtDescCountMap]);
  64. const onRenamed = useCallback((fromPath: string | undefined, toPath: string) => {
  65. mutatePageTree();
  66. mutateSearching();
  67. mutatePageList();
  68. if (currentPagePath === fromPath || currentPagePath === toPath) {
  69. fetchCurrentPage();
  70. }
  71. }, [currentPagePath, fetchCurrentPage]);
  72. const onClickDuplicateMenuItem = useCallback((pageToDuplicate: IPageForPageDuplicateModal) => {
  73. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  74. const duplicatedHandler: OnDuplicatedFunction = (fromPath, toPath) => {
  75. toastSuccess(t('duplicated_pages', { fromPath }));
  76. mutatePageTree();
  77. mutateSearching();
  78. mutatePageList();
  79. };
  80. openDuplicateModal(pageToDuplicate, { onDuplicated: duplicatedHandler });
  81. }, [openDuplicateModal, t]);
  82. const onClickDeleteMenuItem = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
  83. const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, isRecursively, isCompletely) => {
  84. if (typeof pathOrPathsToDelete !== 'string') {
  85. return;
  86. }
  87. if (isCompletely) {
  88. toastSuccess(t('deleted_pages_completely', { path: pathOrPathsToDelete }));
  89. }
  90. else {
  91. toastSuccess(t('deleted_pages', { path: pathOrPathsToDelete }));
  92. }
  93. mutatePageTree();
  94. mutateSearching();
  95. mutatePageList();
  96. mutateAllPageInfo();
  97. if (currentPagePath === pathOrPathsToDelete) {
  98. fetchCurrentPage();
  99. router.push(isCompletely ? path.dirname(pathOrPathsToDelete) : `/trash${pathOrPathsToDelete}`);
  100. }
  101. };
  102. openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler });
  103. }, [currentPagePath, fetchCurrentPage, openDeleteModal, router, t]);
  104. if (error != null) {
  105. toastError(t('pagetree.error_retrieving_the_pagetree'));
  106. return <></>;
  107. }
  108. const initialItemNode = rootPageResult ? new ItemNode(rootPageResult.rootPage) : null;
  109. if (initialItemNode != null) {
  110. return (
  111. <ul className={`${moduleClass} list-group`}>
  112. <CustomTreeItem
  113. key={initialItemNode.page.path}
  114. targetPath={targetPath}
  115. targetPathOrId={targetPathOrId}
  116. itemNode={initialItemNode}
  117. isOpen
  118. isEnableActions={isEnableActions}
  119. isWipPageShown={isWipPageShown}
  120. isReadOnlyUser={isReadOnlyUser}
  121. onRenamed={onRenamed}
  122. onClickDuplicateMenuItem={onClickDuplicateMenuItem}
  123. onClickDeleteMenuItem={onClickDeleteMenuItem}
  124. onClick={onClickTreeItem}
  125. />
  126. </ul>
  127. );
  128. }
  129. return <ItemsTreeContentSkeleton />;
  130. };