DocSpace-buildtools/packages/asc-web-common/components/MediaViewer/MediaViewer.js

547 lines
16 KiB
JavaScript
Raw Normal View History

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';
2020-04-19 20:06:17 +00:00
const StyledVideoViewer = styled(VideoViewer)`
2021-05-06 09:52:19 +00:00
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,
2021-08-10 13:12:51 +00:00
s: 83,
});
const StyledMediaDeleteIcon = styled(MediaDeleteIcon)`
${commonIconsStyles}
`;
const StyledMediaDownloadIcon = styled(MediaDownloadIcon)`
${commonIconsStyles}
`;
let ctrIsPressed = false;
2020-04-19 20:06:17 +00:00
class MediaViewer extends React.Component {
constructor(props) {
super(props);
const item = props.playlist.find((file) => file.fileId === props.currentFileId);
2021-03-22 16:21:36 +00:00
const playlistPos = item ? item.id : 0;
this.state = {
visible: props.visible,
allowConvert: true,
playlist: props.playlist,
2021-03-22 16:21:36 +00:00
playlistPos,
2021-08-19 02:23:50 +00:00
fileUrl: item.src,
};
this.detailsContainer = React.createRef();
this.viewerToolbox = React.createRef();
}
updateHammer() {
2021-08-19 01:10:32 +00:00
const { playlistPos, playlist } = this.state;
2021-08-19 02:23:50 +00:00
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);
2020-07-02 08:02:50 +00:00
}
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]);
2020-07-02 08:02:50 +00:00
}
if (_this.hammer && !isDesktop) {
_this.hammer.on('swipeleft', _this.nextMedia);
_this.hammer.on('swiperight', _this.prevMedia);
2020-07-02 08:02:50 +00:00
}
} catch (ex) {
//console.error("MediaViewer updateHammer", ex);
this.hammer = null;
}
}, 500);
}
2021-08-19 02:23:50 +00:00
componentDidUpdate(prevProps, prevState) {
const { visible, playlist, currentFileId, onEmptyPlaylistError } = this.props;
2021-08-19 01:10:32 +00:00
const { playlistPos, fileUrl } = this.state;
const src = playlist[playlistPos]?.src;
2021-10-12 13:16:40 +00:00
const title = playlist[playlistPos]?.title;
const ext = this.getFileExtension(title);
2021-08-19 01:10:32 +00:00
if (visible !== prevProps.visible) {
const newPlaylistPos =
playlist.length > 0 ? playlist.find((file) => file.fileId === currentFileId).id : 0;
2021-08-19 01:10:32 +00:00
this.setState({
2021-08-19 01:10:32 +00:00
visible: visible,
playlistPos: newPlaylistPos,
});
2020-07-02 08:02:50 +00:00
}
2021-08-19 01:10:32 +00:00
2021-10-12 13:16:40 +00:00
if (
src &&
src !== fileUrl &&
playlistPos === prevState.playlistPos &&
ext !== '.tif' &&
ext !== '.tiff'
2021-10-12 13:16:40 +00:00
) {
this.setState({ fileUrl: src });
}
if (visible && visible === prevProps.visible && playlistPos !== prevState.playlistPos) {
if (ext === '.tiff' || ext === '.tif') {
2021-08-19 10:30:42 +00:00
this.getTiffDataURL(src);
} else {
this.setState({ fileUrl: src });
}
2021-08-19 02:23:50 +00:00
}
if (visible && visible === prevProps.visible && !equal(playlist, prevProps.playlist)) {
2021-08-19 01:10:32 +00:00
if (playlist.length > 0) {
this.updateHammer();
2021-08-19 01:10:32 +00:00
const newPlaylistPos = playlistPos < playlist.length ? playlistPos : 0;
this.setState({
2021-08-19 01:10:32 +00:00
playlist: playlist,
playlistPos: newPlaylistPos,
});
} else {
2021-08-19 01:10:32 +00:00
onEmptyPlaylistError();
this.setState({
visible: false,
});
}
2021-08-19 01:10:32 +00:00
} else if (!equal(playlist, prevProps.playlist)) {
this.setState({
2021-08-19 01:10:32 +00:00
playlist: playlist,
});
2020-05-05 13:15:33 +00:00
}
}
componentDidMount() {
2021-08-19 10:30:42 +00:00
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') {
2021-08-19 10:30:42 +00:00
this.getTiffDataURL(src);
}
this.updateHammer();
document.addEventListener('keydown', this.onKeydown, false);
document.addEventListener('keyup', this.onKeyup, false);
}
componentWillUnmount() {
2021-05-06 09:52:19 +00:00
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);
2021-05-06 09:52:19 +00:00
}
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) {
2021-08-19 01:10:32 +00:00
const { extsImagePreviewed } = this.props;
return extsImagePreviewed.indexOf(ext) != -1;
};
2021-08-19 01:10:32 +00:00
canPlay = (fileTitle, allowConvert) => {
2021-08-19 01:10:32 +00:00
const { extsMediaPreviewed } = this.props;
const ext = fileTitle[0] === '.' ? fileTitle : this.getFileExtension(fileTitle);
2021-08-19 01:10:32 +00:00
const supply = this.mapSupplied[ext];
2021-08-19 01:10:32 +00:00
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;
2021-08-19 01:10:32 +00:00
handleZoomEnd = () => {
this.zoom = 1;
};
2021-08-19 01:10:32 +00:00
handleZoomIn = (e) => {
if (this.zoom - e.scale > 0.1) {
this.zoom = e.scale;
document.querySelector('li[data-key="zoomOut"]').click();
}
};
2021-08-19 01:10:32 +00:00
handleZoomOut = (e) => {
if (e.scale - this.zoom > 0.3) {
this.zoom = e.scale;
document.querySelector('li[data-key="zoomIn"]').click();
}
};
2021-08-19 01:10:32 +00:00
doubleTap = () => {
document.querySelector('li[data-key="zoomIn"]').click();
};
2021-08-19 01:10:32 +00:00
prevMedia = () => {
2021-08-19 01:10:32 +00:00
const { playlistPos, playlist } = this.state;
let currentPlaylistPos = playlistPos;
currentPlaylistPos--;
2021-08-19 01:10:32 +00:00
if (currentPlaylistPos < 0) currentPlaylistPos = playlist.length - 1;
this.setState({
playlistPos: currentPlaylistPos,
});
};
nextMedia = () => {
2021-08-19 01:10:32 +00:00
const { playlistPos, playlist } = this.state;
let currentPlaylistPos = playlistPos;
currentPlaylistPos = (currentPlaylistPos + 1) % playlist.length;
this.setState({
playlistPos: currentPlaylistPos,
});
};
2021-08-19 01:10:32 +00:00
getOffset = () => {
if (this.detailsContainer.current && this.viewerToolbox.current) {
return this.detailsContainer.current.offsetHeight + this.viewerToolbox.current.offsetHeight;
} else {
return 0;
}
};
2021-08-19 01:10:32 +00:00
onDelete = () => {
2021-08-19 01:10:32 +00:00
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);
};
2021-08-19 01:10:32 +00:00
onDownload = () => {
2021-08-19 01:10:32 +00:00
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);
};
2021-08-10 13:49:17 +00:00
onKeyup = (e) => {
if (ButtonKeys.ctr === e.keyCode) {
ctrIsPressed = false;
}
};
2021-08-10 13:49:17 +00:00
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:
2021-08-10 13:49:17 +00:00
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;
2021-08-10 13:12:51 +00:00
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 = () => {
this.props.onClose();
this.setState({ visible: false });
};
2021-08-19 02:23:50 +00:00
getTiffDataURL = (src) => {
2021-08-19 10:30:42 +00:00
if (!window.Tiff) return;
2021-08-19 02:23:50 +00:00
const _this = this;
const xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.open('GET', src);
2021-08-19 10:30:42 +00:00
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);
}
2021-08-19 02:23:50 +00:00
};
xhr.send();
};
render() {
2021-08-19 02:23:50 +00:00
const { playlistPos, playlist, visible, fileUrl } = this.state;
const { onClose, userAccess, canDelete, canDownload, errorLabel, previewFile } = this.props;
2021-08-19 01:10:32 +00:00
const currentFileId =
playlist.length > 0 ? playlist.find((file) => file.id === playlistPos).fileId : 0;
2021-08-19 01:10:32 +00:00
const currentFile = playlist[playlistPos];
2021-08-19 02:23:50 +00:00
const { title } = currentFile;
let isImage = false;
let isVideo = false;
let canOpen = true;
2021-08-19 02:23:50 +00:00
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 {
isImage = false;
isVideo = this.mapSupplied[ext] ? this.mapSupplied[ext].type == mediaTypes.video : false;
}
2021-08-19 02:23:50 +00:00
// TODO: rewrite with fileURL
/*if (this.mapSupplied[ext])
2021-08-19 01:10:32 +00:00
if (!isImage && this.mapSupplied[ext].convertable && !src.includes("#")) {
src += (src.includes("?") ? "&" : "?") + "convpreview=true";
2021-08-19 02:23:50 +00:00
}*/
return (
2021-08-19 01:10:32 +00:00
<StyledMediaViewer visible={visible}>
<div className="videoViewerOverlay"></div>
{!isImage && (
<>
<MediaScrollButton
orientation="right"
onClick={this.prevMedia}
2021-08-19 01:10:32 +00:00
inactive={playlist.length <= 1}
/>
<MediaScrollButton
orientation="left"
onClick={this.nextMedia}
2021-08-19 01:10:32 +00:00
inactive={playlist.length <= 1}
/>
</>
)}
<div>
<div className="details" ref={this.detailsContainer}>
<Text isBold fontSize="14px" className="title">
2021-08-19 01:10:32 +00:00
{title}
</Text>
<ControlBtn onClick={onClose && onClose} className="mediaPlayerClose">
<IconButton iconName="/static/images/cross.react.svg" size={25} isClickable />
</ControlBtn>
</div>
</div>
{canOpen &&
(isImage ? (
<ImageViewer
2021-08-19 01:10:32 +00:00
userAccess={userAccess}
visible={visible}
onClose={this.onClose}
images={[{ src: fileUrl, alt: '' }]}
2021-08-19 01:10:32 +00:00
inactive={playlist.length <= 1}
onNextClick={this.nextMedia}
onPrevClick={this.prevMedia}
onDeleteClick={this.onDelete}
onDownloadClick={this.onDownload}
/>
) : (
<StyledVideoViewer
2021-08-19 02:23:50 +00:00
url={fileUrl}
isVideo={isVideo}
getOffset={this.getOffset}
2021-08-20 09:06:00 +00:00
errorLabel={errorLabel}
/>
))}
<div className="mediaViewerToolbox" ref={this.viewerToolbox}>
{!isImage && (
<span>
{canDelete(currentFileId) && !previewFile && (
<ControlBtn onClick={this.onDelete}>
<div className="deleteBtnContainer">
<StyledMediaDeleteIcon size="scale" />
</div>
</ControlBtn>
)}
2021-08-19 01:10:32 +00:00
{canDownload(currentFileId) && (
<ControlBtn onClick={this.onDownload}>
<div className="downloadBtnContainer">
<StyledMediaDownloadIcon size="scale" />
</div>
</ControlBtn>
)}
</span>
)}
</div>
</StyledMediaViewer>
);
}
2020-04-19 20:06:17 +00:00
}
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,
2021-08-10 13:49:17 +00:00
deleteDialogVisible: PropTypes.bool,
2021-08-20 09:06:00 +00:00
errorLabel: PropTypes.string,
previewFile: PropTypes.bool,
};
2020-04-19 20:06:17 +00:00
MediaViewer.defaultProps = {
currentFileId: 0,
visible: false,
allowConvert: true,
canDelete: () => {
return true;
},
canDownload: () => {
return true;
},
previewFile: false,
};
export default MediaViewer;