DeviceModal.js (9096B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 createFactory, 9 PureComponent, 10 } = require("resource://devtools/client/shared/vendor/react.mjs"); 11 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 12 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 13 14 const DeviceForm = createFactory( 15 require("resource://devtools/client/responsive/components/DeviceForm.js") 16 ); 17 const DeviceList = createFactory( 18 require("resource://devtools/client/responsive/components/DeviceList.js") 19 ); 20 21 const { 22 getFormatStr, 23 getStr, 24 } = require("resource://devtools/client/responsive/utils/l10n.js"); 25 const { 26 getDeviceString, 27 } = require("resource://devtools/client/shared/devices.js"); 28 const Types = require("resource://devtools/client/responsive/types.js"); 29 30 class DeviceModal extends PureComponent { 31 static get propTypes() { 32 return { 33 deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired, 34 devices: PropTypes.shape(Types.devices).isRequired, 35 onAddCustomDevice: PropTypes.func.isRequired, 36 onDeviceListUpdate: PropTypes.func.isRequired, 37 onEditCustomDevice: PropTypes.func.isRequired, 38 onRemoveCustomDevice: PropTypes.func.isRequired, 39 onUpdateDeviceDisplayed: PropTypes.func.isRequired, 40 onUpdateDeviceModal: PropTypes.func.isRequired, 41 }; 42 } 43 44 constructor(props) { 45 super(props); 46 47 this.state = { 48 // The device form type can be 3 states: "add", "edit", or "". 49 // "add" - The form shown is adding a new device. 50 // "edit" - The form shown is editing an existing custom device. 51 // "" - The form is closed. 52 deviceFormType: "", 53 // The device being edited from the edit form. 54 editingDevice: null, 55 }; 56 for (const type of this.props.devices.types) { 57 for (const device of this.props.devices[type]) { 58 this.state[device.name] = device.displayed; 59 } 60 } 61 62 this.onAddCustomDevice = this.onAddCustomDevice.bind(this); 63 this.onDeviceCheckboxChange = this.onDeviceCheckboxChange.bind(this); 64 this.onDeviceFormShow = this.onDeviceFormShow.bind(this); 65 this.onDeviceFormHide = this.onDeviceFormHide.bind(this); 66 this.onDeviceModalSubmit = this.onDeviceModalSubmit.bind(this); 67 this.onEditCustomDevice = this.onEditCustomDevice.bind(this); 68 this.onKeyDown = this.onKeyDown.bind(this); 69 } 70 71 componentDidMount() { 72 window.addEventListener("keydown", this.onKeyDown, true); 73 } 74 75 componentWillUnmount() { 76 this.onDeviceModalSubmit(); 77 window.removeEventListener("keydown", this.onKeyDown, true); 78 } 79 80 onAddCustomDevice(device) { 81 this.props.onAddCustomDevice(device); 82 // Default custom devices to enabled 83 this.setState({ 84 [device.name]: true, 85 }); 86 } 87 88 onDeviceCheckboxChange({ nativeEvent: { button }, target }) { 89 if (button !== 0) { 90 return; 91 } 92 this.setState({ 93 [target.value]: !this.state[target.value], 94 }); 95 } 96 97 onDeviceFormShow(type, device) { 98 this.setState({ 99 deviceFormType: type, 100 editingDevice: device, 101 }); 102 } 103 104 onDeviceFormHide() { 105 this.setState({ 106 deviceFormType: "", 107 editingDevice: null, 108 }); 109 } 110 111 onDeviceModalSubmit() { 112 const { devices, onDeviceListUpdate, onUpdateDeviceDisplayed } = this.props; 113 114 const preferredDevices = { 115 added: new Set(), 116 removed: new Set(), 117 }; 118 119 for (const type of devices.types) { 120 for (const device of devices[type]) { 121 const newState = this.state[device.name]; 122 123 if (device.featured && !newState) { 124 preferredDevices.removed.add(device.name); 125 } else if (!device.featured && newState) { 126 preferredDevices.added.add(device.name); 127 } 128 129 if (this.state[device.name] != device.displayed) { 130 onUpdateDeviceDisplayed(device, type, this.state[device.name]); 131 } 132 } 133 } 134 135 onDeviceListUpdate(preferredDevices); 136 } 137 138 onEditCustomDevice(newDevice) { 139 this.props.onEditCustomDevice(this.state.editingDevice, newDevice); 140 141 // We want to remove the original device name from state after editing, so create a 142 // new state setting the old key to null and the new one to true. 143 this.setState({ 144 [this.state.editingDevice.name]: null, 145 [newDevice.name]: true, 146 }); 147 } 148 149 onKeyDown(event) { 150 if (!this.props.devices.isModalOpen) { 151 return; 152 } 153 // Escape keycode 154 if (event.keyCode === 27) { 155 const { onUpdateDeviceModal } = this.props; 156 onUpdateDeviceModal(false); 157 } 158 } 159 160 renderAddForm() { 161 // If a device is currently selected, fold its attributes into a single object for use 162 // as the starting values of the form. If no device is selected, use the values for 163 // the current window. 164 const { deviceAdderViewportTemplate: viewportTemplate } = this.props; 165 const deviceTemplate = this.props.deviceAdderViewportTemplate; 166 if (viewportTemplate.device) { 167 const device = this.props.devices[viewportTemplate.deviceType].find(d => { 168 return d.name == viewportTemplate.device; 169 }); 170 Object.assign(deviceTemplate, { 171 pixelRatio: device.pixelRatio, 172 userAgent: device.userAgent, 173 touch: device.touch, 174 name: getFormatStr("responsive.customDeviceNameFromBase", device.name), 175 }); 176 } else { 177 Object.assign(deviceTemplate, { 178 pixelRatio: window.devicePixelRatio, 179 userAgent: navigator.userAgent, 180 touch: false, 181 name: getStr("responsive.customDeviceName"), 182 }); 183 } 184 185 return DeviceForm({ 186 formType: "add", 187 device: deviceTemplate, 188 devices: this.props.devices, 189 onDeviceFormHide: this.onDeviceFormHide, 190 onSave: this.onAddCustomDevice, 191 viewportTemplate, 192 }); 193 } 194 195 renderDevices() { 196 const sortedDevices = {}; 197 for (const type of this.props.devices.types) { 198 sortedDevices[type] = this.props.devices[type].sort((a, b) => 199 a.name.localeCompare(b.name) 200 ); 201 202 sortedDevices[type].forEach(device => { 203 device.isChecked = this.state[device.name]; 204 }); 205 } 206 207 return this.props.devices.types.map(type => { 208 return sortedDevices[type].length 209 ? dom.div( 210 { 211 className: `device-type device-type-${type}`, 212 key: type, 213 }, 214 dom.header({ className: "device-header" }, getDeviceString(type)), 215 DeviceList({ 216 devices: sortedDevices, 217 isDeviceFormShown: this.state.deviceFormType, 218 type, 219 onDeviceCheckboxChange: this.onDeviceCheckboxChange, 220 onDeviceFormHide: this.onDeviceFormHide, 221 onDeviceFormShow: this.onDeviceFormShow, 222 onEditCustomDevice: this.onEditCustomDevice, 223 onRemoveCustomDevice: this.props.onRemoveCustomDevice, 224 }) 225 ) 226 : null; 227 }); 228 } 229 230 renderEditForm() { 231 return DeviceForm({ 232 formType: "edit", 233 device: this.state.editingDevice, 234 devices: this.props.devices, 235 onDeviceFormHide: this.onDeviceFormHide, 236 onSave: this.onEditCustomDevice, 237 viewportTemplate: { 238 width: this.state.editingDevice.width, 239 height: this.state.editingDevice.height, 240 }, 241 }); 242 } 243 244 renderForm() { 245 let form = null; 246 247 if (this.state.deviceFormType === "add") { 248 form = this.renderAddForm(); 249 } else if (this.state.deviceFormType === "edit") { 250 form = this.renderEditForm(); 251 } 252 253 return form; 254 } 255 256 render() { 257 const { onUpdateDeviceModal } = this.props; 258 const isDeviceFormShown = this.state.deviceFormType; 259 260 return dom.div( 261 { 262 id: "device-modal-wrapper", 263 className: this.props.devices.isModalOpen ? "opened" : "closed", 264 }, 265 dom.div( 266 { className: "device-modal" }, 267 dom.div( 268 { className: "device-modal-header" }, 269 !isDeviceFormShown 270 ? dom.header( 271 { className: "device-modal-title" }, 272 getStr("responsive.deviceSettings"), 273 dom.button({ 274 id: "device-close-button", 275 className: "devtools-button", 276 onClick: () => onUpdateDeviceModal(false), 277 }) 278 ) 279 : null, 280 !isDeviceFormShown 281 ? dom.button( 282 { 283 id: "device-add-button", 284 onClick: () => this.onDeviceFormShow("add"), 285 }, 286 getStr("responsive.addDevice2") 287 ) 288 : null, 289 this.renderForm() 290 ), 291 dom.div({ className: "device-modal-content" }, this.renderDevices()) 292 ), 293 dom.div({ 294 className: "modal-overlay", 295 onClick: () => onUpdateDeviceModal(false), 296 }) 297 ); 298 } 299 } 300 301 module.exports = DeviceModal;