feat: add VideoViewer component for enhanced video playback experience; implement download functionality and viewer modal
This commit is contained in:
@@ -6,6 +6,10 @@ import {
|
||||
Chip,
|
||||
Icon,
|
||||
Icons,
|
||||
Modal,
|
||||
Overlay,
|
||||
OverlayBackdrop,
|
||||
OverlayCenter,
|
||||
Spinner,
|
||||
Text,
|
||||
Tooltip,
|
||||
@@ -14,6 +18,7 @@ import {
|
||||
} from 'folds';
|
||||
import classNames from 'classnames';
|
||||
import { BlurhashCanvas } from 'react-blurhash';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
|
||||
import {
|
||||
IThumbnailContent,
|
||||
@@ -33,7 +38,14 @@ import {
|
||||
import { getCurrentAccessToken } from '../../../utils/auth';
|
||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||
import { validBlurHash } from '../../../utils/blurHash';
|
||||
import { ModalWide } from '../../../styles/Modal.css';
|
||||
import { stopPropagation } from '../../../utils/keyboard';
|
||||
|
||||
type RenderViewerProps = {
|
||||
src: string;
|
||||
alt: string;
|
||||
requestClose: () => void;
|
||||
};
|
||||
type RenderVideoProps = {
|
||||
title: string;
|
||||
src: string;
|
||||
@@ -41,6 +53,7 @@ type RenderVideoProps = {
|
||||
onError: () => void;
|
||||
autoPlay: boolean;
|
||||
controls: boolean;
|
||||
onExpand?: () => void;
|
||||
};
|
||||
type VideoContentProps = {
|
||||
body: string;
|
||||
@@ -52,6 +65,7 @@ type VideoContentProps = {
|
||||
markedAsSpoiler?: boolean;
|
||||
spoilerReason?: string;
|
||||
renderThumbnail?: () => ReactNode;
|
||||
renderViewer?: (props: RenderViewerProps) => ReactNode;
|
||||
renderVideo: (props: RenderVideoProps) => ReactNode;
|
||||
};
|
||||
export const VideoContent = as<'div', VideoContentProps>(
|
||||
@@ -67,6 +81,7 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||
markedAsSpoiler,
|
||||
spoilerReason,
|
||||
renderThumbnail,
|
||||
renderViewer,
|
||||
renderVideo,
|
||||
...props
|
||||
},
|
||||
@@ -78,6 +93,7 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||
|
||||
const [load, setLoad] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [viewer, setViewer] = useState(false);
|
||||
const [blurred, setBlurred] = useState(markedAsSpoiler ?? false);
|
||||
|
||||
const [srcState, loadSrc] = useAsyncCallback(
|
||||
@@ -113,6 +129,28 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||
|
||||
return (
|
||||
<Box className={classNames(css.RelativeBase, className)} {...props} ref={ref}>
|
||||
{renderViewer && srcState.status === AsyncStatus.Success && (
|
||||
<Overlay open={viewer} backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: () => setViewer(false),
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Modal className={ModalWide} size="500">
|
||||
{renderViewer({
|
||||
src: srcState.data,
|
||||
alt: body,
|
||||
requestClose: () => setViewer(false),
|
||||
})}
|
||||
</Modal>
|
||||
</FocusTrap>
|
||||
</OverlayCenter>
|
||||
</Overlay>
|
||||
)}
|
||||
{typeof blurHash === 'string' && !load && (
|
||||
<BlurhashCanvas
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
@@ -154,6 +192,7 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||
onError: handleError,
|
||||
autoPlay: true,
|
||||
controls: true,
|
||||
onExpand: renderViewer ? () => setViewer(true) : undefined,
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user