diff --git a/web/media/multimedia.react.js b/web/media/multimedia.react.js --- a/web/media/multimedia.react.js +++ b/web/media/multimedia.react.js @@ -10,10 +10,7 @@ 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'; @@ -34,21 +31,21 @@ +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' @@ -56,7 +53,7 @@ return; } - const prevUri = prevProps.mediaSource?.uri; + const prevUri = prevProps.mediaSource.uri; if (!prevUri || mediaSource.uri === prevUri) { return; } @@ -66,128 +63,113 @@ ) { 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;