Source code

Revision control

Copy as Markdown

Other Tools

/* 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 {
createFactory,
PureComponent,
} = require("resource://devtools/client/shared/vendor/react.js");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
const DeviceForm = createFactory(
require("resource://devtools/client/responsive/components/DeviceForm.js")
);
const DeviceList = createFactory(
require("resource://devtools/client/responsive/components/DeviceList.js")
);
const {
getFormatStr,
getStr,
} = require("resource://devtools/client/responsive/utils/l10n.js");
const {
getDeviceString,
} = require("resource://devtools/client/shared/devices.js");
const Types = require("resource://devtools/client/responsive/types.js");
class DeviceModal extends PureComponent {
static get propTypes() {
return {
deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired,
devices: PropTypes.shape(Types.devices).isRequired,
onAddCustomDevice: PropTypes.func.isRequired,
onDeviceListUpdate: PropTypes.func.isRequired,
onEditCustomDevice: PropTypes.func.isRequired,
onRemoveCustomDevice: PropTypes.func.isRequired,
onUpdateDeviceDisplayed: PropTypes.func.isRequired,
onUpdateDeviceModal: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
// The device form type can be 3 states: "add", "edit", or "".
// "add" - The form shown is adding a new device.
// "edit" - The form shown is editing an existing custom device.
// "" - The form is closed.
deviceFormType: "",
// The device being edited from the edit form.
editingDevice: null,
};
for (const type of this.props.devices.types) {
for (const device of this.props.devices[type]) {
this.state[device.name] = device.displayed;
}
}
this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
this.onDeviceCheckboxChange = this.onDeviceCheckboxChange.bind(this);
this.onDeviceFormShow = this.onDeviceFormShow.bind(this);
this.onDeviceFormHide = this.onDeviceFormHide.bind(this);
this.onDeviceModalSubmit = this.onDeviceModalSubmit.bind(this);
this.onEditCustomDevice = this.onEditCustomDevice.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}
componentDidMount() {
window.addEventListener("keydown", this.onKeyDown, true);
}
componentWillUnmount() {
this.onDeviceModalSubmit();
window.removeEventListener("keydown", this.onKeyDown, true);
}
onAddCustomDevice(device) {
this.props.onAddCustomDevice(device);
// Default custom devices to enabled
this.setState({
[device.name]: true,
});
}
onDeviceCheckboxChange({ nativeEvent: { button }, target }) {
if (button !== 0) {
return;
}
this.setState({
[target.value]: !this.state[target.value],
});
}
onDeviceFormShow(type, device) {
this.setState({
deviceFormType: type,
editingDevice: device,
});
}
onDeviceFormHide() {
this.setState({
deviceFormType: "",
editingDevice: null,
});
}
onDeviceModalSubmit() {
const { devices, onDeviceListUpdate, onUpdateDeviceDisplayed } = this.props;
const preferredDevices = {
added: new Set(),
removed: new Set(),
};
for (const type of devices.types) {
for (const device of devices[type]) {
const newState = this.state[device.name];
if (device.featured && !newState) {
preferredDevices.removed.add(device.name);
} else if (!device.featured && newState) {
preferredDevices.added.add(device.name);
}
if (this.state[device.name] != device.displayed) {
onUpdateDeviceDisplayed(device, type, this.state[device.name]);
}
}
}
onDeviceListUpdate(preferredDevices);
}
onEditCustomDevice(newDevice) {
this.props.onEditCustomDevice(this.state.editingDevice, newDevice);
// We want to remove the original device name from state after editing, so create a
// new state setting the old key to null and the new one to true.
this.setState({
[this.state.editingDevice.name]: null,
[newDevice.name]: true,
});
}
onKeyDown(event) {
if (!this.props.devices.isModalOpen) {
return;
}
// Escape keycode
if (event.keyCode === 27) {
const { onUpdateDeviceModal } = this.props;
onUpdateDeviceModal(false);
}
}
renderAddForm() {
// 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.
const { deviceAdderViewportTemplate: viewportTemplate } = this.props;
const deviceTemplate = this.props.deviceAdderViewportTemplate;
if (viewportTemplate.device) {
const device = this.props.devices[viewportTemplate.deviceType].find(d => {
return d.name == viewportTemplate.device;
});
Object.assign(deviceTemplate, {
pixelRatio: device.pixelRatio,
userAgent: device.userAgent,
touch: device.touch,
name: getFormatStr("responsive.customDeviceNameFromBase", device.name),
});
} else {
Object.assign(deviceTemplate, {
pixelRatio: window.devicePixelRatio,
userAgent: navigator.userAgent,
touch: false,
name: getStr("responsive.customDeviceName"),
});
}
return DeviceForm({
formType: "add",
device: deviceTemplate,
devices: this.props.devices,
onDeviceFormHide: this.onDeviceFormHide,
onSave: this.onAddCustomDevice,
viewportTemplate,
});
}
renderDevices() {
const sortedDevices = {};
for (const type of this.props.devices.types) {
sortedDevices[type] = this.props.devices[type].sort((a, b) =>
a.name.localeCompare(b.name)
);
sortedDevices[type].forEach(device => {
device.isChecked = this.state[device.name];
});
}
return this.props.devices.types.map(type => {
return sortedDevices[type].length
? dom.div(
{
className: `device-type device-type-${type}`,
key: type,
},
dom.header({ className: "device-header" }, getDeviceString(type)),
DeviceList({
devices: sortedDevices,
isDeviceFormShown: this.state.deviceFormType,
type,
onDeviceCheckboxChange: this.onDeviceCheckboxChange,
onDeviceFormHide: this.onDeviceFormHide,
onDeviceFormShow: this.onDeviceFormShow,
onEditCustomDevice: this.onEditCustomDevice,
onRemoveCustomDevice: this.props.onRemoveCustomDevice,
})
)
: null;
});
}
renderEditForm() {
return DeviceForm({
formType: "edit",
device: this.state.editingDevice,
devices: this.props.devices,
onDeviceFormHide: this.onDeviceFormHide,
onSave: this.onEditCustomDevice,
viewportTemplate: {
width: this.state.editingDevice.width,
height: this.state.editingDevice.height,
},
});
}
renderForm() {
let form = null;
if (this.state.deviceFormType === "add") {
form = this.renderAddForm();
} else if (this.state.deviceFormType === "edit") {
form = this.renderEditForm();
}
return form;
}
render() {
const { onUpdateDeviceModal } = this.props;
const isDeviceFormShown = this.state.deviceFormType;
return dom.div(
{
id: "device-modal-wrapper",
className: this.props.devices.isModalOpen ? "opened" : "closed",
},
dom.div(
{ className: "device-modal" },
dom.div(
{ className: "device-modal-header" },
!isDeviceFormShown
? dom.header(
{ className: "device-modal-title" },
getStr("responsive.deviceSettings"),
dom.button({
id: "device-close-button",
className: "devtools-button",
onClick: () => onUpdateDeviceModal(false),
})
)
: null,
!isDeviceFormShown
? dom.button(
{
id: "device-add-button",
onClick: () => this.onDeviceFormShow("add"),
},
getStr("responsive.addDevice2")
)
: null,
this.renderForm()
),
dom.div({ className: "device-modal-content" }, this.renderDevices())
),
dom.div({
className: "modal-overlay",
onClick: () => onUpdateDeviceModal(false),
})
);
}
}
module.exports = DeviceModal;