Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

Toolbox.js 7.6KB

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