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

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