From 28f5a2bd1c05f8df989f8e73aa0eb02e48fc420a Mon Sep 17 00:00:00 2001 From: Josh Perez <60019601+josh-signal@users.noreply.github.com> Date: Tue, 24 Aug 2021 17:47:14 -0400 Subject: [PATCH] Fixes view once videos in lightbox --- stylesheets/_variables.scss | 1 + stylesheets/components/Lightbox.scss | 2 +- ts/components/Lightbox.stories.tsx | 16 +++++++ ts/components/Lightbox.tsx | 72 +++++++++++++++++----------- ts/util/lint/exceptions.json | 14 ------ ts/views/conversation_view.ts | 66 +++++++++++++++---------- 6 files changed, 104 insertions(+), 67 deletions(-) diff --git a/stylesheets/_variables.scss b/stylesheets/_variables.scss index ac1ce07ca7..f3d7dccd30 100644 --- a/stylesheets/_variables.scss +++ b/stylesheets/_variables.scss @@ -41,6 +41,7 @@ $color-black-alpha-50: rgba($color-black, 0.5); $color-black-alpha-60: rgba($color-black, 0.6); $color-black-alpha-70: rgba($color-black, 0.7); $color-black-alpha-80: rgba($color-black, 0.8); +$color-black-alpha-90: rgba($color-black, 0.9); $color-ultramarine-dark: #1851b4; $color-ultramarine-icon: #3a76f0; diff --git a/stylesheets/components/Lightbox.scss b/stylesheets/components/Lightbox.scss index 7ed343611e..613aff664e 100644 --- a/stylesheets/components/Lightbox.scss +++ b/stylesheets/components/Lightbox.scss @@ -3,7 +3,7 @@ .Lightbox { &__container { - background-color: $color-black-alpha-80; + background-color: $color-black-alpha-90; bottom: 0; display: flex; flex-direction: column; diff --git a/ts/components/Lightbox.stories.tsx b/ts/components/Lightbox.stories.tsx index 138198a541..3a62daf993 100644 --- a/ts/components/Lightbox.stories.tsx +++ b/ts/components/Lightbox.stories.tsx @@ -52,6 +52,7 @@ function createMediaItem( const createProps = (overrideProps: Partial = {}): PropsType => ({ close: action('close'), i18n, + isViewOnce: Boolean(overrideProps.isViewOnce), media: overrideProps.media || [], onSave: action('onSave'), selectedIndex: number('selectedIndex', overrideProps.selectedIndex || 0), @@ -288,3 +289,18 @@ story.add('Conversation Header', () => ( ]} /> )); + +story.add('View Once Video', () => ( + +)); diff --git a/ts/components/Lightbox.tsx b/ts/components/Lightbox.tsx index 0054440ca6..1de438fbb0 100644 --- a/ts/components/Lightbox.tsx +++ b/ts/components/Lightbox.tsx @@ -9,9 +9,10 @@ import React, { useRef, useState, } from 'react'; -import moment from 'moment'; import classNames from 'classnames'; +import moment from 'moment'; import { createPortal } from 'react-dom'; +import { noop } from 'lodash'; import * as GoogleChrome from '../util/GoogleChrome'; import { AttachmentType, isGIF } from '../types/Attachment'; @@ -20,12 +21,14 @@ import { ConversationType } from '../state/ducks/conversations'; import { IMAGE_PNG, isImage, isVideo } from '../types/MIME'; import { LocalizerType } from '../types/Util'; import { MediaItemType, MessageAttributesType } from '../types/MediaItem'; +import { formatDuration } from '../util/formatDuration'; export type PropsType = { children?: ReactNode; close: () => void; getConversation?: (id: string) => ConversationType; i18n: LocalizerType; + isViewOnce?: boolean; media: Array; onForward?: (messageId: string) => void; onSave?: (options: { @@ -42,20 +45,24 @@ export function Lightbox({ getConversation, media, i18n, + isViewOnce = false, onForward, onSave, - selectedIndex: initialSelectedIndex, + selectedIndex: initialSelectedIndex = 0, }: PropsType): JSX.Element | null { const [root, setRoot] = React.useState(); const [selectedIndex, setSelectedIndex] = useState( - initialSelectedIndex || 0 + initialSelectedIndex ); const [previousFocus, setPreviousFocus] = useState(); + const [videoElement, setVideoElement] = useState( + null + ); + const [videoTime, setVideoTime] = useState(); const [zoomed, setZoomed] = useState(false); const containerRef = useRef(null); const focusRef = useRef(null); - const videoRef = useRef(null); const restorePreviousFocus = useCallback(() => { if (previousFocus && previousFocus.focus) { @@ -73,6 +80,13 @@ export function Lightbox({ ); }, [media]); + const onTimeUpdate = useCallback(() => { + if (!videoElement) { + return; + } + setVideoTime(videoElement.currentTime); + }, [setVideoTime, videoElement]); + const handleSave = () => { const mediaItem = media[selectedIndex]; const { attachment, message, index } = mediaItem; @@ -130,18 +144,17 @@ export function Lightbox({ close(); }; - const playVideo = () => { - const video = videoRef.current; - if (!video) { + const playVideo = useCallback(() => { + if (!videoElement) { return; } - if (video.paused) { - video.play(); + if (videoElement.paused) { + videoElement.play(); } else { - video.pause(); + videoElement.pause(); } - }; + }, [videoElement]); useEffect(() => { const div = document.createElement('div'); @@ -176,22 +189,22 @@ export function Lightbox({ }, [onKeyDown]); useEffect(() => { - // Wait until we're added to the DOM. ConversationView first creates this - // view, then appends its elements into the DOM. - const timeout = window.setTimeout(() => { - playVideo(); + playVideo(); - if (focusRef && focusRef.current) { - focusRef.current.focus(); - } - }); + if (focusRef && focusRef.current) { + focusRef.current.focus(); + } - return () => { - if (timeout) { - window.clearTimeout(timeout); - } - }; - }, [selectedIndex]); + if (videoElement && isViewOnce) { + videoElement.addEventListener('timeupdate', onTimeUpdate); + + return () => { + videoElement.removeEventListener('timeupdate', onTimeUpdate); + }; + } + + return noop; + }, [isViewOnce, onTimeUpdate, playVideo, videoElement]); const { attachment, contentType, loop = false, objectURL, message } = media[selectedIndex] || {}; @@ -248,14 +261,14 @@ export function Lightbox({ ); } } else if (isVideoTypeSupported) { - const shouldLoop = loop || isGIF([attachment]); + const shouldLoop = loop || isGIF([attachment]) || isViewOnce; content = ( @@ -388,6 +401,11 @@ export function Lightbox({ {!zoomed && (
+ {isViewOnce && videoTime ? ( +
+ {formatDuration(videoTime)} +
+ ) : null} {caption ? (
{caption}
) : null} diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index d2e948f7b7..96b45f0ad6 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -13535,13 +13535,6 @@ "reasonCategory": "usageTrusted", "updated": "2021-08-23T18:39:37.081Z" }, - { - "rule": "React-useRef", - "path": "ts/components/Lightbox.js", - "line": " const videoRef = react_1.useRef(null);", - "reasonCategory": "usageTrusted", - "updated": "2021-08-23T18:39:37.081Z" - }, { "rule": "React-useRef", "path": "ts/components/Lightbox.tsx", @@ -13556,13 +13549,6 @@ "reasonCategory": "usageTrusted", "updated": "2021-08-23T18:39:37.081Z" }, - { - "rule": "React-useRef", - "path": "ts/components/Lightbox.tsx", - "line": " const videoRef = useRef(null);", - "reasonCategory": "usageTrusted", - "updated": "2021-08-23T18:39:37.081Z" - }, { "rule": "React-createRef", "path": "ts/components/MainHeader.js", diff --git a/ts/views/conversation_view.ts b/ts/views/conversation_view.ts index 8b4a5f3227..cec3fb3b5d 100644 --- a/ts/views/conversation_view.ts +++ b/ts/views/conversation_view.ts @@ -2602,7 +2602,19 @@ Whisper.ConversationView = Whisper.View.extend({ contentType: attachment.contentType, index, attachment, - message, + message: { + attachments: message.attachments || [], + conversationId: + window.ConversationController.get( + window.ConversationController.ensureContactIds({ + uuid: message.sourceUuid, + e164: message.source, + }) + )?.id || message.conversationId, + id: message.id, + received_at: message.received_at, + received_at_ms: Number(message.received_at_ms), + }, }; }); }) @@ -2652,22 +2664,9 @@ Whisper.ConversationView = Whisper.View.extend({ } case 'media': { - const selectedIndex = media.findIndex( - mediaMessage => mediaMessage.attachment.path === attachment.path - ); - this.lightboxGalleryView = new Whisper.ReactWrapperView({ - className: 'lightbox-wrapper', - Component: window.Signal.Components.Lightbox, - props: { - media, - onSave: saveAttachment, - selectedIndex, - }, - onClose: () => window.Signal.Backbone.Views.Lightbox.hide(), - }); - window.Signal.Backbone.Views.Lightbox.show( - this.lightboxGalleryView.el - ); + const selectedMedia = + media.find(item => attachment.path === item.path) || media[0]; + this.showLightboxForMedia(selectedMedia, media); break; } @@ -2947,12 +2946,25 @@ Whisper.ConversationView = Whisper.View.extend({ const { path, contentType } = tempAttachment; return { - objectURL: getAbsoluteTempPath(path), - contentType, - onSave: null, // important so download button is omitted + media: [ + { + attachment: tempAttachment, + objectURL: getAbsoluteTempPath(path), + contentType, + index: 0, + message: { + attachments: message.get('attachments'), + id: message.get('id'), + conversationId: message.get('conversationId'), + received_at: message.get('received_at'), + received_at_ms: message.get('received_at_ms'), + }, + }, + ], isViewOnce: true, }; }; + this.lightboxView = new Whisper.ReactWrapperView({ className: 'lightbox-wrapper', Component: window.Signal.Components.Lightbox, @@ -3044,8 +3056,7 @@ Whisper.ConversationView = Whisper.View.extend({ showLightboxForMedia( selectedMediaItem: MediaItemType, - media: Array = [], - loop = false + media: Array = [] ) { const onSave = async (options: WhatIsThis = {}) => { const fullPath = await window.Signal.Types.Attachment.save({ @@ -3071,7 +3082,6 @@ Whisper.ConversationView = Whisper.View.extend({ Component: window.Signal.Components.Lightbox, props: { getConversation: getConversationSelector(window.reduxStore.getState()), - loop, media, onForward: this.showForwardMessageModal.bind(this), onSave, @@ -3127,7 +3137,13 @@ Whisper.ConversationView = Whisper.View.extend({ message: { attachments: message.get('attachments'), id: message.get('id'), - conversationId: message.get('conversationId'), + conversationId: + window.ConversationController.get( + window.ConversationController.ensureContactIds({ + uuid: message.get('sourceUuid'), + e164: message.get('source'), + }) + )?.id || message.get('conversationId'), received_at: message.get('received_at'), received_at_ms: message.get('received_at_ms'), }, @@ -3140,7 +3156,7 @@ Whisper.ConversationView = Whisper.View.extend({ const selectedMedia = media.find(item => attachment.path === item.path) || media[0]; - this.showLightboxForMedia(selectedMedia, media, loop); + this.showLightboxForMedia(selectedMedia, media); }, showContactModal(contactId: string) {