You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Toolbox.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // @flow
  2. import React, { Component } from 'react';
  3. import { View } from 'react-native';
  4. import { connect } from 'react-redux';
  5. import { Container } from '../../../base/react';
  6. import { ChatButton } from '../../../chat';
  7. import { isToolboxVisible } from '../../functions';
  8. import AudioMuteButton from '../AudioMuteButton';
  9. import HangupButton from '../HangupButton';
  10. import VideoMuteButton from '../VideoMuteButton';
  11. import OverflowMenuButton from './OverflowMenuButton';
  12. import styles, {
  13. chatButtonOverride,
  14. hangupButtonStyles,
  15. toolbarButtonStyles,
  16. toolbarToggledButtonStyles
  17. } from './styles';
  18. /**
  19. * The number of buttons other than {@link HangupButton} to render in
  20. * {@link Toolbox}.
  21. *
  22. * @private
  23. * @type number
  24. */
  25. const _BUTTON_COUNT = 4;
  26. /**
  27. * Factor relating the hangup button and other toolbar buttons.
  28. *
  29. * @private
  30. * @type number
  31. */
  32. const _BUTTON_SIZE_FACTOR = 0.85;
  33. /**
  34. * The type of {@link Toolbox}'s React {@code Component} props.
  35. */
  36. type Props = {
  37. /**
  38. * The indicator which determines whether the toolbox is visible.
  39. */
  40. _visible: boolean,
  41. /**
  42. * The redux {@code dispatch} function.
  43. */
  44. dispatch: Function
  45. };
  46. /**
  47. * The type of {@link Toolbox}'s React {@code Component} state.
  48. */
  49. type State = {
  50. /**
  51. * The detected width for this component.
  52. */
  53. width: number
  54. };
  55. /**
  56. * Implements the conference toolbox on React Native.
  57. */
  58. class Toolbox extends Component<Props, State> {
  59. state = {
  60. width: 0
  61. };
  62. /**
  63. * Initializes a new {@code Toolbox} instance.
  64. *
  65. * @inheritdoc
  66. */
  67. constructor(props: Props) {
  68. super(props);
  69. // Bind event handlers so they are only bound once per instance.
  70. this._onLayout = this._onLayout.bind(this);
  71. }
  72. /**
  73. * Implements React's {@link Component#render()}.
  74. *
  75. * @inheritdoc
  76. * @returns {ReactElement}
  77. */
  78. render() {
  79. return (
  80. <Container
  81. onLayout = { this._onLayout }
  82. style = { styles.toolbox }
  83. visible = { this.props._visible }>
  84. { this._renderToolbar() }
  85. </Container>
  86. );
  87. }
  88. /**
  89. * Calculates how large our toolbar buttons can be, given the available
  90. * width. In the future we might want to have a size threshold, and once
  91. * it's passed a completely different style could be used, akin to the web.
  92. *
  93. * @private
  94. * @returns {number}
  95. */
  96. _calculateButtonSize() {
  97. const { width } = this.state;
  98. if (width <= 0) {
  99. // We don't know how much space is allocated to the toolbar yet.
  100. return width;
  101. }
  102. const hangupButtonSize = styles.hangupButton.width;
  103. const { style } = toolbarButtonStyles;
  104. let buttonSize
  105. = (width
  106. // Account for HangupButton without its margin which is not
  107. // included in _BUTTON_COUNT:
  108. - hangupButtonSize
  109. // Account for the horizontal margins of all buttons:
  110. - ((_BUTTON_COUNT + 1) * style.marginHorizontal * 2))
  111. / _BUTTON_COUNT;
  112. // Well, don't return a non-positive button size.
  113. if (buttonSize <= 0) {
  114. buttonSize = style.width;
  115. }
  116. // The button should be at most _BUTTON_SIZE_FACTOR of the hangup
  117. // button's size.
  118. buttonSize
  119. = Math.min(buttonSize, hangupButtonSize * _BUTTON_SIZE_FACTOR);
  120. // Make sure it's an even number.
  121. return 2 * Math.round(buttonSize / 2);
  122. }
  123. /**
  124. * Constructs the toggled style of the chat button. This cannot be done by
  125. * simple style inheritance due to the size calculation done in this
  126. * component.
  127. *
  128. * @param {Object} baseStyle - The base style that was originally
  129. * calculated.
  130. * @returns {Object | Array}
  131. */
  132. _getChatButtonToggledStyle(baseStyle) {
  133. if (Array.isArray(baseStyle.style)) {
  134. return {
  135. ...baseStyle,
  136. style: [
  137. ...baseStyle.style,
  138. chatButtonOverride.toggled
  139. ]
  140. };
  141. }
  142. return {
  143. ...baseStyle,
  144. style: [
  145. baseStyle.style,
  146. chatButtonOverride.toggled
  147. ]
  148. };
  149. }
  150. _onLayout: (Object) => void;
  151. /**
  152. * Handles the "on layout" View's event and stores the width as state.
  153. *
  154. * @param {Object} event - The "on layout" event object/structure passed
  155. * by react-native.
  156. * @private
  157. * @returns {void}
  158. */
  159. _onLayout({ nativeEvent: { layout: { width } } }) {
  160. this.setState({ width });
  161. }
  162. /**
  163. * Renders the toolbar. In order to avoid a weird visual effect in which the
  164. * toolbar is (visually) rendered and then visibly changes its size, it is
  165. * rendered only after we've figured out the width available to the toolbar.
  166. *
  167. * @returns {React$Node}
  168. */
  169. _renderToolbar() {
  170. const buttonSize = this._calculateButtonSize();
  171. let buttonStyles = toolbarButtonStyles;
  172. let toggledButtonStyles = toolbarToggledButtonStyles;
  173. if (buttonSize > 0) {
  174. const extraButtonStyle = {
  175. borderRadius: buttonSize / 2,
  176. height: buttonSize,
  177. width: buttonSize
  178. };
  179. // XXX The following width equality checks attempt to minimize
  180. // unnecessary objects and possibly re-renders.
  181. if (buttonStyles.style.width !== extraButtonStyle.width) {
  182. buttonStyles = {
  183. ...buttonStyles,
  184. style: [ buttonStyles.style, extraButtonStyle ]
  185. };
  186. }
  187. if (toggledButtonStyles.style.width !== extraButtonStyle.width) {
  188. toggledButtonStyles = {
  189. ...toggledButtonStyles,
  190. style: [ toggledButtonStyles.style, extraButtonStyle ]
  191. };
  192. }
  193. } else {
  194. // XXX In order to avoid a weird visual effect in which the toolbar
  195. // is (visually) rendered and then visibly changes its size, it is
  196. // rendered only after we've figured out the width available to the
  197. // toolbar.
  198. return null;
  199. }
  200. return (
  201. <View
  202. pointerEvents = 'box-none'
  203. style = { styles.toolbar }>
  204. <ChatButton
  205. styles = { buttonStyles }
  206. toggledStyles = {
  207. this._getChatButtonToggledStyle(toggledButtonStyles)
  208. } />
  209. <AudioMuteButton
  210. styles = { buttonStyles }
  211. toggledStyles = { toggledButtonStyles } />
  212. <HangupButton styles = { hangupButtonStyles } />
  213. <VideoMuteButton
  214. styles = { buttonStyles }
  215. toggledStyles = { toggledButtonStyles } />
  216. <OverflowMenuButton
  217. styles = { buttonStyles }
  218. toggledStyles = { toggledButtonStyles } />
  219. </View>
  220. );
  221. }
  222. }
  223. /**
  224. * Maps parts of the redux state to {@link Toolbox} (React {@code Component})
  225. * props.
  226. *
  227. * @param {Object} state - The redux state of which parts are to be mapped to
  228. * {@code Toolbox} props.
  229. * @private
  230. * @returns {{
  231. * _visible: boolean
  232. * }}
  233. */
  234. function _mapStateToProps(state: Object): Object {
  235. return {
  236. _visible: isToolboxVisible(state)
  237. };
  238. }
  239. export default connect(_mapStateToProps)(Toolbox);