From c25d6eb9a8b7c9b917b35d08ed0818d4a6eb6510 Mon Sep 17 00:00:00 2001 From: virtuacoplenny Date: Thu, 13 Sep 2018 08:20:22 -0700 Subject: [PATCH] [RN] Implement tile view * feat(tile-view): initial implementation for mobile - Create a tile view component for displaying thumbnails in a two-dimensional grid. - Update the existing TileViewButton so it shows a label in the overflow menu. - Modify conference so it can display TileView while hiding Filmstrip. - Modify Thumbnail so its width/height can be set and to prevent pinning while in tile view mode. * use style array for thumbnail styles * change ternary to math.min for expressiveness * use dimensiondetector * pass explicit disableTint prop * use makeAspectRatioAware instead of aspectRatio prop * update docs * fix docs again (fix laziest copy/paste job I've ever done) * large-video: rename onPress prop to onClick * change forEach to for...of * use truthy check fallthrough logic instead of explicit if * put tile view button second to last in menu * move spacer to a constant * the magical incantation to make flow shut up --- .../react/components/AbstractContainer.js | 101 +++--- .../base/react/components/native/Container.js | 10 +- .../base/react/components/web/Container.js | 3 +- .../components/Conference.native.js | 45 ++- .../filmstrip/components/native/Thumbnail.js | 103 ++++-- .../filmstrip/components/native/TileView.js | 337 ++++++++++++++++++ .../filmstrip/components/native/index.js | 1 + react/features/filmstrip/components/styles.js | 13 + .../components/LargeVideo.native.js | 8 +- .../toolbox/components/native/OverflowMenu.js | 2 + .../video-layout/components/TileViewButton.js | 1 + react/features/video-layout/functions.js | 3 +- 12 files changed, 532 insertions(+), 95 deletions(-) create mode 100644 react/features/filmstrip/components/native/TileView.js diff --git a/react/features/base/react/components/AbstractContainer.js b/react/features/base/react/components/AbstractContainer.js index 2a78d1282..14f90663d 100644 --- a/react/features/base/react/components/AbstractContainer.js +++ b/react/features/base/react/components/AbstractContainer.js @@ -1,54 +1,71 @@ /* @flow */ -import PropTypes from 'prop-types'; import React, { Component } from 'react'; +/** + * {@code AbstractContainer} component's property types. + */ +export type Props = { + + /** + * An optional accessibility label to apply to the container root. + */ + accessibilityLabel?: string, + + /** + * Whether or not this element is an accessibility element. + */ + accessible?: boolean, + + /** + * React Elements to display within the component. + */ + children: React$Node | Object, + + /** + * The event handler/listener to be invoked when this + * {@code AbstractContainer} is clicked on Web or pressed on React + * Native. If {@code onClick} is defined and {@link touchFeedback} is + * undefined, {@code touchFeedback} is considered defined as + * {@code true}. + */ + onClick?: ?Function, + + /** + * The style (as in stylesheet) to be applied to this + * {@code AbstractContainer}. + */ + style?: Array | Object, + + /** + * If this instance is to provide visual feedback when touched, then + * {@code true}; otherwise, {@code false}. If {@code touchFeedback} is + * undefined and {@link onClick} is defined, {@code touchFeedback} is + * considered defined as {@code true}. + */ + touchFeedback?: ?Function, + + /** + * Color to display when clicked. + */ + underlayColor?: string, + + /** + * If this {@code AbstractContainer} is to be visible, then {@code true} + * or {@code false} if this instance is to be hidden or not rendered at + * all. + */ + visible?: ?boolean +}; + + /** * Abstract (base) class for container of React {@link Component} children with * a style. * * @extends Component */ -export default class AbstractContainer extends Component<*> { - /** - * {@code AbstractContainer} component's property types. - * - * @static - */ - static propTypes = { - children: PropTypes.node, - - /** - * The event handler/listener to be invoked when this - * {@code AbstractContainer} is clicked on Web or pressed on React - * Native. If {@code onClick} is defined and {@link touchFeedback} is - * undefined, {@code touchFeedback} is considered defined as - * {@code true}. - */ - onClick: PropTypes.func, - - /** - * The style (as in stylesheet) to be applied to this - * {@code AbstractContainer}. - */ - style: PropTypes.object, - - /** - * If this instance is to provide visual feedback when touched, then - * {@code true}; otherwise, {@code false}. If {@code touchFeedback} is - * undefined and {@link onClick} is defined, {@code touchFeedback} is - * considered defined as {@code true}. - */ - touchFeedback: PropTypes.bool, - - /** - * If this {@code AbstractContainer} is to be visible, then {@code true} - * or {@code false} if this instance is to be hidden or not rendered at - * all. - */ - visible: PropTypes.bool - }; - +export default class AbstractContainer extends Component

{ /** * Renders this {@code AbstractContainer} as a React {@code Component} of a * specific type. @@ -61,7 +78,7 @@ export default class AbstractContainer extends Component<*> { * @protected * @returns {ReactElement} */ - _render(type, props) { + _render(type, props?: P) { const { children, diff --git a/react/features/base/react/components/native/Container.js b/react/features/base/react/components/native/Container.js index b586e4673..95c7c2080 100644 --- a/react/features/base/react/components/native/Container.js +++ b/react/features/base/react/components/native/Container.js @@ -8,20 +8,14 @@ import { } from 'react-native'; import AbstractContainer from '../AbstractContainer'; +import type { Props } from '../AbstractContainer'; /** * Represents a container of React Native/mobile {@link Component} children. * * @extends AbstractContainer */ -export default class Container extends AbstractContainer { - /** - * {@code Container} component's property types. - * - * @static - */ - static propTypes = AbstractContainer.propTypes; - +export default class Container extends AbstractContainer

{ /** * Implements React's {@link Component#render()}. * diff --git a/react/features/base/react/components/web/Container.js b/react/features/base/react/components/web/Container.js index 721d4b731..7aa5b14ad 100644 --- a/react/features/base/react/components/web/Container.js +++ b/react/features/base/react/components/web/Container.js @@ -1,13 +1,14 @@ /* @flow */ import AbstractContainer from '../AbstractContainer'; +import type { Props } from '../AbstractContainer'; /** * Represents a container of React/Web {@link Component} children with a style. * * @extends AbstractContainer */ -export default class Container extends AbstractContainer { +export default class Container extends AbstractContainer

{ /** * {@code Container} component's property types. * diff --git a/react/features/conference/components/Conference.native.js b/react/features/conference/components/Conference.native.js index 046114088..16c182573 100644 --- a/react/features/conference/components/Conference.native.js +++ b/react/features/conference/components/Conference.native.js @@ -17,12 +17,18 @@ import { import { TestConnectionInfo } from '../../base/testing'; import { createDesiredLocalTracks } from '../../base/tracks'; import { ConferenceNotification } from '../../calendar-sync'; -import { FILMSTRIP_SIZE, Filmstrip, isFilmstripVisible } from '../../filmstrip'; +import { + FILMSTRIP_SIZE, + Filmstrip, + isFilmstripVisible, + TileView +} from '../../filmstrip'; import { LargeVideo } from '../../large-video'; import { CalleeInfoContainer } from '../../invite'; import { NotificationsContainer } from '../../notifications'; import { Captions } from '../../subtitles'; import { setToolboxVisible, Toolbox } from '../../toolbox'; +import { shouldDisplayTileView } from '../../video-layout'; import styles from './styles'; @@ -115,6 +121,13 @@ type Props = { */ _setToolboxVisible: Function, + /** + * Whether or not the layout should change to support tile view mode. + * + * @private + */ + _shouldDisplayTileView: boolean, + /** * The indicator which determines whether the Toolbox is visible. * @@ -252,6 +265,12 @@ class Conference extends Component { * @returns {ReactElement} */ render() { + const { + _connecting, + _reducedUI, + _shouldDisplayTileView + } = this.props; + return ( { {/* * The LargeVideo is the lowermost stacking layer. - */} - + */ + _shouldDisplayTileView + ? + : + } {/* * If there is a ringing call, show the callee's info. */ - this.props._reducedUI || + _reducedUI || } {/* * The activity/loading indicator goes above everything, except * the toolbox/toolbars and the dialogs. */ - this.props._connecting + _connecting && @@ -304,8 +326,9 @@ class Conference extends Component { * name and grouping stem from the fact that these two * React Components depict the videos of the conference's * participants. - */} - + */ + _shouldDisplayTileView ? undefined : + } @@ -548,6 +571,14 @@ function _mapStateToProps(state) { */ _room: room, + /** + * Whether or not the layout should change to support tile view mode. + * + * @private + * @type {boolean} + */ + _shouldDisplayTileView: shouldDisplayTileView(state), + /** * The indicator which determines whether the Toolbox is visible. * diff --git a/react/features/filmstrip/components/native/Thumbnail.js b/react/features/filmstrip/components/native/Thumbnail.js index 04154f592..8bbcee099 100644 --- a/react/features/filmstrip/components/native/Thumbnail.js +++ b/react/features/filmstrip/components/native/Thumbnail.js @@ -1,4 +1,5 @@ -import PropTypes from 'prop-types'; +// @flow + import React, { Component } from 'react'; import { connect } from 'react-redux'; @@ -18,31 +19,67 @@ import { AVATAR_SIZE } from '../styles'; import styles from './styles'; import VideoMutedIndicator from './VideoMutedIndicator'; +/** + * Thumbnail component's property types. + */ +type Props = { + + /** + * The Redux representation of the participant's audio track. + */ + _audioTrack: Object, + + /** + * The Redux representation of the state "features/large-video". + */ + _largeVideo: Object, + + /** + * The Redux representation of the participant's video track. + */ + _videoTrack: Object, + + /** + * If true, tapping on the thumbnail will not pin the participant to large + * video. By default tapping does pin the participant. + */ + disablePin?: boolean, + + /** + * If true, there will be no color overlay (tint) on the thumbnail + * indicating the participant associated with the thumbnail is displayed on + * large video. By default there will be a tint. + */ + disableTint?: boolean, + + /** + * Invoked to trigger state changes in Redux. + */ + dispatch: Dispatch<*>, + + /** + * The Redux representation of the participant to display. + */ + participant: Object, + + /** + * Optional styling to add or override on the Thumbnail component root. + */ + styleOverrides?: Object +}; + /** * React component for video thumbnail. * * @extends Component */ -class Thumbnail extends Component { - /** - * Thumbnail component's property types. - * - * @static - */ - static propTypes = { - _audioTrack: PropTypes.object, - _largeVideo: PropTypes.object, - _videoTrack: PropTypes.object, - dispatch: PropTypes.func, - participant: PropTypes.object - }; - +class Thumbnail extends Component { /** * Initializes new Video Thumbnail component. * * @param {Object} props - Component props. */ - constructor(props) { + constructor(props: Props) { super(props); // Bind event handlers so they are only bound once for every instance. @@ -56,19 +93,14 @@ class Thumbnail extends Component { * @returns {ReactElement} */ render() { - const audioTrack = this.props._audioTrack; - const largeVideo = this.props._largeVideo; - const participant = this.props.participant; - const videoTrack = this.props._videoTrack; - - let style = styles.thumbnail; - - if (participant.pinned) { - style = { - ...style, - ...styles.thumbnailPinned - }; - } + const { + _audioTrack: audioTrack, + _largeVideo: largeVideo, + _videoTrack: videoTrack, + disablePin, + disableTint, + participant + } = this.props; // We don't render audio in any of the following: // 1. The audio (source) is muted. There's no practical reason (that we @@ -85,8 +117,13 @@ class Thumbnail extends Component { return ( + onClick = { disablePin ? undefined : this._onClick } + style = { [ + styles.thumbnail, + participant.pinned && !disablePin + ? styles.thumbnailPinned : null, + this.props.styleOverrides || null + ] }> { renderAudio &&