From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- .../client/responsive.html/components/browser.js | 149 ++++++++++++++++ .../responsive.html/components/device-modal.js | 181 +++++++++++++++++++ .../responsive.html/components/device-selector.js | 122 +++++++++++++ .../responsive.html/components/dpr-selector.js | 131 ++++++++++++++ .../responsive.html/components/global-toolbar.js | 101 +++++++++++ .../client/responsive.html/components/moz.build | 19 ++ .../components/network-throttling-selector.js | 92 ++++++++++ .../components/resizable-viewport.js | 195 +++++++++++++++++++++ .../components/viewport-dimension.js | 173 ++++++++++++++++++ .../responsive.html/components/viewport-toolbar.js | 55 ++++++ .../client/responsive.html/components/viewport.js | 114 ++++++++++++ .../client/responsive.html/components/viewports.js | 70 ++++++++ 12 files changed, 1402 insertions(+) create mode 100644 devtools/client/responsive.html/components/browser.js create mode 100644 devtools/client/responsive.html/components/device-modal.js create mode 100644 devtools/client/responsive.html/components/device-selector.js create mode 100644 devtools/client/responsive.html/components/dpr-selector.js create mode 100644 devtools/client/responsive.html/components/global-toolbar.js create mode 100644 devtools/client/responsive.html/components/moz.build create mode 100644 devtools/client/responsive.html/components/network-throttling-selector.js create mode 100644 devtools/client/responsive.html/components/resizable-viewport.js create mode 100644 devtools/client/responsive.html/components/viewport-dimension.js create mode 100644 devtools/client/responsive.html/components/viewport-toolbar.js create mode 100644 devtools/client/responsive.html/components/viewport.js create mode 100644 devtools/client/responsive.html/components/viewports.js (limited to 'devtools/client/responsive.html/components') diff --git a/devtools/client/responsive.html/components/browser.js b/devtools/client/responsive.html/components/browser.js new file mode 100644 index 000000000..f2902905b --- /dev/null +++ b/devtools/client/responsive.html/components/browser.js @@ -0,0 +1,149 @@ +/* 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 { Task } = require("devtools/shared/task"); +const flags = require("devtools/shared/flags"); +const { getToplevelWindow } = require("sdk/window/utils"); +const { DOM: dom, createClass, addons, PropTypes } = + require("devtools/client/shared/vendor/react"); + +const Types = require("../types"); +const e10s = require("../utils/e10s"); +const message = require("../utils/message"); + +module.exports = createClass({ + + /** + * This component is not allowed to depend directly on frequently changing + * data (width, height) due to the use of `dangerouslySetInnerHTML` below. + * Any changes in props will cause the ` + } + } + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/device-modal.js b/devtools/client/responsive.html/components/device-modal.js new file mode 100644 index 000000000..d28b97472 --- /dev/null +++ b/devtools/client/responsive.html/components/device-modal.js @@ -0,0 +1,181 @@ +/* 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 } = + require("devtools/client/shared/vendor/react"); +const { getStr } = require("../utils/l10n"); +const Types = require("../types"); + +module.exports = createClass({ + displayName: "DeviceModal", + + propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, + onDeviceListUpdate: PropTypes.func.isRequired, + onUpdateDeviceDisplayed: PropTypes.func.isRequired, + onUpdateDeviceModalOpen: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + getInitialState() { + return {}; + }, + + componentDidMount() { + window.addEventListener("keydown", this.onKeyDown, true); + }, + + componentWillReceiveProps(nextProps) { + let { + devices, + } = nextProps; + + for (let type of devices.types) { + for (let device of devices[type]) { + this.setState({ + [device.name]: device.displayed, + }); + } + } + }, + + componentWillUnmount() { + window.removeEventListener("keydown", this.onKeyDown, true); + }, + + onDeviceCheckboxClick({ target }) { + this.setState({ + [target.value]: !this.state[target.value] + }); + }, + + onDeviceModalSubmit() { + let { + devices, + onDeviceListUpdate, + onUpdateDeviceDisplayed, + onUpdateDeviceModalOpen, + } = this.props; + + let preferredDevices = { + "added": new Set(), + "removed": new Set(), + }; + + for (let type of devices.types) { + for (let device of devices[type]) { + let 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); + onUpdateDeviceModalOpen(false); + }, + + onKeyDown(event) { + if (!this.props.devices.isModalOpen) { + return; + } + // Escape keycode + if (event.keyCode === 27) { + let { + onUpdateDeviceModalOpen + } = this.props; + onUpdateDeviceModalOpen(false); + } + }, + + render() { + let { + devices, + onUpdateDeviceModalOpen, + } = this.props; + + const sortedDevices = {}; + for (let type of devices.types) { + sortedDevices[type] = Object.assign([], devices[type]) + .sort((a, b) => a.name.localeCompare(b.name)); + } + + return dom.div( + { + id: "device-modal-wrapper", + className: this.props.devices.isModalOpen ? "opened" : "closed", + }, + dom.div( + { + className: "device-modal container", + }, + dom.button({ + id: "device-close-button", + className: "toolbar-button devtools-button", + onClick: () => onUpdateDeviceModalOpen(false), + }), + dom.div( + { + className: "device-modal-content", + }, + devices.types.map(type => { + return dom.div( + { + className: "device-type", + key: type, + }, + dom.header( + { + className: "device-header", + }, + type + ), + sortedDevices[type].map(device => { + return dom.label( + { + className: "device-label", + key: device.name, + }, + dom.input({ + className: "device-input-checkbox", + type: "checkbox", + value: device.name, + checked: this.state[device.name], + onChange: this.onDeviceCheckboxClick, + }), + device.name + ); + }) + ); + }) + ), + dom.button( + { + id: "device-submit-button", + onClick: this.onDeviceModalSubmit, + }, + getStr("responsive.done") + ) + ), + dom.div( + { + className: "modal-overlay", + onClick: () => onUpdateDeviceModalOpen(false), + } + ) + ); + }, +}); diff --git a/devtools/client/responsive.html/components/device-selector.js b/devtools/client/responsive.html/components/device-selector.js new file mode 100644 index 000000000..3215ce5fb --- /dev/null +++ b/devtools/client/responsive.html/components/device-selector.js @@ -0,0 +1,122 @@ +/* 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 { getStr } = require("../utils/l10n"); +const { DOM: dom, createClass, PropTypes, addons } = + require("devtools/client/shared/vendor/react"); + +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, + onChangeDevice: PropTypes.func.isRequired, + onResizeViewport: PropTypes.func.isRequired, + onUpdateDeviceModalOpen: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + onSelectChange({ target }) { + let { + devices, + onChangeDevice, + onResizeViewport, + onUpdateDeviceModalOpen, + } = this.props; + + if (target.value === OPEN_DEVICE_MODAL_VALUE) { + onUpdateDeviceModalOpen(true); + 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); + return; + } + } + } + }, + + render() { + let { + devices, + selectedDevice, + } = this.props; + + let options = []; + for (let type of devices.types) { + for (let device of devices[type]) { + if (device.displayed) { + options.push(device); + } + } + } + + options.sort(function (a, b) { + return a.name.localeCompare(b.name); + }); + + let selectClass = "viewport-device-selector"; + if (selectedDevice) { + selectClass += " selected"; + } + + let state = devices.listState; + let listContent; + + if (state == Types.deviceListState.LOADED) { + listContent = [dom.option({ + value: "", + title: "", + disabled: true, + hidden: true, + }, getStr("responsive.noDeviceSelected")), + options.map(device => { + return dom.option({ + key: device.name, + value: device.name, + title: "", + }, device.name); + }), + dom.option({ + value: OPEN_DEVICE_MODAL_VALUE, + title: "", + }, getStr("responsive.editDeviceList"))]; + } else if (state == Types.deviceListState.LOADING + || state == Types.deviceListState.INITIALIZED) { + listContent = [dom.option({ + value: "", + title: "", + disabled: true, + }, getStr("responsive.deviceListLoading"))]; + } else if (state == Types.deviceListState.ERROR) { + listContent = [dom.option({ + value: "", + title: "", + disabled: true, + }, getStr("responsive.deviceListError"))]; + } + + return dom.select( + { + className: selectClass, + value: selectedDevice, + title: selectedDevice, + onChange: this.onSelectChange, + disabled: (state !== Types.deviceListState.LOADED), + }, + ...listContent + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/dpr-selector.js b/devtools/client/responsive.html/components/dpr-selector.js new file mode 100644 index 000000000..31b8db1c2 --- /dev/null +++ b/devtools/client/responsive.html/components/dpr-selector.js @@ -0,0 +1,131 @@ +/* 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 } = + require("devtools/client/shared/vendor/react"); + +const Types = require("../types"); +const { getStr, getFormatStr } = require("../utils/l10n"); + +const PIXEL_RATIO_PRESET = [1, 2, 3]; + +const createVisibleOption = value => + dom.option({ + value, + title: value, + key: value, + }, value); + +const createHiddenOption = value => + dom.option({ + value, + title: value, + hidden: true, + disabled: true, + }, value); + +module.exports = createClass({ + displayName: "DPRSelector", + + propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, + displayPixelRatio: Types.pixelRatio.value.isRequired, + selectedDevice: PropTypes.string.isRequired, + selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired, + onChangePixelRatio: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + getInitialState() { + return { + isFocused: false + }; + }, + + onFocusChange({type}) { + this.setState({ + isFocused: type === "focus" + }); + }, + + onSelectChange({ target }) { + this.props.onChangePixelRatio(+target.value); + }, + + render() { + let { + devices, + displayPixelRatio, + selectedDevice, + selectedPixelRatio, + } = this.props; + + let hiddenOptions = []; + + for (let type of devices.types) { + for (let device of devices[type]) { + if (device.displayed && + !hiddenOptions.includes(device.pixelRatio) && + !PIXEL_RATIO_PRESET.includes(device.pixelRatio)) { + hiddenOptions.push(device.pixelRatio); + } + } + } + + if (!PIXEL_RATIO_PRESET.includes(displayPixelRatio)) { + hiddenOptions.push(displayPixelRatio); + } + + let state = devices.listState; + let isDisabled = (state !== Types.deviceListState.LOADED) || (selectedDevice !== ""); + let selectorClass = ""; + let title; + + if (isDisabled) { + selectorClass += " disabled"; + title = getFormatStr("responsive.autoDPR", selectedDevice); + } else { + title = getStr("responsive.devicePixelRatio"); + + if (selectedPixelRatio.value) { + selectorClass += " selected"; + } + } + + if (this.state.isFocused) { + selectorClass += " focused"; + } + + let listContent = PIXEL_RATIO_PRESET.map(createVisibleOption); + + if (state == Types.deviceListState.LOADED) { + listContent = listContent.concat(hiddenOptions.map(createHiddenOption)); + } + + return dom.label( + { + id: "global-dpr-selector", + className: selectorClass, + title, + }, + "DPR", + dom.select( + { + value: selectedPixelRatio.value || displayPixelRatio, + disabled: isDisabled, + onChange: this.onSelectChange, + onFocus: this.onFocusChange, + onBlur: this.onFocusChange, + }, + ...listContent + ) + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/global-toolbar.js b/devtools/client/responsive.html/components/global-toolbar.js new file mode 100644 index 000000000..6c31fa338 --- /dev/null +++ b/devtools/client/responsive.html/components/global-toolbar.js @@ -0,0 +1,101 @@ +/* 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 { DOM: dom, createClass, createFactory, PropTypes, addons } = + require("devtools/client/shared/vendor/react"); + +const { getStr } = require("../utils/l10n"); +const Types = require("../types"); +const DPRSelector = createFactory(require("./dpr-selector")); +const NetworkThrottlingSelector = createFactory(require("./network-throttling-selector")); + +module.exports = createClass({ + displayName: "GlobalToolbar", + + propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, + displayPixelRatio: Types.pixelRatio.value.isRequired, + networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired, + screenshot: PropTypes.shape(Types.screenshot).isRequired, + selectedDevice: PropTypes.string.isRequired, + selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired, + touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired, + onChangeNetworkThrottling: PropTypes.func.isRequired, + onChangePixelRatio: PropTypes.func.isRequired, + onChangeTouchSimulation: PropTypes.func.isRequired, + onExit: PropTypes.func.isRequired, + onScreenshot: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + render() { + let { + devices, + displayPixelRatio, + networkThrottling, + screenshot, + selectedDevice, + selectedPixelRatio, + touchSimulation, + onChangeNetworkThrottling, + onChangePixelRatio, + onChangeTouchSimulation, + onExit, + onScreenshot, + } = this.props; + + let touchButtonClass = "toolbar-button devtools-button"; + if (touchSimulation.enabled) { + touchButtonClass += " active"; + } + + return dom.header( + { + id: "global-toolbar", + className: "container", + }, + dom.span( + { + className: "title", + }, + getStr("responsive.title") + ), + NetworkThrottlingSelector({ + networkThrottling, + onChangeNetworkThrottling, + }), + DPRSelector({ + devices, + displayPixelRatio, + selectedDevice, + selectedPixelRatio, + onChangePixelRatio, + }), + dom.button({ + id: "global-touch-simulation-button", + className: touchButtonClass, + title: (touchSimulation.enabled ? + getStr("responsive.disableTouch") : getStr("responsive.enableTouch")), + onClick: () => onChangeTouchSimulation(!touchSimulation.enabled), + }), + dom.button({ + id: "global-screenshot-button", + className: "toolbar-button devtools-button", + title: getStr("responsive.screenshot"), + onClick: onScreenshot, + disabled: screenshot.isCapturing, + }), + dom.button({ + id: "global-exit-button", + className: "toolbar-button devtools-button", + title: getStr("responsive.exit"), + onClick: onExit, + }) + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/moz.build b/devtools/client/responsive.html/components/moz.build new file mode 100644 index 000000000..4ad36f992 --- /dev/null +++ b/devtools/client/responsive.html/components/moz.build @@ -0,0 +1,19 @@ +# -*- 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-modal.js', + 'device-selector.js', + 'dpr-selector.js', + 'global-toolbar.js', + 'network-throttling-selector.js', + 'resizable-viewport.js', + 'viewport-dimension.js', + 'viewport-toolbar.js', + 'viewport.js', + 'viewports.js', +) diff --git a/devtools/client/responsive.html/components/network-throttling-selector.js b/devtools/client/responsive.html/components/network-throttling-selector.js new file mode 100644 index 000000000..fa9f5c6a0 --- /dev/null +++ b/devtools/client/responsive.html/components/network-throttling-selector.js @@ -0,0 +1,92 @@ +/* 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 { DOM: dom, createClass, PropTypes, addons } = + require("devtools/client/shared/vendor/react"); + +const Types = require("../types"); +const { getStr } = require("../utils/l10n"); +const throttlingProfiles = require("devtools/client/shared/network-throttling-profiles"); + +module.exports = createClass({ + + displayName: "NetworkThrottlingSelector", + + propTypes: { + networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired, + onChangeNetworkThrottling: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + onSelectChange({ target }) { + let { + onChangeNetworkThrottling, + } = this.props; + + if (target.value == getStr("responsive.noThrottling")) { + onChangeNetworkThrottling(false, ""); + return; + } + + for (let profile of throttlingProfiles) { + if (profile.id === target.value) { + onChangeNetworkThrottling(true, profile.id); + return; + } + } + }, + + render() { + let { + networkThrottling, + } = this.props; + + let selectClass = ""; + let selectedProfile; + if (networkThrottling.enabled) { + selectClass += " selected"; + selectedProfile = networkThrottling.profile; + } else { + selectedProfile = getStr("responsive.noThrottling"); + } + + let listContent = [ + dom.option( + { + key: "disabled", + }, + getStr("responsive.noThrottling") + ), + dom.option( + { + key: "divider", + className: "divider", + disabled: true, + } + ), + throttlingProfiles.map(profile => { + return dom.option( + { + key: profile.id, + }, + profile.id + ); + }), + ]; + + return dom.select( + { + id: "global-network-throttling-selector", + className: selectClass, + value: selectedProfile, + onChange: this.onSelectChange, + }, + ...listContent + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/resizable-viewport.js b/devtools/client/responsive.html/components/resizable-viewport.js new file mode 100644 index 000000000..1d94cd052 --- /dev/null +++ b/devtools/client/responsive.html/components/resizable-viewport.js @@ -0,0 +1,195 @@ +/* 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/. */ + +/* global window */ + +"use strict"; + +const { DOM: dom, createClass, createFactory, PropTypes } = + require("devtools/client/shared/vendor/react"); + +const Constants = require("../constants"); +const Types = require("../types"); +const Browser = createFactory(require("./browser")); +const ViewportToolbar = createFactory(require("./viewport-toolbar")); + +const VIEWPORT_MIN_WIDTH = Constants.MIN_VIEWPORT_DIMENSION; +const VIEWPORT_MIN_HEIGHT = Constants.MIN_VIEWPORT_DIMENSION; + +module.exports = createClass({ + + displayName: "ResizableViewport", + + propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, + location: Types.location.isRequired, + screenshot: PropTypes.shape(Types.screenshot).isRequired, + 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, + }, + + getInitialState() { + return { + isResizing: false, + lastClientX: 0, + lastClientY: 0, + ignoreX: false, + ignoreY: false, + }; + }, + + onResizeStart({ target, clientX, clientY }) { + window.addEventListener("mousemove", this.onResizeDrag, true); + window.addEventListener("mouseup", this.onResizeStop, true); + + this.setState({ + isResizing: true, + lastClientX: clientX, + lastClientY: clientY, + ignoreX: target === this.refs.resizeBarY, + ignoreY: target === this.refs.resizeBarX, + }); + }, + + onResizeStop() { + window.removeEventListener("mousemove", this.onResizeDrag, true); + window.removeEventListener("mouseup", this.onResizeStop, true); + + this.setState({ + isResizing: false, + lastClientX: 0, + lastClientY: 0, + ignoreX: false, + ignoreY: false, + }); + }, + + onResizeDrag({ clientX, clientY }) { + if (!this.state.isResizing) { + return; + } + + let { lastClientX, lastClientY, ignoreX, ignoreY } = this.state; + // the viewport is centered horizontally, so horizontal resize resizes + // by twice the distance the mouse was dragged - on left and right side. + let deltaX = 2 * (clientX - lastClientX); + let deltaY = (clientY - lastClientY); + + if (ignoreX) { + deltaX = 0; + } + if (ignoreY) { + deltaY = 0; + } + + let width = this.props.viewport.width + deltaX; + let height = this.props.viewport.height + deltaY; + + if (width < VIEWPORT_MIN_WIDTH) { + width = VIEWPORT_MIN_WIDTH; + } else { + lastClientX = clientX; + } + + if (height < VIEWPORT_MIN_HEIGHT) { + height = VIEWPORT_MIN_HEIGHT; + } else { + lastClientY = clientY; + } + + // Update the viewport store with the new width and height. + this.props.onResizeViewport(width, height); + // Change the device selector back to an unselected device + // TODO: Bug 1332754: Logic like this probably belongs in the action creator. + if (this.props.viewport.device) { + // In bug 1329843 and others, we may eventually stop this approach of removing the + // the properties of the device on resize. However, at the moment, there is no + // way to edit dPR when a device is selected, and there is no UI at all for editing + // UA, so it's important to keep doing this for now. + this.props.onRemoveDevice(); + } + + this.setState({ + lastClientX, + lastClientY + }); + }, + + render() { + let { + devices, + location, + screenshot, + swapAfterMount, + viewport, + onBrowserMounted, + onChangeDevice, + onContentResize, + onResizeViewport, + onRotateViewport, + onUpdateDeviceModalOpen, + } = this.props; + + let resizeHandleClass = "viewport-resize-handle"; + if (screenshot.isCapturing) { + resizeHandleClass += " hidden"; + } + + let contentClass = "viewport-content"; + if (this.state.isResizing) { + contentClass += " resizing"; + } + + return dom.div( + { + className: "resizable-viewport", + }, + ViewportToolbar({ + devices, + selectedDevice: viewport.device, + onChangeDevice, + onResizeViewport, + onRotateViewport, + onUpdateDeviceModalOpen, + }), + dom.div( + { + className: contentClass, + style: { + width: viewport.width + "px", + height: viewport.height + "px", + }, + }, + Browser({ + location, + swapAfterMount, + onBrowserMounted, + onContentResize, + }) + ), + dom.div({ + className: resizeHandleClass, + onMouseDown: this.onResizeStart, + }), + dom.div({ + ref: "resizeBarX", + className: "viewport-horizontal-resize-handle", + onMouseDown: this.onResizeStart, + }), + dom.div({ + ref: "resizeBarY", + className: "viewport-vertical-resize-handle", + onMouseDown: this.onResizeStart, + }) + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/viewport-dimension.js b/devtools/client/responsive.html/components/viewport-dimension.js new file mode 100644 index 000000000..a359cecf7 --- /dev/null +++ b/devtools/client/responsive.html/components/viewport-dimension.js @@ -0,0 +1,173 @@ +/* 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 { DOM: dom, createClass, PropTypes } = + require("devtools/client/shared/vendor/react"); + +const Constants = require("../constants"); +const Types = require("../types"); + +module.exports = createClass({ + displayName: "ViewportDimension", + + propTypes: { + viewport: PropTypes.shape(Types.viewport).isRequired, + onRemoveDevice: PropTypes.func.isRequired, + onResizeViewport: PropTypes.func.isRequired, + }, + + getInitialState() { + let { width, height } = this.props.viewport; + + return { + width, + height, + isEditing: false, + isInvalid: false, + }; + }, + + componentWillReceiveProps(nextProps) { + let { width, height } = nextProps.viewport; + + this.setState({ + width, + height, + }); + }, + + validateInput(value) { + let isInvalid = true; + + // Check the value is a number and greater than MIN_VIEWPORT_DIMENSION + if (/^\d{3,4}$/.test(value) && + parseInt(value, 10) >= Constants.MIN_VIEWPORT_DIMENSION) { + isInvalid = false; + } + + this.setState({ + isInvalid, + }); + }, + + onInputBlur() { + let { width, height } = this.props.viewport; + + if (this.state.width != width || this.state.height != height) { + this.onInputSubmit(); + } + + this.setState({ + isEditing: false, + inInvalid: false, + }); + }, + + onInputChange({ target }) { + if (target.value.length > 4) { + return; + } + + if (this.refs.widthInput == target) { + this.setState({ width: target.value }); + this.validateInput(target.value); + } + + if (this.refs.heightInput == target) { + this.setState({ height: target.value }); + this.validateInput(target.value); + } + }, + + onInputFocus() { + this.setState({ + isEditing: true, + }); + }, + + onInputKeyUp({ target, keyCode }) { + // On Enter, submit the input + if (keyCode == 13) { + this.onInputSubmit(); + } + + // On Esc, blur the target + if (keyCode == 27) { + target.blur(); + } + }, + + onInputSubmit() { + if (this.state.isInvalid) { + let { width, height } = this.props.viewport; + + this.setState({ + width, + height, + isInvalid: false, + }); + + return; + } + + // Change the device selector back to an unselected device + // TODO: Bug 1332754: Logic like this probably belongs in the action creator. + if (this.props.viewport.device) { + this.props.onRemoveDevice(); + } + this.props.onResizeViewport(parseInt(this.state.width, 10), + parseInt(this.state.height, 10)); + }, + + render() { + let editableClass = "viewport-dimension-editable"; + let inputClass = "viewport-dimension-input"; + + if (this.state.isEditing) { + editableClass += " editing"; + inputClass += " editing"; + } + + if (this.state.isInvalid) { + editableClass += " invalid"; + } + + return dom.div( + { + className: "viewport-dimension", + }, + dom.div( + { + className: editableClass, + }, + dom.input({ + ref: "widthInput", + className: inputClass, + size: 4, + value: this.state.width, + onBlur: this.onInputBlur, + onChange: this.onInputChange, + onFocus: this.onInputFocus, + onKeyUp: this.onInputKeyUp, + }), + dom.span({ + className: "viewport-dimension-separator", + }, "×"), + dom.input({ + ref: "heightInput", + className: inputClass, + size: 4, + value: this.state.height, + onBlur: this.onInputBlur, + onChange: this.onInputChange, + onFocus: this.onInputFocus, + onKeyUp: this.onInputKeyUp, + }) + ) + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/viewport-toolbar.js b/devtools/client/responsive.html/components/viewport-toolbar.js new file mode 100644 index 000000000..7cbc73f67 --- /dev/null +++ b/devtools/client/responsive.html/components/viewport-toolbar.js @@ -0,0 +1,55 @@ +/* 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 { DOM: dom, createClass, createFactory, PropTypes, addons } = + require("devtools/client/shared/vendor/react"); + +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, + onChangeDevice: PropTypes.func.isRequired, + onResizeViewport: PropTypes.func.isRequired, + onRotateViewport: PropTypes.func.isRequired, + onUpdateDeviceModalOpen: PropTypes.func.isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + render() { + let { + devices, + selectedDevice, + onChangeDevice, + onResizeViewport, + onRotateViewport, + onUpdateDeviceModalOpen, + } = this.props; + + return dom.div( + { + className: "viewport-toolbar container", + }, + DeviceSelector({ + devices, + selectedDevice, + onChangeDevice, + onResizeViewport, + onUpdateDeviceModalOpen, + }), + dom.button({ + className: "viewport-rotate-button toolbar-button devtools-button", + onClick: onRotateViewport, + }) + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/viewport.js b/devtools/client/responsive.html/components/viewport.js new file mode 100644 index 000000000..fe41b41ee --- /dev/null +++ b/devtools/client/responsive.html/components/viewport.js @@ -0,0 +1,114 @@ +/* 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 { DOM: dom, createClass, createFactory, PropTypes } = + require("devtools/client/shared/vendor/react"); + +const Types = require("../types"); +const ResizableViewport = createFactory(require("./resizable-viewport")); +const ViewportDimension = createFactory(require("./viewport-dimension")); + +module.exports = createClass({ + + displayName: "Viewport", + + propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, + location: Types.location.isRequired, + screenshot: PropTypes.shape(Types.screenshot).isRequired, + 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, + }, + + onChangeDevice(device) { + let { + viewport, + onChangeDevice, + } = this.props; + + onChangeDevice(viewport.id, device); + }, + + onRemoveDevice() { + let { + viewport, + onRemoveDevice, + } = this.props; + + onRemoveDevice(viewport.id); + }, + + onResizeViewport(width, height) { + let { + viewport, + onResizeViewport, + } = this.props; + + onResizeViewport(viewport.id, width, height); + }, + + onRotateViewport() { + let { + viewport, + onRotateViewport, + } = this.props; + + onRotateViewport(viewport.id); + }, + + render() { + let { + devices, + location, + screenshot, + swapAfterMount, + viewport, + onBrowserMounted, + onContentResize, + onUpdateDeviceModalOpen, + } = this.props; + + let { + onChangeDevice, + onRemoveDevice, + onRotateViewport, + onResizeViewport, + } = this; + + return dom.div( + { + className: "viewport", + }, + ViewportDimension({ + viewport, + onRemoveDevice, + onResizeViewport, + }), + ResizableViewport({ + devices, + location, + screenshot, + swapAfterMount, + viewport, + onBrowserMounted, + onChangeDevice, + onContentResize, + onRemoveDevice, + onResizeViewport, + onRotateViewport, + onUpdateDeviceModalOpen, + }) + ); + }, + +}); diff --git a/devtools/client/responsive.html/components/viewports.js b/devtools/client/responsive.html/components/viewports.js new file mode 100644 index 000000000..b305d1e07 --- /dev/null +++ b/devtools/client/responsive.html/components/viewports.js @@ -0,0 +1,70 @@ +/* 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 { DOM: dom, createClass, createFactory, PropTypes } = + require("devtools/client/shared/vendor/react"); + +const Types = require("../types"); +const Viewport = createFactory(require("./viewport")); + +module.exports = createClass({ + + displayName: "Viewports", + + propTypes: { + devices: PropTypes.shape(Types.devices).isRequired, + location: Types.location.isRequired, + 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, + }, + + render() { + let { + devices, + location, + screenshot, + viewports, + onBrowserMounted, + onChangeDevice, + onContentResize, + onRemoveDevice, + onResizeViewport, + onRotateViewport, + onUpdateDeviceModalOpen, + } = this.props; + + return dom.div( + { + id: "viewports", + }, + viewports.map((viewport, i) => { + return Viewport({ + key: viewport.id, + devices, + location, + screenshot, + swapAfterMount: i == 0, + viewport, + onBrowserMounted, + onChangeDevice, + onContentResize, + onRemoveDevice, + onResizeViewport, + onRotateViewport, + onUpdateDeviceModalOpen, + }); + }) + ); + }, + +}); -- cgit v1.2.3