зеркало из 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
|
||||
# device selector
|
||||
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_VIEWPORT",
|
||||
|
||||
// Take a screenshot of the viewport.
|
||||
"TAKE_SCREENSHOT_START",
|
||||
|
||||
// Indicates when the screenshot action ends.
|
||||
"TAKE_SCREENSHOT_END",
|
||||
|
||||
], module.exports);
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,5 +8,6 @@ DevToolsModules(
|
|||
'devices.js',
|
||||
'index.js',
|
||||
'location.js',
|
||||
'screenshot.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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { createClass, createFactory, PropTypes, DOM: dom } =
|
||||
|
@ -13,6 +15,7 @@ const {
|
|||
resizeViewport,
|
||||
rotateViewport
|
||||
} = require("./actions/viewports");
|
||||
const { takeScreenshot } = require("./actions/screenshot");
|
||||
const Types = require("./types");
|
||||
const Viewports = createFactory(require("./components/viewports"));
|
||||
const GlobalToolbar = createFactory(require("./components/global-toolbar"));
|
||||
|
@ -25,13 +28,17 @@ let App = createClass({
|
|||
devices: PropTypes.shape(Types.devices).isRequired,
|
||||
location: Types.location.isRequired,
|
||||
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
|
||||
onExit: PropTypes.func.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
},
|
||||
|
||||
onChangeViewportDevice(id, device) {
|
||||
this.props.dispatch(changeDevice(id, device));
|
||||
},
|
||||
|
||||
onExit() {
|
||||
window.postMessage({ type: "exit" }, "*");
|
||||
},
|
||||
|
||||
onResizeViewport(id, width, height) {
|
||||
this.props.dispatch(resizeViewport(id, width, height));
|
||||
},
|
||||
|
@ -40,18 +47,24 @@ let App = createClass({
|
|||
this.props.dispatch(rotateViewport(id));
|
||||
},
|
||||
|
||||
onScreenshot() {
|
||||
this.props.dispatch(takeScreenshot());
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
devices,
|
||||
location,
|
||||
screenshot,
|
||||
viewports,
|
||||
onExit,
|
||||
} = this.props;
|
||||
|
||||
let {
|
||||
onChangeViewportDevice,
|
||||
onExit,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onScreenshot,
|
||||
} = this;
|
||||
|
||||
return dom.div(
|
||||
|
@ -59,11 +72,14 @@ let App = createClass({
|
|||
id: "app",
|
||||
},
|
||||
GlobalToolbar({
|
||||
screenshot,
|
||||
onExit,
|
||||
onScreenshot,
|
||||
}),
|
||||
Viewports({
|
||||
devices,
|
||||
location,
|
||||
screenshot,
|
||||
viewports,
|
||||
onChangeViewportDevice,
|
||||
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";
|
||||
|
||||
const { getStr } = require("./utils/l10n");
|
||||
const { getStr } = require("../utils/l10n");
|
||||
const { DOM: dom, createClass, PropTypes, addons } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
|
|
|
@ -4,23 +4,28 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { getStr } = require("./utils/l10n");
|
||||
const { getStr } = require("../utils/l10n");
|
||||
const { DOM: dom, createClass, PropTypes, addons } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Types = require("../types");
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "GlobalToolbar",
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
propTypes: {
|
||||
onExit: PropTypes.func.isRequired,
|
||||
onScreenshot: PropTypes.func.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
render() {
|
||||
let {
|
||||
onExit,
|
||||
onScreenshot,
|
||||
screenshot,
|
||||
} = this.props;
|
||||
|
||||
return dom.header(
|
||||
|
@ -33,6 +38,13 @@ module.exports = createClass({
|
|||
className: "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({
|
||||
id: "global-exit-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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += [
|
||||
'utils',
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'browser.js',
|
||||
'device-selector.js',
|
||||
|
|
|
@ -24,6 +24,7 @@ module.exports = createClass({
|
|||
propTypes: {
|
||||
devices: PropTypes.shape(Types.devices).isRequired,
|
||||
location: Types.location.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
|
@ -112,12 +113,19 @@ module.exports = createClass({
|
|||
let {
|
||||
devices,
|
||||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onChangeViewportDevice,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
} = this.props;
|
||||
|
||||
let resizeHandleClass = "viewport-resize-handle";
|
||||
|
||||
if (screenshot.isCapturing) {
|
||||
resizeHandleClass += " hidden";
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "resizable-viewport",
|
||||
|
@ -136,7 +144,7 @@ module.exports = createClass({
|
|||
isResizing: this.state.isResizing
|
||||
}),
|
||||
dom.div({
|
||||
className: "viewport-resize-handle",
|
||||
className: resizeHandleClass,
|
||||
onMouseDown: this.onResizeStart,
|
||||
}),
|
||||
dom.div({
|
||||
|
|
|
@ -18,6 +18,7 @@ module.exports = createClass({
|
|||
propTypes: {
|
||||
devices: PropTypes.shape(Types.devices).isRequired,
|
||||
location: Types.location.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
|
@ -55,6 +56,7 @@ module.exports = createClass({
|
|||
let {
|
||||
devices,
|
||||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
} = this.props;
|
||||
|
||||
|
@ -71,6 +73,7 @@ module.exports = createClass({
|
|||
ResizableViewport({
|
||||
devices,
|
||||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onChangeViewportDevice,
|
||||
onResizeViewport,
|
||||
|
|
|
@ -17,6 +17,7 @@ module.exports = createClass({
|
|||
propTypes: {
|
||||
devices: PropTypes.shape(Types.devices).isRequired,
|
||||
location: Types.location.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
|
@ -27,6 +28,7 @@ module.exports = createClass({
|
|||
let {
|
||||
devices,
|
||||
location,
|
||||
screenshot,
|
||||
viewports,
|
||||
onChangeViewportDevice,
|
||||
onResizeViewport,
|
||||
|
@ -42,6 +44,7 @@ module.exports = createClass({
|
|||
key: viewport.id,
|
||||
devices,
|
||||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onChangeViewportDevice,
|
||||
onResizeViewport,
|
||||
|
|
|
@ -8,5 +8,6 @@ DevToolsModules(
|
|||
'close.svg',
|
||||
'grippers.svg',
|
||||
'rotate-viewport.svg',
|
||||
'screenshot.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;
|
||||
}
|
||||
|
||||
#global-exit-button,
|
||||
#global-exit-button::before {
|
||||
#global-toolbar .toolbar-button,
|
||||
#global-toolbar .toolbar-button::before {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
#global-screenshot-button::before {
|
||||
background-image: url("./images/screenshot.svg");
|
||||
margin: -6px 0 0 -6px;
|
||||
}
|
||||
|
||||
#global-exit-button::before {
|
||||
background-image: url("./images/close.svg");
|
||||
margin: -6px 0 0 -6px;
|
||||
}
|
||||
|
||||
#global-screenshot-button:disabled {
|
||||
filter: url("chrome://devtools/skin/images/filters.svg#checked-icon-state");
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
|
||||
#viewports {
|
||||
/* 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) */
|
||||
|
@ -220,6 +231,10 @@ body {
|
|||
cursor: se-resize;
|
||||
}
|
||||
|
||||
.viewport-resize-handle.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.viewport-horizontal-resize-handle {
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
|
|
|
@ -42,10 +42,8 @@ let bootstrap = {
|
|||
"agent");
|
||||
this.telemetry.toolOpened("responsive");
|
||||
let store = this.store = Store();
|
||||
let app = App({
|
||||
onExit: () => window.postMessage({ type: "exit" }, "*"),
|
||||
});
|
||||
let provider = createElement(Provider, { store }, app);
|
||||
let provider = createElement(Provider, { store }, App());
|
||||
|
||||
ReactDOM.render(provider, document.querySelector("#root"));
|
||||
this.initDevices();
|
||||
window.postMessage({ type: "init" }, "*");
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
|
||||
DIRS += [
|
||||
'actions',
|
||||
'audio',
|
||||
'components',
|
||||
'images',
|
||||
'reducers',
|
||||
'utils',
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
|
||||
exports.devices = require("./reducers/devices");
|
||||
exports.location = require("./reducers/location");
|
||||
exports.screenshot = require("./reducers/screenshot");
|
||||
exports.viewports = require("./reducers/viewports");
|
||||
|
|
|
@ -7,5 +7,6 @@
|
|||
DevToolsModules(
|
||||
'devices.js',
|
||||
'location.js',
|
||||
'screenshot.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
|
||||
|
||||
[browser_exit_button.js]
|
||||
[browser_screenshot_button.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;
|
||||
|
||||
/**
|
||||
* The progression of the screenshot
|
||||
*/
|
||||
exports.screenshot = {
|
||||
|
||||
isCapturing: PropTypes.bool.isRequired,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A single viewport displaying a document.
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче