Merge branch 'develop' into bugfix/Bug61132
This commit is contained in:
commit
9124709643
@ -225,6 +225,10 @@ const StyledToggleButton = styled(ToggleButton)`
|
||||
margin-top: -4px;
|
||||
`;
|
||||
|
||||
const StyledText = styled(Text)`
|
||||
flex-basis: 72%;
|
||||
`;
|
||||
|
||||
export {
|
||||
StyledBlock,
|
||||
StyledHeading,
|
||||
@ -247,4 +251,5 @@ export {
|
||||
ScrollList,
|
||||
StyledAccessSelector,
|
||||
StyledToggleButton,
|
||||
StyledText,
|
||||
};
|
||||
|
@ -1,8 +1,6 @@
|
||||
import InfoEditReactSvgUrl from "PUBLIC_DIR/images/info.edit.react.svg?url";
|
||||
import AtReactSvgUrl from "PUBLIC_DIR/images/@.react.svg?url";
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import Text from "@docspace/components/text";
|
||||
import Avatar from "@docspace/components/avatar";
|
||||
|
||||
import { parseAddresses } from "@docspace/components/utils/email";
|
||||
@ -16,6 +14,7 @@ import {
|
||||
StyledCrossIcon,
|
||||
StyledHelpButton,
|
||||
StyledDeleteIcon,
|
||||
StyledText,
|
||||
} from "../StyledInvitePanel";
|
||||
|
||||
const Item = ({
|
||||
@ -110,9 +109,9 @@ const Item = ({
|
||||
|
||||
const displayBody = (
|
||||
<>
|
||||
<Text {...textProps} noSelect>
|
||||
<StyledText {...textProps} truncate noSelect>
|
||||
{inputValue}
|
||||
</Text>
|
||||
</StyledText>
|
||||
{hasError ? (
|
||||
<>
|
||||
<StyledHelpButton
|
||||
|
@ -36,11 +36,13 @@ const elementResizeDetector = elementResizeDetectorMaker({
|
||||
const FilesTileContainer = ({ filesList, t, sectionWidth, withPaging }) => {
|
||||
const tileRef = useRef(null);
|
||||
const timerRef = useRef(null);
|
||||
const isMountedRef = useRef(true);
|
||||
const [thumbSize, setThumbSize] = useState("");
|
||||
const [columnCount, setColumnCount] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
isMountedRef.current = false;
|
||||
if (!tileRef?.current) return;
|
||||
clearTimeout(timerRef.current);
|
||||
elementResizeDetector.uninstall(tileRef.current);
|
||||
@ -49,7 +51,7 @@ const FilesTileContainer = ({ filesList, t, sectionWidth, withPaging }) => {
|
||||
|
||||
const onResize = useCallback(
|
||||
(node) => {
|
||||
if (!node) return;
|
||||
if (!node || !isMountedRef.current) return;
|
||||
|
||||
const { width } = node.getBoundingClientRect();
|
||||
|
||||
|
@ -140,7 +140,7 @@ class AccountsContextOptionsStore {
|
||||
key: option,
|
||||
icon: InfoOutlineReactSvgUrl,
|
||||
label: t("Common:Info"),
|
||||
onClick: this.onDetailsClick,
|
||||
onClick: () => this.onDetailsClick(item),
|
||||
};
|
||||
|
||||
case "invite-again":
|
||||
@ -389,8 +389,10 @@ class AccountsContextOptionsStore {
|
||||
setDeleteProfileDialogVisible(true);
|
||||
};
|
||||
|
||||
onDetailsClick = () => {
|
||||
onDetailsClick = (item) => {
|
||||
const { setIsVisible } = this.peopleStore.infoPanelStore;
|
||||
const { setBufferSelection } = this.peopleStore.selectionStore;
|
||||
setBufferSelection(item);
|
||||
setIsVisible(true);
|
||||
};
|
||||
|
||||
|
@ -100,6 +100,8 @@ class FilesStore {
|
||||
createdItem = null;
|
||||
scrollToItem = null;
|
||||
|
||||
roomCreated = false;
|
||||
|
||||
isLoadingFilesFind = false;
|
||||
pageItemsLength = null;
|
||||
isHidePagination = false;
|
||||
@ -179,6 +181,8 @@ class FilesStore {
|
||||
|
||||
const newFiles = [fileInfo, ...this.files];
|
||||
|
||||
if (this.files.findIndex((x) => x.id === opt?.id) > -1) return;
|
||||
|
||||
if (newFiles.length > this.filter.pageCount && withPaging) {
|
||||
newFiles.pop(); // Remove last
|
||||
}
|
||||
@ -192,11 +196,18 @@ class FilesStore {
|
||||
});
|
||||
} else if (opt?.type === "folder" && opt?.id) {
|
||||
const foundIndex = this.folders.findIndex((x) => x.id === opt?.id);
|
||||
|
||||
if (foundIndex > -1) return;
|
||||
|
||||
const folder = JSON.parse(opt?.data);
|
||||
|
||||
if (this.selectedFolderStore.id !== folder.parentId) return;
|
||||
if (
|
||||
this.selectedFolderStore.id !== folder.parentId ||
|
||||
(folder.roomType &&
|
||||
folder.createdBy.id === this.authStore.userStore.user.id &&
|
||||
this.roomCreated)
|
||||
)
|
||||
return (this.roomCreated = false);
|
||||
|
||||
const folderInfo = await api.files.getFolderInfo(folder.id);
|
||||
|
||||
@ -1974,9 +1985,10 @@ class FilesStore {
|
||||
return api.files.createFolder(parentFolderId, title);
|
||||
}
|
||||
|
||||
createRoom(roomParams) {
|
||||
createRoom = (roomParams) => {
|
||||
this.roomCreated = true;
|
||||
return api.rooms.createRoom(roomParams);
|
||||
}
|
||||
};
|
||||
|
||||
createRoomInThirdpary(thirpartyFolderId, roomParams) {
|
||||
return api.rooms.createRoomInThirdpary(thirpartyFolderId, roomParams);
|
||||
|
@ -0,0 +1,22 @@
|
||||
import styled from "styled-components";
|
||||
import { Base } from "@docspace/components/themes";
|
||||
|
||||
const ControlBtn = styled.div`
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
line-height: 25px;
|
||||
margin: 5px;
|
||||
width: 40px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
props.theme.mediaViewer.controlBtn.backgroundColor};
|
||||
}
|
||||
`;
|
||||
|
||||
ControlBtn.defaultProps = { theme: Base };
|
||||
|
||||
export default ControlBtn;
|
@ -0,0 +1,15 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
type StyledButtonScrollProps = {
|
||||
orientation: "left" | "right";
|
||||
};
|
||||
|
||||
const StyledButtonScroll = styled.div<StyledButtonScrollProps>`
|
||||
z-index: 307;
|
||||
position: fixed;
|
||||
top: calc(50% - 20px);
|
||||
|
||||
${(props) => (props.orientation === "left" ? "left: 20px;" : "right: 20px;")}
|
||||
`;
|
||||
|
||||
export default StyledButtonScroll;
|
@ -0,0 +1,47 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledMobileDetails = styled.div`
|
||||
z-index: 307;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 53px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(0, 0, 0, 0.8) 100%
|
||||
);
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-close {
|
||||
position: fixed;
|
||||
left: 21px;
|
||||
top: 22px;
|
||||
}
|
||||
|
||||
.mobile-context {
|
||||
position: fixed;
|
||||
right: 22px;
|
||||
top: 22px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
margin-top: 6px;
|
||||
width: calc(100% - 100px);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledMobileDetails;
|
@ -0,0 +1,23 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
type StyledSwitchToolbarProps = {
|
||||
left?: boolean;
|
||||
};
|
||||
|
||||
const StyledSwitchToolbar = styled.div<StyledSwitchToolbarProps>`
|
||||
height: 100%;
|
||||
z-index: 306;
|
||||
position: fixed;
|
||||
width: 73px;
|
||||
background: inherit;
|
||||
display: block;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
${(props) => (props.left ? "left: 0" : "right: 0")};
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledSwitchToolbar;
|
@ -0,0 +1,84 @@
|
||||
import styled from "styled-components";
|
||||
import { Base } from "@docspace/components/themes";
|
||||
|
||||
type StyledViewerContainerProps = {
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
const StyledViewerContainer = styled.div<StyledViewerContainerProps>`
|
||||
color: ${(props) => props.theme.mediaViewer.color};
|
||||
display: ${(props) => (props.visible ? "block" : "none")};
|
||||
overflow: hidden;
|
||||
span {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 5px;
|
||||
margin-right: 10px;
|
||||
z-index: 305;
|
||||
}
|
||||
.deleteBtnContainer,
|
||||
.downloadBtnContainer {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 4px 12px;
|
||||
line-height: 19px;
|
||||
svg {
|
||||
path {
|
||||
fill: ${(props) => props.theme.mediaViewer.fill};
|
||||
}
|
||||
}
|
||||
}
|
||||
.details {
|
||||
z-index: 307;
|
||||
padding-top: 21px;
|
||||
height: 64px;
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(0, 0, 0, 0.8) 100%
|
||||
);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
.title {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
width: calc(100% - 50px);
|
||||
padding-left: 16px;
|
||||
box-sizing: border-box;
|
||||
color: ${(props) => props.theme.mediaViewer.titleColor};
|
||||
}
|
||||
}
|
||||
.mediaPlayerClose {
|
||||
position: fixed;
|
||||
top: 13px;
|
||||
right: 12px;
|
||||
height: 17px;
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
svg {
|
||||
path {
|
||||
fill: ${(props) => props.theme.mediaViewer.iconColor};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.containerVideo {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledViewerContainer.defaultProps = { theme: Base };
|
||||
|
||||
export default StyledViewerContainer;
|
@ -0,0 +1,5 @@
|
||||
export { default as ControlBtn } from "./ControlBtn";
|
||||
export { default as StyledButtonScroll } from "./StyledButtonScroll";
|
||||
export { default as StyledSwitchToolbar } from "./StyledSwitchToolbar";
|
||||
export { default as StyledViewerContainer } from "./StyledViewerContainer";
|
||||
export { default as StyledMobileDetails } from "./StyledMobileDetails";
|
@ -64,5 +64,5 @@ export const findNearestIndex = (
|
||||
};
|
||||
|
||||
export const isSeparator = (arg: ContextMenuModel): arg is SeparatorType => {
|
||||
return arg?.isSeparator;
|
||||
return arg?.isSeparator !== undefined && arg.isSeparator;
|
||||
};
|
||||
|
@ -0,0 +1,15 @@
|
||||
import { ContextMenuModel } from "../../types";
|
||||
|
||||
interface MobileDetailsProps {
|
||||
icon: string;
|
||||
title: string;
|
||||
isError: boolean;
|
||||
isPreviewFile: boolean;
|
||||
contextModel: () => ContextMenuModel[];
|
||||
|
||||
onHide: VoidFunction;
|
||||
onMaskClick: VoidFunction;
|
||||
onContextMenu: (e: TouchEvent) => void;
|
||||
}
|
||||
|
||||
export default MobileDetailsProps;
|
@ -0,0 +1,70 @@
|
||||
import React, { ForwardedRef, useMemo } from "react";
|
||||
|
||||
import Text from "@docspace/components/text";
|
||||
import ContextMenu from "@docspace/components/context-menu";
|
||||
|
||||
import { StyledMobileDetails } from "../../StyledComponents";
|
||||
|
||||
import type MobileDetailsProps from "./MobileDetails.props";
|
||||
|
||||
import BackArrow from "PUBLIC_DIR/images/viewer.media.back.react.svg";
|
||||
import MediaContextMenu from "PUBLIC_DIR/images/vertical-dots.react.svg";
|
||||
|
||||
function MobileDetails(
|
||||
{
|
||||
icon,
|
||||
title,
|
||||
isError,
|
||||
isPreviewFile,
|
||||
onHide,
|
||||
onMaskClick,
|
||||
onContextMenu,
|
||||
contextModel,
|
||||
}: MobileDetailsProps,
|
||||
ref: ForwardedRef<ContextMenu>
|
||||
) {
|
||||
const contextMenuHeader = useMemo(
|
||||
() => ({
|
||||
icon: icon,
|
||||
title: title,
|
||||
}),
|
||||
[icon, title]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledMobileDetails>
|
||||
<BackArrow className="mobile-close" onClick={onMaskClick} />
|
||||
<Text
|
||||
fontSize="14px"
|
||||
color="#fff"
|
||||
className="title"
|
||||
as={undefined}
|
||||
tag={undefined}
|
||||
title={undefined}
|
||||
textAlign={undefined}
|
||||
fontWeight={undefined}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
{!isPreviewFile && !isError && (
|
||||
<div className="details-context">
|
||||
<MediaContextMenu
|
||||
className="mobile-context"
|
||||
onClick={onContextMenu}
|
||||
/>
|
||||
<ContextMenu
|
||||
ref={ref}
|
||||
withBackdrop={true}
|
||||
onHide={onHide}
|
||||
header={contextMenuHeader}
|
||||
getContextModel={contextModel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</StyledMobileDetails>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(
|
||||
React.forwardRef<ContextMenu, MobileDetailsProps>(MobileDetails)
|
||||
);
|
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
StyledButtonScroll,
|
||||
StyledSwitchToolbar,
|
||||
} from "../../StyledComponents";
|
||||
import MediaNextIcon from "PUBLIC_DIR/images/viewer.next.react.svg";
|
||||
|
||||
type NextButtonProps = {
|
||||
nextClick: VoidFunction;
|
||||
};
|
||||
|
||||
function NextButton({ nextClick }: NextButtonProps) {
|
||||
return (
|
||||
<StyledSwitchToolbar onClick={nextClick}>
|
||||
<StyledButtonScroll orientation="right">
|
||||
<MediaNextIcon />
|
||||
</StyledButtonScroll>
|
||||
</StyledSwitchToolbar>
|
||||
);
|
||||
}
|
||||
|
||||
export default NextButton;
|
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
StyledButtonScroll,
|
||||
StyledSwitchToolbar,
|
||||
} from "../../StyledComponents";
|
||||
import MediaPrevIcon from "PUBLIC_DIR/images/viewer.prew.react.svg";
|
||||
|
||||
type PrevButtonProps = {
|
||||
prevClick: VoidFunction;
|
||||
};
|
||||
|
||||
function PrevButton({ prevClick }: PrevButtonProps) {
|
||||
return (
|
||||
<StyledSwitchToolbar left onClick={prevClick}>
|
||||
<StyledButtonScroll orientation="left">
|
||||
<MediaPrevIcon />
|
||||
</StyledButtonScroll>
|
||||
</StyledSwitchToolbar>
|
||||
);
|
||||
}
|
||||
|
||||
export default PrevButton;
|
@ -0,0 +1,36 @@
|
||||
import { getCustomToolbar } from "../../helpers/getCustomToolbar";
|
||||
import { ContextMenuModel, PlaylistType } from "../../types";
|
||||
|
||||
interface ViewerProps {
|
||||
title: string;
|
||||
images: { src: string; alt: string }[];
|
||||
isAudio: boolean;
|
||||
isVideo: boolean;
|
||||
visible: boolean;
|
||||
isImage: boolean;
|
||||
|
||||
playlist: PlaylistType[];
|
||||
inactive: boolean;
|
||||
|
||||
audioIcon: string;
|
||||
zoomSpeed: number;
|
||||
errorTitle: string;
|
||||
headerIcon: string;
|
||||
customToolbar: () => ReturnType<typeof getCustomToolbar>;
|
||||
playlistPos: number;
|
||||
archiveRoom: boolean;
|
||||
isPreviewFile: boolean;
|
||||
onMaskClick: VoidFunction;
|
||||
onNextClick: VoidFunction;
|
||||
onPrevClick: VoidFunction;
|
||||
contextModel: () => ContextMenuModel[];
|
||||
onDownloadClick: VoidFunction;
|
||||
generateContextMenu: (
|
||||
isOpen: boolean,
|
||||
right: string,
|
||||
bottom: string
|
||||
) => JSX.Element;
|
||||
onSetSelectionFile: VoidFunction;
|
||||
}
|
||||
|
||||
export default ViewerProps;
|
@ -0,0 +1,215 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import React, { useRef, useState, useEffect, useCallback } from "react";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
|
||||
import Text from "@docspace/components/text";
|
||||
import IconButton from "@docspace/components/icon-button";
|
||||
import ContextMenu from "@docspace/components/context-menu";
|
||||
|
||||
import { StyledViewer } from "@docspace/components/viewer/styled-viewer";
|
||||
import ViewerPlayer from "@docspace/components/viewer/sub-components/viewer-player";
|
||||
|
||||
import { ControlBtn, StyledViewerContainer } from "../../StyledComponents";
|
||||
|
||||
import MobileDetails from "../MobileDetails";
|
||||
import PrevButton from "../PrevButton";
|
||||
import NextButton from "../NextButton";
|
||||
|
||||
import type ViewerProps from "./Viewer.props";
|
||||
|
||||
import ViewerMediaCloseSvgUrl from "PUBLIC_DIR/images/viewer.media.close.svg?url";
|
||||
|
||||
function Viewer(props: ViewerProps) {
|
||||
const timerIDRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const containerRef = React.useRef(document.createElement("div"));
|
||||
|
||||
const [panelVisible, setPanelVisible] = useState<boolean>(true);
|
||||
const [isOpenContextMenu, setIsOpenContextMenu] = useState<boolean>(false);
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
const [isPlay, setIsPlay] = useState<boolean | null>(null);
|
||||
|
||||
const [imageTimer, setImageTimer] = useState<NodeJS.Timeout>();
|
||||
|
||||
const contextMenuRef = useRef<ContextMenu>(null);
|
||||
const videoElementRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
const [isFullscreen, setIsFullScreen] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
document.body.appendChild(containerRef.current);
|
||||
|
||||
return () => {
|
||||
document.body.removeChild(containerRef.current);
|
||||
timerIDRef.current && clearTimeout(timerIDRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if ((!isPlay || isOpenContextMenu) && (!props.isImage || isOpenContextMenu))
|
||||
return clearTimeout(timerIDRef.current);
|
||||
}, [isPlay, isOpenContextMenu, props.isImage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobileOnly) return;
|
||||
|
||||
const resetTimer = () => {
|
||||
setPanelVisible(true);
|
||||
clearTimeout(timerIDRef.current);
|
||||
timerIDRef.current = setTimeout(() => setPanelVisible(false), 2500);
|
||||
setImageTimer(timerIDRef.current);
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", resetTimer, { passive: true });
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousemove", resetTimer);
|
||||
clearTimeout(timerIDRef.current);
|
||||
setPanelVisible(true);
|
||||
};
|
||||
}, [setImageTimer, setPanelVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("touchstart", onTouch);
|
||||
|
||||
return () => document.removeEventListener("touchstart", onTouch);
|
||||
}, [setPanelVisible]);
|
||||
|
||||
const onTouch = useCallback(
|
||||
(e: TouchEvent, canTouch?: boolean) => {
|
||||
if (e.target === videoElementRef.current || canTouch) {
|
||||
setPanelVisible((visible) => !visible);
|
||||
}
|
||||
},
|
||||
[setPanelVisible]
|
||||
);
|
||||
|
||||
const nextClick = () => {
|
||||
clearTimeout(imageTimer);
|
||||
props.onNextClick();
|
||||
};
|
||||
|
||||
const prevClick = () => {
|
||||
clearTimeout(imageTimer);
|
||||
props.onPrevClick();
|
||||
};
|
||||
|
||||
const onMobileContextMenu = useCallback(
|
||||
(e: TouchEvent) => {
|
||||
setIsOpenContextMenu((open) => !open);
|
||||
props.onSetSelectionFile();
|
||||
contextMenuRef.current?.show(e);
|
||||
},
|
||||
[props.onSetSelectionFile, setIsOpenContextMenu]
|
||||
);
|
||||
|
||||
const onHide = useCallback(() => {
|
||||
setIsOpenContextMenu(false);
|
||||
}, [setIsOpenContextMenu]);
|
||||
|
||||
const mobileDetails = (
|
||||
<MobileDetails
|
||||
isError={isError}
|
||||
title={props.title}
|
||||
icon={props.headerIcon}
|
||||
contextModel={props.contextModel}
|
||||
isPreviewFile={props.isPreviewFile}
|
||||
onHide={onHide}
|
||||
onContextMenu={onMobileContextMenu}
|
||||
onMaskClick={props.onMaskClick}
|
||||
ref={contextMenuRef}
|
||||
/>
|
||||
);
|
||||
|
||||
const displayUI = (isMobileOnly && props.isAudio) || panelVisible;
|
||||
|
||||
const isNotFirstElement = props.playlistPos !== 0;
|
||||
const isNotLastElement = props.playlistPos < props.playlist.length - 1;
|
||||
|
||||
return (
|
||||
<StyledViewerContainer visible={props.visible}>
|
||||
{!isFullscreen && !isMobileOnly && displayUI && (
|
||||
<div>
|
||||
<div className="details">
|
||||
<Text
|
||||
isBold
|
||||
fontSize="14px"
|
||||
className="title"
|
||||
title={undefined}
|
||||
tag={undefined}
|
||||
as={undefined}
|
||||
fontWeight={undefined}
|
||||
color={undefined}
|
||||
textAlign={undefined}
|
||||
>
|
||||
{props.title}
|
||||
</Text>
|
||||
<ControlBtn
|
||||
onClick={props.onMaskClick}
|
||||
className="mediaPlayerClose"
|
||||
>
|
||||
<IconButton
|
||||
color={"#fff"}
|
||||
iconName={ViewerMediaCloseSvgUrl}
|
||||
size={28}
|
||||
isClickable
|
||||
/>
|
||||
</ControlBtn>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.playlist.length > 1 && !isFullscreen && displayUI && (
|
||||
<>
|
||||
{isNotFirstElement && <PrevButton prevClick={prevClick} />}
|
||||
{isNotLastElement && <NextButton nextClick={nextClick} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
{props.isImage
|
||||
? ReactDOM.createPortal(
|
||||
<StyledViewer
|
||||
{...props}
|
||||
displayUI={displayUI}
|
||||
mobileDetails={mobileDetails}
|
||||
setIsOpenContextMenu={setIsOpenContextMenu}
|
||||
container={containerRef.current}
|
||||
imageTimer={imageTimer}
|
||||
onMaskClick={props.onMaskClick}
|
||||
setPanelVisible={setPanelVisible}
|
||||
generateContextMenu={props.generateContextMenu}
|
||||
/>,
|
||||
containerRef.current
|
||||
)
|
||||
: (props.isVideo || props.isAudio) &&
|
||||
ReactDOM.createPortal(
|
||||
<ViewerPlayer
|
||||
{...props}
|
||||
onNextClick={nextClick}
|
||||
onPrevClick={prevClick}
|
||||
isAudio={props.isAudio}
|
||||
audioIcon={props.audioIcon}
|
||||
contextModel={props.contextModel}
|
||||
mobileDetails={mobileDetails}
|
||||
displayUI={displayUI}
|
||||
isOpenContextMenu={isOpenContextMenu}
|
||||
onTouch={onTouch}
|
||||
title={props.title}
|
||||
setIsPlay={setIsPlay}
|
||||
setIsOpenContextMenu={setIsOpenContextMenu}
|
||||
isPlay={isPlay}
|
||||
onMaskClick={props.onMaskClick}
|
||||
setPanelVisible={setPanelVisible}
|
||||
generateContextMenu={props.generateContextMenu}
|
||||
setIsFullScreen={setIsFullScreen}
|
||||
setIsError={setIsError}
|
||||
videoRef={videoElementRef}
|
||||
video={props.playlist[props.playlistPos]}
|
||||
activeIndex={props.playlistPos}
|
||||
/>,
|
||||
containerRef.current
|
||||
)}
|
||||
</StyledViewerContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Viewer;
|
@ -1,7 +1,7 @@
|
||||
import React, { useMemo, memo, useCallback } from "react";
|
||||
import equal from "fast-deep-equal/react";
|
||||
|
||||
import { Viewer } from "@docspace/components/viewer";
|
||||
import Viewer from "../Viewer";
|
||||
import { isSeparator } from "../../helpers";
|
||||
import { getCustomToolbar } from "../../helpers/getCustomToolbar";
|
||||
import { ContextMenuModel } from "../../types";
|
||||
|
@ -1,24 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Duration({ className, seconds }) {
|
||||
return (
|
||||
<time dateTime={`P${Math.round(seconds)}S`} className={className}>
|
||||
{format(seconds)}
|
||||
</time>
|
||||
);
|
||||
}
|
||||
|
||||
function format(seconds) {
|
||||
const date = new Date(seconds * 1000);
|
||||
const hh = date.getUTCHours();
|
||||
const mm = date.getUTCMinutes();
|
||||
const ss = pad(date.getUTCSeconds());
|
||||
if (hh) {
|
||||
return `${hh}:${pad(mm)}:${ss}`;
|
||||
}
|
||||
return `${mm}:${ss}`;
|
||||
}
|
||||
|
||||
function pad(string) {
|
||||
return ("0" + string).slice(-2);
|
||||
}
|
@ -1,415 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
|
||||
import MediaZoomInIcon from "PUBLIC_DIR/images/media.zoomin.react.svg";
|
||||
import MediaZoomOutIcon from "PUBLIC_DIR/images/media.zoomout.react.svg";
|
||||
import MediaRotateLeftIcon from "PUBLIC_DIR/images/media.rotateleft.react.svg";
|
||||
import MediaRotateRightIcon from "PUBLIC_DIR/images/media.rotateright.react.svg";
|
||||
import MediaDeleteIcon from "PUBLIC_DIR/images/media.delete.react.svg";
|
||||
import MediaDownloadIcon from "PUBLIC_DIR/images/download.react.svg";
|
||||
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
|
||||
import MediaFavoriteIcon from "PUBLIC_DIR/images/favorite.react.svg";
|
||||
|
||||
import ViewerSeparator from "PUBLIC_DIR/images/viewer.separator.react.svg";
|
||||
import MediaShare from "PUBLIC_DIR/images/share.react.svg";
|
||||
|
||||
import DropDownItem from "@docspace/components/drop-down-item";
|
||||
import DropDown from "@docspace/components/drop-down";
|
||||
import equal from "fast-deep-equal/react";
|
||||
import { Base } from "@docspace/components/themes";
|
||||
import { Viewer } from "@docspace/components/viewer";
|
||||
|
||||
const StyledViewer = styled(Viewer)`
|
||||
.react-viewer-footer {
|
||||
bottom: 5px !important;
|
||||
z-index: 301 !important;
|
||||
overflow: visible;
|
||||
}
|
||||
.react-viewer-canvas {
|
||||
z-index: 300 !important;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.react-viewer-navbar,
|
||||
.react-viewer-mask,
|
||||
.react-viewer-attribute,
|
||||
.react-viewer-close {
|
||||
display: none;
|
||||
}
|
||||
.react-viewer-toolbar {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
bottom: 4px;
|
||||
}
|
||||
.react-viewer-toolbar li {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
margin-top: 4px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
line-height: 24px;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
.react-viewer-btn {
|
||||
background-color: transparent;
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
props.theme.mediaViewer.imageViewer.backgroundColor};
|
||||
}
|
||||
}
|
||||
.react-viewer-image-transition {
|
||||
transition-duration: 0s;
|
||||
}
|
||||
li[data-key="prev"] {
|
||||
left: 20px;
|
||||
}
|
||||
li[data-key="next"] {
|
||||
right: 20px;
|
||||
}
|
||||
li[data-key="prev"],
|
||||
li[data-key="next"] {
|
||||
position: fixed;
|
||||
top: calc(50% - 20px);
|
||||
|
||||
height: auto;
|
||||
background: none;
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
li[data-key="delete"],
|
||||
li[data-key="customDownload"] {
|
||||
position: fixed;
|
||||
@media (max-width: 600px) {
|
||||
position: initial;
|
||||
}
|
||||
bottom: 9px;
|
||||
.controlBtn {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
li[data-key="delete"] {
|
||||
right: 62px;
|
||||
}
|
||||
li[data-key="customDownload"] {
|
||||
right: 12px;
|
||||
}
|
||||
.iconContainer {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 20px;
|
||||
margin: 3px auto;
|
||||
|
||||
&.reset {
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
path,
|
||||
rect {
|
||||
fill: ${(props) => props.theme.mediaViewer.imageViewer.fill};
|
||||
}
|
||||
}
|
||||
|
||||
.btnContainer {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 4px 12px;
|
||||
line-height: 19px;
|
||||
|
||||
path,
|
||||
rect {
|
||||
fill: ${(props) => props.theme.mediaViewer.imageViewer.fill};
|
||||
}
|
||||
}
|
||||
.scrollBtn {
|
||||
cursor: ${(props) => (props.inactive ? "default" : "pointer")};
|
||||
opacity: ${(props) => (props.inactive ? "0.2" : "1")};
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
!props.inactive
|
||||
? props.theme.mediaViewer.imageViewer.backgroundColor
|
||||
: props.theme.mediaViewer.imageViewer.inactiveBackgroundColor};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDropDown = styled(DropDown)`
|
||||
background: #333;
|
||||
`;
|
||||
|
||||
const StyledDropDownItem = styled(DropDownItem)`
|
||||
color: #fff;
|
||||
|
||||
.drop-down-item_icon svg {
|
||||
path {
|
||||
fill: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* .is-separator {
|
||||
height: 1px;
|
||||
background: #474747;
|
||||
} */
|
||||
|
||||
&:hover {
|
||||
background: #444;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledViewer.defaultProps = { theme: Base };
|
||||
|
||||
class ImageViewer extends React.Component {
|
||||
// componentDidUpdate() {
|
||||
// document.getElementsByClassName("iconContainer reset").length > 0 &&
|
||||
// document.getElementsByClassName("iconContainer reset")[0].click();
|
||||
// }
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !equal(this.props, nextProps);
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
visible,
|
||||
images,
|
||||
inactive,
|
||||
onClose,
|
||||
userAccess,
|
||||
title,
|
||||
errorTitle,
|
||||
onPrevClick,
|
||||
onNextClick,
|
||||
playlist,
|
||||
playlistPos,
|
||||
isImage,
|
||||
isAudio,
|
||||
isVideo,
|
||||
isPreviewFile,
|
||||
archiveRoom,
|
||||
contextModel,
|
||||
audioIcon,
|
||||
headerIcon,
|
||||
onSetSelectionFile,
|
||||
onDownloadClick,
|
||||
} = this.props;
|
||||
|
||||
const generateContextMenu = (isOpen, right, bottom) => {
|
||||
const model = contextModel();
|
||||
|
||||
const onItemClick = (e, item) => {
|
||||
const { action, onClick } = item;
|
||||
|
||||
return onClick({ originalEvent: e, action: action, item });
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledDropDown
|
||||
open={isOpen}
|
||||
isDefaultMode={false}
|
||||
directionY="top"
|
||||
directionX="right"
|
||||
fixedDirection={true}
|
||||
withBackdrop={false}
|
||||
manualY={(bottom || "63") + "px"}
|
||||
manualX={(right || "-31") + "px"}
|
||||
>
|
||||
{model.map((item) => {
|
||||
if (item.disabled) return;
|
||||
|
||||
const onClick = (e) => {
|
||||
onClose();
|
||||
onItemClick(e, item);
|
||||
};
|
||||
return (
|
||||
<StyledDropDownItem
|
||||
className={`${item.isSeparator ? "is-separator" : ""}`}
|
||||
key={item.key}
|
||||
label={item.label}
|
||||
icon={item.icon ? item.icon : ""}
|
||||
action={item.action}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledDropDown>
|
||||
);
|
||||
};
|
||||
|
||||
var customToolbar = [
|
||||
{
|
||||
key: "zoomOut",
|
||||
percent: true,
|
||||
actionType: 2,
|
||||
render: (
|
||||
<div className="iconContainer zoomOut">
|
||||
<MediaZoomOutIcon size="scale" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "percent",
|
||||
actionType: 999,
|
||||
},
|
||||
{
|
||||
key: "zoomIn",
|
||||
actionType: 1,
|
||||
render: (
|
||||
<div className="iconContainer zoomIn">
|
||||
<MediaZoomInIcon size="scale" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "rotateLeft",
|
||||
actionType: 5,
|
||||
render: (
|
||||
<div className="iconContainer rotateLeft">
|
||||
<MediaRotateLeftIcon size="scale" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "rotateRight",
|
||||
actionType: 6,
|
||||
render: (
|
||||
<div className="iconContainer rotateRight">
|
||||
<MediaRotateRightIcon size="scale" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "separator download-separator",
|
||||
actionType: -1,
|
||||
noHover: true,
|
||||
render: (
|
||||
<div className="separator" style={{ height: "16px" }}>
|
||||
<ViewerSeparator size="scale" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// key: "share",
|
||||
// actionType: 101,
|
||||
// render: (
|
||||
// <div className="iconContainer share" style={{ height: "16px" }}>
|
||||
// <MediaShare size="scale" />
|
||||
// </div>
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
key: "download",
|
||||
actionType: 102,
|
||||
render: (
|
||||
<div className="iconContainer download" style={{ height: "16px" }}>
|
||||
<MediaDownloadIcon size="scale" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "context-separator",
|
||||
actionType: -1,
|
||||
noHover: true,
|
||||
render: (
|
||||
<div className="separator" style={{ height: "16px" }}>
|
||||
<ViewerSeparator size="scale" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "context-menu",
|
||||
actionType: -1,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
actionType: 103,
|
||||
render: (
|
||||
<div className="iconContainer viewer-delete">
|
||||
<MediaDeleteIcon size="scale" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "favorite",
|
||||
actionType: 104,
|
||||
render: (
|
||||
<div className="iconContainer viewer-favorite">
|
||||
<MediaFavoriteIcon size="scale" />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
customToolbar.forEach((button) => {
|
||||
switch (button.key) {
|
||||
case "prev":
|
||||
button.onClick = this.props.onPrevClick;
|
||||
break;
|
||||
case "next":
|
||||
button.onClick = this.props.onNextClick;
|
||||
break;
|
||||
case "delete":
|
||||
button.onClick = this.props.onDeleteClick;
|
||||
break;
|
||||
case "download":
|
||||
button.onClick = onDownloadClick;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const canShare = playlist[playlistPos].canShare;
|
||||
const toolbars =
|
||||
!canShare && userAccess
|
||||
? customToolbar.filter(
|
||||
(x) => x.key !== "share" && x.key !== "share-separator"
|
||||
)
|
||||
: customToolbar.filter((x) => x.key !== "delete");
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Viewer
|
||||
inactive={inactive}
|
||||
visible={visible}
|
||||
zoomSpeed={0.25}
|
||||
title={title}
|
||||
errorTitle={errorTitle}
|
||||
contextModel={contextModel}
|
||||
generateContextMenu={generateContextMenu}
|
||||
isImage={isImage}
|
||||
headerIcon={headerIcon}
|
||||
isAudio={isAudio}
|
||||
isVideo={isVideo}
|
||||
isPreviewFile={isPreviewFile}
|
||||
archiveRoom={archiveRoom}
|
||||
audioIcon={audioIcon}
|
||||
onSetSelectionFile={onSetSelectionFile}
|
||||
onMaskClick={onClose}
|
||||
onNextClick={onNextClick}
|
||||
onPrevClick={onPrevClick}
|
||||
onDownloadClick={onDownloadClick}
|
||||
playlist={playlist}
|
||||
playlistPos={playlistPos}
|
||||
customToolbar={() => toolbars}
|
||||
images={images}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImageViewer.propTypes = {
|
||||
className: PropTypes.string,
|
||||
visible: PropTypes.bool,
|
||||
inactive: PropTypes.bool,
|
||||
images: PropTypes.arrayOf(PropTypes.object),
|
||||
onNextClick: PropTypes.func,
|
||||
onPrevClick: PropTypes.func,
|
||||
onDeleteClick: PropTypes.func,
|
||||
onDownloadClick: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ImageViewer;
|
@ -1,138 +0,0 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import PropTypes from "prop-types";
|
||||
import { Base } from "@docspace/components/themes";
|
||||
|
||||
const StyledProgress = styled.div`
|
||||
display: inline-block;
|
||||
|
||||
.slider-container {
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
width: ${(props) => props.width}px;
|
||||
height: 6px;
|
||||
background: ${(props) =>
|
||||
props.theme.mediaViewer.progressBar.backgroundColor};
|
||||
margin: 15px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.fill {
|
||||
cursor: pointer;
|
||||
width: ${(props) => 100 * props.value}%;
|
||||
position: absolute;
|
||||
|
||||
top: calc(50% - 3px);
|
||||
height: 6px;
|
||||
background: ${(props) => props.theme.mediaViewer.progressBar.background};
|
||||
border-radius: 2px;
|
||||
}
|
||||
input[type="range"] {
|
||||
display: block;
|
||||
overflow: visible;
|
||||
background: transparent;
|
||||
width: ${(props) => props.width}px;
|
||||
height: 6px;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
position: relative;
|
||||
appearance: none;
|
||||
box-sizing: content-box;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-top: -3px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
position: relative;
|
||||
appearance: none;
|
||||
box-sizing: content-box;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
margin-top: -3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="range"]::-ms-thumb {
|
||||
position: relative;
|
||||
appearance: none;
|
||||
box-sizing: content-box;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
margin-top: -3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-runnable-track {
|
||||
margin: 12px 0;
|
||||
height: 6px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
text-align: right;
|
||||
pointer-events: none;
|
||||
}
|
||||
input[type="range"]::-moz-range-track {
|
||||
margin: 12px 0;
|
||||
height: 6px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
text-align: right;
|
||||
pointer-events: none;
|
||||
}
|
||||
input[type="range"]::-ms-track {
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
|
||||
margin: 12px 0;
|
||||
height: 6px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
text-align: right;
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledProgress.defaultProps = { theme: Base };
|
||||
const Progress = (props) => {
|
||||
return (
|
||||
<StyledProgress {...props}>
|
||||
<div className="slider-container">
|
||||
<div className="fill"></div>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={0.999999}
|
||||
step="any"
|
||||
value={props.value}
|
||||
onMouseDown={props.handleSeekMouseDown}
|
||||
onChange={(event) => props.handleSeekChange(event)}
|
||||
onMouseUp={props.handleSeekMouseUp}
|
||||
/>
|
||||
</div>
|
||||
</StyledProgress>
|
||||
);
|
||||
};
|
||||
|
||||
Progress.propTypes = {
|
||||
value: PropTypes.number,
|
||||
handleSeekMouseDown: PropTypes.func,
|
||||
handleSeekChange: PropTypes.func,
|
||||
handleSeekMouseUp: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Progress;
|
@ -1,579 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled, { css } from "styled-components";
|
||||
import { findDOMNode } from "react-dom";
|
||||
import screenfull from "screenfull";
|
||||
import ReactPlayer from "react-player";
|
||||
|
||||
import Duration from "./duration";
|
||||
import Progress from "./progress";
|
||||
import MediaPauseIcon from "PUBLIC_DIR/images/media.pause.react.svg";
|
||||
import MediaPlayIcon from "PUBLIC_DIR/images/media.play.react.svg";
|
||||
import MediaFullScreenIcon from "PUBLIC_DIR/images/media.fullscreen.video.react.svg";
|
||||
import MediaMuteIcon from "PUBLIC_DIR/images/media.mute.react.svg";
|
||||
import MediaMuteOffIcon from "PUBLIC_DIR/images/media.muteoff.react.svg";
|
||||
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
|
||||
import { Base } from "@docspace/components/themes";
|
||||
|
||||
const iconsStyles = css`
|
||||
path,
|
||||
stroke,
|
||||
rect {
|
||||
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
|
||||
}
|
||||
`;
|
||||
|
||||
const controlsHeight = 40;
|
||||
const StyledControls = styled.div`
|
||||
height: ${(props) => props.height}px;
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 301;
|
||||
${(props) =>
|
||||
!props.isVideo &&
|
||||
`background-color: ${props.theme.mediaViewer.videoViewer.backgroundColor};`}
|
||||
top: calc(50% + ${(props) => props.top}px);
|
||||
left: ${(props) => props.left}px;
|
||||
`;
|
||||
|
||||
StyledControls.defaultProps = { theme: Base };
|
||||
const StyledVideoControlBtn = styled.div`
|
||||
display: inline-block;
|
||||
height: 26px;
|
||||
line-height: 30px;
|
||||
margin: 5px 2px;
|
||||
width: 38px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
props.theme.mediaViewer.videoViewer.background};
|
||||
}
|
||||
|
||||
.playBtnContainer {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 0;
|
||||
margin: 5px auto;
|
||||
}
|
||||
.pauseBtnContainer {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 3px 10px;
|
||||
line-height: 19px;
|
||||
}
|
||||
.muteBtnContainer {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 3px 11px;
|
||||
line-height: 19px;
|
||||
}
|
||||
.fullscreenBtnContainer {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 3px 11px;
|
||||
line-height: 19px;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledVideoControlBtn.defaultProps = { theme: Base };
|
||||
const StyledMediaPauseIcon = styled(MediaPauseIcon)`
|
||||
${commonIconsStyles}
|
||||
${iconsStyles}
|
||||
`;
|
||||
StyledMediaPauseIcon.defaultProps = { theme: Base };
|
||||
const StyledMediaPlayIcon = styled(MediaPlayIcon)`
|
||||
${commonIconsStyles}
|
||||
${iconsStyles}
|
||||
`;
|
||||
StyledMediaPlayIcon.defaultProps = { theme: Base };
|
||||
const StyledMediaFullScreenIcon = styled(MediaFullScreenIcon)`
|
||||
${commonIconsStyles}
|
||||
${iconsStyles}
|
||||
`;
|
||||
StyledMediaFullScreenIcon.defaultProps = { theme: Base };
|
||||
const StyledMediaMuteIcon = styled(MediaMuteIcon)`
|
||||
${commonIconsStyles}
|
||||
|
||||
path:first-child {
|
||||
stroke: ${(props) => props.theme.mediaViewer.videoViewer.stroke};
|
||||
}
|
||||
|
||||
path:last-child {
|
||||
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
|
||||
}
|
||||
`;
|
||||
const StyledMediaMuteOffIcon = styled(MediaMuteOffIcon)`
|
||||
${commonIconsStyles}
|
||||
|
||||
path, rect {
|
||||
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
|
||||
}
|
||||
`;
|
||||
|
||||
StyledMediaMuteIcon.defaultProps = { theme: Base };
|
||||
const VideoControlBtn = (props) => {
|
||||
return (
|
||||
<StyledVideoControlBtn {...props}>{props.children}</StyledVideoControlBtn>
|
||||
);
|
||||
};
|
||||
VideoControlBtn.propTypes = {
|
||||
children: PropTypes.any,
|
||||
};
|
||||
const Controls = (props) => {
|
||||
return <StyledControls {...props}>{props.children}</StyledControls>;
|
||||
};
|
||||
Controls.propTypes = {
|
||||
children: PropTypes.any,
|
||||
};
|
||||
const PlayBtn = (props) => {
|
||||
return (
|
||||
<VideoControlBtn onClick={props.onClick}>
|
||||
{props.playing ? (
|
||||
<div className="pauseBtnContainer">
|
||||
<StyledMediaPauseIcon size="scale" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="playBtnContainer">
|
||||
<StyledMediaPlayIcon size="scale" />
|
||||
</div>
|
||||
)}
|
||||
</VideoControlBtn>
|
||||
);
|
||||
};
|
||||
PlayBtn.propTypes = {
|
||||
playing: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
const FullScreenBtn = (props) => {
|
||||
return (
|
||||
<VideoControlBtn onClick={props.onClick}>
|
||||
<div className="fullscreenBtnContainer">
|
||||
<StyledMediaFullScreenIcon size="scale" />
|
||||
</div>
|
||||
</VideoControlBtn>
|
||||
);
|
||||
};
|
||||
FullScreenBtn.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
const StyledValumeContainer = styled.div`
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
line-height: 39px;
|
||||
position: relative;
|
||||
|
||||
.muteConteiner {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 80px;
|
||||
border-radius: 5px;
|
||||
top: -76px;
|
||||
left: 5px;
|
||||
background: black;
|
||||
}
|
||||
&:hover {
|
||||
.muteConteiner {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.mute {
|
||||
display: inline-block;
|
||||
transform: rotate(-90deg);
|
||||
margin: 22px -14px;
|
||||
}
|
||||
`;
|
||||
const StyledDuration = styled.div`
|
||||
display: inline-block;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
margin: 5px;
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
props.theme.mediaViewer.videoViewer.background};
|
||||
}
|
||||
`;
|
||||
|
||||
StyledValumeContainer.defaultProps = { theme: Base };
|
||||
const StyledVideoViewer = styled.div`
|
||||
color: ${(props) => props.theme.mediaViewer.videoViewer.color};
|
||||
|
||||
.playerWrapper {
|
||||
display: ${(props) => (props.isVideo ? "block" : "none")};
|
||||
width: ${(props) => props.width}px;
|
||||
height: ${(props) => props.height}px;
|
||||
left: ${(props) => props.left}px;
|
||||
top: calc(50% - ${(props) => props.top / 2}px);
|
||||
z-index: 301;
|
||||
position: fixed;
|
||||
padding-bottom: 40px;
|
||||
background-color: ${(props) =>
|
||||
props.theme.mediaViewer.videoViewer.backgroundColor};
|
||||
|
||||
video {
|
||||
z-index: 300;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledVideoViewer.defaultProps = { theme: Base };
|
||||
|
||||
const ErrorContainer = styled.div`
|
||||
z-index: 301;
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: calc(50% - 110px);
|
||||
top: calc(50% - 40px);
|
||||
background-color: ${(props) =>
|
||||
props.theme.mediaViewer.videoViewer.backgroundColorError};
|
||||
color: ${(props) => props.theme.mediaViewer.videoViewer.colorError};
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
ErrorContainer.defaultProps = { theme: Base };
|
||||
|
||||
class ValumeBtn extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyledValumeContainer>
|
||||
<div className="muteConteiner">
|
||||
<Progress
|
||||
className="mute"
|
||||
width={this.props.width}
|
||||
value={this.props.volume}
|
||||
onMouseDown={this.props.onMouseDown}
|
||||
handleSeekChange={this.props.onChange}
|
||||
onMouseUp={this.props.handleSeekMouseUp}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VideoControlBtn onClick={this.props.onChangeMute}>
|
||||
<div className="muteBtnContainer">
|
||||
{this.props.muted ? (
|
||||
<StyledMediaMuteOffIcon size="scale" />
|
||||
) : (
|
||||
<StyledMediaMuteIcon size="scale" />
|
||||
)}
|
||||
</div>
|
||||
</VideoControlBtn>
|
||||
</StyledValumeContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
ValumeBtn.propTypes = {
|
||||
width: PropTypes.number,
|
||||
volume: PropTypes.number,
|
||||
muted: PropTypes.bool,
|
||||
onMouseDown: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
handleSeekMouseUp: PropTypes.func,
|
||||
onChangeMute: PropTypes.func,
|
||||
};
|
||||
|
||||
class VideoViewer extends Component {
|
||||
state = {
|
||||
url: this.props.url,
|
||||
pip: false,
|
||||
playing: false,
|
||||
controls: false,
|
||||
light: false,
|
||||
volume: 0.3,
|
||||
muted: false,
|
||||
played: 0,
|
||||
loaded: 0,
|
||||
duration: 0,
|
||||
playbackRate: 1.0,
|
||||
loop: false,
|
||||
isNew: false,
|
||||
error: false,
|
||||
isLoaded: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener("keydown", this.onKeydown, false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("keydown", this.onKeydown, false);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.url !== prevProps.url) {
|
||||
this.setState({
|
||||
url: this.props.url,
|
||||
isNew: true,
|
||||
error: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onKeydown = (e) => {
|
||||
if (e.keyCode === 32) this.handlePlayPause();
|
||||
};
|
||||
|
||||
handlePlayPause = () => {
|
||||
this.setState({ playing: !this.state.playing, isNew: false });
|
||||
};
|
||||
|
||||
handleStop = () => {
|
||||
this.setState({ url: null, playing: false });
|
||||
};
|
||||
|
||||
handleVolumeChange = (e) => {
|
||||
this.setState({
|
||||
volume: parseFloat(e.target.value),
|
||||
muted: false,
|
||||
});
|
||||
};
|
||||
|
||||
handleToggleMuted = () => {
|
||||
this.setState({ muted: !this.state.muted });
|
||||
};
|
||||
|
||||
handlePlay = () => {
|
||||
this.setState({ playing: true });
|
||||
};
|
||||
|
||||
handleEnablePIP = () => {
|
||||
this.setState({ pip: true });
|
||||
};
|
||||
|
||||
handleDisablePIP = () => {
|
||||
this.setState({ pip: false });
|
||||
};
|
||||
|
||||
handlePause = () => {
|
||||
this.setState({ playing: false });
|
||||
};
|
||||
|
||||
handleSeekMouseDown = (e) => {
|
||||
this.setState({ seeking: true });
|
||||
};
|
||||
|
||||
handleSeekChange = (e) => {
|
||||
this.setState({ played: parseFloat(e.target.value) });
|
||||
};
|
||||
|
||||
handleSeekMouseUp = (e) => {
|
||||
console.log(!isNaN(parseFloat(e.target.value)), parseFloat(e.target.value));
|
||||
if (!isNaN(parseFloat(e.target.value))) {
|
||||
this.setState({ seeking: false });
|
||||
this.player.seekTo(parseFloat(e.target.value));
|
||||
}
|
||||
};
|
||||
|
||||
handleProgress = (state) => {
|
||||
if (!this.state.seeking) {
|
||||
this.setState(state);
|
||||
}
|
||||
};
|
||||
|
||||
handleEnded = () => {
|
||||
this.setState({ playing: this.state.loop });
|
||||
};
|
||||
|
||||
handleDuration = (duration) => {
|
||||
this.setState({ duration });
|
||||
};
|
||||
|
||||
handleClickFullscreen = () => {
|
||||
screenfull.request(findDOMNode(this.player));
|
||||
};
|
||||
|
||||
ref = (player) => {
|
||||
this.player = player;
|
||||
};
|
||||
|
||||
resizePlayer = (videoSize, screenSize) => {
|
||||
var ratio = videoSize.h / videoSize.w;
|
||||
|
||||
if (videoSize.h > screenSize.h) {
|
||||
videoSize.h = screenSize.h;
|
||||
videoSize.w = videoSize.h / ratio;
|
||||
}
|
||||
if (videoSize.w > screenSize.w) {
|
||||
videoSize.w = screenSize.w;
|
||||
videoSize.h = videoSize.w * ratio;
|
||||
}
|
||||
|
||||
return {
|
||||
width: videoSize.w,
|
||||
height: videoSize.h,
|
||||
};
|
||||
};
|
||||
|
||||
onError = (e) => {
|
||||
console.log("onError", e);
|
||||
this.setState({ error: true });
|
||||
};
|
||||
|
||||
onPlay = () => {
|
||||
this.setState({ playing: !this.state.isNew, isNew: false, isLoaded: true });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
url,
|
||||
playing,
|
||||
controls,
|
||||
light,
|
||||
volume,
|
||||
muted,
|
||||
loop,
|
||||
played,
|
||||
loaded,
|
||||
duration,
|
||||
playbackRate,
|
||||
pip,
|
||||
error,
|
||||
isLoaded,
|
||||
} = this.state;
|
||||
const { errorLabel } = this.props;
|
||||
|
||||
const parentOffset = this.props.getOffset() || 0;
|
||||
var screenSize = {
|
||||
w: window.innerWidth,
|
||||
h: window.innerHeight,
|
||||
};
|
||||
screenSize.h -= parentOffset + controlsHeight;
|
||||
|
||||
let width = screenSize.w;
|
||||
let height = screenSize.h;
|
||||
|
||||
let centerAreaOx = screenSize.w / 2 + document.documentElement.scrollLeft;
|
||||
let centerAreaOy = screenSize.h / 2 + document.documentElement.scrollTop;
|
||||
|
||||
let videoElement = document.getElementsByTagName("video")[0];
|
||||
if (videoElement) {
|
||||
width = this.props.isVideo
|
||||
? videoElement.videoWidth || 480
|
||||
: screenSize.w - 150;
|
||||
height = this.props.isVideo ? videoElement.videoHeight || 270 : 0;
|
||||
|
||||
let resize = this.resizePlayer(
|
||||
{
|
||||
w: width,
|
||||
h: height,
|
||||
},
|
||||
screenSize
|
||||
);
|
||||
width = resize.width;
|
||||
height = resize.height;
|
||||
}
|
||||
|
||||
let left = this.props.isVideo
|
||||
? centerAreaOx - width / 2
|
||||
: centerAreaOx - width / 2;
|
||||
|
||||
const videoControlBtnWidth = 220;
|
||||
const audioControlBtnWidth = 170;
|
||||
let progressWidth = this.props.isVideo
|
||||
? width - videoControlBtnWidth
|
||||
: width - audioControlBtnWidth;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorContainer>
|
||||
<p>{errorLabel}</p>
|
||||
</ErrorContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledVideoViewer
|
||||
isVideo={this.props.isVideo}
|
||||
width={width}
|
||||
height={height}
|
||||
left={left}
|
||||
top={height + controlsHeight}
|
||||
>
|
||||
<div>
|
||||
<div className="playerWrapper" onClick={this.handlePlayPause}>
|
||||
<ReactPlayer
|
||||
ref={this.ref}
|
||||
className="react-player"
|
||||
style={{ opacity: isLoaded ? 1 : 0 }}
|
||||
width="100%"
|
||||
height="100%"
|
||||
url={url}
|
||||
pip={pip}
|
||||
playing={playing}
|
||||
playsinline={true}
|
||||
controls={controls}
|
||||
light={light}
|
||||
loop={loop}
|
||||
playbackRate={playbackRate}
|
||||
volume={volume}
|
||||
muted={muted}
|
||||
onPlay={this.onPlay}
|
||||
onEnablePIP={this.handleEnablePIP}
|
||||
onDisablePIP={this.handleDisablePIP}
|
||||
onPause={this.handlePause}
|
||||
onEnded={this.handleEnded}
|
||||
onError={this.onError}
|
||||
onProgress={this.handleProgress}
|
||||
onDuration={this.handleDuration}
|
||||
/>
|
||||
</div>
|
||||
<Controls
|
||||
height={controlsHeight}
|
||||
left={left}
|
||||
top={height / 2 - controlsHeight / 2}
|
||||
isVideo={this.props.isVideo}
|
||||
>
|
||||
<PlayBtn onClick={this.handlePlayPause} playing={playing} />
|
||||
<Progress
|
||||
value={played}
|
||||
width={progressWidth}
|
||||
onMouseDown={this.handleSeekMouseDown}
|
||||
handleSeekChange={this.handleSeekChange}
|
||||
onMouseUp={this.handleSeekMouseUp}
|
||||
onTouchEnd={this.handleSeekMouseUp}
|
||||
/>
|
||||
<StyledDuration>
|
||||
-<Duration seconds={duration * (1 - played)} />
|
||||
</StyledDuration>
|
||||
<ValumeBtn
|
||||
width={64}
|
||||
muted={muted}
|
||||
volume={muted ? 0 : volume}
|
||||
onChangeMute={this.handleToggleMuted}
|
||||
onChange={this.handleVolumeChange}
|
||||
/>
|
||||
{this.props.isVideo && (
|
||||
<FullScreenBtn onClick={this.handleClickFullscreen} />
|
||||
)}
|
||||
</Controls>
|
||||
</div>
|
||||
</StyledVideoViewer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
VideoViewer.propTypes = {
|
||||
isVideo: PropTypes.bool,
|
||||
url: PropTypes.string,
|
||||
getOffset: PropTypes.func,
|
||||
errorLabel: PropTypes.string,
|
||||
};
|
||||
|
||||
export default VideoViewer;
|
@ -356,9 +356,6 @@ export default function ViewerPlayer(props) {
|
||||
setPanelVisible,
|
||||
onTouch,
|
||||
isOpenContextMenu,
|
||||
setGlobalTimer,
|
||||
globalTimer,
|
||||
videoControls,
|
||||
setIsOpenContextMenu,
|
||||
contextModel,
|
||||
onDownloadClick,
|
||||
@ -418,10 +415,12 @@ export default function ViewerPlayer(props) {
|
||||
const inputRef = React.useRef(null);
|
||||
const volumeRef = React.useRef(null);
|
||||
const actionRef = React.useRef(null);
|
||||
const videoControls = React.useRef(null);
|
||||
const mobileProgressRef = React.useRef(null);
|
||||
|
||||
const [state, dispatch] = React.useReducer(reducer, initialState);
|
||||
const [currentVolume, setCurrentVolume] = React.useState(stateVolume);
|
||||
const [globalTimer, setGlobalTimer] = React.useState(null);
|
||||
const speedIcons = [<Icon05x />, <Icon1x />, <Icon15x />, <Icon2x />];
|
||||
const handlers = useSwipeable({
|
||||
onSwiping: (e) => {
|
||||
|
Loading…
Reference in New Issue
Block a user