/* 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/>. */
import React, { Component } from
"devtools/client/shared/vendor/react";
import {
button,
div,
main,
span,
} from
"devtools/client/shared/vendor/react-dom-factories";
import PropTypes from
"devtools/client/shared/vendor/react-prop-types";
import { connect } from
"devtools/client/shared/vendor/react-redux";
import { prefs } from
"../utils/prefs";
import { primaryPaneTabs } from
"../constants";
import actions from
"../actions/index";
import AccessibleImage from
"./shared/AccessibleImage";
import {
getSelectedLocation,
getPaneCollapse,
getActiveSearch,
getQuickOpenEnabled,
getOrientation,
getIsCurrentThreadPaused,
isMapScopesEnabled,
getSourceMapErrorForSourceActor,
} from
"../selectors/index";
const KeyShortcuts = require(
"resource://devtools/client/shared/key-shortcuts.js");
const SplitBox = require(
"resource://devtools/client/shared/components/splitter/SplitBox.js");
const AppErrorBoundary = require(
"resource://devtools/client/shared/components/AppErrorBoundary.js");
const shortcuts =
new KeyShortcuts({ window });
const horizontalLayoutBreakpoint = window.matchMedia(
"(min-width: 800px)");
const verticalLayoutBreakpoint = window.matchMedia(
"(min-width: 10px) and (max-width: 799px)"
);
import { ShortcutsModal } from
"./ShortcutsModal";
import PrimaryPanes from
"./PrimaryPanes/index";
import Editor from
"./Editor/index";
import SecondaryPanes from
"./SecondaryPanes/index";
import WelcomeBox from
"./WelcomeBox";
import EditorTabs from
"./Editor/Tabs";
import EditorFooter from
"./Editor/Footer";
import QuickOpenModal from
"./QuickOpenModal";
class App
extends Component {
constructor(props) {
super(props);
this.state = {
shortcutsModalEnabled:
false,
startPanelSize: 0,
endPanelSize: 0,
};
}
static get propTypes() {
return {
activeSearch: PropTypes.oneOf([
"file",
"project"]),
closeActiveSearch: PropTypes.func.isRequired,
closeQuickOpen: PropTypes.func.isRequired,
endPanelCollapsed: PropTypes.bool.isRequired,
fluentBundles: PropTypes.array.isRequired,
openQuickOpen: PropTypes.func.isRequired,
orientation: PropTypes.oneOf([
"horizontal",
"vertical"]).isRequired,
quickOpenEnabled: PropTypes.bool.isRequired,
selectedLocation: PropTypes.object,
setActiveSearch: PropTypes.func.isRequired,
setOrientation: PropTypes.func.isRequired,
setPrimaryPaneTab: PropTypes.func.isRequired,
startPanelCollapsed: PropTypes.bool.isRequired,
toolboxDoc: PropTypes.object.isRequired,
showOriginalVariableMappingWarning: PropTypes.bool,
};
}
getChildContext() {
return {
fluentBundles:
this.props.fluentBundles,
toolboxDoc:
this.props.toolboxDoc,
shortcuts,
l10n: L10N,
};
}
componentDidMount() {
horizontalLayoutBreakpoint.addListener(
this.onLayoutChange);
verticalLayoutBreakpoint.addListener(
this.onLayoutChange);
this.setOrientation();
shortcuts.on(L10N.getStr(
"symbolSearch.search.key2"), e =>
this.toggleQuickOpenModal(e,
"@")
);
[
L10N.getStr(
"sources.search.key2"),
L10N.getStr(
"sources.search.alt.key"),
].forEach(key => shortcuts.on(key,
this.toggleQuickOpenModal));
shortcuts.on(L10N.getStr(
"gotoLineModal.key3"), e =>
this.toggleQuickOpenModal(e,
":")
);
shortcuts.on(
L10N.getStr(
"projectTextSearch.key"),
this.jumpToProjectSearch
);
shortcuts.on(
"Escape",
this.onEscape);
shortcuts.on(
"CmdOrCtrl+/",
this.onCommandSlash);
}
componentWillUnmount() {
horizontalLayoutBreakpoint.removeListener(
this.onLayoutChange);
verticalLayoutBreakpoint.removeListener(
this.onLayoutChange);
shortcuts.off(
L10N.getStr(
"symbolSearch.search.key2"),
this.toggleQuickOpenModal
);
[
L10N.getStr(
"sources.search.key2"),
L10N.getStr(
"sources.search.alt.key"),
].forEach(key => shortcuts.off(key,
this.toggleQuickOpenModal));
shortcuts.off(L10N.getStr(
"gotoLineModal.key3"),
this.toggleQuickOpenModal);
shortcuts.off(
L10N.getStr(
"projectTextSearch.key"),
this.jumpToProjectSearch
);
shortcuts.off(
"Escape",
this.onEscape);
shortcuts.off(
"CmdOrCtrl+/",
this.onCommandSlash);
}
jumpToProjectSearch = e => {
e.preventDefault();
this.props.setPrimaryPaneTab(primaryPaneTabs.PROJECT_SEARCH);
this.props.setActiveSearch(primaryPaneTabs.PROJECT_SEARCH);
};
onEscape = e => {
const {
activeSearch,
closeActiveSearch,
closeQuickOpen,
quickOpenEnabled,
} =
this.props;
const { shortcutsModalEnabled } =
this.state;
if (activeSearch) {
e.preventDefault();
closeActiveSearch();
}
if (quickOpenEnabled) {
e.preventDefault();
closeQuickOpen();
}
if (shortcutsModalEnabled) {
e.preventDefault();
this.toggleShortcutsModal();
}
};
onCommandSlash = () => {
this.toggleShortcutsModal();
};
isHorizontal() {
return this.props.orientation ===
"horizontal";
}
toggleQuickOpenModal = (e, query) => {
const { quickOpenEnabled, openQuickOpen, closeQuickOpen } =
this.props;
e.preventDefault();
e.stopPropagation();
if (quickOpenEnabled ===
true) {
closeQuickOpen();
return;
}
if (query !=
null) {
openQuickOpen(query);
return;
}
openQuickOpen();
};
onLayoutChange = () => {
this.setOrientation();
};
setOrientation() {
// If the orientation does not match (if it is not visible) it will
// not setOrientation, or if it is the same as before, calling
// setOrientation will not cause a rerender.
if (horizontalLayoutBreakpoint.matches) {
this.props.setOrientation(
"horizontal");
}
else if (verticalLayoutBreakpoint.matches) {
this.props.setOrientation(
"vertical");
}
}
closeSourceMapError = () => {
this.setState({ hiddenSourceMapError:
this.props.sourceMapError });
};
renderEditorNotificationBar() {
if (
this.props.sourceMapError &&
this.state.hiddenSourceMapError !=
this.props.sourceMapError
) {
return div(
{ className:
"editor-notification-footer",
"aria-role":
"status" },
span(
{ className:
"info icon" },
React.createElement(AccessibleImage, { className:
"sourcemap" })
),
`Source Map Error: ${
this.props.sourceMapError}`,
button({ className:
"close-button", onClick:
this.closeSourceMapError })
);
}
if (
this.props.showOriginalVariableMappingWarning) {
return div(
{ className:
"editor-notification-footer",
"aria-role":
"status" },
span(
{ className:
"info icon" },
React.createElement(AccessibleImage, { className:
"sourcemap" })
),
L10N.getFormatStr(
"editorNotificationFooter.noOriginalScopes",
L10N.getStr(
"scopes.showOriginalScopes")
)
);
}
return null;
}
renderEditorPane = () => {
const { startPanelCollapsed, endPanelCollapsed } =
this.props;
const { endPanelSize, startPanelSize } =
this.state;
const horizontal =
this.isHorizontal();
return main(
{
className:
"editor-pane",
},
div(
{
className:
"editor-container",
},
React.createElement(EditorTabs, {
startPanelCollapsed,
endPanelCollapsed,
horizontal,
}),
React.createElement(Editor, {
startPanelSize,
endPanelSize,
}),
!
this.props.selectedLocation
? React.createElement(WelcomeBox, {
horizontal,
toggleShortcutsModal: () =>
this.toggleShortcutsModal(),
})
:
null,
this.renderEditorNotificationBar(),
React.createElement(EditorFooter, {
horizontal,
})
)
);
};
toggleShortcutsModal() {
this.setState(prevState => ({
shortcutsModalEnabled: !prevState.shortcutsModalEnabled,
}));
}
// Important so that the tabs chevron updates appropriately when
// the user resizes the left or right columns
triggerEditorPaneResize() {
const editorPane = window.document.querySelector(
".editor-pane");
if (editorPane) {
editorPane.dispatchEvent(
new Event(
"resizeend"));
}
}
renderLayout = () => {
const { startPanelCollapsed, endPanelCollapsed } =
this.props;
const horizontal =
this.isHorizontal();
return React.createElement(SplitBox, {
style: {
width:
"100vw",
},
initialSize: prefs.endPanelSize,
minSize: 30,
maxSize:
"70%",
splitterSize: 1,
vert: horizontal,
onResizeEnd: num => {
prefs.endPanelSize = num;
this.triggerEditorPaneResize();
},
startPanel: React.createElement(SplitBox, {
style: {
width:
"100vw",
},
initialSize: prefs.startPanelSize,
minSize: 30,
maxSize:
"85%",
splitterSize: 1,
onResizeEnd: num => {
prefs.startPanelSize = num;
this.triggerEditorPaneResize();
},
startPanelCollapsed,
startPanel: React.createElement(PrimaryPanes, {
horizontal,
}),
endPanel:
this.renderEditorPane(),
}),
endPanelControl:
true,
endPanel: React.createElement(SecondaryPanes, {
horizontal,
}),
endPanelCollapsed,
});
};
render() {
const { quickOpenEnabled } =
this.props;
return div(
{
className:
"debugger",
},
React.createElement(
AppErrorBoundary,
{
componentName:
"Debugger",
panel: L10N.getStr(
"ToolboxDebugger.label"),
},
this.renderLayout(),
quickOpenEnabled ===
true &&
React.createElement(QuickOpenModal, {
shortcutsModalEnabled:
this.state.shortcutsModalEnabled,
toggleShortcutsModal: () =>
this.toggleShortcutsModal(),
}),
React.createElement(ShortcutsModal, {
enabled:
this.state.shortcutsModalEnabled,
handleClose: () =>
this.toggleShortcutsModal(),
})
)
);
}
}
App.childContextTypes = {
toolboxDoc: PropTypes.object,
shortcuts: PropTypes.object,
l10n: PropTypes.object,
fluentBundles: PropTypes.array,
};
const mapStateToProps = state => {
const selectedLocation = getSelectedLocation(state);
const mapScopeEnabled = isMapScopesEnabled(state);
const isPaused = getIsCurrentThreadPaused(state);
const showOriginalVariableMappingWarning =
isPaused &&
selectedLocation?.source.isOriginal &&
!selectedLocation?.source.isPrettyPrinted &&
!mapScopeEnabled;
return {
showOriginalVariableMappingWarning,
selectedLocation,
startPanelCollapsed: getPaneCollapse(state,
"start"),
endPanelCollapsed: getPaneCollapse(state,
"end"),
activeSearch: getActiveSearch(state),
quickOpenEnabled: getQuickOpenEnabled(state),
orientation: getOrientation(state),
sourceMapError: selectedLocation?.sourceActor
? getSourceMapErrorForSourceActor(state, selectedLocation.sourceActor.id)
:
null,
};
};
export
default connect(mapStateToProps, {
setActiveSearch: actions.setActiveSearch,
closeActiveSearch: actions.closeActiveSearch,
openQuickOpen: actions.openQuickOpen,
closeQuickOpen: actions.closeQuickOpen,
setOrientation: actions.setOrientation,
setPrimaryPaneTab: actions.setPrimaryPaneTab,
})(App);