From 00879965b792e572092e073da74d23a794af961b Mon Sep 17 00:00:00 2001 From: Joe Fleming Date: Mon, 16 Apr 2018 15:53:31 -0700 Subject: [PATCH] Fix: Use mock fullscreen instead of Fullscreen API (#492) * chore: rewrite fullscreen component use component syntax, simply tie into redux in index * chore: remove conditional fullscreen controls * chore: rewrite fullscreen control and service fullscreen toggle is entired managed in state, and the control simply wraps keybindings and sets the state * fix: tweak styles for fullscreen greatness * fix: page hotkeys in fullscreen mode * feat: replicate edit page keys in presentation mode * chore: move fullscreen service to middleware * fix: use classList for fullscreen * fix: remove id passing in fullscreen component it's not used, or really even useful, and can be put back if we need it * fix: no scrolling when scaling below 1 * chore: fix imports, drop file extensions --- public/app.js | 4 - public/components/fullscreen/fullscreen.js | 66 +++++++++-- public/components/fullscreen/fullscreen.less | 38 +++++++ public/components/fullscreen/index.js | 35 +----- .../fullscreen_control/fullscreen_control.js | 47 ++------ public/components/fullscreen_control/index.js | 24 +--- public/components/workpad/workpad.js | 21 +++- .../workpad_header/workpad_header.js | 4 +- public/lib/fullscreen.js | 105 ++---------------- public/lib/keymap.js | 5 +- public/state/middleware/fullscreen.js | 14 +++ public/state/middleware/index.js | 2 + 12 files changed, 159 insertions(+), 206 deletions(-) create mode 100644 public/components/fullscreen/fullscreen.less create mode 100644 public/state/middleware/fullscreen.js diff --git a/public/app.js b/public/app.js index c1b899689c214..4069e3d918ce9 100644 --- a/public/app.js +++ b/public/app.js @@ -3,14 +3,10 @@ import chrome from 'ui/chrome'; import './angular/config'; import './angular/services'; import { CanvasRootController } from './angular/controllers'; -import { initialize as initializeFullscreen } from './lib/fullscreen'; // TODO: We needed button style support. Remove this and hackery.less when you can import 'bootstrap/dist/css/bootstrap.css'; import './style/main.less'; -// enable fullscreen controls -initializeFullscreen(document); - // load the application chrome.setRootController('canvas', CanvasRootController); diff --git a/public/components/fullscreen/fullscreen.js b/public/components/fullscreen/fullscreen.js index 01f4cee56c410..7217f368662d2 100644 --- a/public/components/fullscreen/fullscreen.js +++ b/public/components/fullscreen/fullscreen.js @@ -1,15 +1,57 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { debounce } from 'lodash'; +import { getWindow } from '../../lib/get_window'; +import './fullscreen.less'; -export const Fullscreen = ({ ident, isFullscreen, windowSize, children }) => ( -
- {children({ isFullscreen, windowSize })} -
-); - -Fullscreen.propTypes = { - ident: PropTypes.string.isRequired, - isFullscreen: PropTypes.bool, - windowSize: PropTypes.object, - children: PropTypes.func, -}; +export class Fullscreen extends React.Component { + static propTypes = { + isFullscreen: PropTypes.bool, + children: PropTypes.func, + }; + + state = { + width: 0, + height: 0, + }; + + componentWillMount() { + this.win = getWindow(); + this.setState({ + width: this.win.innerWidth, + height: this.win.innerHeight, + }); + } + + componentDidMount() { + this.win.addEventListener('resize', this.onWindowResize); + } + + componentWillUnmount() { + this.win.removeEventListener('resize', this.onWindowResize); + } + + getWindowSize = () => ({ + width: this.win.innerWidth, + height: this.win.innerHeight, + }); + + onWindowResize = debounce(() => { + const { width, height } = this.getWindowSize(); + this.setState({ width, height }); + }, 100); + + render() { + const { isFullscreen, children } = this.props; + const windowSize = { + width: this.state.width, + height: this.state.height, + }; + + return ( +
+ {children({ isFullscreen, windowSize })} +
+ ); + } +} diff --git a/public/components/fullscreen/fullscreen.less b/public/components/fullscreen/fullscreen.less new file mode 100644 index 0000000000000..d0756001d8596 --- /dev/null +++ b/public/components/fullscreen/fullscreen.less @@ -0,0 +1,38 @@ +body.canvas-fullscreen { + nav.global-nav { + display: none; + } + + .content .app-wrapper { + left: 0; + } + + .canvas__workpad_app { + .canvas__workpad_app--main .canvas__workpad_app--sidebar, + .canvas__workpad_app--main .canvas__workpad_header, + .canvas__toolbar { + display: none; + } + + .canvas__workpad_app--main .canvas__workpad_app--workpad { + padding: 0; + background-color: black; + align-items: center; + justify-content: center; + overflow: hidden; + + .canvas__workpad_app--workspace { + width: auto; + + .canvas__checkered { + background: none; + } + + .canvas__workpad { + box-shadow: none; + overflow: hidden; + } + } + } + } +} diff --git a/public/components/fullscreen/index.js b/public/components/fullscreen/index.js index 3f902c8fc9a27..efdbbcdb35db5 100644 --- a/public/components/fullscreen/index.js +++ b/public/components/fullscreen/index.js @@ -1,40 +1,9 @@ import { connect } from 'react-redux'; -import { compose, withProps, withState, withHandlers, lifecycle } from 'recompose'; -import { debounce } from 'lodash'; -import { getFullscreen } from '../../state/selectors/app.js'; -import { defaultIdent } from '../../lib/fullscreen.js'; -import { getWindow } from '../../lib/get_window.js'; +import { getFullscreen } from '../../state/selectors/app'; import { Fullscreen as Component } from './fullscreen'; -const win = getWindow(); - const mapStateToProps = state => ({ isFullscreen: getFullscreen(state), }); -const getWindowSize = () => ({ - width: win.innerWidth, - height: win.innerHeight, -}); - -export const Fullscreen = compose( - withProps(({ ident }) => ({ - ident: ident || defaultIdent, - })), - withState('windowSize', 'setWindowSize', getWindowSize()), - withHandlers({ - windowResizeHandler: ({ setWindowSize }) => - debounce(() => { - setWindowSize(getWindowSize()); - }, 100), - }), - connect(mapStateToProps), - lifecycle({ - componentWillMount() { - win.addEventListener('resize', this.props.windowResizeHandler); - }, - componentWillUnmount() { - win.removeEventListener('resize', this.props.windowResizeHandler); - }, - }) -)(Component); +export const Fullscreen = connect(mapStateToProps)(Component); diff --git a/public/components/fullscreen_control/fullscreen_control.js b/public/components/fullscreen_control/fullscreen_control.js index 600c1f0a31873..3821803705d33 100644 --- a/public/components/fullscreen_control/fullscreen_control.js +++ b/public/components/fullscreen_control/fullscreen_control.js @@ -1,56 +1,33 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Shortcuts } from 'react-shortcuts'; -import { createListener, canFullscreen } from '../../lib/fullscreen.js'; -// TODO: this is a class because this has to use ref, and it seemed best to allow -// multile instances of this component... can we use ref with SFCs? export class FullscreenControl extends React.PureComponent { - componentDidMount() { - // check that the fullscreen api is available, deactivate control if not - const el = this.node; - if (!canFullscreen(el)) { - this.props.setActive(false); - return; - } - - // listen for changes to the fullscreen element, update state to match - this.fullscreenListener = createListener(({ fullscreen, element }) => { - // if the app is the fullscreen element, set the app state - if (!element || element.id === this.props.ident) this.props.setFullscreen(fullscreen); - }); - } - - componentWillUmount() { - // remove the fullscreen event listener - this.fullscreenListener && this.fullscreenListener(); - } + toggleFullscreen = () => { + const { setFullscreen, isFullscreen } = this.props; + setFullscreen(!isFullscreen); + }; render() { - const { isActive, children, isFullscreen, onFullscreen } = this.props; - if (!isActive) return null; + const { children, isFullscreen } = this.props; const keyHandler = action => { - if (action === 'FULLSCREEN') onFullscreen(); + if (action === 'FULLSCREEN' || (isFullscreen && action === 'FULLSCREEN_EXIT')) { + this.toggleFullscreen(); + } }; return ( - (this.node = node)}> - {!isFullscreen && ( - - )} - {children({ isFullscreen, onFullscreen })} + + + {children({ isFullscreen, toggleFullscreen: this.toggleFullscreen })} ); } } FullscreenControl.propTypes = { - isActive: PropTypes.bool.isRequired, - setActive: PropTypes.func.isRequired, setFullscreen: PropTypes.func.isRequired, + isFullscreen: PropTypes.bool.isRequired, children: PropTypes.func.isRequired, - ident: PropTypes.string.isRequired, - onFullscreen: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), - isFullscreen: PropTypes.bool, }; diff --git a/public/components/fullscreen_control/index.js b/public/components/fullscreen_control/index.js index 16cbc1a8e8913..b99bab75acbea 100644 --- a/public/components/fullscreen_control/index.js +++ b/public/components/fullscreen_control/index.js @@ -1,9 +1,6 @@ -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { compose, withState, withProps } from 'recompose'; -import { defaultIdent, createHandler } from '../../lib/fullscreen.js'; -import { setFullscreen } from '../../state/actions/transient.js'; -import { getFullscreen } from '../../state/selectors/app.js'; +import { setFullscreen } from '../../state/actions/transient'; +import { getFullscreen } from '../../state/selectors/app'; import { FullscreenControl as Component } from './fullscreen_control'; const mapStateToProps = state => ({ @@ -14,19 +11,4 @@ const mapDispatchToProps = { setFullscreen, }; -export const FullscreenControl = compose( - connect(mapStateToProps, mapDispatchToProps), - withState('isActive', 'setActive', true), - withProps(({ ident, isActive }) => ({ - ident: ident || defaultIdent, - onFullscreen: ev => { - if (!isActive) return; - const fullscreenHandler = createHandler(ident || defaultIdent); - fullscreenHandler(ev); - }, - })) -)(Component); - -FullscreenControl.propTypes = { - ident: PropTypes.string, -}; +export const FullscreenControl = connect(mapStateToProps, mapDispatchToProps)(Component); diff --git a/public/components/workpad/workpad.js b/public/components/workpad/workpad.js index b296298edad22..2a7107aec2205 100644 --- a/public/components/workpad/workpad.js +++ b/public/components/workpad/workpad.js @@ -20,16 +20,23 @@ export const Workpad = props => { previousPage, isFullscreen, } = props; + const { height, width } = workpad; // TODO: I think this is mixing in background color, that should be pushed down to a page component, otherwise reporting wont work right const itsTheNewStyle = { ...style, height, width }; const keyHandler = action => { + // handle keypress events for editor and presentation events + // this exists in both contexts if (action === 'REFRESH') return fetchAllRenderables(); + + // editor events if (action === 'UNDO') return undoHistory(); if (action === 'REDO') return redoHistory(); if (action === 'GRID') return setGrid(!grid); + + // presentation events if (action === 'PREV') return previousPage(); if (action === 'NEXT') return nextPage(); }; @@ -43,12 +50,13 @@ export const Workpad = props => { {({ isFullscreen, windowSize }) => { const scale = Math.min(windowSize.height / height, windowSize.width / width); + const transform = `scale3d(${scale}, ${scale}, 1)`; const fsStyle = !isFullscreen ? {} : { - WebkitTransform: `scale3d(${scale}, ${scale}, 1)`, - msTransform: `scale3d(${scale}, ${scale}, 1)`, - transform: `scale3d(${scale}, ${scale}, 1)`, + transform, + WebkitTransform: transform, + msTransform: transform, }; return ( @@ -57,7 +65,12 @@ export const Workpad = props => { style={{ ...itsTheNewStyle, ...fsStyle }} > {isFullscreen && ( - + )} { - {({ onFullscreen }) => ( + {({ toggleFullscreen }) => ( - + )} diff --git a/public/lib/fullscreen.js b/public/lib/fullscreen.js index f0156a15b84c7..90a55869372e4 100644 --- a/public/lib/fullscreen.js +++ b/public/lib/fullscreen.js @@ -1,95 +1,14 @@ -export const defaultIdent = 'canvas--fullscreen'; - -const _listeners = []; - -function requestFullscreen(el) { - if (el.requestFullscreen) return el.requestFullscreen(); - if (el.msRequestFullscreen) return el.msRequestFullscreen(); - if (el.mozRequestFullScreen) return el.mozRequestFullScreen(); - if (el.webkitRequestFullscreen) return el.webkitRequestFullscreen(); - return false; -} - -function getFullscreenElement(doc) { - if (typeof doc.fullScreenElement !== 'undefined') { - return doc.fullScreenElement; - } - - if (typeof doc.msFullscreenElement !== 'undefined') { - return doc.msFullscreenElement; - } - - if (typeof doc.mozFullScreenElement !== 'undefined') { - return doc.mozFullScreenElement; - } - - if (typeof doc.webkitFullscreenElement !== 'undefined') { - return doc.webkitFullscreenElement; - } - - return false; -} - -export function canFullscreen(el) { - return ( - typeof el.requestFullscreen === 'function' || - typeof el.msRequestFullscreen === 'function' || - typeof el.mozRequestFullScreen === 'function' || - typeof el.webkitRequestFullscreen === 'function' - ); -} - -export function createHandler(ident = defaultIdent, doc = document) { - const el = doc.getElementById && doc.getElementById(ident); - - // TODO: tell the user something failed, at least in dev mode - if (!el || !canFullscreen(el)) return false; - - return ev => { - ev && ev.preventDefault(); - - // check to see if any element is already fullscreen, do nothing if so - if (getFullscreenElement(doc)) return; - - // nothing is already fullscreen, and we have a setter; call it - requestFullscreen(el); - }; -} - -export function createListener(fn) { - _listeners.push(fn); - - return function removeListener() { - const index = _listeners.findIndex(listener => listener === fn); - _listeners.splice(index, 1); - }; -} - -export function initialize(doc = document) { - const checks = [ - 'onfullscreenchange', - 'onmsfullscreenchange', - 'onmozfullscreenchange', - 'onwebkitfullscreenchange', - ]; - - for (const m in checks) { - if (typeof doc[checks[m]] !== 'undefined') { - if (doc[checks[m]] === null) { - doc[checks[m]] = ev => { - const el = getFullscreenElement(doc); - const payload = { - fullscreen: Boolean(el), - target: ev.target, - element: el, - }; - - // _emitter.emit('onfullscreenchange', payload); - _listeners.forEach(listener => { - listener.call(null, payload); - }); - }; - } - } +export const fullscreenClass = 'canvas-fullscreen'; + +export function setFullscreen(fullscreen, doc = document) { + const enabled = Boolean(fullscreen); + const body = doc.querySelector('body'); + const bodyClassList = body.classList; + const isFullscreen = bodyClassList.contains(fullscreenClass); + + if (enabled && !isFullscreen) { + bodyClassList.add(fullscreenClass); + } else if (!enabled && isFullscreen) { + bodyClassList.remove(fullscreenClass); } } diff --git a/public/lib/keymap.js b/public/lib/keymap.js index d80843049382c..0ad82deedb7f8 100644 --- a/public/lib/keymap.js +++ b/public/lib/keymap.js @@ -5,6 +5,7 @@ export const keymap = { NEXT: 'alt+]', PREV: 'alt+[', FULLSCREEN: ['alt+p', 'alt+f'], + FULLSCREEN_EXIT: ['escape'], EDITING: ['alt+e'], GRID: 'alt+g', REFRESH: 'alt+r', @@ -13,8 +14,8 @@ export const keymap = { DELETE: 'del', }, PRESENTATION: { - NEXT: ['space', 'right'], - PREV: 'left', + NEXT: ['space', 'right', 'alt+]'], + PREV: ['left', 'alt+['], REFRESH: 'alt+r', }, }; diff --git a/public/state/middleware/fullscreen.js b/public/state/middleware/fullscreen.js new file mode 100644 index 0000000000000..38097f970cbf5 --- /dev/null +++ b/public/state/middleware/fullscreen.js @@ -0,0 +1,14 @@ +import { setFullscreen } from '../../lib/fullscreen'; +import { setFullscreen as setFullscreenAction } from '../actions/transient'; +import { getFullscreen } from '../selectors/app'; + +export const fullscreen = ({ getState }) => next => action => { + // execute the default action + next(action); + + // pass current state's fullscreen info to the fullscreen service + if (action.type === setFullscreenAction.toString()) { + const fullscreen = getFullscreen(getState()); + setFullscreen(fullscreen); + } +}; diff --git a/public/state/middleware/index.js b/public/state/middleware/index.js index e4d8b49643a6d..7aab21ef84ef4 100644 --- a/public/state/middleware/index.js +++ b/public/state/middleware/index.js @@ -2,6 +2,7 @@ import { applyMiddleware, compose } from 'redux'; import thunkMiddleware from 'redux-thunk'; import { getWindow } from '../../lib/get_window'; import { esPersistMiddleware } from './es_persist'; +import { fullscreen } from './fullscreen'; import { historyMiddleware } from './history'; import { inFlight } from './in_flight'; import { workpadUpdate } from './workpad_update'; @@ -13,6 +14,7 @@ const middlewares = [ thunkMiddleware, esPersistMiddleware, historyMiddleware, + fullscreen, inFlight, appReady, workpadUpdate,