import React from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; import Text from "@appserver/components/text"; import MediaDeleteIcon from "../../../../public/images/media.delete.react.svg"; import MediaDownloadIcon from "../../../../public/images/media.download.react.svg"; import ImageViewer from "./sub-components/image-viewer"; import VideoViewer from "./sub-components/video-viewer"; import MediaScrollButton from "./sub-components/scroll-button"; import ControlBtn from "./sub-components/control-btn"; import StyledMediaViewer from "./StyledMediaViewer"; import equal from "fast-deep-equal/react"; import Hammer from "hammerjs"; import IconButton from "@appserver/components/icon-button"; import commonIconsStyles from "@appserver/components/utils/common-icons-style"; import { isDesktop } from "react-device-detect"; //import history from "@appserver/common/history"; const StyledVideoViewer = styled(VideoViewer)` z-index: 301; `; const mediaTypes = Object.freeze({ audio: 1, video: 2, }); const ButtonKeys = Object.freeze({ leftArrow: 37, rightArrow: 39, upArrow: 38, downArrow: 40, esc: 27, ctr: 17, one: 49, del: 46, s: 83, }); const StyledMediaDeleteIcon = styled(MediaDeleteIcon)` ${commonIconsStyles} `; const StyledMediaDownloadIcon = styled(MediaDownloadIcon)` ${commonIconsStyles} `; let ctrIsPressed = false; class MediaViewer extends React.Component { constructor(props) { super(props); const item = props.playlist.find( (file) => file.fileId === props.currentFileId ); const playlistPos = item ? item.id : 0; this.state = { visible: props.visible, allowConvert: true, playlist: props.playlist, playlistPos, fileUrl: item.src, }; this.detailsContainer = React.createRef(); this.viewerToolbox = React.createRef(); } updateHammer() { const { playlistPos, playlist } = this.state; const currentFile = playlist[playlistPos]; const { title } = currentFile; const ext = this.getFileExtension(title); const _this = this; if (this.hammer) { this.hammer.off("swipeleft", this.nextMedia); this.hammer.off("swiperight", this.prevMedia); this.hammer.off("pinchout", this.prevMedia); this.hammer.off("pinchin", this.prevMedia); this.hammer.off("pinchend", this.prevMedia); this.hammer.off("doubletap", this.prevMedia); } this.hammer = null; setTimeout(function () { try { if (_this.canImageView(ext)) { const pinch = new Hammer.Pinch(); _this.hammer = Hammer( document.getElementsByClassName("react-viewer-canvas")[0] ); _this.hammer.add([pinch]); _this.hammer.on("pinchout", _this.handleZoomOut); _this.hammer.on("pinchin", _this.handleZoomIn); _this.hammer.on("pinchend", _this.handleZoomEnd); _this.hammer.on("doubletap", _this.doubleTap); } else if ( _this.mapSupplied[ext] && (_this.mapSupplied[ext].type == mediaTypes.video || _this.mapSupplied[ext].type == mediaTypes.audio) ) { _this.hammer = Hammer( document.getElementsByClassName("videoViewerOverlay")[0] ); } if (_this.hammer && !isDesktop) { _this.hammer.on("swipeleft", _this.nextMedia); _this.hammer.on("swiperight", _this.prevMedia); } } catch (ex) { //console.error("MediaViewer updateHammer", ex); this.hammer = null; } }, 500); } componentDidUpdate(prevProps, prevState) { const { visible, playlist, currentFileId, onEmptyPlaylistError, } = this.props; const { playlistPos, fileUrl } = this.state; const src = playlist[playlistPos]?.src; const title = playlist[playlistPos]?.title; const ext = this.getFileExtension(title); if (visible !== prevProps.visible) { const newPlaylistPos = playlist.length > 0 ? playlist.find((file) => file.fileId === currentFileId).id : 0; this.setState({ visible: visible, playlistPos: newPlaylistPos, }); } if ( src && src !== fileUrl && playlistPos === prevState.playlistPos && ext !== ".tif" && ext !== ".tiff" ) { this.setState({ fileUrl: src }); } if ( visible && visible === prevProps.visible && playlistPos !== prevState.playlistPos ) { if (ext === ".tiff" || ext === ".tif") { this.getTiffDataURL(src); } else { this.setState({ fileUrl: src }); } } if ( visible && visible === prevProps.visible && !equal(playlist, prevProps.playlist) ) { if (playlist.length > 0) { this.updateHammer(); const newPlaylistPos = playlistPos < playlist.length ? playlistPos : 0; this.setState({ playlist: playlist, playlistPos: newPlaylistPos, }); } else { onEmptyPlaylistError(); this.setState({ visible: false, }); } } else if (!equal(playlist, prevProps.playlist)) { this.setState({ playlist: playlist, }); } } componentDidMount() { const { playlist } = this.props; const { playlistPos } = this.state; const currentFile = playlist[playlistPos]; const { src, title } = currentFile; const ext = this.getFileExtension(title); if (ext === ".tiff" || ext === ".tif") { this.getTiffDataURL(src); } this.updateHammer(); document.addEventListener("keydown", this.onKeydown, false); document.addEventListener("keyup", this.onKeyup, false); } componentWillUnmount() { if (this.hammer) { this.hammer.off("swipeleft", this.nextMedia); this.hammer.off("swiperight", this.prevMedia); this.hammer.off("pinchout", this.prevMedia); this.hammer.off("pinchin", this.prevMedia); this.hammer.off("pinchend", this.prevMedia); this.hammer.off("doubletap", this.prevMedia); } document.removeEventListener("keydown", this.onKeydown, false); document.removeEventListener("keyup", this.onKeyup, false); //this.onClose(); } mapSupplied = { ".aac": { supply: "m4a", type: mediaTypes.audio }, ".flac": { supply: "mp3", type: mediaTypes.audio }, ".m4a": { supply: "m4a", type: mediaTypes.audio }, ".mp3": { supply: "mp3", type: mediaTypes.audio }, ".oga": { supply: "oga", type: mediaTypes.audio }, ".ogg": { supply: "oga", type: mediaTypes.audio }, ".wav": { supply: "wav", type: mediaTypes.audio }, ".f4v": { supply: "m4v", type: mediaTypes.video }, ".m4v": { supply: "m4v", type: mediaTypes.video }, ".mov": { supply: "m4v", type: mediaTypes.video }, ".mp4": { supply: "m4v", type: mediaTypes.video }, ".ogv": { supply: "ogv", type: mediaTypes.video }, ".webm": { supply: "webmv", type: mediaTypes.video }, ".wmv": { supply: "m4v", type: mediaTypes.video, convertable: true }, ".avi": { supply: "m4v", type: mediaTypes.video, convertable: true }, ".mpeg": { supply: "m4v", type: mediaTypes.video, convertable: true }, ".mpg": { supply: "m4v", type: mediaTypes.video, convertable: true }, }; canImageView = function (ext) { const { extsImagePreviewed } = this.props; return extsImagePreviewed.indexOf(ext) != -1; }; canPlay = (fileTitle, allowConvert) => { const { extsMediaPreviewed } = this.props; const ext = fileTitle[0] === "." ? fileTitle : this.getFileExtension(fileTitle); const supply = this.mapSupplied[ext]; const canConvert = allowConvert || this.props.allowConvert; return ( !!supply && extsMediaPreviewed.indexOf(ext) != -1 && (!supply.convertable || canConvert) ); }; getFileExtension = (fileTitle) => { if (!fileTitle) { return ""; } fileTitle = fileTitle.trim(); const posExt = fileTitle.lastIndexOf("."); return 0 <= posExt ? fileTitle.substring(posExt).trim().toLowerCase() : ""; }; zoom = 1; handleZoomEnd = () => { this.zoom = 1; }; handleZoomIn = (e) => { if (this.zoom - e.scale > 0.1) { this.zoom = e.scale; document.querySelector('li[data-key="zoomOut"]').click(); } }; handleZoomOut = (e) => { if (e.scale - this.zoom > 0.3) { this.zoom = e.scale; document.querySelector('li[data-key="zoomIn"]').click(); } }; doubleTap = () => { document.querySelector('li[data-key="zoomIn"]').click(); }; prevMedia = () => { const { playlistPos, playlist } = this.state; let currentPlaylistPos = playlistPos; currentPlaylistPos--; if (currentPlaylistPos < 0) currentPlaylistPos = playlist.length - 1; this.setState({ playlistPos: currentPlaylistPos, }); const id = playlist[currentPlaylistPos].fileId; const url = "/products/files/#preview/" + id; history.pushState({ id, type: "image" }, null, url); }; nextMedia = () => { const { playlistPos, playlist } = this.state; let currentPlaylistPos = playlistPos; currentPlaylistPos = (currentPlaylistPos + 1) % playlist.length; this.setState({ playlistPos: currentPlaylistPos, }); const id = playlist[currentPlaylistPos].fileId; const url = "/products/files/#preview/" + id; history.pushState({ id, type: "image" }, null, url); console.log("history.state", history.state); console.log("history.length", history.length); }; getOffset = () => { if (this.detailsContainer.current && this.viewerToolbox.current) { return ( this.detailsContainer.current.offsetHeight + this.viewerToolbox.current.offsetHeight ); } else { return 0; } }; onDelete = () => { const { playlist, playlistPos } = this.state; let currentFileId = playlist.length > 0 ? playlist.find((file) => file.id === playlistPos).fileId : 0; this.props.onDelete && this.props.onDelete(currentFileId); }; onDownload = () => { const { playlist, playlistPos } = this.state; let currentFileId = playlist.length > 0 ? playlist.find((file) => file.id === playlistPos).fileId : 0; this.props.onDownload && this.props.onDownload(currentFileId); }; onKeyup = (e) => { if (ButtonKeys.ctr === e.keyCode) { ctrIsPressed = false; } }; onKeydown = (e) => { let isActionKey = false; for (let key in ButtonKeys) { if (ButtonKeys[key] === e.keyCode) { e.preventDefault(); isActionKey = true; } } if (isActionKey) { switch (e.keyCode) { case ButtonKeys.leftArrow: ctrIsPressed ? document.getElementsByClassName("iconContainer rotateLeft") .length > 0 && document .getElementsByClassName("iconContainer rotateLeft")[0] .click() : this.prevMedia(); break; case ButtonKeys.rightArrow: ctrIsPressed ? document.getElementsByClassName("iconContainer rotateRight") .length > 0 && document .getElementsByClassName("iconContainer rotateRight")[0] .click() : this.nextMedia(); break; case ButtonKeys.esc: if (!this.props.deleteDialogVisible) this.props.onClose(); break; case ButtonKeys.upArrow: document.getElementsByClassName("iconContainer zoomIn").length > 0 && document.getElementsByClassName("iconContainer zoomIn")[0].click(); break; case ButtonKeys.downArrow: document.getElementsByClassName("iconContainer zoomOut").length > 0 && document.getElementsByClassName("iconContainer zoomOut")[0].click(); break; case ButtonKeys.ctr: ctrIsPressed = true; break; case ButtonKeys.s: if (ctrIsPressed) this.onDownload(); break; case ButtonKeys.one: ctrIsPressed && document.getElementsByClassName("iconContainer reset").length > 0 && document.getElementsByClassName("iconContainer reset")[0].click(); break; case ButtonKeys.del: this.onDelete(); break; default: break; } } }; onClose = () => { console.log("onClose"); this.props.onClose(); this.setState({ visible: false }); }; getTiffDataURL = (src) => { if (!window.Tiff) return; const _this = this; const xhr = new XMLHttpRequest(); xhr.responseType = "arraybuffer"; xhr.open("GET", src); xhr.onload = function () { try { const tiff = new window.Tiff({ buffer: xhr.response }); const dataUrl = tiff.toDataURL(); _this.setState({ fileUrl: dataUrl }); } catch (e) { console.log(e); } }; xhr.send(); }; render() { const { playlistPos, playlist, visible, fileUrl } = this.state; const { onClose, userAccess, canDelete, canDownload, errorLabel, previewFile, } = this.props; const currentFileId = playlist.length > 0 ? playlist.find((file) => file.id === playlistPos).fileId : 0; const currentFile = playlist[playlistPos]; const { title } = currentFile; let isImage = false; let isVideo = false; let canOpen = true; const ext = this.getFileExtension(title); if (!this.canPlay(ext) && !this.canImageView(ext)) { canOpen = false; this.props.onError && this.props.onError(); } if (this.canImageView(ext)) { isImage = true; } else { console.log("isVideo"); isImage = false; isVideo = this.mapSupplied[ext] ? this.mapSupplied[ext].type == mediaTypes.video : false; } // TODO: rewrite with fileURL /*if (this.mapSupplied[ext]) if (!isImage && this.mapSupplied[ext].convertable && !src.includes("#")) { src += (src.includes("?") ? "&" : "?") + "convpreview=true"; }*/ return (
{!isImage && ( <> )}
{title}
{canOpen && (isImage ? ( ) : ( ))}
{!isImage && ( {canDelete(currentFileId) && !previewFile && (
)} {canDownload(currentFileId) && (
)}
)}
); } } MediaViewer.propTypes = { allowConvert: PropTypes.bool, visible: PropTypes.bool, currentFileId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), playlist: PropTypes.arrayOf(PropTypes.object), extsImagePreviewed: PropTypes.arrayOf(PropTypes.string), extsMediaPreviewed: PropTypes.arrayOf(PropTypes.string), onError: PropTypes.func, canDelete: PropTypes.func, canDownload: PropTypes.func, onDelete: PropTypes.func, onDownload: PropTypes.func, onClose: PropTypes.func, onEmptyPlaylistError: PropTypes.func, deleteDialogVisible: PropTypes.bool, errorLabel: PropTypes.string, previewFile: PropTypes.bool, }; MediaViewer.defaultProps = { currentFileId: 0, visible: false, allowConvert: true, canDelete: () => { return true; }, canDownload: () => { return true; }, previewFile: false, }; export default MediaViewer;