entity-attachments.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { View, Row, Pressable } from 'app/design/view';
  2. import { useState } from 'react';
  3. import Image from 'app/ui/atoms/image';
  4. import { Modal } from 'app/design/controls'
  5. import Video from 'app/ui/atoms/video';
  6. import VideoThumb from 'app/ui/atoms/video-thumb';
  7. import FileCard from 'app/ui/molecules/content/file-card';
  8. import { FeedbackHaptics } from 'app/lib/util';
  9. import { BlockWrapper } from 'app/components/block-wrapper'
  10. import { useTranslation } from 'react-i18next'
  11. const AttachmentContainer = ({ children }) => (
  12. <View className="p-1 w-1/4">
  13. <View className="aspect-video rounded-lg shadow-btn-outline dark:shadow-btn-outline-deep items-center justify-center">
  14. <View className="h-full w-full overflow-hidden rounded-lg">
  15. {children}
  16. </View>
  17. </View>
  18. </View>
  19. );
  20. // API `ext` can be a mime subtype (e.g. "x-zip-compressed"), so FileCard prefers the name's extension.
  21. const FileItem = ({ data }) => (
  22. <View className="p-1 w-full">
  23. <FileCard href={data.url} name={data.file_name} size={data.size} ext={data.ext} />
  24. </View>
  25. );
  26. const ImageItem = ({ data, handleShowImage }) => (
  27. <AttachmentContainer>
  28. <Pressable className="w-full h-full" onPress={() => handleShowImage({ ...data, type: 'image' })}>
  29. <Image sizes="160px" src={data.src} alt='' view="cover" />
  30. </Pressable>
  31. </AttachmentContainer>
  32. );
  33. const VideoItem = ({ data, handleShowImage }) => (
  34. <AttachmentContainer>
  35. <Pressable className="w-full h-full" onPress={() => handleShowImage({ ...data, type: 'video' })}>
  36. {/* Poster only: decoding a frame from the video itself downloaded part of every file. */}
  37. <VideoThumb src={data.src} poster={data.src_poster || data.poster} sizes="(max-width:768px) 25vw, 224px" />
  38. </Pressable>
  39. </AttachmentContainer>
  40. );
  41. export default function ElementEntityAttachments({ data, blockWrapperProps }) {
  42. const { t } = useTranslation();
  43. const [showImage, setShowImage] = useState(false);
  44. const handleShowImage = (img) => {
  45. FeedbackHaptics('Medium');
  46. setShowImage(img);
  47. }
  48. // Media go to the thumbnail grid, everything else to a file list below it.
  49. const aMedia = [];
  50. const aFiles = [];
  51. data.forEach((item, index) => {
  52. switch(item.type) {
  53. case 'image':
  54. aMedia.push(<ImageItem data={item.data} key={`link-${index}`} handleShowImage={handleShowImage} />);
  55. break;
  56. case 'video':
  57. aMedia.push(<VideoItem data={item.data} key={`link-${index}`} handleShowImage={handleShowImage} />);
  58. break;
  59. default:
  60. aFiles.push(<FileItem data={item} key={`link-${index}`} />);
  61. }
  62. });
  63. if (aMedia.length === 0 && aFiles.length === 0) {
  64. return null;
  65. }
  66. return (
  67. <BlockWrapper {...blockWrapperProps}>
  68. <Modal id={'file-preview'} title={t('Media')} onVisible={!!showImage} onClose={() => {setShowImage(null)}}>
  69. {showImage && (
  70. // Real proportions from API width/height; contain so nothing is cropped even when capped by max-h.
  71. <View className="w-full max-h-[75vh]" style={{ aspectRatio: showImage.width && showImage.height ? showImage.width / showImage.height : 16 / 9 }}>
  72. {showImage.type == 'image' && <Image src={showImage.src} alt='' view="cover" contentFit="contain" />}
  73. {showImage.type == 'video' && <Video fill controls autoplay src={showImage.src} poster={showImage.src_poster || showImage.poster} />}
  74. </View>
  75. )}
  76. </Modal>
  77. {aMedia.length > 0 && (
  78. // Stretched with -mx-1 so the tiles' p-1 gutter doesn't inset the outer edges from the post text.
  79. <Row className="relative flex-wrap -mx-1 max-w-4xl">
  80. {aMedia}
  81. </Row>
  82. )}
  83. {aFiles.length > 0 && (
  84. <Row className={`flex-wrap -mx-1 max-w-4xl${aMedia.length > 0 ? ' mt-2' : ''}`}>
  85. {aFiles}
  86. </Row>
  87. )}
  88. </BlockWrapper>
  89. );
  90. }