tor-browser

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

App.js (11692B)


      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 import React, { Component } from "devtools/client/shared/vendor/react";
      6 import {
      7  button,
      8  div,
      9  main,
     10  span,
     11 } from "devtools/client/shared/vendor/react-dom-factories";
     12 import PropTypes from "devtools/client/shared/vendor/react-prop-types";
     13 import { connect } from "devtools/client/shared/vendor/react-redux";
     14 import { prefs } from "../utils/prefs";
     15 import { primaryPaneTabs } from "../constants";
     16 import actions from "../actions/index";
     17 import DebuggerImage from "./shared/DebuggerImage";
     18 
     19 import {
     20  getSelectedLocation,
     21  getPaneCollapse,
     22  getActiveSearch,
     23  getQuickOpenEnabled,
     24  getOrientation,
     25  getIsCurrentThreadPaused,
     26  isMapScopesEnabled,
     27  getSourceMapErrorForSourceActor,
     28 } from "../selectors/index";
     29 const KeyShortcuts = require("resource://devtools/client/shared/key-shortcuts.js");
     30 
     31 const SplitBox = require("resource://devtools/client/shared/components/splitter/SplitBox.js");
     32 const AppErrorBoundary = require("resource://devtools/client/shared/components/AppErrorBoundary.js");
     33 
     34 const horizontalLayoutBreakpoint = window.matchMedia("(min-width: 800px)");
     35 const verticalLayoutBreakpoint = window.matchMedia(
     36  "(min-width: 10px) and (max-width: 799px)"
     37 );
     38 
     39 import { ShortcutsModal } from "./ShortcutsModal";
     40 import PrimaryPanes from "./PrimaryPanes/index";
     41 import Editor from "./Editor/index";
     42 import SecondaryPanes from "./SecondaryPanes/index";
     43 import WelcomeBox from "./WelcomeBox";
     44 import EditorTabs from "./Editor/Tabs";
     45 import EditorFooter from "./Editor/Footer";
     46 import QuickOpenModal from "./QuickOpenModal";
     47 
     48 class App extends Component {
     49  #shortcuts;
     50 
     51  constructor(props) {
     52    super(props);
     53 
     54    // The shortcuts should be built as early as possible because they are
     55    // exposed via getChildContext.
     56    this.#shortcuts = new KeyShortcuts({ window });
     57 
     58    this.state = {
     59      shortcutsModalEnabled: false,
     60      startPanelSize: 0,
     61      endPanelSize: 0,
     62    };
     63  }
     64 
     65  static get propTypes() {
     66    return {
     67      activeSearch: PropTypes.oneOf(["file", "project"]),
     68      closeActiveSearch: PropTypes.func.isRequired,
     69      closeQuickOpen: PropTypes.func.isRequired,
     70      endPanelCollapsed: PropTypes.bool.isRequired,
     71      fluentBundles: PropTypes.array.isRequired,
     72      openQuickOpen: PropTypes.func.isRequired,
     73      orientation: PropTypes.oneOf(["horizontal", "vertical"]).isRequired,
     74      quickOpenEnabled: PropTypes.bool.isRequired,
     75      showWelcomeBox: PropTypes.bool.isRequired,
     76      setActiveSearch: PropTypes.func.isRequired,
     77      setOrientation: PropTypes.func.isRequired,
     78      setPrimaryPaneTab: PropTypes.func.isRequired,
     79      startPanelCollapsed: PropTypes.bool.isRequired,
     80      toolboxDoc: PropTypes.object.isRequired,
     81      showOriginalVariableMappingWarning: PropTypes.bool,
     82    };
     83  }
     84 
     85  getChildContext() {
     86    return {
     87      fluentBundles: this.props.fluentBundles,
     88      toolboxDoc: this.props.toolboxDoc,
     89      shortcuts: this.#shortcuts,
     90      l10n: L10N,
     91    };
     92  }
     93 
     94  componentDidMount() {
     95    horizontalLayoutBreakpoint.addListener(this.onLayoutChange);
     96    verticalLayoutBreakpoint.addListener(this.onLayoutChange);
     97    this.setOrientation();
     98 
     99    this.#shortcuts.on(L10N.getStr("symbolSearch.search.key2"), e =>
    100      this.toggleQuickOpenModal(e, "@")
    101    );
    102 
    103    [
    104      L10N.getStr("sources.search.key2"),
    105      L10N.getStr("sources.search.alt.key"),
    106    ].forEach(key => this.#shortcuts.on(key, this.toggleQuickOpenModal));
    107 
    108    [
    109      L10N.getStr("gotoLineModal.key3"),
    110      // For consistency with sourceeditor and codemirror5 shortcuts, map
    111      // sourceeditor jumpToLine command key.
    112      `CmdOrCtrl+${L10N.getStr("jumpToLine.commandkey")}`,
    113    ].forEach(key => this.#shortcuts.on(key, this.toggleJumpToLine));
    114 
    115    this.#shortcuts.on(
    116      L10N.getStr("projectTextSearch.key"),
    117      this.jumpToProjectSearch
    118    );
    119 
    120    this.#shortcuts.on("Escape", this.onEscape);
    121    this.#shortcuts.on("CmdOrCtrl+/", this.onCommandSlash);
    122  }
    123 
    124  componentWillUnmount() {
    125    horizontalLayoutBreakpoint.removeListener(this.onLayoutChange);
    126    verticalLayoutBreakpoint.removeListener(this.onLayoutChange);
    127    this.#shortcuts.destroy();
    128  }
    129 
    130  jumpToProjectSearch = e => {
    131    e.preventDefault();
    132    this.props.setPrimaryPaneTab(primaryPaneTabs.PROJECT_SEARCH);
    133    this.props.setActiveSearch(primaryPaneTabs.PROJECT_SEARCH);
    134  };
    135 
    136  onEscape = e => {
    137    const {
    138      activeSearch,
    139      closeActiveSearch,
    140      closeQuickOpen,
    141      quickOpenEnabled,
    142    } = this.props;
    143    const { shortcutsModalEnabled } = this.state;
    144 
    145    if (activeSearch) {
    146      e.preventDefault();
    147      closeActiveSearch();
    148    }
    149 
    150    if (quickOpenEnabled) {
    151      e.preventDefault();
    152      closeQuickOpen();
    153    }
    154 
    155    if (shortcutsModalEnabled) {
    156      e.preventDefault();
    157      this.toggleShortcutsModal();
    158    }
    159  };
    160 
    161  onCommandSlash = () => {
    162    this.toggleShortcutsModal();
    163  };
    164 
    165  isHorizontal() {
    166    return this.props.orientation === "horizontal";
    167  }
    168 
    169  toggleJumpToLine = e => {
    170    this.toggleQuickOpenModal(e, ":");
    171  };
    172 
    173  toggleQuickOpenModal = (e, query) => {
    174    const { quickOpenEnabled, openQuickOpen, closeQuickOpen } = this.props;
    175 
    176    e.preventDefault();
    177    e.stopPropagation();
    178 
    179    if (quickOpenEnabled === true) {
    180      closeQuickOpen();
    181      return;
    182    }
    183 
    184    if (query != null) {
    185      openQuickOpen(query);
    186      return;
    187    }
    188    openQuickOpen();
    189  };
    190 
    191  onLayoutChange = () => {
    192    this.setOrientation();
    193  };
    194 
    195  setOrientation() {
    196    // If the orientation does not match (if it is not visible) it will
    197    // not setOrientation, or if it is the same as before, calling
    198    // setOrientation will not cause a rerender.
    199    if (horizontalLayoutBreakpoint.matches) {
    200      this.props.setOrientation("horizontal");
    201    } else if (verticalLayoutBreakpoint.matches) {
    202      this.props.setOrientation("vertical");
    203    }
    204  }
    205 
    206  closeSourceMapError = () => {
    207    this.setState({ hiddenSourceMapError: this.props.sourceMapError });
    208  };
    209 
    210  renderEditorNotificationBar() {
    211    if (
    212      this.props.sourceMapError &&
    213      this.state.hiddenSourceMapError != this.props.sourceMapError
    214    ) {
    215      return div(
    216        { className: "editor-notification-footer", "aria-role": "status" },
    217        span(
    218          { className: "info icon" },
    219          React.createElement(DebuggerImage, { name: "sourcemap" })
    220        ),
    221        `Source Map Error: ${this.props.sourceMapError}`,
    222        button({ className: "close-button", onClick: this.closeSourceMapError })
    223      );
    224    }
    225    if (this.props.showOriginalVariableMappingWarning) {
    226      return div(
    227        { className: "editor-notification-footer", "aria-role": "status" },
    228        span(
    229          { className: "info icon" },
    230          React.createElement(DebuggerImage, { name: "sourcemap" })
    231        ),
    232        L10N.getFormatStr(
    233          "editorNotificationFooter.noOriginalScopes",
    234          L10N.getStr("scopes.showOriginalScopes")
    235        )
    236      );
    237    }
    238    return null;
    239  }
    240 
    241  renderEditorPane = () => {
    242    const { startPanelCollapsed, endPanelCollapsed } = this.props;
    243    const { endPanelSize, startPanelSize } = this.state;
    244    const horizontal = this.isHorizontal();
    245    return main(
    246      {
    247        className: "editor-pane",
    248      },
    249      div(
    250        {
    251          className: "editor-container",
    252        },
    253        React.createElement(EditorTabs, {
    254          startPanelCollapsed,
    255          endPanelCollapsed,
    256          horizontal,
    257        }),
    258        React.createElement(Editor, {
    259          startPanelSize,
    260          endPanelSize,
    261        }),
    262        this.props.showWelcomeBox
    263          ? React.createElement(WelcomeBox, {
    264              horizontal,
    265              toggleShortcutsModal: () => this.toggleShortcutsModal(),
    266            })
    267          : null,
    268        this.renderEditorNotificationBar(),
    269        React.createElement(EditorFooter, {
    270          horizontal,
    271        })
    272      )
    273    );
    274  };
    275 
    276  toggleShortcutsModal() {
    277    this.setState(prevState => ({
    278      shortcutsModalEnabled: !prevState.shortcutsModalEnabled,
    279    }));
    280  }
    281 
    282  // Important so that the tabs chevron updates appropriately when
    283  // the user resizes the left or right columns
    284  triggerEditorPaneResize() {
    285    const editorPane = window.document.querySelector(".editor-pane");
    286    if (editorPane) {
    287      editorPane.dispatchEvent(new Event("resizeend"));
    288    }
    289  }
    290 
    291  renderLayout = () => {
    292    const { startPanelCollapsed, endPanelCollapsed } = this.props;
    293    const horizontal = this.isHorizontal();
    294    return React.createElement(SplitBox, {
    295      style: {
    296        width: "100vw",
    297      },
    298      initialSize: prefs.endPanelSize,
    299      minSize: 30,
    300      maxSize: "70%",
    301      splitterSize: 1,
    302      vert: horizontal,
    303      onResizeEnd: num => {
    304        prefs.endPanelSize = num;
    305        this.triggerEditorPaneResize();
    306      },
    307      startPanel: React.createElement(SplitBox, {
    308        style: {
    309          width: "100vw",
    310        },
    311        initialSize: prefs.startPanelSize,
    312        minSize: 30,
    313        maxSize: "85%",
    314        splitterSize: 1,
    315        onResizeEnd: num => {
    316          prefs.startPanelSize = num;
    317          this.triggerEditorPaneResize();
    318        },
    319        startPanelCollapsed,
    320        startPanel: React.createElement(PrimaryPanes, {
    321          horizontal,
    322        }),
    323        endPanel: this.renderEditorPane(),
    324      }),
    325      endPanelControl: true,
    326      endPanel: React.createElement(SecondaryPanes, {
    327        horizontal,
    328      }),
    329      endPanelCollapsed,
    330    });
    331  };
    332 
    333  render() {
    334    const { quickOpenEnabled } = this.props;
    335    return div(
    336      {
    337        className: "debugger",
    338      },
    339      React.createElement(
    340        AppErrorBoundary,
    341        {
    342          componentName: "Debugger",
    343          panel: L10N.getStr("ToolboxDebugger.label"),
    344        },
    345        this.renderLayout(),
    346        quickOpenEnabled === true &&
    347          React.createElement(QuickOpenModal, {
    348            shortcutsModalEnabled: this.state.shortcutsModalEnabled,
    349            toggleShortcutsModal: () => this.toggleShortcutsModal(),
    350          }),
    351        React.createElement(ShortcutsModal, {
    352          enabled: this.state.shortcutsModalEnabled,
    353          handleClose: () => this.toggleShortcutsModal(),
    354        })
    355      )
    356    );
    357  }
    358 }
    359 
    360 App.childContextTypes = {
    361  toolboxDoc: PropTypes.object,
    362  shortcuts: PropTypes.object,
    363  l10n: PropTypes.object,
    364  fluentBundles: PropTypes.array,
    365 };
    366 
    367 const mapStateToProps = state => {
    368  const selectedLocation = getSelectedLocation(state);
    369  const mapScopeEnabled = isMapScopesEnabled(state);
    370  const isPaused = getIsCurrentThreadPaused(state);
    371 
    372  const showOriginalVariableMappingWarning =
    373    isPaused &&
    374    selectedLocation?.source.isOriginal &&
    375    !selectedLocation?.source.isPrettyPrinted &&
    376    !mapScopeEnabled;
    377 
    378  return {
    379    showOriginalVariableMappingWarning,
    380    showWelcomeBox: !selectedLocation,
    381    startPanelCollapsed: getPaneCollapse(state, "start"),
    382    endPanelCollapsed: getPaneCollapse(state, "end"),
    383    activeSearch: getActiveSearch(state),
    384    quickOpenEnabled: getQuickOpenEnabled(state),
    385    orientation: getOrientation(state),
    386    sourceMapError: selectedLocation?.sourceActor
    387      ? getSourceMapErrorForSourceActor(state, selectedLocation.sourceActor.id)
    388      : null,
    389  };
    390 };
    391 
    392 export default connect(mapStateToProps, {
    393  setActiveSearch: actions.setActiveSearch,
    394  closeActiveSearch: actions.closeActiveSearch,
    395  openQuickOpen: actions.openQuickOpen,
    396  closeQuickOpen: actions.closeQuickOpen,
    397  setOrientation: actions.setOrientation,
    398  setPrimaryPaneTab: actions.setPrimaryPaneTab,
    399 })(App);