ItemsTree.tsx 5.4 KB

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