DescendantsPageList.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import React, { useCallback, useState } from 'react';
  2. import type {
  3. IDataWithMeta,
  4. IPageHasId,
  5. IPageInfoForOperation,
  6. } from '@growi/core';
  7. import { LoadingSpinner } from '@growi/ui/dist/components';
  8. import { useTranslation } from 'next-i18next';
  9. import { toastSuccess } from '~/client/util/toastr';
  10. import type { IPagingResult } from '~/interfaces/paging-result';
  11. import type { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
  12. import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/stores/context';
  13. import {
  14. mutatePageTree,
  15. useSWRxPageInfoForList, useSWRxPageList,
  16. } from '~/stores/page-listing';
  17. import type { ForceHideMenuItems } from './Common/Dropdown/PageItemControl';
  18. import PageList from './PageList/PageList';
  19. import PaginationWrapper from './PaginationWrapper';
  20. type SubstanceProps = {
  21. pagingResult: IPagingResult<IPageHasId> | undefined,
  22. activePage: number,
  23. setActivePage: (activePage: number) => void,
  24. forceHideMenuItems?: ForceHideMenuItems,
  25. onPagesDeleted?: OnDeletedFunction,
  26. onPagePutBacked?: OnPutBackedFunction,
  27. }
  28. const convertToIDataWithMeta = (page: IPageHasId): IDataWithMeta<IPageHasId> => {
  29. return { data: page };
  30. };
  31. const DescendantsPageListSubstance = (props: SubstanceProps): JSX.Element => {
  32. const { t } = useTranslation();
  33. const {
  34. pagingResult, activePage, setActivePage, forceHideMenuItems, onPagesDeleted, onPagePutBacked,
  35. } = props;
  36. const { data: isGuestUser } = useIsGuestUser();
  37. const { data: isReadOnlyUser } = useIsReadOnlyUser();
  38. const pageIds = pagingResult?.items?.map(page => page._id);
  39. const { injectTo } = useSWRxPageInfoForList(pageIds, null, true, true);
  40. let pageWithMetas: IDataWithMeta<IPageHasId, IPageInfoForOperation>[] = [];
  41. // initial data
  42. if (pagingResult != null) {
  43. // convert without meta at first
  44. const dataWithMetas = pagingResult.items.map(page => convertToIDataWithMeta(page));
  45. // inject data for listing
  46. pageWithMetas = injectTo(dataWithMetas);
  47. }
  48. const pageDeletedHandler: OnDeletedFunction = useCallback((...args) => {
  49. const path = args[0];
  50. const isCompletely = args[2];
  51. if (path == null || isCompletely == null) {
  52. toastSuccess(t('deleted_page'));
  53. }
  54. else {
  55. toastSuccess(t('deleted_pages_completely', { path }));
  56. }
  57. mutatePageTree();
  58. if (onPagesDeleted != null) {
  59. onPagesDeleted(...args);
  60. }
  61. }, [onPagesDeleted, t]);
  62. const pagePutBackedHandler: OnPutBackedFunction = useCallback((path) => {
  63. toastSuccess(t('page_has_been_reverted', { path }));
  64. mutatePageTree();
  65. if (onPagePutBacked != null) {
  66. onPagePutBacked(path);
  67. }
  68. }, [onPagePutBacked, t]);
  69. if (pagingResult == null) {
  70. return (
  71. <div className="wiki">
  72. <div className="text-muted text-center">
  73. <LoadingSpinner className="me-1 fs-3" />
  74. </div>
  75. </div>
  76. );
  77. }
  78. const showPager = pagingResult.totalCount > pagingResult.limit;
  79. return (
  80. <>
  81. <PageList
  82. pages={pageWithMetas}
  83. isEnableActions={!isGuestUser}
  84. isReadOnlyUser={!!isReadOnlyUser}
  85. forceHideMenuItems={forceHideMenuItems}
  86. onPagesDeleted={pageDeletedHandler}
  87. onPagePutBacked={pagePutBackedHandler}
  88. />
  89. { showPager && (
  90. <div className="my-4">
  91. <PaginationWrapper
  92. activePage={activePage}
  93. changePage={selectedPageNumber => setActivePage(selectedPageNumber)}
  94. totalItemsCount={pagingResult.totalCount}
  95. pagingLimit={pagingResult.limit}
  96. align="center"
  97. />
  98. </div>
  99. ) }
  100. </>
  101. );
  102. };
  103. export type DescendantsPageListProps = {
  104. path: string,
  105. limit?: number,
  106. forceHideMenuItems?: ForceHideMenuItems,
  107. }
  108. export const DescendantsPageList = (props: DescendantsPageListProps): JSX.Element => {
  109. const { path, limit, forceHideMenuItems } = props;
  110. const [activePage, setActivePage] = useState(1);
  111. const { data: isSharedUser } = useIsSharedUser();
  112. const { data: pagingResult, error, mutate } = useSWRxPageList(isSharedUser ? null : path, activePage, limit);
  113. if (error != null) {
  114. return (
  115. <div className="my-5">
  116. <div className="text-danger">{error.message}</div>
  117. </div>
  118. );
  119. }
  120. return (
  121. <DescendantsPageListSubstance
  122. pagingResult={pagingResult}
  123. activePage={activePage}
  124. setActivePage={setActivePage}
  125. forceHideMenuItems={forceHideMenuItems}
  126. onPagesDeleted={() => mutate()}
  127. onPagePutBacked={() => mutate()}
  128. />
  129. );
  130. };