Merge pull request #1195 from ONLYOFFICE/feature/refactoring-mediaviewer
Feature/refactoring mediaviewer
This commit is contained in:
commit
621efcf870
@ -11,6 +11,7 @@ const FilesMediaViewer = (props) => {
|
||||
t,
|
||||
files,
|
||||
playlist,
|
||||
currentPostionIndex,
|
||||
visible,
|
||||
currentMediaFileId,
|
||||
deleteItemAction,
|
||||
@ -43,10 +44,19 @@ const FilesMediaViewer = (props) => {
|
||||
extsMediaPreviewed,
|
||||
setIsPreview,
|
||||
isPreview,
|
||||
nextMedia,
|
||||
prevMedia,
|
||||
resetUrl,
|
||||
firstLoad,
|
||||
setSelection,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
resetSelection();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
const previewId = queryString.parse(location.search).preview;
|
||||
|
||||
@ -88,6 +98,10 @@ const FilesMediaViewer = (props) => {
|
||||
window.history.pushState(null, null, url);
|
||||
};
|
||||
|
||||
const resetSelection = () => {
|
||||
setSelection([]);
|
||||
};
|
||||
|
||||
const removeQuery = (queryName) => {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
|
||||
@ -108,9 +122,6 @@ const FilesMediaViewer = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const canDelete = (fileId) => true; //TODO:
|
||||
const canDownload = (fileId) => true; //TODO:
|
||||
|
||||
const onDeleteMediaFile = (id) => {
|
||||
const translations = {
|
||||
deleteOperation: t("Translations:DeleteOperation"),
|
||||
@ -138,22 +149,12 @@ const FilesMediaViewer = (props) => {
|
||||
if (isPreview) {
|
||||
setIsPreview(false);
|
||||
resetUrl();
|
||||
setScrollToItem({ id: previewFile.id, type: "file" });
|
||||
setBufferSelection(previewFile);
|
||||
if (previewFile) {
|
||||
setScrollToItem({ id: previewFile.id, type: "file" });
|
||||
setBufferSelection(previewFile);
|
||||
}
|
||||
setToPreviewFile(null);
|
||||
}
|
||||
// if (previewFile) {
|
||||
// setIsLoading(true);
|
||||
// setFirstLoad(true);
|
||||
|
||||
// fetchFiles(previewFile.folderId).finally(() => {
|
||||
// setIsLoading(false);
|
||||
// setFirstLoad(false);
|
||||
// setScrollToItem({ id: previewFile.id, type: "file" });
|
||||
// setBufferSelection(previewFile);
|
||||
// setToPreviewFile(null);
|
||||
// });
|
||||
// }
|
||||
|
||||
setMediaViewerData({ visible: false, id: null });
|
||||
|
||||
@ -176,20 +177,16 @@ const FilesMediaViewer = (props) => {
|
||||
t={t}
|
||||
userAccess={userAccess}
|
||||
currentFileId={currentMediaFileId}
|
||||
allowConvert={true} //TODO:
|
||||
canDelete={canDelete} //TODO:
|
||||
canDownload={canDownload} //TODO:
|
||||
visible={visible}
|
||||
playlist={playlist}
|
||||
playlistPos={currentPostionIndex}
|
||||
onDelete={onDeleteMediaFile}
|
||||
onDownload={onDownloadMediaFile}
|
||||
onClickFavorite={onClickFavorite}
|
||||
setBufferSelection={setBufferSelection}
|
||||
archiveRoomsId={archiveRoomsId}
|
||||
files={files}
|
||||
onClickDownload={onClickDownload}
|
||||
onShowInfoPanel={onShowInfoPanel}
|
||||
onClickDownloadAs={onClickDownloadAs}
|
||||
onClickDelete={onClickDelete}
|
||||
onClickRename={onClickRename}
|
||||
onMoveAction={onMoveAction}
|
||||
@ -201,11 +198,10 @@ const FilesMediaViewer = (props) => {
|
||||
deleteDialogVisible={deleteDialogVisible}
|
||||
extsMediaPreviewed={extsMediaPreviewed}
|
||||
extsImagePreviewed={extsImagePreviewed}
|
||||
errorLabel={t("Translations:MediaLoadError")}
|
||||
isPreviewFile={firstLoad}
|
||||
previewFile={previewFile}
|
||||
onChangeUrl={onChangeUrl}
|
||||
isFavoritesFolder={isFavoritesFolder}
|
||||
nextMedia={nextMedia}
|
||||
prevMedia={prevMedia}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@ -233,15 +229,19 @@ export default inject(
|
||||
setIsPreview,
|
||||
isPreview,
|
||||
resetUrl,
|
||||
setSelection,
|
||||
} = filesStore;
|
||||
const {
|
||||
visible,
|
||||
id: currentMediaFileId,
|
||||
currentPostionIndex,
|
||||
setMediaViewerData,
|
||||
playlist,
|
||||
previewFile,
|
||||
setToPreviewFile,
|
||||
setCurrentId,
|
||||
nextMedia,
|
||||
prevMedia,
|
||||
} = mediaViewerDataStore;
|
||||
const { deleteItemAction } = filesActionsStore;
|
||||
const { getIcon, extsImagePreviewed, extsMediaPreviewed } = settingsStore;
|
||||
@ -262,6 +262,9 @@ export default inject(
|
||||
return {
|
||||
files,
|
||||
playlist,
|
||||
currentPostionIndex,
|
||||
nextMedia,
|
||||
prevMedia,
|
||||
userAccess,
|
||||
visible: playlist.length > 0 && visible,
|
||||
currentMediaFileId,
|
||||
@ -295,6 +298,7 @@ export default inject(
|
||||
onCopyAction,
|
||||
onDuplicate,
|
||||
archiveRoomsId,
|
||||
setSelection,
|
||||
};
|
||||
}
|
||||
)(
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
import {
|
||||
isNullOrUndefined,
|
||||
findNearestIndex,
|
||||
} from "@docspace/common/components/MediaViewer/helpers";
|
||||
|
||||
class MediaViewerDataStore {
|
||||
filesStore;
|
||||
@ -8,6 +12,7 @@ class MediaViewerDataStore {
|
||||
visible = false;
|
||||
previewFile = null;
|
||||
currentItem = null;
|
||||
prevPostionIndex = 0;
|
||||
|
||||
constructor(filesStore, settingsStore) {
|
||||
makeAutoObservable(this);
|
||||
@ -45,6 +50,68 @@ class MediaViewerDataStore {
|
||||
this.id = id;
|
||||
};
|
||||
|
||||
changeUrl = (id) => {
|
||||
const url = "/products/files/#preview/" + id;
|
||||
window.history.pushState(null, null, url);
|
||||
};
|
||||
|
||||
nextMedia = () => {
|
||||
const { setBufferSelection, files } = this.filesStore;
|
||||
|
||||
const postionIndex = (this.currentPostionIndex + 1) % this.playlist.length;
|
||||
|
||||
if (postionIndex === 0) {
|
||||
return;
|
||||
}
|
||||
const currentFileId = this.playlist[postionIndex].fileId;
|
||||
|
||||
const targetFile = files.find((item) => item.id === currentFileId);
|
||||
|
||||
if (!isNullOrUndefined(targetFile)) setBufferSelection(targetFile);
|
||||
|
||||
const fileId = this.playlist[postionIndex].fileId;
|
||||
this.setCurrentId(fileId);
|
||||
this.changeUrl(fileId);
|
||||
};
|
||||
|
||||
prevMedia = () => {
|
||||
const { setBufferSelection, files } = this.filesStore;
|
||||
|
||||
let currentPlaylistPos = this.currentPostionIndex - 1;
|
||||
|
||||
if (currentPlaylistPos === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFileId = this.playlist[currentPlaylistPos].fileId;
|
||||
|
||||
const targetFile = files.find((item) => item.id === currentFileId);
|
||||
|
||||
if (!isNullOrUndefined(targetFile)) setBufferSelection(targetFile);
|
||||
|
||||
const fileId = this.playlist[currentPlaylistPos].fileId;
|
||||
this.setCurrentId(fileId);
|
||||
this.changeUrl(fileId);
|
||||
};
|
||||
|
||||
get currentPostionIndex() {
|
||||
if (this.playlist.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let index = this.playlist.find((file) => file.fileId === this.id)?.id;
|
||||
|
||||
if (isNullOrUndefined(index)) {
|
||||
index = findNearestIndex(this.playlist, this.prevPostionIndex);
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this.prevPostionIndex = index;
|
||||
});
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
get playlist() {
|
||||
const { files } = this.filesStore;
|
||||
|
||||
@ -83,6 +150,11 @@ class MediaViewerDataStore {
|
||||
id++;
|
||||
}
|
||||
});
|
||||
if (this.previewFile) {
|
||||
runInAction(() => {
|
||||
this.previewFile = null;
|
||||
});
|
||||
}
|
||||
} else if (this.previewFile) {
|
||||
playlist.push({
|
||||
...this.previewFile,
|
||||
|
@ -1,722 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import ImageViewer from "./sub-components/image-viewer";
|
||||
import equal from "fast-deep-equal/react";
|
||||
import Hammer from "hammerjs";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import { FileStatus } from "@docspace/common/constants";
|
||||
|
||||
import InfoOutlineReactSvgUrl from "PUBLIC_DIR/images/info.outline.react.svg?url";
|
||||
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
|
||||
import DuplicateReactSvgUrl from "PUBLIC_DIR/images/duplicate.react.svg?url";
|
||||
import DownloadReactSvgUrl from "PUBLIC_DIR/images/download.react.svg?url";
|
||||
import DownloadAsReactSvgUrl from "PUBLIC_DIR/images/download-as.react.svg?url";
|
||||
import RenameReactSvgUrl from "PUBLIC_DIR/images/rename.react.svg?url";
|
||||
import TrashReactSvgUrl from "PUBLIC_DIR/images/trash.react.svg?url";
|
||||
import MoveReactSvgUrl from "PUBLIC_DIR/images/duplicate.react.svg?url";
|
||||
|
||||
const mediaTypes = Object.freeze({
|
||||
audio: 1,
|
||||
video: 2,
|
||||
});
|
||||
|
||||
const ButtonKeys = Object.freeze({
|
||||
leftArrow: 37,
|
||||
rightArrow: 39,
|
||||
upArrow: 38,
|
||||
downArrow: 40,
|
||||
space: 32,
|
||||
esc: 27,
|
||||
ctr: 17,
|
||||
one: 49,
|
||||
del: 46,
|
||||
s: 83,
|
||||
});
|
||||
|
||||
let ctrIsPressed = false;
|
||||
class MediaViewer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { playlist, currentFileId, visible } = props;
|
||||
|
||||
const item = playlist.find(
|
||||
(file) => String(file.fileId) === String(currentFileId)
|
||||
);
|
||||
|
||||
if (!item) {
|
||||
console.error("MediaViewer: file not found in playlist", {
|
||||
playlist,
|
||||
currentFileId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const playlistPos = item ? item.id : 0;
|
||||
|
||||
this.state = {
|
||||
visible,
|
||||
allowConvert: true,
|
||||
playlist,
|
||||
playlistPos,
|
||||
fileUrl: item.src,
|
||||
canSwipeImage: true,
|
||||
};
|
||||
|
||||
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("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("doubletap", _this.doubleTap);
|
||||
}
|
||||
} 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
|
||||
) {
|
||||
this.updateHammer();
|
||||
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();
|
||||
//switching from index to id
|
||||
const newPlaylistPos = currentFileId
|
||||
? playlist.find((file) => file.fileId === currentFileId)?.id ?? 0
|
||||
: 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, files, setBufferSelection } = this.props;
|
||||
const { playlistPos } = this.state;
|
||||
|
||||
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 = 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("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;
|
||||
const { setBufferSelection } = this.props;
|
||||
|
||||
let currentPlaylistPos = playlistPos;
|
||||
currentPlaylistPos--;
|
||||
if (currentPlaylistPos === -1) return;
|
||||
if (currentPlaylistPos < 0) currentPlaylistPos = playlist.length - 1;
|
||||
|
||||
const currentFileId = playlist[currentPlaylistPos].fileId;
|
||||
|
||||
const targetFile = this.props.files.find(
|
||||
(item) => item.id === currentFileId
|
||||
);
|
||||
setBufferSelection(targetFile);
|
||||
|
||||
this.setState({
|
||||
playlistPos: currentPlaylistPos,
|
||||
});
|
||||
|
||||
const id = playlist[currentPlaylistPos].fileId;
|
||||
this.props.onChangeUrl(id);
|
||||
};
|
||||
|
||||
nextMedia = () => {
|
||||
const { playlistPos, playlist } = this.state;
|
||||
const { setBufferSelection } = this.props;
|
||||
|
||||
let currentPlaylistPos = playlistPos;
|
||||
currentPlaylistPos = (currentPlaylistPos + 1) % playlist.length;
|
||||
if (currentPlaylistPos === 0) return;
|
||||
|
||||
const currentFileId = playlist[currentPlaylistPos].fileId;
|
||||
|
||||
const targetFile = this.props.files.find(
|
||||
(item) => item.id === currentFileId
|
||||
);
|
||||
setBufferSelection(targetFile);
|
||||
|
||||
this.setState({
|
||||
playlistPos: currentPlaylistPos,
|
||||
});
|
||||
|
||||
const id = playlist[currentPlaylistPos].fileId;
|
||||
this.props.onChangeUrl(id);
|
||||
};
|
||||
|
||||
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);
|
||||
this.setState({
|
||||
canSwipeImage: false,
|
||||
});
|
||||
};
|
||||
|
||||
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:
|
||||
if (document.fullscreenElement) return;
|
||||
this.state.canSwipeImage
|
||||
? ctrIsPressed
|
||||
? document.getElementsByClassName("iconContainer rotateLeft")
|
||||
.length > 0 &&
|
||||
document
|
||||
.getElementsByClassName("iconContainer rotateLeft")[0]
|
||||
.click()
|
||||
: this.prevMedia()
|
||||
: null;
|
||||
break;
|
||||
case ButtonKeys.rightArrow:
|
||||
if (document.fullscreenElement) return;
|
||||
this.state.canSwipeImage
|
||||
? ctrIsPressed
|
||||
? document.getElementsByClassName("iconContainer rotateRight")
|
||||
.length > 0 &&
|
||||
document
|
||||
.getElementsByClassName("iconContainer rotateRight")[0]
|
||||
.click()
|
||||
: this.nextMedia()
|
||||
: null;
|
||||
break;
|
||||
case ButtonKeys.space:
|
||||
document.getElementsByClassName("video-play").length > 0 &&
|
||||
document.getElementsByClassName("video-play")[0].click();
|
||||
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 = (e) => {
|
||||
//fix memory leak
|
||||
this.setState({ visible: false });
|
||||
this.props.onClose(e);
|
||||
};
|
||||
|
||||
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 {
|
||||
t,
|
||||
onClose,
|
||||
userAccess,
|
||||
canDelete,
|
||||
canDownload,
|
||||
errorLabel,
|
||||
isPreviewFile,
|
||||
onClickFavorite,
|
||||
onShowInfoPanel,
|
||||
onClickDownload,
|
||||
onMoveAction,
|
||||
onCopyAction,
|
||||
onDuplicate,
|
||||
onClickDownloadAs,
|
||||
getIcon,
|
||||
onClickRename,
|
||||
onClickDelete,
|
||||
setBufferSelection,
|
||||
files,
|
||||
archiveRoomsId,
|
||||
} = this.props;
|
||||
|
||||
const currentFileId =
|
||||
playlist.length > 0
|
||||
? playlist.find((file) => file.id === playlistPos).fileId
|
||||
: 0;
|
||||
|
||||
const currentFile = playlist[playlistPos];
|
||||
|
||||
const targetFile =
|
||||
files.find((item) => item.id === currentFileId) || playlist[0];
|
||||
|
||||
const archiveRoom =
|
||||
archiveRoomsId === targetFile.rootFolderId ||
|
||||
(!targetFile?.security?.Rename && !targetFile?.security?.Delete);
|
||||
const { title } = currentFile;
|
||||
|
||||
let isImage = false;
|
||||
let isVideo = false;
|
||||
let isAudio = false;
|
||||
let canOpen = true;
|
||||
|
||||
const isFavorite =
|
||||
(playlist[playlistPos].fileStatus & FileStatus.IsFavorite) ===
|
||||
FileStatus.IsFavorite;
|
||||
|
||||
const ext = this.getFileExtension(title);
|
||||
|
||||
const onSetSelectionFile = () => {
|
||||
setBufferSelection(targetFile);
|
||||
};
|
||||
|
||||
const getContextModel = () => {
|
||||
const desktopModel = [
|
||||
{
|
||||
key: "download",
|
||||
label: t("Common:Download"),
|
||||
icon: DownloadReactSvgUrl,
|
||||
onClick: () => onClickDownload(targetFile, t),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "rename",
|
||||
label: t("Rename"),
|
||||
icon: RenameReactSvgUrl,
|
||||
onClick: () => onClickRename(targetFile),
|
||||
disabled: archiveRoom,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: TrashReactSvgUrl,
|
||||
onClick: () => onClickDelete(targetFile, t),
|
||||
disabled: archiveRoom,
|
||||
},
|
||||
];
|
||||
|
||||
const model = [
|
||||
{
|
||||
id: "option_room-info",
|
||||
key: "room-info",
|
||||
label: t("Common:Info"),
|
||||
icon: InfoOutlineReactSvgUrl,
|
||||
onClick: () => {
|
||||
return onShowInfoPanel(targetFile);
|
||||
},
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "download",
|
||||
label: t("Common:Download"),
|
||||
icon: DownloadReactSvgUrl,
|
||||
onClick: () => onClickDownload(targetFile, t),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "move-to",
|
||||
label: t("MoveTo"),
|
||||
icon: MoveReactSvgUrl,
|
||||
onClick: onMoveAction,
|
||||
disabled: !targetFile.security.Move,
|
||||
},
|
||||
// {
|
||||
// key: "download-as",
|
||||
// label: t("Translations:DownloadAs"),
|
||||
// icon: DownloadAsReactSvgUrl, // TODO: uncomment when we can download media by changing the format
|
||||
// onClick: onClickDownloadAs,
|
||||
// disabled: false,
|
||||
// },
|
||||
{
|
||||
id: "option_copy-to",
|
||||
key: "copy-to",
|
||||
label: t("Translations:Copy"),
|
||||
icon: CopyReactSvgUrl,
|
||||
onClick: onCopyAction,
|
||||
disabled: !targetFile.security.Copy,
|
||||
},
|
||||
{
|
||||
id: "option_create-copy",
|
||||
key: "copy",
|
||||
label: t("Common:Duplicate"),
|
||||
icon: DuplicateReactSvgUrl,
|
||||
onClick: () => onDuplicate(targetFile, t),
|
||||
disabled: !targetFile.security.Duplicate,
|
||||
},
|
||||
{
|
||||
key: "rename",
|
||||
label: t("Rename"),
|
||||
icon: RenameReactSvgUrl,
|
||||
onClick: () => onClickRename(targetFile),
|
||||
disabled: !targetFile.security.Rename,
|
||||
},
|
||||
|
||||
{
|
||||
key: "separator0",
|
||||
isSeparator: true,
|
||||
disabled: !targetFile.security.Delete,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: TrashReactSvgUrl,
|
||||
onClick: () => onClickDelete(targetFile, t),
|
||||
disabled: !targetFile.security.Delete,
|
||||
},
|
||||
];
|
||||
|
||||
return isMobileOnly
|
||||
? model
|
||||
: isImage && !isMobileOnly
|
||||
? desktopModel.filter((el) => el.key !== "download")
|
||||
: desktopModel;
|
||||
};
|
||||
|
||||
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;
|
||||
isAudio = this.mapSupplied[ext]
|
||||
? this.mapSupplied[ext].type == mediaTypes.audio
|
||||
: false;
|
||||
}
|
||||
|
||||
let audioIcon = getIcon(96, ext);
|
||||
let headerIcon = getIcon(24, ext);
|
||||
|
||||
// TODO: rewrite with fileURL
|
||||
/*if (this.mapSupplied[ext])
|
||||
if (!isImage && this.mapSupplied[ext].convertable && !src.includes("#")) {
|
||||
src += (src.includes("?") ? "&" : "?") + "convpreview=true";
|
||||
}*/
|
||||
return (
|
||||
<>
|
||||
{canOpen && (
|
||||
<ImageViewer
|
||||
userAccess={userAccess}
|
||||
visible={visible}
|
||||
title={title}
|
||||
onClose={this.onClose}
|
||||
images={[{ src: fileUrl, alt: "" }]}
|
||||
inactive={playlist.length <= 1}
|
||||
playlist={playlist}
|
||||
playlistPos={playlistPos}
|
||||
onNextClick={this.nextMedia}
|
||||
onSetSelectionFile={onSetSelectionFile}
|
||||
contextModel={getContextModel}
|
||||
onPrevClick={this.prevMedia}
|
||||
onDeleteClick={this.onDelete}
|
||||
isFavorite={isFavorite}
|
||||
headerIcon={headerIcon}
|
||||
isImage={isImage}
|
||||
isAudio={isAudio}
|
||||
isVideo={isVideo}
|
||||
isPreviewFile={isPreviewFile}
|
||||
audioIcon={audioIcon}
|
||||
onDownloadClick={this.onDownload}
|
||||
archiveRoom={archiveRoom}
|
||||
errorTitle={t("Files:MediaError")}
|
||||
// isFavoritesFolder={isFavoritesFolder}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
isPreviewFile: PropTypes.bool,
|
||||
onChangeUrl: PropTypes.func,
|
||||
};
|
||||
|
||||
MediaViewer.defaultProps = {
|
||||
currentFileId: 0,
|
||||
visible: false,
|
||||
allowConvert: true,
|
||||
canDelete: () => {
|
||||
return true;
|
||||
},
|
||||
canDownload: () => {
|
||||
return true;
|
||||
},
|
||||
isPreviewFile: false,
|
||||
};
|
||||
|
||||
export default MediaViewer;
|
45
packages/common/components/MediaViewer/MediaViewer.props.ts
Normal file
45
packages/common/components/MediaViewer/MediaViewer.props.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { IFile, NumberOrString, PlaylistType, TranslationType } from "./types";
|
||||
export interface MediaViewerProps {
|
||||
t: TranslationType;
|
||||
|
||||
userAccess: boolean;
|
||||
currentFileId: NumberOrString;
|
||||
|
||||
visible: boolean;
|
||||
|
||||
extsMediaPreviewed: string[];
|
||||
extsImagePreviewed: string[];
|
||||
|
||||
deleteDialogVisible: boolean;
|
||||
errorLabel: string;
|
||||
isPreviewFile: boolean;
|
||||
|
||||
files: IFile[];
|
||||
|
||||
playlist: PlaylistType[];
|
||||
|
||||
setBufferSelection: Function;
|
||||
archiveRoomsId: number;
|
||||
|
||||
playlistPos: number;
|
||||
|
||||
getIcon: (size: number, ext: string, ...arg: any) => any;
|
||||
|
||||
onClose: VoidFunction;
|
||||
onError?: VoidFunction;
|
||||
onEmptyPlaylistError: VoidFunction;
|
||||
onDelete: (id: NumberOrString) => void;
|
||||
onDownload: (id: NumberOrString) => void;
|
||||
onChangeUrl: (id: NumberOrString) => void;
|
||||
|
||||
onMoveAction: VoidFunction;
|
||||
onCopyAction: VoidFunction;
|
||||
onClickRename: (file: IFile) => void;
|
||||
onShowInfoPanel: (file: IFile) => void;
|
||||
onDuplicate: (file: IFile, t: TranslationType) => void;
|
||||
onClickDelete: (file: IFile, t: TranslationType) => void;
|
||||
onClickDownload: (file: IFile, t: TranslationType) => void;
|
||||
|
||||
nextMedia: VoidFunction;
|
||||
prevMedia: VoidFunction;
|
||||
}
|
59
packages/common/components/MediaViewer/helpers/index.ts
Normal file
59
packages/common/components/MediaViewer/helpers/index.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { NullOrUndefined, PlaylistType } from "../types";
|
||||
|
||||
export const mediaTypes = Object.freeze({
|
||||
audio: 1,
|
||||
video: 2,
|
||||
});
|
||||
|
||||
export enum KeyboardEventKeys {
|
||||
ArrowRight = "ArrowRight",
|
||||
ArrowLeft = "ArrowLeft",
|
||||
Escape = "Escape",
|
||||
Space = "Space",
|
||||
Delete = "Delete",
|
||||
KeyS = "KeyS",
|
||||
Numpad1 = "Numpad1",
|
||||
Digit1 = "Digit1",
|
||||
}
|
||||
|
||||
export const 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 },
|
||||
} as Record<string, { supply: string; type: number } | undefined>;
|
||||
|
||||
export const isNullOrUndefined = (arg: unknown): arg is NullOrUndefined => {
|
||||
return arg === undefined || arg === null;
|
||||
};
|
||||
|
||||
export const findNearestIndex = (
|
||||
items: PlaylistType[],
|
||||
index: number
|
||||
): number => {
|
||||
if (!Array.isArray(items) || items.length === 0 || index < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let found = items[0].id;
|
||||
for (const item of items) {
|
||||
if (Math.abs(item.id - index) < Math.abs(found - index)) {
|
||||
found = item.id;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
};
|
@ -1 +0,0 @@
|
||||
export default from "./MediaViewer";
|
448
packages/common/components/MediaViewer/index.tsx
Normal file
448
packages/common/components/MediaViewer/index.tsx
Normal file
@ -0,0 +1,448 @@
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import React, { useState, useCallback, useMemo, useEffect } from "react";
|
||||
|
||||
import ImageViewer from "./sub-components/image-viewer";
|
||||
|
||||
import { MediaViewerProps } from "./MediaViewer.props";
|
||||
import { FileStatus } from "@docspace/common/constants";
|
||||
import {
|
||||
isNullOrUndefined,
|
||||
KeyboardEventKeys,
|
||||
mapSupplied,
|
||||
mediaTypes,
|
||||
} from "./helpers";
|
||||
|
||||
import InfoOutlineReactSvgUrl from "PUBLIC_DIR/images/info.outline.react.svg?url";
|
||||
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
|
||||
import DuplicateReactSvgUrl from "PUBLIC_DIR/images/duplicate.react.svg?url";
|
||||
import DownloadReactSvgUrl from "PUBLIC_DIR/images/download.react.svg?url";
|
||||
import RenameReactSvgUrl from "PUBLIC_DIR/images/rename.react.svg?url";
|
||||
import TrashReactSvgUrl from "PUBLIC_DIR/images/trash.react.svg?url";
|
||||
import MoveReactSvgUrl from "PUBLIC_DIR/images/duplicate.react.svg?url";
|
||||
|
||||
function MediaViewer({
|
||||
playlistPos,
|
||||
nextMedia,
|
||||
prevMedia,
|
||||
...props
|
||||
}: MediaViewerProps): JSX.Element {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [canSwipeImage, setCanSwipeImage] = useState<boolean>(true);
|
||||
const [fileUrl, setFileUrl] = useState<string>(() => {
|
||||
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<boolean>(() => {
|
||||
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(() => {
|
||||
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);
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", onKeydown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKeydown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
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) {
|
||||
setFileUrl(src);
|
||||
}
|
||||
|
||||
if (ext === ".tiff" || ext === ".tif") {
|
||||
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.length,
|
||||
props.files.length,
|
||||
props.currentFileId,
|
||||
playlistPos,
|
||||
]);
|
||||
|
||||
const getContextModel = () => {
|
||||
const {
|
||||
t,
|
||||
onClickDownload,
|
||||
onClickRename,
|
||||
onClickDelete,
|
||||
onShowInfoPanel,
|
||||
onMoveAction,
|
||||
onCopyAction,
|
||||
onDuplicate,
|
||||
} = props;
|
||||
|
||||
if (!targetFile) return [];
|
||||
|
||||
const desktopModel = [
|
||||
{
|
||||
key: "download",
|
||||
label: t("Common:Download"),
|
||||
icon: DownloadReactSvgUrl,
|
||||
onClick: () => onClickDownload(targetFile, t),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "rename",
|
||||
label: t("Rename"),
|
||||
icon: RenameReactSvgUrl,
|
||||
onClick: () => onClickRename(targetFile),
|
||||
disabled: archiveRoom,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: TrashReactSvgUrl,
|
||||
onClick: () => onClickDelete(targetFile, t),
|
||||
disabled: archiveRoom,
|
||||
},
|
||||
];
|
||||
|
||||
const model = [
|
||||
{
|
||||
id: "option_room-info",
|
||||
key: "room-info",
|
||||
label: t("Common:Info"),
|
||||
icon: InfoOutlineReactSvgUrl,
|
||||
onClick: () => {
|
||||
return onShowInfoPanel(targetFile);
|
||||
},
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "download",
|
||||
label: t("Common:Download"),
|
||||
icon: DownloadReactSvgUrl,
|
||||
onClick: () => onClickDownload(targetFile, t),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "move-to",
|
||||
label: t("MoveTo"),
|
||||
icon: MoveReactSvgUrl,
|
||||
onClick: onMoveAction,
|
||||
disabled: !targetFile.security.Move,
|
||||
},
|
||||
{
|
||||
id: "option_copy-to",
|
||||
key: "copy-to",
|
||||
label: t("Translations:Copy"),
|
||||
icon: CopyReactSvgUrl,
|
||||
onClick: onCopyAction,
|
||||
disabled: !targetFile.security.Copy,
|
||||
},
|
||||
{
|
||||
id: "option_create-copy",
|
||||
key: "copy",
|
||||
label: t("Common:Duplicate"),
|
||||
icon: DuplicateReactSvgUrl,
|
||||
onClick: () => onDuplicate(targetFile, t),
|
||||
disabled: !targetFile.security.Duplicate,
|
||||
},
|
||||
{
|
||||
key: "rename",
|
||||
label: t("Rename"),
|
||||
icon: RenameReactSvgUrl,
|
||||
onClick: () => onClickRename(targetFile),
|
||||
disabled: !targetFile.security.Rename,
|
||||
},
|
||||
|
||||
{
|
||||
key: "separator0",
|
||||
isSeparator: true,
|
||||
disabled: !targetFile.security.Delete,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: TrashReactSvgUrl,
|
||||
onClick: () => onClickDelete(targetFile, t),
|
||||
disabled: !targetFile.security.Delete,
|
||||
},
|
||||
];
|
||||
|
||||
return isMobileOnly
|
||||
? model
|
||||
: isImage && !isMobileOnly
|
||||
? 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]
|
||||
);
|
||||
|
||||
const getFileExtension = useCallback((fileTitle: string) => {
|
||||
if (!fileTitle) {
|
||||
return "";
|
||||
}
|
||||
fileTitle = fileTitle.trim();
|
||||
const posExt = fileTitle.lastIndexOf(".");
|
||||
return 0 <= posExt ? fileTitle.substring(posExt).trim().toLowerCase() : "";
|
||||
}, []);
|
||||
|
||||
const onDelete = () => {
|
||||
const { playlist, onDelete } = props;
|
||||
|
||||
let currentFileId = playlist.find((file) => file.id === playlistPos)
|
||||
?.fileId;
|
||||
|
||||
setCanSwipeImage(false);
|
||||
if (!isNullOrUndefined(currentFileId)) onDelete(currentFileId);
|
||||
};
|
||||
|
||||
const onDownload = () => {
|
||||
const { playlist, onDownload } = props;
|
||||
|
||||
let currentFileId = playlist.find((file) => file.id === playlistPos)
|
||||
?.fileId;
|
||||
|
||||
if (!isNullOrUndefined(currentFileId)) onDownload(currentFileId);
|
||||
};
|
||||
|
||||
const onKeydown = (event: KeyboardEvent) => {
|
||||
const { code, ctrlKey } = event;
|
||||
|
||||
if (code in KeyboardEventKeys) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case KeyboardEventKeys.ArrowLeft:
|
||||
if (document.fullscreenElement || !canSwipeImage) return;
|
||||
|
||||
if (ctrlKey) {
|
||||
const rotateLeftElement = document.getElementsByClassName(
|
||||
"iconContainer rotateLeft"
|
||||
)?.[0] as HTMLElement | undefined;
|
||||
rotateLeftElement?.click();
|
||||
} else {
|
||||
prevMedia();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.ArrowRight:
|
||||
if (document.fullscreenElement || !canSwipeImage) return;
|
||||
|
||||
if (ctrlKey) {
|
||||
const rotateRightElement = document.getElementsByClassName(
|
||||
"iconContainer rotateRight"
|
||||
)?.[0] as HTMLElement | undefined;
|
||||
rotateRightElement?.click();
|
||||
} else {
|
||||
nextMedia();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.Space:
|
||||
const videoPlayElement = document.getElementsByClassName(
|
||||
"video-play"
|
||||
)?.[0] as HTMLElement | undefined;
|
||||
|
||||
videoPlayElement?.click();
|
||||
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.Escape:
|
||||
if (!props.deleteDialogVisible) props.onClose();
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.KeyS:
|
||||
if (ctrlKey) onDownload();
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.Digit1:
|
||||
case KeyboardEventKeys.Numpad1:
|
||||
if (ctrlKey) {
|
||||
const resetElement = document.getElementsByClassName(
|
||||
"iconContainer reset"
|
||||
)?.[0] as HTMLElement | undefined;
|
||||
resetElement?.click();
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.Delete:
|
||||
onDelete();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
props.onClose();
|
||||
}, [props.onClose]);
|
||||
|
||||
const fetchAndSetTiffDataURL = useCallback((src: string) => {
|
||||
if (!window.Tiff) return;
|
||||
|
||||
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();
|
||||
|
||||
setFileUrl(dataUrl);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}, []);
|
||||
|
||||
const onSetSelectionFile = useCallback(() => {
|
||||
props.setBufferSelection(targetFile);
|
||||
}, [targetFile]);
|
||||
|
||||
const ext = getFileExtension(title);
|
||||
const images = useMemo(() => [{ src: fileUrl, alt: "" }], [fileUrl]);
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{canOpen && (
|
||||
<ImageViewer
|
||||
userAccess={props.userAccess}
|
||||
visible={props.visible}
|
||||
title={title}
|
||||
onClose={onClose}
|
||||
images={images}
|
||||
inactive={props.playlist.length <= 1}
|
||||
playlist={props.playlist}
|
||||
playlistPos={playlistPos}
|
||||
onNextClick={nextMedia}
|
||||
onSetSelectionFile={onSetSelectionFile}
|
||||
contextModel={getContextModel}
|
||||
onPrevClick={prevMedia}
|
||||
onDeleteClick={onDelete}
|
||||
isFavorite={isFavorite}
|
||||
isImage={isImage}
|
||||
isAudio={isAudio}
|
||||
isVideo={isVideo}
|
||||
isPreviewFile={props.isPreviewFile}
|
||||
onDownloadClick={onDownload}
|
||||
archiveRoom={archiveRoom}
|
||||
errorTitle={props.t("Files:MediaError")}
|
||||
headerIcon={headerIcon}
|
||||
audioIcon={audioIcon}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MediaViewer;
|
89
packages/common/components/MediaViewer/types/index.ts
Normal file
89
packages/common/components/MediaViewer/types/index.ts
Normal file
@ -0,0 +1,89 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
Tiff: new (arg: object) => any;
|
||||
}
|
||||
}
|
||||
|
||||
export type TranslationType = (key: string, opt?: object) => string;
|
||||
|
||||
export type NumberOrString = number | string;
|
||||
|
||||
export type NullOrUndefined = null | undefined;
|
||||
|
||||
export type PlaylistType = {
|
||||
id: number;
|
||||
canShare: boolean;
|
||||
fileExst: string;
|
||||
fileId: number;
|
||||
fileStatus: number;
|
||||
src: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type CreatedType = {
|
||||
id: string;
|
||||
avatarSmall: string;
|
||||
displayName: string;
|
||||
hasAvatar: boolean;
|
||||
profileUrl: string;
|
||||
};
|
||||
|
||||
export type SecurityType = {
|
||||
Comment: boolean;
|
||||
Copy: boolean;
|
||||
CustomFilter: boolean;
|
||||
Delete: boolean;
|
||||
Duplicate: boolean;
|
||||
Edit: boolean;
|
||||
EditHistory: boolean;
|
||||
FillForms: boolean;
|
||||
Lock: boolean;
|
||||
Move: boolean;
|
||||
Read: boolean;
|
||||
ReadHistory: boolean;
|
||||
Rename: boolean;
|
||||
Review: boolean;
|
||||
};
|
||||
|
||||
export type ViewAccessabilityType = {
|
||||
CoAuhtoring: boolean;
|
||||
Convert: boolean;
|
||||
ImageView: boolean;
|
||||
MediaView: boolean;
|
||||
WebComment: boolean;
|
||||
WebCustomFilterEditing: boolean;
|
||||
WebEdit: boolean;
|
||||
WebRestrictedEditing: boolean;
|
||||
WebReview: boolean;
|
||||
WebView: boolean;
|
||||
};
|
||||
|
||||
export interface IFile {
|
||||
id: number;
|
||||
access: number;
|
||||
canShare: boolean;
|
||||
comment: string;
|
||||
contentLength: string;
|
||||
created: string;
|
||||
createdBy: CreatedType;
|
||||
denyDownload: boolean;
|
||||
denySharing: boolean;
|
||||
fileExst: string;
|
||||
fileStatus: number;
|
||||
fileType: number;
|
||||
folderId: number;
|
||||
pureContentLength: number;
|
||||
rootFolderId: number;
|
||||
rootFolderType: number;
|
||||
security: SecurityType;
|
||||
shared: boolean;
|
||||
thumbnailStatus: number;
|
||||
title: string;
|
||||
updated: string;
|
||||
updatedBy: CreatedType;
|
||||
version: number;
|
||||
versionGroup: number;
|
||||
viewAccessability: ViewAccessabilityType;
|
||||
viewUrl: string;
|
||||
webUrl: string;
|
||||
}
|
@ -455,7 +455,7 @@ const ViewerBase = (props) => {
|
||||
document[funcName]("keydown", handleKeydown, true);
|
||||
}
|
||||
if (viewerCore.current) {
|
||||
viewerCore.current[funcName]("wheel", handleMouseScroll, false);
|
||||
viewerCore.current[funcName]("wheel", handleMouseScroll, {passive: true});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user