[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.
This commit is contained in:
Lyubo Marinov 2017-06-05 13:00:02 -05:00
parent d1ea29beeb
commit 409255f056
28 changed files with 314 additions and 97 deletions

View File

@ -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;
}
}

View File

@ -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.
*

View File

@ -0,0 +1 @@
export * from './web';

View File

@ -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()}.

View File

@ -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,

View File

@ -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';
/**

View File

@ -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';

View File

@ -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');
}
}

View File

@ -0,0 +1 @@
export { default as Audio } from './Audio';

View File

@ -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 (
<Image
{ ...props }
// XXX Avatar is expected to display the whole image.
resizeMode = 'contain'
source = { this.state.source }
style = { this.props.style } />
source = { this.state.source } />
);
}
}

View File

@ -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 <img src = { this.props.uri } />;
// 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 (
<img
{ ...props }
src = { uri } />
);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1 @@
export * from './native';

View File

@ -0,0 +1 @@
export * from './web';

View File

@ -1,3 +1 @@
export * from './Container';
export * from './Link';
export { default as Watermarks } from './Watermarks';
export * from './_';

View File

@ -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;
}
}

View File

@ -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 {
<Text
onPress = { this._onPress }
style = { this.props.style }>
{
this.props.children
}
{ this.props.children }
</Text>
);
}

View File

@ -0,0 +1 @@
export { Text as default } from 'react-native';

View File

@ -0,0 +1,3 @@
export { default as Container } from './Container';
export { default as Link } from './Link';
export { default as Text } from './Text';

View File

@ -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');
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -0,0 +1,3 @@
export { default as Container } from './Container';
export { default as Text } from './Text';
export { default as Watermarks } from './Watermarks';

View File

@ -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';