PageTreeSubstance.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import React, { memo, useCallback } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import { ItemsTree } from '~/features/page-tree/components';
  4. import { usePageTreeInformationUpdate } from '~/features/page-tree/states/page-tree-update';
  5. import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
  6. import { useCurrentPageId, useCurrentPagePath } from '~/states/page';
  7. import { useSidebarScrollerElem } from '~/states/ui/sidebar';
  8. import {
  9. mutatePageTree,
  10. mutateRecentlyUpdated,
  11. useSWRxRootPage,
  12. useSWRxV5MigrationStatus,
  13. } from '~/stores/page-listing';
  14. import loggerFactory from '~/utils/logger';
  15. import { PageTreeItem, pageTreeItemSize } from '../PageTreeItem';
  16. import { SidebarHeaderReloadButton } from '../SidebarHeaderReloadButton';
  17. import { PrivateLegacyPagesLink } from './PrivateLegacyPagesLink';
  18. const logger = loggerFactory('growi:cli:PageTreeSubstance');
  19. type HeaderProps = {
  20. isWipPageShown: boolean;
  21. onWipPageShownChange?: () => void;
  22. };
  23. export const PageTreeHeader = memo(
  24. ({ isWipPageShown, onWipPageShownChange }: HeaderProps) => {
  25. const { t } = useTranslation();
  26. const { mutate: mutateRootPage } = useSWRxRootPage({ suspense: true });
  27. useSWRxV5MigrationStatus({ suspense: true });
  28. const { notifyUpdateAllTrees } = usePageTreeInformationUpdate();
  29. const mutate = useCallback(() => {
  30. mutateRootPage();
  31. mutatePageTree();
  32. mutateRecentlyUpdated();
  33. // Notify headless-tree to rebuild with fresh data
  34. notifyUpdateAllTrees();
  35. }, [mutateRootPage, notifyUpdateAllTrees]);
  36. return (
  37. <>
  38. <SidebarHeaderReloadButton onClick={() => mutate()} />
  39. <div className="me-1">
  40. <button
  41. color="transparent"
  42. className="btn p-0 border-0"
  43. type="button"
  44. data-bs-toggle="dropdown"
  45. data-bs-auto-close="outside"
  46. aria-expanded="false"
  47. >
  48. <span className="material-symbols-outlined">more_horiz</span>
  49. </button>
  50. <ul className="dropdown-menu">
  51. <li>
  52. <button
  53. type="button"
  54. className="dropdown-item"
  55. onClick={onWipPageShownChange}
  56. >
  57. <div className="form-check form-switch">
  58. <input
  59. id="page-tree-wip-toggle"
  60. className="form-check-input pe-none"
  61. type="checkbox"
  62. checked={isWipPageShown}
  63. onChange={() => {}}
  64. />
  65. <label
  66. className="form-check-label pe-none"
  67. htmlFor="page-tree-wip-toggle"
  68. >
  69. {t('sidebar_header.show_wip_page')}
  70. </label>
  71. </div>
  72. </button>
  73. </li>
  74. </ul>
  75. </div>
  76. </>
  77. );
  78. },
  79. );
  80. PageTreeHeader.displayName = 'PageTreeHeader';
  81. const PageTreeUnavailable = () => {
  82. const { t } = useTranslation();
  83. return (
  84. <div className="mt-5 mx-2 text-center">
  85. <h3 className="text-gray">
  86. {t('v5_page_migration.page_tree_not_avaliable')}
  87. </h3>
  88. <a href="/admin">{t('v5_page_migration.go_to_settings')}</a>
  89. </div>
  90. );
  91. };
  92. type PageTreeContentProps = {
  93. isWipPageShown: boolean;
  94. };
  95. export const PageTreeContent = memo(
  96. ({ isWipPageShown }: PageTreeContentProps) => {
  97. const isGuestUser = useIsGuestUser();
  98. const isReadOnlyUser = useIsReadOnlyUser();
  99. const currentPath = useCurrentPagePath();
  100. const targetId = useCurrentPageId();
  101. const { data: migrationStatus } = useSWRxV5MigrationStatus({
  102. suspense: true,
  103. });
  104. const targetPathOrId = targetId || currentPath;
  105. const path = currentPath || '/';
  106. const sidebarScrollerElem = useSidebarScrollerElem();
  107. const estimateTreeItemSize = useCallback(() => pageTreeItemSize, []);
  108. if (!migrationStatus?.isV5Compatible) {
  109. return <PageTreeUnavailable />;
  110. }
  111. /*
  112. * dependencies
  113. */
  114. if (isGuestUser == null) {
  115. return null;
  116. }
  117. return (
  118. <div className="pt-4">
  119. <ItemsTree
  120. enableRenaming
  121. enableDragAndDrop
  122. isEnableActions={!isGuestUser}
  123. isReadOnlyUser={!!isReadOnlyUser}
  124. isWipPageShown={isWipPageShown}
  125. targetPath={path}
  126. targetPathOrId={targetPathOrId}
  127. CustomTreeItem={PageTreeItem}
  128. estimateTreeItemSize={estimateTreeItemSize}
  129. scrollerElem={sidebarScrollerElem}
  130. />
  131. {!isGuestUser &&
  132. !isReadOnlyUser &&
  133. migrationStatus?.migratablePagesCount != null &&
  134. migrationStatus.migratablePagesCount !== 0 && (
  135. <div className="grw-pagetree-footer border-top mt-4 py-2 w-100">
  136. <div className="private-legacy-pages-link px-3 py-2">
  137. <PrivateLegacyPagesLink />
  138. </div>
  139. </div>
  140. )}
  141. </div>
  142. );
  143. },
  144. );
  145. PageTreeContent.displayName = 'PageTreeContent';