import React, { useState, useCallback } from 'react'; import { IAttachmentHasId } from '@growi/core'; import { Property } from 'csstype'; import Carousel, { Modal, ModalGateway } from 'react-images'; import { RefsContext } from './util/refs-context'; type Props = { attachments: IAttachmentHasId[], refsContext: RefsContext, }; /** * 1. when 'fileFormat' is image, render Attachment as an image * 2. when 'fileFormat' is not image, render Attachment as an Attachment component */ export const ExtractedAttachments = React.memo(({ attachments, refsContext, }: Props): JSX.Element => { const [showCarousel, setShowCarousel] = useState(false); const [currentIndex, setCurrentIndex] = useState(null); const imageClickedHandler = useCallback((index: number) => { setShowCarousel(true); setCurrentIndex(index); }, []); const getAttachmentsFilteredByFormat = useCallback(() => { return attachments .filter(attachment => attachment.fileFormat.startsWith('image/')); }, []); const getClassesAndStylesForNonGrid = useCallback(() => { const { options } = refsContext; const width = options?.width; const height = options?.height; const maxWidth = options?.maxWidth; const maxHeight = options?.maxHeight; const display = options?.display || 'block'; const containerStyles = { width, height, maxWidth, maxHeight, display, }; const imageClasses = []; const imageStyles = { width, height, maxWidth, maxHeight, }; return { containerStyles, imageClasses, imageStyles, }; }, []); const getClassesAndStylesForGrid = useCallback(() => { const { options } = refsContext; const maxWidth = options?.maxWidth; const maxHeight = options?.maxHeight; const containerStyles = { width: refsContext.getOptGridWidth(), height: refsContext.getOptGridHeight(), maxWidth, maxHeight, }; const imageClasses = ['w-100', 'h-100']; const imageStyles = { objectFit: 'cover' as Property.ObjectFit, maxWidth, maxHeight, }; return { containerStyles, imageClasses, imageStyles, }; }, []); /** * wrapper method for getClassesAndStylesForGrid/getClassesAndStylesForNonGrid */ const getClassesAndStyles = useCallback(() => { const { options } = refsContext; return (options?.grid != null) ? getClassesAndStylesForGrid() : getClassesAndStylesForNonGrid(); }, []); const renderExtractedImage = useCallback((attachment: IAttachmentHasId, index: number) => { const { options } = refsContext; // determine alt let alt = refsContext.isSingle ? options?.alt : undefined; // use only when single mode alt = alt || attachment.originalName; // use 'originalName' if options.alt is not specified // get styles const { containerStyles, imageClasses, imageStyles, } = getClassesAndStyles(); // carousel settings let onClick; if (options?.noCarousel == null) { // pointer cursor Object.assign(containerStyles, { cursor: 'pointer' }); // set click handler onClick = () => { imageClickedHandler(index); }; } return (
{alt}
); }, []); const renderCarousel = useCallback(() => { const { options } = refsContext; const withCarousel = options?.noCarousel == null; const images = getAttachmentsFilteredByFormat() .map((attachment) => { return { src: attachment.filePathProxied }; }); // overwrite react-images modal styles const zIndex = 1030; // > grw-navbar const modalStyles = { blanket: (styleObj) => { return Object.assign(styleObj, { zIndex }); }, positioner: (styleObj) => { return Object.assign(styleObj, { zIndex }); }, }; return ( { withCarousel && showCarousel && ( { setShowCarousel(false) }}> ) } ); }, []); const { options } = refsContext; const grid = options?.grid; const gridGap = options?.gridGap; const styles = {}; // Grid mode if (grid != null) { const gridTemplateColumns = (refsContext.isOptGridColumnEnabled()) ? `repeat(${refsContext.getOptGridColumnsNum()}, 1fr)` : `repeat(auto-fill, ${refsContext.getOptGridWidth()})`; Object.assign(styles, { display: 'grid', gridTemplateColumns, gridAutoRows: '1fr', gridGap, }); } const contents = getAttachmentsFilteredByFormat() .map((attachment, index) => renderExtractedImage(attachment, index)); return (
{contents}
{ renderCarousel() }
); });