您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

Toolbox.js 6.7KB

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