tor-browser

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

InputMap.js (6272B)


      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  Component,
      9  createRef,
     10 } = require("resource://devtools/client/shared/vendor/react.mjs");
     11 const {
     12  L10N,
     13 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js");
     14 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     15 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     16 const { div, input, textarea, button } = dom;
     17 
     18 const CUSTOM_NEW_REQUEST_INPUT_NAME = L10N.getStr(
     19  "netmonitor.custom.placeholder.name"
     20 );
     21 
     22 const CUSTOM_NEW_REQUEST_INPUT_VALUE = L10N.getStr(
     23  "netmonitor.custom.placeholder.value"
     24 );
     25 
     26 const REMOVE_ITEM = L10N.getStr("netmonitor.custom.removeItem");
     27 
     28 /**
     29 * Editable name and value list component with optional form to add new items
     30 */
     31 class InputMap extends Component {
     32  static get propTypes() {
     33    return {
     34      list: PropTypes.arrayOf(
     35        PropTypes.shape({
     36          name: PropTypes.string.isRequired,
     37          value: PropTypes.string.isRequired,
     38          disabled: PropTypes.bool,
     39        })
     40      ).isRequired,
     41      onUpdate: PropTypes.func,
     42      onAdd: PropTypes.func,
     43      onDelete: PropTypes.func,
     44      onChange: PropTypes.func,
     45      onChecked: PropTypes.func,
     46    };
     47  }
     48 
     49  constructor(props) {
     50    super(props);
     51 
     52    this.listRef = createRef();
     53 
     54    this.state = {
     55      name: "",
     56      value: "",
     57    };
     58  }
     59 
     60  render() {
     61    const { list, onUpdate, onAdd, onDelete, onChecked } = this.props;
     62    const { name, value } = this.state;
     63 
     64    // Adds a new item with name and value when the user starts typing on the form
     65    const onKeyDown = event => {
     66      const { target } = event;
     67      onAdd(name, value);
     68      this.setState({ name: "", value: "" }, () => {
     69        // Get next to last child on the list,
     70        // because that was the item that was just added and
     71        // we need to focous on it, so the user can keep editing it.
     72        const targetParentNode =
     73          this.listRef.current.childNodes?.[
     74            this.listRef.current.childElementCount - 2
     75          ];
     76        targetParentNode?.querySelector(`.${target.className}`).focus();
     77      });
     78    };
     79 
     80    return div(
     81      {
     82        ref: this.listRef,
     83        className: "http-custom-input-and-map-form",
     84      },
     85      list.map((item, index) => {
     86        return div(
     87          {
     88            className: "tabpanel-summary-container http-custom-input",
     89            id: `http-custom-${item.name.toLowerCase()}`,
     90            key: index,
     91          },
     92          input({
     93            className: "tabpanel-summary-input-checkbox",
     94            name: `checked-${index}`,
     95            type: "checkbox",
     96            onChange: event => {
     97              onChecked(index, event.target.checked);
     98            },
     99            checked: item.checked,
    100            disabled: !!item.disabled,
    101            wrap: "off",
    102          }),
    103          div(
    104            { className: "tabpanel-summary-input-name" },
    105            div(
    106              {
    107                className: "auto-growing-textarea",
    108                "data-replicated-value": item.name,
    109                title: item.name,
    110              },
    111              textarea({
    112                className: "http-custom-input-name",
    113                name: `name-${index}`,
    114                value: item.name,
    115                disabled: !!item.disabled,
    116                onChange: event => {
    117                  onUpdate(event);
    118                },
    119                rows: 1,
    120              })
    121            )
    122          ),
    123          div(
    124            { className: "tabpanel-summary-input-value" },
    125            div(
    126              {
    127                className: "auto-growing-textarea",
    128                "data-replicated-value": item.value,
    129                title: item.value,
    130              },
    131              textarea({
    132                className: "http-custom-input-value",
    133                name: `value-${index}`,
    134                placeholder: "value",
    135                disabled: !!item.disabled,
    136                onChange: event => {
    137                  onUpdate(event);
    138                },
    139                value: item.value,
    140                rows: 1,
    141              })
    142            )
    143          ),
    144          !item.disabled &&
    145            onDelete &&
    146            button({
    147              className: "http-custom-delete-button",
    148              title: REMOVE_ITEM,
    149              "aria-label": REMOVE_ITEM,
    150              onClick: () => onDelete(index),
    151            })
    152        );
    153      }),
    154      onAdd &&
    155        div(
    156          {
    157            className: "map-add-new-inputs",
    158          },
    159          input({
    160            className: "tabpanel-summary-input-checkbox",
    161            onChange: () => {},
    162            checked: true,
    163            type: "checkbox",
    164          }),
    165          div(
    166            { className: "tabpanel-summary-input-name" },
    167            div(
    168              {
    169                className: "auto-growing-textarea",
    170                "data-replicated-value": name,
    171                title: value,
    172              },
    173              textarea({
    174                className: "http-custom-input-name",
    175                type: "text",
    176                ref: "addInputName",
    177                checked: true,
    178                value: name,
    179                rows: 1,
    180                placeholder: CUSTOM_NEW_REQUEST_INPUT_NAME,
    181                onChange: e => this.setState({ name: e.target.value }),
    182                onKeyDown,
    183              })
    184            )
    185          ),
    186          div(
    187            { className: "tabpanel-summary-input-value" },
    188            div(
    189              {
    190                className: "auto-growing-textarea",
    191                "data-replicated-value": value,
    192                title: value,
    193              },
    194              textarea({
    195                className: "http-custom-input-value",
    196                type: "text",
    197                ref: "addInputValue",
    198                value,
    199                onChange: e => this.setState({ value: e.target.value }),
    200                rows: 1,
    201                placeholder: CUSTOM_NEW_REQUEST_INPUT_VALUE,
    202                onKeyDown,
    203              })
    204            )
    205          )
    206        )
    207    );
    208  }
    209 }
    210 
    211 module.exports = InputMap;