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

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