UNPKG

chrome-devtools-frontend

Version:
328 lines (289 loc) • 14.4 kB
// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../core/common/common.js'; import * as i18n from '../../core/i18n/i18n.js'; import throttlingSettingsTabStyles from './throttlingSettingsTab.css.js'; import type * as SDK from '../../core/sdk/sdk.js'; import * as UI from '../../ui/legacy/legacy.js'; const UIStrings = { /** *@description Text in Throttling Settings Tab of the Network panel */ networkThrottlingProfiles: 'Network Throttling Profiles', /** *@description Text of add conditions button in Throttling Settings Tab of the Network panel */ addCustomProfile: 'Add custom profile...', /** *@description A value in milliseconds *@example {3} PH1 */ dms: '{PH1} `ms`', /** *@description Text in Throttling Settings Tab of the Network panel */ profileName: 'Profile Name', /** * @description Label for a textbox that sets the download speed in the Throttling Settings Tab. * Noun, short for 'download speed'. */ download: 'Download', /** * @description Label for a textbox that sets the upload speed in the Throttling Settings Tab. * Noun, short for 'upload speed'. */ upload: 'Upload', /** * @description Label for a textbox that sets the latency in the Throttling Settings Tab. */ latency: 'Latency', /** *@description Text in Throttling Settings Tab of the Network panel */ optional: 'optional', /** *@description Error message for Profile Name input in Throtting pane of the Settings *@example {49} PH1 */ profileNameCharactersLengthMust: 'Profile Name characters length must be between 1 to {PH1} inclusive', /** *@description Error message for Download and Upload inputs in Throttling pane of the Settings *@example {Download} PH1 *@example {0} PH2 *@example {10000000} PH3 */ sMustBeANumberBetweenSkbsToSkbs: '{PH1} must be a number between {PH2} `kbit/s` to {PH3} `kbit/s` inclusive', /** *@description Error message for Latency input in Throttling pane of the Settings *@example {0} PH1 *@example {1000000} PH2 */ latencyMustBeAnIntegerBetweenSms: 'Latency must be an integer between {PH1} `ms` to {PH2} `ms` inclusive', /** * @description Text in Throttling Settings Tab of the Network panel, indicating the download or * upload speed that will be applied in kilobits per second. * @example {25} PH1 */ dskbits: '{PH1} `kbit/s`', /** * @description Text in Throttling Settings Tab of the Network panel, indicating the download or * upload speed that will be applied in megabits per second. * @example {25.4} PH1 */ fsmbits: '{PH1} `Mbit/s`', }; const str_ = i18n.i18n.registerUIStrings('panels/mobile_throttling/ThrottlingSettingsTab.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); let throttlingSettingsTabInstance: ThrottlingSettingsTab; export class ThrottlingSettingsTab extends UI.Widget.VBox implements UI.ListWidget.Delegate<SDK.NetworkManager.Conditions> { private readonly list: UI.ListWidget.ListWidget<SDK.NetworkManager.Conditions>; private readonly customSetting: Common.Settings.Setting<SDK.NetworkManager.Conditions[]>; private editor?: UI.ListWidget.Editor<SDK.NetworkManager.Conditions>; constructor() { super(true); const header = this.contentElement.createChild('div', 'header'); header.textContent = i18nString(UIStrings.networkThrottlingProfiles); UI.ARIAUtils.markAsHeading(header, 1); const addButton = UI.UIUtils.createTextButton( i18nString(UIStrings.addCustomProfile), this.addButtonClicked.bind(this), 'add-conditions-button'); this.contentElement.appendChild(addButton); this.list = new UI.ListWidget.ListWidget(this); this.list.element.classList.add('conditions-list'); this.list.show(this.contentElement); this.customSetting = Common.Settings.Settings.instance().moduleSetting('customNetworkConditions'); this.customSetting.addChangeListener(this.conditionsUpdated, this); this.setDefaultFocusedElement(addButton); } static instance(opts = {forceNew: null}): ThrottlingSettingsTab { const {forceNew} = opts; if (!throttlingSettingsTabInstance || forceNew) { throttlingSettingsTabInstance = new ThrottlingSettingsTab(); } return throttlingSettingsTabInstance; } override wasShown(): void { super.wasShown(); this.list.registerCSSFiles([throttlingSettingsTabStyles]); this.registerCSSFiles([throttlingSettingsTabStyles]); this.conditionsUpdated(); } private conditionsUpdated(): void { this.list.clear(); const conditions = this.customSetting.get(); for (let i = 0; i < conditions.length; ++i) { this.list.appendItem(conditions[i], true); } this.list.appendSeparator(); } private addButtonClicked(): void { this.list.addNewItem(this.customSetting.get().length, {title: () => '', download: -1, upload: -1, latency: 0}); } renderItem(conditions: SDK.NetworkManager.Conditions, _editable: boolean): Element { const element = document.createElement('div'); element.classList.add('conditions-list-item'); const title = element.createChild('div', 'conditions-list-text conditions-list-title'); const titleText = title.createChild('div', 'conditions-list-title-text'); const castedTitle = this.retrieveOptionsTitle(conditions); titleText.textContent = castedTitle; UI.Tooltip.Tooltip.install(titleText, castedTitle); element.createChild('div', 'conditions-list-separator'); element.createChild('div', 'conditions-list-text').textContent = throughputText(conditions.download); element.createChild('div', 'conditions-list-separator'); element.createChild('div', 'conditions-list-text').textContent = throughputText(conditions.upload); element.createChild('div', 'conditions-list-separator'); element.createChild('div', 'conditions-list-text').textContent = i18nString(UIStrings.dms, {PH1: conditions.latency}); return element; } removeItemRequested(_item: SDK.NetworkManager.Conditions, index: number): void { const list = this.customSetting.get(); list.splice(index, 1); this.customSetting.set(list); } retrieveOptionsTitle(conditions: SDK.NetworkManager.Conditions): string { // The title is usually an i18nLazyString except for custom values that are stored in the local storage in the form of a string. const castedTitle = typeof conditions.title === 'function' ? conditions.title() : conditions.title; return castedTitle; } commitEdit( conditions: SDK.NetworkManager.Conditions, editor: UI.ListWidget.Editor<SDK.NetworkManager.Conditions>, isNew: boolean): void { conditions.title = editor.control('title').value.trim(); const download = editor.control('download').value.trim(); conditions.download = download ? parseInt(download, 10) * (1000 / 8) : -1; const upload = editor.control('upload').value.trim(); conditions.upload = upload ? parseInt(upload, 10) * (1000 / 8) : -1; const latency = editor.control('latency').value.trim(); conditions.latency = latency ? parseInt(latency, 10) : 0; const list = this.customSetting.get(); if (isNew) { list.push(conditions); } this.customSetting.set(list); } beginEdit(conditions: SDK.NetworkManager.Conditions): UI.ListWidget.Editor<SDK.NetworkManager.Conditions> { const editor = this.createEditor(); editor.control('title').value = this.retrieveOptionsTitle(conditions); editor.control('download').value = conditions.download <= 0 ? '' : String(conditions.download / (1000 / 8)); editor.control('upload').value = conditions.upload <= 0 ? '' : String(conditions.upload / (1000 / 8)); editor.control('latency').value = conditions.latency ? String(conditions.latency) : ''; return editor; } private createEditor(): UI.ListWidget.Editor<SDK.NetworkManager.Conditions> { if (this.editor) { return this.editor; } const editor = new UI.ListWidget.Editor<SDK.NetworkManager.Conditions>(); this.editor = editor; const content = editor.contentElement(); const titles = content.createChild('div', 'conditions-edit-row'); const nameLabel = titles.createChild('div', 'conditions-list-text conditions-list-title'); const nameStr = i18nString(UIStrings.profileName); const nameLabelText = nameLabel.createChild('div', 'conditions-list-title-text'); nameLabelText.textContent = nameStr; titles.createChild('div', 'conditions-list-separator conditions-list-separator-invisible'); const downloadLabel = titles.createChild('div', 'conditions-list-text'); const downloadStr = i18nString(UIStrings.download); const downloadLabelText = downloadLabel.createChild('div', 'conditions-list-title-text'); downloadLabelText.textContent = downloadStr; titles.createChild('div', 'conditions-list-separator conditions-list-separator-invisible'); const uploadLabel = titles.createChild('div', 'conditions-list-text'); const uploadLabelText = uploadLabel.createChild('div', 'conditions-list-title-text'); const uploadStr = i18nString(UIStrings.upload); uploadLabelText.textContent = uploadStr; titles.createChild('div', 'conditions-list-separator conditions-list-separator-invisible'); const latencyLabel = titles.createChild('div', 'conditions-list-text'); const latencyStr = i18nString(UIStrings.latency); const latencyLabelText = latencyLabel.createChild('div', 'conditions-list-title-text'); latencyLabelText.textContent = latencyStr; const fields = content.createChild('div', 'conditions-edit-row'); const nameInput = editor.createInput('title', 'text', '', titleValidator); UI.ARIAUtils.setAccessibleName(nameInput, nameStr); fields.createChild('div', 'conditions-list-text conditions-list-title').appendChild(nameInput); fields.createChild('div', 'conditions-list-separator conditions-list-separator-invisible'); let cell = fields.createChild('div', 'conditions-list-text'); const downloadInput = editor.createInput('download', 'text', i18n.i18n.lockedString('kbit/s'), throughputValidator); cell.appendChild(downloadInput); UI.ARIAUtils.setAccessibleName(downloadInput, downloadStr); const downloadOptional = cell.createChild('div', 'conditions-edit-optional'); const optionalStr = i18nString(UIStrings.optional); downloadOptional.textContent = optionalStr; UI.ARIAUtils.setDescription(downloadInput, optionalStr); fields.createChild('div', 'conditions-list-separator conditions-list-separator-invisible'); cell = fields.createChild('div', 'conditions-list-text'); const uploadInput = editor.createInput('upload', 'text', i18n.i18n.lockedString('kbit/s'), throughputValidator); UI.ARIAUtils.setAccessibleName(uploadInput, uploadStr); cell.appendChild(uploadInput); const uploadOptional = cell.createChild('div', 'conditions-edit-optional'); uploadOptional.textContent = optionalStr; UI.ARIAUtils.setDescription(uploadInput, optionalStr); fields.createChild('div', 'conditions-list-separator conditions-list-separator-invisible'); cell = fields.createChild('div', 'conditions-list-text'); const latencyInput = editor.createInput('latency', 'text', i18n.i18n.lockedString('ms'), latencyValidator); UI.ARIAUtils.setAccessibleName(latencyInput, latencyStr); cell.appendChild(latencyInput); const latencyOptional = cell.createChild('div', 'conditions-edit-optional'); latencyOptional.textContent = optionalStr; UI.ARIAUtils.setDescription(latencyInput, optionalStr); return editor; function titleValidator(_item: SDK.NetworkManager.Conditions, _index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult { const maxLength = 49; const value = input.value.trim(); const valid = value.length > 0 && value.length <= maxLength; if (!valid) { const errorMessage = i18nString(UIStrings.profileNameCharactersLengthMust, {PH1: maxLength}); return {valid, errorMessage}; } return {valid, errorMessage: undefined}; } function throughputValidator( _item: SDK.NetworkManager.Conditions, _index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult { const minThroughput = 0; const maxThroughput = 10000000; const value = input.value.trim(); const parsedValue = Number(value); const throughput = input.getAttribute('aria-label'); const valid = !Number.isNaN(parsedValue) && parsedValue >= minThroughput && parsedValue <= maxThroughput; if (!valid) { const errorMessage = i18nString( UIStrings.sMustBeANumberBetweenSkbsToSkbs, {PH1: String(throughput), PH2: minThroughput, PH3: maxThroughput}); return {valid, errorMessage}; } return {valid, errorMessage: undefined}; } function latencyValidator(_item: SDK.NetworkManager.Conditions, _index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult { const minLatency = 0; const maxLatency = 1000000; const value = input.value.trim(); const parsedValue = Number(value); const valid = Number.isInteger(parsedValue) && parsedValue >= minLatency && parsedValue <= maxLatency; if (!valid) { const errorMessage = i18nString(UIStrings.latencyMustBeAnIntegerBetweenSms, {PH1: minLatency, PH2: maxLatency}); return {valid, errorMessage}; } return {valid, errorMessage: undefined}; } } } function throughputText(throughput: number): string { if (throughput < 0) { return ''; } const throughputInKbps = throughput / (1000 / 8); if (throughputInKbps < 1000) { return i18nString(UIStrings.dskbits, {PH1: throughputInKbps}); } if (throughputInKbps < 1000 * 10) { const formattedResult = (throughputInKbps / 1000).toFixed(1); return i18nString(UIStrings.fsmbits, {PH1: formattedResult}); } // TODO(petermarshall): Figure out if there is a difference we need to tell i18n about // for these two versions: one with decimal places and one without. return i18nString(UIStrings.fsmbits, {PH1: (throughputInKbps / 1000) | 0}); }