DocSpace-client/packages/common/components/MediaViewer/index.tsx

411 lines
9.7 KiB
TypeScript
Raw Normal View History

import { isMobile } from "react-device-detect";
import React, {
useState,
useCallback,
useMemo,
useEffect,
useRef,
} from "react";
2023-02-07 17:09:48 +00:00
import ViewerWrapper from "./sub-components/ViewerWrapper";
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
import { MediaViewerProps } from "./MediaViewer.props";
2023-02-07 17:09:48 +00:00
import { FileStatus } from "@docspace/common/constants";
2023-02-10 10:04:57 +00:00
import {
isNullOrUndefined,
KeyboardEventKeys,
mapSupplied,
mediaTypes,
} from "./helpers";
2023-02-07 17:09:48 +00:00
2023-05-15 07:53:27 +00:00
import {
2023-05-15 08:32:30 +00:00
getDesktopMediaContextModel,
2023-05-15 07:53:27 +00:00
getMobileMediaContextModel,
getPDFContextModel,
} from "./helpers/contextModel";
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
function MediaViewer({
playlistPos,
nextMedia,
prevMedia,
...props
}: MediaViewerProps): JSX.Element {
const TiffXMLHttpRequestRef = useRef<XMLHttpRequest>();
2023-02-10 10:04:57 +00:00
const [title, setTitle] = useState<string>("");
const [fileUrl, setFileUrl] = useState<string | undefined>(() => {
2023-02-10 10:04:57 +00:00
const { playlist, currentFileId } = props;
const item = playlist.find(
(file) => file.fileId.toString() === currentFileId.toString()
);
return item?.src;
2023-02-10 10:04:57 +00:00
});
const [targetFile, setTargetFile] = useState(() => {
const { files, currentFileId } = props;
return files.find((item) => item.id === currentFileId);
});
const [isFavorite, setIsFavorite] = useState<boolean>(() => {
const { playlist } = props;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
return (
(playlist[playlistPos].fileStatus & FileStatus.IsFavorite) ===
FileStatus.IsFavorite
);
});
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
useEffect(() => {
const fileId = props.playlist[playlistPos]?.fileId;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
if (!isNullOrUndefined(fileId) && props.currentFileId !== fileId) {
props.onChangeUrl(fileId);
}
}, [props.playlist.length]);
2023-02-07 17:09:48 +00:00
useEffect(() => {
return () => {
props.onClose();
};
}, []);
2023-02-10 10:04:57 +00:00
useEffect(() => {
const { playlist, files, setBufferSelection } = props;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
const currentFile = playlist[playlistPos];
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
const currentFileId =
playlist.length > 0
? playlist.find((file) => file.id === playlistPos)?.fileId
: 0;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
const targetFile = files.find((item) => item.id === currentFileId);
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
if (targetFile) setBufferSelection(targetFile);
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
const { src, title } = currentFile;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
const ext = getFileExtension(title);
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
if (ext === ".tiff" || ext === ".tif") {
fetchAndSetTiffDataURL(src);
}
}, []);
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
useEffect(() => {
const { playlist, onEmptyPlaylistError, files, setBufferSelection } = props;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
const { src, title, fileId } = playlist[playlistPos];
const ext = getFileExtension(title);
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
if (!src) return onEmptyPlaylistError();
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
if (ext !== ".tif" && ext !== ".tiff" && src !== fileUrl) {
TiffXMLHttpRequestRef.current?.abort();
2023-02-10 10:04:57 +00:00
setFileUrl(src);
}
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
if (ext === ".tiff" || ext === ".tif") {
setFileUrl(undefined);
2023-02-10 10:04:57 +00:00
fetchAndSetTiffDataURL(src);
}
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
const foundFile = files.find((file) => file.id === fileId);
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
if (!isNullOrUndefined(foundFile)) {
setTargetFile(foundFile);
setBufferSelection(foundFile);
}
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
setTitle(title);
setIsFavorite(
(playlist[playlistPos].fileStatus & FileStatus.IsFavorite) ===
FileStatus.IsFavorite
);
}, [props.playlist, props.files.length, props.currentFileId, playlistPos]);
2023-02-10 10:04:57 +00:00
useEffect(() => {
document.addEventListener("keydown", onKeydown);
return () => {
document.removeEventListener("keydown", onKeydown);
TiffXMLHttpRequestRef.current?.abort();
};
}, [
props.playlist.length,
props.files.length,
playlistPos,
props.deleteDialogVisible,
]);
2023-02-10 10:04:57 +00:00
const getContextModel = () => {
const {
t,
2023-05-15 07:34:25 +00:00
onClickDownloadAs,
onClickLinkEdit,
2023-02-10 10:04:57 +00:00
onClickDownload,
2023-05-15 07:34:25 +00:00
onPreviewClick,
2023-02-10 10:04:57 +00:00
onClickRename,
onClickDelete,
onShowInfoPanel,
onMoveAction,
onCopyAction,
onDuplicate,
2023-05-15 07:34:25 +00:00
onCopyLink,
2023-02-10 10:04:57 +00:00
} = props;
if (!targetFile) return [];
2023-05-15 08:32:30 +00:00
const desktopModel = getDesktopMediaContextModel(
2023-05-15 07:53:27 +00:00
t,
targetFile,
archiveRoom,
2023-02-10 10:04:57 +00:00
{
2023-05-15 07:53:27 +00:00
onClickDownload,
onClickRename,
onClickDelete,
}
);
2023-02-10 10:04:57 +00:00
2023-05-15 07:53:27 +00:00
const model = getMobileMediaContextModel(t, targetFile, {
onShowInfoPanel,
onClickDownload,
onMoveAction,
onCopyAction,
onDuplicate,
onClickRename,
onClickDelete,
});
2023-02-10 10:04:57 +00:00
2023-05-15 07:34:25 +00:00
if (isPdf)
return getPDFContextModel(t, targetFile, {
onClickDownloadAs,
onMoveAction,
onCopyAction,
onClickRename,
onDuplicate,
onClickDelete,
onClickDownload,
onClickLinkEdit,
onPreviewClick,
onCopyLink,
});
return isMobile
2023-02-10 10:04:57 +00:00
? model
: isImage && !isMobile
2023-02-10 10:04:57 +00:00
? 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() : "";
}, []);
2023-02-07 17:09:48 +00:00
let lastRemovedFileId: null | number = null;
2023-02-10 10:04:57 +00:00
const onDelete = () => {
2023-02-13 11:19:21 +00:00
const { playlist, onDelete } = props;
2023-02-07 17:09:48 +00:00
2023-04-28 12:04:46 +00:00
let currentFileId = playlist.find(
(file) => file.id === playlistPos
)?.fileId;
2023-02-07 17:09:48 +00:00
if (currentFileId === lastRemovedFileId) return;
2023-02-13 11:19:21 +00:00
const canDelete = targetFile?.security?.Delete;
if (!canDelete) return;
if (!isNullOrUndefined(currentFileId)) {
onDelete(currentFileId);
lastRemovedFileId = currentFileId;
}
2023-02-10 10:04:57 +00:00
};
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
const onDownload = () => {
const { playlist, onDownload } = props;
2023-02-07 17:09:48 +00:00
2023-04-28 12:04:46 +00:00
let currentFileId = playlist.find(
(file) => file.id === playlistPos
)?.fileId;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
if (!isNullOrUndefined(currentFileId)) onDownload(currentFileId);
};
2023-02-07 17:09:48 +00:00
2023-02-10 12:13:52 +00:00
const onKeydown = (event: KeyboardEvent) => {
2023-02-10 10:04:57 +00:00
const { code, ctrlKey } = event;
if (props.deleteDialogVisible) return;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
if (code in KeyboardEventKeys) {
const includesKeyboardCode = [
KeyboardEventKeys.KeyS,
KeyboardEventKeys.Numpad1,
KeyboardEventKeys.Digit1,
KeyboardEventKeys.Space,
].includes(code as KeyboardEventKeys);
if (!includesKeyboardCode || ctrlKey) event.preventDefault();
2023-02-10 10:04:57 +00:00
}
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
switch (code) {
case KeyboardEventKeys.ArrowLeft:
if (document.fullscreenElement) return;
2023-02-10 10:04:57 +00:00
if (!ctrlKey) prevMedia();
2023-02-10 10:04:57 +00:00
break;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
case KeyboardEventKeys.ArrowRight:
if (document.fullscreenElement) return;
2023-02-07 17:09:48 +00:00
if (!ctrlKey) nextMedia();
2023-02-08 14:21:14 +00:00
2023-02-10 10:04:57 +00:00
break;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
case KeyboardEventKeys.Escape:
if (!props.deleteDialogVisible) props.onClose();
break;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
case KeyboardEventKeys.KeyS:
if (ctrlKey) onDownload();
break;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
case KeyboardEventKeys.Delete:
onDelete();
break;
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
default:
break;
}
};
2023-02-10 10:04:57 +00:00
const onClose = useCallback(() => {
props.onClose();
}, [props.onClose]);
2023-02-10 10:04:57 +00:00
const fetchAndSetTiffDataURL = useCallback((src: string) => {
if (!window.Tiff) return;
TiffXMLHttpRequestRef.current?.abort();
2023-02-10 10:04:57 +00:00
const xhr = new XMLHttpRequest();
TiffXMLHttpRequestRef.current = xhr;
2023-02-10 10:04:57 +00:00
xhr.responseType = "arraybuffer";
2023-02-10 10:04:57 +00:00
xhr.open("GET", src);
xhr.onload = function () {
try {
const tiff = new window.Tiff({ buffer: xhr.response });
2023-02-08 14:21:14 +00:00
2023-02-10 10:04:57 +00:00
const dataUrl = tiff.toDataURL();
2023-02-07 17:09:48 +00:00
2023-02-10 10:04:57 +00:00
setFileUrl(dataUrl);
} catch (e) {
console.log(e);
}
2023-02-07 17:09:48 +00:00
};
2023-02-10 10:04:57 +00:00
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;
2023-04-28 12:04:46 +00:00
let isPdf = false;
2023-02-10 10:04:57 +00:00
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;
2023-04-28 12:04:46 +00:00
2023-02-10 10:04:57 +00:00
isVideo = mapSupplied[ext]
? mapSupplied[ext]?.type == mediaTypes.video
: false;
2023-04-28 12:04:46 +00:00
2023-02-10 10:04:57 +00:00
isAudio = mapSupplied[ext]
? mapSupplied[ext]?.type == mediaTypes.audio
: false;
2023-04-28 12:04:46 +00:00
isPdf = mapSupplied[ext] ? mapSupplied[ext]?.type == mediaTypes.pdf : false;
2023-02-10 10:04:57 +00:00
}
return (
<>
{canOpen && (
<ViewerWrapper
2023-02-10 10:04:57 +00:00
userAccess={props.userAccess}
visible={props.visible}
title={title}
onClose={onClose}
fileUrl={fileUrl}
2023-02-10 10:04:57 +00:00
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}
2023-04-28 12:04:46 +00:00
isPdf={isPdf}
2023-02-10 10:04:57 +00:00
isPreviewFile={props.isPreviewFile}
onDownloadClick={onDownload}
archiveRoom={archiveRoom}
2023-03-14 10:15:43 +00:00
errorTitle={props.t("Common:MediaError")}
2023-02-10 10:04:57 +00:00
headerIcon={headerIcon}
audioIcon={audioIcon}
/>
)}
</>
);
}
2023-02-10 10:04:57 +00:00
export default MediaViewer;