diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js index 119cf1b..8a474cc 100644 --- a/app/lib/RoomClient.js +++ b/app/lib/RoomClient.js @@ -20,9 +20,8 @@ const ROOM_OPTIONS = const VIDEO_CONSTRAINS = { - qvga : { width: { ideal: 320 }, height: { ideal: 240 }, aspectRatio: 1.334 }, - vga : { width: { ideal: 640 }, height: { ideal: 480 }, aspectRatio: 1.334 }, - hd : { width: { ideal: 800 }, height: { ideal: 600 }, aspectRatio: 1.334 } + width : { ideal: 1280 }, + aspectRatio : 1.334 }; export default class RoomClient @@ -55,6 +54,9 @@ export default class RoomClient // Redux store getState function. this._getState = getState; + // This device + this._device = device; + // My peer name. this._peerName = peerName; @@ -79,7 +81,9 @@ export default class RoomClient // Map of webcam MediaDeviceInfos indexed by deviceId. // @type {Map} - this._webcams = new Map(); + this._webcams = {}; + + this._audioDevices = {}; // Local Webcam. Object with: // - {MediaDeviceInfo} [device] @@ -89,6 +93,10 @@ export default class RoomClient resolution : 'hd' }; + this._audioDevice = { + device : null + }; + this._screenSharing = ScreenShare.create(); this._screenSharingProducer = null; @@ -117,6 +125,8 @@ export default class RoomClient login() { + this._dispatch(stateActions.setLoginInProgress(true)); + const url = `/login?roomId=${this._room.roomId}&peerName=${this._peerName}`; this._loginWindow = window.open(url, 'loginWindow'); @@ -366,9 +376,75 @@ export default class RoomClient }); } - changeWebcam() + changeAudioDevice(deviceId) { - logger.debug('changeWebcam()'); + logger.debug('changeAudioDevice() [deviceId: %s]', deviceId); + + this._dispatch( + stateActions.setAudioInProgress(true)); + + return Promise.resolve() + .then(() => + { + this._audioDevice.device = this._audioDevices[deviceId]; + + logger.debug( + 'changeAudioDevice() | new selected webcam [device:%o]', + this._audioDevice.device); + }) + .then(() => + { + const { device } = this._audioDevice; + + if (!device) + throw new Error('no audio devices'); + + logger.debug('changeAudioDevice() | calling getUserMedia()'); + + return navigator.mediaDevices.getUserMedia( + { + audio : + { + deviceId : { exact: device.deviceId } + } + }); + }) + .then((stream) => + { + const track = stream.getAudioTracks()[0]; + + return this._micProducer.replaceTrack(track) + .then((newTrack) => + { + track.stop(); + + return newTrack; + }); + }) + .then((newTrack) => + { + this._dispatch( + stateActions.setProducerTrack(this._micProducer.id, newTrack)); + + return this._updateAudioDevices(); + }) + .then(() => + { + this._dispatch( + stateActions.setAudioInProgress(false)); + }) + .catch((error) => + { + logger.error('changeAudioDevice() failed: %o', error); + + this._dispatch( + stateActions.setAudioInProgress(false)); + }); + } + + changeWebcam(deviceId) + { + logger.debug('changeWebcam() [deviceId: %s]', deviceId); this._dispatch( stateActions.setWebcamInProgress(true)); @@ -376,22 +452,7 @@ export default class RoomClient return Promise.resolve() .then(() => { - return this._updateWebcams(); - }) - .then(() => - { - const array = Array.from(this._webcams.keys()); - const len = array.length; - const deviceId = - this._webcam.device ? this._webcam.device.deviceId : undefined; - let idx = array.indexOf(deviceId); - - if (idx < len - 1) - idx++; - else - idx = 0; - - this._webcam.device = this._webcams.get(array[idx]); + this._webcam.device = this._webcams[deviceId]; logger.debug( 'changeWebcam() | new selected webcam [device:%o]', @@ -402,7 +463,7 @@ export default class RoomClient }) .then(() => { - const { device, resolution } = this._webcam; + const { device } = this._webcam; if (!device) throw new Error('no webcam devices'); @@ -414,7 +475,7 @@ export default class RoomClient video : { deviceId : { exact: device.deviceId }, - ...VIDEO_CONSTRAINS[resolution] + ...VIDEO_CONSTRAINS } }); }) @@ -435,6 +496,10 @@ export default class RoomClient this._dispatch( stateActions.setProducerTrack(this._webcamProducer.id, newTrack)); + return this._updateWebcams(); + }) + .then(() => + { this._dispatch( stateActions.setWebcamInProgress(false)); }) @@ -479,7 +544,7 @@ export default class RoomClient }) .then(() => { - const { device, resolution } = this._webcam; + const { device } = this._webcam; logger.debug('changeWebcamResolution() | calling getUserMedia()'); @@ -488,7 +553,7 @@ export default class RoomClient video : { deviceId : { exact: device.deviceId }, - ...VIDEO_CONSTRAINS[resolution] + ...VIDEO_CONSTRAINS } }); }) @@ -935,6 +1000,8 @@ export default class RoomClient )); } this.closeLoginWindow(); + + this._dispatch(stateActions.setLoginInProgress(false)); break; } @@ -1163,6 +1230,12 @@ export default class RoomClient let producer; return Promise.resolve() + .then(() => + { + logger.debug('_setMicProducer() | calling _updateAudioDevices()'); + + return this._updateAudioDevices(); + }) .then(() => { logger.debug('_setMicProducer() | calling getUserMedia()'); @@ -1380,7 +1453,7 @@ export default class RoomClient return Promise.resolve() .then(() => { - const { device, resolution } = this._webcam; + const { device } = this._webcam; if (!device) throw new Error('no webcam devices'); @@ -1392,7 +1465,7 @@ export default class RoomClient video : { deviceId : { exact: device.deviceId }, - ...VIDEO_CONSTRAINS[resolution] + ...VIDEO_CONSTRAINS } }); }) @@ -1482,12 +1555,64 @@ export default class RoomClient }); } + _updateAudioDevices() + { + logger.debug('_updateAudioDevices()'); + + // Reset the list. + this._audioDevices = {}; + + return Promise.resolve() + .then(() => + { + logger.debug('_updateAudioDevices() | calling enumerateDevices()'); + + return navigator.mediaDevices.enumerateDevices(); + }) + .then((devices) => + { + for (const device of devices) + { + if (device.kind !== 'audioinput') + continue; + + this._audioDevices[device.deviceId] = { + value : device.deviceId, + label : device.label, + deviceId : device.deviceId + }; + } + }) + .then(() => + { + const currentAudioDeviceId = + this._audioDevice.device ? this._audioDevice.device.deviceId : undefined; + + logger.debug('_updateAudioDevices() [audiodevices:%o]', this._audioDevices); + + const len = Object.keys(this._audioDevices).length; + + if (len === 0) + this._audioDevice.device = null; + else if (!this._audioDevices[currentAudioDeviceId]) + for (this._audioDevice.device in this._audioDevices) + if (this._audioDevices.hasOwnProperty(this._audioDevice.device)) + break; + + this._dispatch( + stateActions.setCanChangeAudioDevice(len >= 2)); + if (len >= 1) + this._dispatch( + stateActions.setAudioDevices(this._audioDevices)); + }); + } + _updateWebcams() { logger.debug('_updateWebcams()'); // Reset the list. - this._webcams = new Map(); + this._webcams = {}; return Promise.resolve() .then(() => @@ -1503,25 +1628,34 @@ export default class RoomClient if (device.kind !== 'videoinput') continue; - this._webcams.set(device.deviceId, device); + this._webcams[device.deviceId] = { + value : device.deviceId, + label : device.label, + deviceId : device.deviceId + }; } }) .then(() => { - const array = Array.from(this._webcams.values()); - const len = array.length; const currentWebcamId = this._webcam.device ? this._webcam.device.deviceId : undefined; - logger.debug('_updateWebcams() [webcams:%o]', array); + logger.debug('_updateWebcams() [webcams:%o]', this._webcams); + + const len = Object.keys(this._webcams).length; if (len === 0) this._webcam.device = null; - else if (!this._webcams.has(currentWebcamId)) - this._webcam.device = array[0]; + else if (!this._webcams[currentWebcamId]) + for (this._webcam.device in this._webcams) + if (this._webcams.hasOwnProperty(this._webcam.device)) + break; this._dispatch( - stateActions.setCanChangeWebcam(this._webcams.size >= 2)); + stateActions.setCanChangeWebcam(len >= 2)); + if (len >= 1) + this._dispatch( + stateActions.setWebcamDevices(this._webcams)); }); } diff --git a/app/lib/components/Me.jsx b/app/lib/components/Me.jsx index 36251c8..41b6231 100644 --- a/app/lib/components/Me.jsx +++ b/app/lib/components/Me.jsx @@ -35,8 +35,7 @@ class Me extends React.Component onMuteMic, onUnmuteMic, onEnableWebcam, - onDisableWebcam, - onChangeWebcam + onDisableWebcam } = this.props; let micState; @@ -59,13 +58,6 @@ class Me extends React.Component else webcamState = 'off'; - let changeWebcamState; - - if (Boolean(webcamProducer) && me.canChangeWebcam) - changeWebcamState = 'on'; - else - changeWebcamState = 'unsupported'; - const videoVisible = ( Boolean(webcamProducer) && !webcamProducer.locallyPaused && @@ -88,7 +80,9 @@ class Me extends React.Component {connected ?
{ micState === 'on' ? onMuteMic() : onUnmuteMic(); @@ -104,13 +98,6 @@ class Me extends React.Component webcamState === 'on' ? onDisableWebcam() : onEnableWebcam(); }} /> - -
onChangeWebcam()} - />
:null } @@ -179,8 +166,7 @@ Me.propTypes = onMuteMic : PropTypes.func.isRequired, onUnmuteMic : PropTypes.func.isRequired, onEnableWebcam : PropTypes.func.isRequired, - onDisableWebcam : PropTypes.func.isRequired, - onChangeWebcam : PropTypes.func.isRequired + onDisableWebcam : PropTypes.func.isRequired }; const mapStateToProps = (state) => @@ -209,8 +195,7 @@ const mapDispatchToProps = (dispatch) => onMuteMic : () => dispatch(requestActions.muteMic()), onUnmuteMic : () => dispatch(requestActions.unmuteMic()), onEnableWebcam : () => dispatch(requestActions.enableWebcam()), - onDisableWebcam : () => dispatch(requestActions.disableWebcam()), - onChangeWebcam : () => dispatch(requestActions.changeWebcam()) + onDisableWebcam : () => dispatch(requestActions.disableWebcam()) }; }; diff --git a/app/lib/components/Peers.jsx b/app/lib/components/Peers.jsx index 39dd406..33d74dc 100644 --- a/app/lib/components/Peers.jsx +++ b/app/lib/components/Peers.jsx @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import * as appPropTypes from './appPropTypes'; -import * as stateActions from '../redux/stateActions'; import { Appear } from './transitions'; import Peer from './Peer'; @@ -13,13 +12,20 @@ class Peers extends React.Component { super(); this.state = { - ratio : 1.334 + peerWidth : 400, + peerHeight : 300, + ratio : 1.334 }; } - updateDimensions(nextProps = null) + resizeUpdate() { - const n = nextProps ? nextProps.peers.length : this.props.peers.length; + this.updateDimensions(); + } + + updateDimensions(props = this.props) + { + const n = props.peers ? props.peers.length : 0; if (n == 0) { @@ -47,40 +53,42 @@ class Peers extends React.Component break; } } - if (Math.ceil(this.props.peerWidth) !== Math.ceil(0.9 * x)) + if (Math.ceil(this.state.peerWidth) !== Math.ceil(0.9 * x)) { - this.props.onComponentResize(0.9 * x, 0.9 * y); + this.setState({ + peerWidth : 0.9 * x, + peerHeight : 0.9 * y + }); } } componentDidMount() { - window.addEventListener('resize', this.updateDimensions.bind(this)); + window.addEventListener('resize', this.resizeUpdate.bind(this)); } componentWillUnmount() { - window.removeEventListener('resize', this.updateDimensions.bind(this)); + window.removeEventListener('resize', this.resizeUpdate.bind(this)); } componentWillReceiveProps(nextProps) { - this.updateDimensions(nextProps); + if (nextProps.peers) + this.updateDimensions(nextProps); } render() { const { activeSpeakerName, - peers, - peerWidth, - peerHeight + peers } = this.props; const style = { - 'width' : peerWidth, - 'height' : peerHeight + 'width' : this.state.peerWidth, + 'height' : this.state.peerHeight }; return ( @@ -109,39 +117,21 @@ class Peers extends React.Component Peers.propTypes = { peers : PropTypes.arrayOf(appPropTypes.Peer).isRequired, - activeSpeakerName : PropTypes.string, - peerHeight : PropTypes.number, - peerWidth : PropTypes.number, - onComponentResize : PropTypes.func.isRequired -}; - -const mapDispatchToProps = (dispatch) => -{ - return { - onComponentResize : (peerWidth, peerHeight) => - { - dispatch(stateActions.onComponentResize(peerWidth, peerHeight)); - } - }; + activeSpeakerName : PropTypes.string }; const mapStateToProps = (state) => { - // TODO: This is not OK since it's creating a new array every time, so triggering a - // component rendering. const peersArray = Object.values(state.peers); return { peers : peersArray, - activeSpeakerName : state.room.activeSpeakerName, - peerHeight : state.room.peerHeight, - peerWidth : state.room.peerWidth + activeSpeakerName : state.room.activeSpeakerName }; }; const PeersContainer = connect( - mapStateToProps, - mapDispatchToProps + mapStateToProps )(Peers); export default PeersContainer; diff --git a/app/lib/components/Room.jsx b/app/lib/components/Room.jsx index 80f030b..f6f8acf 100644 --- a/app/lib/components/Room.jsx +++ b/app/lib/components/Room.jsx @@ -5,12 +5,14 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import ClipboardButton from 'react-clipboard.js'; import * as appPropTypes from './appPropTypes'; +import * as stateActions from '../redux/stateActions'; import * as requestActions from '../redux/requestActions'; import { Appear } from './transitions'; import Me from './Me'; import Peers from './Peers'; import Notifications from './Notifications'; import ChatWidget from './ChatWidget'; +import Settings from './Settings'; class Room extends React.Component { @@ -22,8 +24,8 @@ class Room extends React.Component amActiveSpeaker, screenProducer, onRoomLinkCopy, - onSetAudioMode, - onRestartIce, + onToggleSettings, + onLogin, onShareScreen, onUnShareScreen, onNeedExtension, @@ -140,22 +142,22 @@ class Room extends React.Component />
onSetAudioMode(!me.audioOnly)} + onClick={() => onToggleSettings()} />
onRestartIce()} + onClick={() => onLogin()} />
+ + @@ -227,16 +233,9 @@ const mapDispatchToProps = (dispatch) => text : 'Room link copied to the clipboard' })); }, - onSetAudioMode : (enable) => + onToggleSettings : () => { - if (enable) - dispatch(requestActions.enableAudioOnly()); - else - dispatch(requestActions.disableAudioOnly()); - }, - onRestartIce : () => - { - dispatch(requestActions.restartIce()); + dispatch(stateActions.toggleSettings()); }, onToggleHand : (enable) => { @@ -260,6 +259,10 @@ const mapDispatchToProps = (dispatch) => onNeedExtension : () => { dispatch(requestActions.installExtension()); + }, + onLogin : () => + { + dispatch(requestActions.userLogin()); } }; }; diff --git a/app/lib/components/Settings.jsx b/app/lib/components/Settings.jsx new file mode 100644 index 0000000..9e130a1 --- /dev/null +++ b/app/lib/components/Settings.jsx @@ -0,0 +1,127 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import * as appPropTypes from './appPropTypes'; +import * as requestActions from '../redux/requestActions'; +import PropTypes from 'prop-types'; +import { Appear } from './transitions'; +import Dropdown from 'react-dropdown'; + +class Settings extends React.Component +{ + constructor(props) + { + super(props); + } + + render() + { + const { + room, + me, + handleChangeWebcam, + handleChangeAudioDevice, + onToggleSettings + } = this.props; + + if (!room.showSettings) + return null; + + let webcams; + let webcamText; + + if (me.canChangeWebcam) + webcamText = 'Select camera'; + else + webcamText = 'Unable to select camera'; + + if (me.webcamDevices) + webcams = Object.values(me.webcamDevices); + else + webcams = []; + + let audioDevices; + let audioDevicesText; + + if (me.canChangeAudioDevice) + audioDevicesText = 'Select audio input device'; + else + audioDevicesText = 'Unable to select audio input device'; + + if (me.audioDevices) + audioDevices = Object.values(me.audioDevices); + else + audioDevices = []; + + return ( + +
+
+
+ Settings +
+
+ + +
+
+ onToggleSettings()} + > + Close + +
+
+
+
+ ); + } +} + +Settings.propTypes = +{ + me : appPropTypes.Me.isRequired, + room : appPropTypes.Room.isRequired, + onToggleSettings : PropTypes.func.isRequired, + handleChangeWebcam : PropTypes.func.isRequired, + handleChangeAudioDevice : PropTypes.func.isRequired +}; + +const mapStateToProps = (state) => +{ + return { + me : state.me, + room : state.room + }; +}; + +const mapDispatchToProps = (dispatch) => +{ + return { + handleChangeWebcam : (device) => + { + dispatch(requestActions.changeWebcam(device.value)); + }, + handleChangeAudioDevice : (device) => + { + dispatch(requestActions.changeAudioDevice(device.value)); + } + }; +}; + +const SettingsContainer = connect( + mapStateToProps, + mapDispatchToProps +)(Settings); + +export default SettingsContainer; diff --git a/app/lib/redux/reducers/me.js b/app/lib/redux/reducers/me.js index 8f15038..752ef6d 100644 --- a/app/lib/redux/reducers/me.js +++ b/app/lib/redux/reducers/me.js @@ -8,9 +8,14 @@ const initialState = canSendWebcam : false, canShareScreen : false, needExtension : false, + canChangeAudioDevice : false, + audioDevices : null, canChangeWebcam : false, + webcamDevices : null, webcamInProgress : false, + audioInProgress : false, screenShareInProgress : false, + loginInProgress : false, audioOnly : false, audioOnlyInProgress : false, raiseHand : false, @@ -43,6 +48,20 @@ const me = (state = initialState, action) => return { ...state, canShareScreen, needExtension }; } + case 'SET_CAN_CHANGE_AUDIO_DEVICE': + { + const canChangeAudioDevice = action.payload; + + return { ...state, canChangeAudioDevice }; + } + + case 'SET_AUDIO_DEVICES': + { + const { devices } = action.payload; + + return { ...state, audioDevices: devices }; + } + case 'SET_CAN_CHANGE_WEBCAM': { const canChangeWebcam = action.payload; @@ -50,6 +69,20 @@ const me = (state = initialState, action) => return { ...state, canChangeWebcam }; } + case 'SET_WEBCAM_DEVICES': + { + const { devices } = action.payload; + + return { ...state, webcamDevices: devices }; + } + + case 'SET_AUDIO_IN_PROGRESS': + { + const { flag } = action.payload; + + return { ...state, audioInProgress: flag }; + } + case 'SET_WEBCAM_IN_PROGRESS': { const { flag } = action.payload; @@ -64,6 +97,13 @@ const me = (state = initialState, action) => return { ...state, screenShareInProgress: flag }; } + case 'SET_LOGIN_IN_PROGRESS': + { + const { flag } = action.payload; + + return { ...state, loginInProgress: flag }; + } + case 'SET_DISPLAY_NAME': { let { displayName } = action.payload; diff --git a/app/lib/redux/reducers/room.js b/app/lib/redux/reducers/room.js index 622ba71..1122f82 100644 --- a/app/lib/redux/reducers/room.js +++ b/app/lib/redux/reducers/room.js @@ -4,7 +4,8 @@ const initialState = state : 'new', // new/connecting/connected/disconnected/closed, activeSpeakerName : null, peerHeight : 300, - peerWidth : 400 + peerWidth : 400, + showSettings : false }; const room = (state = initialState, action) => @@ -42,6 +43,13 @@ const room = (state = initialState, action) => return { ...state, peerWidth: peerWidth, peerHeight: peerHeight }; } + case 'TOGGLE_SETTINGS': + { + const showSettings = !state.showSettings; + + return { ...state, showSettings }; + } + default: return state; } diff --git a/app/lib/redux/requestActions.js b/app/lib/redux/requestActions.js index f8d2178..ff865fa 100644 --- a/app/lib/redux/requestActions.js +++ b/app/lib/redux/requestActions.js @@ -57,10 +57,19 @@ export const disableWebcam = () => }; }; -export const changeWebcam = () => +export const changeWebcam = (deviceId) => { return { - type : 'CHANGE_WEBCAM' + type : 'CHANGE_WEBCAM', + payload : { deviceId } + }; +}; + +export const changeAudioDevice = (deviceId) => +{ + return { + type : 'CHANGE_AUDIO_DEVICE', + payload : { deviceId } }; }; @@ -110,6 +119,13 @@ export const resumePeerVideo = (peerName) => }; }; +export const userLogin = () => +{ + return { + type : 'USER_LOGIN' + }; +}; + export const raiseHand = () => { return { diff --git a/app/lib/redux/roomClientMiddleware.js b/app/lib/redux/roomClientMiddleware.js index 628cba3..64a1059 100644 --- a/app/lib/redux/roomClientMiddleware.js +++ b/app/lib/redux/roomClientMiddleware.js @@ -83,7 +83,18 @@ export default ({ dispatch, getState }) => (next) => case 'CHANGE_WEBCAM': { - client.changeWebcam(); + const { deviceId } = action.payload; + + client.changeWebcam(deviceId); + + break; + } + + case 'CHANGE_AUDIO_DEVICE': + { + const { deviceId } = action.payload; + + client.changeAudioDevice(deviceId); break; } @@ -145,6 +156,13 @@ export default ({ dispatch, getState }) => (next) => break; } + case 'USER_LOGIN': + { + client.login(); + + break; + } + case 'LOWER_HAND': { client.sendRaiseHandState(false); diff --git a/app/lib/redux/stateActions.js b/app/lib/redux/stateActions.js index 4c6b61d..d6bf721 100644 --- a/app/lib/redux/stateActions.js +++ b/app/lib/redux/stateActions.js @@ -54,6 +54,22 @@ export const setScreenCapabilities = ({ canShareScreen, needExtension }) => }; }; +export const setCanChangeAudioDevice = (flag) => +{ + return { + type : 'SET_CAN_CHANGE_AUDIO_DEVICE', + payload : flag + }; +}; + +export const setAudioDevices = (devices) => +{ + return { + type : 'SET_AUDIO_DEVICES', + payload : { devices } + }; +}; + export const setCanChangeWebcam = (flag) => { return { @@ -62,6 +78,14 @@ export const setCanChangeWebcam = (flag) => }; }; +export const setWebcamDevices = (devices) => +{ + return { + type : 'SET_WEBCAM_DEVICES', + payload : { devices } + }; +}; + export const setDisplayName = (displayName) => { return { @@ -110,6 +134,21 @@ export const setMyRaiseHandState = (flag) => }; }; +export const setLoginInProgress = (flag) => +{ + return { + type : 'SET_LOGIN_IN_PROGRESS', + payload : { flag } + }; +}; + +export const toggleSettings = () => +{ + return { + type : 'TOGGLE_SETTINGS' + }; +}; + export const setMyRaiseHandStateInProgress = (flag) => { return { @@ -174,6 +213,14 @@ export const setProducerTrack = (producerId, track) => }; }; +export const setAudioInProgress = (flag) => +{ + return { + type : 'SET_AUDIO_IN_PROGRESS', + payload : { flag } + }; +}; + export const setWebcamInProgress = (flag) => { return { diff --git a/app/package-lock.json b/app/package-lock.json index 1dfaf80..920397c 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -591,6 +591,28 @@ } } }, + "babel-helper-bindify-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", + "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, "babel-helper-builder-react-jsx": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", @@ -626,6 +648,29 @@ "lodash": "4.17.4" } }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-helper-explode-class": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", + "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", + "dev": true, + "requires": { + "babel-helper-bindify-decorators": "6.24.1", + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, "babel-helper-function-name": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", @@ -680,6 +725,19 @@ "lodash": "4.17.4" } }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, "babel-helper-replace-supers": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", @@ -722,12 +780,72 @@ "babel-runtime": "6.26.0" } }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-async-generators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", + "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", + "dev": true + }, + "babel-plugin-syntax-class-constructor-call": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", + "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", + "dev": true + }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, + "babel-plugin-syntax-decorators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", + "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", + "dev": true + }, + "babel-plugin-syntax-do-expressions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz", + "integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=", + "dev": true + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-export-extensions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", + "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", + "dev": true + }, "babel-plugin-syntax-flow": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", "dev": true }, + "babel-plugin-syntax-function-bind": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz", + "integrity": "sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y=", + "dev": true + }, "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", @@ -740,6 +858,80 @@ "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", "dev": true }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-generator-functions": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", + "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-generators": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-class-constructor-call": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz", + "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", + "dev": true, + "requires": { + "babel-plugin-syntax-class-constructor-call": "6.18.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true, + "requires": { + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-plugin-transform-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", + "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "dev": true, + "requires": { + "babel-helper-explode-class": "6.24.1", + "babel-plugin-syntax-decorators": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-types": "6.26.0" + } + }, + "babel-plugin-transform-do-expressions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz", + "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=", + "dev": true, + "requires": { + "babel-plugin-syntax-do-expressions": "6.13.0", + "babel-runtime": "6.26.0" + } + }, "babel-plugin-transform-es2015-arrow-functions": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", @@ -974,6 +1166,27 @@ "regexpu-core": "2.0.0" } }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", + "babel-plugin-syntax-exponentiation-operator": "6.13.0", + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-transform-export-extensions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz", + "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", + "dev": true, + "requires": { + "babel-plugin-syntax-export-extensions": "6.13.0", + "babel-runtime": "6.26.0" + } + }, "babel-plugin-transform-flow-strip-types": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", @@ -984,6 +1197,16 @@ "babel-runtime": "6.26.0" } }, + "babel-plugin-transform-function-bind": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz", + "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=", + "dev": true, + "requires": { + "babel-plugin-syntax-function-bind": "6.13.0", + "babel-runtime": "6.26.0" + } + }, "babel-plugin-transform-object-assign": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz", @@ -1126,6 +1349,53 @@ "babel-preset-flow": "6.23.0" } }, + "babel-preset-stage-0": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz", + "integrity": "sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo=", + "dev": true, + "requires": { + "babel-plugin-transform-do-expressions": "6.22.0", + "babel-plugin-transform-function-bind": "6.22.0", + "babel-preset-stage-1": "6.24.1" + } + }, + "babel-preset-stage-1": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz", + "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", + "dev": true, + "requires": { + "babel-plugin-transform-class-constructor-call": "6.24.1", + "babel-plugin-transform-export-extensions": "6.22.0", + "babel-preset-stage-2": "6.24.1" + } + }, + "babel-preset-stage-2": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", + "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "dev": true, + "requires": { + "babel-plugin-syntax-dynamic-import": "6.18.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-decorators": "6.24.1", + "babel-preset-stage-3": "6.24.1" + } + }, + "babel-preset-stage-3": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", + "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "dev": true, + "requires": { + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-generator-functions": "6.24.1", + "babel-plugin-transform-async-to-generator": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-object-rest-spread": "6.26.0" + } + }, "babel-register": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", @@ -3850,8 +4120,8 @@ "dev": true, "optional": true, "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" } }, "ansi-regex": { @@ -3924,7 +4194,7 @@ "bundled": true, "dev": true, "requires": { - "inherits": "2.0.3" + "inherits": "~2.0.0" } }, "boom": { @@ -3932,7 +4202,7 @@ "bundled": true, "dev": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "brace-expansion": { @@ -3994,7 +4264,7 @@ "bundled": true, "dev": true, "requires": { - "boom": "2.10.1" + "boom": "2.x.x" } }, "dashdash": { @@ -4052,7 +4322,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "extend": { @@ -4132,7 +4402,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -4261,7 +4531,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "jsbn": { @@ -4282,7 +4552,7 @@ "dev": true, "optional": true, "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stringify-safe": { @@ -8579,6 +8849,14 @@ "prop-types": "15.6.0" } }, + "react-dropdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-dropdown/-/react-dropdown-1.5.0.tgz", + "integrity": "sha512-rRv3a7NiP++yC1rzdjzkviC5ujq759i4SRa0M3C0Cr7loYT4Z3+JhSPekv1/04JiZNXX46cV3/g6A9kS7rkI4Q==", + "requires": { + "classnames": "2.2.5" + } + }, "react-redux": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz", diff --git a/app/package.json b/app/package.json index 8c5a1c4..85f4bc4 100644 --- a/app/package.json +++ b/app/package.json @@ -21,6 +21,7 @@ "react": "^16.2.0", "react-clipboard.js": "^1.1.3", "react-dom": "^16.2.0", + "react-dropdown": "^1.5.0", "react-redux": "^5.0.6", "react-spinner": "^0.2.7", "react-tooltip": "^3.4.0", @@ -38,6 +39,7 @@ "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", + "babel-preset-stage-0": "^6.24.1", "babelify": "^8.0.0", "browser-sync": "^2.23.6", "browserify": "^16.1.0", diff --git a/app/resources/images/icon_audio_only_white.svg b/app/resources/images/icon_audio_only_white.svg index 12a0389..fd5a889 100644 --- a/app/resources/images/icon_audio_only_white.svg +++ b/app/resources/images/icon_audio_only_white.svg @@ -1,4 +1,4 @@ - + diff --git a/app/resources/images/icon_login_black.svg b/app/resources/images/icon_login_black.svg new file mode 100644 index 0000000..6f366fe --- /dev/null +++ b/app/resources/images/icon_login_black.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/resources/images/icon_login_white.svg b/app/resources/images/icon_login_white.svg new file mode 100644 index 0000000..c7c190e --- /dev/null +++ b/app/resources/images/icon_login_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/resources/images/icon_restart_ice_white.svg b/app/resources/images/icon_restart_ice_white.svg index 2190d8c..6be67f1 100644 --- a/app/resources/images/icon_restart_ice_white.svg +++ b/app/resources/images/icon_restart_ice_white.svg @@ -1,4 +1,4 @@ - + diff --git a/app/resources/images/icon_settings_black.svg b/app/resources/images/icon_settings_black.svg new file mode 100644 index 0000000..131f14f --- /dev/null +++ b/app/resources/images/icon_settings_black.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/resources/images/icon_settings_white.svg b/app/resources/images/icon_settings_white.svg new file mode 100644 index 0000000..0c17069 --- /dev/null +++ b/app/resources/images/icon_settings_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/stylus/components/PeerView.styl b/app/stylus/components/PeerView.styl index f5dbdc0..1230284 100644 --- a/app/stylus/components/PeerView.styl +++ b/app/stylus/components/PeerView.styl @@ -170,7 +170,7 @@ flex: 100 100 auto; height: 100%; width: 100%; - object-fit: contain; + object-fit: cover; user-select: none; transition-property: opacity; transition-duration: .15s; diff --git a/app/stylus/components/Room.styl b/app/stylus/components/Room.styl index 6685afd..ec3e43c 100644 --- a/app/stylus/components/Room.styl +++ b/app/stylus/components/Room.styl @@ -216,19 +216,19 @@ opacity: 0.5; } - &.audio-only { - background-image: url('/resources/images/icon_audio_only_white.svg'); - - &.on { - background-image: url('/resources/images/icon_audio_only_black.svg'); + &.login { + &.off { + background-image: url('/resources/images/icon_login_white.svg'); } } - &.restart-ice { - background-image: url('/resources/images/icon_restart_ice_white.svg'); + &.settings { + &.off { + background-image: url('/resources/images/icon_settings_white.svg'); + } &.on { - background-image: url('/resources/images/icon_restart_ice__black.svg'); + background-image: url('/resources/images/icon_settings_black.svg'); } } @@ -265,6 +265,101 @@ } } +.Dropdown-root { + position: relative; + padding: 0.3vmin; +} + +.Dropdown-control { + position: relative; + overflow: hidden; + background-color: white; + border: 1px solid #ccc; + border-radius: 2px; + box-sizing: border-box; + color: #333; + cursor: default; + outline: none; + padding: 8px 52px 8px 10px; + transition: all 200ms ease; +} + +.Dropdown-control:hover { + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06); +} + +.Dropdown-arrow { + border-color: #999 transparent transparent; + border-style: solid; + border-width: 5px 5px 0; + content: ' '; + display: block; + height: 0; + margin-top: -ceil(2.5); + position: absolute; + right: 10px; + top: 14px; + width: 0 +} + +.is-open .Dropdown-arrow { + border-color: transparent transparent #999; + border-width: 0 5px 5px; +} + +.Dropdown-menu { + background-color: white; + border: 1px solid #ccc; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06); + box-sizing: border-box; + margin-top: -1px; + max-height: 200px; + overflow-y: auto; + position: absolute; + top: 100%; + width: 100%; + z-index: 1000; + -webkit-overflow-scrolling: touch; +} + +.Dropdown-menu .Dropdown-group > .Dropdown-title { + padding: 8px 10px; + color: rgba(51, 51, 51, 1.2); + font-weight: bold; + text-transform: capitalize; +} + +.Dropdown-option { + box-sizing: border-box; + color: rgba(51, 51, 51, 0.8); + cursor: pointer; + display: block; + padding: 8px 10px; +} + +.Dropdown-option:last-child { + border-bottom-right-radius: 2px; + border-bottom-left-radius: 2px; +} + +.Dropdown-option:hover { + background-color: #f2f9fc; + color: #333; +} + +.Dropdown-option.is-selected { + background-color: #f2f9fc; + color: #333; +} + +.Dropdown-noresults { + box-sizing: border-box; + color: #ccc; + cursor: default; + display: block; + padding: 8px 10px; +} + @keyframes Room-info-state-connecting { 50% { background-color: rgba(orange, 0.75); } } diff --git a/app/stylus/components/Settings.styl b/app/stylus/components/Settings.styl new file mode 100644 index 0000000..a58d04a --- /dev/null +++ b/app/stylus/components/Settings.styl @@ -0,0 +1,53 @@ +[data-component='Settings'] { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 19999; + background-color: rgba(000, 000, 000, 0.5); + + AppearFadeIn(500ms); + + > .dialog { + position: absolute; + width: 40vmin; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + background-color: #fff; + border-radius: 4px; + box-shadow: 0px 3px 12px 2px rgba(#111, 0.4); + padding: 1vmin; + + > .header { + > span { + font-size: 2vmin; + font-weight: 400; + } + } + + > .settings { + } + + > .footer { + bottom: 0; + right: 0; + left: 0; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-end; + + > .button { + flex: 0 0 auto; + margin: 1vmin; + background-color: rgba(#000, 0.8); + color: #fff; + cursor: pointer; + border-radius: 4px; + padding: 0.5vmin; + } + } + } +} diff --git a/app/stylus/index.styl b/app/stylus/index.styl index f73898b..9f9fec5 100644 --- a/app/stylus/index.styl +++ b/app/stylus/index.styl @@ -42,13 +42,14 @@ body { @import './components/PeerView'; @import './components/Notifications'; @import './components/Chat'; + @import './components/Settings'; } // Hack to detect in JS the current media query #multiparty-meeting-media-query-detector { - position: relative; + position: absolute; z-index: -1000; - bottom: 1px; + bottom: 0; left: 0; height: 1px; width: 1px;