App.js (12420B)
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 const { 14 connect, 15 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 16 17 const Toolbar = createFactory( 18 require("resource://devtools/client/responsive/components/Toolbar.js") 19 ); 20 21 loader.lazyGetter(this, "DeviceModal", () => 22 createFactory( 23 require("resource://devtools/client/responsive/components/DeviceModal.js") 24 ) 25 ); 26 27 const { 28 changeNetworkThrottling, 29 } = require("resource://devtools/client/shared/components/throttling/actions.js"); 30 const { 31 addCustomDevice, 32 editCustomDevice, 33 removeCustomDevice, 34 updateDeviceDisplayed, 35 updateDeviceModal, 36 updatePreferredDevices, 37 } = require("resource://devtools/client/responsive/actions/devices.js"); 38 const { 39 takeScreenshot, 40 } = require("resource://devtools/client/responsive/actions/screenshot.js"); 41 const { 42 changeUserAgent, 43 toggleLeftAlignment, 44 toggleReloadOnTouchSimulation, 45 toggleReloadOnUserAgent, 46 toggleTouchSimulation, 47 toggleUserAgentInput, 48 } = require("resource://devtools/client/responsive/actions/ui.js"); 49 const { 50 changeDevice, 51 changePixelRatio, 52 changeViewportAngle, 53 removeDeviceAssociation, 54 resizeViewport, 55 rotateViewport, 56 } = require("resource://devtools/client/responsive/actions/viewports.js"); 57 const { 58 getOrientation, 59 } = require("resource://devtools/client/responsive/utils/orientation.js"); 60 61 const Types = require("resource://devtools/client/responsive/types.js"); 62 63 class App extends PureComponent { 64 static get propTypes() { 65 return { 66 devices: PropTypes.shape(Types.devices).isRequired, 67 dispatch: PropTypes.func.isRequired, 68 leftAlignmentEnabled: PropTypes.bool.isRequired, 69 networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired, 70 screenshot: PropTypes.shape(Types.screenshot).isRequired, 71 viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired, 72 }; 73 } 74 75 constructor(props) { 76 super(props); 77 78 this.onAddCustomDevice = this.onAddCustomDevice.bind(this); 79 this.onChangeDevice = this.onChangeDevice.bind(this); 80 this.onChangeNetworkThrottling = this.onChangeNetworkThrottling.bind(this); 81 this.onChangePixelRatio = this.onChangePixelRatio.bind(this); 82 this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this); 83 this.onChangeUserAgent = this.onChangeUserAgent.bind(this); 84 this.onChangeViewportOrientation = 85 this.onChangeViewportOrientation.bind(this); 86 this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this); 87 this.onEditCustomDevice = this.onEditCustomDevice.bind(this); 88 this.onExit = this.onExit.bind(this); 89 this.onRemoveCustomDevice = this.onRemoveCustomDevice.bind(this); 90 this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this); 91 this.doResizeViewport = this.doResizeViewport.bind(this); 92 this.onRotateViewport = this.onRotateViewport.bind(this); 93 this.onScreenshot = this.onScreenshot.bind(this); 94 this.onToggleLeftAlignment = this.onToggleLeftAlignment.bind(this); 95 this.onToggleReloadOnTouchSimulation = 96 this.onToggleReloadOnTouchSimulation.bind(this); 97 this.onToggleReloadOnUserAgent = this.onToggleReloadOnUserAgent.bind(this); 98 this.onToggleUserAgentInput = this.onToggleUserAgentInput.bind(this); 99 this.onUpdateDeviceDisplayed = this.onUpdateDeviceDisplayed.bind(this); 100 this.onUpdateDeviceModal = this.onUpdateDeviceModal.bind(this); 101 } 102 103 onAddCustomDevice(device) { 104 this.props.dispatch(addCustomDevice(device)); 105 } 106 107 onChangeDevice(id, device, deviceType) { 108 // Resize the viewport first. 109 this.doResizeViewport(id, device.width, device.height); 110 111 // TODO: Bug 1332754: Move messaging and logic into the action creator so that the 112 // message is sent from the action creator and device property changes are sent from 113 // there instead of this function. 114 window.postMessage( 115 { 116 type: "change-device", 117 device, 118 viewport: device, 119 }, 120 "*" 121 ); 122 123 const orientation = getOrientation(device, device); 124 125 this.props.dispatch(changeViewportAngle(0, orientation.angle)); 126 this.props.dispatch(changeDevice(id, device.name, deviceType)); 127 this.props.dispatch(changePixelRatio(id, device.pixelRatio)); 128 this.props.dispatch(changeUserAgent(device.userAgent)); 129 this.props.dispatch(toggleTouchSimulation(device.touch)); 130 } 131 132 onChangeNetworkThrottling(enabled, profile) { 133 window.postMessage( 134 { 135 type: "change-network-throttling", 136 enabled, 137 profile, 138 }, 139 "*" 140 ); 141 this.props.dispatch(changeNetworkThrottling(enabled, profile)); 142 } 143 144 onChangePixelRatio(pixelRatio) { 145 window.postMessage( 146 { 147 type: "change-pixel-ratio", 148 pixelRatio, 149 }, 150 "*" 151 ); 152 this.props.dispatch(changePixelRatio(0, pixelRatio)); 153 } 154 155 onChangeTouchSimulation(enabled) { 156 window.postMessage( 157 { 158 type: "change-touch-simulation", 159 enabled, 160 }, 161 "*" 162 ); 163 this.props.dispatch(toggleTouchSimulation(enabled)); 164 } 165 166 onChangeUserAgent(userAgent) { 167 window.postMessage( 168 { 169 type: "change-user-agent", 170 userAgent, 171 }, 172 "*" 173 ); 174 this.props.dispatch(changeUserAgent(userAgent)); 175 } 176 177 onChangeViewportOrientation(id, type, angle) { 178 window.postMessage( 179 { 180 type: "viewport-orientation-change", 181 orientationType: type, 182 angle, 183 }, 184 "*" 185 ); 186 187 this.props.dispatch(changeViewportAngle(id, angle)); 188 } 189 190 onDeviceListUpdate(devices) { 191 updatePreferredDevices(devices); 192 } 193 194 onEditCustomDevice(oldDevice, newDevice) { 195 // If the edited device is currently selected, then update its original association 196 // and reset UI state. 197 let viewport = this.props.viewports.find( 198 ({ device }) => device === oldDevice.name 199 ); 200 201 if (viewport) { 202 viewport = { 203 ...viewport, 204 device: newDevice.name, 205 deviceType: "custom", 206 height: newDevice.height, 207 width: newDevice.width, 208 pixelRatio: newDevice.pixelRatio, 209 touch: newDevice.touch, 210 userAgent: newDevice.userAgent, 211 }; 212 } 213 214 this.props.dispatch(editCustomDevice(viewport, oldDevice, newDevice)); 215 } 216 217 onExit() { 218 window.postMessage({ type: "exit" }, "*"); 219 } 220 221 onRemoveCustomDevice(device) { 222 // If the custom device is currently selected on any of the viewports, 223 // remove the device association and reset all the ui state. 224 for (const viewport of this.props.viewports) { 225 if (viewport.device === device.name) { 226 this.onRemoveDeviceAssociation(viewport.id, { resetProfile: true }); 227 } 228 } 229 230 this.props.dispatch(removeCustomDevice(device)); 231 } 232 233 onRemoveDeviceAssociation(id, { resetProfile }) { 234 // TODO: Bug 1332754: Move messaging and logic into the action creator so that device 235 // property changes are sent from there instead of this function. 236 this.props.dispatch(removeDeviceAssociation(id, { resetProfile })); 237 if (resetProfile) { 238 this.props.dispatch(toggleTouchSimulation(false)); 239 this.props.dispatch(changePixelRatio(id, 0)); 240 this.props.dispatch(changeUserAgent("")); 241 } 242 } 243 244 doResizeViewport(id, width, height) { 245 // This is the setter function that we pass to Toolbar and Viewports 246 // so they can modify the viewport. 247 window.postMessage( 248 { 249 type: "viewport-resize", 250 width, 251 height, 252 }, 253 "*" 254 ); 255 this.props.dispatch(resizeViewport(id, width, height)); 256 } 257 258 /** 259 * Dispatches the rotateViewport action creator. This utilized by the RDM toolbar as 260 * a prop. 261 * 262 * @param {number} id 263 * The viewport ID. 264 */ 265 onRotateViewport(id) { 266 let currentDevice; 267 const viewport = this.props.viewports[id]; 268 269 for (const type of this.props.devices.types) { 270 for (const device of this.props.devices[type]) { 271 if (viewport.device === device.name) { 272 currentDevice = device; 273 } 274 } 275 } 276 277 // If no device is selected, then assume the selected device's primary orientation is 278 // opposite of the viewport orientation. 279 if (!currentDevice) { 280 currentDevice = { 281 height: viewport.width, 282 width: viewport.height, 283 }; 284 } 285 286 const currentAngle = Services.prefs.getIntPref( 287 "devtools.responsive.viewport.angle" 288 ); 289 const angleToRotateTo = currentAngle === 90 ? 0 : 90; 290 const { type, angle } = getOrientation( 291 currentDevice, 292 viewport, 293 angleToRotateTo 294 ); 295 296 this.onChangeViewportOrientation(id, type, angle, true); 297 this.props.dispatch(rotateViewport(id)); 298 299 window.postMessage( 300 { 301 type: "viewport-resize", 302 height: viewport.width, 303 width: viewport.height, 304 }, 305 "*" 306 ); 307 } 308 309 onScreenshot() { 310 this.props.dispatch(takeScreenshot()); 311 } 312 313 onToggleLeftAlignment() { 314 this.props.dispatch(toggleLeftAlignment()); 315 316 window.postMessage( 317 { 318 type: "toggle-left-alignment", 319 leftAlignmentEnabled: this.props.leftAlignmentEnabled, 320 }, 321 "*" 322 ); 323 } 324 325 onToggleReloadOnTouchSimulation() { 326 this.props.dispatch(toggleReloadOnTouchSimulation()); 327 } 328 329 onToggleReloadOnUserAgent() { 330 this.props.dispatch(toggleReloadOnUserAgent()); 331 } 332 333 onToggleUserAgentInput() { 334 this.props.dispatch(toggleUserAgentInput()); 335 } 336 337 onUpdateDeviceDisplayed(device, deviceType, displayed) { 338 this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed)); 339 } 340 341 onUpdateDeviceModal(isOpen, modalOpenedFromViewport) { 342 this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport)); 343 window.postMessage({ type: "update-device-modal", isOpen }, "*"); 344 } 345 346 render() { 347 const { devices, networkThrottling, screenshot, viewports } = this.props; 348 349 const { 350 onAddCustomDevice, 351 onChangeDevice, 352 onChangeNetworkThrottling, 353 onChangePixelRatio, 354 onChangeTouchSimulation, 355 onChangeUserAgent, 356 onDeviceListUpdate, 357 onEditCustomDevice, 358 onExit, 359 onRemoveCustomDevice, 360 onRemoveDeviceAssociation, 361 doResizeViewport, 362 onRotateViewport, 363 onScreenshot, 364 onToggleLeftAlignment, 365 onToggleReloadOnTouchSimulation, 366 onToggleReloadOnUserAgent, 367 onToggleUserAgentInput, 368 onUpdateDeviceDisplayed, 369 onUpdateDeviceModal, 370 } = this; 371 372 if (!viewports.length) { 373 return null; 374 } 375 376 const selectedDevice = viewports[0].device; 377 const selectedPixelRatio = viewports[0].pixelRatio; 378 379 let deviceAdderViewportTemplate = {}; 380 if (devices.modalOpenedFromViewport !== null) { 381 deviceAdderViewportTemplate = viewports[devices.modalOpenedFromViewport]; 382 } 383 384 return dom.div( 385 { id: "app" }, 386 Toolbar({ 387 devices, 388 networkThrottling, 389 screenshot, 390 selectedDevice, 391 selectedPixelRatio, 392 viewport: viewports[0], 393 onChangeDevice, 394 onChangeNetworkThrottling, 395 onChangePixelRatio, 396 onChangeTouchSimulation, 397 onChangeUserAgent, 398 onExit, 399 onRemoveDeviceAssociation, 400 doResizeViewport, 401 onRotateViewport, 402 onScreenshot, 403 onToggleLeftAlignment, 404 onToggleReloadOnTouchSimulation, 405 onToggleReloadOnUserAgent, 406 onToggleUserAgentInput, 407 onUpdateDeviceModal, 408 }), 409 devices.isModalOpen 410 ? DeviceModal({ 411 deviceAdderViewportTemplate, 412 devices, 413 onAddCustomDevice, 414 onDeviceListUpdate, 415 onEditCustomDevice, 416 onRemoveCustomDevice, 417 onUpdateDeviceDisplayed, 418 onUpdateDeviceModal, 419 }) 420 : null 421 ); 422 } 423 } 424 425 const mapStateToProps = state => { 426 return { 427 ...state, 428 leftAlignmentEnabled: state.ui.leftAlignmentEnabled, 429 }; 430 }; 431 432 module.exports = connect(mapStateToProps)(App);