blob: 6f4d782e01df697cc4ddf4784bde3cb694a72dec [file] [log] [blame]
Mathias Bynens4e1cc672020-03-25 06:53:181// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Tim van der Lippe76961572021-04-06 10:48:075import * as Common from '../../core/common/common.js';
Tim van der Lippebb352e62021-04-01 17:57:286import * as i18n from '../../core/i18n/i18n.js';
Tim van der Lippeaa61faf2021-04-07 15:32:077import * as UI from '../../ui/legacy/legacy.js';
Tim van der Lippe4866dfa2021-08-02 16:09:108import locationsSettingsTabStyles from './locationsSettingsTab.css.js';
Mathias Bynens4e1cc672020-03-25 06:53:189
Jan Scheffler050013a2021-04-13 14:11:0210let locationsSettingsTabInstance: LocationsSettingsTab;
Tim van der Lippe4b527cf2020-12-14 15:37:5111
Simon Zünd6f95e842021-03-01 07:41:5512const UIStrings = {
Kalon Hinds1fb85432021-02-26 19:30:4713 /**
Jack Franklinfd72c072022-12-21 11:45:0114 *@description Title in the Locations Settings Tab, where custom geographic locations that the user
15 *has entered are stored.
16 */
Kalon Hinds1fb85432021-02-26 19:30:4717 customLocations: 'Custom locations',
18 /**
Jack Franklinfd72c072022-12-21 11:45:0119 *@description Label for the name of a geographic location that the user has entered.
20 */
Kalon Hinds1fb85432021-02-26 19:30:4721 locationName: 'Location name',
22 /**
Jack Franklinfd72c072022-12-21 11:45:0123 *@description Abbreviation of latitude in Locations Settings Tab of the Device Toolbar
24 */
Kalon Hinds1fb85432021-02-26 19:30:4725 lat: 'Lat',
26 /**
Jack Franklinfd72c072022-12-21 11:45:0127 *@description Abbreviation of longitude in Locations Settings Tab of the Device Toolbar
28 */
Kalon Hinds1fb85432021-02-26 19:30:4729 long: 'Long',
30 /**
Jack Franklinfd72c072022-12-21 11:45:0131 *@description Text in Sensors View of the Device Toolbar
32 */
Kalon Hinds1fb85432021-02-26 19:30:4733 timezoneId: 'Timezone ID',
34 /**
Jack Franklinfd72c072022-12-21 11:45:0135 *@description Label for text input for the locale of a particular location.
36 */
Kalon Hinds1fb85432021-02-26 19:30:4737 locale: 'Locale',
38 /**
Jack Franklinfd72c072022-12-21 11:45:0139 *@description Label for text input for the latitude of a GPS position.
40 */
Kalon Hinds1fb85432021-02-26 19:30:4741 latitude: 'Latitude',
42 /**
Jack Franklinfd72c072022-12-21 11:45:0143 *@description Label for text input for the longitude of a GPS position.
44 */
Kalon Hinds1fb85432021-02-26 19:30:4745 longitude: 'Longitude',
46 /**
Jack Franklinfd72c072022-12-21 11:45:0147 *@description Error message in the Locations settings pane that declares the location name input must not be empty
48 */
Kalon Hinds1fb85432021-02-26 19:30:4749 locationNameCannotBeEmpty: 'Location name cannot be empty',
50 /**
Jack Franklinfd72c072022-12-21 11:45:0151 *@description Error message in the Locations settings pane that declares the maximum length of the location name
52 *@example {50} PH1
53 */
Kalon Hinds1fb85432021-02-26 19:30:4754 locationNameMustBeLessThanS: 'Location name must be less than {PH1} characters',
55 /**
Jack Franklinfd72c072022-12-21 11:45:0156 *@description Error message in the Locations settings pane that declares that the value for the latitude input must be a number
57 */
Kalon Hinds1fb85432021-02-26 19:30:4758 latitudeMustBeANumber: 'Latitude must be a number',
59 /**
Jack Franklinfd72c072022-12-21 11:45:0160 *@description Error message in the Locations settings pane that declares the minimum value for the latitude input
61 *@example {-90} PH1
62 */
Kalon Hinds1fb85432021-02-26 19:30:4763 latitudeMustBeGreaterThanOrEqual: 'Latitude must be greater than or equal to {PH1}',
64 /**
Jack Franklinfd72c072022-12-21 11:45:0165 *@description Error message in the Locations settings pane that declares the maximum value for the latitude input
66 *@example {90} PH1
67 */
Kalon Hinds1fb85432021-02-26 19:30:4768 latitudeMustBeLessThanOrEqualToS: 'Latitude must be less than or equal to {PH1}',
69 /**
Jack Franklinfd72c072022-12-21 11:45:0170 *@description Error message in the Locations settings pane that declares that the value for the longitude input must be a number
71 */
Kalon Hinds1fb85432021-02-26 19:30:4772 longitudeMustBeANumber: 'Longitude must be a number',
73 /**
Jack Franklinfd72c072022-12-21 11:45:0174 *@description Error message in the Locations settings pane that declares the minimum value for the longitude input
75 *@example {-180} PH1
76 */
Kalon Hinds1fb85432021-02-26 19:30:4777 longitudeMustBeGreaterThanOr: 'Longitude must be greater than or equal to {PH1}',
78 /**
Jack Franklinfd72c072022-12-21 11:45:0179 *@description Error message in the Locations settings pane that declares the maximum value for the longitude input
80 *@example {180} PH1
81 */
Kalon Hinds1fb85432021-02-26 19:30:4782 longitudeMustBeLessThanOrEqualTo: 'Longitude must be less than or equal to {PH1}',
83 /**
Jack Franklinfd72c072022-12-21 11:45:0184 *@description Error message in the Locations settings pane that declares timezone ID input invalid
85 */
Kalon Hinds1fb85432021-02-26 19:30:4786 timezoneIdMustContainAlphabetic: 'Timezone ID must contain alphabetic characters',
87 /**
Jack Franklinfd72c072022-12-21 11:45:0188 *@description Error message in the Locations settings pane that declares locale input invalid
89 */
Kalon Hinds1fb85432021-02-26 19:30:4790 localeMustContainAlphabetic: 'Locale must contain alphabetic characters',
91 /**
Jack Franklinfd72c072022-12-21 11:45:0192 *@description Text of add locations button in Locations Settings Tab of the Device Toolbar
93 */
Kalon Hinds1fb85432021-02-26 19:30:4794 addLocation: 'Add location...',
95};
Tim van der Lippe4866dfa2021-08-02 16:09:1096const str_ = i18n.i18n.registerUIStrings('panels/sensors/LocationsSettingsTab.ts', UIStrings);
Kalon Hinds1fb85432021-02-26 19:30:4797const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
98
Sigurd Schneiderb3ab8f62021-07-16 06:19:4999export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidget.Delegate<LocationDescription> {
Jan Scheffler2a0ff4e2021-08-10 10:15:04100 private readonly list: UI.ListWidget.ListWidget<LocationDescription>;
101 private readonly customSetting: Common.Settings.Setting<LocationDescription[]>;
102 private editor?: UI.ListWidget.Editor<LocationDescription>;
Jan Scheffler050013a2021-04-13 14:11:02103
104 private constructor() {
Mathias Bynens4e1cc672020-03-25 06:53:18105 super(true);
Mathias Bynens4e1cc672020-03-25 06:53:18106
Kalon Hinds1fb85432021-02-26 19:30:47107 this.contentElement.createChild('div', 'header').textContent = i18nString(UIStrings.customLocations);
Mathias Bynens4e1cc672020-03-25 06:53:18108
109 const addButton = UI.UIUtils.createTextButton(
Jan Scheffler2a0ff4e2021-08-10 10:15:04110 i18nString(UIStrings.addLocation), this.addButtonClicked.bind(this), 'add-locations-button');
Mathias Bynens4e1cc672020-03-25 06:53:18111 this.contentElement.appendChild(addButton);
112
Jan Scheffler2a0ff4e2021-08-10 10:15:04113 this.list = new UI.ListWidget.ListWidget(this);
114 this.list.element.classList.add('locations-list');
115 this.list.show(this.contentElement);
116 this.customSetting =
Sigurd Schneiderb3ab8f62021-07-16 06:19:49117 Common.Settings.Settings.instance().moduleSetting<LocationDescription[]>('emulation.locations');
118 const list =
Simon Zünda7f6c982021-10-05 10:58:02119 this.customSetting.get().map(location => replaceLocationTitles(location, this.customSetting.defaultValue));
Mathias Bynens4e1cc672020-03-25 06:53:18120
Jan Scheffler050013a2021-04-13 14:11:02121 function replaceLocationTitles(
122 location: LocationDescription, defaultValues: LocationDescription[]): LocationDescription {
Andres Olivaresceb3ec22021-02-02 16:23:40123 // This check is done for locations that might had been cached wrongly due to crbug.com/1171670.
124 // Each of the default values would have been stored without a title if the user had added a new location
125 // while the bug was present in the application. This means that getting the setting's default value with the `get`
126 // method would return the default locations without a title. To cope with this, the setting values are
127 // preemptively checked and corrected so that any default value mistakenly stored without a title is replaced
128 // with the corresponding declared value in the pre-registered setting.
129 if (!location.title) {
130 const replacement = defaultValues.find(
131 defaultLocation => defaultLocation.lat === location.lat && defaultLocation.long === location.long &&
132 defaultLocation.timezoneId === location.timezoneId && defaultLocation.locale === location.locale);
133 if (!replacement) {
134 console.error('Could not determine a location setting title');
135 } else {
136 return replacement;
137 }
138 }
139 return location;
140 }
141
Jan Scheffler2a0ff4e2021-08-10 10:15:04142 this.customSetting.set(list);
143 this.customSetting.addChangeListener(this.locationsUpdated, this);
Mathias Bynens4e1cc672020-03-25 06:53:18144
145 this.setDefaultFocusedElement(addButton);
146 }
147
Jan Scheffler050013a2021-04-13 14:11:02148 static instance(): LocationsSettingsTab {
Tim van der Lippe4b527cf2020-12-14 15:37:51149 if (!locationsSettingsTabInstance) {
150 locationsSettingsTabInstance = new LocationsSettingsTab();
151 }
152
153 return locationsSettingsTabInstance;
154 }
155
Jan Scheffler050013a2021-04-13 14:11:02156 wasShown(): void {
Mathias Bynens4e1cc672020-03-25 06:53:18157 super.wasShown();
Tim van der Lippe4866dfa2021-08-02 16:09:10158 this.registerCSSFiles([locationsSettingsTabStyles]);
Jan Scheffler2a0ff4e2021-08-10 10:15:04159 this.list.registerCSSFiles([locationsSettingsTabStyles]);
160 this.locationsUpdated();
Mathias Bynens4e1cc672020-03-25 06:53:18161 }
162
Jan Scheffler2a0ff4e2021-08-10 10:15:04163 private locationsUpdated(): void {
164 this.list.clear();
Mathias Bynens4e1cc672020-03-25 06:53:18165
Jan Scheffler2a0ff4e2021-08-10 10:15:04166 const conditions = this.customSetting.get();
Andres Olivaresedcab582021-01-27 13:17:19167 for (const condition of conditions) {
Jan Scheffler2a0ff4e2021-08-10 10:15:04168 this.list.appendItem(condition, true);
Mathias Bynens4e1cc672020-03-25 06:53:18169 }
170
Jan Scheffler2a0ff4e2021-08-10 10:15:04171 this.list.appendSeparator();
Mathias Bynens4e1cc672020-03-25 06:53:18172 }
173
Jan Scheffler2a0ff4e2021-08-10 10:15:04174 private addButtonClicked(): void {
175 this.list.addNewItem(this.customSetting.get().length, {title: '', lat: 0, long: 0, timezoneId: '', locale: ''});
Mathias Bynens4e1cc672020-03-25 06:53:18176 }
177
Sigurd Schneiderb3ab8f62021-07-16 06:19:49178 renderItem(location: LocationDescription, _editable: boolean): Element {
Tim van der Lippef49e2322020-05-01 15:03:09179 const element = document.createElement('div');
180 element.classList.add('locations-list-item');
Mathias Bynens4e1cc672020-03-25 06:53:18181 const title = element.createChild('div', 'locations-list-text locations-list-title');
182 const titleText = title.createChild('div', 'locations-list-title-text');
183 titleText.textContent = location.title;
Tim van der Lippe70842f32020-11-23 16:56:57184 UI.Tooltip.Tooltip.install(titleText, location.title);
Mathias Bynens4e1cc672020-03-25 06:53:18185 element.createChild('div', 'locations-list-separator');
Alex Rudenkoe0930972020-10-09 09:00:34186 element.createChild('div', 'locations-list-text').textContent = String(location.lat);
Mathias Bynens4e1cc672020-03-25 06:53:18187 element.createChild('div', 'locations-list-separator');
Alex Rudenkoe0930972020-10-09 09:00:34188 element.createChild('div', 'locations-list-text').textContent = String(location.long);
Mathias Bynens4e1cc672020-03-25 06:53:18189 element.createChild('div', 'locations-list-separator');
Mathias Bynens7767e4f2020-04-06 12:54:54190 element.createChild('div', 'locations-list-text').textContent = location.timezoneId;
Mathias Bynens4e1cc672020-03-25 06:53:18191 element.createChild('div', 'locations-list-separator');
192 element.createChild('div', 'locations-list-text').textContent = location.locale;
193 return element;
194 }
195
Sigurd Schneiderb3ab8f62021-07-16 06:19:49196 removeItemRequested(item: LocationDescription, index: number): void {
Jan Scheffler2a0ff4e2021-08-10 10:15:04197 const list = this.customSetting.get();
Mathias Bynens4e1cc672020-03-25 06:53:18198 list.splice(index, 1);
Jan Scheffler2a0ff4e2021-08-10 10:15:04199 this.customSetting.set(list);
Mathias Bynens4e1cc672020-03-25 06:53:18200 }
201
Sigurd Schneiderb3ab8f62021-07-16 06:19:49202 commitEdit(location: LocationDescription, editor: UI.ListWidget.Editor<LocationDescription>, isNew: boolean): void {
Mathias Bynens4e1cc672020-03-25 06:53:18203 location.title = editor.control('title').value.trim();
204 const lat = editor.control('lat').value.trim();
205 location.lat = lat ? parseFloat(lat) : 0;
206 const long = editor.control('long').value.trim();
207 location.long = long ? parseFloat(long) : 0;
208 const timezoneId = editor.control('timezoneId').value.trim();
209 location.timezoneId = timezoneId;
210 const locale = editor.control('locale').value.trim();
211 location.locale = locale;
212
Jan Scheffler2a0ff4e2021-08-10 10:15:04213 const list = this.customSetting.get();
Mathias Bynens4e1cc672020-03-25 06:53:18214 if (isNew) {
215 list.push(location);
216 }
Jan Scheffler2a0ff4e2021-08-10 10:15:04217 this.customSetting.set(list);
Mathias Bynens4e1cc672020-03-25 06:53:18218 }
219
Sigurd Schneiderb3ab8f62021-07-16 06:19:49220 beginEdit(location: LocationDescription): UI.ListWidget.Editor<LocationDescription> {
Jan Scheffler2a0ff4e2021-08-10 10:15:04221 const editor = this.createEditor();
Mathias Bynens4e1cc672020-03-25 06:53:18222 editor.control('title').value = location.title;
223 editor.control('lat').value = String(location.lat);
224 editor.control('long').value = String(location.long);
225 editor.control('timezoneId').value = location.timezoneId;
226 editor.control('locale').value = location.locale;
227 return editor;
228 }
229
Jan Scheffler2a0ff4e2021-08-10 10:15:04230 private createEditor(): UI.ListWidget.Editor<LocationDescription> {
231 if (this.editor) {
232 return this.editor;
Mathias Bynens4e1cc672020-03-25 06:53:18233 }
234
Sigurd Schneiderb3ab8f62021-07-16 06:19:49235 const editor = new UI.ListWidget.Editor<LocationDescription>();
Jan Scheffler2a0ff4e2021-08-10 10:15:04236 this.editor = editor;
Mathias Bynens4e1cc672020-03-25 06:53:18237 const content = editor.contentElement();
238
239 const titles = content.createChild('div', 'locations-edit-row');
240 titles.createChild('div', 'locations-list-text locations-list-title').textContent =
Kalon Hinds1fb85432021-02-26 19:30:47241 i18nString(UIStrings.locationName);
Mathias Bynens4e1cc672020-03-25 06:53:18242 titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
Kalon Hinds1fb85432021-02-26 19:30:47243 titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.lat);
Mathias Bynens4e1cc672020-03-25 06:53:18244 titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
Kalon Hinds1fb85432021-02-26 19:30:47245 titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.long);
Mathias Bynens4e1cc672020-03-25 06:53:18246 titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
Kalon Hinds1fb85432021-02-26 19:30:47247 titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.timezoneId);
Mathias Bynens4e1cc672020-03-25 06:53:18248 titles.createChild('div', 'locations-list-separator locations-list-separator-invisible');
Kalon Hinds1fb85432021-02-26 19:30:47249 titles.createChild('div', 'locations-list-text').textContent = i18nString(UIStrings.locale);
Mathias Bynens4e1cc672020-03-25 06:53:18250
251 const fields = content.createChild('div', 'locations-edit-row');
Alex Rudenkoac1817c2020-04-08 09:57:56252 fields.createChild('div', 'locations-list-text locations-list-title locations-input-container')
Kalon Hinds1fb85432021-02-26 19:30:47253 .appendChild(editor.createInput('title', 'text', i18nString(UIStrings.locationName), titleValidator));
Mathias Bynens4e1cc672020-03-25 06:53:18254 fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
255
Alex Rudenkoac1817c2020-04-08 09:57:56256 let cell = fields.createChild('div', 'locations-list-text locations-input-container');
Kalon Hinds1fb85432021-02-26 19:30:47257 cell.appendChild(editor.createInput('lat', 'text', i18nString(UIStrings.latitude), latValidator));
Mathias Bynens4e1cc672020-03-25 06:53:18258 fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
259
Alex Rudenkoac1817c2020-04-08 09:57:56260 cell = fields.createChild('div', 'locations-list-text locations-list-text-longitude locations-input-container');
Kalon Hinds1fb85432021-02-26 19:30:47261 cell.appendChild(editor.createInput('long', 'text', i18nString(UIStrings.longitude), longValidator));
Mathias Bynens4e1cc672020-03-25 06:53:18262 fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
263
Alex Rudenkoac1817c2020-04-08 09:57:56264 cell = fields.createChild('div', 'locations-list-text locations-input-container');
Kalon Hinds1fb85432021-02-26 19:30:47265 cell.appendChild(editor.createInput('timezoneId', 'text', i18nString(UIStrings.timezoneId), timezoneIdValidator));
Mathias Bynens7767e4f2020-04-06 12:54:54266 fields.createChild('div', 'locations-list-separator locations-list-separator-invisible');
Mathias Bynens4e1cc672020-03-25 06:53:18267
Alex Rudenkoac1817c2020-04-08 09:57:56268 cell = fields.createChild('div', 'locations-list-text locations-input-container');
Kalon Hinds1fb85432021-02-26 19:30:47269 cell.appendChild(editor.createInput('locale', 'text', i18nString(UIStrings.locale), localeValidator));
Mathias Bynens4e1cc672020-03-25 06:53:18270
271 return editor;
272
Jan Scheffler050013a2021-04-13 14:11:02273 function titleValidator(
Sigurd Schneiderb3ab8f62021-07-16 06:19:49274 item: LocationDescription, index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
Mathias Bynens4e1cc672020-03-25 06:53:18275 const maxLength = 50;
276 const value = input.value.trim();
277
278 let errorMessage;
279 if (!value.length) {
Kalon Hinds1fb85432021-02-26 19:30:47280 errorMessage = i18nString(UIStrings.locationNameCannotBeEmpty);
Mathias Bynens4e1cc672020-03-25 06:53:18281 } else if (value.length > maxLength) {
Kalon Hinds1fb85432021-02-26 19:30:47282 errorMessage = i18nString(UIStrings.locationNameMustBeLessThanS, {PH1: maxLength});
Mathias Bynens4e1cc672020-03-25 06:53:18283 }
284
285 if (errorMessage) {
286 return {valid: false, errorMessage};
287 }
Alex Rudenkoe0930972020-10-09 09:00:34288 return {valid: true, errorMessage: undefined};
Mathias Bynens4e1cc672020-03-25 06:53:18289 }
290
Jan Scheffler050013a2021-04-13 14:11:02291 function latValidator(
Sigurd Schneiderb3ab8f62021-07-16 06:19:49292 item: LocationDescription, index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
Mathias Bynens4e1cc672020-03-25 06:53:18293 const minLat = -90;
294 const maxLat = 90;
295 const value = input.value.trim();
296 const parsedValue = Number(value);
297
298 if (!value) {
Alex Rudenkoe0930972020-10-09 09:00:34299 return {valid: true, errorMessage: undefined};
Mathias Bynens4e1cc672020-03-25 06:53:18300 }
301
302 let errorMessage;
303 if (Number.isNaN(parsedValue)) {
Kalon Hinds1fb85432021-02-26 19:30:47304 errorMessage = i18nString(UIStrings.latitudeMustBeANumber);
Mathias Bynens4e1cc672020-03-25 06:53:18305 } else if (parseFloat(value) < minLat) {
Kalon Hinds1fb85432021-02-26 19:30:47306 errorMessage = i18nString(UIStrings.latitudeMustBeGreaterThanOrEqual, {PH1: minLat});
Mathias Bynens4e1cc672020-03-25 06:53:18307 } else if (parseFloat(value) > maxLat) {
Kalon Hinds1fb85432021-02-26 19:30:47308 errorMessage = i18nString(UIStrings.latitudeMustBeLessThanOrEqualToS, {PH1: maxLat});
Mathias Bynens4e1cc672020-03-25 06:53:18309 }
310
311 if (errorMessage) {
312 return {valid: false, errorMessage};
313 }
Alex Rudenkoe0930972020-10-09 09:00:34314 return {valid: true, errorMessage: undefined};
Mathias Bynens4e1cc672020-03-25 06:53:18315 }
316
Jan Scheffler050013a2021-04-13 14:11:02317 function longValidator(
Sigurd Schneiderb3ab8f62021-07-16 06:19:49318 item: LocationDescription, index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
Mathias Bynens4e1cc672020-03-25 06:53:18319 const minLong = -180;
320 const maxLong = 180;
321 const value = input.value.trim();
322 const parsedValue = Number(value);
323
324 if (!value) {
Alex Rudenkoe0930972020-10-09 09:00:34325 return {valid: true, errorMessage: undefined};
Mathias Bynens4e1cc672020-03-25 06:53:18326 }
327
328 let errorMessage;
329 if (Number.isNaN(parsedValue)) {
Kalon Hinds1fb85432021-02-26 19:30:47330 errorMessage = i18nString(UIStrings.longitudeMustBeANumber);
Mathias Bynens4e1cc672020-03-25 06:53:18331 } else if (parseFloat(value) < minLong) {
Kalon Hinds1fb85432021-02-26 19:30:47332 errorMessage = i18nString(UIStrings.longitudeMustBeGreaterThanOr, {PH1: minLong});
Mathias Bynens4e1cc672020-03-25 06:53:18333 } else if (parseFloat(value) > maxLong) {
Kalon Hinds1fb85432021-02-26 19:30:47334 errorMessage = i18nString(UIStrings.longitudeMustBeLessThanOrEqualTo, {PH1: maxLong});
Mathias Bynens4e1cc672020-03-25 06:53:18335 }
336
337 if (errorMessage) {
338 return {valid: false, errorMessage};
339 }
Alex Rudenkoe0930972020-10-09 09:00:34340 return {valid: true, errorMessage: undefined};
Mathias Bynens4e1cc672020-03-25 06:53:18341 }
342
Jan Scheffler050013a2021-04-13 14:11:02343 function timezoneIdValidator(
Sigurd Schneiderb3ab8f62021-07-16 06:19:49344 item: LocationDescription, index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
Mathias Bynens4e1cc672020-03-25 06:53:18345 const value = input.value.trim();
346 // Chromium uses ICU's timezone implementation, which is very
347 // liberal in what it accepts. ICU does not simply use an allowlist
348 // but instead tries to make sense of the input, even for
349 // weird-looking timezone IDs. There's not much point in validating
350 // the input other than checking if it contains at least one
351 // alphabetic character. The empty string resets the override,
352 // and is accepted as well.
353 if (value === '' || /[a-zA-Z]/.test(value)) {
Alex Rudenkoe0930972020-10-09 09:00:34354 return {valid: true, errorMessage: undefined};
Mathias Bynens4e1cc672020-03-25 06:53:18355 }
Kalon Hinds1fb85432021-02-26 19:30:47356 const errorMessage = i18nString(UIStrings.timezoneIdMustContainAlphabetic);
Mathias Bynens4e1cc672020-03-25 06:53:18357 return {valid: false, errorMessage};
358 }
359
Jan Scheffler050013a2021-04-13 14:11:02360 function localeValidator(
Sigurd Schneiderb3ab8f62021-07-16 06:19:49361 item: LocationDescription, index: number, input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
Mathias Bynens4e1cc672020-03-25 06:53:18362 const value = input.value.trim();
363 // Similarly to timezone IDs, there's not much point in validating
364 // input locales other than checking if it contains at least two
365 // alphabetic characters.
366 // https://unicode.org/reports/tr35/#Unicode_language_identifier
367 // The empty string resets the override, and is accepted as
368 // well.
369 if (value === '' || /[a-zA-Z]{2}/.test(value)) {
Alex Rudenkoe0930972020-10-09 09:00:34370 return {valid: true, errorMessage: undefined};
Mathias Bynens4e1cc672020-03-25 06:53:18371 }
Kalon Hinds1fb85432021-02-26 19:30:47372 const errorMessage = i18nString(UIStrings.localeMustContainAlphabetic);
Mathias Bynens4e1cc672020-03-25 06:53:18373 return {valid: false, errorMessage};
374 }
375 }
376}
Jan Scheffler050013a2021-04-13 14:11:02377export interface LocationDescription {
Sigurd Schneiderb3ab8f62021-07-16 06:19:49378 title: string;
Jan Scheffler050013a2021-04-13 14:11:02379 lat: number;
380 long: number;
381 timezoneId: string;
382 locale: string;
383}