Merge pull request #12 from torjusti/feat/hide-controls

Hide controls after inactivity
master
Stefan Otto 2018-07-16 14:39:07 +02:00 committed by GitHub
commit 51de7bb33d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 3532 additions and 2193 deletions

View File

@ -24,14 +24,14 @@ module.exports =
version: '15'
}
},
parser: 'babel-eslint',
parserOptions:
{
ecmaVersion: 6,
ecmaVersion: 9,
sourceType: 'module',
ecmaFeatures:
{
impliedStrict: true,
experimentalObjectRestSpread: true,
jsx: true
}
},
@ -121,7 +121,6 @@ module.exports =
'no-implicit-globals': 2,
'no-inner-declarations': 2,
'no-invalid-regexp': 2,
'no-invalid-this': 2,
'no-irregular-whitespace': 2,
'no-lonely-if': 2,
'no-mixed-operators': 2,

View File

@ -79,13 +79,7 @@ function bundle(options)
})
.transform('babelify',
{
presets : [ 'es2015', 'react' ],
plugins :
[
'transform-runtime',
'transform-object-assign',
'transform-object-rest-spread'
]
presets : [ 'env', 'react-app' ]
})
.transform(envify(
{
@ -132,21 +126,29 @@ function changeHTML(content)
gulp.task('clean', () => del(OUTPUT_DIR, { force: true }));
const LINTING_FILES = [
'gulpfile.js',
'lib/**/*.js',
'lib/**/*.jsx'
];
gulp.task('lint', () =>
{
const src =
[
'gulpfile.js',
'lib/**/*.js',
'lib/**/*.jsx'
];
return gulp.src(src)
return gulp.src(LINTING_FILES)
.pipe(plumber())
.pipe(eslint())
.pipe(eslint.format());
});
gulp.task('lint-fix', function()
{
return gulp.src(LINTING_FILES)
.pipe(plumber())
.pipe(eslint({ fix: true }))
.pipe(eslint.format())
.pipe(gulp.dest((file) => file.base));
});
gulp.task('css', () =>
{
return gulp.src('stylus/index.styl')

View File

@ -11,6 +11,24 @@ import ScreenView from './ScreenView';
class Me extends React.Component
{
state = {
controlsVisible : false
};
handleMouseOver = () =>
{
this.setState({
controlsVisible : true
});
};
handleMouseOut = () =>
{
this.setState({
controlsVisible : false
});
};
constructor(props)
{
super(props);
@ -85,10 +103,15 @@ class Me extends React.Component
data-tip={tip}
data-tip-disable={!tip}
data-type='dark'
onMouseOver={this.handleMouseOver}
onMouseOut={this.handleMouseOut}
>
<div className={classnames('view-container', 'webcam')}>
{connected ?
<div className='controls'>
<div className={classnames('controls', {
visible : this.state.controlsVisible
})}
>
<div
className={classnames('button', 'mic', micState, {
disabled : me.audioInProgress

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from 'classnames';
@ -8,155 +8,182 @@ import * as stateActions from '../redux/stateActions';
import PeerView from './PeerView';
import ScreenView from './ScreenView';
const Peer = (props) =>
class Peer extends Component
{
const {
advancedMode,
peer,
micConsumer,
webcamConsumer,
screenConsumer,
onMuteMic,
onUnmuteMic,
onDisableWebcam,
onEnableWebcam,
onDisableScreen,
onEnableScreen,
toggleConsumerFullscreen,
style
} = props;
state = {
controlsVisible : false
};
const micEnabled = (
Boolean(micConsumer) &&
!micConsumer.locallyPaused &&
!micConsumer.remotelyPaused
);
handleMouseOver = () =>
{
this.setState({
controlsVisible : true
});
};
const videoVisible = (
Boolean(webcamConsumer) &&
!webcamConsumer.locallyPaused &&
!webcamConsumer.remotelyPaused
);
handleMouseOut = () =>
{
this.setState({
controlsVisible : false
});
};
const screenVisible = (
Boolean(screenConsumer) &&
!screenConsumer.locallyPaused &&
!screenConsumer.remotelyPaused
);
let videoProfile;
if (webcamConsumer)
videoProfile = webcamConsumer.profile;
let screenProfile;
if (screenConsumer)
screenProfile = screenConsumer.profile;
return (
<div
data-component='Peer'
className={classnames({
screen : screenConsumer
})}
>
{videoVisible && !webcamConsumer.supported ?
<div className='incompatible-video'>
<p>incompatible video</p>
</div>
:null
}
<div className={classnames('view-container', 'webcam')} style={style}>
<div className='controls'>
render()
{
const {
advancedMode,
peer,
micConsumer,
webcamConsumer,
screenConsumer,
onMuteMic,
onUnmuteMic,
onDisableWebcam,
onEnableWebcam,
onDisableScreen,
onEnableScreen,
toggleConsumerFullscreen,
style
} = this.props;
const micEnabled = (
Boolean(micConsumer) &&
!micConsumer.locallyPaused &&
!micConsumer.remotelyPaused
);
const videoVisible = (
Boolean(webcamConsumer) &&
!webcamConsumer.locallyPaused &&
!webcamConsumer.remotelyPaused
);
const screenVisible = (
Boolean(screenConsumer) &&
!screenConsumer.locallyPaused &&
!screenConsumer.remotelyPaused
);
let videoProfile;
if (webcamConsumer)
videoProfile = webcamConsumer.profile;
let screenProfile;
if (screenConsumer)
screenProfile = screenConsumer.profile;
return (
<div
data-component='Peer'
className={classnames({
screen : screenConsumer
})}
onMouseOver={this.handleMouseOver}
onMouseOut={this.handleMouseOut}
>
{videoVisible && !webcamConsumer.supported ?
<div className='incompatible-video'>
<p>incompatible video</p>
</div>
:null
}
<div className={classnames('view-container', 'webcam')} style={style}>
<div
className={classnames('button', 'mic', {
on : micEnabled,
off : !micEnabled,
disabled : peer.peerAudioInProgress
className={classnames('controls', {
visible : this.state.controlsVisible
})}
onClick={(e) =>
{
e.stopPropagation();
micEnabled ? onMuteMic(peer.name) : onUnmuteMic(peer.name);
}}
/>
<div
className={classnames('button', 'webcam', {
on : videoVisible,
off : !videoVisible,
disabled : peer.peerVideoInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
videoVisible ?
onDisableWebcam(peer.name) : onEnableWebcam(peer.name);
}}
/>
<div
className={classnames('button', 'fullscreen')}
onClick={(e) =>
{
e.stopPropagation();
toggleConsumerFullscreen(webcamConsumer);
}}
/>
</div>
<PeerView
advancedMode={advancedMode}
peer={peer}
audioTrack={micConsumer ? micConsumer.track : null}
videoTrack={webcamConsumer ? webcamConsumer.track : null}
videoVisible={videoVisible}
videoProfile={videoProfile}
audioCodec={micConsumer ? micConsumer.codec : null}
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
/>
</div>
{screenConsumer ?
<div className={classnames('view-container', 'screen')} style={style}>
<div className='controls'>
>
<div
className={classnames('button', 'screen', {
on : screenVisible,
off : !screenVisible,
disabled : peer.peerScreenInProgress
className={classnames('button', 'mic', {
on : micEnabled,
off : !micEnabled,
disabled : peer.peerAudioInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
screenVisible ?
onDisableScreen(peer.name) : onEnableScreen(peer.name);
micEnabled ? onMuteMic(peer.name) : onUnmuteMic(peer.name);
}}
/>
<div
className={classnames('button', 'webcam', {
on : videoVisible,
off : !videoVisible,
disabled : peer.peerVideoInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
videoVisible ?
onDisableWebcam(peer.name) : onEnableWebcam(peer.name);
}}
/>
<div
className={classnames('button', 'fullscreen')}
onClick={(e) =>
{
e.stopPropagation();
toggleConsumerFullscreen(screenConsumer);
toggleConsumerFullscreen(webcamConsumer);
}}
/>
</div>
<ScreenView
<PeerView
advancedMode={advancedMode}
screenTrack={screenConsumer ? screenConsumer.track : null}
screenVisible={screenVisible}
screenProfile={screenProfile}
screenCodec={screenConsumer ? screenConsumer.codec : null}
peer={peer}
audioTrack={micConsumer ? micConsumer.track : null}
videoTrack={webcamConsumer ? webcamConsumer.track : null}
videoVisible={videoVisible}
videoProfile={videoProfile}
audioCodec={micConsumer ? micConsumer.codec : null}
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
/>
</div>
:null
}
</div>
);
};
{screenConsumer ?
<div className={classnames('view-container', 'screen')} style={style}>
<div className='controls'>
<div
className={classnames('button', 'screen', {
on : screenVisible,
off : !screenVisible,
disabled : peer.peerScreenInProgress
})}
onClick={(e) =>
{
e.stopPropagation();
screenVisible ?
onDisableScreen(peer.name) : onEnableScreen(peer.name);
}}
/>
<div
className={classnames('button', 'fullscreen')}
onClick={(e) =>
{
e.stopPropagation();
toggleConsumerFullscreen(screenConsumer);
}}
/>
</div>
<ScreenView
advancedMode={advancedMode}
screenTrack={screenConsumer ? screenConsumer.track : null}
screenVisible={screenVisible}
screenProfile={screenProfile}
screenCodec={screenConsumer ? screenConsumer.codec : null}
/>
</div>
:null
}
</div>
);
}
}
Peer.propTypes =
{

View File

@ -6,6 +6,7 @@ import classnames from 'classnames';
import ClipboardButton from 'react-clipboard.js';
import * as appPropTypes from './appPropTypes';
import * as requestActions from '../redux/requestActions';
import * as stateActions from '../redux/stateActions';
import { Appear } from './transitions';
import Me from './Me';
import Peers from './Peers';
@ -14,9 +15,45 @@ import ToolAreaButton from './ToolArea/ToolAreaButton';
import ToolArea from './ToolArea/ToolArea';
import FullScreenView from './FullScreenView';
import Draggable from 'react-draggable';
import { idle } from '../utils';
// Hide toolbars after 10 seconds of inactivity.
const TIMEOUT = 10 * 1000;
class Room extends React.Component
{
/**
* Hides the different toolbars on the page after a
* given amount of time has passed since the
* last time the cursor was moved.
*/
waitForHide = idle(() =>
{
this.props.setToolbarsVisible(false);
}, TIMEOUT);
handleMouseMove = () =>
{
// If the toolbars were hidden, show them again when
// the user moves their cursor.
if (!this.props.room.toolbarsVisible)
{
this.props.setToolbarsVisible(true);
}
this.waitForHide();
}
componentDidMount()
{
window.addEventListener('mousemove', this.handleMouseMove);
}
componentWillUnmount()
{
window.removeEventListener('mousemove', this.handleMouseMove);
}
render()
{
const {
@ -77,8 +114,12 @@ class Room extends React.Component
</div>
:null
}
<div className='room-link-wrapper'>
<div
className={classnames('room-link-wrapper room-controls', {
'visible' : this.props.room.toolbarsVisible
})}
>
<div className='room-link'>
<ClipboardButton
component='a'
@ -125,7 +166,10 @@ class Room extends React.Component
</div>
</Draggable>
<div className='sidebar'>
<div className={classnames('sidebar room-controls', {
'visible' : this.props.room.toolbarsVisible
})}
>
<div
className={classnames('button', 'screen', screenState)}
data-tip={screenTip}
@ -204,17 +248,18 @@ class Room extends React.Component
Room.propTypes =
{
room : appPropTypes.Room.isRequired,
me : appPropTypes.Me.isRequired,
amActiveSpeaker : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
screenProducer : appPropTypes.Producer,
onRoomLinkCopy : PropTypes.func.isRequired,
onShareScreen : PropTypes.func.isRequired,
onUnShareScreen : PropTypes.func.isRequired,
onNeedExtension : PropTypes.func.isRequired,
onLeaveMeeting : PropTypes.func.isRequired,
onLogin : PropTypes.func.isRequired
room : appPropTypes.Room.isRequired,
me : appPropTypes.Me.isRequired,
amActiveSpeaker : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
screenProducer : appPropTypes.Producer,
onRoomLinkCopy : PropTypes.func.isRequired,
onShareScreen : PropTypes.func.isRequired,
onUnShareScreen : PropTypes.func.isRequired,
onNeedExtension : PropTypes.func.isRequired,
onLeaveMeeting : PropTypes.func.isRequired,
onLogin : PropTypes.func.isRequired,
setToolbarsVisible : PropTypes.func.isRequired
};
const mapStateToProps = (state) =>
@ -261,6 +306,10 @@ const mapDispatchToProps = (dispatch) =>
onLogin : () =>
{
dispatch(requestActions.userLogin());
},
setToolbarsVisible : (visible) =>
{
dispatch(stateActions.setToolbarsVisible(visible));
}
};
};

View File

@ -17,8 +17,9 @@ class ToolAreaButton extends React.Component
return (
<div data-component='ToolAreaButton'>
<div
className={classnames('button', 'toolarea-button', {
on : toolAreaOpen
className={classnames('button toolarea-button room-controls', {
on : toolAreaOpen,
visible : this.props.visible
})}
data-tip='Toggle tool area'
data-type='dark'
@ -40,13 +41,15 @@ ToolAreaButton.propTypes =
{
toolAreaOpen : PropTypes.bool.isRequired,
toggleToolArea : PropTypes.func.isRequired,
unread : PropTypes.bool.isRequired
unread : PropTypes.bool.isRequired,
visible : PropTypes.bool.isRequired
};
const mapStateToProps = (state) =>
{
return {
toolAreaOpen : state.toolarea.toolAreaOpen,
visible : state.room.toolbarsVisible,
unread : state.toolarea.unread
};
};

View File

@ -5,7 +5,8 @@ const initialState =
activeSpeakerName : null,
showSettings : false,
advancedMode : false,
fullScreenConsumer : null // ConsumerID
fullScreenConsumer : null, // ConsumerID
toolbarsVisible : true
};
const room = (state = initialState, action) =>
@ -58,6 +59,12 @@ const room = (state = initialState, action) =>
return { ...state, fullScreenConsumer: currentConsumer ? null : consumerId };
}
case 'SET_TOOLBARS_VISIBLE':
{
const { toolbarsVisible } = action.payload;
return { ...state, toolbarsVisible };
}
default:
return state;
}

View File

@ -369,6 +369,11 @@ export const toggleConsumerFullscreen = (consumerId) =>
};
};
export const setToolbarsVisible = (toolbarsVisible) => ({
type : 'SET_TOOLBARS_VISIBLE',
payload : { toolbarsVisible }
});
export const increaseBadge = () =>
{
return {

View File

@ -43,3 +43,23 @@ export function getBrowserType()
return 'N/A';
}
/**
* Create a function which will call the callback function
* after the given amount of milliseconds has passed since
* the last time the callback function was called.
*/
export const idle = (callback, delay) =>
{
let handle;
return () =>
{
if (handle)
{
clearTimeout(handle);
}
handle = setTimeout(callback, delay);
};
};

5150
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,65 +8,65 @@
"main": "lib/index.jsx",
"dependencies": {
"babel-runtime": "^6.26.0",
"classnames": "^2.2.5",
"classnames": "^2.2.6",
"debug": "^3.1.0",
"domready": "^1.0.8",
"hark": "^1.1.6",
"hark": "^1.2.0",
"js-cookie": "^2.2.0",
"marked": "^0.3.17",
"marked": "^0.4.0",
"mediasoup-client": "^2.1.1",
"prop-types": "^15.6.0",
"protoo-client": "^2.0.7",
"prop-types": "^15.6.2",
"protoo-client": "^3.0.0",
"random-string": "^0.2.0",
"react": "^16.2.0",
"react-clipboard.js": "^1.1.3",
"react-dom": "^16.2.0",
"react": "^16.4.1",
"react-clipboard.js": "^1.1.4",
"react-dom": "^16.4.1",
"react-draggable": "^3.0.5",
"react-dropdown": "^1.5.0",
"react-redux": "^5.0.6",
"react-redux": "^5.0.7",
"react-spinner": "^0.2.7",
"react-tooltip": "^3.4.0",
"react-transition-group": "^2.2.1",
"redux": "^3.7.2",
"react-tooltip": "^3.6.1",
"react-transition-group": "^2.4.0",
"redux": "^4.0.0",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0",
"redux-thunk": "^2.3.0",
"riek": "^1.1.0",
"url-parse": "^1.2.0"
"url-parse": "^1.4.1"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.6",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-react-app": "^3.1.2",
"babel-preset-stage-0": "^6.24.1",
"babelify": "^8.0.0",
"browser-sync": "^2.23.6",
"browserify": "^16.1.0",
"browser-sync": "^2.24.5",
"browserify": "^16.2.2",
"del": "^3.0.0",
"envify": "^4.1.0",
"eslint": "^4.17.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-react": "^7.6.1",
"eslint": "^5.1.0",
"eslint-plugin-import": "^2.13.0",
"eslint-plugin-react": "^7.10.0",
"gulp": "^4.0.0",
"gulp-css-base64": "^1.3.4",
"gulp-eslint": "^4.0.2",
"gulp-change": "^1.0.0",
"gulp-header": "^2.0.1",
"gulp-css-base64": "^1.3.4",
"gulp-eslint": "^5.0.0",
"gulp-header": "^2.0.5",
"gulp-if": "^2.0.2",
"gulp-plumber": "^1.2.0",
"gulp-rename": "^1.2.2",
"gulp-rename": "^1.3.0",
"gulp-stylus": "^2.7.0",
"gulp-touch-cmd": "0.0.1",
"gulp-uglify": "^3.0.0",
"gulp-util": "^3.0.8",
"lodash": "^4.17.10",
"mkdirp": "^0.5.1",
"ncp": "^2.0.0",
"nib": "^1.1.2",
"supports-color": "^5.2.0",
"supports-color": "^5.4.0",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"watchify": "^3.10.0"
"watchify": "^3.11.0"
}
}

View File

@ -27,7 +27,16 @@
justify-content: flex-start;
align-items: center;
padding: 0.4vmin;
visibility: hidden;
opacity: 0;
animation: fade-out 0.3s;
&.visible {
visibility: visible;
opacity: 1;
animation: fade-in 0.3s;
}
> .button {
flex: 0 0 auto;
margin: 0.2vmin;

View File

@ -41,7 +41,16 @@
justify-content: flex-start;
align-items: center;
padding: 0.4vmin;
visibility: hidden;
opacity: 0;
animation: fade-out 0.3s;
&.visible {
visibility: visible;
opacity: 1;
animation: fade-in 0.3s;
}
> .button {
flex: 0 0 auto;
margin: 0.2vmin;

View File

@ -276,6 +276,18 @@
}
}
.room-controls {
visibility: hidden;
animation: fade-out 0.5s;
opacity: 0;
&.visible {
visibility: visible;
opacity: 1;
animation: fade-in 0.5s;
}
}
.Dropdown-root {
position: relative;
padding: 0.3vmin;

View File

@ -5,6 +5,7 @@ global-reset();
@import './mixins';
@import './fonts';
@import './reset';
@import './keyframes';
html {
height: 100%;

View File

@ -0,0 +1,23 @@
@keyframes fade-in {
from {
opacity: 0;
visibility: hidden;
}
to {
opacity: 1;
visibility: visible;
}
}
@keyframes fade-out {
from {
opacity: 1;
visibility: visible;
}
to {
opacity: 0;
visibility: hidden;
}
}