From b81dc4e59b3678b74686ad33499f45aaaf656260 Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Mon, 31 Jul 2017 11:02:41 +0300 Subject: [PATCH] ref(toolbar): Implement stateless toolbar --- .../components/StatelessToolbar.web.js | 65 ++++++++ .../components/StatelessToolbarButton.js | 148 ++++++++++++++++++ .../toolbox/components/Toolbar.web.js | 60 ++++--- .../toolbox/components/ToolbarButton.web.js | 49 ++---- react/features/toolbox/functions.web.js | 43 ----- react/features/toolbox/index.js | 1 + 6 files changed, 262 insertions(+), 104 deletions(-) create mode 100644 react/features/toolbox/components/StatelessToolbar.web.js create mode 100644 react/features/toolbox/components/StatelessToolbarButton.js diff --git a/react/features/toolbox/components/StatelessToolbar.web.js b/react/features/toolbox/components/StatelessToolbar.web.js new file mode 100644 index 000000000..c5fa79166 --- /dev/null +++ b/react/features/toolbox/components/StatelessToolbar.web.js @@ -0,0 +1,65 @@ +/* @flow */ + +import React, { Component } from 'react'; + +/** + * Implements a toolbar in React/Web. It is a strip that contains a set of + * toolbar items such as buttons. Toolbar is commonly placed inside of a + * Toolbox. + * + * @class Toolbar + * @extends Component + */ +export default class StatelessToolbar extends Component { + /** + * Base toolbar component's property types. + * + * @static + */ + static propTypes = { + /** + * Children of current React component. + */ + children: React.PropTypes.node, + + /** + * Toolbar's class name. + */ + className: React.PropTypes.string, + + /** + * Handler for mouse out event. + */ + onMouseOut: React.PropTypes.func, + + /** + * Handler for mouse over event. + */ + onMouseOver: React.PropTypes.func + }; + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render(): ReactElement<*> { + const { + className, + onMouseOut, + onMouseOver + } = this.props; + + return ( +
+ { + this.props.children + } +
+ ); + } +} diff --git a/react/features/toolbox/components/StatelessToolbarButton.js b/react/features/toolbox/components/StatelessToolbarButton.js new file mode 100644 index 000000000..ce410eda3 --- /dev/null +++ b/react/features/toolbox/components/StatelessToolbarButton.js @@ -0,0 +1,148 @@ +/* @flow */ + +import React from 'react'; + +import AbstractToolbarButton from './AbstractToolbarButton'; + +type MapOfAttributes = { [key: string]: * }; + + +/* eslint-disable flowtype/space-before-type-colon */ + +/** + * Takes toolbar button props and maps them to HTML attributes to set. + * + * @param {Object} props - Props set to the React component. + * @returns {MapOfAttributes} + */ +function getButtonAttributesByProps(props: Object = {}) + : MapOfAttributes { + // XXX Make sure to not modify props.classNames because that'd be bad + // practice. + const classNames = (props.classNames && [ ...props.classNames ]) || []; + + props.toggled && classNames.push('toggled'); + props.unclickable && classNames.push('unclickable'); + + const result: MapOfAttributes = { + className: classNames.join(' '), + 'data-container': 'body', + 'data-placement': 'bottom', + id: props.id + }; + + if (!props.enabled) { + result.disabled = 'disabled'; + } + + if (props.hidden) { + result.style = { display: 'none' }; + } + + if (props.tooltipText) { + result.content = props.tooltipText; + } + + return result; +} + +/* eslint-enable flowtype/space-before-type-colon */ + +/** + * Represents a button in Toolbar on React. + * + * @class ToolbarButton + * @extends AbstractToolbarButton + */ +export default class StatelessToolbarButton extends AbstractToolbarButton { + _onClick: Function; + + /** + * Toolbar button component's property types. + * + * @static + */ + static propTypes = { + ...AbstractToolbarButton.propTypes, + + /** + * Object describing button. + */ + button: React.PropTypes.object.isRequired, + + /** + * Handler for button's reference. + */ + createRefToButton: React.PropTypes.func + }; + + /** + * Initializes new ToolbarButton instance. + * + * @param {Object} props - The read-only properties with which the new + * instance is to be initialized. + */ + constructor(props: Object) { + super(props); + + // Bind methods to save the context + this._onClick = this._onClick.bind(this); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render(): ReactElement<*> { + const { button } = this.props; + const attributes = getButtonAttributesByProps(button); + + return ( + + { this._renderInnerElementsIfRequired() } + + ); + } + + /** + * Wrapper on on click handler props for current button. + * + * @param {Event} event - Click event object. + * @returns {void} + * @private + */ + _onClick(event: Event): void { + const { + button, + onClick + } = this.props; + const { + enabled, + unclickable + } = button; + + if (enabled && !unclickable && onClick) { + onClick(event); + } + } + + /** + * If toolbar button should contain children elements + * renders them. + * + * @returns {ReactElement|null} + * @private + */ + _renderInnerElementsIfRequired(): ReactElement<*> | null { + if (this.props.button.html) { + return this.props.button.html; + } + + return null; + } +} diff --git a/react/features/toolbox/components/Toolbar.web.js b/react/features/toolbox/components/Toolbar.web.js index 8faa15606..fa1336c81 100644 --- a/react/features/toolbox/components/Toolbar.web.js +++ b/react/features/toolbox/components/Toolbar.web.js @@ -4,6 +4,8 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { setToolbarHovered } from '../actions'; + +import StatelessToolbar from './StatelessToolbar'; import ToolbarButton from './ToolbarButton'; /** @@ -15,6 +17,8 @@ import ToolbarButton from './ToolbarButton'; * @extends Component */ class Toolbar extends Component { + _onMouseOut: Function; + _onMouseOver: Function; _renderToolbarButton: Function; /** @@ -56,10 +60,12 @@ class Toolbar extends Component { * * @param {Object} props - Object containing React component properties. */ - constructor(props) { + constructor(props: Object) { super(props); // Bind callbacks to preverse this. + this._onMouseOut = this._onMouseOut.bind(this); + this._onMouseOver = this._onMouseOver.bind(this); this._renderToolbarButton = this._renderToolbarButton.bind(this); } @@ -70,21 +76,36 @@ class Toolbar extends Component { * @returns {ReactElement} */ render(): ReactElement<*> { - const { className } = this.props; + const toolbarButtons = new Map(); + + this.props.toolbarButtons + .forEach((button, key) => { + const { onClick } = button; + + toolbarButtons.set(key, { + ...button, + onClick: (...args) => + onClick && onClick(this.props.dispatch, ...args) + }); + }); + + const props = { + ...this.props, + onMouseOut: this._onMouseOut, + onMouseOver: this._onMouseOver, + toolbarButtons + }; return ( -
+ { [ ...this.props.toolbarButtons.entries() ] - .reduce(this._renderToolbarButton, []) + .map(this._renderToolbarButton) } { this.props.children } -
+ ); } @@ -109,47 +130,38 @@ class Toolbar extends Component { } /** - * Renders toolbar button. Method is passed to reduce function. + * Renders toolbar button. Method is passed to map function. * - * @param {Array} acc - Toolbar buttons array. * @param {Array} keyValuePair - Key value pair containing button and its * key. * @private - * @returns {Array} Array of toolbar buttons. + * @returns {ReactElement} A toolbar button. */ - _renderToolbarButton(acc: Array<*>, - keyValuePair: Array<*>): Array> { + _renderToolbarButton( + keyValuePair: Array<*>): ReactElement<*> { const [ key, button ] = keyValuePair; if (button.component) { - acc.push( + return ( ); - - return acc; } const { tooltipPosition } = this.props; const { onClick, onMount, onUnmount } = button; - const onClickHandler - = (...args) => - onClick(this.props.dispatch, ...args); - - acc.push( + return ( ); - - return acc; } } diff --git a/react/features/toolbox/components/ToolbarButton.web.js b/react/features/toolbox/components/ToolbarButton.web.js index 778d445ea..bd902bceb 100644 --- a/react/features/toolbox/components/ToolbarButton.web.js +++ b/react/features/toolbox/components/ToolbarButton.web.js @@ -1,6 +1,6 @@ /* @flow */ -import React from 'react'; +import React, { Component } from 'react'; import { translate } from '../../base/i18n'; @@ -9,8 +9,7 @@ import { setTooltipText } from '../../../../modules/UI/util/Tooltip'; -import AbstractToolbarButton from './AbstractToolbarButton'; -import { getButtonAttributesByProps } from '../functions'; +import StatelessToolbarButton from './StatelessToolbarButton'; declare var APP: Object; @@ -20,18 +19,17 @@ declare var APP: Object; * @class ToolbarButton * @extends AbstractToolbarButton */ -class ToolbarButton extends AbstractToolbarButton { +class ToolbarButton extends Component { + button: Object; _createRefToButton: Function; - _onClick: Function; - /** * Toolbar button component's property types. * * @static */ static propTypes = { - ...AbstractToolbarButton.propTypes, + ...StatelessToolbarButton.propTypes, /** * Object describing button. @@ -71,7 +69,6 @@ class ToolbarButton extends AbstractToolbarButton { // Bind methods to save the context this._createRefToButton = this._createRefToButton.bind(this); - this._onClick = this._onClick.bind(this); } /** @@ -109,17 +106,17 @@ class ToolbarButton extends AbstractToolbarButton { */ render(): ReactElement<*> { const { button } = this.props; - const attributes = getButtonAttributesByProps(button); const popups = button.popups || []; + const props = { + ...this.props, + createRefToButton: this._createRefToButton + }; + return ( - - { this._renderInnerElementsIfRequired() } + { this._renderPopups(popups) } - + ); } @@ -135,28 +132,6 @@ class ToolbarButton extends AbstractToolbarButton { this.button = element; } - /** - * Wrapper on on click handler props for current button. - * - * @param {Event} event - Click event object. - * @returns {void} - * @private - */ - _onClick(event: Event): void { - const { - button, - onClick - } = this.props; - const { - enabled, - unclickable - } = button; - - if (enabled && !unclickable && onClick) { - onClick(event); - } - } - /** * If toolbar button should contain children elements * renders them. diff --git a/react/features/toolbox/functions.web.js b/react/features/toolbox/functions.web.js index 354e8a598..7c8aa66f5 100644 --- a/react/features/toolbox/functions.web.js +++ b/react/features/toolbox/functions.web.js @@ -3,55 +3,12 @@ import SideContainerToggler import defaultToolbarButtons from './defaultToolbarButtons'; -type MapOfAttributes = { [key: string]: * }; - declare var $: Function; declare var AJS: Object; declare var interfaceConfig: Object; export { abstractMapStateToProps } from './functions.native'; -/* eslint-disable flowtype/space-before-type-colon */ - -/** - * Takes toolbar button props and maps them to HTML attributes to set. - * - * @param {Object} props - Props set to the React component. - * @returns {MapOfAttributes} - */ -export function getButtonAttributesByProps(props: Object = {}) - : MapOfAttributes { - // XXX Make sure to not modify props.classNames because that'd be bad - // practice. - const classNames = (props.classNames && [ ...props.classNames ]) || []; - - props.toggled && classNames.push('toggled'); - props.unclickable && classNames.push('unclickable'); - - const result: MapOfAttributes = { - className: classNames.join(' '), - 'data-container': 'body', - 'data-placement': 'bottom', - id: props.id - }; - - if (!props.enabled) { - result.disabled = 'disabled'; - } - - if (props.hidden) { - result.style = { display: 'none' }; - } - - if (props.tooltipText) { - result.content = props.tooltipText; - } - - return result; -} - -/* eslint-enable flowtype/space-before-type-colon */ - /** * Returns an object which contains the default buttons for the primary and * secondary toolbars. diff --git a/react/features/toolbox/index.js b/react/features/toolbox/index.js index a29aa08e0..44000a1a7 100644 --- a/react/features/toolbox/index.js +++ b/react/features/toolbox/index.js @@ -1,6 +1,7 @@ export * from './actions'; export * from './actionTypes'; export * from './components'; +export * from './functions'; import './middleware'; import './reducer';