DocSpace-buildtools/web/ASC.Web.Common/src/components/MediaViewer/MediaViewer.js

433 lines
16 KiB
JavaScript
Raw Normal View History

2020-04-19 20:06:17 +00:00
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { Icons } from "asc-web-components";
2020-04-19 20:06:17 +00:00
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"
2020-05-13 13:20:23 +00:00
import isEqual from "lodash/isEqual";
import Hammer from "hammerjs"
2020-04-19 20:06:17 +00:00
const StyledVideoViewer = styled(VideoViewer)`
z-index: 4001;
`
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
});
let ctrIsPressed = false;
2020-04-19 20:06:17 +00:00
class MediaViewer extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: this.props.visible,
allowConvert: true,
playlist: this.props.playlist,
playlistPos: this.props.playlist.length > 0 ? this.props.playlist.find(file => file.fileId === this.props.currentFileId).id : 0
};
2020-05-05 13:15:33 +00:00
this.detailsContainer = React.createRef();
this.viewerToolbox = React.createRef();
2020-04-19 20:06:17 +00:00
}
updateHammer() {
let currentPlaylistPos = this.state.playlistPos;
let currentFile = this.state.playlist[currentPlaylistPos];
let fileTitle = currentFile.title;
let url = currentFile.src;
var ext = this.getFileExtension(fileTitle) ? this.getFileExtension(fileTitle) : this.getFileExtension(url);
var _this = this;
2020-07-02 08:02:50 +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);
2020-07-02 14:17:23 +00:00
this.hammer.off('doubletap', this.prevMedia);
2020-07-02 08:02:50 +00:00
}
this.hammer = null;
setTimeout(function () {
if (_this.canImageView(ext)) {
2020-07-02 08:02:50 +00:00
var pinch = new Hammer.Pinch();
_this.hammer = Hammer(document.getElementsByClassName('react-viewer-canvas')[0]);
2020-07-02 08:02:50 +00:00
_this.hammer.add([pinch]);
_this.hammer.on('pinchout', _this.handleZoomOut);
_this.hammer.on('pinchin', _this.handleZoomIn);
_this.hammer.on('pinchend', _this.handleZoomEnd);
2020-07-02 14:17:23 +00:00
_this.hammer.on('doubletap', _this.doubleTap);
2020-07-02 08:02:50 +00:00
} 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) {
_this.hammer.on('swipeleft', _this.nextMedia);
_this.hammer.on('swiperight', _this.prevMedia);
}
}, 500)
}
componentDidUpdate(prevProps) {
this.updateHammer()
if (this.props.visible !== prevProps.visible) {
this.setState(
{
visible: this.props.visible,
playlistPos: this.props.playlist.length > 0 ? this.props.playlist.find(file => file.fileId === this.props.currentFileId).id : 0
}
);
}
if (this.props.visible && this.props.visible === prevProps.visible && !isEqual(this.props.playlist, prevProps.playlist)) {
2020-05-13 13:20:23 +00:00
let playlistPos = 0;
2020-05-18 21:12:48 +00:00
if (this.props.playlist.length > 0) {
if (this.props.playlist.length - 1 < this.state.playlistPos) {
2020-05-13 13:20:23 +00:00
playlistPos = this.props.playlist.length - 1;
}
this.setState(
{
playlist: this.props.playlist,
playlistPos: playlistPos
}
);
2020-05-18 21:12:48 +00:00
} else {
this.props.onEmptyPlaylistError();
this.setState(
{
visible: false
}
);
2020-05-13 13:20:23 +00:00
}
} else if (!isEqual(this.props.playlist, prevProps.playlist)) {
2020-05-25 13:21:14 +00:00
this.setState(
{
playlist: this.props.playlist
}
);
2020-05-13 13:20:23 +00:00
}
2020-05-05 13:15:33 +00:00
}
componentDidMount() {
var _this = this;
setTimeout(function () {
if (document.getElementsByClassName('react-viewer-canvas').length > 0) {
2020-07-02 14:17:23 +00:00
_this.hammer = Hammer(document.getElementsByClassName('react-viewer-canvas')[0]);
2020-07-02 08:02:50 +00:00
var pinch = new Hammer.Pinch();
_this.hammer.add([pinch]);
_this.hammer.on('pinchout', _this.handleZoomOut);
_this.hammer.on('pinchin', _this.handleZoomIn);
_this.hammer.on('pinchend', _this.handleZoomEnd);
2020-07-02 14:17:23 +00:00
_this.hammer.on('doubletap', _this.doubleTap);
} else {
2020-07-02 14:17:23 +00:00
_this.hammer = Hammer(document.getElementsByClassName('videoViewerOverlay')[0]);
}
if (_this.hammer) {
_this.hammer.on('swipeleft', _this.nextMedia);
_this.hammer.on('swiperight', _this.prevMedia);
}
}, 500);
document.addEventListener("keydown", this.onKeydown, false);
document.addEventListener("keyup", this.onKeyup, false);
}
componentWillUnmount() {
2020-07-02 14:17:23 +00:00
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);
}
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) {
return this.props.extsImagePreviewed.indexOf(ext) != -1;
};
canPlay = (fileTitle, allowConvert) => {
var ext = fileTitle[0] === "." ? fileTitle : this.getFileExtension(fileTitle);
var supply = this.mapSupplied[ext];
var canConv = allowConvert || this.props.allowConvert;
return !!supply && this.props.extsMediaPreviewed.indexOf(ext) != -1
&& (!supply.convertable || canConv);
};
getFileExtension = (fileTitle) => {
if (!fileTitle) {
return "";
}
fileTitle = fileTitle.trim();
var posExt = fileTitle.lastIndexOf(".");
return 0 <= posExt ? fileTitle.substring(posExt).trim().toLowerCase() : "";
};
2020-07-02 08:02:50 +00:00
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()
}
}
2020-07-02 14:17:23 +00:00
doubleTap = () =>{
document.querySelector('li[data-key="zoomIn"]').click()
}
prevMedia = () => {
let currentPlaylistPos = this.state.playlistPos;
currentPlaylistPos--;
if (currentPlaylistPos < 0)
currentPlaylistPos = this.state.playlist.length - 1;
this.setState({
playlistPos: currentPlaylistPos
});
};
nextMedia = () => {
let currentPlaylistPos = this.state.playlistPos;
currentPlaylistPos = (currentPlaylistPos + 1) % this.state.playlist.length;
this.setState({
playlistPos: currentPlaylistPos
});
};
2020-05-05 13:15:33 +00:00
getOffset = () => {
if (this.detailsContainer.current && this.viewerToolbox.current) {
return this.detailsContainer.current.offsetHeight + this.viewerToolbox.current.offsetHeight;
} else {
return 0;
}
}
onDelete = () => {
let currentFileId = this.state.playlist.length > 0 ? this.state.playlist.find(file => file.id === this.state.playlistPos).fileId : 0;
this.props.onDelete && this.props.onDelete(currentFileId);
}
onDownload = () => {
let currentFileId = this.state.playlist.length > 0 ? this.state.playlist.find(file => file.id === this.state.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:
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.one:
ctrIsPressed && document.getElementsByClassName("iconContainer reset").length > 0 && document.getElementsByClassName("iconContainer reset")[0].click();
break
default:
break
}
}
}
render() {
let currentPlaylistPos = this.state.playlistPos;
let currentFileId = this.state.playlist.length > 0 ? this.state.playlist.find(file => file.id === currentPlaylistPos).fileId : 0;
let currentFile = this.state.playlist[currentPlaylistPos];
let fileTitle = currentFile.title;
let url = currentFile.src;
let isImage = false;
let isVideo = false;
let canOpen = true;
var ext = this.getFileExtension(fileTitle) ? this.getFileExtension(fileTitle) : this.getFileExtension(url);
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;
}
2020-05-18 21:12:48 +00:00
if (this.mapSupplied[ext])
if (!isImage && this.mapSupplied[ext].convertable && !url.includes("#")) {
url += (url.includes("?") ? "&" : "?") + "convpreview=true";
}
2020-05-09 18:11:54 +00:00
return (
<StyledMediaViewer visible={this.state.visible}>
<div className="videoViewerOverlay"></div>
{
!isImage &&
<>
<MediaScrollButton orientation="right" onClick={this.prevMedia} inactive={this.state.playlist.length <= 1} />
<MediaScrollButton orientation="left" onClick={this.nextMedia} inactive={this.state.playlist.length <= 1} />
</>
}
<div>
2020-05-05 13:15:33 +00:00
<div className="details" ref={this.detailsContainer}>
<div className="title">{fileTitle}</div>
<ControlBtn onClick={this.props.onClose && (this.props.onClose)} className="mediaPlayerClose">
<Icons.CrossIcon size="medium" isfill={true} color="#fff" />
</ControlBtn>
</div>
</div>
{canOpen &&
(
isImage ?
<ImageViewer
visible={this.state.visible}
onClose={() => { this.setState({ visible: false }); }}
images={[
{ src: url, alt: '' }
]}
inactive={this.state.playlist.length <= 1}
onNextClick={this.nextMedia}
onPrevClick={this.prevMedia}
onDeleteClick={this.onDelete}
onDownloadClick={this.onDownload}
/>
:
2020-05-05 13:15:33 +00:00
<StyledVideoViewer url={url} playing={this.state.visible} isVideo={isVideo} getOffset={this.getOffset} />
)
}
2020-05-06 15:02:44 +00:00
<div className="mediaViewerToolbox" ref={this.viewerToolbox}>
{
!isImage &&
<span>
{
this.props.canDelete(currentFileId) &&
<ControlBtn onClick={this.onDelete}>
<div className="deleteBtnContainer">
<Icons.MediaDeleteIcon size="scale" />
</div>
</ControlBtn>
}
{
this.props.canDownload(currentFileId) &&
<ControlBtn onClick={this.onDownload}>
<div className="downloadBtnContainer">
<Icons.MediaDownloadIcon size="scale" />
</div>
</ControlBtn>
}
</span>
}
2020-05-06 15:02:44 +00:00
</div>
2020-05-18 21:12:48 +00:00
</StyledMediaViewer>
2020-04-19 20:06:17 +00:00
)
}
2020-04-19 20:06:17 +00:00
}
MediaViewer.propTypes = {
allowConvert: PropTypes.bool,
visible: PropTypes.bool,
currentFileId: PropTypes.number,
playlist: PropTypes.arrayOf(PropTypes.object),
extsImagePreviewed: PropTypes.arrayOf(PropTypes.string),
extsMediaPreviewed: PropTypes.arrayOf(PropTypes.string),
onError: PropTypes.func,
canDelete: PropTypes.func,
2020-05-18 21:12:48 +00:00
canDownload: PropTypes.func,
onDelete: PropTypes.func,
onDownload: PropTypes.func,
onClose: PropTypes.func,
2020-05-18 21:12:48 +00:00
onEmptyPlaylistError: PropTypes.func
}
2020-04-19 20:06:17 +00:00
MediaViewer.defaultProps = {
currentFileId: 0,
visible: false,
allowConvert: true,
2020-05-18 21:12:48 +00:00
canDelete: () => { return true },
canDownload: () => { return true }
}
2020-04-19 20:06:17 +00:00
export default MediaViewer;