Merge pull request #24 from torjusti/feat/app-fullscreen

Fine-tune fullscreen functionality
master
Stefan Otto 2018-07-19 10:36:12 +02:00 committed by GitHub
commit b25f2eb012
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 308 additions and 234 deletions

View File

@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Spinner from 'react-spinner';
import fscreen from 'fscreen';
export default class FullView extends React.Component
{
@ -51,27 +50,8 @@ export default class FullView extends React.Component
const { videoTrack } = this.props;
this._setTracks(videoTrack);
if (fscreen.fullscreenEnabled)
{
fscreen.addEventListener('fullscreenchange', this.handleExitFullscreen, false);
fscreen.requestFullscreen(this.video.current);
}
}
componentWillUnmount()
{
fscreen.removeEventListener('fullscreenchange', this.handleExitFullscreen);
}
handleExitFullscreen = () =>
{
if (!fscreen.fullscreenElement)
{
this.props.toggleFullscreen();
}
};
componentDidUpdate()
{
const { videoTrack } = this.props;

View File

@ -16,6 +16,7 @@ import ToolArea from './ToolArea/ToolArea';
import FullScreenView from './FullScreenView';
import Draggable from 'react-draggable';
import { idle } from '../utils';
import Sidebar from './Sidebar';
// Hide toolbars after 10 seconds of inactivity.
const TIMEOUT = 10 * 1000;
@ -58,42 +59,11 @@ class Room extends React.Component
{
const {
room,
me,
toolAreaOpen,
amActiveSpeaker,
screenProducer,
onRoomLinkCopy,
onLogin,
onShareScreen,
onUnShareScreen,
onNeedExtension,
onLeaveMeeting
onRoomLinkCopy
} = this.props;
let screenState;
let screenTip;
if (me.needExtension)
{
screenState = 'need-extension';
screenTip = 'Install screen sharing extension';
}
else if (!me.canShareScreen)
{
screenState = 'unsupported';
screenTip = 'Screen sharing not supported';
}
else if (screenProducer)
{
screenState = 'on';
screenTip = 'Stop screen sharing';
}
else
{
screenState = 'off';
screenTip = 'Start screen sharing';
}
return (
<Appear duration={300}>
<div data-component='Room'>
@ -105,6 +75,7 @@ class Room extends React.Component
}}
>
<Notifications />
<ToolAreaButton />
{room.advancedMode ?
@ -166,60 +137,7 @@ class Room extends React.Component
</div>
</Draggable>
<div className={classnames('sidebar room-controls', {
'visible' : this.props.room.toolbarsVisible
})}
>
<div
className={classnames('button', 'screen', screenState)}
data-tip={screenTip}
data-type='dark'
onClick={() =>
{
switch (screenState)
{
case 'on':
{
onUnShareScreen();
break;
}
case 'off':
{
onShareScreen();
break;
}
case 'need-extension':
{
onNeedExtension();
break;
}
default:
{
break;
}
}
}}
/>
{me.loginEnabled ?
<div
className={classnames('button', 'login', 'off', {
disabled : me.loginInProgress
})}
data-tip='Login'
data-type='dark'
onClick={() => onLogin()}
/>
:null
}
<div
className={classnames('button', 'leave-meeting')}
data-tip='Leave meeting'
data-type='dark'
onClick={() => onLeaveMeeting()}
/>
</div>
<Sidebar />
<ReactTooltip
effect='solid'
@ -254,11 +172,6 @@ Room.propTypes =
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
};
@ -287,26 +200,7 @@ const mapDispatchToProps = (dispatch) =>
text : 'Room link copied to the clipboard'
}));
},
onLeaveMeeting : () =>
{
dispatch(requestActions.leaveRoom());
},
onShareScreen : () =>
{
dispatch(requestActions.enableScreenSharing());
},
onUnShareScreen : () =>
{
dispatch(requestActions.disableScreenSharing());
},
onNeedExtension : () =>
{
dispatch(requestActions.installExtension());
},
onLogin : () =>
{
dispatch(requestActions.userLogin());
},
setToolbarsVisible : (visible) =>
{
dispatch(stateActions.setToolbarsVisible(visible));

View File

@ -0,0 +1,183 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import * as appPropTypes from './appPropTypes';
import * as requestActions from '../redux/requestActions';
import fscreen from 'fscreen';
class Sidebar extends Component
{
state = {
fullscreen : false
};
handleToggleFullscreen = () =>
{
if (fscreen.fullscreenElement)
{
fscreen.exitFullscreen();
}
else
{
fscreen.requestFullscreen(document.documentElement);
}
};
handleFullscreenChange = () =>
{
this.setState({
fullscreen : fscreen.fullscreenElement !== null
});
};
componentDidMount()
{
if (fscreen.fullscreenEnabled)
{
fscreen.addEventListener('fullscreenchange', this.handleFullscreenChange);
}
}
componentWillUnmount()
{
if (fscreen.fullscreenEnabled)
{
fscreen.removeEventListener('fullscreenchange', this.handleFullscreenChange);
}
}
render()
{
const {
toolbarsVisible, me, screenProducer, onLogin, onShareScreen,
onUnShareScreen, onNeedExtension, onLeaveMeeting
} = this.props;
let screenState;
let screenTip;
if (me.needExtension)
{
screenState = 'need-extension';
screenTip = 'Install screen sharing extension';
}
else if (!me.canShareScreen)
{
screenState = 'unsupported';
screenTip = 'Screen sharing not supported';
}
else if (screenProducer)
{
screenState = 'on';
screenTip = 'Stop screen sharing';
}
else
{
screenState = 'off';
screenTip = 'Start screen sharing';
}
return (
<div
className={classnames('sidebar room-controls', {
'visible' : toolbarsVisible
})}
data-component='Sidebar'
>
{fscreen.fullscreenEnabled && (
<div
className={classnames('button', 'fullscreen', {
on : this.state.fullscreen
})}
onClick={this.handleToggleFullscreen}
data-tip='Fullscreen'
data-type='dark'
/>
)}
<div
className={classnames('button', 'screen', screenState)}
data-tip={screenTip}
data-type='dark'
onClick={() =>
{
switch (screenState)
{
case 'on':
{
onUnShareScreen();
break;
}
case 'off':
{
onShareScreen();
break;
}
case 'need-extension':
{
onNeedExtension();
break;
}
default:
{
break;
}
}
}}
/>
{me.loginEnabled ?
<div
className={classnames('button', 'login', 'off', {
disabled : me.loginInProgress
})}
data-tip='Login'
data-type='dark'
onClick={() => onLogin()}
/>
:null
}
<div
className={classnames('button', 'leave-meeting')}
data-tip='Leave meeting'
data-type='dark'
onClick={() => onLeaveMeeting()}
/>
</div>
);
}
}
Sidebar.propTypes = {
toolbarsVisible : PropTypes.bool.isRequired,
me : appPropTypes.Me.isRequired,
onShareScreen : PropTypes.func.isRequired,
onUnShareScreen : PropTypes.func.isRequired,
onNeedExtension : PropTypes.func.isRequired,
onLeaveMeeting : PropTypes.func.isRequired,
onLogin : PropTypes.func.isRequired,
screenProducer : appPropTypes.Producer
};
const mapStateToProps = (state) =>
({
toolbarsVisible : state.room.toolbarsVisible,
screenProducer : Object.values(state.producers)
.find((producer) => producer.source === 'screen'),
me : state.me
});
const mapDispatchToProps = {
onLeaveMeeting : requestActions.leaveRoom,
onShareScreen : requestActions.enableScreenSharing,
onUnShareScreen : requestActions.disableScreenSharing,
onNeedExtension : requestActions.installExtension,
onLogin : requestActions.userLogin
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Sidebar);

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFF">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
</svg>

After

Width:  |  Height:  |  Size: 239 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFF">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
</svg>

After

Width:  |  Height:  |  Size: 242 B

View File

@ -6,10 +6,10 @@
display: flex;
flex-direction: column;
overflow: hidden;
background-color: rgba(#2a4b58, 0.9);
background-image: url('/resources/images/buddy.svg');
background-position: bottom;
background-size: auto 85%;
background-image: url('/resources/images/background.svg');
background-attachment: fixed;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
> .info {

View File

@ -164,105 +164,6 @@
border: 1px solid rgba(#fff, 0.25);
}
}
> .sidebar {
position: fixed;
z-index: 101;
top: calc(50% - 60px);
height: 120px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
+desktop() {
left: 20px;
width: 36px;
}
+mobile() {
left: 10px;
width: 32px;
}
> .button {
flex: 0 0 auto;
margin: 4px 0;
background-position: center;
background-size: 75%;
background-repeat: no-repeat;
background-color: rgba(#fff, 0.3);
cursor: pointer;
transition-property: opacity, background-color;
transition-duration: 0.15s;
border-radius: 100%;
+desktop() {
height: 36px;
width: 36px;
}
+mobile() {
height: 32px;
width: 32px;
}
&.on {
background-color: rgba(#fff, 0.7);
}
&.disabled {
pointer-events: none;
opacity: 0.5;
}
&.login {
&.off {
background-image: url('/resources/images/icon_login_white.svg');
}
}
&.settings {
&.off {
background-image: url('/resources/images/icon_settings_white.svg');
}
&.on {
background-image: url('/resources/images/icon_settings_black.svg');
}
}
&.screen {
&.on {
background-image: url('/resources/images/no-share-screen-black.svg');
}
&.off {
background-image: url('/resources/images/share-screen-white.svg');
}
&.unsupported {
background-image: url('/resources/images/no-share-screen-white.svg');
background-color: rgba(#d42241, 0.7);
}
&.need-extension {
background-image: url('/resources/images/share-screen-extension.svg');
}
}
&.raise-hand {
background-image: url('/resources/images/icon-hand-white.svg');
&.on {
background-image: url('/resources/images/icon-hand-black.svg');
}
}
&.leave-meeting {
background-image: url('/resources/images/leave-meeting.svg');
}
}
}
}
> .toolarea-wrapper {

View File

@ -0,0 +1,107 @@
[data-component='Sidebar'] {
position: fixed;
z-index: 101;
top: calc(50% - 60px);
height: 120px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
+desktop() {
left: 20px;
width: 36px;
}
+mobile() {
left: 10px;
width: 32px;
}
> .button {
flex: 0 0 auto;
margin: 4px 0;
background-position: center;
background-size: 75%;
background-repeat: no-repeat;
background-color: rgba(#fff, 0.3);
cursor: pointer;
transition-property: opacity, background-color;
transition-duration: 0.15s;
border-radius: 100%;
+desktop() {
height: 36px;
width: 36px;
}
+mobile() {
height: 32px;
width: 32px;
}
&.on {
background-color: rgba(#fff, 0.7);
}
&.disabled {
pointer-events: none;
opacity: 0.5;
}
&.login {
&.off {
background-image: url('/resources/images/icon_login_white.svg');
}
}
&.settings {
&.off {
background-image: url('/resources/images/icon_settings_white.svg');
}
&.on {
background-image: url('/resources/images/icon_settings_black.svg');
}
}
&.fullscreen {
background-image: url('/resources/images/icon_fullscreen_white.svg');
&.on {
background-image: url('/resources/images/icon_fullscreen_exit_white.svg');
}
}
&.screen {
&.on {
background-image: url('/resources/images/no-share-screen-black.svg');
}
&.off {
background-image: url('/resources/images/share-screen-white.svg');
}
&.unsupported {
background-image: url('/resources/images/no-share-screen-white.svg');
background-color: rgba(#d42241, 0.7);
}
&.need-extension {
background-image: url('/resources/images/share-screen-extension.svg');
}
}
&.raise-hand {
background-image: url('/resources/images/icon-hand-white.svg');
&.on {
background-image: url('/resources/images/icon-hand-black.svg');
}
}
&.leave-meeting {
background-image: url('/resources/images/leave-meeting.svg');
}
}
}

View File

@ -37,6 +37,7 @@ body {
// Components
@import './components/Room';
@import './components/Sidebar';
@import './components/Me';
@import './components/Peers';
@import './components/Peer';