PageAttachment.tsx 4.4 KB

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