diff options
Diffstat (limited to 'devtools/client/aboutdebugging/src/components/connect')
11 files changed, 817 insertions, 0 deletions
diff --git a/devtools/client/aboutdebugging/src/components/connect/ConnectPage.css b/devtools/client/aboutdebugging/src/components/connect/ConnectPage.css new file mode 100644 index 0000000000..a693bf4113 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/ConnectPage.css @@ -0,0 +1,50 @@ +/* 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/. */ + +.connect-page__breather { + margin-block-start: calc(var(--base-unit) * 6); +} + +/* + * +--------+----------------------+ + * | USB | |<button> | + * +--------+ | | + * | status | | | + * +--------+----------------------+ + */ +.connect-page__usb-section__heading { + display: grid; + align-items: center; + grid-template-areas: "title . toggle" + "status . toggle"; + grid-template-columns: auto 1fr auto; + grid-column-gap: calc(var(--base-unit) * 2); + grid-row-gap: var(--base-unit); +} + +.connect-page__usb-section__heading__toggle { + grid-area: toggle; +} + +.connect-page__usb-section__heading__title { + grid-area: title; + line-height: 1; +} +.connect-page__usb-section__heading__status { + grid-area: status; + line-height: 1; + font-size: var(--caption-20-font-size); + font-weight: var(--caption-20-font-weight); + color: var(--secondary-text-color); +} + +.connect-page__troubleshoot { + font-size: var(--body-10-font-size); + font-weight: var(--body-10-font-weight); + margin-block-start: calc(var(--base-unit) * 2); +} + +.connect-page__troubleshoot--network { + padding-inline: calc(var(--base-unit) * 6); +} diff --git a/devtools/client/aboutdebugging/src/components/connect/ConnectPage.js b/devtools/client/aboutdebugging/src/components/connect/ConnectPage.js new file mode 100644 index 0000000000..97b1b01df7 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/ConnectPage.js @@ -0,0 +1,315 @@ +/* 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 { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const { + USB_STATES, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); + +loader.lazyRequireGetter( + this, + "ADB_ADDON_STATES", + "resource://devtools/client/shared/remote-debugging/adb/adb-addon.js", + true +); + +const Link = createFactory( + require("resource://devtools/client/shared/vendor/react-router-dom.js").Link +); +const ConnectSection = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/connect/ConnectSection.js") +); +const ConnectSteps = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/connect/ConnectSteps.js") +); +const NetworkLocationsForm = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/connect/NetworkLocationsForm.js") +); +const NetworkLocationsList = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/connect/NetworkLocationsList.js") +); + +const { + PAGE_TYPES, + RUNTIMES, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); +const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js"); + +const USB_ICON_SRC = + "chrome://devtools/skin/images/aboutdebugging-usb-icon.svg"; +const GLOBE_ICON_SRC = + "chrome://devtools/skin/images/aboutdebugging-globe-icon.svg"; + +const TROUBLESHOOT_USB_URL = + "https://firefox-source-docs.mozilla.org/devtools-user/about_colon_debugging/index.html#connecting-to-a-remote-device"; +const TROUBLESHOOT_NETWORK_URL = + "https://firefox-source-docs.mozilla.org/devtools-user/about_colon_debugging/index.html#connecting-over-the-network"; + +class ConnectPage extends PureComponent { + static get propTypes() { + return { + adbAddonStatus: Types.adbAddonStatus, + dispatch: PropTypes.func.isRequired, + networkLocations: PropTypes.arrayOf(Types.location).isRequired, + }; + } + + // TODO: avoid the use of this method + // https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 + UNSAFE_componentWillMount() { + this.props.dispatch(Actions.selectPage(PAGE_TYPES.CONNECT)); + } + + onToggleUSBClick() { + const { adbAddonStatus } = this.props; + const isAddonInstalled = adbAddonStatus === ADB_ADDON_STATES.INSTALLED; + if (isAddonInstalled) { + this.props.dispatch(Actions.uninstallAdbAddon()); + } else { + this.props.dispatch(Actions.installAdbAddon()); + } + } + + getUsbStatus() { + switch (this.props.adbAddonStatus) { + case ADB_ADDON_STATES.INSTALLED: + return USB_STATES.ENABLED_USB; + case ADB_ADDON_STATES.UNINSTALLED: + return USB_STATES.DISABLED_USB; + default: + return USB_STATES.UPDATING_USB; + } + } + + renderUsbStatus() { + const statusTextId = { + [USB_STATES.ENABLED_USB]: "about-debugging-setup-usb-status-enabled", + [USB_STATES.DISABLED_USB]: "about-debugging-setup-usb-status-disabled", + [USB_STATES.UPDATING_USB]: "about-debugging-setup-usb-status-updating", + }[this.getUsbStatus()]; + + return Localized( + { + id: statusTextId, + }, + dom.span( + { + className: "connect-page__usb-section__heading__status", + }, + statusTextId + ) + ); + } + + renderUsbToggleButton() { + const usbStatus = this.getUsbStatus(); + + const localizedStates = { + [USB_STATES.ENABLED_USB]: "about-debugging-setup-usb-disable-button", + [USB_STATES.DISABLED_USB]: "about-debugging-setup-usb-enable-button", + [USB_STATES.UPDATING_USB]: "about-debugging-setup-usb-updating-button", + }; + const localizedState = localizedStates[usbStatus]; + + // Disable the button while the USB status is updating. + const disabled = usbStatus === USB_STATES.UPDATING_USB; + + return Localized( + { + id: localizedState, + }, + dom.button( + { + className: + "default-button connect-page__usb-section__heading__toggle " + + "qa-connect-usb-toggle-button", + disabled, + onClick: () => this.onToggleUSBClick(), + }, + localizedState + ) + ); + } + + renderUsb() { + const { adbAddonStatus } = this.props; + const isAddonInstalled = adbAddonStatus === ADB_ADDON_STATES.INSTALLED; + return ConnectSection( + { + icon: USB_ICON_SRC, + title: dom.div( + { + className: "connect-page__usb-section__heading", + }, + Localized( + { id: "about-debugging-setup-usb-title" }, + dom.span( + { + className: "connect-page__usb-section__heading__title", + }, + "USB" + ) + ), + this.renderUsbStatus(), + this.renderUsbToggleButton() + ), + }, + isAddonInstalled + ? ConnectSteps({ + steps: [ + { + localizationId: + "about-debugging-setup-usb-step-enable-dev-menu2", + }, + { + localizationId: "about-debugging-setup-usb-step-enable-debug2", + }, + { + localizationId: + "about-debugging-setup-usb-step-enable-debug-firefox2", + }, + { + localizationId: "about-debugging-setup-usb-step-plug-device", + }, + ], + }) + : Localized( + { + id: "about-debugging-setup-usb-disabled", + }, + dom.aside( + { + className: "qa-connect-usb-disabled-message", + }, + "Enabling this will download and add the required Android USB debugging " + + "components to Firefox." + ) + ), + this.renderTroubleshootText(RUNTIMES.USB) + ); + } + + renderNetwork() { + const { dispatch, networkLocations } = this.props; + + return Localized( + { + id: "about-debugging-setup-network", + attrs: { title: true }, + }, + ConnectSection({ + icon: GLOBE_ICON_SRC, + title: "Network Location", + extraContent: dom.div( + {}, + NetworkLocationsList({ dispatch, networkLocations }), + NetworkLocationsForm({ dispatch, networkLocations }), + this.renderTroubleshootText(RUNTIMES.NETWORK) + ), + }) + ); + } + + renderTroubleshootText(connectionType) { + const localizationId = + connectionType === RUNTIMES.USB + ? "about-debugging-setup-usb-troubleshoot" + : "about-debugging-setup-network-troubleshoot"; + + const className = + "connect-page__troubleshoot connect-page__troubleshoot--" + + `${connectionType === RUNTIMES.USB ? "usb" : "network"}`; + + const url = + connectionType === RUNTIMES.USB + ? TROUBLESHOOT_USB_URL + : TROUBLESHOOT_NETWORK_URL; + + return dom.aside( + { + className, + }, + Localized( + { + id: localizationId, + a: dom.a({ + href: url, + target: "_blank", + }), + }, + dom.p({}, localizationId) + ) + ); + } + + render() { + return dom.article( + { + className: "page connect-page qa-connect-page", + }, + Localized( + { + id: "about-debugging-setup-title", + }, + dom.h1( + { + className: "alt-heading alt-heading--larger", + }, + "Setup" + ) + ), + Localized( + { + id: "about-debugging-setup-intro", + }, + dom.p( + {}, + "Configure the connection method you wish to remotely debug your device with." + ) + ), + Localized( + { + id: "about-debugging-setup-this-firefox2", + a: Link({ + to: `/runtime/${RUNTIMES.THIS_FIREFOX}`, + }), + }, + dom.p({}, "about-debugging-setup-this-firefox") + ), + dom.section( + { + className: "connect-page__breather", + }, + Localized( + { + id: "about-debugging-setup-connect-heading", + }, + dom.h2( + { + className: "alt-heading", + }, + "Connect a device" + ) + ), + this.renderUsb(), + this.renderNetwork() + ) + ); + } +} + +module.exports = FluentReact.withLocalization(ConnectPage); diff --git a/devtools/client/aboutdebugging/src/components/connect/ConnectSection.css b/devtools/client/aboutdebugging/src/components/connect/ConnectSection.css new file mode 100644 index 0000000000..4349b147b0 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/ConnectSection.css @@ -0,0 +1,50 @@ +/* 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/. */ + +.connect-section { + --icon-size: calc(var(--base-unit) * 9); + --header-col-gap: calc(var(--base-unit) * 2); + margin-block-end: calc(var(--base-unit) * 4); +} + +/* + * +--------+----------------+ + * | <icon> | <heading> 1fr | + * +--------+----------------+ + */ +.connect-section__header { + display: grid; + grid-template-areas: "icon heading"; + grid-template-columns: auto 1fr; + grid-template-rows: var(--icon-size); + grid-column-gap: var(--header-col-gap); + align-items: center; + + padding-block-end: calc(var(--base-unit) * 4); + padding-inline: calc(var(--base-unit) * 5); +} + +.connect-section__header__title { + grid-area: heading; +} + +.connect-section__header__icon { + grid-area: icon; + width: var(--icon-size); + height: var(--icon-size); + + -moz-context-properties: fill; + fill: currentColor; +} + +.connect-section__content { + line-height: 1.5; + padding-inline-start: calc(var(--base-unit) * 5 + + var(--header-col-gap) + var(--icon-size)); + padding-inline-end: calc(var(--base-unit) * 5); +} + +.connect-section__extra { + border-block-start: 1px solid var(--card-separator-color); +} diff --git a/devtools/client/aboutdebugging/src/components/connect/ConnectSection.js b/devtools/client/aboutdebugging/src/components/connect/ConnectSection.js new file mode 100644 index 0000000000..55f8eb4e78 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/ConnectSection.js @@ -0,0 +1,69 @@ +/* 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 { + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +class ConnectSection extends PureComponent { + static get propTypes() { + return { + children: PropTypes.node, + className: PropTypes.string, + extraContent: PropTypes.node, + icon: PropTypes.string.isRequired, + title: PropTypes.node.isRequired, + }; + } + + renderExtraContent() { + const { extraContent } = this.props; + return dom.section( + { + className: "connect-section__extra", + }, + extraContent + ); + } + + render() { + const { extraContent } = this.props; + + return dom.section( + { + className: `card connect-section ${this.props.className || ""}`, + }, + dom.header( + { + className: "connect-section__header", + }, + dom.img({ + className: "connect-section__header__icon", + src: this.props.icon, + }), + dom.h1( + { + className: "card__heading connect-section__header__title", + }, + this.props.title + ) + ), + this.props.children + ? dom.div( + { + className: "connect-section__content", + }, + this.props.children + ) + : null, + extraContent ? this.renderExtraContent() : null + ); + } +} + +module.exports = ConnectSection; diff --git a/devtools/client/aboutdebugging/src/components/connect/ConnectSteps.css b/devtools/client/aboutdebugging/src/components/connect/ConnectSteps.css new file mode 100644 index 0000000000..bddd513aa7 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/ConnectSteps.css @@ -0,0 +1,13 @@ +/* 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/. */ + +.connect-page__step-list { + list-style-type: decimal; + list-style-position: outside; + margin-inline-start: calc(var(--base-unit) * 4); +} + +.connect-page__step { + padding-inline-start: var(--base-unit); +} diff --git a/devtools/client/aboutdebugging/src/components/connect/ConnectSteps.js b/devtools/client/aboutdebugging/src/components/connect/ConnectSteps.js new file mode 100644 index 0000000000..0e8d304108 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/ConnectSteps.js @@ -0,0 +1,51 @@ +/* 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 { + PureComponent, + createFactory, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +class ConnectSteps extends PureComponent { + static get propTypes() { + return { + steps: PropTypes.arrayOf( + PropTypes.shape({ + localizationId: PropTypes.string.isRequired, + }).isRequired + ), + }; + } + + render() { + return dom.ul( + { + className: "connect-page__step-list", + }, + ...this.props.steps.map(step => + Localized( + { + id: step.localizationId, + }, + dom.li( + { + className: "connect-page__step", + key: step.localizationId, + }, + step.localizationId + ) + ) + ) + ); + } +} + +module.exports = ConnectSteps; diff --git a/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsForm.css b/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsForm.css new file mode 100644 index 0000000000..5694bcf216 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsForm.css @@ -0,0 +1,23 @@ +/* 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/. */ + +/* + * Layout of a network location form + * + * +-------------+--------------------+------------+ + * | "Host:port" | Input | Add button | + * +-------------+--------------------+------------+ + */ +.connect-page__network-form { + display: grid; + grid-column-gap: calc(var(--base-unit) * 2); + grid-template-columns: auto 1fr auto; + align-items: center; + padding-block-start: calc(var(--base-unit) * 4); + padding-inline: calc(var(--base-unit) * 6); +} + +.connect-page__network-form__error-message { + grid-column: 1 / -1; +} diff --git a/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsForm.js b/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsForm.js new file mode 100644 index 0000000000..347167921b --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsForm.js @@ -0,0 +1,148 @@ +/* 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 { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Message = createFactory( + require("resource://devtools/client/aboutdebugging/src/components/shared/Message.js") +); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); +const { + MESSAGE_LEVEL, +} = require("resource://devtools/client/aboutdebugging/src/constants.js"); +const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js"); + +class NetworkLocationsForm extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + networkLocations: PropTypes.arrayOf(Types.location).isRequired, + }; + } + + constructor(props) { + super(props); + this.state = { + errorHostValue: null, + errorMessageId: null, + value: "", + }; + } + + onSubmit(e) { + const { networkLocations } = this.props; + const { value } = this.state; + + e.preventDefault(); + + if (!value) { + return; + } + + if (!value.match(/[^:]+:\d+/)) { + this.setState({ + errorHostValue: value, + errorMessageId: "about-debugging-network-location-form-invalid", + }); + return; + } + + if (networkLocations.includes(value)) { + this.setState({ + errorHostValue: value, + errorMessageId: "about-debugging-network-location-form-duplicate", + }); + return; + } + + this.props.dispatch(Actions.addNetworkLocation(value)); + this.setState({ errorHostValue: null, errorMessageId: null, value: "" }); + } + + renderError() { + const { errorHostValue, errorMessageId } = this.state; + + if (!errorMessageId) { + return null; + } + + return Message( + { + className: + "connect-page__network-form__error-message " + + "qa-connect-page__network-form__error-message", + level: MESSAGE_LEVEL.ERROR, + isCloseable: true, + }, + Localized( + { + id: errorMessageId, + "$host-value": errorHostValue, + }, + dom.p( + { + className: "technical-text", + }, + errorMessageId + ) + ) + ); + } + + render() { + return dom.form( + { + className: "connect-page__network-form", + onSubmit: e => this.onSubmit(e), + }, + this.renderError(), + Localized( + { + id: "about-debugging-network-locations-host-input-label", + }, + dom.label( + { + htmlFor: "about-debugging-network-locations-host-input", + }, + "Host" + ) + ), + dom.input({ + id: "about-debugging-network-locations-host-input", + className: "default-input qa-network-form-input", + placeholder: "localhost:6080", + type: "text", + value: this.state.value, + onChange: e => { + const value = e.target.value; + this.setState({ value }); + }, + }), + Localized( + { + id: "about-debugging-network-locations-add-button", + }, + dom.button( + { + className: "primary-button qa-network-form-submit-button", + }, + "Add" + ) + ) + ); + } +} + +module.exports = NetworkLocationsForm; diff --git a/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsList.css b/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsList.css new file mode 100644 index 0000000000..e5676b784a --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsList.css @@ -0,0 +1,20 @@ +/* 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/. */ + +/* + * Layout of a network location item + * + * +-------------------------------------+---------------+ + * | Location (eg localhost:8080) | Remove button | + * +-------------------------------------+---------------+ + */ +.network-location { + display: grid; + grid-template-columns: auto max-content; + align-items: center; + + padding-block: calc(var(--base-unit) * 2); + padding-inline: calc(var(--base-unit) * 6); + border-bottom: 1px solid var(--card-separator-color); +} diff --git a/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsList.js b/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsList.js new file mode 100644 index 0000000000..e680fe525f --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/NetworkLocationsList.js @@ -0,0 +1,67 @@ +/* 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 { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); +const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js"); + +class NetworkLocationsList extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + networkLocations: PropTypes.arrayOf(Types.location).isRequired, + }; + } + + renderList() { + return dom.ul( + {}, + this.props.networkLocations.map(location => + dom.li( + { + className: "network-location qa-network-location", + key: location, + }, + dom.span( + { + className: "ellipsis-text qa-network-location-value", + }, + location + ), + Localized( + { + id: "about-debugging-network-locations-remove-button", + }, + dom.button( + { + className: "default-button qa-network-location-remove-button", + onClick: () => { + this.props.dispatch(Actions.removeNetworkLocation(location)); + }, + }, + "Remove" + ) + ) + ) + ) + ); + } + + render() { + return this.props.networkLocations.length ? this.renderList() : null; + } +} + +module.exports = NetworkLocationsList; diff --git a/devtools/client/aboutdebugging/src/components/connect/moz.build b/devtools/client/aboutdebugging/src/components/connect/moz.build new file mode 100644 index 0000000000..9228e80125 --- /dev/null +++ b/devtools/client/aboutdebugging/src/components/connect/moz.build @@ -0,0 +1,11 @@ +# 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( + "ConnectPage.js", + "ConnectSection.js", + "ConnectSteps.js", + "NetworkLocationsForm.js", + "NetworkLocationsList.js", +) |