tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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;