Added support for setting separate window in fullscreen

master
Håvar Aambø Fosstveit 2018-11-15 13:13:39 +01:00
parent 8035fd39bc
commit ca54a49dd3
7 changed files with 430 additions and 53 deletions

View File

@ -0,0 +1,107 @@
const key = {
fullscreenEnabled : 0,
fullscreenElement : 1,
requestFullscreen : 2,
exitFullscreen : 3,
fullscreenchange : 4,
fullscreenerror : 5
};
const webkit = [
'webkitFullscreenEnabled',
'webkitFullscreenElement',
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitfullscreenchange',
'webkitfullscreenerror'
];
const moz = [
'mozFullScreenEnabled',
'mozFullScreenElement',
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozfullscreenchange',
'mozfullscreenerror'
];
const ms = [
'msFullscreenEnabled',
'msFullscreenElement',
'msRequestFullscreen',
'msExitFullscreen',
'MSFullscreenChange',
'MSFullscreenError'
];
export default class FullScreen
{
constructor(document)
{
this.document = document;
this.vendor = (
('fullscreenEnabled' in this.document && Object.keys(key)) ||
(webkit[0] in this.document && webkit) ||
(moz[0] in this.document && moz) ||
(ms[0] in this.document && ms) ||
[]
);
}
requestFullscreen(element)
{
element[this.vendor[key.requestFullscreen]]();
}
requestFullscreenFunction(element)
{
element[this.vendor[key.requestFullscreen]];
}
addEventListener(type, handler)
{
this.document.addEventListener(this.vendor[key[type]], handler);
}
removeEventListener(type, handler)
{
this.document.removeEventListener(this.vendor[key[type]], handler);
}
get exitFullscreen()
{
return this.document[this.vendor[key.exitFullscreen]].bind(this.document);
}
get fullscreenEnabled()
{
return Boolean(this.document[this.vendor[key.fullscreenEnabled]]);
}
set fullscreenEnabled(val) {}
get fullscreenElement()
{
return this.document[this.vendor[key.fullscreenElement]];
}
set fullscreenElement(val) {}
get onfullscreenchange()
{
return this.document[`on${this.vendor[key.fullscreenchange]}`.toLowerCase()];
}
set onfullscreenchange(handler)
{
this.document[`on${this.vendor[key.fullscreenchange]}`.toLowerCase()] = handler;
}
get onfullscreenerror()
{
return this.document[`on${this.vendor[key.fullscreenerror]}`.toLowerCase()];
}
set onfullscreenerror(handler)
{
this.document[`on${this.vendor[key.fullscreenerror]}`.toLowerCase()] = handler;
}
}

View File

@ -40,7 +40,7 @@ const FullScreenView = (props) =>
<div className='controls'>
<div
className={classnames('button', 'fullscreen', 'room-controls', {
className={classnames('button', 'exitfullscreen', 'room-controls', {
visible : toolbarsVisible
})}
onClick={(e) =>

View File

@ -4,46 +4,52 @@ import { connect } from 'react-redux';
import classnames from 'classnames';
import * as appPropTypes from './appPropTypes';
import * as requestActions from '../redux/requestActions';
import fscreen from 'fscreen';
import FullScreen from './FullScreen';
class Sidebar extends Component
{
state = {
constructor(props)
{
super(props);
this.fullscreen = new FullScreen(document);
this.state = {
fullscreen : false
};
}
handleToggleFullscreen = () =>
{
if (fscreen.fullscreenElement)
if (this.fullscreen.fullscreenElement)
{
fscreen.exitFullscreen();
this.fullscreen.exitFullscreen();
}
else
{
fscreen.requestFullscreen(document.documentElement);
this.fullscreen.requestFullscreen(document.documentElement);
}
};
handleFullscreenChange = () =>
{
this.setState({
fullscreen : fscreen.fullscreenElement !== null
fullscreen : this.fullscreen.fullscreenElement !== null
});
};
componentDidMount()
{
if (fscreen.fullscreenEnabled)
if (this.fullscreen.fullscreenEnabled)
{
fscreen.addEventListener('fullscreenchange', this.handleFullscreenChange);
this.fullscreen.addEventListener('fullscreenchange', this.handleFullscreenChange);
}
}
componentWillUnmount()
{
if (fscreen.fullscreenEnabled)
if (this.fullscreen.fullscreenEnabled)
{
fscreen.removeEventListener('fullscreenchange', this.handleFullscreenChange);
this.fullscreen.removeEventListener('fullscreenchange', this.handleFullscreenChange);
}
}
@ -85,7 +91,7 @@ class Sidebar extends Component
})}
data-component='Sidebar'
>
{fscreen.fullscreenEnabled && (
{this.fullscreen.fullscreenEnabled && (
<div
className={classnames('button', 'fullscreen', {
on : this.state.fullscreen

View File

@ -0,0 +1,286 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import FullScreen from '../FullScreen';
import classnames from 'classnames';
class NewWindow extends React.PureComponent
{
static defaultProps =
{
url : '',
name : '',
title : '',
features : { width: '800px', height: '600px' },
onBlock : null,
onUnload : null,
center : 'parent',
copyStyles : true
};
handleToggleFullscreen = () =>
{
if (this.fullscreen.fullscreenElement)
{
this.fullscreen.exitFullscreen();
}
else
{
this.fullscreen.requestFullscreen(this.window.document.documentElement);
}
};
handleFullscreenChange = () =>
{
this.setState({
fullscreen : this.fullscreen.fullscreenElement !== null
});
};
constructor(props)
{
super(props);
this.container = document.createElement('div');
this.window = null;
this.windowCheckerInterval = null;
this.released = false;
this.fullscreen = null;
this.state = {
mounted : false,
fullscreen : false
};
}
render()
{
if (!this.state.mounted)
return null;
return ReactDOM.createPortal([
<div key='newwindow' data-component='FullScreenView'>
<div className='controls'>
{this.fullscreen.fullscreenEnabled && (
<div
className={classnames('button', {
fullscreen : !this.state.fullscreen,
exitFullscreen : this.state.fullscreen
})}
onClick={this.handleToggleFullscreen}
data-tip='Fullscreen'
data-place='right'
data-type='dark'
/>
)}
</div>
{this.props.children}
</div>
], this.container);
}
componentDidMount()
{
this.openChild();
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({ mounted: true });
this.fullscreen = new FullScreen(this.window.document);
if (this.fullscreen.fullscreenEnabled)
{
this.fullscreen.addEventListener('fullscreenchange', this.handleFullscreenChange);
}
}
openChild()
{
const {
url,
title,
name,
features,
onBlock,
center
} = this.props;
if (center === 'parent')
{
features.left =
(window.top.outerWidth / 2) + window.top.screenX - (features.width / 2);
features.top =
(window.top.outerHeight / 2) + window.top.screenY - (features.height / 2);
}
else if (center === 'screen')
{
const screenLeft =
window.screenLeft !== undefined ? window.screenLeft : screen.left;
const screenTop =
window.screenTop !== undefined ? window.screenTop : screen.top;
const width = window.innerWidth
? window.innerWidth
: document.documentElement.clientWidth
? document.documentElement.clientWidth
: screen.width;
const height = window.innerHeight
? window.innerHeight
: document.documentElement.clientHeight
? document.documentElement.clientHeight
: screen.height;
features.left = (width / 2) - (features.width / 2) + screenLeft;
features.top = (height / 2) - (features.height / 2) + screenTop;
}
this.window = window.open(url, name, toWindowFeatures(features));
this.windowCheckerInterval = setInterval(() =>
{
if (!this.window || this.window.closed)
{
this.release();
}
}, 50);
if (this.window)
{
this.window.document.title = title;
this.window.document.body.appendChild(this.container);
if (this.props.copyStyles)
{
setTimeout(() => copyStyles(document, this.window.document), 0);
}
this.window.addEventListener('beforeunload', () => this.release());
}
else if (typeof onBlock === 'function')
{
onBlock(null);
}
}
componentWillUnmount()
{
if (this.window)
{
if (this.fullscreen.fullscreenEnabled)
{
this.fullscreen.removeEventListener('fullscreenchange', this.handleFullscreenChange);
}
this.window.close();
}
}
release()
{
if (this.released)
{
return;
}
this.released = true;
clearInterval(this.windowCheckerInterval);
const { onUnload } = this.props;
if (typeof onUnload === 'function')
{
onUnload(null);
}
}
}
NewWindow.propTypes = {
children : PropTypes.node,
url : PropTypes.string,
name : PropTypes.string,
title : PropTypes.string,
features : PropTypes.object,
onUnload : PropTypes.func,
onBlock : PropTypes.func,
center : PropTypes.oneOf([ 'parent', 'screen' ]),
copyStyles : PropTypes.bool
};
function copyStyles(source, target)
{
Array.from(source.styleSheets).forEach((styleSheet) =>
{
let rules;
try
{
rules = styleSheet.cssRules;
}
catch (err) {}
if (rules)
{
const newStyleEl = source.createElement('style');
Array.from(styleSheet.cssRules).forEach((cssRule) =>
{
const { cssText, type } = cssRule;
let returnText = cssText;
if ([ 3, 5 ].includes(type))
{
returnText = cssText
.split('url(')
.map((line) =>
{
if (line[1] === '/')
{
return `${line.slice(0, 1)}${
window.location.origin
}${line.slice(1)}`;
}
return line;
})
.join('url(');
}
newStyleEl.appendChild(source.createTextNode(returnText));
});
target.head.appendChild(newStyleEl);
}
else if (styleSheet.href)
{
const newLinkEl = source.createElement('link');
newLinkEl.rel = 'stylesheet';
newLinkEl.href = styleSheet.href;
target.head.appendChild(newLinkEl);
}
});
}
function toWindowFeatures(obj)
{
return Object.keys(obj)
.reduce((features, name) =>
{
const value = obj[name];
if (typeof value === 'boolean')
{
features.push(`${name}=${value ? 'yes' : 'no'}`);
}
else
{
features.push(`${name}=${value}`);
}
return features;
}, [])
.join(',');
}
export default NewWindow;

View File

@ -1,8 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import NewWindow from 'react-new-window';
import NewWindow from './NewWindow';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes';
import * as stateActions from '../../redux/stateActions';
import FullView from '../FullView';
@ -12,8 +11,7 @@ const VideoWindow = (props) =>
const {
advancedMode,
consumer,
toggleConsumerWindow,
toolbarsVisible
toggleConsumerWindow
} = props;
if (!consumer)
@ -32,34 +30,12 @@ const VideoWindow = (props) =>
return (
<NewWindow onUnload={toggleConsumerWindow}>
<div data-component='FullScreenView'>
{consumerVisible && !consumer.supported ?
<div className='incompatible-video'>
<p>incompatible video</p>
</div>
:null
}
<div className='controls'>
<div
className={classnames('button', 'fullscreen', 'room-controls', {
visible : toolbarsVisible
})}
onClick={(e) =>
{
e.stopPropagation();
toggleConsumerWindow();
}}
/>
</div>
<FullView
advancedMode={advancedMode}
videoTrack={consumer ? consumer.track : null}
videoVisible={consumerVisible}
videoProfile={consumerProfile}
/>
</div>
</NewWindow>
);
};
@ -68,15 +44,13 @@ VideoWindow.propTypes =
{
advancedMode : PropTypes.bool,
consumer : appPropTypes.Consumer,
toggleConsumerWindow : PropTypes.func.isRequired,
toolbarsVisible : PropTypes.bool
toggleConsumerWindow : PropTypes.func.isRequired
};
const mapStateToProps = (state) =>
{
return {
consumer : state.consumers[state.room.windowConsumer],
toolbarsVisible : state.room.toolbarsVisible
consumer : state.consumers[state.room.windowConsumer]
};
};
@ -85,7 +59,7 @@ const mapDispatchToProps = (dispatch) =>
return {
toggleConsumerWindow : () =>
{
dispatch(stateActions.toggleConsumerWindow(null));
dispatch(stateActions.toggleConsumerWindow());
}
};
};

View File

@ -14,7 +14,6 @@
"domready": "^1.0.8",
"drag-drop": "^4.2.0",
"file-saver": "^1.3.8",
"fscreen": "^1.0.2",
"hark": "^1.2.2",
"js-cookie": "^2.2.0",
"magnet-uri": "^5.2.3",

View File

@ -44,10 +44,15 @@
height: 5vmin;
}
&.fullscreen {
&.exitfullscreen {
background-image: url('/resources/images/icon_fullscreen_exit_black.svg');
background-color: rgba(#fff, 0.7);
}
&.fullscreen {
background-image: url('/resources/images/icon_fullscreen_black.svg');
background-color: rgba(#fff, 0.7);
}
}
}