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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
@ -8,8 +8,28 @@ import * as stateActions from '../redux/stateActions';
import PeerView from './PeerView'; import PeerView from './PeerView';
import ScreenView from './ScreenView'; import ScreenView from './ScreenView';
const Peer = (props) => class Peer extends Component
{ {
state = {
controlsVisible : false
};
handleMouseOver = () =>
{
this.setState({
controlsVisible : true
});
};
handleMouseOut = () =>
{
this.setState({
controlsVisible : false
});
};
render()
{
const { const {
advancedMode, advancedMode,
peer, peer,
@ -24,7 +44,7 @@ const Peer = (props) =>
onEnableScreen, onEnableScreen,
toggleConsumerFullscreen, toggleConsumerFullscreen,
style style
} = props; } = this.props;
const micEnabled = ( const micEnabled = (
Boolean(micConsumer) && Boolean(micConsumer) &&
@ -60,6 +80,8 @@ const Peer = (props) =>
className={classnames({ className={classnames({
screen : screenConsumer screen : screenConsumer
})} })}
onMouseOver={this.handleMouseOver}
onMouseOut={this.handleMouseOut}
> >
{videoVisible && !webcamConsumer.supported ? {videoVisible && !webcamConsumer.supported ?
<div className='incompatible-video'> <div className='incompatible-video'>
@ -69,7 +91,11 @@ const Peer = (props) =>
} }
<div className={classnames('view-container', 'webcam')} style={style}> <div className={classnames('view-container', 'webcam')} style={style}>
<div className='controls'> <div
className={classnames('controls', {
visible : this.state.controlsVisible
})}
>
<div <div
className={classnames('button', 'mic', { className={classnames('button', 'mic', {
on : micEnabled, on : micEnabled,
@ -156,7 +182,8 @@ const Peer = (props) =>
} }
</div> </div>
); );
}; }
}
Peer.propTypes = Peer.propTypes =
{ {

View File

@ -6,6 +6,7 @@ import classnames from 'classnames';
import ClipboardButton from 'react-clipboard.js'; import ClipboardButton from 'react-clipboard.js';
import * as appPropTypes from './appPropTypes'; import * as appPropTypes from './appPropTypes';
import * as requestActions from '../redux/requestActions'; import * as requestActions from '../redux/requestActions';
import * as stateActions from '../redux/stateActions';
import { Appear } from './transitions'; import { Appear } from './transitions';
import Me from './Me'; import Me from './Me';
import Peers from './Peers'; import Peers from './Peers';
@ -14,9 +15,45 @@ import ToolAreaButton from './ToolArea/ToolAreaButton';
import ToolArea from './ToolArea/ToolArea'; import ToolArea from './ToolArea/ToolArea';
import FullScreenView from './FullScreenView'; import FullScreenView from './FullScreenView';
import Draggable from 'react-draggable'; 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 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() render()
{ {
const { const {
@ -78,7 +115,11 @@ class Room extends React.Component
:null :null
} }
<div className='room-link-wrapper'> <div
className={classnames('room-link-wrapper room-controls', {
'visible' : this.props.room.toolbarsVisible
})}
>
<div className='room-link'> <div className='room-link'>
<ClipboardButton <ClipboardButton
component='a' component='a'
@ -125,7 +166,10 @@ class Room extends React.Component
</div> </div>
</Draggable> </Draggable>
<div className='sidebar'> <div className={classnames('sidebar room-controls', {
'visible' : this.props.room.toolbarsVisible
})}
>
<div <div
className={classnames('button', 'screen', screenState)} className={classnames('button', 'screen', screenState)}
data-tip={screenTip} data-tip={screenTip}
@ -214,7 +258,8 @@ Room.propTypes =
onUnShareScreen : PropTypes.func.isRequired, onUnShareScreen : PropTypes.func.isRequired,
onNeedExtension : PropTypes.func.isRequired, onNeedExtension : PropTypes.func.isRequired,
onLeaveMeeting : PropTypes.func.isRequired, onLeaveMeeting : PropTypes.func.isRequired,
onLogin : PropTypes.func.isRequired onLogin : PropTypes.func.isRequired,
setToolbarsVisible : PropTypes.func.isRequired
}; };
const mapStateToProps = (state) => const mapStateToProps = (state) =>
@ -261,6 +306,10 @@ const mapDispatchToProps = (dispatch) =>
onLogin : () => onLogin : () =>
{ {
dispatch(requestActions.userLogin()); dispatch(requestActions.userLogin());
},
setToolbarsVisible : (visible) =>
{
dispatch(stateActions.setToolbarsVisible(visible));
} }
}; };
}; };

View File

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

View File

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

View File

@ -43,3 +43,23 @@ export function getBrowserType()
return 'N/A'; 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);
};
};

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

View File

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

View File

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

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 { .Dropdown-root {
position: relative; position: relative;
padding: 0.3vmin; padding: 0.3vmin;

View File

@ -5,6 +5,7 @@ global-reset();
@import './mixins'; @import './mixins';
@import './fonts'; @import './fonts';
@import './reset'; @import './reset';
@import './keyframes';
html { html {
height: 100%; 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;
}
}