PageAttachment.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import React, {
  2. useCallback, useMemo, useState,
  3. } from 'react';
  4. import { IAttachmentHasId } from '@growi/core';
  5. import { useSWRxAttachments } from '~/stores/attachment';
  6. import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
  7. import { useSWRxCurrentPage, useCurrentPageId } from '~/stores/page';
  8. import { DeleteAttachmentModal } from './PageAttachment/DeleteAttachmentModal';
  9. import { PageAttachmentList } from './PageAttachment/PageAttachmentList';
  10. import PaginationWrapper from './PaginationWrapper';
  11. // Utility
  12. const checkIfFileInUse = (markdown: string, attachment): boolean => {
  13. return markdown.indexOf(attachment._id) >= 0;
  14. };
  15. const PageAttachment = (): JSX.Element => {
  16. const { data: currentPage } = useSWRxCurrentPage();
  17. const markdown = currentPage?.revision.body;
  18. // Static SWRs
  19. const { data: pageId } = useCurrentPageId();
  20. const { data: isGuestUser } = useIsGuestUser();
  21. const { data: isReadOnlyUser } = useIsReadOnlyUser();
  22. const isPageAttachmentDisabled = !!isGuestUser || !!isReadOnlyUser;
  23. // States
  24. const [pageNumber, setPageNumber] = useState(1);
  25. const [attachmentToDelete, setAttachmentToDelete] = useState<(IAttachmentHasId) | null>(null);
  26. const [deleting, setDeleting] = useState(false);
  27. const [deleteError, setDeleteError] = useState('');
  28. // SWRs
  29. const { data: dataAttachments, remove } = useSWRxAttachments(pageId, pageNumber);
  30. // Custom hooks
  31. const inUseAttachmentsMap: { [id: string]: boolean } | undefined = useMemo(() => {
  32. if (markdown == null || dataAttachments == null) {
  33. return undefined;
  34. }
  35. const attachmentEntries = dataAttachments.attachments
  36. .map((attachment) => {
  37. return [attachment._id, checkIfFileInUse(markdown, attachment)];
  38. });
  39. return Object.fromEntries(attachmentEntries);
  40. }, [dataAttachments, markdown]);
  41. // Methods
  42. const onChangePageHandler = useCallback((newPageNumber: number) => {
  43. setPageNumber(newPageNumber);
  44. }, []);
  45. const onAttachmentDeleteClicked = useCallback((attachment) => {
  46. setAttachmentToDelete(attachment);
  47. }, []);
  48. const onAttachmentDeleteClickedConfirmHandler = useCallback(async(attachment: IAttachmentHasId) => {
  49. setDeleting(true);
  50. try {
  51. await remove({ attachment_id: attachment._id });
  52. setAttachmentToDelete(null);
  53. setDeleting(false);
  54. }
  55. catch {
  56. setDeleteError('Something went wrong.');
  57. setDeleting(false);
  58. }
  59. }, [remove]);
  60. const onToggleHandler = useCallback(() => {
  61. setAttachmentToDelete(null);
  62. setDeleteError('');
  63. }, []);
  64. // Renderers
  65. const renderPageAttachmentList = useCallback(() => {
  66. if (dataAttachments == null || inUseAttachmentsMap == null) {
  67. return (
  68. <div className="text-muted text-center">
  69. <i className="fa fa-2x fa-spinner fa-pulse mr-1"></i>
  70. </div>
  71. );
  72. }
  73. return (
  74. <PageAttachmentList
  75. attachments={dataAttachments.attachments}
  76. inUse={inUseAttachmentsMap}
  77. onAttachmentDeleteClicked={onAttachmentDeleteClicked}
  78. isUserLoggedIn={!isPageAttachmentDisabled}
  79. />
  80. );
  81. }, [dataAttachments, inUseAttachmentsMap, isPageAttachmentDisabled, onAttachmentDeleteClicked]);
  82. const renderDeleteAttachmentModal = useCallback(() => {
  83. if (isPageAttachmentDisabled) {
  84. return <></>;
  85. }
  86. if (dataAttachments == null || dataAttachments.attachments.length === 0 || attachmentToDelete == null) {
  87. return <></>;
  88. }
  89. const isOpen = attachmentToDelete != null;
  90. return (
  91. <DeleteAttachmentModal
  92. isOpen={isOpen}
  93. toggle={onToggleHandler}
  94. attachmentToDelete={attachmentToDelete}
  95. deleting={deleting}
  96. deleteError={deleteError}
  97. onAttachmentDeleteClickedConfirm={onAttachmentDeleteClickedConfirmHandler}
  98. />
  99. );
  100. // eslint-disable-next-line max-len
  101. }, [attachmentToDelete, dataAttachments, deleteError, deleting, isPageAttachmentDisabled, onAttachmentDeleteClickedConfirmHandler, onToggleHandler]);
  102. const renderPaginationWrapper = useCallback(() => {
  103. if (dataAttachments == null || dataAttachments.attachments.length === 0) {
  104. return <></>;
  105. }
  106. return (
  107. <PaginationWrapper
  108. activePage={pageNumber}
  109. changePage={onChangePageHandler}
  110. totalItemsCount={dataAttachments.totalAttachments}
  111. pagingLimit={dataAttachments.limit}
  112. align="center"
  113. />
  114. );
  115. }, [dataAttachments, onChangePageHandler, pageNumber]);
  116. return (
  117. <div data-testid="page-attachment">
  118. {renderPageAttachmentList()}
  119. {renderDeleteAttachmentModal()}
  120. {renderPaginationWrapper()}
  121. </div>
  122. );
  123. };
  124. export default PageAttachment;