diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/client/responsive/actions/devices.js | 254 | ||||
-rw-r--r-- | devtools/client/responsive/actions/index.js | 109 | ||||
-rw-r--r-- | devtools/client/responsive/actions/moz.build | 13 | ||||
-rw-r--r-- | devtools/client/responsive/actions/screenshot.js | 36 | ||||
-rw-r--r-- | devtools/client/responsive/actions/ui.js | 71 | ||||
-rw-r--r-- | devtools/client/responsive/actions/viewports.js | 128 |
6 files changed, 611 insertions, 0 deletions
diff --git a/devtools/client/responsive/actions/devices.js b/devtools/client/responsive/actions/devices.js new file mode 100644 index 0000000000..b3f2bab982 --- /dev/null +++ b/devtools/client/responsive/actions/devices.js @@ -0,0 +1,254 @@ +/* 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 asyncStorage = require("resource://devtools/shared/async-storage.js"); + +const { + ADD_DEVICE, + ADD_DEVICE_TYPE, + EDIT_DEVICE, + LOAD_DEVICE_LIST_START, + LOAD_DEVICE_LIST_ERROR, + LOAD_DEVICE_LIST_END, + REMOVE_DEVICE, + UPDATE_DEVICE_DISPLAYED, + UPDATE_DEVICE_MODAL, +} = require("resource://devtools/client/responsive/actions/index.js"); +const { + post, +} = require("resource://devtools/client/responsive/utils/message.js"); + +const { + addDevice, + editDevice, + getDevices, + removeDevice, +} = require("resource://devtools/client/shared/devices.js"); +const { + changeUserAgent, + toggleTouchSimulation, +} = require("resource://devtools/client/responsive/actions/ui.js"); +const { + changeDevice, + changePixelRatio, + changeViewportAngle, +} = require("resource://devtools/client/responsive/actions/viewports.js"); + +const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList"; + +/** + * Returns an object containing the user preference of displayed devices. + * + * @return {Object} containing two Sets: + * - added: Names of the devices that were explicitly enabled by the user + * - removed: Names of the devices that were explicitly removed by the user + */ +function loadPreferredDevices() { + const preferredDevices = { + added: new Set(), + removed: new Set(), + }; + + if (Services.prefs.prefHasUserValue(DISPLAYED_DEVICES_PREF)) { + try { + let savedData = Services.prefs.getStringPref(DISPLAYED_DEVICES_PREF); + savedData = JSON.parse(savedData); + if (savedData.added && savedData.removed) { + preferredDevices.added = new Set(savedData.added); + preferredDevices.removed = new Set(savedData.removed); + } + } catch (e) { + console.error(e); + } + } + + return preferredDevices; +} + +/** + * Update the displayed device list preference with the given device list. + * + * @param {Object} containing two Sets: + * - added: Names of the devices that were explicitly enabled by the user + * - removed: Names of the devices that were explicitly removed by the user + */ +function updatePreferredDevices(devices) { + let devicesToSave = { + added: Array.from(devices.added), + removed: Array.from(devices.removed), + }; + devicesToSave = JSON.stringify(devicesToSave); + Services.prefs.setStringPref(DISPLAYED_DEVICES_PREF, devicesToSave); +} + +module.exports = { + // This function is only exported for testing purposes + _loadPreferredDevices: loadPreferredDevices, + + updatePreferredDevices, + + addCustomDevice(device) { + return async function ({ dispatch }) { + // Add custom device to device storage + await addDevice(device, "custom"); + dispatch({ + type: ADD_DEVICE, + device, + deviceType: "custom", + }); + }; + }, + + addDevice(device, deviceType) { + return { + type: ADD_DEVICE, + device, + deviceType, + }; + }, + + addDeviceType(deviceType) { + return { + type: ADD_DEVICE_TYPE, + deviceType, + }; + }, + + editCustomDevice(viewport, oldDevice, newDevice) { + return async function ({ dispatch }) { + // Edit custom device in storage + await editDevice(oldDevice, newDevice, "custom"); + // Notify the window that the device should be updated in the device selector. + post(window, { + type: "change-device", + device: newDevice, + viewport, + }); + + // Update UI if the device is selected. + if (viewport) { + dispatch(changeUserAgent(newDevice.userAgent)); + dispatch(toggleTouchSimulation(newDevice.touch)); + } + + dispatch({ + type: EDIT_DEVICE, + deviceType: "custom", + viewport, + oldDevice, + newDevice, + }); + }; + }, + + removeCustomDevice(device) { + return async function ({ dispatch }) { + // Remove custom device from device storage + await removeDevice(device, "custom"); + dispatch({ + type: REMOVE_DEVICE, + device, + deviceType: "custom", + }); + }; + }, + + updateDeviceDisplayed(device, deviceType, displayed) { + return { + type: UPDATE_DEVICE_DISPLAYED, + device, + deviceType, + displayed, + }; + }, + + loadDevices() { + return async function ({ dispatch }) { + dispatch({ type: LOAD_DEVICE_LIST_START }); + const preferredDevices = loadPreferredDevices(); + let deviceByTypes; + + try { + deviceByTypes = await getDevices(); + } catch (e) { + console.error("Could not load device list: " + e); + dispatch({ type: LOAD_DEVICE_LIST_ERROR }); + return; + } + + for (const [type, devices] of deviceByTypes.entries()) { + dispatch(module.exports.addDeviceType(type)); + for (const device of devices) { + if (device.os == "fxos") { + continue; + } + + const newDevice = Object.assign({}, device, { + displayed: + preferredDevices.added.has(device.name) || + (device.featured && !preferredDevices.removed.has(device.name)), + }); + + dispatch(module.exports.addDevice(newDevice, type)); + } + } + + // Add an empty "custom" type if it doesn't exist in device storage + if (!deviceByTypes.has("custom")) { + dispatch(module.exports.addDeviceType("custom")); + } + + dispatch({ type: LOAD_DEVICE_LIST_END }); + }; + }, + + restoreDeviceState() { + return async function ({ dispatch, getState }) { + const deviceState = await asyncStorage.getItem( + "devtools.responsive.deviceState" + ); + if (!deviceState) { + return; + } + + const { id, device: deviceName, deviceType } = deviceState; + const devices = getState().devices; + + if (!devices.types.includes(deviceType)) { + // Can't find matching device type. + return; + } + + const device = devices[deviceType].find(d => d.name === deviceName); + if (!device) { + // Can't find device with the same device name. + return; + } + + const viewport = getState().viewports[0]; + + post(window, { + type: "change-device", + device, + viewport, + }); + + dispatch(changeDevice(id, device.name, deviceType)); + dispatch(changeViewportAngle(id, viewport.angle)); + dispatch(changePixelRatio(id, device.pixelRatio)); + dispatch(changeUserAgent(device.userAgent)); + dispatch(toggleTouchSimulation(device.touch)); + }; + }, + + updateDeviceModal(isOpen, modalOpenedFromViewport = null) { + return { + type: UPDATE_DEVICE_MODAL, + isOpen, + modalOpenedFromViewport, + }; + }, +}; diff --git a/devtools/client/responsive/actions/index.js b/devtools/client/responsive/actions/index.js new file mode 100644 index 0000000000..7d42c06053 --- /dev/null +++ b/devtools/client/responsive/actions/index.js @@ -0,0 +1,109 @@ +/* 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"; + +// This file lists all of the actions available in responsive design. This +// central list of constants makes it easy to see all possible action names at +// a glance. Please add a comment with each new action type. + +const { createEnum } = require("resource://devtools/client/shared/enum.js"); + +const { + CHANGE_NETWORK_THROTTLING, +} = require("resource://devtools/client/shared/components/throttling/actions.js"); + +createEnum( + [ + // Add a new device. + "ADD_DEVICE", + + // Add a new device type. + "ADD_DEVICE_TYPE", + + // Add an additional viewport to display the document. + "ADD_VIEWPORT", + + // Change the device displayed in the viewport. + "CHANGE_DEVICE", + + // Change the location of the page. This may be triggered by the user + // directly entering a new URL, navigating with links, etc. + "CHANGE_LOCATION", + + // The pixel ratio of the display has changed. This may be triggered by the user + // when changing the monitor resolution, or when the window is dragged to a different + // display with a different pixel ratio. + "CHANGE_DISPLAY_PIXEL_RATIO", + + // Change the network throttling profile. + CHANGE_NETWORK_THROTTLING, + + // Change the user agent of the viewport. + "CHANGE_USER_AGENT", + + // The pixel ratio of the viewport has changed. This may be triggered by the user + // when changing the device displayed in the viewport, or when a pixel ratio is + // selected from the device pixel ratio dropdown. + "CHANGE_PIXEL_RATIO", + + // Change the viewport angle. + "CHANGE_VIEWPORT_ANGLE", + + // Edit a device. + "EDIT_DEVICE", + + // Indicates that the device list is being loaded. + "LOAD_DEVICE_LIST_START", + + // Indicates that the device list loading action threw an error. + "LOAD_DEVICE_LIST_ERROR", + + // Indicates that the device list has been loaded successfully. + "LOAD_DEVICE_LIST_END", + + // Remove a device. + "REMOVE_DEVICE", + + // Remove the viewport's device assocation. + "REMOVE_DEVICE_ASSOCIATION", + + // Resize the viewport. + "RESIZE_VIEWPORT", + + // Rotate the viewport. + "ROTATE_VIEWPORT", + + // Take a screenshot of the viewport. + "TAKE_SCREENSHOT_START", + + // Indicates when the screenshot action ends. + "TAKE_SCREENSHOT_END", + + // Toggles the left alignment of the viewports. + "TOGGLE_LEFT_ALIGNMENT", + + // Toggles the reload on touch simulation changes. + "TOGGLE_RELOAD_ON_TOUCH_SIMULATION", + + // Toggles the reload on user agent changes. + "TOGGLE_RELOAD_ON_USER_AGENT", + + // Toggles the touch simulation state of the viewports. + "TOGGLE_TOUCH_SIMULATION", + + // Toggles the user agent input displayed in the toolbar. + "TOGGLE_USER_AGENT_INPUT", + + // Update the device display state in the device selector. + "UPDATE_DEVICE_DISPLAYED", + + // Update the device modal state. + "UPDATE_DEVICE_MODAL", + + // Zoom the viewport. + "ZOOM_VIEWPORT", + ], + module.exports +); diff --git a/devtools/client/responsive/actions/moz.build b/devtools/client/responsive/actions/moz.build new file mode 100644 index 0000000000..d4fa0d243f --- /dev/null +++ b/devtools/client/responsive/actions/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; 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( + "devices.js", + "index.js", + "screenshot.js", + "ui.js", + "viewports.js", +) diff --git a/devtools/client/responsive/actions/screenshot.js b/devtools/client/responsive/actions/screenshot.js new file mode 100644 index 0000000000..c429b41060 --- /dev/null +++ b/devtools/client/responsive/actions/screenshot.js @@ -0,0 +1,36 @@ +/* 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 { + TAKE_SCREENSHOT_START, + TAKE_SCREENSHOT_END, +} = require("resource://devtools/client/responsive/actions/index.js"); + +const message = require("resource://devtools/client/responsive/utils/message.js"); + +const animationFrame = () => + new Promise(resolve => { + window.requestAnimationFrame(resolve); + }); + +module.exports = { + takeScreenshot() { + return async function ({ dispatch }) { + await dispatch({ type: TAKE_SCREENSHOT_START }); + + // Waiting the next repaint, to ensure the react components + // can be properly render after the action dispatched above + await animationFrame(); + + window.postMessage({ type: "screenshot" }, "*"); + await message.wait(window, "screenshot-captured"); + + dispatch({ type: TAKE_SCREENSHOT_END }); + }; + }, +}; diff --git a/devtools/client/responsive/actions/ui.js b/devtools/client/responsive/actions/ui.js new file mode 100644 index 0000000000..b2f05cb7c9 --- /dev/null +++ b/devtools/client/responsive/actions/ui.js @@ -0,0 +1,71 @@ +/* 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 { + CHANGE_DISPLAY_PIXEL_RATIO, + CHANGE_USER_AGENT, + TOGGLE_LEFT_ALIGNMENT, + TOGGLE_RELOAD_ON_TOUCH_SIMULATION, + TOGGLE_RELOAD_ON_USER_AGENT, + TOGGLE_TOUCH_SIMULATION, + TOGGLE_USER_AGENT_INPUT, +} = require("resource://devtools/client/responsive/actions/index.js"); + +module.exports = { + /** + * The pixel ratio of the display has changed. This may be triggered by the user + * when changing the monitor resolution, or when the window is dragged to a different + * display with a different pixel ratio. + */ + changeDisplayPixelRatio(displayPixelRatio) { + return { + type: CHANGE_DISPLAY_PIXEL_RATIO, + displayPixelRatio, + }; + }, + + changeUserAgent(userAgent) { + return { + type: CHANGE_USER_AGENT, + userAgent, + }; + }, + + toggleLeftAlignment(enabled) { + return { + type: TOGGLE_LEFT_ALIGNMENT, + enabled, + }; + }, + + toggleReloadOnTouchSimulation(enabled) { + return { + type: TOGGLE_RELOAD_ON_TOUCH_SIMULATION, + enabled, + }; + }, + + toggleReloadOnUserAgent(enabled) { + return { + type: TOGGLE_RELOAD_ON_USER_AGENT, + enabled, + }; + }, + + toggleTouchSimulation(enabled) { + return { + type: TOGGLE_TOUCH_SIMULATION, + enabled, + }; + }, + + toggleUserAgentInput(enabled) { + return { + type: TOGGLE_USER_AGENT_INPUT, + enabled, + }; + }, +}; diff --git a/devtools/client/responsive/actions/viewports.js b/devtools/client/responsive/actions/viewports.js new file mode 100644 index 0000000000..f8fec1a8c5 --- /dev/null +++ b/devtools/client/responsive/actions/viewports.js @@ -0,0 +1,128 @@ +/* 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 asyncStorage = require("resource://devtools/shared/async-storage.js"); + +const { + ADD_VIEWPORT, + CHANGE_DEVICE, + CHANGE_PIXEL_RATIO, + CHANGE_VIEWPORT_ANGLE, + REMOVE_DEVICE_ASSOCIATION, + RESIZE_VIEWPORT, + ROTATE_VIEWPORT, + ZOOM_VIEWPORT, +} = require("resource://devtools/client/responsive/actions/index.js"); + +const { + post, +} = require("resource://devtools/client/responsive/utils/message.js"); + +module.exports = { + /** + * Add an additional viewport to display the document. + */ + addViewport(userContextId = 0) { + return { + type: ADD_VIEWPORT, + userContextId, + }; + }, + + /** + * Change the viewport device. + */ + changeDevice(id, device, deviceType) { + return async function ({ dispatch }) { + dispatch({ + type: CHANGE_DEVICE, + id, + device, + deviceType, + }); + + try { + await asyncStorage.setItem("devtools.responsive.deviceState", { + id, + device, + deviceType, + }); + } catch (e) { + console.error(e); + } + }; + }, + + /** + * Change the viewport pixel ratio. + */ + changePixelRatio(id, pixelRatio = 0) { + return { + type: CHANGE_PIXEL_RATIO, + id, + pixelRatio, + }; + }, + + changeViewportAngle(id, angle) { + return { + type: CHANGE_VIEWPORT_ANGLE, + id, + angle, + }; + }, + + /** + * Remove the viewport's device assocation. + */ + removeDeviceAssociation(id) { + return async function ({ dispatch }) { + post(window, "remove-device-association"); + + dispatch({ + type: REMOVE_DEVICE_ASSOCIATION, + id, + }); + + await asyncStorage.removeItem("devtools.responsive.deviceState"); + }; + }, + + /** + * Resize the viewport. + */ + resizeViewport(id, width, height) { + return { + type: RESIZE_VIEWPORT, + id, + width, + height, + }; + }, + + /** + * Rotate the viewport. + */ + rotateViewport(id) { + return { + type: ROTATE_VIEWPORT, + id, + }; + }, + + /** + * Zoom the viewport. + */ + zoomViewport(id, zoom) { + return { + type: ZOOM_VIEWPORT, + id, + zoom, + }; + }, +}; |