From 441c99775bfe224dad7033e4fc51ceb67c77de00 Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Thu, 11 Feb 2016 18:14:07 -0500 Subject: [PATCH] Bug 1241326 - Implement resize handles to change viewport size on drag r=jryans --- .../client/responsive.html/actions/index.js | 3 + .../responsive.html/actions/viewports.js | 18 ++- devtools/client/responsive.html/app.js | 4 +- .../responsive.html/components/browser.js | 11 +- .../responsive.html/components/moz.build | 1 + .../components/resizable-viewport.js | 143 ++++++++++++++++++ .../responsive.html/components/viewport.js | 17 +-- .../responsive.html/components/viewports.js | 4 + .../responsive.html/images/grippers.svg | 6 + .../client/responsive.html/images/moz.build | 1 + devtools/client/responsive.html/index.css | 52 +++++++ .../responsive.html/reducers/viewports.js | 19 ++- .../test/unit/test_resize_viewport.js | 21 +++ .../responsive.html/test/unit/xpcshell.ini | 1 + 14 files changed, 287 insertions(+), 14 deletions(-) create mode 100644 devtools/client/responsive.html/components/resizable-viewport.js create mode 100644 devtools/client/responsive.html/images/grippers.svg create mode 100644 devtools/client/responsive.html/test/unit/test_resize_viewport.js diff --git a/devtools/client/responsive.html/actions/index.js b/devtools/client/responsive.html/actions/index.js index 9e84fa8f887..88dab35d295 100644 --- a/devtools/client/responsive.html/actions/index.js +++ b/devtools/client/responsive.html/actions/index.js @@ -17,6 +17,9 @@ createEnum([ // Add an additional viewport to display the document. "ADD_VIEWPORT", + // Resize the viewport. + "RESIZE_VIEWPORT", + // Rotate the viewport. "ROTATE_VIEWPORT", diff --git a/devtools/client/responsive.html/actions/viewports.js b/devtools/client/responsive.html/actions/viewports.js index 0ded91cb232..6723032aba1 100644 --- a/devtools/client/responsive.html/actions/viewports.js +++ b/devtools/client/responsive.html/actions/viewports.js @@ -4,7 +4,11 @@ "use strict"; -const { ADD_VIEWPORT, ROTATE_VIEWPORT } = require("./index"); +const { + ADD_VIEWPORT, + RESIZE_VIEWPORT, + ROTATE_VIEWPORT +} = require("./index"); module.exports = { @@ -17,6 +21,18 @@ module.exports = { }; }, + /** + * Resize the viewport. + */ + resizeViewport(id, width, height) { + return { + type: RESIZE_VIEWPORT, + id, + width, + height, + }; + }, + /** * Rotate the viewport. */ diff --git a/devtools/client/responsive.html/app.js b/devtools/client/responsive.html/app.js index c189224a46c..a34049901ee 100644 --- a/devtools/client/responsive.html/app.js +++ b/devtools/client/responsive.html/app.js @@ -8,7 +8,7 @@ const { createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react"); const { connect } = require("devtools/client/shared/vendor/react-redux"); -const { rotateViewport } = require("./actions/viewports"); +const { resizeViewport, rotateViewport } = require("./actions/viewports"); const Types = require("./types"); const Viewports = createFactory(require("./components/viewports")); @@ -34,6 +34,8 @@ let App = createClass({ location, viewports, onRotateViewport: id => dispatch(rotateViewport(id)), + onResizeViewport: (id, width, height) => + dispatch(resizeViewport(id, width, height)), }); }, diff --git a/devtools/client/responsive.html/components/browser.js b/devtools/client/responsive.html/components/browser.js index f20de52c01a..ff1b4bbdac9 100644 --- a/devtools/client/responsive.html/components/browser.js +++ b/devtools/client/responsive.html/components/browser.js @@ -4,7 +4,7 @@ "use strict"; -const { DOM: dom, createClass } = +const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); const Types = require("../types"); @@ -17,6 +17,7 @@ module.exports = createClass({ location: Types.location.isRequired, width: Types.viewport.width.isRequired, height: Types.viewport.height.isRequired, + isResizing: PropTypes.bool.isRequired, }, render() { @@ -24,11 +25,17 @@ module.exports = createClass({ location, width, height, + isResizing, } = this.props; + let className = "browser"; + if (isResizing) { + className += " resizing"; + } + return dom.iframe( { - className: "browser", + className, src: location, width, height, diff --git a/devtools/client/responsive.html/components/moz.build b/devtools/client/responsive.html/components/moz.build index 426a572e8d8..9a98dbe75c5 100644 --- a/devtools/client/responsive.html/components/moz.build +++ b/devtools/client/responsive.html/components/moz.build @@ -6,6 +6,7 @@ DevToolsModules( 'browser.js', + 'resizable-viewport.js', 'viewport-toolbar.js', 'viewport.js', 'viewports.js', diff --git a/devtools/client/responsive.html/components/resizable-viewport.js b/devtools/client/responsive.html/components/resizable-viewport.js new file mode 100644 index 00000000000..91a6623f174 --- /dev/null +++ b/devtools/client/responsive.html/components/resizable-viewport.js @@ -0,0 +1,143 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* global window */ + +"use strict"; + +const { DOM: dom, createClass, createFactory, PropTypes } = + require("devtools/client/shared/vendor/react"); + +const Types = require("../types"); +const Browser = createFactory(require("./browser")); +const ViewportToolbar = createFactory(require("./viewport-toolbar")); + +const VIEWPORT_MIN_WIDTH = 280; +const VIEWPORT_MIN_HEIGHT = 280; + +module.exports = createClass({ + + displayName: "ResizableViewport", + + propTypes: { + location: Types.location.isRequired, + viewport: PropTypes.shape(Types.viewport).isRequired, + onResizeViewport: PropTypes.func.isRequired, + onRotateViewport: PropTypes.func.isRequired, + }, + + getInitialState() { + return { + isResizing: false, + lastClientX: 0, + lastClientY: 0, + ignoreX: false, + ignoreY: false, + }; + }, + + onResizeStart({ target, clientX, clientY }) { + window.addEventListener("mousemove", this.onResizeDrag, true); + window.addEventListener("mouseup", this.onResizeStop, true); + + this.setState({ + isResizing: true, + lastClientX: clientX, + lastClientY: clientY, + ignoreX: target === this.refs.resizeBarY, + ignoreY: target === this.refs.resizeBarX, + }); + }, + + onResizeStop() { + window.removeEventListener("mousemove", this.onResizeDrag, true); + window.removeEventListener("mouseup", this.onResizeStop, true); + + this.setState({ + isResizing: false, + lastClientX: 0, + lastClientY: 0, + ignoreX: false, + ignoreY: false, + }); + }, + + onResizeDrag({ clientX, clientY }) { + if (!this.state.isResizing) { + return; + } + + let { lastClientX, lastClientY, ignoreX, ignoreY } = this.state; + let deltaX = clientX - lastClientX; + let deltaY = clientY - lastClientY; + + if (ignoreX) { + deltaX = 0; + } + if (ignoreY) { + deltaY = 0; + } + + let width = this.props.viewport.width + deltaX; + let height = this.props.viewport.height + deltaY; + + if (width < VIEWPORT_MIN_WIDTH) { + width = VIEWPORT_MIN_WIDTH; + } else { + lastClientX = clientX; + } + + if (height < VIEWPORT_MIN_HEIGHT) { + height = VIEWPORT_MIN_HEIGHT; + } else { + lastClientY = clientY; + } + + // Update the viewport store with the new width and height. + this.props.onResizeViewport(width, height); + + this.setState({ + lastClientX, + lastClientY + }); + }, + + render() { + let { + location, + viewport, + onRotateViewport, + } = this.props; + + return dom.div( + { + className: "resizable-viewport", + }, + ViewportToolbar({ + onRotateViewport, + }), + Browser({ + location, + width: viewport.width, + height: viewport.height, + isResizing: this.state.isResizing + }), + dom.div({ + className: "viewport-resize-handle", + onMouseDown: this.onResizeStart, + }), + dom.div({ + ref: "resizeBarX", + className: "viewport-horizontal-resize-handle", + onMouseDown: this.onResizeStart, + }), + dom.div({ + ref: "resizeBarY", + className: "viewport-vertical-resize-handle", + onMouseDown: this.onResizeStart, + }) + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/viewport.js b/devtools/client/responsive.html/components/viewport.js index 8ed829a7f09..8f1767c40b9 100644 --- a/devtools/client/responsive.html/components/viewport.js +++ b/devtools/client/responsive.html/components/viewport.js @@ -8,8 +8,7 @@ const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react"); const Types = require("../types"); -const Browser = createFactory(require("./browser")); -const ViewportToolbar = createFactory(require("./viewport-toolbar")); +const ResizableViewport = createFactory(require("./resizable-viewport")); module.exports = createClass({ @@ -18,6 +17,7 @@ module.exports = createClass({ propTypes: { location: Types.location.isRequired, viewport: PropTypes.shape(Types.viewport).isRequired, + onResizeViewport: PropTypes.func.isRequired, onRotateViewport: PropTypes.func.isRequired, }, @@ -25,20 +25,19 @@ module.exports = createClass({ let { location, viewport, + onResizeViewport, onRotateViewport, } = this.props; return dom.div( { - className: "viewport" + className: "viewport", }, - ViewportToolbar({ - onRotateViewport, - }), - Browser({ + ResizableViewport({ location, - width: viewport.width, - height: viewport.height, + viewport, + onResizeViewport, + onRotateViewport, }) ); }, diff --git a/devtools/client/responsive.html/components/viewports.js b/devtools/client/responsive.html/components/viewports.js index 229b401fc3b..97d216f2748 100644 --- a/devtools/client/responsive.html/components/viewports.js +++ b/devtools/client/responsive.html/components/viewports.js @@ -17,6 +17,7 @@ module.exports = createClass({ propTypes: { location: Types.location.isRequired, viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired, + onResizeViewport: PropTypes.func.isRequired, onRotateViewport: PropTypes.func.isRequired, }, @@ -24,6 +25,7 @@ module.exports = createClass({ let { location, viewports, + onResizeViewport, onRotateViewport, } = this.props; @@ -36,6 +38,8 @@ module.exports = createClass({ key: viewport.id, location, viewport, + onResizeViewport: (width, height) => + onResizeViewport(viewport.id, width, height), onRotateViewport: () => onRotateViewport(viewport.id), }); }) diff --git a/devtools/client/responsive.html/images/grippers.svg b/devtools/client/responsive.html/images/grippers.svg new file mode 100644 index 00000000000..91db83af9d2 --- /dev/null +++ b/devtools/client/responsive.html/images/grippers.svg @@ -0,0 +1,6 @@ + + + + diff --git a/devtools/client/responsive.html/images/moz.build b/devtools/client/responsive.html/images/moz.build index c97d4941f60..e7bf743777c 100644 --- a/devtools/client/responsive.html/images/moz.build +++ b/devtools/client/responsive.html/images/moz.build @@ -5,5 +5,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( + 'grippers.svg', 'rotate-viewport.svg', ) diff --git a/devtools/client/responsive.html/index.css b/devtools/client/responsive.html/index.css index a7df3e3b6bc..1f7aa4841e4 100644 --- a/devtools/client/responsive.html/index.css +++ b/devtools/client/responsive.html/index.css @@ -13,6 +13,10 @@ --viewport-box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26); } +* { + box-sizing: border-box; +} + html, body { margin: 0; height: 100%; @@ -57,6 +61,10 @@ body { box-shadow: var(--viewport-box-shadow); } +.resizable-viewport { + position: relative; +} + /** * Viewport Toolbar */ @@ -96,7 +104,51 @@ body { mask-image: url("./images/rotate-viewport.svg"); } +/** + * Viewport Browser + */ + .browser { display: block; border: 0; } + +.browser.resizing { + pointer-events: none; +} + +/** + * Viewport Resize Handles + */ + +.viewport-resize-handle { + position: absolute; + width: 16px; + height: 16px; + bottom: 0; + right: 0; + background-image: url("./images/grippers.svg"); + background-position: bottom right; + padding: 0 1px 1px 0; + background-repeat: no-repeat; + background-origin: content-box; + cursor: se-resize; +} + +.viewport-horizontal-resize-handle { + position: absolute; + width: 5px; + height: calc(100% - 16px); + right: -4px; + top: 0; + cursor: e-resize; +} + +.viewport-vertical-resize-handle { + position: absolute; + width: calc(100% - 16px); + height: 5px; + left: 0; + bottom: -4px; + cursor: s-resize; +} diff --git a/devtools/client/responsive.html/reducers/viewports.js b/devtools/client/responsive.html/reducers/viewports.js index 177e3861722..14674cd18dc 100644 --- a/devtools/client/responsive.html/reducers/viewports.js +++ b/devtools/client/responsive.html/reducers/viewports.js @@ -4,7 +4,11 @@ "use strict"; -const { ADD_VIEWPORT, ROTATE_VIEWPORT } = require("../actions/index"); +const { + ADD_VIEWPORT, + RESIZE_VIEWPORT, + ROTATE_VIEWPORT, +} = require("../actions/index"); let nextViewportId = 0; @@ -25,6 +29,19 @@ let reducers = { return [...viewports, Object.assign({}, INITIAL_VIEWPORT)]; }, + [RESIZE_VIEWPORT](viewports, { id, width, height }) { + return viewports.map(viewport => { + if (viewport.id !== id) { + return viewport; + } + + return Object.assign({}, viewport, { + width, + height, + }); + }); + }, + [ROTATE_VIEWPORT](viewports, { id }) { return viewports.map(viewport => { if (viewport.id !== id) { diff --git a/devtools/client/responsive.html/test/unit/test_resize_viewport.js b/devtools/client/responsive.html/test/unit/test_resize_viewport.js new file mode 100644 index 00000000000..0d3cf780946 --- /dev/null +++ b/devtools/client/responsive.html/test/unit/test_resize_viewport.js @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test resizing the viewport. + +const { addViewport, resizeViewport } = + require("devtools/client/responsive.html/actions/viewports"); + +add_task(function*() { + let store = Store(); + const { getState, dispatch } = store; + + dispatch(addViewport()); + dispatch(resizeViewport(0, 500, 500)); + + let viewport = getState().viewports[0]; + equal(viewport.width, 500, "Resized width of 500"); + equal(viewport.height, 500, "Resized height of 500"); +}); diff --git a/devtools/client/responsive.html/test/unit/xpcshell.ini b/devtools/client/responsive.html/test/unit/xpcshell.ini index 95d978f5358..3b9e704e333 100644 --- a/devtools/client/responsive.html/test/unit/xpcshell.ini +++ b/devtools/client/responsive.html/test/unit/xpcshell.ini @@ -6,4 +6,5 @@ firefox-appdir = browser [test_add_viewport.js] [test_change_location.js] +[test_resize_viewport.js] [test_rotate_viewport.js]