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.

DesktopPicker.js 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. // @flow
  2. import Tabs from '@atlaskit/tabs';
  3. import PropTypes from 'prop-types';
  4. import React, { Component } from 'react';
  5. import { connect } from 'react-redux';
  6. import { Dialog, hideDialog } from '../../base/dialog';
  7. import { translate } from '../../base/i18n';
  8. import DesktopPickerPane from './DesktopPickerPane';
  9. import { obtainDesktopSources } from '../functions';
  10. const THUMBNAIL_SIZE = {
  11. height: 300,
  12. width: 300
  13. };
  14. const UPDATE_INTERVAL = 2000;
  15. type TabConfiguration = {
  16. defaultSelected?: boolean,
  17. label: string
  18. };
  19. const TAB_CONFIGURATIONS: { [type: string]: TabConfiguration} = {
  20. screen: {
  21. /**
  22. * The indicator which determines whether this tab configuration is
  23. * selected by default.
  24. *
  25. * @type {boolean}
  26. */
  27. defaultSelected: true,
  28. label: 'dialog.yourEntireScreen'
  29. },
  30. window: {
  31. label: 'dialog.applicationWindow'
  32. }
  33. };
  34. const VALID_TYPES = Object.keys(TAB_CONFIGURATIONS);
  35. /**
  36. * React component for DesktopPicker.
  37. *
  38. * @extends Component
  39. */
  40. class DesktopPicker extends Component {
  41. /**
  42. * DesktopPicker component's property types.
  43. *
  44. * @static
  45. */
  46. static propTypes = {
  47. /**
  48. * An array with desktop sharing sources to be displayed.
  49. */
  50. desktopSharingSources: PropTypes.arrayOf(PropTypes.string),
  51. /**
  52. * Used to request DesktopCapturerSources.
  53. */
  54. dispatch: PropTypes.func,
  55. /**
  56. * The callback to be invoked when the component is closed or when
  57. * a DesktopCapturerSource has been chosen.
  58. */
  59. onSourceChoose: PropTypes.func,
  60. /**
  61. * Used to obtain translations.
  62. */
  63. t: PropTypes.func
  64. };
  65. _poller = null;
  66. state = {
  67. selectedSource: {},
  68. sources: {},
  69. types: []
  70. };
  71. /**
  72. * Initializes a new DesktopPicker instance.
  73. *
  74. * @param {Object} props - The read-only properties with which the new
  75. * instance is to be initialized.
  76. */
  77. constructor(props) {
  78. super(props);
  79. // Bind event handlers so they are only bound once per instance.
  80. this._onCloseModal = this._onCloseModal.bind(this);
  81. this._onPreviewClick = this._onPreviewClick.bind(this);
  82. this._onSubmit = this._onSubmit.bind(this);
  83. this._updateSources = this._updateSources.bind(this);
  84. this.state.types
  85. = this._getValidTypes(this.props.desktopSharingSources);
  86. }
  87. /**
  88. * Starts polling.
  89. *
  90. * @inheritdoc
  91. * @returns {void}
  92. */
  93. componentDidMount() {
  94. this._startPolling();
  95. }
  96. /**
  97. * Notifies this mounted React Component that it will receive new props.
  98. * Sets a default selected source if one is not already set.
  99. *
  100. * @inheritdoc
  101. * @param {Object} nextProps - The read-only React Component props that this
  102. * instance will receive.
  103. * @returns {void}
  104. */
  105. componentWillReceiveProps(nextProps) {
  106. const { desktopSharingSources } = nextProps;
  107. /**
  108. * Do only reference check in order to not calculate the types on every
  109. * update. This is enough for our use case and we don't need value
  110. * checking because if the value is the same we won't change the
  111. * reference for the desktopSharingSources array.
  112. */
  113. if (desktopSharingSources !== this.props.desktopSharingSources) {
  114. this.setState({
  115. types: this._getValidTypes(desktopSharingSources)
  116. });
  117. }
  118. }
  119. /**
  120. * Clean up component and DesktopCapturerSource store state.
  121. *
  122. * @inheritdoc
  123. */
  124. componentWillUnmount() {
  125. this._stopPolling();
  126. }
  127. /**
  128. * Implements React's {@link Component#render()}.
  129. *
  130. * @inheritdoc
  131. */
  132. render() {
  133. return (
  134. <Dialog
  135. isModal = { false }
  136. okDisabled = { Boolean(!this.state.selectedSource.id) }
  137. okTitleKey = 'dialog.Share'
  138. onCancel = { this._onCloseModal }
  139. onSubmit = { this._onSubmit }
  140. titleKey = 'dialog.shareYourScreen'
  141. width = 'medium' >
  142. { this._renderTabs() }
  143. </Dialog>
  144. );
  145. }
  146. _onCloseModal: (?string, string) => void;
  147. /**
  148. * Dispatches an action to hide the DesktopPicker and invokes the passed in
  149. * callback with a selectedSource, if any.
  150. *
  151. * @param {string} [id] - The id of the DesktopCapturerSource to pass into
  152. * the onSourceChoose callback.
  153. * @param {string} type - The type of the DesktopCapturerSource to pass into
  154. * the onSourceChoose callback.
  155. * @returns {void}
  156. */
  157. _onCloseModal(id = '', type) {
  158. this.props.onSourceChoose(id, type);
  159. this.props.dispatch(hideDialog());
  160. }
  161. _onPreviewClick: (string, string) => void;
  162. /**
  163. * Sets the currently selected DesktopCapturerSource.
  164. *
  165. * @param {string} id - The id of DesktopCapturerSource.
  166. * @param {string} type - The type of DesktopCapturerSource.
  167. * @returns {void}
  168. */
  169. _onPreviewClick(id, type) {
  170. this.setState({
  171. selectedSource: {
  172. id,
  173. type
  174. }
  175. });
  176. }
  177. /**
  178. * Extracts only the valid types from the passed {@code types}.
  179. *
  180. * @param {Array<string>} types - The types to filter.
  181. * @returns {Array<string>} The filtered types.
  182. */
  183. _getValidTypes(types = []) {
  184. return types.filter(
  185. type => VALID_TYPES.includes(type));
  186. }
  187. _onSubmit: () => void;
  188. /**
  189. * Request to close the modal and execute callbacks with the selected source
  190. * id.
  191. *
  192. * @returns {void}
  193. */
  194. _onSubmit() {
  195. const { id, type } = this.state.selectedSource;
  196. this._onCloseModal(id, type);
  197. }
  198. /**
  199. * Configures and renders the tabs for display.
  200. *
  201. * @private
  202. * @returns {ReactElement}
  203. */
  204. _renderTabs() {
  205. const { selectedSource, sources, types } = this.state;
  206. const { t } = this.props;
  207. const tabs
  208. = types.map(
  209. type => {
  210. const { defaultSelected, label } = TAB_CONFIGURATIONS[type];
  211. return {
  212. content: <DesktopPickerPane
  213. key = { type }
  214. onClick = { this._onPreviewClick }
  215. onDoubleClick = { this._onCloseModal }
  216. selectedSourceId = { selectedSource.id }
  217. sources = { sources[type] }
  218. type = { type } />,
  219. defaultSelected,
  220. label: t(label)
  221. };
  222. });
  223. return <Tabs tabs = { tabs } />;
  224. }
  225. /**
  226. * Create an interval to update knwon available DesktopCapturerSources.
  227. *
  228. * @private
  229. * @returns {void}
  230. */
  231. _startPolling() {
  232. this._stopPolling();
  233. this._updateSources();
  234. this._poller = window.setInterval(this._updateSources, UPDATE_INTERVAL);
  235. }
  236. /**
  237. * Cancels the interval to update DesktopCapturerSources.
  238. *
  239. * @private
  240. * @returns {void}
  241. */
  242. _stopPolling() {
  243. window.clearInterval(this._poller);
  244. this._poller = null;
  245. }
  246. _updateSources: () => void;
  247. /**
  248. * Dispatches an action to get currently available DesktopCapturerSources.
  249. *
  250. * @private
  251. * @returns {void}
  252. */
  253. _updateSources() {
  254. const { types } = this.state;
  255. if (types.length > 0) {
  256. obtainDesktopSources(
  257. this.state.types,
  258. { thumbnailSize: THUMBNAIL_SIZE }
  259. )
  260. .then(sources => {
  261. const nextState: Object = {
  262. sources
  263. };
  264. // FIXME: selectedSource when screen is disabled, when the
  265. // source has been removed or when the selectedTab is changed!!!
  266. if (!this.state.selectedSource.id
  267. && sources.screen.length > 0) {
  268. nextState.selectedSource = {
  269. id: sources.screen[0].id,
  270. type: 'screen'
  271. };
  272. }
  273. // TODO: Maybe check if we have stopped the timer and unmounted
  274. // the component.
  275. this.setState(nextState);
  276. })
  277. .catch(() => { /* ignore */ });
  278. }
  279. }
  280. }
  281. export default translate(connect()(DesktopPicker));