From 409255f0563f9ca4378a43a1767c7f192a44d06a Mon Sep 17 00:00:00 2001 From: Lyubo Marinov Date: Mon, 5 Jun 2017 13:00:02 -0500 Subject: [PATCH] [React] Cross-platform Components Introduce certain React Components which may be used to write cross-platform source code such as Audio like Web's audio, Container like Web's div, Text like Web's p, etc. --- .../base/media/components/AbstractAudio.js | 111 ++++++++++++++++++ .../media/components/AbstractVideoTrack.js | 5 +- react/features/base/media/components/_.web.js | 1 + .../base/media/components/native/Audio.js | 18 +-- .../base/media/components/native/Video.js | 12 +- .../media/components/native/VideoTrack.js | 2 +- .../base/media/components/native/index.js | 4 +- .../base/media/components/web/Audio.js | 26 ++++ .../base/media/components/web/index.js | 1 + .../participants/components/Avatar.native.js | 22 +++- .../participants/components/Avatar.web.js | 16 ++- .../react/components/AbstractContainer.js | 5 + .../base/react/components/Container.native.js | 58 --------- .../base/react/components/Container.web.js | 0 .../base/react/components/Link.web.js | 0 .../react/components/Watermarks.native.js | 0 .../base/react/components/_.native.js | 1 + react/features/base/react/components/_.web.js | 1 + react/features/base/react/components/index.js | 4 +- .../base/react/components/native/Container.js | 55 +++++++++ .../{Link.native.js => native/Link.js} | 12 +- .../base/react/components/native/Text.js | 1 + .../base/react/components/native/index.js | 3 + .../base/react/components/web/Container.js | 25 ++++ .../base/react/components/web/Text.js | 19 +++ .../{Watermarks.web.js => web/Watermarks.js} | 2 +- .../base/react/components/web/index.js | 3 + .../welcome/components/WelcomePage.native.js | 4 +- 28 files changed, 314 insertions(+), 97 deletions(-) create mode 100644 react/features/base/media/components/AbstractAudio.js create mode 100644 react/features/base/media/components/web/Audio.js create mode 100644 react/features/base/media/components/web/index.js delete mode 100644 react/features/base/react/components/Container.native.js delete mode 100644 react/features/base/react/components/Container.web.js delete mode 100644 react/features/base/react/components/Link.web.js delete mode 100644 react/features/base/react/components/Watermarks.native.js create mode 100644 react/features/base/react/components/_.native.js create mode 100644 react/features/base/react/components/_.web.js create mode 100644 react/features/base/react/components/native/Container.js rename react/features/base/react/components/{Link.native.js => native/Link.js} (90%) create mode 100644 react/features/base/react/components/native/Text.js create mode 100644 react/features/base/react/components/native/index.js create mode 100644 react/features/base/react/components/web/Container.js create mode 100644 react/features/base/react/components/web/Text.js rename react/features/base/react/components/{Watermarks.web.js => web/Watermarks.js} (99%) create mode 100644 react/features/base/react/components/web/index.js diff --git a/react/features/base/media/components/AbstractAudio.js b/react/features/base/media/components/AbstractAudio.js new file mode 100644 index 000000000..a2c19a397 --- /dev/null +++ b/react/features/base/media/components/AbstractAudio.js @@ -0,0 +1,111 @@ +import React, { Component } from 'react'; + +/** + * The React {@link Component} which is similar to Web's + * {@code HTMLAudioElement}. + */ +export default class AbstractAudio extends Component { + /** + * The (reference to the) {@link ReactElement} which actually implements + * this {@code AbstractAudio}. + */ + _ref: ?Object + + /** + * {@code AbstractAudio} component's property types. + * + * @static + */ + static propTypes = { + /** + * The URL of a media resource to use in the element. + * + * @type {string} + */ + src: React.PropTypes.string, + stream: React.PropTypes.object + }; + + /** + * Initializes a new {@code AbstractAudio} instance. + * + * @param {Object} props - The read-only properties with which the new + * instance is to be initialized. + */ + constructor(props) { + super(props); + + // Bind event handlers so they are only bound once for every instance. + this._setRef = this._setRef.bind(this); + } + + /** + * Attempts to pause the playback of the media. + * + * @public + * @returns {void} + */ + pause() { + this._ref && typeof this._ref.pause === 'function' && this._ref.pause(); + } + + /** + * Attempts to being the playback of the media. + * + * @public + * @returns {void} + */ + play() { + this._ref && typeof this._ref.play === 'function' && this._ref.play(); + } + + /** + * Renders this {@code AbstractAudio} as a React {@link Component} of a + * specific type. + * + * @param {string|ReactClass} type - The type of the React {@code Component} + * which is to be rendered. + * @param {Object|undefined} props - The read-only React {@code Component} + * properties, if any, to render. If {@code undefined}, the props of this + * instance will be rendered. + * @protected + * @returns {ReactElement} + */ + _render(type, props) { + const { + children, + + /* eslint-disable no-unused-vars */ + + // The following properties are consumed by React itself so they are + // to not be propagated. + ref, + + /* eslint-enable no-unused-vars */ + + ...filteredProps + } = props || this.props; + + return ( + React.createElement( + type, + { + ...filteredProps, + ref: this._setRef + }, + children)); + } + + /** + * Set the (reference to the) {@link ReactElement} which actually implements + * this {@code AbstractAudio}. + * + * @param {Object} ref - The (reference to the) {@code ReactElement} which + * actually implements this {@code AbstractAudio}. + * @private + * @returns {void} + */ + _setRef(ref) { + this._ref = ref; + } +} diff --git a/react/features/base/media/components/AbstractVideoTrack.js b/react/features/base/media/components/AbstractVideoTrack.js index d15cded48..4c7bbe25b 100644 --- a/react/features/base/media/components/AbstractVideoTrack.js +++ b/react/features/base/media/components/AbstractVideoTrack.js @@ -6,11 +6,12 @@ import { shouldRenderVideoTrack } from '../functions'; import { Video } from './_'; /** - * Component that renders video element for a specified video track. + * Implements a React {@link Component} that renders video element for a + * specific video track. * * @abstract */ -export class AbstractVideoTrack extends Component { +export default class AbstractVideoTrack extends Component { /** * AbstractVideoTrack component's property types. * diff --git a/react/features/base/media/components/_.web.js b/react/features/base/media/components/_.web.js index e69de29bb..b80c83af3 100644 --- a/react/features/base/media/components/_.web.js +++ b/react/features/base/media/components/_.web.js @@ -0,0 +1 @@ +export * from './web'; diff --git a/react/features/base/media/components/native/Audio.js b/react/features/base/media/components/native/Audio.js index 82d9cfad1..6335b8d7f 100644 --- a/react/features/base/media/components/native/Audio.js +++ b/react/features/base/media/components/native/Audio.js @@ -1,19 +1,19 @@ -import React, { Component } from 'react'; +/* @flow */ + +import AbstractAudio from '../AbstractAudio'; /** - * The React Native component which is similar to Web's audio element and wraps - * around react-native-webrtc's RTCView. + * The React Native/mobile {@link Component} which is similar to Web's + * {@code HTMLAudioElement} and wraps around react-native-webrtc's + * {@link RTCView}. */ -export class Audio extends Component { +export default class Audio extends AbstractAudio { /** - * Audio component's property types. + * {@code Audio} component's property types. * * @static */ - static propTypes = { - muted: React.PropTypes.bool, - stream: React.PropTypes.object - }; + static propTypes = AbstractAudio.propTypes; /** * Implements React's {@link Component#render()}. diff --git a/react/features/base/media/components/native/Video.js b/react/features/base/media/components/native/Video.js index a19dd570b..471de3223 100644 --- a/react/features/base/media/components/native/Video.js +++ b/react/features/base/media/components/native/Video.js @@ -1,21 +1,23 @@ +/* @flow */ + import React, { Component } from 'react'; import { RTCView } from 'react-native-webrtc'; import { styles } from './styles'; /** - * The React Native component which is similar to Web's video element and wraps - * around react-native-webrtc's RTCView. + * The React Native {@link Component} which is similar to Web's + * {@code HTMLVideoElement} and wraps around react-native-webrtc's + * {@link RTCView}. */ -export class Video extends Component { +export default class Video extends Component { /** - * Video component's property types. + * {@code Video} component's property types. * * @static */ static propTypes = { mirror: React.PropTypes.bool, - muted: React.PropTypes.bool, onPlaying: React.PropTypes.func, stream: React.PropTypes.object, diff --git a/react/features/base/media/components/native/VideoTrack.js b/react/features/base/media/components/native/VideoTrack.js index 444b9d053..a5ad3dd3f 100644 --- a/react/features/base/media/components/native/VideoTrack.js +++ b/react/features/base/media/components/native/VideoTrack.js @@ -2,7 +2,7 @@ import React from 'react'; import { Animated } from 'react-native'; import { connect } from 'react-redux'; -import { AbstractVideoTrack } from '../AbstractVideoTrack'; +import AbstractVideoTrack from '../AbstractVideoTrack'; import { styles } from './styles'; /** diff --git a/react/features/base/media/components/native/index.js b/react/features/base/media/components/native/index.js index 50258385e..bb4237197 100644 --- a/react/features/base/media/components/native/index.js +++ b/react/features/base/media/components/native/index.js @@ -1,3 +1,3 @@ -export * from './Audio'; -export * from './Video'; +export { default as Audio } from './Audio'; +export { default as Video } from './Video'; export { default as VideoTrack } from './VideoTrack'; diff --git a/react/features/base/media/components/web/Audio.js b/react/features/base/media/components/web/Audio.js new file mode 100644 index 000000000..22b58b381 --- /dev/null +++ b/react/features/base/media/components/web/Audio.js @@ -0,0 +1,26 @@ +/* @flow */ + +import AbstractAudio from '../AbstractAudio'; + +/** + * The React/Web {@link Component} which is similar to and wraps around + * {@code HTMLAudioElement} in order to facilitate cross-platform source code. + */ +export default class Audio extends AbstractAudio { + /** + * {@code Audio} component's property types. + * + * @static + */ + static propTypes = AbstractAudio.propTypes; + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return super._render('audio'); + } +} diff --git a/react/features/base/media/components/web/index.js b/react/features/base/media/components/web/index.js new file mode 100644 index 000000000..528c1e495 --- /dev/null +++ b/react/features/base/media/components/web/index.js @@ -0,0 +1 @@ +export { default as Audio } from './Audio'; diff --git a/react/features/base/participants/components/Avatar.native.js b/react/features/base/participants/components/Avatar.native.js index b0bdcde9d..3c10b9c54 100644 --- a/react/features/base/participants/components/Avatar.native.js +++ b/react/features/base/participants/components/Avatar.native.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import { Image } from 'react-native'; /** - * Display a participant avatar. + * Implements an avatar as a React Native/mobile {@link Component}. */ export default class Avatar extends Component { /** @@ -12,10 +12,16 @@ export default class Avatar extends Component { */ static propTypes = { /** - * The optional style to add to an Avatar in order to customize its base - * look (and feel). + * The optional style to add to the {@link Avatar} in order to customize + * its base look (and feel). */ style: React.PropTypes.object, + + /** + * The URI of the {@link Avatar}. + * + * @type {string} + */ uri: React.PropTypes.string }; @@ -87,13 +93,19 @@ export default class Avatar extends Component { * @inheritdoc */ render() { + // Propagate all props of this Avatar but the ones consumed by this + // Avatar to the Image it renders. + + // eslint-disable-next-line no-unused-vars + const { uri, ...props } = this.props; + return ( + source = { this.state.source } /> ); } } diff --git a/react/features/base/participants/components/Avatar.web.js b/react/features/base/participants/components/Avatar.web.js index 87caa5105..9dd038cc0 100644 --- a/react/features/base/participants/components/Avatar.web.js +++ b/react/features/base/participants/components/Avatar.web.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; /** - * Display a participant avatar. + * Implements an avatar as a React/Web {@link Component}. */ export default class Avatar extends Component { /** @@ -11,7 +11,7 @@ export default class Avatar extends Component { */ static propTypes = { /** - * The URL for the avatar. + * The URI of the {@link Avatar}. * * @type {string} */ @@ -24,6 +24,16 @@ export default class Avatar extends Component { * @inheritdoc */ render() { - return ; + // Propagate all props of this Avatar but the ones consumed by this + // Avatar to the img it renders. + + // eslint-disable-next-line no-unused-vars + const { uri, ...props } = this.props; + + return ( + + ); } } diff --git a/react/features/base/react/components/AbstractContainer.js b/react/features/base/react/components/AbstractContainer.js index 8a4f1d6ea..9bddb2d27 100644 --- a/react/features/base/react/components/AbstractContainer.js +++ b/react/features/base/react/components/AbstractContainer.js @@ -69,6 +69,11 @@ export default class AbstractContainer extends Component { ...filteredProps } = props || this.props; + // visible + if (typeof visible !== 'undefined' && !visible) { + return null; + } + return React.createElement(type, filteredProps, children); } } diff --git a/react/features/base/react/components/Container.native.js b/react/features/base/react/components/Container.native.js deleted file mode 100644 index 1e1557156..000000000 --- a/react/features/base/react/components/Container.native.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import { - TouchableHighlight, - TouchableWithoutFeedback, - View -} from 'react-native'; - -import AbstractContainer from './AbstractContainer'; - -/** - * Represents a container of React Native Component children with a style. - * - * @extends AbstractContainer - */ -export class Container extends AbstractContainer { - /** - * Container component's property types. - * - * @static - */ - static propTypes = AbstractContainer.propTypes - - /** - * Implements React's {@link Component#render()}. - * - * @inheritdoc - * @returns {ReactElement} - */ - render() { - // eslint-disable-next-line prefer-const - let { onClick, style, touchFeedback, visible, ...props } = this.props; - - // visible - if (typeof visible !== 'undefined' && !visible) { - return null; - } - - // onClick & touchFeedback - (typeof touchFeedback === 'undefined') && (touchFeedback = onClick); - - const renderParent = touchFeedback || onClick; - - // eslint-disable-next-line object-property-newline - let component = this._render(View, { ...props, style }); - - if (renderParent) { - const parentType - = touchFeedback ? TouchableHighlight : TouchableWithoutFeedback; - const parentProps = {}; - - onClick && (parentProps.onPress = onClick); - - component = React.createElement(parentType, parentProps, component); - } - - return component; - } -} diff --git a/react/features/base/react/components/Container.web.js b/react/features/base/react/components/Container.web.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/base/react/components/Link.web.js b/react/features/base/react/components/Link.web.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/base/react/components/Watermarks.native.js b/react/features/base/react/components/Watermarks.native.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/react/features/base/react/components/_.native.js b/react/features/base/react/components/_.native.js new file mode 100644 index 000000000..738c4d2b8 --- /dev/null +++ b/react/features/base/react/components/_.native.js @@ -0,0 +1 @@ +export * from './native'; diff --git a/react/features/base/react/components/_.web.js b/react/features/base/react/components/_.web.js new file mode 100644 index 000000000..b80c83af3 --- /dev/null +++ b/react/features/base/react/components/_.web.js @@ -0,0 +1 @@ +export * from './web'; diff --git a/react/features/base/react/components/index.js b/react/features/base/react/components/index.js index 06bf02ab3..cda61441e 100644 --- a/react/features/base/react/components/index.js +++ b/react/features/base/react/components/index.js @@ -1,3 +1 @@ -export * from './Container'; -export * from './Link'; -export { default as Watermarks } from './Watermarks'; +export * from './_'; diff --git a/react/features/base/react/components/native/Container.js b/react/features/base/react/components/native/Container.js new file mode 100644 index 000000000..1c9f33d20 --- /dev/null +++ b/react/features/base/react/components/native/Container.js @@ -0,0 +1,55 @@ +import React from 'react'; +import { + TouchableHighlight, + TouchableWithoutFeedback, + View +} from 'react-native'; + +import AbstractContainer 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; + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + // eslint-disable-next-line prefer-const + let { onClick, style, touchFeedback, ...props } = this.props; + + // eslint-disable-next-line object-property-newline + let component = this._render(View, { ...props, style }); + + if (component) { + // onClick & touchFeedback + (typeof touchFeedback === 'undefined') && (touchFeedback = onClick); + if (touchFeedback || onClick) { + const parentType + = touchFeedback + ? TouchableHighlight + : TouchableWithoutFeedback; + const parentProps = {}; + + onClick && (parentProps.onPress = onClick); + + component + = React.createElement(parentType, parentProps, component); + } + } + + return component; + } +} diff --git a/react/features/base/react/components/Link.native.js b/react/features/base/react/components/native/Link.js similarity index 90% rename from react/features/base/react/components/Link.native.js rename to react/features/base/react/components/native/Link.js index 39006b1a5..521c92fcf 100644 --- a/react/features/base/react/components/Link.native.js +++ b/react/features/base/react/components/native/Link.js @@ -1,13 +1,15 @@ import React, { Component } from 'react'; -import { Linking, Text } from 'react-native'; +import { Linking } from 'react-native'; + +import Text from './Text'; /** * Implements a (hyper)link to a URL in the fashion of the HTML anchor element * and its href attribute. */ -export class Link extends Component { +export default class Link extends Component { /** - * Link component's property types. + * {@code Link} component's property types. * * @static */ @@ -56,9 +58,7 @@ export class Link extends Component { - { - this.props.children - } + { this.props.children } ); } diff --git a/react/features/base/react/components/native/Text.js b/react/features/base/react/components/native/Text.js new file mode 100644 index 000000000..2fc0f070c --- /dev/null +++ b/react/features/base/react/components/native/Text.js @@ -0,0 +1 @@ +export { Text as default } from 'react-native'; diff --git a/react/features/base/react/components/native/index.js b/react/features/base/react/components/native/index.js new file mode 100644 index 000000000..42d7e8783 --- /dev/null +++ b/react/features/base/react/components/native/index.js @@ -0,0 +1,3 @@ +export { default as Container } from './Container'; +export { default as Link } from './Link'; +export { default as Text } from './Text'; diff --git a/react/features/base/react/components/web/Container.js b/react/features/base/react/components/web/Container.js new file mode 100644 index 000000000..d2d603597 --- /dev/null +++ b/react/features/base/react/components/web/Container.js @@ -0,0 +1,25 @@ +import AbstractContainer from '../AbstractContainer'; + +/** + * Represents a container of React/Web {@link Component} children with a style. + * + * @extends AbstractContainer + */ +export default class Container extends AbstractContainer { + /** + * {@code Container} component's property types. + * + * @static + */ + static propTypes = AbstractContainer.propTypes; + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return this._render('div'); + } +} diff --git a/react/features/base/react/components/web/Text.js b/react/features/base/react/components/web/Text.js new file mode 100644 index 000000000..1a492531d --- /dev/null +++ b/react/features/base/react/components/web/Text.js @@ -0,0 +1,19 @@ +import React, { Component } from 'react'; + +/** + * Implements a React/Web {@link Component} for displaying text similar to React + * Native's {@code Text} in order to faciliate cross-platform source code. + * + * @extends Component + */ +export default class Text extends Component { + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return React.createElement('p', this.props); + } +} diff --git a/react/features/base/react/components/Watermarks.web.js b/react/features/base/react/components/web/Watermarks.js similarity index 99% rename from react/features/base/react/components/Watermarks.web.js rename to react/features/base/react/components/web/Watermarks.js index 9da8c27f0..c133da929 100644 --- a/react/features/base/react/components/Watermarks.web.js +++ b/react/features/base/react/components/web/Watermarks.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; -import { translate } from '../../i18n'; +import { translate } from '../../../i18n'; declare var interfaceConfig: Object; diff --git a/react/features/base/react/components/web/index.js b/react/features/base/react/components/web/index.js new file mode 100644 index 000000000..e7b01210d --- /dev/null +++ b/react/features/base/react/components/web/index.js @@ -0,0 +1,3 @@ +export { default as Container } from './Container'; +export { default as Text } from './Text'; +export { default as Watermarks } from './Watermarks'; diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js index 8a37dca8d..2da8c9db8 100644 --- a/react/features/welcome/components/WelcomePage.native.js +++ b/react/features/welcome/components/WelcomePage.native.js @@ -1,9 +1,9 @@ import React from 'react'; -import { Text, TextInput, TouchableHighlight, View } from 'react-native'; +import { TextInput, TouchableHighlight, View } from 'react-native'; import { connect } from 'react-redux'; import { translate } from '../../base/i18n'; -import { Link } from '../../base/react'; +import { Link, Text } from '../../base/react'; import { ColorPalette } from '../../base/styles'; import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';