import { isMobile } from "react-device-detect"; import React, { useState, useCallback, useMemo, useEffect, useRef, } from "react"; import ViewerWrapper from "./sub-components/ViewerWrapper"; import { MediaViewerProps } from "./MediaViewer.props"; import { FileStatus } from "@docspace/common/constants"; import { isNullOrUndefined, KeyboardEventKeys, mapSupplied, mediaTypes, } from "./helpers"; import { getFileExtension } from "@docspace/common/utils"; import { getDesktopMediaContextModel, getMobileMediaContextModel, getPDFContextModel, } from "./helpers/contextModel"; import { checkDialogsOpen } from "../../utils/checkDialogsOpen"; function MediaViewer({ playlistPos, nextMedia, prevMedia, pluginContextMenuItems, setActiveFiles, ...props }: MediaViewerProps): JSX.Element { const TiffXMLHttpRequestRef = useRef(); const [title, setTitle] = useState(""); const [fileUrl, setFileUrl] = useState(() => { const { playlist, currentFileId } = props; const item = playlist.find( (file) => file.fileId.toString() === currentFileId.toString() ); return item?.src; }); const [targetFile, setTargetFile] = useState(() => { const { files, currentFileId } = props; return files.find((item) => item.id === currentFileId); }); const [isFavorite, setIsFavorite] = useState(() => { const { playlist } = props; return ( (playlist[playlistPos].fileStatus & FileStatus.IsFavorite) === FileStatus.IsFavorite ); }); useEffect(() => { const fileId = props.playlist[playlistPos]?.fileId; if (!isNullOrUndefined(fileId) && props.currentFileId !== fileId) { props.onChangeUrl(fileId); } }, [props.playlist.length]); useEffect(() => { return () => { props.onClose(); }; }, []); useEffect(() => { const { playlist, files, setBufferSelection } = props; const currentFile = playlist[playlistPos]; const currentFileId = playlist.length > 0 ? playlist.find((file) => file.id === playlistPos)?.fileId : 0; const targetFile = files.find((item) => item.id === currentFileId); if (targetFile) setBufferSelection(targetFile); const { src, title } = currentFile; const ext = getFileExtension(title); if (ext === ".tiff" || ext === ".tif") { fetchAndSetTiffDataURL(src); } }, []); useEffect(() => { const { playlist, onEmptyPlaylistError, files, setBufferSelection } = props; const { src, title, fileId } = playlist[playlistPos]; const ext = getFileExtension(title); if (!src) return onEmptyPlaylistError(); if (ext !== ".tif" && ext !== ".tiff" && src !== fileUrl) { TiffXMLHttpRequestRef.current?.abort(); setFileUrl(src); } if (ext === ".tiff" || ext === ".tif") { setFileUrl(undefined); fetchAndSetTiffDataURL(src); } const foundFile = files.find((file) => file.id === fileId); if (!isNullOrUndefined(foundFile)) { setTargetFile(foundFile); setBufferSelection(foundFile); } setTitle(title); setIsFavorite( (playlist[playlistPos].fileStatus & FileStatus.IsFavorite) === FileStatus.IsFavorite ); }, [props.playlist, props.files.length, props.currentFileId, playlistPos]); useEffect(() => { document.addEventListener("keydown", onKeydown); return () => { document.removeEventListener("keydown", onKeydown); TiffXMLHttpRequestRef.current?.abort(); }; }, [ props.playlist.length, props.files.length, playlistPos, props.deleteDialogVisible, ]); const getContextModel = () => { const { t, onClickDownloadAs, onClickLinkEdit, onClickDownload, onPreviewClick, onClickRename, onClickDelete, onShowInfoPanel, onMoveAction, onCopyAction, onDuplicate, onCopyLink, } = props; if (!targetFile) return []; const desktopModel = getDesktopMediaContextModel( t, targetFile, archiveRoom, { onClickDownload, onClickRename, onClickDelete, } ); const model = getMobileMediaContextModel(t, targetFile, { onShowInfoPanel, onClickDownload, onMoveAction, onCopyAction, onDuplicate, onClickRename, onClickDelete, }); if (isPdf) return getPDFContextModel(t, targetFile, { onClickDownloadAs, onMoveAction, onCopyAction, onClickRename, onDuplicate, onClickDelete, onClickDownload, onClickLinkEdit, onPreviewClick, onCopyLink, }); if (pluginContextMenuItems && pluginContextMenuItems.length > 0) { model.unshift({ key: "separator-plugin", isSeparator: true, disabled: false, }); pluginContextMenuItems.forEach((item) => { const onClick = async (): Promise => { props.onClose(); if (item.value.withActiveItem) setActiveFiles([targetFile.id]); await item.value.onClick(targetFile.id); if (item.value.withActiveItem) setActiveFiles([]); }; if ( item.value.fileType && item.value.fileType.includes("image") && !targetFile.viewAccessability.ImageView ) return; if ( item.value.fileType && item.value.fileType.includes("video") && !targetFile.viewAccessability.MediaView ) return; model.unshift({ id: item.key, key: item.key, disabled: false, ...item.value, onClick, }); desktopModel.unshift({ key: item.key, disabled: false, ...item.value, onClick, }); }); } return isMobile ? model : isImage && !isMobile ? desktopModel.filter((el) => el.key !== "download") : desktopModel; }; const canImageView = useCallback( (ext: string) => { const { extsImagePreviewed } = props; return extsImagePreviewed.indexOf(ext) != -1; }, [props.extsImagePreviewed] ); const canPlay = useCallback( (fileTitle: string) => { const { extsMediaPreviewed } = props; const ext = fileTitle[0] === "." ? fileTitle : getFileExtension(fileTitle); const supply = mapSupplied[ext]; return !!supply && extsMediaPreviewed.indexOf(ext) != -1; }, [props.extsMediaPreviewed] ); let lastRemovedFileId: null | number = null; const onDelete = () => { const { playlist, onDelete } = props; let currentFileId = playlist.find( (file) => file.id === playlistPos )?.fileId; if (currentFileId === lastRemovedFileId) return; const canDelete = targetFile?.security?.Delete; if (!canDelete) return; if (!isNullOrUndefined(currentFileId)) { onDelete(currentFileId); lastRemovedFileId = currentFileId; } }; const onDownload = () => { const { playlist, onDownload } = props; if (!targetFile?.security.Download) return; let currentFileId = playlist.find( (file) => file.id === playlistPos )?.fileId; if (!isNullOrUndefined(currentFileId)) onDownload(currentFileId); }; const onKeydown = (event: KeyboardEvent) => { const { code, ctrlKey } = event; const someDialogIsOpen = checkDialogsOpen(); if (props.deleteDialogVisible || someDialogIsOpen) return; if (code in KeyboardEventKeys) { const includesKeyboardCode = [ KeyboardEventKeys.KeyS, KeyboardEventKeys.Numpad1, KeyboardEventKeys.Digit1, KeyboardEventKeys.Space, ].includes(code as KeyboardEventKeys); if (!includesKeyboardCode || ctrlKey) event.preventDefault(); } switch (code) { case KeyboardEventKeys.ArrowLeft: if (document.fullscreenElement) return; if (!ctrlKey) prevMedia(); break; case KeyboardEventKeys.ArrowRight: if (document.fullscreenElement) return; if (!ctrlKey) nextMedia(); break; case KeyboardEventKeys.Escape: if (!props.deleteDialogVisible) props.onClose(); break; case KeyboardEventKeys.KeyS: if (ctrlKey) onDownload(); break; case KeyboardEventKeys.Delete: onDelete(); break; default: break; } }; const onClose = useCallback(() => { props.onClose(); }, [props.onClose]); const fetchAndSetTiffDataURL = useCallback((src: string) => { if (!window.Tiff) return; TiffXMLHttpRequestRef.current?.abort(); const xhr = new XMLHttpRequest(); TiffXMLHttpRequestRef.current = xhr; xhr.responseType = "arraybuffer"; xhr.open("GET", src); xhr.onload = function () { try { const tiff = new window.Tiff({ buffer: xhr.response }); const dataUrl = tiff.toDataURL(); setFileUrl(dataUrl); } catch (e) { console.log(e); } }; xhr.send(); }, []); const onSetSelectionFile = useCallback(() => { props.setBufferSelection(targetFile); }, [targetFile]); const ext = getFileExtension(title); const audioIcon = useMemo(() => props.getIcon(96, ext), [ext]); const headerIcon = useMemo(() => props.getIcon(24, ext), [ext]); let isVideo = false; let isAudio = false; let canOpen = true; let isImage = false; let isPdf = false; const archiveRoom = props.archiveRoomsId === targetFile?.rootFolderId || (!targetFile?.security?.Rename && !targetFile?.security?.Delete); if (canPlay(ext) && canImageView(ext)) { canOpen = false; props.onError?.(); } if (canImageView(ext)) { isImage = true; } else { isImage = false; isVideo = mapSupplied[ext] ? mapSupplied[ext]?.type == mediaTypes.video : false; isAudio = mapSupplied[ext] ? mapSupplied[ext]?.type == mediaTypes.audio : false; isPdf = mapSupplied[ext] ? mapSupplied[ext]?.type == mediaTypes.pdf : false; } return ( <> {canOpen && ( )} ); } export default MediaViewer;