From 51f0c8a3887f612c3a319aaf5f928f2afd70b407 Mon Sep 17 00:00:00 2001 From: damencho Date: Mon, 6 Mar 2017 21:34:51 -0600 Subject: [PATCH] Adds base dialog implementation. --- package.json | 3 + react/features/base/dialog/actionTypes.js | 22 +++ react/features/base/dialog/actions.js | 32 ++++ .../base/dialog/components/AbstractDialog.js | 110 ++++++++++++ .../base/dialog/components/Dialog.native.js | 58 +++++++ .../base/dialog/components/Dialog.web.js | 159 ++++++++++++++++++ .../base/dialog/components/DialogContainer.js | 61 +++++++ .../features/base/dialog/components/index.js | 2 + react/features/base/dialog/index.js | 4 + react/features/base/dialog/reducer.js | 31 ++++ .../components/Conference.native.js | 3 + .../conference/components/Conference.web.js | 3 +- 12 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 react/features/base/dialog/actionTypes.js create mode 100644 react/features/base/dialog/actions.js create mode 100644 react/features/base/dialog/components/AbstractDialog.js create mode 100644 react/features/base/dialog/components/Dialog.native.js create mode 100644 react/features/base/dialog/components/Dialog.web.js create mode 100644 react/features/base/dialog/components/DialogContainer.js create mode 100644 react/features/base/dialog/components/index.js create mode 100644 react/features/base/dialog/index.js create mode 100644 react/features/base/dialog/reducer.js diff --git a/package.json b/package.json index 8fc5318d3..f9b17fd3c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "//": "Callstats.io does not work with recent versions of jsSHA (2.0.1 in particular)", "dependencies": { "@atlassian/aui": "6.0.6", + "@atlaskit/button": "1.0.3", + "@atlaskit/button-group": "1.0.0", + "@atlaskit/modal-dialog": "1.2.4", "async": "0.9.0", "autosize": "1.18.13", "bootstrap": "3.1.1", diff --git a/react/features/base/dialog/actionTypes.js b/react/features/base/dialog/actionTypes.js new file mode 100644 index 000000000..9fcbdb006 --- /dev/null +++ b/react/features/base/dialog/actionTypes.js @@ -0,0 +1,22 @@ +import { Symbol } from '../react'; + +/** + * The type of Redux action which closes a dialog + * + * { + * type: HIDE_DIALOG + * } + */ +export const HIDE_DIALOG = Symbol('HIDE_DIALOG'); + +/** + * The type of Redux action which begins a request to open a dialog. + * + * { + * type: OPEN_DIALOG, + * component: React.Component, + * props: React.PropTypes.object + * } + * + */ +export const OPEN_DIALOG = Symbol('OPEN_DIALOG'); diff --git a/react/features/base/dialog/actions.js b/react/features/base/dialog/actions.js new file mode 100644 index 000000000..5d22dc209 --- /dev/null +++ b/react/features/base/dialog/actions.js @@ -0,0 +1,32 @@ +import { + HIDE_DIALOG, + OPEN_DIALOG +} from './actionTypes'; + +/** + * Signals Dialog to close its dialog. + * + * @returns {{ + * type: HIDE_DIALOG + * }} + */ +export function hideDialog() { + return { + type: HIDE_DIALOG + }; +} + +/** + * Signals Dialog to open dialog. + * + * @param {Object} component - The component to display as dialog. + * @param {Object} componentProps - The properties needed for that component. + * @returns {Object} + */ +export function openDialog(component, componentProps) { + return { + type: OPEN_DIALOG, + component, + componentProps + }; +} diff --git a/react/features/base/dialog/components/AbstractDialog.js b/react/features/base/dialog/components/AbstractDialog.js new file mode 100644 index 000000000..ae75ea57c --- /dev/null +++ b/react/features/base/dialog/components/AbstractDialog.js @@ -0,0 +1,110 @@ +import React, { Component } from 'react'; + +import { hideDialog } from '../actions'; + +/** + * Abstract dialog to display dialogs. + */ +export default class AbstractDialog extends Component { + + /** + * Abstract Dialog component's property types. + * + * @static + */ + static propTypes = { + /** + * Whether cancel button is disabled. Enabled by default. + */ + cancelDisabled: React.PropTypes.bool, + + /** + * Optional i18n key to change the cancel button title. + */ + cancelTitleKey: React.PropTypes.string, + + /** + * Used to show hide the dialog on cancel. + */ + dispatch: React.PropTypes.func, + + /** + * Is ok button enabled/disabled. Enabled by default. + */ + okDisabled: React.PropTypes.bool, + + /** + * Optional i18n key to change the ok button title. + */ + okTitleKey: React.PropTypes.string, + + /** + * The handler for onCancel event. + */ + onCancel: React.PropTypes.func, + + /** + * The handler for the event when submitting the dialog. + */ + onSubmit: React.PropTypes.func, + + /** + * Used to obtain translations in children classes. + */ + t: React.PropTypes.func, + + /** + * Key to use for showing a title. + */ + titleKey: React.PropTypes.string + } + + /** + * Initializes a new Dialog instance. + * + * @param {Object} props - The read-only properties with which the new + * instance is to be initialized. + */ + constructor(props) { + super(props); + + this._onCancel = this._onCancel.bind(this); + this._onSubmit = this._onSubmit.bind(this); + } + + /** + * Dispatches action to hide the dialog. + * + * @returns {void} + */ + _onCancel() { + let hide = true; + + if (this.props.onCancel) { + hide = this.props.onCancel(); + } + + if (hide) { + this.props.dispatch(hideDialog()); + } + } + + /** + * Dispatches the action when submitting the dialog. + * + * @private + * @param {string} value - The submitted value if any. + * @returns {void} + */ + _onSubmit(value) { + let hide = true; + + if (this.props.onSubmit) { + hide = this.props.onSubmit(value); + } + + if (hide) { + this.props.dispatch(hideDialog()); + } + } +} diff --git a/react/features/base/dialog/components/Dialog.native.js b/react/features/base/dialog/components/Dialog.native.js new file mode 100644 index 000000000..4905fe169 --- /dev/null +++ b/react/features/base/dialog/components/Dialog.native.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import Prompt from 'react-native-prompt'; + +import { translate } from '../../i18n'; + +import AbstractDialog from './AbstractDialog'; + +/** + * Native dialog using Prompt. + */ +class Dialog extends AbstractDialog { + + /** + * Native sialog component's property types. + * + * @static + */ + static propTypes = { + /** + * I18n key to put as body title. + */ + bodyKey: React.PropTypes.string + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + const { + cancelDisabled, + cancelTitleKey, + bodyKey, + okDisabled, + okTitleKey, + t, + titleKey + } = this.props; + + return ( + + ); + } +} + +export default translate(connect()(Dialog)); diff --git a/react/features/base/dialog/components/Dialog.web.js b/react/features/base/dialog/components/Dialog.web.js new file mode 100644 index 000000000..9d53efe9f --- /dev/null +++ b/react/features/base/dialog/components/Dialog.web.js @@ -0,0 +1,159 @@ +import AKButton from '@atlaskit/button'; +import AKButtonGroup from '@atlaskit/button-group'; +import ModalDialog from '@atlaskit/modal-dialog'; +import React from 'react'; +import { connect } from 'react-redux'; + +import { translate } from '../../i18n'; + +import AbstractDialog from './AbstractDialog'; + +/** + * Web dialog that uses atlaskit modal-dialog to display dialogs. + */ +class Dialog extends AbstractDialog { + + /** + * Web dialog component's property types. + * + * @static + */ + static propTypes = { + /** + * This is the body of the dialog, the component children. + */ + children: React.PropTypes.node, + + /** + * Whether the dialog is modal. This means clicking on the blanket will + * leave the dialog open. No cancel button. + */ + isModal: React.PropTypes.bool, + + /** + * Width of the dialog, can be: + * - 'small' (400px), 'medium' (600px), 'large' (800px), + * 'x-large' (968px) + * - integer value for pixel width + * - string value for percentage + */ + width: React.PropTypes.string + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return ( + +
+ +
+
); + } + + /** + * Render cancel button. + * + * @returns {*} The cancel button if enabled and dialog is not modal. + * @private + */ + _renderCancelButton() { + if (this.props.cancelDisabled || this.props.isModal) { + return null; + } + + return ( + + { this.props.t(this.props.cancelTitleKey || 'dialog.Cancel') } + + ); + } + + /** + * Render component in dialog footer. + * + * @returns {ReactElement} + * @private + */ + _renderFooter() { + return ( +
+ + { this._renderCancelButton() } + { this._renderOKButton() } + +
+ ); + } + + /** + * Render component in dialog header. + * + * @returns {ReactElement} + * @private + */ + _renderHeader() { + const { t } = this.props; + + return ( +
+

+ { t(this.props.titleKey) } +

+
+ ); + } + + /** + * Render ok button. + * + * @returns {*} The ok button if enabled. + * @private + */ + _renderOKButton() { + if (this.props.submitDisabled) { + return null; + } + + return ( + + { this.props.t(this.props.okTitleKey || 'dialog.Ok') } + + ); + } + + /** + * Dispatches action to hide the dialog. + * + * @returns {void} + */ + _onCancel() { + if (this.props.isModal) { + return; + } + + super._onCancel(); + } +} + +export default translate(connect()(Dialog)); diff --git a/react/features/base/dialog/components/DialogContainer.js b/react/features/base/dialog/components/DialogContainer.js new file mode 100644 index 000000000..ee4bdcd85 --- /dev/null +++ b/react/features/base/dialog/components/DialogContainer.js @@ -0,0 +1,61 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; + +/** + * Implements a DialogContainer that will be responsible for + * showing all dialogs. We will need a separate container so we can handle + * multiple dialogs, showing them simultaneously or queueing them. + */ +export class DialogContainer extends Component { + + /** + * DialogContainer component's property types. + * + * @static + */ + static propTypes = { + /** + * The component to render. + */ + _component: React.PropTypes.func, + + /** + * The props to pass to the component that will be rendered. + */ + _componentProps: React.PropTypes.object + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + if (!this.props._component) { + return null; + } + + return React.createElement( + this.props._component, this.props._componentProps); + } +} + +/** + * Maps (parts of) the Redux state to the associated Dialog's props. + * + * @param {Object} state - The Redux state. + * @private + * @returns {{ + * _component: React.Component, + * _props: React.PropTypes.object + * }} + */ +function _mapStateToProps(state) { + return { + _component: state['features/base/dialog'].component, + _componentProps: state['features/base/dialog'].componentProps + }; +} + +export default connect(_mapStateToProps)(DialogContainer); diff --git a/react/features/base/dialog/components/index.js b/react/features/base/dialog/components/index.js new file mode 100644 index 000000000..8c7c7d9eb --- /dev/null +++ b/react/features/base/dialog/components/index.js @@ -0,0 +1,2 @@ +export { default as DialogContainer } from './DialogContainer'; +export { default as Dialog } from './Dialog'; diff --git a/react/features/base/dialog/index.js b/react/features/base/dialog/index.js new file mode 100644 index 000000000..582e1f9dd --- /dev/null +++ b/react/features/base/dialog/index.js @@ -0,0 +1,4 @@ +export * from './actions'; +export * from './components'; + +import './reducer'; diff --git a/react/features/base/dialog/reducer.js b/react/features/base/dialog/reducer.js new file mode 100644 index 000000000..b67ac6ee7 --- /dev/null +++ b/react/features/base/dialog/reducer.js @@ -0,0 +1,31 @@ +import { ReducerRegistry, setStateProperties } from '../redux'; + +import { + HIDE_DIALOG, + OPEN_DIALOG +} from './actionTypes'; + +/** + * Listen for actions which show or hide dialogs. + * + * @param {Object[]} state - Current state. + * @param {Object} action - Action object. + * @param {string} action.type - Type of action. + * @returns {{}} + */ +ReducerRegistry.register('features/base/dialog', (state = {}, action) => { + switch (action.type) { + case HIDE_DIALOG: + return setStateProperties(state, { + component: undefined, + componentProps: undefined + }); + case OPEN_DIALOG: + return setStateProperties(state, { + component: action.component, + componentProps: action.componentProps + }); + } + + return state; +}); diff --git a/react/features/conference/components/Conference.native.js b/react/features/conference/components/Conference.native.js index 593acb645..60997f1cb 100644 --- a/react/features/conference/components/Conference.native.js +++ b/react/features/conference/components/Conference.native.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import { connect as reactReduxConnect } from 'react-redux'; import { connect, disconnect } from '../../base/connection'; +import { DialogContainer } from '../../base/dialog'; import { Container } from '../../base/react'; import { FilmStrip } from '../../film-strip'; import { LargeVideo } from '../../large-video'; @@ -125,6 +126,8 @@ class Conference extends Component { + + { this._renderPrompt() } diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js index be7b366e7..82f7c2815 100644 --- a/react/features/conference/components/Conference.web.js +++ b/react/features/conference/components/Conference.web.js @@ -4,6 +4,7 @@ import React, { Component } from 'react'; import { connect as reactReduxConnect } from 'react-redux'; import { connect, disconnect } from '../../base/connection'; +import { DialogContainer } from '../../base/dialog'; import { Watermarks } from '../../base/react'; import { FeedbackButton } from '../../feedback'; import { OverlayContainer } from '../../overlay'; @@ -170,7 +171,7 @@ class Conference extends Component { - +