import { CSSProperties, MouseEvent, MutableRefObject, useRef } from 'react'; import { isUndefined } from 'lodash'; import { useDispatch } from 'react-redux'; import useUnmount from 'react-use/lib/useUnmount'; import styled from 'styled-components'; import { useDisableDrag } from '../../hooks/useDisableDrag'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch'; import { updateLightBoxOptions } from '../../state/ducks/modalDialog'; import * as MIME from '../../types/MIME'; import { assertUnreachable } from '../../types/sqlSharedTypes'; import { GoogleChrome } from '../../util'; import { Flex } from '../basic/Flex'; import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; const colorSVG = (url: string, color: string) => { return { WebkitMask: `url(${url}) no-repeat center`, WebkitMaskSize: '100%', backgroundColor: color, }; }; type Props = { contentType: MIME.MIMEType | undefined; objectURL: string; caption?: string; onNext?: () => void; onPrevious?: () => void; onSave?: () => void; onClose?: () => void; }; const CONTROLS_WIDTH = 50; const CONTROLS_SPACING = 10; const styles = { container: { display: 'flex', flexDirection: 'column', position: 'fixed', width: '100vw', height: '100vh', left: 0, zIndex: 150, // modals are 100 right: 0, top: 0, bottom: 0, backgroundColor: 'var(--lightbox-background-color)', } as CSSProperties, mainContainer: { display: 'flex', flexDirection: 'row', flexGrow: 1, paddingTop: 40, paddingLeft: 40, paddingRight: 40, paddingBottom: 0, minHeight: 0, overflow: 'hidden', minWidth: 0, } as CSSProperties, objectContainer: { position: 'relative', flexGrow: 1, display: 'inline-flex', justifyContent: 'center', } as CSSProperties, objectParentContainer: { flexGrow: 1, textAlign: 'center' as const, margin: 'auto', }, object: { flexGrow: 1, flexShrink: 0, maxWidth: '80vw', maxHeight: '80vh', objectFit: 'contain', } as CSSProperties, caption: { position: 'absolute', bottom: 0, left: 0, right: 0, textAlign: 'center', color: 'black', padding: '1em', paddingLeft: '3em', paddingRight: '3em', backgroundColor: 'var(--lightbox-caption-background-color)', } as CSSProperties, controlsOffsetPlaceholder: { width: CONTROLS_WIDTH, marginRight: CONTROLS_SPACING, flexShrink: 0, }, controls: { width: CONTROLS_WIDTH, flexShrink: 0, display: 'flex', flexDirection: 'column', marginLeft: CONTROLS_SPACING, justifyContent: 'space-between', } as CSSProperties, navigationContainer: { flexShrink: 0, display: 'flex', flexDirection: 'row', justifyContent: 'center', padding: 10, height: '50px', // force it so the buttons stick to the bottom } as CSSProperties, saveButton: { marginTop: 10, }, iconButtonPlaceholder: { // Dimensions match `.iconButton`: display: 'inline-block', width: 30, height: 30, }, }; const StyledIconButton = styled.div` .session-icon-button { opacity: 0.4; transition: opacity var(--default-duration); &:hover { opacity: 1; } } `; interface IconButtonProps { onClick?: () => void; style?: CSSProperties; type: 'save' | 'close' | 'previous' | 'next'; } const IconButton = ({ onClick, type }: IconButtonProps) => { const clickHandler = (): void => { if (!onClick) { return; } onClick(); }; let iconRotation = 0; let iconType: SessionIconType = 'chevron'; let iconSize: SessionIconSize = 'huge'; switch (type) { case 'next': iconRotation = 270; break; case 'previous': iconRotation = 90; break; case 'close': iconType = 'exit'; break; case 'save': iconType = 'save'; iconSize = 'huge2'; break; default: assertUnreachable(type, `Invalid button type: ${type}`); } return ( ); }; const IconButtonPlaceholder = () =>
; const Icon = ({ onClick, url, }: { onClick?: (event: MouseEvent) => void; url: string; }) => (
); export const LightboxObject = ({ objectURL, contentType, renderedRef, onObjectClick, }: { objectURL: string; contentType: MIME.MIMEType; renderedRef: MutableRefObject; onObjectClick: (event: any) => any; }) => { const { urlToLoad } = useEncryptedFileFetch(objectURL, contentType, false); const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType); // auto play video on showing a video attachment useUnmount(() => { if (!renderedRef?.current) { return; } renderedRef.current.pause.pause(); }); const disableDrag = useDisableDrag(); if (isImageTypeSupported) { return ( {window.i18n('lightboxImageAlt')} ); } const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType); if (isVideoTypeSupported) { if (urlToLoad) { if (renderedRef?.current?.paused) { void renderedRef?.current?.play(); } } return ( ); } const isUnsupportedImageType = !isImageTypeSupported && MIME.isImage(contentType); const isUnsupportedVideoType = !isVideoTypeSupported && MIME.isVideo(contentType); if (isUnsupportedImageType || isUnsupportedVideoType) { const iconUrl = isUnsupportedVideoType ? 'images/video.svg' : 'images/image.svg'; return ; } window.log.info('Lightbox: Unexpected content type', { contentType }); return ; }; export const Lightbox = (props: Props) => { const renderedRef = useRef(null); const dispatch = useDispatch(); const { caption, contentType, objectURL, onNext, onPrevious, onSave, onClose } = props; const onObjectClick = (event: any) => { event.stopPropagation(); dispatch(updateLightBoxOptions(null)); }; const handleClose = () => { if (onClose) { onClose(); } dispatch(updateLightBoxOptions(null)); }; const onContainerClick = (event: MouseEvent) => { if (renderedRef && event.target === renderedRef.current) { return; } handleClose(); }; return (
{!isUndefined(contentType) ? ( ) : null} {caption ?
{caption}
: null}
{onSave ? : null}
{onPrevious ? ( ) : ( )} {onNext ? : }
); };