diff --git a/css/modals/desktop-picker/_desktop-picker.scss b/css/modals/desktop-picker/_desktop-picker.scss index 7ca455880..7a175cd12 100644 --- a/css/modals/desktop-picker/_desktop-picker.scss +++ b/css/modals/desktop-picker/_desktop-picker.scss @@ -26,6 +26,13 @@ width: 30%; } } + + &-spinner { + justify-content: center; + display: flex; + height: 100%; + align-items: center; + } } .desktop-picker-source { diff --git a/react/features/desktop-picker/actionTypes.js b/react/features/desktop-picker/actionTypes.js deleted file mode 100644 index 28f11a11e..000000000 --- a/react/features/desktop-picker/actionTypes.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Action to remove known DesktopCapturerSources. - * - * { - * type: RESET_DESKTOP_SOURCES, - * } - */ -export const RESET_DESKTOP_SOURCES = Symbol('RESET_DESKTOP_SOURCES'); - -/** - * Action to replace stored DesktopCapturerSources with new sources. - * - * { - * type: UPDATE_DESKTOP_SOURCES, - * sources: {Array} - * } - */ -export const UPDATE_DESKTOP_SOURCES = Symbol('UPDATE_DESKTOP_SOURCES'); diff --git a/react/features/desktop-picker/actions.js b/react/features/desktop-picker/actions.js index c8bed9774..894d33890 100644 --- a/react/features/desktop-picker/actions.js +++ b/react/features/desktop-picker/actions.js @@ -1,64 +1,7 @@ import { openDialog } from '../base/dialog'; -import { - RESET_DESKTOP_SOURCES, - UPDATE_DESKTOP_SOURCES -} from './actionTypes'; import { DesktopPicker } from './components'; -const logger = require('jitsi-meet-logger').getLogger(__filename); - -/** - * Begins a request to get available DesktopCapturerSources. - * - * @param {Array} types - An array with DesktopCapturerSource type strings. - * @param {Object} options - Additional configuration for getting a list of - * sources. - * @param {Object} options.thumbnailSize - The desired height and width of the - * return native image object used for the preview image of the source. - * @returns {Function} - */ -export function obtainDesktopSources(types, options = {}) { - const capturerOptions = { - types - }; - - if (options.thumbnailSize) { - capturerOptions.thumbnailSize = options.thumbnailSize; - } - - return dispatch => { - const { JitsiMeetElectron } = window; - - if (JitsiMeetElectron && JitsiMeetElectron.obtainDesktopStreams) { - JitsiMeetElectron.obtainDesktopStreams( - sources => dispatch(updateDesktopSources(sources)), - error => - logger.error( - `Error while obtaining desktop sources: ${error}`), - capturerOptions - ); - } else { - logger.error( - 'Called JitsiMeetElectron.obtainDesktopStreams' - + ' but it is not defined'); - } - }; -} - -/** - * Signals to remove all stored DesktopCapturerSources. - * - * @returns {{ - * type: RESET_DESKTOP_SOURCES - * }} - */ -export function resetDesktopSources() { - return { - type: RESET_DESKTOP_SOURCES - }; -} - /** * Signals to open a dialog with the DesktopPicker component. * @@ -67,25 +10,11 @@ export function resetDesktopSources() { * a DesktopCapturerSource has been chosen. * @returns {Object} */ -export function showDesktopPicker(options, onSourceChoose) { +export function showDesktopPicker(options = {}, onSourceChoose) { + const { desktopSharingSources } = options; + return openDialog(DesktopPicker, { - options, + desktopSharingSources, onSourceChoose }); } - -/** - * Signals new DesktopCapturerSources have been received. - * - * @param {Object} sources - Arrays with DesktopCapturerSources. - * @returns {{ - * type: UPDATE_DESKTOP_SOURCES, - * sources: Array - * }} - */ -export function updateDesktopSources(sources) { - return { - type: UPDATE_DESKTOP_SOURCES, - sources - }; -} diff --git a/react/features/desktop-picker/components/DesktopPicker.js b/react/features/desktop-picker/components/DesktopPicker.js index 04ad232af..d42f6c876 100644 --- a/react/features/desktop-picker/components/DesktopPicker.js +++ b/react/features/desktop-picker/components/DesktopPicker.js @@ -8,22 +8,23 @@ import { connect } from 'react-redux'; import { Dialog, hideDialog } from '../../base/dialog'; import { translate } from '../../base/i18n'; -import { obtainDesktopSources, resetDesktopSources } from '../actions'; import DesktopPickerPane from './DesktopPickerPane'; +import { obtainDesktopSources } from '../functions'; const THUMBNAIL_SIZE = { height: 300, width: 300 }; -const UPDATE_INTERVAL = 1000; + +const UPDATE_INTERVAL = 2000; type TabConfiguration = { defaultSelected?: boolean, - label: string, - type: string + label: string }; -const TAB_CONFIGURATIONS: Array = [ - { + +const TAB_CONFIGURATIONS: { [type: string]: TabConfiguration} = { + screen: { /** * The indicator which determines whether this tab configuration is * selected by default. @@ -31,15 +32,14 @@ const TAB_CONFIGURATIONS: Array = [ * @type {boolean} */ defaultSelected: true, - label: 'dialog.yourEntireScreen', - type: 'screen' + label: 'dialog.yourEntireScreen' }, - { - label: 'dialog.applicationWindow', - type: 'window' + window: { + label: 'dialog.applicationWindow' } -]; -const VALID_TYPES = TAB_CONFIGURATIONS.map(c => c.type); +}; + +const VALID_TYPES = Object.keys(TAB_CONFIGURATIONS); /** * React component for DesktopPicker. @@ -47,21 +47,18 @@ const VALID_TYPES = TAB_CONFIGURATIONS.map(c => c.type); * @extends Component */ class DesktopPicker extends Component { - /** - * Default values for DesktopPicker component's properties. - * - * @static - */ - static defaultProps = { - options: {} - }; - /** * DesktopPicker component's property types. * * @static */ static propTypes = { + + /** + * An array with desktop sharing sources to be displayed. + */ + desktopSharingSources: PropTypes.arrayOf(PropTypes.string), + /** * Used to request DesktopCapturerSources. */ @@ -73,17 +70,6 @@ class DesktopPicker extends Component { */ onSourceChoose: PropTypes.func, - /** - * An object with options related to desktop sharing. - */ - options: PropTypes.object, - - /** - * An object with arrays of DesktopCapturerSources. The key should be - * the source type. - */ - sources: PropTypes.object, - /** * Used to obtain translations. */ @@ -94,8 +80,8 @@ class DesktopPicker extends Component { state = { selectedSource: {}, - tabsToPopulate: [], - typesToFetch: [] + sources: {}, + types: [] }; /** @@ -112,20 +98,18 @@ class DesktopPicker extends Component { this._onPreviewClick = this._onPreviewClick.bind(this); this._onSubmit = this._onSubmit.bind(this); this._updateSources = this._updateSources.bind(this); + + this.state.types + = this._getValidTypes(this.props.desktopSharingSources); } /** - * Perform an immediate update request for DesktopCapturerSources and begin - * requesting updates at an interval. + * Starts polling. * * @inheritdoc + * @returns {void} */ - componentWillMount() { - const { desktopSharingSources } = this.props.options; - - this._onSourceTypesConfigChanged( - desktopSharingSources); - this._updateSources(); + componentDidMount() { this._startPolling(); } @@ -139,20 +123,19 @@ class DesktopPicker extends Component { * @returns {void} */ componentWillReceiveProps(nextProps) { - if (!this.state.selectedSource.id - && nextProps.sources.screen.length) { + const { desktopSharingSources } = nextProps; + + /** + * Do only reference check in order to not calculate the types on every + * update. This is enough for our use case and we don't need value + * checking because if the value is the same we won't change the + * reference for the desktopSharingSources array. + */ + if (desktopSharingSources !== this.props.desktopSharingSources) { this.setState({ - selectedSource: { - id: nextProps.sources.screen[0].id, - type: 'screen' - } + types: this._getValidTypes(desktopSharingSources) }); } - - const { desktopSharingSources } = this.props.options; - - this._onSourceTypesConfigChanged( - desktopSharingSources); } /** @@ -162,7 +145,6 @@ class DesktopPicker extends Component { */ componentWillUnmount() { this._stopPolling(); - this.props.dispatch(resetDesktopSources()); } /** @@ -174,6 +156,7 @@ class DesktopPicker extends Component { return ( } desktopSharingSourceTypes - The types that will be - * fetched and displayed. - * @returns {void} + * @param {Array} types - The types to filter. + * @returns {Array} The filtered types. */ - _onSourceTypesConfigChanged(desktopSharingSourceTypes = []) { - const tabsToPopulate - = TAB_CONFIGURATIONS.filter(({ type }) => - desktopSharingSourceTypes.includes(type) - && VALID_TYPES.includes(type)); - - this.setState({ - tabsToPopulate, - typesToFetch: tabsToPopulate.map(c => c.type) - }); + _getValidTypes(types = []) { + return types.filter( + type => VALID_TYPES.includes(type)); } _onSubmit: () => void; @@ -259,18 +234,20 @@ class DesktopPicker extends Component { * @returns {ReactElement} */ _renderTabs() { - const { selectedSource } = this.state; - const { sources, t } = this.props; + const { selectedSource, sources, types } = this.state; + const { t } = this.props; const tabs - = this.state.tabsToPopulate.map( - ({ defaultSelected, label, type }) => { + = types.map( + type => { + const { defaultSelected, label } = TAB_CONFIGURATIONS[type]; + return { content: , defaultSelected, label: t(label) @@ -288,6 +265,7 @@ class DesktopPicker extends Component { */ _startPolling() { this._stopPolling(); + this._updateSources(); this._poller = window.setInterval(this._updateSources, UPDATE_INTERVAL); } @@ -311,28 +289,35 @@ class DesktopPicker extends Component { * @returns {void} */ _updateSources() { - this.props.dispatch(obtainDesktopSources( - this.state.typesToFetch, - { - THUMBNAIL_SIZE - } - )); + const { types } = this.state; + + if (types.length > 0) { + obtainDesktopSources( + this.state.types, + { thumbnailSize: THUMBNAIL_SIZE } + ) + .then(sources => { + const nextState: Object = { + sources + }; + + // FIXME: selectedSource when screen is disabled, when the + // source has been removed or when the selectedTab is changed!!! + if (!this.state.selectedSource.id + && sources.screen.length > 0) { + nextState.selectedSource = { + id: sources.screen[0].id, + type: 'screen' + }; + } + + // TODO: Maybe check if we have stopped the timer and unmounted + // the component. + this.setState(nextState); + }) + .catch(() => { /* ignore */ }); + } } } -/** - * Maps (parts of) the Redux state to the associated DesktopPicker's props. - * - * @param {Object} state - Redux state. - * @private - * @returns {{ - * sources: Object - * }} - */ -function _mapStateToProps(state) { - return { - sources: state['features/desktop-picker'] - }; -} - -export default translate(connect(_mapStateToProps)(DesktopPicker)); +export default translate(connect()(DesktopPicker)); diff --git a/react/features/desktop-picker/components/DesktopPickerPane.js b/react/features/desktop-picker/components/DesktopPickerPane.js index 1b864c261..c8546a67c 100644 --- a/react/features/desktop-picker/components/DesktopPickerPane.js +++ b/react/features/desktop-picker/components/DesktopPickerPane.js @@ -1,3 +1,4 @@ +import Spinner from '@atlaskit/spinner'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; @@ -60,7 +61,7 @@ class DesktopPickerPane extends Component { const classNames = `desktop-picker-pane default-scrollbar source-type-${type}`; const previews - = sources.map( + = sources ? sources.map( source => // eslint-disable-next-line react/jsx-wrap-multilines @@ -70,7 +71,14 @@ class DesktopPickerPane extends Component { onDoubleClick = { onDoubleClick } selected = { source.id === selectedSourceId } source = { source } - type = { type } />); + type = { type } />) + : ( // eslint-disable-line no-extra-parens +
+ +
+ ); return (
diff --git a/react/features/desktop-picker/functions.js b/react/features/desktop-picker/functions.js new file mode 100644 index 000000000..b90d9794a --- /dev/null +++ b/react/features/desktop-picker/functions.js @@ -0,0 +1,73 @@ + +const logger = require('jitsi-meet-logger').getLogger(__filename); + +/** + * Begins a request to get available DesktopCapturerSources. + * + * @param {Array} types - An array with DesktopCapturerSource type strings. + * @param {Object} options - Additional configuration for getting a list of + * sources. + * @param {Object} options.thumbnailSize - The desired height and width of the + * return native image object used for the preview image of the source. + * @returns {Function} + */ +export function obtainDesktopSources(types, options = {}) { + const capturerOptions = { + types + }; + + if (options.thumbnailSize) { + capturerOptions.thumbnailSize = options.thumbnailSize; + } + + return new Promise((resolve, reject) => { + const { JitsiMeetElectron } = window; + + if (JitsiMeetElectron && JitsiMeetElectron.obtainDesktopStreams) { + JitsiMeetElectron.obtainDesktopStreams( + sources => resolve(_seperateSourcesByType(sources)), + error => { + logger.error( + `Error while obtaining desktop sources: ${error}`); + reject(error); + }, + capturerOptions + ); + } else { + const reason = 'Called JitsiMeetElectron.obtainDesktopStreams' + + ' but it is not defined'; + + logger.error(reason); + + return Promise.reject(new Error(reason)); + } + }); +} + + +/** + * Converts an array of DesktopCapturerSources to an object with types for keys + * and values being an array with sources of the key's type. + * + * @param {Array} sources - DesktopCapturerSources. + * @private + * @returns {Object} An object with the sources split into seperate arrays based + * on source type. + */ +function _seperateSourcesByType(sources = []) { + const sourcesByType = { + screen: [], + window: [] + }; + + sources.forEach(source => { + const idParts = source.id.split(':'); + const type = idParts[0]; + + if (sourcesByType[type]) { + sourcesByType[type].push(source); + } + }); + + return sourcesByType; +} diff --git a/react/features/desktop-picker/index.js b/react/features/desktop-picker/index.js index b2e9c09f6..3c46ed49d 100644 --- a/react/features/desktop-picker/index.js +++ b/react/features/desktop-picker/index.js @@ -1,5 +1,2 @@ export * from './actions'; -export * from './actionTypes'; export * from './components'; - -import './reducer'; diff --git a/react/features/desktop-picker/reducer.js b/react/features/desktop-picker/reducer.js deleted file mode 100644 index 561eeee8f..000000000 --- a/react/features/desktop-picker/reducer.js +++ /dev/null @@ -1,62 +0,0 @@ -import { ReducerRegistry } from '../base/redux'; - -import { - RESET_DESKTOP_SOURCES, - UPDATE_DESKTOP_SOURCES -} from './actionTypes'; - -const DEFAULT_STATE = { - screen: [], - window: [] -}; - -/** - * Listen for actions that mutate the known available DesktopCapturerSources. - * - * @param {Object[]} state - Current state. - * @param {Object} action - Action object. - * @param {string} action.type - Type of action. - * @param {Array} action.sources - DesktopCapturerSources. - * @returns {Object} - */ -ReducerRegistry.register( - 'features/desktop-picker', - (state = DEFAULT_STATE, action) => { - switch (action.type) { - case RESET_DESKTOP_SOURCES: - return { ...DEFAULT_STATE }; - - case UPDATE_DESKTOP_SOURCES: - return _seperateSourcesByType(action.sources); - - default: - return state; - } - }); - -/** - * Converts an array of DesktopCapturerSources to an object with types for keys - * and values being an array with sources of the key's type. - * - * @param {Array} sources - DesktopCapturerSources. - * @private - * @returns {Object} An object with the sources split into seperate arrays based - * on source type. - */ -function _seperateSourcesByType(sources = []) { - const sourcesByType = { - screen: [], - window: [] - }; - - sources.forEach(source => { - const idParts = source.id.split(':'); - const type = idParts[0]; - - if (sourcesByType[type]) { - sourcesByType[type].push(source); - } - }); - - return sourcesByType; -}