--- a/devtools/client/locales/en-US/responsive.properties
+++ b/devtools/client/locales/en-US/responsive.properties
@@ -77,8 +77,53 @@ responsive.noThrottling=No throttling
# DevicePixelRatio (DPR) dropdown when is enabled.
responsive.devicePixelRatio=Device Pixel Ratio
# LOCALIZATION NOTE (responsive.autoDPR): tooltip for the DevicePixelRatio
# (DPR) dropdown when is disabled because a device is selected.
# The argument (%1$S) is the selected device (e.g. iPhone 6) that set
# automatically the DPR value.
responsive.autoDPR=DPR automatically set by %1$S
+
+# LOCALIZATION NOTE (responsive.customDeviceName): Default value in a form to
+# add a custom device based on an arbitrary size (no association to an existing
+# device).
+responsive.customDeviceName=Custom Device
+
+# LOCALIZATION NOTE (responsive.customDeviceNameFromBase): Default value in a
+# form to add a custom device based on the properties of another. %1$S is the
+# name of the device we're staring from, such as "Apple iPhone 6".
+responsive.customDeviceNameFromBase=%1$S (Custom)
+
+# LOCALIZATION NOTE (responsive.addDevice): Button text that reveals a form to
+# be used for adding custom devices.
+responsive.addDevice=Add Device
+
+# LOCALIZATION NOTE (responsive.deviceAdderName): Label of form field for the
+# name of a new device. The available width is very low, so you might see
+# overlapping text if the length is much longer than 5 or so characters.
+responsive.deviceAdderName=Name
+
+# LOCALIZATION NOTE (responsive.deviceAdderSize): Label of form field for the
+# size of a new device. The available width is very low, so you might see
+# overlapping text if the length is much longer than 5 or so characters.
+responsive.deviceAdderSize=Size
+
+# LOCALIZATION NOTE (responsive.deviceAdderPixelRatio): Label of form field for
+# the devicePixelRatio of a new device. The available width is very low, so you
+# might see overlapping text if the length is much longer than 5 or so
+# characters.
+responsive.deviceAdderPixelRatio=DPR
+
+# LOCALIZATION NOTE (responsive.deviceAdderUserAgent): Label of form field for
+# the user agent of a new device. The available width is very low, so you might
+# see overlapping text if the length is much longer than 5 or so characters.
+responsive.deviceAdderUserAgent=UA
+
+# LOCALIZATION NOTE (responsive.deviceAdderTouch): Label of form field for the
+# touch input support of a new device. The available width is very low, so you
+# might see overlapping text if the length is much longer than 5 or so
+# characters.
+responsive.deviceAdderTouch=Touch
+
+# LOCALIZATION NOTE (responsive.deviceAdderSave): Button text that submits a
+# form to add a new device.
+responsive.deviceAdderSave=Save
--- a/devtools/client/responsive.html/actions/devices.js
+++ b/devtools/client/responsive.html/actions/devices.js
@@ -6,17 +6,17 @@
const {
ADD_DEVICE,
ADD_DEVICE_TYPE,
LOAD_DEVICE_LIST_START,
LOAD_DEVICE_LIST_ERROR,
LOAD_DEVICE_LIST_END,
UPDATE_DEVICE_DISPLAYED,
- UPDATE_DEVICE_MODAL_OPEN,
+ UPDATE_DEVICE_MODAL,
} = require("./index");
const { getDevices } = require("devtools/client/shared/devices");
const Services = require("Services");
const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
/**
@@ -92,17 +92,17 @@ module.exports = {
device,
deviceType,
displayed,
};
},
loadDevices() {
return function* (dispatch, getState) {
- yield dispatch({ type: LOAD_DEVICE_LIST_START });
+ dispatch({ type: LOAD_DEVICE_LIST_START });
let preferredDevices = loadPreferredDevices();
let devices;
try {
devices = yield getDevices();
} catch (e) {
console.error("Could not load device list: " + e);
dispatch({ type: LOAD_DEVICE_LIST_ERROR });
@@ -119,20 +119,24 @@ module.exports = {
let newDevice = Object.assign({}, device, {
displayed: preferredDevices.added.has(device.name) ||
(device.featured && !(preferredDevices.removed.has(device.name))),
});
dispatch(module.exports.addDevice(newDevice, type));
}
}
+
+ dispatch(module.exports.addDeviceType("custom"));
+
dispatch({ type: LOAD_DEVICE_LIST_END });
};
},
- updateDeviceModalOpen(isOpen) {
+ updateDeviceModal(isOpen, modalOpenedFromViewport = null) {
return {
- type: UPDATE_DEVICE_MODAL_OPEN,
+ type: UPDATE_DEVICE_MODAL,
isOpen,
+ modalOpenedFromViewport,
};
},
};
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -66,12 +66,12 @@ createEnum([
"TAKE_SCREENSHOT_START",
// Indicates when the screenshot action ends.
"TAKE_SCREENSHOT_END",
// Update the device display state in the device selector.
"UPDATE_DEVICE_DISPLAYED",
- // Update the device modal open state.
- "UPDATE_DEVICE_MODAL_OPEN",
+ // Update the device modal state.
+ "UPDATE_DEVICE_MODAL",
], module.exports);
--- a/devtools/client/responsive.html/actions/viewports.js
+++ b/devtools/client/responsive.html/actions/viewports.js
@@ -22,21 +22,22 @@ module.exports = {
return {
type: ADD_VIEWPORT,
};
},
/**
* Change the viewport device.
*/
- changeDevice(id, device) {
+ changeDevice(id, device, deviceType) {
return {
type: CHANGE_DEVICE,
id,
device,
+ deviceType,
};
},
/**
* Change the viewport pixel ratio.
*/
changePixelRatio(id, pixelRatio = 0) {
return {
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -7,17 +7,17 @@
"use strict";
const { createClass, createFactory, PropTypes, DOM: dom } =
require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const {
updateDeviceDisplayed,
- updateDeviceModalOpen,
+ updateDeviceModal,
updatePreferredDevices,
} = require("./actions/devices");
const { changeNetworkThrottling } = require("./actions/network-throttling");
const { takeScreenshot } = require("./actions/screenshot");
const { changeTouchSimulation } = require("./actions/touch-simulation");
const {
changeDevice,
changePixelRatio,
@@ -43,22 +43,22 @@ let App = createClass({
touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
},
onBrowserMounted() {
window.postMessage({ type: "browser-mounted" }, "*");
},
- onChangeDevice(id, device) {
+ onChangeDevice(id, device, deviceType) {
window.postMessage({
type: "change-device",
device,
}, "*");
- this.props.dispatch(changeDevice(id, device.name));
+ this.props.dispatch(changeDevice(id, device.name, deviceType));
this.props.dispatch(changeTouchSimulation(device.touch));
this.props.dispatch(changePixelRatio(id, device.pixelRatio));
},
onChangeNetworkThrottling(enabled, profile) {
window.postMessage({
type: "change-network-throtting",
enabled,
@@ -120,18 +120,18 @@ let App = createClass({
onScreenshot() {
this.props.dispatch(takeScreenshot());
},
onUpdateDeviceDisplayed(device, deviceType, displayed) {
this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
},
- onUpdateDeviceModalOpen(isOpen) {
- this.props.dispatch(updateDeviceModalOpen(isOpen));
+ onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
+ this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
},
render() {
let {
devices,
displayPixelRatio,
location,
networkThrottling,
@@ -149,27 +149,32 @@ let App = createClass({
onContentResize,
onDeviceListUpdate,
onExit,
onRemoveDevice,
onResizeViewport,
onRotateViewport,
onScreenshot,
onUpdateDeviceDisplayed,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
} = this;
let selectedDevice = "";
let selectedPixelRatio = { value: 0 };
if (viewports.length) {
selectedDevice = viewports[0].device;
selectedPixelRatio = viewports[0].pixelRatio;
}
+ let deviceAdderViewportTemplate = {};
+ if (devices.modalOpenedFromViewport !== null) {
+ deviceAdderViewportTemplate = viewports[devices.modalOpenedFromViewport];
+ }
+
return dom.div(
{
id: "app",
},
GlobalToolbar({
devices,
displayPixelRatio,
networkThrottling,
@@ -189,22 +194,23 @@ let App = createClass({
screenshot,
viewports,
onBrowserMounted,
onChangeDevice,
onContentResize,
onRemoveDevice,
onRotateViewport,
onResizeViewport,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
}),
DeviceModal({
+ deviceAdderViewportTemplate,
devices,
onDeviceListUpdate,
onUpdateDeviceDisplayed,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
})
);
},
});
module.exports = connect(state => state)(App);
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/device-adder.js
@@ -0,0 +1,170 @@
+/* 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 { DOM: dom, createClass, createFactory, PropTypes, addons } =
+ require("devtools/client/shared/vendor/react");
+
+const { getFormatStr, getStr } = require("../utils/l10n");
+const Types = require("../types");
+const ViewportDimension = createFactory(require("./viewport-dimension"));
+
+module.exports = createClass({
+ displayName: "DeviceAdder",
+
+ propTypes: {
+ devices: PropTypes.shape(Types.devices).isRequired,
+ viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
+ },
+
+ mixins: [ addons.PureRenderMixin ],
+
+ getInitialState() {
+ return {};
+ },
+
+ onDeviceAdderShow() {
+ this.setState({
+ deviceAdderDisplayed: true,
+ });
+ },
+
+ onDeviceAdderSave() {
+ this.setState({
+ deviceAdderDisplayed: false,
+ });
+ },
+
+ render() {
+ let {
+ devices,
+ viewportTemplate,
+ } = this.props;
+
+ let {
+ deviceAdderDisplayed,
+ } = this.state;
+
+ if (!deviceAdderDisplayed) {
+ return dom.div(
+ {
+ id: "device-adder"
+ },
+ dom.button(
+ {
+ id: "device-adder-show",
+ onClick: this.onDeviceAdderShow,
+ },
+ getStr("responsive.addDevice")
+ )
+ );
+ }
+
+ // If a device is currently selected, fold its attributes into a single object for use
+ // as the starting values of the form. If no device is selected, use the values for
+ // the current window.
+ let deviceName;
+ let normalizedViewport = Object.assign({}, viewportTemplate);
+ if (viewportTemplate.device) {
+ let device = devices[viewportTemplate.deviceType].find(d => {
+ return d.name == viewportTemplate.device;
+ });
+ deviceName = getFormatStr("responsive.customDeviceNameFromBase", device.name);
+ Object.assign(normalizedViewport, {
+ pixelRatio: device.pixelRatio,
+ userAgent: device.userAgent,
+ touch: device.touch,
+ });
+ } else {
+ deviceName = getStr("responsive.customDeviceName");
+ Object.assign(normalizedViewport, {
+ pixelRatio: window.devicePixelRatio,
+ userAgent: navigator.userAgent,
+ touch: false,
+ });
+ }
+
+ return dom.div(
+ {
+ id: "device-adder"
+ },
+ dom.label(
+ {},
+ dom.span(
+ {
+ className: "device-adder-label",
+ },
+ getStr("responsive.deviceAdderName")
+ ),
+ dom.input({
+ defaultValue: deviceName,
+ })
+ ),
+ dom.label(
+ {},
+ dom.span(
+ {
+ className: "device-adder-label"
+ },
+ getStr("responsive.deviceAdderSize")
+ ),
+ ViewportDimension({
+ viewport: {
+ width: normalizedViewport.width,
+ height: normalizedViewport.height,
+ },
+ onRemoveDevice: () => {},
+ onResizeViewport: () => {},
+ })
+ ),
+ dom.label(
+ {},
+ dom.span(
+ {
+ className: "device-adder-label"
+ },
+ getStr("responsive.deviceAdderPixelRatio")
+ ),
+ dom.input({
+ defaultValue: normalizedViewport.pixelRatio,
+ })
+ ),
+ dom.label(
+ {},
+ dom.span(
+ {
+ className: "device-adder-label"
+ },
+ getStr("responsive.deviceAdderUserAgent")
+ ),
+ dom.input({
+ defaultValue: normalizedViewport.userAgent,
+ })
+ ),
+ dom.label(
+ {},
+ dom.span(
+ {
+ className: "device-adder-label"
+ },
+ getStr("responsive.deviceAdderTouch")
+ ),
+ dom.input({
+ defaultChecked: normalizedViewport.touch,
+ type: "checkbox",
+ })
+ ),
+ dom.button(
+ {
+ id: "device-adder-save",
+ onClick: this.onDeviceAdderSave,
+ },
+ getStr("responsive.deviceAdderSave")
+ )
+ );
+ },
+});
--- a/devtools/client/responsive.html/components/device-modal.js
+++ b/devtools/client/responsive.html/components/device-modal.js
@@ -1,29 +1,32 @@
/* 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 { DOM: dom, createClass, PropTypes, addons } =
+const { DOM: dom, createClass, createFactory, PropTypes, addons } =
require("devtools/client/shared/vendor/react");
+
const { getStr } = require("../utils/l10n");
const Types = require("../types");
+const DeviceAdder = createFactory(require("./device-adder"));
module.exports = createClass({
displayName: "DeviceModal",
propTypes: {
+ deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired,
devices: PropTypes.shape(Types.devices).isRequired,
onDeviceListUpdate: PropTypes.func.isRequired,
onUpdateDeviceDisplayed: PropTypes.func.isRequired,
- onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+ onUpdateDeviceModal: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
getInitialState() {
return {};
},
@@ -58,17 +61,17 @@ module.exports = createClass({
});
},
onDeviceModalSubmit() {
let {
devices,
onDeviceListUpdate,
onUpdateDeviceDisplayed,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
} = this.props;
let preferredDevices = {
"added": new Set(),
"removed": new Set(),
};
for (let type of devices.types) {
@@ -83,36 +86,37 @@ module.exports = createClass({
if (this.state[device.name] != device.displayed) {
onUpdateDeviceDisplayed(device, type, this.state[device.name]);
}
}
}
onDeviceListUpdate(preferredDevices);
- onUpdateDeviceModalOpen(false);
+ onUpdateDeviceModal(false);
},
onKeyDown(event) {
if (!this.props.devices.isModalOpen) {
return;
}
// Escape keycode
if (event.keyCode === 27) {
let {
- onUpdateDeviceModalOpen
+ onUpdateDeviceModal
} = this.props;
- onUpdateDeviceModalOpen(false);
+ onUpdateDeviceModal(false);
}
},
render() {
let {
+ deviceAdderViewportTemplate,
devices,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
} = this.props;
const sortedDevices = {};
for (let type of devices.types) {
sortedDevices[type] = Object.assign([], devices[type])
.sort((a, b) => a.name.localeCompare(b.name));
}
@@ -123,17 +127,17 @@ module.exports = createClass({
},
dom.div(
{
className: "device-modal container",
},
dom.button({
id: "device-close-button",
className: "toolbar-button devtools-button",
- onClick: () => onUpdateDeviceModalOpen(false),
+ onClick: () => onUpdateDeviceModal(false),
}),
dom.div(
{
className: "device-modal-content",
},
devices.types.map(type => {
return dom.div(
{
@@ -158,27 +162,31 @@ module.exports = createClass({
value: device.name,
checked: this.state[device.name],
onChange: this.onDeviceCheckboxChange,
}),
device.name
);
})
);
+ }),
+ DeviceAdder({
+ devices,
+ viewportTemplate: deviceAdderViewportTemplate,
})
),
dom.button(
{
id: "device-submit-button",
onClick: this.onDeviceModalSubmit,
},
getStr("responsive.done")
)
),
dom.div(
{
className: "modal-overlay",
- onClick: () => onUpdateDeviceModalOpen(false),
+ onClick: () => onUpdateDeviceModal(false),
}
)
);
},
});
--- a/devtools/client/responsive.html/components/device-selector.js
+++ b/devtools/client/responsive.html/components/device-selector.js
@@ -12,40 +12,42 @@ const Types = require("../types");
const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";
module.exports = createClass({
displayName: "DeviceSelector",
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
selectedDevice: PropTypes.string.isRequired,
+ viewportId: PropTypes.number.isRequired,
onChangeDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
- onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+ onUpdateDeviceModal: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
onSelectChange({ target }) {
let {
devices,
+ viewportId,
onChangeDevice,
onResizeViewport,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
} = this.props;
if (target.value === OPEN_DEVICE_MODAL_VALUE) {
- onUpdateDeviceModalOpen(true);
+ onUpdateDeviceModal(true, viewportId);
return;
}
for (let type of devices.types) {
for (let device of devices[type]) {
if (device.name === target.value) {
onResizeViewport(device.width, device.height);
- onChangeDevice(device);
+ onChangeDevice(device, type);
return;
}
}
}
},
render() {
let {
--- a/devtools/client/responsive.html/components/moz.build
+++ b/devtools/client/responsive.html/components/moz.build
@@ -1,16 +1,17 @@
# -*- 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(
'browser.js',
+ 'device-adder.js',
'device-modal.js',
'device-selector.js',
'dpr-selector.js',
'global-toolbar.js',
'network-throttling-selector.js',
'resizable-viewport.js',
'viewport-dimension.js',
'viewport-toolbar.js',
--- a/devtools/client/responsive.html/components/resizable-viewport.js
+++ b/devtools/client/responsive.html/components/resizable-viewport.js
@@ -28,17 +28,17 @@ module.exports = createClass({
swapAfterMount: PropTypes.bool.isRequired,
viewport: PropTypes.shape(Types.viewport).isRequired,
onBrowserMounted: PropTypes.func.isRequired,
onChangeDevice: PropTypes.func.isRequired,
onContentResize: PropTypes.func.isRequired,
onRemoveDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
- onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+ onUpdateDeviceModal: PropTypes.func.isRequired,
},
getInitialState() {
return {
isResizing: false,
lastClientX: 0,
lastClientY: 0,
ignoreX: false,
@@ -130,17 +130,17 @@ module.exports = createClass({
screenshot,
swapAfterMount,
viewport,
onBrowserMounted,
onChangeDevice,
onContentResize,
onResizeViewport,
onRotateViewport,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
} = this.props;
let resizeHandleClass = "viewport-resize-handle";
if (screenshot.isCapturing) {
resizeHandleClass += " hidden";
}
let contentClass = "viewport-content";
@@ -149,21 +149,21 @@ module.exports = createClass({
}
return dom.div(
{
className: "resizable-viewport",
},
ViewportToolbar({
devices,
- selectedDevice: viewport.device,
+ viewport,
onChangeDevice,
onResizeViewport,
onRotateViewport,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
}),
dom.div(
{
className: contentClass,
style: {
width: viewport.width + "px",
height: viewport.height + "px",
},
--- a/devtools/client/responsive.html/components/viewport-toolbar.js
+++ b/devtools/client/responsive.html/components/viewport-toolbar.js
@@ -11,45 +11,46 @@ const { getStr } = require("../utils/l10
const Types = require("../types");
const DeviceSelector = createFactory(require("./device-selector"));
module.exports = createClass({
displayName: "ViewportToolbar",
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
- selectedDevice: PropTypes.string.isRequired,
+ viewport: PropTypes.shape(Types.viewport).isRequired,
onChangeDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
- onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+ onUpdateDeviceModal: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
render() {
let {
devices,
- selectedDevice,
+ viewport,
onChangeDevice,
onResizeViewport,
onRotateViewport,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
} = this.props;
return dom.div(
{
className: "viewport-toolbar container",
},
DeviceSelector({
devices,
- selectedDevice,
+ selectedDevice: viewport.device,
+ viewportId: viewport.id,
onChangeDevice,
onResizeViewport,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
}),
dom.button({
className: "viewport-rotate-button toolbar-button devtools-button",
onClick: onRotateViewport,
title: getStr("responsive.rotate"),
})
);
},
--- a/devtools/client/responsive.html/components/viewport.js
+++ b/devtools/client/responsive.html/components/viewport.js
@@ -22,26 +22,26 @@ module.exports = createClass({
swapAfterMount: PropTypes.bool.isRequired,
viewport: PropTypes.shape(Types.viewport).isRequired,
onBrowserMounted: PropTypes.func.isRequired,
onChangeDevice: PropTypes.func.isRequired,
onContentResize: PropTypes.func.isRequired,
onRemoveDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
- onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+ onUpdateDeviceModal: PropTypes.func.isRequired,
},
- onChangeDevice(device) {
+ onChangeDevice(device, deviceType) {
let {
viewport,
onChangeDevice,
} = this.props;
- onChangeDevice(viewport.id, device);
+ onChangeDevice(viewport.id, device, deviceType);
},
onRemoveDevice() {
let {
viewport,
onRemoveDevice,
} = this.props;
@@ -70,17 +70,17 @@ module.exports = createClass({
let {
devices,
location,
screenshot,
swapAfterMount,
viewport,
onBrowserMounted,
onContentResize,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
} = this.props;
let {
onChangeDevice,
onRemoveDevice,
onRotateViewport,
onResizeViewport,
} = this;
@@ -101,14 +101,14 @@ module.exports = createClass({
swapAfterMount,
viewport,
onBrowserMounted,
onChangeDevice,
onContentResize,
onRemoveDevice,
onResizeViewport,
onRotateViewport,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
})
);
},
});
--- a/devtools/client/responsive.html/components/viewports.js
+++ b/devtools/client/responsive.html/components/viewports.js
@@ -20,32 +20,32 @@ module.exports = createClass({
screenshot: PropTypes.shape(Types.screenshot).isRequired,
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
onBrowserMounted: PropTypes.func.isRequired,
onChangeDevice: PropTypes.func.isRequired,
onContentResize: PropTypes.func.isRequired,
onRemoveDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
- onUpdateDeviceModalOpen: PropTypes.func.isRequired,
+ onUpdateDeviceModal: PropTypes.func.isRequired,
},
render() {
let {
devices,
location,
screenshot,
viewports,
onBrowserMounted,
onChangeDevice,
onContentResize,
onRemoveDevice,
onResizeViewport,
onRotateViewport,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
} = this.props;
return dom.div(
{
id: "viewports",
},
viewports.map((viewport, i) => {
return Viewport({
@@ -56,15 +56,15 @@ module.exports = createClass({
swapAfterMount: i == 0,
viewport,
onBrowserMounted,
onChangeDevice,
onContentResize,
onRemoveDevice,
onResizeViewport,
onRotateViewport,
- onUpdateDeviceModalOpen,
+ onUpdateDeviceModal,
});
})
);
},
});
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -33,17 +33,18 @@
url("./images/select-arrow.svg#dark-selected");
}
* {
box-sizing: border-box;
}
#root,
-html, body {
+html,
+body {
height: 100%;
margin: 0;
}
#app {
/* Center the viewports container */
display: flex;
align-items: center;
@@ -361,16 +362,17 @@ select > option.divider {
.viewport-dimension-input {
color: var(--theme-body-color-inactive);
transition: all 0.25s ease;
}
.viewport-dimension-editable.editing,
.viewport-dimension-input.editing {
color: var(--viewport-active-color);
+ outline: none;
}
.viewport-dimension-editable.editing {
border-bottom: 1px solid var(--theme-selection-background);
}
.viewport-dimension-editable.editing.invalid {
border-bottom: 1px solid #d92215;
@@ -514,8 +516,65 @@ select > option.divider {
#device-submit-button:hover {
background-color: var(--toolbar-tab-hover);
}
#device-submit-button:hover:active {
background-color: var(--submit-button-active-background-color);
color: var(--submit-button-active-color);
}
+
+/**
+ * Device Adder
+ */
+
+#device-adder {
+ display: flex;
+ flex-direction: column;
+}
+
+#device-adder button {
+ background-color: var(--theme-tab-toolbar-background);
+ border: 1px solid var(--theme-splitter-color);
+ border-radius: 2px;
+ color: var(--theme-body-color);
+ margin: 0 auto;
+}
+
+#device-adder label {
+ display: flex;
+ margin-bottom: 5px;
+ align-items: center;
+}
+
+#device-adder label > input,
+#device-adder label > .viewport-dimension {
+ width: 130px;
+ margin: 0;
+}
+
+#device-adder input {
+ background: transparent;
+ border: none;
+ text-align: center;
+ color: var(--theme-body-color-inactive);
+ transition: all 0.25s ease;
+}
+
+#device-adder input:focus {
+ color: var(--viewport-active-color);
+}
+
+#device-adder label > input:focus,
+#device-adder label > .viewport-dimension:focus {
+ border-bottom: 1px solid var(--theme-selection-background);
+ outline: none;
+}
+
+.device-adder-label {
+ display: inline-block;
+ margin-right: 5px;
+ width: 35px;
+}
+
+#device-adder #device-adder-save {
+ margin-top: 5px;
+}
--- a/devtools/client/responsive.html/reducers/devices.js
+++ b/devtools/client/responsive.html/reducers/devices.js
@@ -6,24 +6,25 @@
const {
ADD_DEVICE,
ADD_DEVICE_TYPE,
LOAD_DEVICE_LIST_START,
LOAD_DEVICE_LIST_ERROR,
LOAD_DEVICE_LIST_END,
UPDATE_DEVICE_DISPLAYED,
- UPDATE_DEVICE_MODAL_OPEN,
+ UPDATE_DEVICE_MODAL,
} = require("../actions/index");
const Types = require("../types");
const INITIAL_DEVICES = {
types: [],
isModalOpen: false,
+ modalOpenedFromViewport: null,
listState: Types.deviceListState.INITIALIZED,
};
let reducers = {
[ADD_DEVICE](devices, { device, deviceType }) {
return Object.assign({}, devices, {
[deviceType]: [...devices[deviceType], device],
@@ -64,19 +65,20 @@ let reducers = {
},
[LOAD_DEVICE_LIST_END](devices, action) {
return Object.assign({}, devices, {
listState: Types.deviceListState.LOADED,
});
},
- [UPDATE_DEVICE_MODAL_OPEN](devices, { isOpen }) {
+ [UPDATE_DEVICE_MODAL](devices, { isOpen, modalOpenedFromViewport }) {
return Object.assign({}, devices, {
isModalOpen: isOpen,
+ modalOpenedFromViewport,
});
},
};
module.exports = function (devices = INITIAL_DEVICES, action) {
let reducer = reducers[action.type];
if (!reducer) {
--- a/devtools/client/responsive.html/reducers/viewports.js
+++ b/devtools/client/responsive.html/reducers/viewports.js
@@ -14,16 +14,17 @@ const {
} = require("../actions/index");
let nextViewportId = 0;
const INITIAL_VIEWPORTS = [];
const INITIAL_VIEWPORT = {
id: nextViewportId++,
device: "",
+ deviceType: "",
width: 320,
height: 480,
pixelRatio: {
value: 0,
},
};
let reducers = {
@@ -31,24 +32,25 @@ let reducers = {
[ADD_VIEWPORT](viewports) {
// For the moment, there can be at most one viewport.
if (viewports.length === 1) {
return viewports;
}
return [...viewports, Object.assign({}, INITIAL_VIEWPORT)];
},
- [CHANGE_DEVICE](viewports, { id, device }) {
+ [CHANGE_DEVICE](viewports, { id, device, deviceType }) {
return viewports.map(viewport => {
if (viewport.id !== id) {
return viewport;
}
return Object.assign({}, viewport, {
device,
+ deviceType,
});
});
},
[CHANGE_PIXEL_RATIO](viewports, { id, pixelRatio }) {
return viewports.map(viewport => {
if (viewport.id !== id) {
return viewport;
@@ -65,16 +67,17 @@ let reducers = {
[REMOVE_DEVICE](viewports, { id }) {
return viewports.map(viewport => {
if (viewport.id !== id) {
return viewport;
}
return Object.assign({}, viewport, {
device: "",
+ deviceType: "",
});
});
},
[RESIZE_VIEWPORT](viewports, { id, width, height }) {
return viewports.map(viewport => {
if (viewport.id !== id) {
return viewport;
--- a/devtools/client/responsive.html/test/unit/test_change_device.js
+++ b/devtools/client/responsive.html/test/unit/test_change_device.js
@@ -29,14 +29,14 @@ add_task(function* () {
"firefoxOS": true,
"os": "fxos"
}, "phones"));
dispatch(addViewport());
let viewport = getState().viewports[0];
equal(viewport.device, "", "Default device is unselected");
- dispatch(changeDevice(0, "Firefox OS Flame"));
+ dispatch(changeDevice(0, "Firefox OS Flame", "phones"));
viewport = getState().viewports[0];
equal(viewport.device, "Firefox OS Flame",
"Changed to Firefox OS Flame device");
});
--- a/devtools/client/responsive.html/types.js
+++ b/devtools/client/responsive.html/types.js
@@ -84,16 +84,19 @@ exports.devices = {
consoles: PropTypes.arrayOf(PropTypes.shape(device)),
// An array of watch devices
watches: PropTypes.arrayOf(PropTypes.shape(device)),
// Whether or not the device modal is open
isModalOpen: PropTypes.bool,
+ // Viewport id that triggered the modal to open
+ modalOpenedFromViewport: PropTypes.number,
+
// Device list state, possible values are exported above in an enum
listState: PropTypes.oneOf(Object.keys(exports.deviceListState)),
};
/* VIEWPORT */
/**
@@ -135,16 +138,19 @@ exports.touchSimulation = {
exports.viewport = {
// The id of the viewport
id: PropTypes.number,
// The currently selected device applied to the viewport
device: PropTypes.string,
+ // The currently selected device type applied to the viewport
+ deviceType: PropTypes.string,
+
// The width of the viewport
width: PropTypes.number,
// The height of the viewport
height: PropTypes.number,
// The devicePixelRatio of the viewport
pixelRatio: PropTypes.shape(pixelRatio),