PageAttachment.tsx 4.4 KB

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