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

Labels.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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 { 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 { shouldDisplayNotifications } from '../../functions';
  22. import styles from './styles';
  23. /**
  24. * The type of the React {@code Component} props of {@link Labels}.
  25. */
  26. type Props = AbstractLabelsProps & {
  27. /**
  28. * Function to translate i18n labels.
  29. */
  30. t: Function,
  31. /**
  32. * The indicator which determines whether the UI is reduced (to accommodate
  33. * smaller display areas).
  34. *
  35. * @private
  36. */
  37. _reducedUI: boolean,
  38. /**
  39. * True if the labels should be visible, false otherwise.
  40. */
  41. _visible: boolean
  42. };
  43. type State = {
  44. /**
  45. * Layout object of the outermost container. For stucture please see:
  46. * https://facebook.github.io/react-native/docs/view#onlayout
  47. */
  48. containerLayout: ?Object,
  49. /**
  50. * Layout objects of the individual labels. This data type contains the same
  51. * structure as the layout is defined here:
  52. * https://facebook.github.io/react-native/docs/view#onlayout
  53. * but keyed with the ID of the label its layout it contains. E.g.
  54. *
  55. * {
  56. * transcribing: {
  57. * { layout: { x, y, width, height } }
  58. * },
  59. * ...
  60. * }
  61. */
  62. labelLayouts: Object,
  63. /**
  64. * Position of the label to render the {@code ExpandedLabel} to.
  65. */
  66. parentPosition: ?number,
  67. /**
  68. * String to show which {@code ExpandedLabel} to be shown. (Equals to the
  69. * label IDs below.)
  70. */
  71. visibleExpandedLabel: ?string
  72. }
  73. const LABEL_ID_QUALITY = 'quality';
  74. const LABEL_ID_RECORDING = 'recording';
  75. const LABEL_ID_STREAMING = 'streaming';
  76. const LABEL_ID_TRANSCRIBING = 'transcribing';
  77. /**
  78. * The {@code ExpandedLabel} components to be rendered for the individual
  79. * {@code Label}s.
  80. */
  81. const EXPANDED_LABELS = {
  82. quality: VideoQualityExpandedLabel,
  83. recording: {
  84. component: RecordingExpandedLabel,
  85. props: {
  86. mode: JitsiRecordingConstants.mode.FILE
  87. }
  88. },
  89. streaming: {
  90. component: RecordingExpandedLabel,
  91. props: {
  92. mode: JitsiRecordingConstants.mode.STREAM
  93. }
  94. },
  95. transcribing: TranscribingExpandedLabel
  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. if (!this.props._visible) {
  139. return null;
  140. }
  141. const wide = !isNarrowAspectRatio(this);
  142. const { _filmstripVisible, _reducedUI } = this.props;
  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
  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. {/*
  183. * Emil, Lyubomir, Nichole, and Zoli said that the Labels
  184. * should not be rendered in Picture-in-Picture. Saul
  185. * argued that the recording Labels should be rendered. As
  186. * a temporary compromise, don't render the
  187. * VideoQualityLabel at least because it's not that
  188. * important.
  189. */
  190. _reducedUI || (
  191. <TouchableOpacity
  192. onLayout = {
  193. this._createOnLayout(LABEL_ID_QUALITY) }
  194. onPress = {
  195. this._createOnPress(LABEL_ID_QUALITY) } >
  196. { this._renderVideoQualityLabel() }
  197. </TouchableOpacity>
  198. )
  199. }
  200. </View>
  201. <View
  202. style = { [
  203. styles.indicatorContainer,
  204. wide && _filmstripVisible
  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<*>;
  305. _renderTranscribingLabel: () => React$Element<*>
  306. _renderVideoQualityLabel: () => React$Element<*>;
  307. }
  308. /**
  309. * Maps (parts of) the redux state to the associated
  310. * {@code Labels}'s props.
  311. *
  312. * @param {Object} state - The redux state.
  313. * @private
  314. * @returns {{
  315. * _filmstripVisible: boolean,
  316. * _reducedUI: boolean,
  317. * _visible: boolean
  318. * }}
  319. */
  320. function _mapStateToProps(state) {
  321. return {
  322. ..._abstractMapStateToProps(state),
  323. _reducedUI: state['features/base/responsive-ui'].reducedUI,
  324. _visible: !isToolboxVisible(state)
  325. && !shouldDisplayTileView(state)
  326. && !shouldDisplayNotifications(state)
  327. };
  328. }
  329. export default connect(_mapStateToProps)(makeAspectRatioAware(Labels));