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.

Labels.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. // @flow
  2. import React from 'react';
  3. import { TouchableOpacity, View } from 'react-native';
  4. import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
  5. import { connect } from '../../../base/redux';
  6. import { ASPECT_RATIO_WIDE } from '../../../base/responsive-ui/constants';
  7. import {
  8. RecordingExpandedLabel
  9. } from '../../../recording';
  10. import { TranscribingExpandedLabel } from '../../../transcribing';
  11. import { VideoQualityExpandedLabel } from '../../../video-quality';
  12. import { shouldDisplayNotifications } from '../../functions';
  13. import AbstractLabels, {
  14. _abstractMapStateToProps,
  15. type Props as AbstractLabelsProps
  16. } from '../AbstractLabels';
  17. import InsecureRoomNameExpandedLabel from './InsecureRoomNameExpandedLabel';
  18. import styles from './styles';
  19. /**
  20. * The type of the React {@code Component} props of {@link Labels}.
  21. */
  22. type Props = AbstractLabelsProps & {
  23. /**
  24. * Application's aspect ratio.
  25. */
  26. _aspectRatio: Symbol,
  27. /**
  28. * True if the labels should be visible, false otherwise.
  29. */
  30. _visible: boolean,
  31. /**
  32. * Function to translate i18n labels.
  33. */
  34. t: Function
  35. };
  36. type State = {
  37. /**
  38. * Layout object of the outermost container. For stucture please see:
  39. * https://facebook.github.io/react-native/docs/view#onlayout
  40. */
  41. containerLayout: ?Object,
  42. /**
  43. * Layout objects of the individual labels. This data type contains the same
  44. * structure as the layout is defined here:
  45. * https://facebook.github.io/react-native/docs/view#onlayout
  46. * but keyed with the ID of the label its layout it contains. E.g.
  47. *
  48. * {
  49. * transcribing: {
  50. * { layout: { x, y, width, height } }
  51. * },
  52. * ...
  53. * }
  54. */
  55. labelLayouts: Object,
  56. /**
  57. * Position of the label to render the {@code ExpandedLabel} to.
  58. */
  59. parentPosition: ?number,
  60. /**
  61. * String to show which {@code ExpandedLabel} to be shown. (Equals to the
  62. * label IDs below.)
  63. */
  64. visibleExpandedLabel: ?string
  65. }
  66. const LABEL_ID_QUALITY = 'quality';
  67. const LABEL_ID_RECORDING = 'recording';
  68. const LABEL_ID_STREAMING = 'streaming';
  69. const LABEL_ID_TRANSCRIBING = 'transcribing';
  70. const LABEL_ID_INSECURE_ROOM_NAME = 'insecure-room-name';
  71. /**
  72. * The {@code ExpandedLabel} components to be rendered for the individual
  73. * {@code Label}s.
  74. */
  75. const EXPANDED_LABELS = {
  76. quality: VideoQualityExpandedLabel,
  77. recording: {
  78. component: RecordingExpandedLabel,
  79. props: {
  80. mode: JitsiRecordingConstants.mode.FILE
  81. }
  82. },
  83. streaming: {
  84. component: RecordingExpandedLabel,
  85. props: {
  86. mode: JitsiRecordingConstants.mode.STREAM
  87. }
  88. },
  89. transcribing: TranscribingExpandedLabel,
  90. 'insecure-room-name': InsecureRoomNameExpandedLabel
  91. };
  92. /**
  93. * Timeout to hide the {@ExpandedLabel}.
  94. */
  95. const EXPANDED_LABEL_TIMEOUT = 5000;
  96. /**
  97. * A container that renders the conference indicators, if any.
  98. */
  99. class Labels extends AbstractLabels<Props, State> {
  100. /**
  101. * Timeout for the expanded labels to disappear.
  102. */
  103. expandedLabelTimeout: TimeoutID;
  104. /**
  105. * Instantiates a new instance of {@code Labels}.
  106. *
  107. * @inheritdoc
  108. */
  109. constructor(props: Props) {
  110. super(props);
  111. this.state = {
  112. containerLayout: undefined,
  113. labelLayouts: {},
  114. parentPosition: undefined,
  115. visibleExpandedLabel: undefined
  116. };
  117. this._onTopViewLayout = this._onTopViewLayout.bind(this);
  118. }
  119. /**
  120. * Implements React {@code Component}'s componentWillUnmount.
  121. *
  122. * @inheritdoc
  123. */
  124. componentWillUnmount() {
  125. clearTimeout(this.expandedLabelTimeout);
  126. }
  127. /**
  128. * Implements React {@code Component}'s render.
  129. *
  130. * @inheritdoc
  131. */
  132. render() {
  133. const { _aspectRatio, _filmstripVisible, _visible } = this.props;
  134. if (!_visible) {
  135. return null;
  136. }
  137. const wide = _aspectRatio === ASPECT_RATIO_WIDE;
  138. return (
  139. <View
  140. pointerEvents = 'box-none'
  141. style = { styles.labelWrapper }>
  142. <View
  143. onLayout = { this._onTopViewLayout }
  144. pointerEvents = 'box-none'
  145. style = { [
  146. styles.indicatorContainer,
  147. wide && _filmstripVisible
  148. && styles.indicatorContainerWide
  149. ] }>
  150. <TouchableOpacity
  151. onLayout = { this._createOnLayout(LABEL_ID_RECORDING) }
  152. onPress = { this._createOnPress(LABEL_ID_RECORDING) } >
  153. {
  154. this._renderRecordingLabel(
  155. JitsiRecordingConstants.mode.FILE)
  156. }
  157. </TouchableOpacity>
  158. <TouchableOpacity
  159. onLayout = { this._createOnLayout(LABEL_ID_STREAMING) }
  160. onPress = { this._createOnPress(LABEL_ID_STREAMING) } >
  161. {
  162. this._renderRecordingLabel(
  163. JitsiRecordingConstants.mode.STREAM)
  164. }
  165. </TouchableOpacity>
  166. <TouchableOpacity
  167. onLayout = {
  168. this._createOnLayout(LABEL_ID_TRANSCRIBING)
  169. }
  170. onPress = {
  171. this._createOnPress(LABEL_ID_TRANSCRIBING)
  172. } >
  173. {
  174. this._renderTranscribingLabel()
  175. }
  176. </TouchableOpacity>
  177. <TouchableOpacity
  178. onLayout = {
  179. this._createOnLayout(LABEL_ID_INSECURE_ROOM_NAME)
  180. }
  181. onPress = {
  182. this._createOnPress(LABEL_ID_INSECURE_ROOM_NAME)
  183. } >
  184. {
  185. this._renderInsecureRoomNameLabel()
  186. }
  187. </TouchableOpacity>
  188. <TouchableOpacity
  189. onLayout = {
  190. this._createOnLayout(LABEL_ID_QUALITY) }
  191. onPress = {
  192. this._createOnPress(LABEL_ID_QUALITY) } >
  193. { this._renderVideoQualityLabel() }
  194. </TouchableOpacity>
  195. </View>
  196. <View
  197. style = { [
  198. styles.indicatorContainer,
  199. wide && _filmstripVisible
  200. && styles.indicatorContainerWide
  201. ] }>
  202. {
  203. this._renderExpandedLabel()
  204. }
  205. </View>
  206. </View>
  207. );
  208. }
  209. /**
  210. * Creates a function to be invoked when the onLayout of the touchables are
  211. * triggered.
  212. *
  213. * @param {string} label - The identifier of the label that's onLayout is
  214. * triggered.
  215. * @returns {Function}
  216. */
  217. _createOnLayout(label) {
  218. return ({ nativeEvent: { layout } }) => {
  219. const { labelLayouts } = this.state;
  220. const updatedLayout = {};
  221. updatedLayout[label] = layout;
  222. this.setState({
  223. labelLayouts: {
  224. ...labelLayouts,
  225. ...updatedLayout
  226. }
  227. });
  228. };
  229. }
  230. /**
  231. * Creates a function to be invoked when the onPress of the touchables are
  232. * triggered.
  233. *
  234. * @param {string} label - The identifier of the label that's onLayout is
  235. * triggered.
  236. * @returns {Function}
  237. */
  238. _createOnPress(label) {
  239. return () => {
  240. const {
  241. containerLayout,
  242. labelLayouts
  243. } = this.state;
  244. let { visibleExpandedLabel } = this.state;
  245. if (containerLayout) {
  246. const labelLayout = labelLayouts[label];
  247. // This calculation has to be changed if the labels are not
  248. // positioned right anymore.
  249. const right = containerLayout.width - labelLayout.x;
  250. visibleExpandedLabel
  251. = visibleExpandedLabel === label ? undefined : label;
  252. clearTimeout(this.expandedLabelTimeout);
  253. this.setState({
  254. parentPosition: right,
  255. visibleExpandedLabel
  256. });
  257. if (visibleExpandedLabel) {
  258. this.expandedLabelTimeout = setTimeout(() => {
  259. this.setState({
  260. visibleExpandedLabel: undefined
  261. });
  262. }, EXPANDED_LABEL_TIMEOUT);
  263. }
  264. }
  265. };
  266. }
  267. _onTopViewLayout: Object => void
  268. /**
  269. * Invoked when the View containing the {@code Label}s is laid out.
  270. *
  271. * @param {Object} layout - The native layout object.
  272. * @returns {void}
  273. */
  274. _onTopViewLayout({ nativeEvent: { layout } }) {
  275. this.setState({
  276. containerLayout: layout
  277. });
  278. }
  279. /**
  280. * Rendes the expanded (explaining) label for the label that was touched.
  281. *
  282. * @returns {React$Element}
  283. */
  284. _renderExpandedLabel() {
  285. const { parentPosition, visibleExpandedLabel } = this.state;
  286. if (visibleExpandedLabel) {
  287. const expandedLabel = EXPANDED_LABELS[visibleExpandedLabel];
  288. if (expandedLabel) {
  289. const component = expandedLabel.component || expandedLabel;
  290. const expandedLabelProps = expandedLabel.props || {};
  291. return React.createElement(component, {
  292. ...expandedLabelProps,
  293. parentPosition
  294. });
  295. }
  296. }
  297. return null;
  298. }
  299. _renderRecordingLabel: string => React$Element<any>;
  300. _renderTranscribingLabel: () => React$Element<any>;
  301. _renderInsecureRoomNameLabel: () => React$Element<any>;
  302. _renderVideoQualityLabel: () => React$Element<any>;
  303. }
  304. /**
  305. * Maps (parts of) the redux state to the associated
  306. * {@code Labels}'s props.
  307. *
  308. * @param {Object} state - The redux state.
  309. * @private
  310. * @returns {Props}
  311. */
  312. function _mapStateToProps(state) {
  313. return {
  314. ..._abstractMapStateToProps(state),
  315. _aspectRatio: state['features/base/responsive-ui'].aspectRatio,
  316. _visible: !shouldDisplayNotifications(state)
  317. };
  318. }
  319. export default connect(_mapStateToProps)(Labels);