Sensitive video refactor
Fixes https://gitlab.com/soapbox-pub/soapbox/-/issues/1190
This commit is contained in:
parent
48a03ed5bd
commit
ba8cab0513
4 changed files with 30 additions and 99 deletions
|
@ -10,6 +10,7 @@ import { useAppDispatch, useSettings } from 'soapbox/hooks';
|
||||||
import { addAutoPlay } from 'soapbox/utils/media';
|
import { addAutoPlay } from 'soapbox/utils/media';
|
||||||
|
|
||||||
import type { List as ImmutableList } from 'immutable';
|
import type { List as ImmutableList } from 'immutable';
|
||||||
|
import type VideoType from 'soapbox/features/video';
|
||||||
import type { Status, Attachment } from 'soapbox/types/entities';
|
import type { Status, Attachment } from 'soapbox/types/entities';
|
||||||
|
|
||||||
interface IStatusMedia {
|
interface IStatusMedia {
|
||||||
|
@ -66,10 +67,6 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
||||||
dispatch(openModal('MEDIA', { media, status, index }));
|
dispatch(openModal('MEDIA', { media, status, index }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const openVideo = (media: Attachment, time: number): void => {
|
|
||||||
dispatch(openModal('VIDEO', { media, time }));
|
|
||||||
};
|
|
||||||
|
|
||||||
if (size > 0 && firstAttachment) {
|
if (size > 0 && firstAttachment) {
|
||||||
if (muted) {
|
if (muted) {
|
||||||
media = (
|
media = (
|
||||||
|
@ -105,20 +102,17 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
media = (
|
media = (
|
||||||
<Bundle fetchComponent={Video} loading={renderLoadingVideoPlayer} >
|
<Bundle fetchComponent={Video} loading={renderLoadingVideoPlayer}>
|
||||||
{(Component: any) => (
|
{(Component: typeof VideoType) => (
|
||||||
<Component
|
<Component
|
||||||
preview={video.preview_url}
|
preview={video.preview_url}
|
||||||
blurhash={video.blurhash}
|
blurhash={video.blurhash}
|
||||||
src={video.url}
|
src={video.url}
|
||||||
alt={video.description}
|
alt={video.description}
|
||||||
aspectRatio={video.meta.getIn(['original', 'aspect'])}
|
aspectRatio={Number(video.meta.getIn(['original', 'aspect']))}
|
||||||
height={285}
|
height={285}
|
||||||
inline
|
|
||||||
sensitive={status.sensitive}
|
|
||||||
onOpenVideo={openVideo}
|
|
||||||
visible={showMedia}
|
visible={showMedia}
|
||||||
onToggleVisibility={onToggleVisibility}
|
inline
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Bundle>
|
</Bundle>
|
||||||
|
|
|
@ -197,7 +197,6 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
startTime={time}
|
startTime={time}
|
||||||
onCloseVideo={onClose}
|
|
||||||
detailed
|
detailed
|
||||||
link={link}
|
link={link}
|
||||||
alt={attachment.description}
|
alt={attachment.description}
|
||||||
|
|
|
@ -37,7 +37,6 @@ const VideoModal: React.FC<IVideoModal> = ({ status, account, media, time, onClo
|
||||||
blurhash={media.blurhash}
|
blurhash={media.blurhash}
|
||||||
src={media.url}
|
src={media.url}
|
||||||
startTime={time}
|
startTime={time}
|
||||||
onCloseVideo={onClose}
|
|
||||||
link={link}
|
link={link}
|
||||||
detailed
|
detailed
|
||||||
alt={media.description}
|
alt={media.description}
|
||||||
|
|
|
@ -2,17 +2,14 @@ import classNames from 'clsx';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import Blurhash from 'soapbox/components/blurhash';
|
import Blurhash from 'soapbox/components/blurhash';
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
import { useSettings } from 'soapbox/hooks';
|
|
||||||
import { isPanoramic, isPortrait, minimumAspectRatio, maximumAspectRatio } from 'soapbox/utils/media-aspect-ratio';
|
import { isPanoramic, isPortrait, minimumAspectRatio, maximumAspectRatio } from 'soapbox/utils/media-aspect-ratio';
|
||||||
|
|
||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
||||||
|
|
||||||
import type { Attachment } from 'soapbox/types/entities';
|
|
||||||
|
|
||||||
const DEFAULT_HEIGHT = 300;
|
const DEFAULT_HEIGHT = 300;
|
||||||
|
|
||||||
type Position = { x: number, y: number };
|
type Position = { x: number, y: number };
|
||||||
|
@ -107,15 +104,11 @@ interface IVideo {
|
||||||
alt?: string,
|
alt?: string,
|
||||||
width?: number,
|
width?: number,
|
||||||
height?: number,
|
height?: number,
|
||||||
sensitive?: boolean,
|
|
||||||
startTime?: number,
|
startTime?: number,
|
||||||
onOpenVideo?: (attachment: Attachment, time: number) => void,
|
|
||||||
onCloseVideo?: () => void,
|
|
||||||
detailed?: boolean,
|
detailed?: boolean,
|
||||||
inline?: boolean,
|
inline?: boolean,
|
||||||
cacheWidth?: (width: number) => void,
|
cacheWidth?: (width: number) => void,
|
||||||
visible?: boolean,
|
visible?: boolean,
|
||||||
onToggleVisibility?: () => void,
|
|
||||||
blurhash?: string,
|
blurhash?: string,
|
||||||
link?: React.ReactNode,
|
link?: React.ReactNode,
|
||||||
aspectRatio?: number,
|
aspectRatio?: number,
|
||||||
|
@ -125,23 +118,18 @@ interface IVideo {
|
||||||
const Video: React.FC<IVideo> = ({
|
const Video: React.FC<IVideo> = ({
|
||||||
width,
|
width,
|
||||||
visible = false,
|
visible = false,
|
||||||
sensitive = false,
|
|
||||||
detailed = false,
|
detailed = false,
|
||||||
cacheWidth,
|
cacheWidth,
|
||||||
onToggleVisibility,
|
|
||||||
startTime,
|
startTime,
|
||||||
src,
|
src,
|
||||||
height,
|
height,
|
||||||
alt,
|
alt,
|
||||||
onCloseVideo,
|
|
||||||
inline,
|
inline,
|
||||||
aspectRatio = 16 / 9,
|
aspectRatio = 16 / 9,
|
||||||
link,
|
link,
|
||||||
blurhash,
|
blurhash,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const settings = useSettings();
|
|
||||||
const displayMedia = settings.get('displayMedia') as string | undefined;
|
|
||||||
|
|
||||||
const player = useRef<HTMLDivElement>(null);
|
const player = useRef<HTMLDivElement>(null);
|
||||||
const video = useRef<HTMLVideoElement>(null);
|
const video = useRef<HTMLVideoElement>(null);
|
||||||
|
@ -157,7 +145,6 @@ const Video: React.FC<IVideo> = ({
|
||||||
const [fullscreen, setFullscreen] = useState(false);
|
const [fullscreen, setFullscreen] = useState(false);
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
const [muted, setMuted] = useState(false);
|
const [muted, setMuted] = useState(false);
|
||||||
const [revealed, setRevealed] = useState<boolean>(visible !== undefined ? visible : (displayMedia !== 'hide_all' && !sensitive || displayMedia === 'show_all'));
|
|
||||||
const [buffer, setBuffer] = useState(0);
|
const [buffer, setBuffer] = useState(0);
|
||||||
|
|
||||||
const setDimensions = () => {
|
const setDimensions = () => {
|
||||||
|
@ -342,7 +329,6 @@ const Video: React.FC<IVideo> = ({
|
||||||
|
|
||||||
// If we are in fullscreen mode, we don't want any hotkeys
|
// If we are in fullscreen mode, we don't want any hotkeys
|
||||||
// interacting with the UI that's not visible
|
// interacting with the UI that's not visible
|
||||||
|
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -411,16 +397,6 @@ const Video: React.FC<IVideo> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleReveal: React.MouseEventHandler = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (onToggleVisibility) {
|
|
||||||
onToggleVisibility();
|
|
||||||
} else {
|
|
||||||
setRevealed(!revealed);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLoadedData = () => {
|
const handleLoadedData = () => {
|
||||||
if (video.current && startTime) {
|
if (video.current && startTime) {
|
||||||
video.current.currentTime = startTime;
|
video.current.currentTime = startTime;
|
||||||
|
@ -459,14 +435,6 @@ const Video: React.FC<IVideo> = ({
|
||||||
playerStyle.height = height || DEFAULT_HEIGHT;
|
playerStyle.height = height || DEFAULT_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
let warning;
|
|
||||||
|
|
||||||
if (sensitive) {
|
|
||||||
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
|
|
||||||
} else {
|
|
||||||
warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('fullscreenchange', handleFullscreenChange, true);
|
document.addEventListener('fullscreenchange', handleFullscreenChange, true);
|
||||||
document.addEventListener('webkitfullscreenchange', handleFullscreenChange, true);
|
document.addEventListener('webkitfullscreenchange', handleFullscreenChange, true);
|
||||||
|
@ -488,21 +456,15 @@ const Video: React.FC<IVideo> = ({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (!visible) {
|
||||||
setRevealed(true);
|
|
||||||
}
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!revealed) {
|
|
||||||
video.current?.pause();
|
video.current?.pause();
|
||||||
}
|
}
|
||||||
}, [revealed]);
|
}, [visible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role='menuitem'
|
role='menuitem'
|
||||||
className={classNames('video-player', { 'video-player--inactive': !revealed, detailed, 'video-player--inline': inline && !fullscreen, fullscreen })}
|
className={classNames('video-player', { detailed, 'video-player--inline': inline && !fullscreen, fullscreen })}
|
||||||
style={playerStyle}
|
style={playerStyle}
|
||||||
ref={player}
|
ref={player}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
|
@ -511,40 +473,29 @@ const Video: React.FC<IVideo> = ({
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<Blurhash
|
{!fullscreen && (
|
||||||
hash={blurhash}
|
<Blurhash hash={blurhash} className='media-gallery__preview' />
|
||||||
className={classNames('media-gallery__preview', {
|
|
||||||
'media-gallery__preview--hidden': revealed,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{revealed && (
|
|
||||||
<video
|
|
||||||
ref={video}
|
|
||||||
src={src}
|
|
||||||
loop
|
|
||||||
role='button'
|
|
||||||
tabIndex={0}
|
|
||||||
aria-label={alt}
|
|
||||||
title={alt}
|
|
||||||
width={width}
|
|
||||||
height={height || DEFAULT_HEIGHT}
|
|
||||||
onClick={togglePlay}
|
|
||||||
onKeyDown={handleVideoKeyDown}
|
|
||||||
onPlay={handlePlay}
|
|
||||||
onPause={handlePause}
|
|
||||||
onTimeUpdate={handleTimeUpdate}
|
|
||||||
onLoadedData={handleLoadedData}
|
|
||||||
onProgress={handleProgress}
|
|
||||||
onVolumeChange={handleVolumeChange}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': !sensitive || revealed })}>
|
<video
|
||||||
<button type='button' className='spoiler-button__overlay' onClick={toggleReveal}>
|
ref={video}
|
||||||
<span className='spoiler-button__overlay__label'>{warning}</span>
|
src={src}
|
||||||
</button>
|
loop
|
||||||
</div>
|
role='button'
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={alt}
|
||||||
|
title={alt}
|
||||||
|
width={width}
|
||||||
|
height={height || DEFAULT_HEIGHT}
|
||||||
|
onClick={togglePlay}
|
||||||
|
onKeyDown={handleVideoKeyDown}
|
||||||
|
onPlay={handlePlay}
|
||||||
|
onPause={handlePause}
|
||||||
|
onTimeUpdate={handleTimeUpdate}
|
||||||
|
onLoadedData={handleLoadedData}
|
||||||
|
onProgress={handleProgress}
|
||||||
|
onVolumeChange={handleVolumeChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className={classNames('video-player__controls', { active: paused || hovered })}>
|
<div className={classNames('video-player__controls', { active: paused || hovered })}>
|
||||||
<div className='video-player__seek' onMouseDown={handleMouseDown} ref={seek}>
|
<div className='video-player__seek' onMouseDown={handleMouseDown} ref={seek}>
|
||||||
|
@ -605,18 +556,6 @@ const Video: React.FC<IVideo> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='video-player__buttons right'>
|
<div className='video-player__buttons right'>
|
||||||
{(sensitive && !onCloseVideo) && (
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
title={intl.formatMessage(messages.hide)}
|
|
||||||
aria-label={intl.formatMessage(messages.hide)}
|
|
||||||
className='player-button'
|
|
||||||
onClick={toggleReveal}
|
|
||||||
>
|
|
||||||
<Icon src={require('@tabler/icons/eye-off.svg')} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)}
|
title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)}
|
||||||
|
|
Loading…
Reference in a new issue