RichAttachment.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import React, { useCallback } from 'react';
  2. import Image from 'next/image';
  3. import { UserPicture } from '@growi/ui/dist/components';
  4. import { useTranslation } from 'next-i18next';
  5. import prettyBytes from 'pretty-bytes';
  6. import {
  7. useIsGuestUser,
  8. useIsReadOnlyUser,
  9. useIsSharedUser,
  10. } from '~/states/context';
  11. import { useDeleteAttachmentModalActions } from '~/states/ui/modal/delete-attachment';
  12. import { useSWRxAttachment } from '~/stores/attachment';
  13. import styles from './RichAttachment.module.scss';
  14. type RichAttachmentProps = {
  15. attachmentId: string;
  16. url: string;
  17. attachmentName: string;
  18. };
  19. export const RichAttachment = React.memo((props: RichAttachmentProps) => {
  20. const { attachmentId, attachmentName } = props;
  21. const { t } = useTranslation();
  22. const { data: attachment, remove } = useSWRxAttachment(attachmentId);
  23. const { open: openDeleteAttachmentModal } = useDeleteAttachmentModalActions();
  24. const isGuestUser = useIsGuestUser();
  25. const isSharedUser = useIsSharedUser();
  26. const isReadOnlyUser = useIsReadOnlyUser();
  27. const showTrashButton =
  28. isGuestUser === false && isSharedUser === false && isReadOnlyUser === false;
  29. const onClickTrashButtonHandler = useCallback(() => {
  30. if (attachment == null) {
  31. return;
  32. }
  33. openDeleteAttachmentModal(attachment, remove);
  34. }, [attachment, openDeleteAttachmentModal, remove]);
  35. if (attachment == null) {
  36. return (
  37. <span className="text-muted">
  38. {t('rich_attachment.attachment_not_be_found')}
  39. </span>
  40. );
  41. }
  42. const {
  43. filePathProxied,
  44. originalName,
  45. downloadPathProxied,
  46. creator,
  47. createdAt,
  48. fileSize,
  49. } = attachment;
  50. // Guard here because attachment properties might be deleted in turn when an attachment is removed
  51. if (
  52. filePathProxied == null ||
  53. originalName == null ||
  54. downloadPathProxied == null ||
  55. creator == null ||
  56. createdAt == null ||
  57. fileSize == null
  58. ) {
  59. return (
  60. <span className="text-muted">
  61. {t('rich_attachment.attachment_not_be_found')}
  62. </span>
  63. );
  64. }
  65. return (
  66. <div
  67. data-testid="rich-attachment"
  68. className={`${styles.attachment} d-inline-block`}
  69. >
  70. <div className="my-2 p-2 card">
  71. <div className="p-1 card-body d-flex align-items-center">
  72. <div className="me-2 px-0 d-flex align-items-center justify-content-center">
  73. <Image
  74. width={20}
  75. height={20}
  76. src="/images/icons/editor/attachment.svg"
  77. className="attachment-icon"
  78. alt="attachment icon"
  79. />
  80. </div>
  81. <div className="ps-0">
  82. <div className="d-inline-block">
  83. {/* Since we need to include the "referer" to view the attachment on the shared page */}
  84. {/* eslint-disable-next-line react/jsx-no-target-blank */}
  85. <a target="_blank" rel="noopener" href={filePathProxied}>
  86. {attachmentName || originalName}
  87. </a>
  88. <a
  89. className="ms-2 attachment-download"
  90. href={downloadPathProxied}
  91. >
  92. <span className="material-symbols-outlined">
  93. cloud_download
  94. </span>
  95. </a>
  96. {showTrashButton && (
  97. <button
  98. type="button"
  99. className="ml-2 text-danger attachment-delete d-share-link-none border-0 bg-transparent p-0"
  100. onClick={onClickTrashButtonHandler}
  101. >
  102. <span className="material-symbols-outlined">delete</span>
  103. </button>
  104. )}
  105. </div>
  106. <div className="d-flex align-items-center">
  107. <UserPicture user={creator} size="sm" />
  108. <span className="ms-2 text-muted">
  109. {new Date(createdAt).toLocaleString('en-US')}
  110. </span>
  111. <span className="ms-2 ps-2 border-start text-muted">
  112. {prettyBytes(fileSize)}
  113. </span>
  114. </div>
  115. </div>
  116. </div>
  117. </div>
  118. </div>
  119. );
  120. });
  121. RichAttachment.displayName = 'RichAttachment';