зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1239461 - Screenshot button for taking a screenshot of the current viewport; r=jryans
MozReview-Commit-ID: AMbzmf1uO0P --HG-- rename : devtools/client/responsive.html/components/utils/l10n.js => devtools/client/responsive.html/utils/l10n.js rename : devtools/client/responsive.html/components/utils/moz.build => devtools/client/responsive.html/utils/moz.build extra : transplant_source : A%97_%C1d%AC%09%7C%E3%7F%0D%BCWl%8C%92V%09%1E%03
This commit is contained in:
Родитель
c6425cbdee
Коммит
d5459ac316
|
@ -21,3 +21,12 @@ responsive.exit=Close Responsive Design Mode
|
||||||
# LOCALIZATION NOTE (responsive.noDeviceSelected): placeholder text for the
|
# LOCALIZATION NOTE (responsive.noDeviceSelected): placeholder text for the
|
||||||
# device selector
|
# device selector
|
||||||
responsive.noDeviceSelected=no device selected
|
responsive.noDeviceSelected=no device selected
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (responsive.screenshot): tooltip of the screenshot button.
|
||||||
|
responsive.screenshot=Take a screenshot of the viewport
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (responsive.screenshotGeneratedFilename): The auto generated
|
||||||
|
# filename.
|
||||||
|
# The first argument (%1$S) is the date string in yyyy-mm-dd format and the
|
||||||
|
# second argument (%2$S) is the time string in HH.MM.SS format.
|
||||||
|
responsive.screenshotGeneratedFilename=Screen Shot %1$S at %2$S
|
||||||
|
|
|
@ -32,6 +32,12 @@ createEnum([
|
||||||
// Rotate the viewport.
|
// Rotate the viewport.
|
||||||
"ROTATE_VIEWPORT",
|
"ROTATE_VIEWPORT",
|
||||||
|
|
||||||
|
// Take a screenshot of the viewport.
|
||||||
|
"TAKE_SCREENSHOT_START",
|
||||||
|
|
||||||
|
// Indicates when the screenshot action ends.
|
||||||
|
"TAKE_SCREENSHOT_END",
|
||||||
|
|
||||||
], module.exports);
|
], module.exports);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,5 +8,6 @@ DevToolsModules(
|
||||||
'devices.js',
|
'devices.js',
|
||||||
'index.js',
|
'index.js',
|
||||||
'location.js',
|
'location.js',
|
||||||
|
'screenshot.js',
|
||||||
'viewports.js',
|
'viewports.js',
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/* eslint-env browser */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
|
|
||||||
|
const {
|
||||||
|
TAKE_SCREENSHOT_START,
|
||||||
|
TAKE_SCREENSHOT_END,
|
||||||
|
} = require("./index");
|
||||||
|
|
||||||
|
const { getRect } = require("devtools/shared/layout/utils");
|
||||||
|
const { getFormatStr } = require("../utils/l10n");
|
||||||
|
const { getToplevelWindow } = require("sdk/window/utils");
|
||||||
|
const { Task: { spawn, async } } = require("resource://gre/modules/Task.jsm");
|
||||||
|
|
||||||
|
const BASE_URL = "resource://devtools/client/responsive.html";
|
||||||
|
const audioCamera = new window.Audio(`${BASE_URL}/audio/camera-click.mp3`);
|
||||||
|
|
||||||
|
function getFileName() {
|
||||||
|
let date = new Date();
|
||||||
|
let month = ("0" + (date.getMonth() + 1)).substr(-2);
|
||||||
|
let day = ("0" + date.getDate()).substr(-2);
|
||||||
|
let dateString = [date.getFullYear(), month, day].join("-");
|
||||||
|
let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
|
||||||
|
|
||||||
|
return getFormatStr("responsive.screenshotGeneratedFilename", dateString,
|
||||||
|
timeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScreenshotFor(node) {
|
||||||
|
let { top, left, width, height } = getRect(window, node, window);
|
||||||
|
|
||||||
|
const canvas = document.createElementNS(HTML_NS, "canvas");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
const ratio = window.devicePixelRatio;
|
||||||
|
canvas.width = width * ratio;
|
||||||
|
canvas.height = height * ratio;
|
||||||
|
ctx.scale(ratio, ratio);
|
||||||
|
ctx.drawWindow(window, left, top, width, height, "#fff");
|
||||||
|
|
||||||
|
return canvas.toDataURL("image/png", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveToFile(data, filename) {
|
||||||
|
return spawn(function* () {
|
||||||
|
const chromeWindow = getToplevelWindow(window);
|
||||||
|
const chromeDocument = chromeWindow.document;
|
||||||
|
|
||||||
|
// append .png extension to filename if it doesn't exist
|
||||||
|
filename = filename.replace(/\.png$|$/i, ".png");
|
||||||
|
|
||||||
|
chromeWindow.saveURL(data, filename, null,
|
||||||
|
true, true,
|
||||||
|
chromeDocument.documentURIObject, chromeDocument);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function simulateCameraEffects(node) {
|
||||||
|
audioCamera.play();
|
||||||
|
node.animate({ opacity: [ 0, 1 ] }, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
takeScreenshot() {
|
||||||
|
return function* (dispatch, getState) {
|
||||||
|
yield dispatch({ type: TAKE_SCREENSHOT_START });
|
||||||
|
|
||||||
|
// Waiting the next repaint, to ensure the react components
|
||||||
|
// can be properly render after the action dispatched above
|
||||||
|
window.requestAnimationFrame(async(function* () {
|
||||||
|
let iframe = document.querySelector("iframe");
|
||||||
|
let data = createScreenshotFor(iframe);
|
||||||
|
|
||||||
|
simulateCameraEffects(iframe);
|
||||||
|
|
||||||
|
yield saveToFile(data, getFileName());
|
||||||
|
|
||||||
|
dispatch({ type: TAKE_SCREENSHOT_END });
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -2,6 +2,8 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/* eslint-env browser */
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { createClass, createFactory, PropTypes, DOM: dom } =
|
const { createClass, createFactory, PropTypes, DOM: dom } =
|
||||||
|
@ -13,6 +15,7 @@ const {
|
||||||
resizeViewport,
|
resizeViewport,
|
||||||
rotateViewport
|
rotateViewport
|
||||||
} = require("./actions/viewports");
|
} = require("./actions/viewports");
|
||||||
|
const { takeScreenshot } = require("./actions/screenshot");
|
||||||
const Types = require("./types");
|
const Types = require("./types");
|
||||||
const Viewports = createFactory(require("./components/viewports"));
|
const Viewports = createFactory(require("./components/viewports"));
|
||||||
const GlobalToolbar = createFactory(require("./components/global-toolbar"));
|
const GlobalToolbar = createFactory(require("./components/global-toolbar"));
|
||||||
|
@ -25,13 +28,17 @@ let App = createClass({
|
||||||
devices: PropTypes.shape(Types.devices).isRequired,
|
devices: PropTypes.shape(Types.devices).isRequired,
|
||||||
location: Types.location.isRequired,
|
location: Types.location.isRequired,
|
||||||
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
|
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
|
||||||
onExit: PropTypes.func.isRequired,
|
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
onChangeViewportDevice(id, device) {
|
onChangeViewportDevice(id, device) {
|
||||||
this.props.dispatch(changeDevice(id, device));
|
this.props.dispatch(changeDevice(id, device));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onExit() {
|
||||||
|
window.postMessage({ type: "exit" }, "*");
|
||||||
|
},
|
||||||
|
|
||||||
onResizeViewport(id, width, height) {
|
onResizeViewport(id, width, height) {
|
||||||
this.props.dispatch(resizeViewport(id, width, height));
|
this.props.dispatch(resizeViewport(id, width, height));
|
||||||
},
|
},
|
||||||
|
@ -40,18 +47,24 @@ let App = createClass({
|
||||||
this.props.dispatch(rotateViewport(id));
|
this.props.dispatch(rotateViewport(id));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onScreenshot() {
|
||||||
|
this.props.dispatch(takeScreenshot());
|
||||||
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let {
|
let {
|
||||||
devices,
|
devices,
|
||||||
location,
|
location,
|
||||||
|
screenshot,
|
||||||
viewports,
|
viewports,
|
||||||
onExit,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
onChangeViewportDevice,
|
onChangeViewportDevice,
|
||||||
|
onExit,
|
||||||
onResizeViewport,
|
onResizeViewport,
|
||||||
onRotateViewport,
|
onRotateViewport,
|
||||||
|
onScreenshot,
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
return dom.div(
|
return dom.div(
|
||||||
|
@ -59,11 +72,14 @@ let App = createClass({
|
||||||
id: "app",
|
id: "app",
|
||||||
},
|
},
|
||||||
GlobalToolbar({
|
GlobalToolbar({
|
||||||
|
screenshot,
|
||||||
onExit,
|
onExit,
|
||||||
|
onScreenshot,
|
||||||
}),
|
}),
|
||||||
Viewports({
|
Viewports({
|
||||||
devices,
|
devices,
|
||||||
location,
|
location,
|
||||||
|
screenshot,
|
||||||
viewports,
|
viewports,
|
||||||
onChangeViewportDevice,
|
onChangeViewportDevice,
|
||||||
onRotateViewport,
|
onRotateViewport,
|
||||||
|
|
Двоичный файл не отображается.
|
@ -0,0 +1,9 @@
|
||||||
|
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||||
|
# vim: set filetype=python:
|
||||||
|
# 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/.
|
||||||
|
|
||||||
|
DevToolsModules(
|
||||||
|
'camera-click.mp3',
|
||||||
|
)
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { getStr } = require("./utils/l10n");
|
const { getStr } = require("../utils/l10n");
|
||||||
const { DOM: dom, createClass, PropTypes, addons } =
|
const { DOM: dom, createClass, PropTypes, addons } =
|
||||||
require("devtools/client/shared/vendor/react");
|
require("devtools/client/shared/vendor/react");
|
||||||
|
|
||||||
|
|
|
@ -4,23 +4,28 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { getStr } = require("./utils/l10n");
|
const { getStr } = require("../utils/l10n");
|
||||||
const { DOM: dom, createClass, PropTypes, addons } =
|
const { DOM: dom, createClass, PropTypes, addons } =
|
||||||
require("devtools/client/shared/vendor/react");
|
require("devtools/client/shared/vendor/react");
|
||||||
|
const Types = require("../types");
|
||||||
|
|
||||||
module.exports = createClass({
|
module.exports = createClass({
|
||||||
|
|
||||||
displayName: "GlobalToolbar",
|
displayName: "GlobalToolbar",
|
||||||
|
|
||||||
mixins: [ addons.PureRenderMixin ],
|
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onExit: PropTypes.func.isRequired,
|
onExit: PropTypes.func.isRequired,
|
||||||
|
onScreenshot: PropTypes.func.isRequired,
|
||||||
|
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [ addons.PureRenderMixin ],
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let {
|
let {
|
||||||
onExit,
|
onExit,
|
||||||
|
onScreenshot,
|
||||||
|
screenshot,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return dom.header(
|
return dom.header(
|
||||||
|
@ -33,6 +38,13 @@ module.exports = createClass({
|
||||||
className: "title",
|
className: "title",
|
||||||
},
|
},
|
||||||
getStr("responsive.title")),
|
getStr("responsive.title")),
|
||||||
|
dom.button({
|
||||||
|
id: "global-screenshot-button",
|
||||||
|
className: "toolbar-button devtools-button",
|
||||||
|
title: getStr("responsive.screenshot"),
|
||||||
|
onClick: onScreenshot,
|
||||||
|
disabled: screenshot.isCapturing,
|
||||||
|
}),
|
||||||
dom.button({
|
dom.button({
|
||||||
id: "global-exit-button",
|
id: "global-exit-button",
|
||||||
className: "toolbar-button devtools-button",
|
className: "toolbar-button devtools-button",
|
||||||
|
|
|
@ -4,10 +4,6 @@
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# 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/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
DIRS += [
|
|
||||||
'utils',
|
|
||||||
]
|
|
||||||
|
|
||||||
DevToolsModules(
|
DevToolsModules(
|
||||||
'browser.js',
|
'browser.js',
|
||||||
'device-selector.js',
|
'device-selector.js',
|
||||||
|
|
|
@ -24,6 +24,7 @@ module.exports = createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
devices: PropTypes.shape(Types.devices).isRequired,
|
devices: PropTypes.shape(Types.devices).isRequired,
|
||||||
location: Types.location.isRequired,
|
location: Types.location.isRequired,
|
||||||
|
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||||
onResizeViewport: PropTypes.func.isRequired,
|
onResizeViewport: PropTypes.func.isRequired,
|
||||||
|
@ -112,12 +113,19 @@ module.exports = createClass({
|
||||||
let {
|
let {
|
||||||
devices,
|
devices,
|
||||||
location,
|
location,
|
||||||
|
screenshot,
|
||||||
viewport,
|
viewport,
|
||||||
onChangeViewportDevice,
|
onChangeViewportDevice,
|
||||||
onResizeViewport,
|
onResizeViewport,
|
||||||
onRotateViewport,
|
onRotateViewport,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
let resizeHandleClass = "viewport-resize-handle";
|
||||||
|
|
||||||
|
if (screenshot.isCapturing) {
|
||||||
|
resizeHandleClass += " hidden";
|
||||||
|
}
|
||||||
|
|
||||||
return dom.div(
|
return dom.div(
|
||||||
{
|
{
|
||||||
className: "resizable-viewport",
|
className: "resizable-viewport",
|
||||||
|
@ -136,7 +144,7 @@ module.exports = createClass({
|
||||||
isResizing: this.state.isResizing
|
isResizing: this.state.isResizing
|
||||||
}),
|
}),
|
||||||
dom.div({
|
dom.div({
|
||||||
className: "viewport-resize-handle",
|
className: resizeHandleClass,
|
||||||
onMouseDown: this.onResizeStart,
|
onMouseDown: this.onResizeStart,
|
||||||
}),
|
}),
|
||||||
dom.div({
|
dom.div({
|
||||||
|
|
|
@ -18,6 +18,7 @@ module.exports = createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
devices: PropTypes.shape(Types.devices).isRequired,
|
devices: PropTypes.shape(Types.devices).isRequired,
|
||||||
location: Types.location.isRequired,
|
location: Types.location.isRequired,
|
||||||
|
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||||
onResizeViewport: PropTypes.func.isRequired,
|
onResizeViewport: PropTypes.func.isRequired,
|
||||||
|
@ -55,6 +56,7 @@ module.exports = createClass({
|
||||||
let {
|
let {
|
||||||
devices,
|
devices,
|
||||||
location,
|
location,
|
||||||
|
screenshot,
|
||||||
viewport,
|
viewport,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -71,6 +73,7 @@ module.exports = createClass({
|
||||||
ResizableViewport({
|
ResizableViewport({
|
||||||
devices,
|
devices,
|
||||||
location,
|
location,
|
||||||
|
screenshot,
|
||||||
viewport,
|
viewport,
|
||||||
onChangeViewportDevice,
|
onChangeViewportDevice,
|
||||||
onResizeViewport,
|
onResizeViewport,
|
||||||
|
|
|
@ -17,6 +17,7 @@ module.exports = createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
devices: PropTypes.shape(Types.devices).isRequired,
|
devices: PropTypes.shape(Types.devices).isRequired,
|
||||||
location: Types.location.isRequired,
|
location: Types.location.isRequired,
|
||||||
|
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||||
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
|
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
|
||||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||||
onResizeViewport: PropTypes.func.isRequired,
|
onResizeViewport: PropTypes.func.isRequired,
|
||||||
|
@ -27,6 +28,7 @@ module.exports = createClass({
|
||||||
let {
|
let {
|
||||||
devices,
|
devices,
|
||||||
location,
|
location,
|
||||||
|
screenshot,
|
||||||
viewports,
|
viewports,
|
||||||
onChangeViewportDevice,
|
onChangeViewportDevice,
|
||||||
onResizeViewport,
|
onResizeViewport,
|
||||||
|
@ -42,6 +44,7 @@ module.exports = createClass({
|
||||||
key: viewport.id,
|
key: viewport.id,
|
||||||
devices,
|
devices,
|
||||||
location,
|
location,
|
||||||
|
screenshot,
|
||||||
viewport,
|
viewport,
|
||||||
onChangeViewportDevice,
|
onChangeViewportDevice,
|
||||||
onResizeViewport,
|
onResizeViewport,
|
||||||
|
|
|
@ -8,5 +8,6 @@ DevToolsModules(
|
||||||
'close.svg',
|
'close.svg',
|
||||||
'grippers.svg',
|
'grippers.svg',
|
||||||
'rotate-viewport.svg',
|
'rotate-viewport.svg',
|
||||||
|
'screenshot.svg',
|
||||||
'select-arrow.svg',
|
'select-arrow.svg',
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!-- 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="#babec3">
|
||||||
|
<path d="M15.5 14H.5c-.3 0-.5-.2-.5-.5v-8c0-.3.2-.5.5-.5H4V2.5c0-.3.2-.5.5-.5h7c.3 0 .5.2.5.5V5h3.5c.3 0 .5.2.5.5v8c0 .3-.2.5-.5.5zM1 13h14V6h-3.5c-.3 0-.5-.2-.5-.5V3H5v2.5c0 .3-.2.5-.5.5H1v7z"/>
|
||||||
|
<path d="M8 12c-1.6 0-2.9-1.3-2.9-2.9S6.4 6.2 8 6.2c1.6 0 2.9 1.3 2.9 2.9S9.6 12 8 12zm0-4.8c-1.1 0-1.9.8-1.9 1.9 0 1.1.8 1.9 1.9 1.9 1.1 0 1.9-.9 1.9-1.9C9.9 8 9.1 7.2 8 7.2z"/>
|
||||||
|
</svg>
|
После Ширина: | Высота: | Размер: 674 B |
|
@ -91,17 +91,28 @@ body {
|
||||||
margin: 0 0 0 5px;
|
margin: 0 0 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#global-exit-button,
|
#global-toolbar .toolbar-button,
|
||||||
#global-exit-button::before {
|
#global-toolbar .toolbar-button::before {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#global-screenshot-button::before {
|
||||||
|
background-image: url("./images/screenshot.svg");
|
||||||
|
margin: -6px 0 0 -6px;
|
||||||
|
}
|
||||||
|
|
||||||
#global-exit-button::before {
|
#global-exit-button::before {
|
||||||
background-image: url("./images/close.svg");
|
background-image: url("./images/close.svg");
|
||||||
margin: -6px 0 0 -6px;
|
margin: -6px 0 0 -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#global-screenshot-button:disabled {
|
||||||
|
filter: url("chrome://devtools/skin/images/filters.svg#checked-icon-state");
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#viewports {
|
#viewports {
|
||||||
/* Snap to the top of the app when there isn't enough vertical space anymore
|
/* Snap to the top of the app when there isn't enough vertical space anymore
|
||||||
to center the viewports (so we don't loose the toolbar) */
|
to center the viewports (so we don't loose the toolbar) */
|
||||||
|
@ -220,6 +231,10 @@ body {
|
||||||
cursor: se-resize;
|
cursor: se-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.viewport-resize-handle.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.viewport-horizontal-resize-handle {
|
.viewport-horizontal-resize-handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
|
|
|
@ -42,10 +42,8 @@ let bootstrap = {
|
||||||
"agent");
|
"agent");
|
||||||
this.telemetry.toolOpened("responsive");
|
this.telemetry.toolOpened("responsive");
|
||||||
let store = this.store = Store();
|
let store = this.store = Store();
|
||||||
let app = App({
|
let provider = createElement(Provider, { store }, App());
|
||||||
onExit: () => window.postMessage({ type: "exit" }, "*"),
|
|
||||||
});
|
|
||||||
let provider = createElement(Provider, { store }, app);
|
|
||||||
ReactDOM.render(provider, document.querySelector("#root"));
|
ReactDOM.render(provider, document.querySelector("#root"));
|
||||||
this.initDevices();
|
this.initDevices();
|
||||||
window.postMessage({ type: "init" }, "*");
|
window.postMessage({ type: "init" }, "*");
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
|
|
||||||
DIRS += [
|
DIRS += [
|
||||||
'actions',
|
'actions',
|
||||||
|
'audio',
|
||||||
'components',
|
'components',
|
||||||
'images',
|
'images',
|
||||||
'reducers',
|
'reducers',
|
||||||
|
'utils',
|
||||||
]
|
]
|
||||||
|
|
||||||
DevToolsModules(
|
DevToolsModules(
|
||||||
|
|
|
@ -6,4 +6,5 @@
|
||||||
|
|
||||||
exports.devices = require("./reducers/devices");
|
exports.devices = require("./reducers/devices");
|
||||||
exports.location = require("./reducers/location");
|
exports.location = require("./reducers/location");
|
||||||
|
exports.screenshot = require("./reducers/screenshot");
|
||||||
exports.viewports = require("./reducers/viewports");
|
exports.viewports = require("./reducers/viewports");
|
||||||
|
|
|
@ -7,5 +7,6 @@
|
||||||
DevToolsModules(
|
DevToolsModules(
|
||||||
'devices.js',
|
'devices.js',
|
||||||
'location.js',
|
'location.js',
|
||||||
|
'screenshot.js',
|
||||||
'viewports.js',
|
'viewports.js',
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {
|
||||||
|
TAKE_SCREENSHOT_END,
|
||||||
|
TAKE_SCREENSHOT_START,
|
||||||
|
} = require("../actions/index");
|
||||||
|
|
||||||
|
const INITIAL_SCREENSHOT = { isCapturing: false };
|
||||||
|
|
||||||
|
let reducers = {
|
||||||
|
|
||||||
|
[TAKE_SCREENSHOT_END](screenshot, action) {
|
||||||
|
return Object.assign({}, screenshot, { isCapturing: false });
|
||||||
|
},
|
||||||
|
|
||||||
|
[TAKE_SCREENSHOT_START](screenshot, action) {
|
||||||
|
return Object.assign({}, screenshot, { isCapturing: true });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function(screenshot = INITIAL_SCREENSHOT, action) {
|
||||||
|
let reducer = reducers[action.type];
|
||||||
|
if (!reducer) {
|
||||||
|
return screenshot;
|
||||||
|
}
|
||||||
|
return reducer(screenshot, action);
|
||||||
|
};
|
|
@ -8,4 +8,5 @@ support-files =
|
||||||
!/devtools/client/framework/test/shared-redux-head.js
|
!/devtools/client/framework/test/shared-redux-head.js
|
||||||
|
|
||||||
[browser_exit_button.js]
|
[browser_exit_button.js]
|
||||||
|
[browser_screenshot_button.js]
|
||||||
[browser_viewport_basics.js]
|
[browser_viewport_basics.js]
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Test global exit button
|
||||||
|
|
||||||
|
const TEST_URL = "data:text/html;charset=utf-8,";
|
||||||
|
|
||||||
|
const { OS } = require("resource://gre/modules/osfile.jsm");
|
||||||
|
|
||||||
|
function* waitUntilScreenshot() {
|
||||||
|
return new Promise(Task.async(function* (resolve) {
|
||||||
|
let { Downloads } = require("resource://gre/modules/Downloads.jsm");
|
||||||
|
let list = yield Downloads.getList(Downloads.ALL);
|
||||||
|
|
||||||
|
let view = {
|
||||||
|
onDownloadAdded: download => {
|
||||||
|
download.whenSucceeded().then(() => {
|
||||||
|
resolve(download.target.path);
|
||||||
|
list.removeView(view);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
yield list.addView(view);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
addRDMTask(TEST_URL, function* ({ ui: {toolWindow} }) {
|
||||||
|
let { store, document } = toolWindow;
|
||||||
|
|
||||||
|
// Wait until the viewport has been added
|
||||||
|
yield waitUntilState(store, state => state.viewports.length == 1);
|
||||||
|
|
||||||
|
info("Click the screenshot button");
|
||||||
|
let screenshotButton = document.getElementById("global-screenshot-button");
|
||||||
|
screenshotButton.click();
|
||||||
|
|
||||||
|
let whenScreenshotSucceeded = waitUntilScreenshot();
|
||||||
|
|
||||||
|
let filePath = yield whenScreenshotSucceeded;
|
||||||
|
let image = new Image();
|
||||||
|
image.src = OS.Path.toFileURI(filePath);
|
||||||
|
|
||||||
|
yield once(image, "load");
|
||||||
|
|
||||||
|
// We have only one viewport at the moment
|
||||||
|
let viewport = store.getState().viewports[0];
|
||||||
|
let ratio = window.devicePixelRatio;
|
||||||
|
|
||||||
|
is(image.width, viewport.width * ratio,
|
||||||
|
"screenshot width has the expected width");
|
||||||
|
|
||||||
|
is(image.height, viewport.height * ratio,
|
||||||
|
"screenshot width has the expected height");
|
||||||
|
|
||||||
|
yield OS.File.remove(filePath);
|
||||||
|
});
|
|
@ -70,6 +70,15 @@ exports.devices = {
|
||||||
*/
|
*/
|
||||||
exports.location = PropTypes.string;
|
exports.location = PropTypes.string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The progression of the screenshot
|
||||||
|
*/
|
||||||
|
exports.screenshot = {
|
||||||
|
|
||||||
|
isCapturing: PropTypes.bool.isRequired,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single viewport displaying a document.
|
* A single viewport displaying a document.
|
||||||
*/
|
*/
|
||||||
|
|
Загрузка…
Ссылка в новой задаче