diff --git a/web/media/multimedia.react.js b/web/media/multimedia.react.js index 0c0e099f6..42b98ce70 100644 --- a/web/media/multimedia.react.js +++ b/web/media/multimedia.react.js @@ -1,193 +1,175 @@ // @flow import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import { CircularProgressbar } from 'react-circular-progressbar'; import 'react-circular-progressbar/dist/styles.css'; import { XCircle as XCircleIcon, AlertCircle as AlertCircleIcon, } from 'react-feather'; -import { - useModalContext, - type PushModal, -} from 'lib/components/modal-provider.react.js'; +import { useModalContext } from 'lib/components/modal-provider.react.js'; import { fetchableMediaURI } from 'lib/media/media-utils.js'; import type { MediaType, EncryptedMediaType } from 'lib/types/media-types.js'; import EncryptedMultimedia from './encrypted-multimedia.react.js'; import css from './media.css'; import MultimediaModal from './multimedia-modal.react.js'; import Button from '../components/button.react.js'; import { type PendingMultimediaUpload } from '../input/input-state.js'; type MediaSource = | { +type: MediaType, +uri: string, } | { +type: EncryptedMediaType, +holder: string, +encryptionKey: string, }; -type BaseProps = { +type Props = { +mediaSource: MediaSource, +pendingUpload?: ?PendingMultimediaUpload, +remove?: (uploadID: string) => void, +multimediaCSSClass: string, +multimediaImageCSSClass: string, }; -type Props = { - ...BaseProps, - +pushModal: PushModal, -}; -class Multimedia extends React.PureComponent { - componentDidUpdate(prevProps: Props) { - const { mediaSource, pendingUpload } = this.props; +function Multimedia(props: Props): React.Node { + const { mediaSource, pendingUpload } = props; + const prevPropsRef = React.useRef({ mediaSource, pendingUpload }); + React.useEffect(() => { + const prevProps = prevPropsRef.current; + prevPropsRef.current = { mediaSource, pendingUpload }; + if ( prevProps.mediaSource.type === 'encrypted_photo' || prevProps.mediaSource.type === 'encrypted_video' ) { return; } - const prevUri = prevProps.mediaSource?.uri; + const prevUri = prevProps.mediaSource.uri; if (!prevUri || mediaSource.uri === prevUri) { return; } if ( (!pendingUpload || pendingUpload.uriIsReal) && (!prevProps.pendingUpload || !prevProps.pendingUpload.uriIsReal) ) { URL.revokeObjectURL(prevUri); } - } + }, [mediaSource, pendingUpload]); + + const { remove: removeProp } = props; + const handleRemove = React.useCallback( + (event: SyntheticEvent) => { + event.stopPropagation(); + invariant( + removeProp && pendingUpload, + 'Multimedia cannot be removed as either remove or pendingUpload ' + + 'are unspecified', + ); + removeProp(pendingUpload.localID); + }, + [removeProp, pendingUpload], + ); - render(): React.Node { - let progressIndicator, errorIndicator, removeButton; - - const { - pendingUpload, - remove, - mediaSource, - multimediaImageCSSClass, - multimediaCSSClass, - } = this.props; - if (pendingUpload) { - const { progressPercent, failed } = pendingUpload; - - if (progressPercent !== 0 && progressPercent !== 1) { - const outOfHundred = Math.floor(progressPercent * 100); - const text = `${outOfHundred}%`; - progressIndicator = ( - - ); - } - - if (failed) { - errorIndicator = ( - - ); - } - - if (remove) { - removeButton = ( - - ); - } + const { pushModal } = useModalContext(); + const handleClick = React.useCallback(() => { + pushModal(); + }, [pushModal, mediaSource]); + + let progressIndicator, errorIndicator, removeButton; + + const { multimediaImageCSSClass, multimediaCSSClass } = props; + if (pendingUpload) { + const { progressPercent, failed } = pendingUpload; + + if (progressPercent !== 0 && progressPercent !== 1) { + const outOfHundred = Math.floor(progressPercent * 100); + const text = `${outOfHundred}%`; + progressIndicator = ( + + ); } - const imageContainerClasses = [ - css.multimediaImage, - multimediaImageCSSClass, - ]; - imageContainerClasses.push(css.clickable); - - // Media element is the actual image or video element (or encrypted version) - let mediaElement; - if (mediaSource.type === 'photo') { - const uri = fetchableMediaURI(mediaSource.uri); - mediaElement = ; - } else if (mediaSource.type === 'video') { - const uri = fetchableMediaURI(mediaSource.uri); - mediaElement = ( - + if (failed) { + errorIndicator = ( + ); - } else if ( - mediaSource.type === 'encrypted_photo' || - mediaSource.type === 'encrypted_video' - ) { - const { ...encryptedMediaProps } = mediaSource; - mediaElement = ; } - // Media node is the container for the media element (button if photo) - let mediaNode; - if ( - mediaSource.type === 'photo' || - mediaSource.type === 'encrypted_photo' - ) { - mediaNode = ( - ); - } else { - mediaNode = ( -
{mediaElement}
- ); } + } - const containerClasses = [css.multimedia, multimediaCSSClass]; - return ( - - {mediaNode} - {progressIndicator} - {errorIndicator} - + const imageContainerClasses = [css.multimediaImage, multimediaImageCSSClass]; + imageContainerClasses.push(css.clickable); + + // Media element is the actual image or video element (or encrypted version) + let mediaElement; + if (mediaSource.type === 'photo') { + const uri = fetchableMediaURI(mediaSource.uri); + mediaElement = ; + } else if (mediaSource.type === 'video') { + const uri = fetchableMediaURI(mediaSource.uri); + mediaElement = ( + ); + } else if ( + mediaSource.type === 'encrypted_photo' || + mediaSource.type === 'encrypted_video' + ) { + mediaElement = ; } - remove: (event: SyntheticEvent) => void = event => { - event.stopPropagation(); - const { remove, pendingUpload } = this.props; - invariant( - remove && pendingUpload, - 'Multimedia cannot be removed as either remove or pendingUpload ' + - 'are unspecified', + // Media node is the container for the media element (button if photo) + let mediaNode; + if (mediaSource.type === 'photo' || mediaSource.type === 'encrypted_photo') { + mediaNode = ( + + ); + } else { + mediaNode = ( +
{mediaElement}
); - remove(pendingUpload.localID); - }; + } - onClick: () => void = () => { - const { pushModal, mediaSource } = this.props; - pushModal(); - }; + const containerClasses = [css.multimedia, multimediaCSSClass]; + return ( + + {mediaNode} + {progressIndicator} + {errorIndicator} + + ); } -function ConnectedMultimediaContainer(props: BaseProps): React.Node { - const modalContext = useModalContext(); - - return ; -} +const MemoizedMultimedia: React.ComponentType = + React.memo(Multimedia); -export default ConnectedMultimediaContainer; +export default MemoizedMultimedia;