UNPKG

chrome-devtools-frontend

Version:
367 lines (325 loc) • 15.4 kB
// Copyright 2015 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 i18n from '../../../core/i18n/i18n.js'; import * as EmulationModel from '../../../models/emulation/emulation.js'; import * as UI from '../../../ui/legacy/legacy.js'; import * as EmulationComponents from './components/components.js'; import devicesSettingsTabStyles from './devicesSettingsTab.css.js'; let devicesSettingsTabInstance: DevicesSettingsTab; const UIStrings = { /** *@description Title for a section of the UI that shows all of the devices the user can emulate, in the Device Toolbar. */ emulatedDevices: 'Emulated Devices', /** *@description Button to add a custom device (e.g. phone, tablet) the Device Toolbar. */ addCustomDevice: 'Add custom device...', /** *@description Label/title for UI to add a new custom device type. Device means mobile/tablet etc. */ device: 'Device', /** *@description Placeholder for text input for the name of a custom device. */ deviceName: 'Device Name', /** *@description Placeholder text for text input for the width of a custom device in pixels. */ width: 'Width', /** *@description Placeholder text for text input for the height of a custom device in pixels. */ height: 'Height', /** *@description Placeholder text for text input for the height/width ratio of a custom device in pixels. */ devicePixelRatio: 'Device pixel ratio', /** *@description Label in the Devices settings pane for the user agent string input of a custom device */ userAgentString: 'User agent string', /** *@description Tooltip text for a drop-down in the Devices settings pane, for the 'user agent type' input of a custom device. * 'Type' refers to different options e.g. mobile or desktop. */ userAgentType: 'User agent type', /** *@description Error message in the Devices settings pane that declares the maximum length of the device name input *@example {50} PH1 */ deviceNameMustBeLessThanS: 'Device name must be less than {PH1} characters.', /** *@description Error message in the Devices settings pane that declares that the device name input must not be empty */ deviceNameCannotBeEmpty: 'Device name cannot be empty.', /** *@description Success message for screen readers when device is added. *@example {TestDevice} PH1 */ deviceAddedOrUpdated: 'Device {PH1} successfully added/updated.', }; const str_ = i18n.i18n.registerUIStrings('panels/settings/emulation/DevicesSettingsTab.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export class DevicesSettingsTab extends UI.Widget.VBox implements UI.ListWidget.Delegate<EmulationModel.EmulatedDevices.EmulatedDevice> { containerElement: HTMLElement; private readonly addCustomButton: HTMLButtonElement; private readonly ariaSuccessMessageElement: HTMLElement; private readonly list: UI.ListWidget.ListWidget<EmulationModel.EmulatedDevices.EmulatedDevice>; private muteUpdate: boolean; private emulatedDevicesList: EmulationModel.EmulatedDevices.EmulatedDevicesList; private editor?: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>; private constructor() { super(); this.element.classList.add('settings-tab-container'); this.element.classList.add('devices-settings-tab'); const header = this.element.createChild('header'); UI.UIUtils.createTextChild(header.createChild('h1'), i18nString(UIStrings.emulatedDevices)); this.containerElement = this.element.createChild('div', 'settings-container-wrapper') .createChild('div', 'settings-tab settings-content settings-container'); const buttonsRow = this.containerElement.createChild('div', 'devices-button-row'); this.addCustomButton = UI.UIUtils.createTextButton(i18nString(UIStrings.addCustomDevice), this.addCustomDevice.bind(this)); this.addCustomButton.id = 'custom-device-add-button'; buttonsRow.appendChild(this.addCustomButton); this.ariaSuccessMessageElement = this.containerElement.createChild('div', 'device-success-message'); UI.ARIAUtils.markAsPoliteLiveRegion(this.ariaSuccessMessageElement, false); this.list = new UI.ListWidget.ListWidget(this, false /* delegatesFocus */); this.list.element.classList.add('devices-list'); this.list.show(this.containerElement); this.muteUpdate = false; this.emulatedDevicesList = EmulationModel.EmulatedDevices.EmulatedDevicesList.instance(); this.emulatedDevicesList.addEventListener( EmulationModel.EmulatedDevices.Events.CustomDevicesUpdated, this.devicesUpdated, this); this.emulatedDevicesList.addEventListener( EmulationModel.EmulatedDevices.Events.StandardDevicesUpdated, this.devicesUpdated, this); this.setDefaultFocusedElement(this.addCustomButton); } static instance(): DevicesSettingsTab { if (!devicesSettingsTabInstance) { devicesSettingsTabInstance = new DevicesSettingsTab(); } return devicesSettingsTabInstance; } override wasShown(): void { super.wasShown(); this.devicesUpdated(); this.registerCSSFiles([devicesSettingsTabStyles]); this.list.registerCSSFiles([devicesSettingsTabStyles]); } private devicesUpdated(): void { if (this.muteUpdate) { return; } this.list.clear(); let devices = this.emulatedDevicesList.custom().slice(); for (let i = 0; i < devices.length; ++i) { this.list.appendItem(devices[i], true); } this.list.appendSeparator(); devices = this.emulatedDevicesList.standard().slice(); devices.sort(EmulationModel.EmulatedDevices.EmulatedDevice.deviceComparator); for (let i = 0; i < devices.length; ++i) { this.list.appendItem(devices[i], false); } } private muteAndSaveDeviceList(custom: boolean): void { this.muteUpdate = true; if (custom) { this.emulatedDevicesList.saveCustomDevices(); } else { this.emulatedDevicesList.saveStandardDevices(); } this.muteUpdate = false; } private addCustomDevice(): void { const device = new EmulationModel.EmulatedDevices.EmulatedDevice(); device.deviceScaleFactor = 0; device.horizontal.width = 700; device.horizontal.height = 400; device.vertical.width = 400; device.vertical.height = 700; this.list.addNewItem(this.emulatedDevicesList.custom().length, device); } private toNumericInputValue(value: number): string { return value ? String(value) : ''; } renderItem(device: EmulationModel.EmulatedDevices.EmulatedDevice, editable: boolean): Element { const label = document.createElement('label'); label.classList.add('devices-list-item'); const checkbox = (label.createChild('input', 'devices-list-checkbox') as HTMLInputElement); checkbox.type = 'checkbox'; checkbox.checked = device.show(); checkbox.addEventListener('click', onItemClicked.bind(this), false); const span = document.createElement('span'); span.classList.add('device-name'); span.appendChild(document.createTextNode(device.title)); label.appendChild(span); return label; function onItemClicked(this: DevicesSettingsTab, event: Event): void { const show = checkbox.checked; device.setShow(show); this.muteAndSaveDeviceList(editable); event.consume(); } } removeItemRequested(item: EmulationModel.EmulatedDevices.EmulatedDevice): void { this.emulatedDevicesList.removeCustomDevice(item); } commitEdit( device: EmulationModel.EmulatedDevices.EmulatedDevice, editor: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>, isNew: boolean): void { device.title = editor.control('title').value.trim(); device.vertical.width = editor.control('width').value ? parseInt(editor.control('width').value, 10) : 0; device.vertical.height = editor.control('height').value ? parseInt(editor.control('height').value, 10) : 0; device.horizontal.width = device.vertical.height; device.horizontal.height = device.vertical.width; device.deviceScaleFactor = editor.control('scale').value ? parseFloat(editor.control('scale').value) : 0; device.userAgent = editor.control('user-agent').value; device.modes = []; device.modes.push({ title: '', orientation: EmulationModel.EmulatedDevices.Vertical, insets: new EmulationModel.DeviceModeModel.Insets(0, 0, 0, 0), image: null, }); device.modes.push({ title: '', orientation: EmulationModel.EmulatedDevices.Horizontal, insets: new EmulationModel.DeviceModeModel.Insets(0, 0, 0, 0), image: null, }); device.capabilities = []; const uaType = editor.control('ua-type').value; if (uaType === EmulationModel.DeviceModeModel.UA.Mobile || uaType === EmulationModel.DeviceModeModel.UA.MobileNoTouch) { device.capabilities.push(EmulationModel.EmulatedDevices.Capability.Mobile); } if (uaType === EmulationModel.DeviceModeModel.UA.Mobile || uaType === EmulationModel.DeviceModeModel.UA.DesktopTouch) { device.capabilities.push(EmulationModel.EmulatedDevices.Capability.Touch); } const userAgentControlValue = (editor.control('ua-metadata') as UI.ListWidget.CustomEditorControl<EmulationComponents.UserAgentClientHintsForm.UserAgentClientHintsFormData>) .value.metaData; if (userAgentControlValue) { device.userAgentMetadata = { ...userAgentControlValue, mobile: (uaType === EmulationModel.DeviceModeModel.UA.Mobile || uaType === EmulationModel.DeviceModeModel.UA.MobileNoTouch), }; } if (isNew) { this.emulatedDevicesList.addCustomDevice(device); } else { this.emulatedDevicesList.saveCustomDevices(); } this.addCustomButton.scrollIntoViewIfNeeded(); this.addCustomButton.focus(); this.ariaSuccessMessageElement.setAttribute( 'aria-label', i18nString(UIStrings.deviceAddedOrUpdated, {PH1: device.title})); } beginEdit(device: EmulationModel.EmulatedDevices.EmulatedDevice): UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice> { const editor = this.createEditor(); editor.control('title').value = device.title; editor.control('width').value = this.toNumericInputValue(device.vertical.width); editor.control('height').value = this.toNumericInputValue(device.vertical.height); editor.control('scale').value = this.toNumericInputValue(device.deviceScaleFactor); editor.control('user-agent').value = device.userAgent; let uaType; if (device.mobile()) { uaType = device.touch() ? EmulationModel.DeviceModeModel.UA.Mobile : EmulationModel.DeviceModeModel.UA.MobileNoTouch; } else { uaType = device.touch() ? EmulationModel.DeviceModeModel.UA.DesktopTouch : EmulationModel.DeviceModeModel.UA.Desktop; } editor.control('ua-type').value = uaType; (editor.control('ua-metadata') as UI.ListWidget.CustomEditorControl<EmulationComponents.UserAgentClientHintsForm.UserAgentClientHintsFormData>) .value = {metaData: device.userAgentMetadata || undefined}; return editor; } private createEditor(): UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice> { if (this.editor) { return this.editor; } const editor = new UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>(); this.editor = editor; const content = editor.contentElement(); const deviceFields = content.createChild('div', 'devices-edit-fields'); UI.UIUtils.createTextChild(deviceFields.createChild('b'), i18nString(UIStrings.device)); const deviceNameField = editor.createInput('title', 'text', i18nString(UIStrings.deviceName), titleValidator); deviceFields.createChild('div', 'hbox').appendChild(deviceNameField); deviceNameField.id = 'custom-device-name-field'; const screen = deviceFields.createChild('div', 'hbox'); screen.appendChild(editor.createInput('width', 'text', i18nString(UIStrings.width), widthValidator)); screen.appendChild(editor.createInput('height', 'text', i18nString(UIStrings.height), heightValidator)); const dpr = editor.createInput('scale', 'text', i18nString(UIStrings.devicePixelRatio), scaleValidator); dpr.classList.add('device-edit-fixed'); screen.appendChild(dpr); const uaStringFields = content.createChild('div', 'devices-edit-fields'); UI.UIUtils.createTextChild(uaStringFields.createChild('b'), i18nString(UIStrings.userAgentString)); const ua = uaStringFields.createChild('div', 'hbox'); ua.appendChild(editor.createInput('user-agent', 'text', i18nString(UIStrings.userAgentString), () => { return {valid: true, errorMessage: undefined}; })); const uaTypeOptions = [ EmulationModel.DeviceModeModel.UA.Mobile, EmulationModel.DeviceModeModel.UA.MobileNoTouch, EmulationModel.DeviceModeModel.UA.Desktop, EmulationModel.DeviceModeModel.UA.DesktopTouch, ]; const uaType = editor.createSelect('ua-type', uaTypeOptions, () => { return {valid: true, errorMessage: undefined}; }, i18nString(UIStrings.userAgentType)); uaType.classList.add('device-edit-fixed'); ua.appendChild(uaType); const uaMetadata = editor.createCustomControl( 'ua-metadata', EmulationComponents.UserAgentClientHintsForm.UserAgentClientHintsForm, userAgentMetadataValidator); uaMetadata.value = {}; uaMetadata.addEventListener('clienthintschange', () => editor.requestValidation(), false); content.appendChild(uaMetadata); return editor; function userAgentMetadataValidator(): UI.ListWidget.ValidatorResult { return uaMetadata.validate(); } function titleValidator( item: EmulationModel.EmulatedDevices.EmulatedDevice, index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult { let valid = false; let errorMessage; const value = input.value.trim(); if (value.length >= EmulationModel.DeviceModeModel.MaxDeviceNameLength) { errorMessage = i18nString(UIStrings.deviceNameMustBeLessThanS, {PH1: EmulationModel.DeviceModeModel.MaxDeviceNameLength}); } else if (value.length === 0) { errorMessage = i18nString(UIStrings.deviceNameCannotBeEmpty); } else { valid = true; } return {valid, errorMessage}; } function widthValidator( item: EmulationModel.EmulatedDevices.EmulatedDevice, index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult { return EmulationModel.DeviceModeModel.DeviceModeModel.widthValidator(input.value); } function heightValidator( item: EmulationModel.EmulatedDevices.EmulatedDevice, index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult { return EmulationModel.DeviceModeModel.DeviceModeModel.heightValidator(input.value); } function scaleValidator( item: EmulationModel.EmulatedDevices.EmulatedDevice, index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult { return EmulationModel.DeviceModeModel.DeviceModeModel.scaleValidator(input.value); } } }