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

Labels.js 11KB

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