Bug 1241326 - Implement resize handles to change viewport size on drag r=jryans

This commit is contained in:
Gabriel Luong 2016-02-11 18:14:07 -05:00
parent 18c72db2b3
commit 441c99775b
14 changed files with 287 additions and 14 deletions

View File

@ -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",

View File

@ -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.
*/

View File

@ -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)),
});
},

View File

@ -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,

View File

@ -6,6 +6,7 @@
DevToolsModules(
'browser.js',
'resizable-viewport.js',
'viewport-toolbar.js',
'viewport.js',
'viewports.js',

View File

@ -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,
})
);
},
});

View File

@ -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,
})
);
},

View File

@ -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),
});
})

View File

@ -0,0 +1,6 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#A5A5A5">
<path d="M16 3.2L3.1 16h1.7L16 4.9zM16 7.2L7.1 16h1.8L16 8.9zM16 11.1L11.1 16h1.8l3.1-3.1z" />
</svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@ -5,5 +5,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'grippers.svg',
'rotate-viewport.svg',
)

View File

@ -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;
}

View File

@ -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) {

View File

@ -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");
});

View File

@ -6,4 +6,5 @@ firefox-appdir = browser
[test_add_viewport.js]
[test_change_location.js]
[test_resize_viewport.js]
[test_rotate_viewport.js]