diff --git a/app/lib/components/FullScreen.js b/app/lib/components/FullScreen.js new file mode 100644 index 0000000..f3a0600 --- /dev/null +++ b/app/lib/components/FullScreen.js @@ -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; + } +} diff --git a/app/lib/components/FullScreenView.jsx b/app/lib/components/FullScreenView.jsx index 0b3d2cb..f01ce7a 100644 --- a/app/lib/components/FullScreenView.jsx +++ b/app/lib/components/FullScreenView.jsx @@ -40,7 +40,7 @@ const FullScreenView = (props) =>
diff --git a/app/lib/components/Sidebar.jsx b/app/lib/components/Sidebar.jsx index 7f03abb..fc9f9ae 100644 --- a/app/lib/components/Sidebar.jsx +++ b/app/lib/components/Sidebar.jsx @@ -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 = { - fullscreen : false - }; + 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 && (
+ { + 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([ +
+
+ {this.fullscreen.fullscreenEnabled && ( +
+ )} +
+ {this.props.children} +
+ ], 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; diff --git a/app/lib/components/VideoWindow/VideoWindow.jsx b/app/lib/components/VideoWindow/VideoWindow.jsx index c03fbdf..c5a4a56 100644 --- a/app/lib/components/VideoWindow/VideoWindow.jsx +++ b/app/lib/components/VideoWindow/VideoWindow.jsx @@ -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 ( -
- {consumerVisible && !consumer.supported ? -
-

incompatible video

-
- :null - } - -
-
- { - e.stopPropagation(); - toggleConsumerWindow(); - }} - /> -
- - -
+ ); }; @@ -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()); } }; }; diff --git a/app/package.json b/app/package.json index ebb8fe1..21db4b7 100644 --- a/app/package.json +++ b/app/package.json @@ -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", diff --git a/app/stylus/components/FullScreenView.styl b/app/stylus/components/FullScreenView.styl index fe6458d..f1f4c29 100644 --- a/app/stylus/components/FullScreenView.styl +++ b/app/stylus/components/FullScreenView.styl @@ -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); + } } }