1/* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31importScript("EditFileSystemDialog.js"); 32 33/** 34 * @constructor 35 * @param {!function()} onHide 36 * @extends {WebInspector.HelpScreen} 37 */ 38WebInspector.SettingsScreen = function(onHide) 39{ 40 WebInspector.HelpScreen.call(this); 41 this.element.id = "settings-screen"; 42 43 /** @type {function()} */ 44 this._onHide = onHide; 45 46 this._tabbedPane = new WebInspector.TabbedPane(); 47 this._tabbedPane.element.classList.add("help-window-main"); 48 var settingsLabelElement = document.createElement("div"); 49 settingsLabelElement.className = "help-window-label"; 50 settingsLabelElement.createTextChild(WebInspector.UIString("Settings")); 51 this._tabbedPane.element.insertBefore(settingsLabelElement, this._tabbedPane.element.firstChild); 52 this._tabbedPane.element.appendChild(this._createCloseButton()); 53 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.General, WebInspector.UIString("General"), new WebInspector.GenericSettingsTab()); 54 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Workspace, WebInspector.UIString("Workspace"), new WebInspector.WorkspaceSettingsTab()); 55 if (WebInspector.experimentsSettings.experimentsEnabled) 56 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Experiments, WebInspector.UIString("Experiments"), new WebInspector.ExperimentsSettingsTab()); 57 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Shortcuts, WebInspector.UIString("Shortcuts"), WebInspector.shortcutsScreen.createShortcutsTabView()); 58 this._tabbedPane.shrinkableTabs = false; 59 this._tabbedPane.verticalTabLayout = true; 60 61 this._lastSelectedTabSetting = WebInspector.settings.createSetting("lastSelectedSettingsTab", WebInspector.SettingsScreen.Tabs.General); 62 this.selectTab(this._lastSelectedTabSetting.get()); 63 this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this); 64 this.element.addEventListener("keydown", this._keyDown.bind(this), false); 65 this._developerModeCounter = 0; 66} 67 68/** 69 * @param {number} min 70 * @param {number} max 71 * @param {string} text 72 * @return {?string} 73 */ 74WebInspector.SettingsScreen.integerValidator = function(min, max, text) 75{ 76 var value = Number(text); 77 if (isNaN(value)) 78 return WebInspector.UIString("Invalid number format"); 79 if (value < min || value > max) 80 return WebInspector.UIString("Value is out of range [%d, %d]", min, max); 81 return null; 82} 83 84WebInspector.SettingsScreen.Tabs = { 85 General: "general", 86 Overrides: "overrides", 87 Workspace: "workspace", 88 Experiments: "experiments", 89 Shortcuts: "shortcuts" 90} 91 92WebInspector.SettingsScreen.prototype = { 93 /** 94 * @param {string} tabId 95 */ 96 selectTab: function(tabId) 97 { 98 this._tabbedPane.selectTab(tabId); 99 }, 100 101 /** 102 * @param {!WebInspector.Event} event 103 */ 104 _tabSelected: function(event) 105 { 106 this._lastSelectedTabSetting.set(this._tabbedPane.selectedTabId); 107 }, 108 109 /** 110 * @override 111 */ 112 wasShown: function() 113 { 114 this._tabbedPane.show(this.element); 115 WebInspector.HelpScreen.prototype.wasShown.call(this); 116 }, 117 118 /** 119 * @override 120 * @return {boolean} 121 */ 122 isClosingKey: function(keyCode) 123 { 124 return [ 125 WebInspector.KeyboardShortcut.Keys.Enter.code, 126 WebInspector.KeyboardShortcut.Keys.Esc.code, 127 ].indexOf(keyCode) >= 0; 128 }, 129 130 /** 131 * @override 132 */ 133 willHide: function() 134 { 135 this._onHide(); 136 WebInspector.HelpScreen.prototype.willHide.call(this); 137 }, 138 139 /** 140 * @param {?Event} event 141 */ 142 _keyDown: function(event) 143 { 144 var shiftKeyCode = 16; 145 if (event.keyCode === shiftKeyCode && ++this._developerModeCounter > 5) 146 this.element.classList.add("settings-developer-mode"); 147 }, 148 149 __proto__: WebInspector.HelpScreen.prototype 150} 151 152/** 153 * @constructor 154 * @extends {WebInspector.VBox} 155 * @param {string} name 156 * @param {string=} id 157 */ 158WebInspector.SettingsTab = function(name, id) 159{ 160 WebInspector.VBox.call(this); 161 this.element.classList.add("settings-tab-container"); 162 if (id) 163 this.element.id = id; 164 var header = this.element.createChild("header"); 165 header.createChild("h3").appendChild(document.createTextNode(name)); 166 this.containerElement = this.element.createChild("div", "help-container-wrapper").createChild("div", "settings-tab help-content help-container"); 167} 168 169WebInspector.SettingsTab.prototype = { 170 /** 171 * @param {string=} name 172 * @return {!Element} 173 */ 174 _appendSection: function(name) 175 { 176 var block = this.containerElement.createChild("div", "help-block"); 177 if (name) 178 block.createChild("div", "help-section-title").textContent = name; 179 return block; 180 }, 181 182 _createSelectSetting: function(name, options, setting) 183 { 184 var p = document.createElement("p"); 185 var labelElement = p.createChild("label"); 186 labelElement.textContent = name; 187 188 var select = p.createChild("select"); 189 var settingValue = setting.get(); 190 191 for (var i = 0; i < options.length; ++i) { 192 var option = options[i]; 193 select.add(new Option(option[0], option[1])); 194 if (settingValue === option[1]) 195 select.selectedIndex = i; 196 } 197 198 function changeListener(e) 199 { 200 // Don't use e.target.value to avoid conversion of the value to string. 201 setting.set(options[select.selectedIndex][1]); 202 } 203 204 select.addEventListener("change", changeListener, false); 205 return p; 206 }, 207 208 __proto__: WebInspector.VBox.prototype 209} 210 211/** 212 * @constructor 213 * @extends {WebInspector.SettingsTab} 214 */ 215WebInspector.GenericSettingsTab = function() 216{ 217 WebInspector.SettingsTab.call(this, WebInspector.UIString("General"), "general-tab-content"); 218 219 this._populateSectionsFromExtensions(); 220 221 var restoreDefaults = this._appendSection().createChild("input", "settings-tab-text-button"); 222 restoreDefaults.type = "button"; 223 restoreDefaults.value = WebInspector.UIString("Restore defaults and reload"); 224 restoreDefaults.addEventListener("click", restoreAndReload, false); 225 226 function restoreAndReload() 227 { 228 if (window.localStorage) 229 window.localStorage.clear(); 230 WebInspector.reload(); 231 } 232} 233 234WebInspector.GenericSettingsTab.prototype = { 235 _populateSectionsFromExtensions: function() 236 { 237 /** @const */ 238 var explicitSectionOrder = ["", "Appearance", "Elements", "Sources", "Profiler", "Console", "Extensions"]; 239 240 var allExtensions = WebInspector.moduleManager.extensions("ui-setting"); 241 242 /** @type {!StringMultimap.<!WebInspector.ModuleManager.Extension>} */ 243 var extensionsBySectionId = new StringMultimap(); 244 /** @type {!StringMultimap.<!WebInspector.ModuleManager.Extension>} */ 245 var childSettingExtensionsByParentName = new StringMultimap(); 246 247 allExtensions.forEach(function(extension) { 248 var descriptor = extension.descriptor(); 249 var sectionName = descriptor["section"] || ""; 250 if (!sectionName && descriptor["parentSettingName"]) { 251 childSettingExtensionsByParentName.put(descriptor["parentSettingName"], extension); 252 return; 253 } 254 extensionsBySectionId.put(sectionName, extension); 255 }); 256 257 var sectionIds = extensionsBySectionId.keys(); 258 var explicitlyOrderedSections = {}; 259 for (var i = 0; i < explicitSectionOrder.length; ++i) { 260 explicitlyOrderedSections[explicitSectionOrder[i]] = true; 261 var extensions = extensionsBySectionId.get(explicitSectionOrder[i]); 262 if (!extensions.size()) 263 continue; 264 this._addSectionWithExtensionProvidedSettings(explicitSectionOrder[i], extensions.values(), childSettingExtensionsByParentName); 265 } 266 for (var i = 0; i < sectionIds.length; ++i) { 267 if (explicitlyOrderedSections[sectionIds[i]]) 268 continue; 269 this._addSectionWithExtensionProvidedSettings(sectionIds[i], extensionsBySectionId.get(sectionIds[i]).values(), childSettingExtensionsByParentName); 270 } 271 }, 272 273 /** 274 * @param {string} sectionName 275 * @param {!Array.<!WebInspector.ModuleManager.Extension>} extensions 276 * @param {!StringMultimap.<!WebInspector.ModuleManager.Extension>} childSettingExtensionsByParentName 277 */ 278 _addSectionWithExtensionProvidedSettings: function(sectionName, extensions, childSettingExtensionsByParentName) 279 { 280 var uiSectionName = sectionName && WebInspector.UIString(sectionName); 281 var sectionElement = this._appendSection(uiSectionName); 282 extensions.forEach(processSetting.bind(this, null)); 283 284 /** 285 * @param {?Element} parentFieldset 286 * @param {!WebInspector.ModuleManager.Extension} extension 287 * @this {WebInspector.GenericSettingsTab} 288 */ 289 function processSetting(parentFieldset, extension) 290 { 291 var descriptor = extension.descriptor(); 292 var experimentName = descriptor["experiment"]; 293 if (experimentName && (!WebInspector.experimentsSettings[experimentName] || !WebInspector.experimentsSettings[experimentName].isEnabled())) 294 return; 295 296 var settingName = descriptor["settingName"]; 297 var setting = WebInspector.settings[settingName]; 298 var instance = extension.instance(); 299 var settingControl; 300 if (instance && descriptor["settingType"] === "custom") { 301 settingControl = instance.settingElement(); 302 if (!settingControl) 303 return; 304 } 305 if (!settingControl) { 306 var uiTitle = WebInspector.UIString(descriptor["title"]); 307 settingControl = createSettingControl.call(this, uiTitle, setting, descriptor, instance); 308 } 309 if (settingName) { 310 var childSettings = childSettingExtensionsByParentName.get(settingName); 311 if (childSettings.size()) { 312 var fieldSet = WebInspector.SettingsUI.createSettingFieldset(setting); 313 settingControl.appendChild(fieldSet); 314 childSettings.values().forEach(function(item) { processSetting.call(this, fieldSet, item); }, this); 315 } 316 } 317 var containerElement = parentFieldset || sectionElement; 318 containerElement.appendChild(settingControl); 319 } 320 321 /** 322 * @param {string} uiTitle 323 * @param {!WebInspector.Setting} setting 324 * @param {!Object} descriptor 325 * @param {?Object} instance 326 * @return {!Element} 327 * @this {WebInspector.GenericSettingsTab} 328 */ 329 function createSettingControl(uiTitle, setting, descriptor, instance) 330 { 331 switch (descriptor["settingType"]) { 332 case "checkbox": 333 return WebInspector.SettingsUI.createSettingCheckbox(uiTitle, setting); 334 case "select": 335 var descriptorOptions = descriptor["options"] 336 var options = new Array(descriptorOptions.length); 337 for (var i = 0; i < options.length; ++i) { 338 // The third array item flags that the option name is "raw" (non-i18n-izable). 339 var optionName = descriptorOptions[i][2] ? descriptorOptions[i][0] : WebInspector.UIString(descriptorOptions[i][0]); 340 options[i] = [WebInspector.UIString(descriptorOptions[i][0]), descriptorOptions[i][1]]; 341 } 342 return this._createSelectSetting(uiTitle, options, setting); 343 default: 344 throw "Invalid setting type: " + descriptor["settingType"]; 345 } 346 } 347 }, 348 349 /** 350 * @param {?Element} p 351 */ 352 _appendDrawerNote: function(p) 353 { 354 var noteElement = p.createChild("div", "help-field-note"); 355 noteElement.createTextChild("Hit "); 356 noteElement.createChild("span", "help-key").textContent = "Esc"; 357 noteElement.createTextChild(WebInspector.UIString(" or click the")); 358 noteElement.appendChild(new WebInspector.StatusBarButton(WebInspector.UIString("Drawer"), "console-status-bar-item").element); 359 noteElement.createTextChild(WebInspector.UIString("toolbar item")); 360 }, 361 362 __proto__: WebInspector.SettingsTab.prototype 363} 364 365/** 366 * @constructor 367 * @extends {WebInspector.SettingsTab} 368 */ 369WebInspector.WorkspaceSettingsTab = function() 370{ 371 WebInspector.SettingsTab.call(this, WebInspector.UIString("Workspace"), "workspace-tab-content"); 372 WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemAdded, this._fileSystemAdded, this); 373 WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this); 374 375 this._commonSection = this._appendSection(WebInspector.UIString("Common")); 376 var folderExcludePatternInput = WebInspector.SettingsUI.createSettingInputField(WebInspector.UIString("Folder exclude pattern"), WebInspector.settings.workspaceFolderExcludePattern, false, 0, "270px", WebInspector.SettingsUI.regexValidator); 377 this._commonSection.appendChild(folderExcludePatternInput); 378 379 this._fileSystemsSection = this._appendSection(WebInspector.UIString("Folders")); 380 this._fileSystemsListContainer = this._fileSystemsSection.createChild("p", "settings-list-container"); 381 382 this._addFileSystemRowElement = this._fileSystemsSection.createChild("div"); 383 var addFileSystemButton = this._addFileSystemRowElement.createChild("input", "settings-tab-text-button"); 384 addFileSystemButton.type = "button"; 385 addFileSystemButton.value = WebInspector.UIString("Add folder\u2026"); 386 addFileSystemButton.addEventListener("click", this._addFileSystemClicked.bind(this), false); 387 388 this._editFileSystemButton = this._addFileSystemRowElement.createChild("input", "settings-tab-text-button"); 389 this._editFileSystemButton.type = "button"; 390 this._editFileSystemButton.value = WebInspector.UIString("Edit\u2026"); 391 this._editFileSystemButton.addEventListener("click", this._editFileSystemClicked.bind(this), false); 392 this._updateEditFileSystemButtonState(); 393 394 this._reset(); 395} 396 397WebInspector.WorkspaceSettingsTab.prototype = { 398 wasShown: function() 399 { 400 WebInspector.SettingsTab.prototype.wasShown.call(this); 401 this._reset(); 402 }, 403 404 _reset: function() 405 { 406 this._resetFileSystems(); 407 }, 408 409 _resetFileSystems: function() 410 { 411 this._fileSystemsListContainer.removeChildren(); 412 var fileSystemPaths = WebInspector.isolatedFileSystemManager.mapping().fileSystemPaths(); 413 delete this._fileSystemsList; 414 415 if (!fileSystemPaths.length) { 416 var noFileSystemsMessageElement = this._fileSystemsListContainer.createChild("div", "no-file-systems-message"); 417 noFileSystemsMessageElement.textContent = WebInspector.UIString("You have no file systems added."); 418 return; 419 } 420 421 this._fileSystemsList = new WebInspector.SettingsList(["path"], this._renderFileSystem.bind(this)); 422 this._fileSystemsList.element.classList.add("file-systems-list"); 423 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Selected, this._fileSystemSelected.bind(this)); 424 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Removed, this._fileSystemRemovedfromList.bind(this)); 425 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.DoubleClicked, this._fileSystemDoubleClicked.bind(this)); 426 this._fileSystemsListContainer.appendChild(this._fileSystemsList.element); 427 for (var i = 0; i < fileSystemPaths.length; ++i) 428 this._fileSystemsList.addItem(fileSystemPaths[i]); 429 this._updateEditFileSystemButtonState(); 430 }, 431 432 _updateEditFileSystemButtonState: function() 433 { 434 this._editFileSystemButton.disabled = !this._selectedFileSystemPath(); 435 }, 436 437 /** 438 * @param {!WebInspector.Event} event 439 */ 440 _fileSystemSelected: function(event) 441 { 442 this._updateEditFileSystemButtonState(); 443 }, 444 445 /** 446 * @param {!WebInspector.Event} event 447 */ 448 _fileSystemDoubleClicked: function(event) 449 { 450 var id = /** @type{?string} */ (event.data); 451 this._editFileSystem(id); 452 }, 453 454 _editFileSystemClicked: function() 455 { 456 this._editFileSystem(this._selectedFileSystemPath()); 457 }, 458 459 /** 460 * @param {?string} id 461 */ 462 _editFileSystem: function(id) 463 { 464 WebInspector.EditFileSystemDialog.show(WebInspector.inspectorView.element, id); 465 }, 466 467 /** 468 * @param {function(?Event)} handler 469 * @return {!Element} 470 */ 471 _createRemoveButton: function(handler) 472 { 473 var removeButton = document.createElement("button"); 474 removeButton.classList.add("button"); 475 removeButton.classList.add("remove-item-button"); 476 removeButton.value = WebInspector.UIString("Remove"); 477 if (handler) 478 removeButton.addEventListener("click", handler, false); 479 else 480 removeButton.disabled = true; 481 return removeButton; 482 }, 483 484 /** 485 * @param {!Element} columnElement 486 * @param {string} column 487 * @param {?string} id 488 */ 489 _renderFileSystem: function(columnElement, column, id) 490 { 491 if (!id) 492 return ""; 493 var fileSystemPath = id; 494 var textElement = columnElement.createChild("span", "list-column-text"); 495 var pathElement = textElement.createChild("span", "file-system-path"); 496 pathElement.title = fileSystemPath; 497 498 const maxTotalPathLength = 55; 499 const maxFolderNameLength = 30; 500 501 var lastIndexOfSlash = fileSystemPath.lastIndexOf(WebInspector.isWin() ? "\\" : "/"); 502 var folderName = fileSystemPath.substr(lastIndexOfSlash + 1); 503 var folderPath = fileSystemPath.substr(0, lastIndexOfSlash + 1); 504 folderPath = folderPath.trimMiddle(maxTotalPathLength - Math.min(maxFolderNameLength, folderName.length)); 505 folderName = folderName.trimMiddle(maxFolderNameLength); 506 507 var folderPathElement = pathElement.createChild("span"); 508 folderPathElement.textContent = folderPath; 509 510 var nameElement = pathElement.createChild("span", "file-system-path-name"); 511 nameElement.textContent = folderName; 512 }, 513 514 /** 515 * @param {!WebInspector.Event} event 516 */ 517 _fileSystemRemovedfromList: function(event) 518 { 519 var id = /** @type{?string} */ (event.data); 520 if (!id) 521 return; 522 WebInspector.isolatedFileSystemManager.removeFileSystem(id); 523 }, 524 525 _addFileSystemClicked: function() 526 { 527 WebInspector.isolatedFileSystemManager.addFileSystem(); 528 }, 529 530 _fileSystemAdded: function(event) 531 { 532 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data); 533 if (!this._fileSystemsList) 534 this._reset(); 535 else 536 this._fileSystemsList.addItem(fileSystem.path()); 537 }, 538 539 _fileSystemRemoved: function(event) 540 { 541 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data); 542 var selectedFileSystemPath = this._selectedFileSystemPath(); 543 if (this._fileSystemsList.itemForId(fileSystem.path())) 544 this._fileSystemsList.removeItem(fileSystem.path()); 545 if (!this._fileSystemsList.itemIds().length) 546 this._reset(); 547 this._updateEditFileSystemButtonState(); 548 }, 549 550 _selectedFileSystemPath: function() 551 { 552 return this._fileSystemsList ? this._fileSystemsList.selectedId() : null; 553 }, 554 555 __proto__: WebInspector.SettingsTab.prototype 556} 557 558 559/** 560 * @constructor 561 * @extends {WebInspector.SettingsTab} 562 */ 563WebInspector.ExperimentsSettingsTab = function() 564{ 565 WebInspector.SettingsTab.call(this, WebInspector.UIString("Experiments"), "experiments-tab-content"); 566 567 var experiments = WebInspector.experimentsSettings.experiments; 568 if (experiments.length) { 569 var experimentsSection = this._appendSection(); 570 experimentsSection.appendChild(this._createExperimentsWarningSubsection()); 571 for (var i = 0; i < experiments.length; ++i) 572 experimentsSection.appendChild(this._createExperimentCheckbox(experiments[i])); 573 } 574} 575 576WebInspector.ExperimentsSettingsTab.prototype = { 577 /** 578 * @return {!Element} element 579 */ 580 _createExperimentsWarningSubsection: function() 581 { 582 var subsection = document.createElement("div"); 583 var warning = subsection.createChild("span", "settings-experiments-warning-subsection-warning"); 584 warning.textContent = WebInspector.UIString("WARNING:"); 585 subsection.appendChild(document.createTextNode(" ")); 586 var message = subsection.createChild("span", "settings-experiments-warning-subsection-message"); 587 message.textContent = WebInspector.UIString("These experiments could be dangerous and may require restart."); 588 return subsection; 589 }, 590 591 _createExperimentCheckbox: function(experiment) 592 { 593 var input = document.createElement("input"); 594 input.type = "checkbox"; 595 input.name = experiment.name; 596 input.checked = experiment.isEnabled(); 597 function listener() 598 { 599 experiment.setEnabled(input.checked); 600 } 601 input.addEventListener("click", listener, false); 602 603 var p = document.createElement("p"); 604 p.className = experiment.hidden && !experiment.isEnabled() ? "settings-experiment-hidden" : ""; 605 var label = p.createChild("label"); 606 label.appendChild(input); 607 label.appendChild(document.createTextNode(WebInspector.UIString(experiment.title))); 608 p.appendChild(label); 609 return p; 610 }, 611 612 __proto__: WebInspector.SettingsTab.prototype 613} 614 615/** 616 * @constructor 617 */ 618WebInspector.SettingsController = function() 619{ 620 /** @type {?WebInspector.SettingsScreen} */ 621 this._settingsScreen; 622 623 window.addEventListener("resize", this._resize.bind(this), true); 624} 625 626WebInspector.SettingsController.prototype = 627{ 628 _onHideSettingsScreen: function() 629 { 630 delete this._settingsScreenVisible; 631 }, 632 633 /** 634 * @param {string=} tabId 635 */ 636 showSettingsScreen: function(tabId) 637 { 638 if (!this._settingsScreen) 639 this._settingsScreen = new WebInspector.SettingsScreen(this._onHideSettingsScreen.bind(this)); 640 641 if (tabId) 642 this._settingsScreen.selectTab(tabId); 643 644 this._settingsScreen.showModal(); 645 this._settingsScreenVisible = true; 646 }, 647 648 _resize: function() 649 { 650 if (this._settingsScreen && this._settingsScreen.isShowing()) 651 this._settingsScreen.doResize(); 652 } 653} 654 655/** 656 * @constructor 657 * @implements {WebInspector.ActionDelegate} 658 */ 659WebInspector.SettingsController.SettingsScreenActionDelegate = function() { } 660 661WebInspector.SettingsController.SettingsScreenActionDelegate.prototype = { 662 /** 663 * @return {boolean} 664 */ 665 handleAction: function() 666 { 667 WebInspector._settingsController.showSettingsScreen(WebInspector.SettingsScreen.Tabs.General); 668 return true; 669 } 670} 671 672/** 673 * @constructor 674 * @extends {WebInspector.Object} 675 * @param {!Array.<string>} columns 676 * @param {function(!Element, string, ?string)} itemRenderer 677 */ 678WebInspector.SettingsList = function(columns, itemRenderer) 679{ 680 this.element = document.createElement("div"); 681 this.element.classList.add("settings-list"); 682 this.element.tabIndex = -1; 683 this._itemRenderer = itemRenderer; 684 this._listItems = {}; 685 this._ids = []; 686 this._columns = columns; 687} 688 689WebInspector.SettingsList.Events = { 690 Selected: "Selected", 691 Removed: "Removed", 692 DoubleClicked: "DoubleClicked", 693} 694 695WebInspector.SettingsList.prototype = { 696 /** 697 * @param {?string} itemId 698 * @param {?string=} beforeId 699 * @return {!Element} 700 */ 701 addItem: function(itemId, beforeId) 702 { 703 var listItem = document.createElement("div"); 704 listItem._id = itemId; 705 listItem.classList.add("settings-list-item"); 706 if (typeof beforeId !== undefined) 707 this.element.insertBefore(listItem, this._listItems[beforeId]); 708 else 709 this.element.appendChild(listItem); 710 711 var listItemContents = listItem.createChild("div", "settings-list-item-contents"); 712 var listItemColumnsElement = listItemContents.createChild("div", "settings-list-item-columns"); 713 714 listItem.columnElements = {}; 715 for (var i = 0; i < this._columns.length; ++i) { 716 var columnElement = listItemColumnsElement.createChild("div", "list-column"); 717 var columnId = this._columns[i]; 718 listItem.columnElements[columnId] = columnElement; 719 this._itemRenderer(columnElement, columnId, itemId); 720 } 721 var removeItemButton = this._createRemoveButton(removeItemClicked.bind(this)); 722 listItemContents.addEventListener("click", this.selectItem.bind(this, itemId), false); 723 listItemContents.addEventListener("dblclick", this._onDoubleClick.bind(this, itemId), false); 724 listItemContents.appendChild(removeItemButton); 725 726 this._listItems[itemId] = listItem; 727 if (typeof beforeId !== undefined) 728 this._ids.splice(this._ids.indexOf(beforeId), 0, itemId); 729 else 730 this._ids.push(itemId); 731 732 /** 733 * @param {?Event} event 734 * @this {WebInspector.SettingsList} 735 */ 736 function removeItemClicked(event) 737 { 738 removeItemButton.disabled = true; 739 this.removeItem(itemId); 740 this.dispatchEventToListeners(WebInspector.SettingsList.Events.Removed, itemId); 741 event.consume(); 742 } 743 744 return listItem; 745 }, 746 747 /** 748 * @param {?string} id 749 */ 750 removeItem: function(id) 751 { 752 this._listItems[id].remove(); 753 delete this._listItems[id]; 754 this._ids.remove(id); 755 if (id === this._selectedId) { 756 delete this._selectedId; 757 if (this._ids.length) 758 this.selectItem(this._ids[0]); 759 } 760 }, 761 762 /** 763 * @return {!Array.<?string>} 764 */ 765 itemIds: function() 766 { 767 return this._ids.slice(); 768 }, 769 770 /** 771 * @return {!Array.<string>} 772 */ 773 columns: function() 774 { 775 return this._columns.slice(); 776 }, 777 778 /** 779 * @return {?string} 780 */ 781 selectedId: function() 782 { 783 return this._selectedId; 784 }, 785 786 /** 787 * @return {!Element} 788 */ 789 selectedItem: function() 790 { 791 return this._selectedId ? this._listItems[this._selectedId] : null; 792 }, 793 794 /** 795 * @param {string} itemId 796 * @return {!Element} 797 */ 798 itemForId: function(itemId) 799 { 800 return this._listItems[itemId]; 801 }, 802 803 /** 804 * @param {?string} id 805 * @param {?Event=} event 806 */ 807 _onDoubleClick: function(id, event) 808 { 809 this.dispatchEventToListeners(WebInspector.SettingsList.Events.DoubleClicked, id); 810 }, 811 812 /** 813 * @param {?string} id 814 * @param {?Event=} event 815 */ 816 selectItem: function(id, event) 817 { 818 if (typeof this._selectedId !== "undefined") { 819 this._listItems[this._selectedId].classList.remove("selected"); 820 } 821 822 this._selectedId = id; 823 if (typeof this._selectedId !== "undefined") { 824 this._listItems[this._selectedId].classList.add("selected"); 825 } 826 this.dispatchEventToListeners(WebInspector.SettingsList.Events.Selected, id); 827 if (event) 828 event.consume(); 829 }, 830 831 /** 832 * @param {function(?Event)} handler 833 * @return {!Element} 834 */ 835 _createRemoveButton: function(handler) 836 { 837 var removeButton = document.createElement("button"); 838 removeButton.classList.add("remove-item-button"); 839 removeButton.value = WebInspector.UIString("Remove"); 840 removeButton.addEventListener("click", handler, false); 841 return removeButton; 842 }, 843 844 __proto__: WebInspector.Object.prototype 845} 846 847/** 848 * @constructor 849 * @extends {WebInspector.SettingsList} 850 * @param {!Array.<string>} columns 851 * @param {function(string, string):string} valuesProvider 852 * @param {function(?string, !Object)} validateHandler 853 * @param {function(?string, !Object)} editHandler 854 */ 855WebInspector.EditableSettingsList = function(columns, valuesProvider, validateHandler, editHandler) 856{ 857 WebInspector.SettingsList.call(this, columns, this._renderColumn.bind(this)); 858 this._validateHandler = validateHandler; 859 this._editHandler = editHandler; 860 this._valuesProvider = valuesProvider; 861 /** @type {!Object.<string, !HTMLInputElement>} */ 862 this._addInputElements = {}; 863 /** @type {!Object.<string, !Object.<string, !HTMLInputElement>>} */ 864 this._editInputElements = {}; 865 /** @type {!Object.<string, !Object.<string, !HTMLSpanElement>>} */ 866 this._textElements = {}; 867 868 this._addMappingItem = this.addItem(null); 869 this._addMappingItem.classList.add("item-editing"); 870 this._addMappingItem.classList.add("add-list-item"); 871} 872 873WebInspector.EditableSettingsList.prototype = { 874 /** 875 * @param {?string} itemId 876 * @param {?string=} beforeId 877 * @return {!Element} 878 */ 879 addItem: function(itemId, beforeId) 880 { 881 var listItem = WebInspector.SettingsList.prototype.addItem.call(this, itemId, beforeId); 882 listItem.classList.add("editable"); 883 return listItem; 884 }, 885 886 /** 887 * @param {!Element} columnElement 888 * @param {string} columnId 889 * @param {?string} itemId 890 */ 891 _renderColumn: function(columnElement, columnId, itemId) 892 { 893 columnElement.classList.add("settings-list-column-" + columnId); 894 var placeholder = (columnId === "url") ? WebInspector.UIString("URL prefix") : WebInspector.UIString("Folder path"); 895 if (itemId === null) { 896 var inputElement = /** @type {!HTMLInputElement} */ (columnElement.createChild("input", "list-column-editor")); 897 inputElement.placeholder = placeholder; 898 inputElement.addEventListener("blur", this._onAddMappingInputBlur.bind(this), false); 899 inputElement.addEventListener("input", this._validateEdit.bind(this, itemId), false); 900 this._addInputElements[columnId] = inputElement; 901 return; 902 } 903 var validItemId = itemId; 904 905 if (!this._editInputElements[itemId]) 906 this._editInputElements[itemId] = {}; 907 if (!this._textElements[itemId]) 908 this._textElements[itemId] = {}; 909 910 var value = this._valuesProvider(itemId, columnId); 911 912 var textElement = /** @type {!HTMLSpanElement} */ (columnElement.createChild("span", "list-column-text")); 913 textElement.textContent = value; 914 textElement.title = value; 915 columnElement.addEventListener("click", rowClicked.bind(this), false); 916 this._textElements[itemId][columnId] = textElement; 917 918 var inputElement = /** @type {!HTMLInputElement} */ (columnElement.createChild("input", "list-column-editor")); 919 inputElement.value = value; 920 inputElement.addEventListener("blur", this._editMappingBlur.bind(this, itemId), false); 921 inputElement.addEventListener("input", this._validateEdit.bind(this, itemId), false); 922 columnElement.inputElement = inputElement; 923 this._editInputElements[itemId][columnId] = inputElement; 924 925 /** 926 * @param {?Event} event 927 * @this {WebInspector.EditableSettingsList} 928 */ 929 function rowClicked(event) 930 { 931 if (itemId === this._editingId) 932 return; 933 event.consume(); 934 console.assert(!this._editingId); 935 this._editingId = validItemId; 936 var listItem = this.itemForId(validItemId); 937 listItem.classList.add("item-editing"); 938 var inputElement = event.target.inputElement || this._editInputElements[validItemId][this.columns()[0]]; 939 inputElement.focus(); 940 inputElement.select(); 941 } 942 }, 943 944 /** 945 * @param {?string} itemId 946 * @return {!Object} 947 */ 948 _data: function(itemId) 949 { 950 var inputElements = this._inputElements(itemId); 951 var data = {}; 952 var columns = this.columns(); 953 for (var i = 0; i < columns.length; ++i) 954 data[columns[i]] = inputElements[columns[i]].value; 955 return data; 956 }, 957 958 /** 959 * @param {?string} itemId 960 * @return {?Object.<string, !HTMLInputElement>} 961 */ 962 _inputElements: function(itemId) 963 { 964 if (!itemId) 965 return this._addInputElements; 966 return this._editInputElements[itemId] || null; 967 }, 968 969 /** 970 * @param {?string} itemId 971 * @return {boolean} 972 */ 973 _validateEdit: function(itemId) 974 { 975 var errorColumns = this._validateHandler(itemId, this._data(itemId)); 976 var hasChanges = this._hasChanges(itemId); 977 var columns = this.columns(); 978 for (var i = 0; i < columns.length; ++i) { 979 var columnId = columns[i]; 980 var inputElement = this._inputElements(itemId)[columnId]; 981 if (hasChanges && errorColumns.indexOf(columnId) !== -1) 982 inputElement.classList.add("editable-item-error"); 983 else 984 inputElement.classList.remove("editable-item-error"); 985 } 986 return !errorColumns.length; 987 }, 988 989 /** 990 * @param {?string} itemId 991 * @return {boolean} 992 */ 993 _hasChanges: function(itemId) 994 { 995 var hasChanges = false; 996 var columns = this.columns(); 997 for (var i = 0; i < columns.length; ++i) { 998 var columnId = columns[i]; 999 var oldValue = itemId ? this._textElements[itemId][columnId].textContent : ""; 1000 var newValue = this._inputElements(itemId)[columnId].value; 1001 if (oldValue !== newValue) { 1002 hasChanges = true; 1003 break; 1004 } 1005 } 1006 return hasChanges; 1007 }, 1008 1009 /** 1010 * @param {string} itemId 1011 * @param {?Event} event 1012 */ 1013 _editMappingBlur: function(itemId, event) 1014 { 1015 var inputElements = Object.values(this._editInputElements[itemId]); 1016 if (inputElements.indexOf(event.relatedTarget) !== -1) 1017 return; 1018 1019 var listItem = this.itemForId(itemId); 1020 listItem.classList.remove("item-editing"); 1021 delete this._editingId; 1022 1023 if (!this._hasChanges(itemId)) 1024 return; 1025 1026 if (!this._validateEdit(itemId)) { 1027 var columns = this.columns(); 1028 for (var i = 0; i < columns.length; ++i) { 1029 var columnId = columns[i]; 1030 var inputElement = this._editInputElements[itemId][columnId]; 1031 inputElement.value = this._textElements[itemId][columnId].textContent; 1032 inputElement.classList.remove("editable-item-error"); 1033 } 1034 return; 1035 } 1036 this._editHandler(itemId, this._data(itemId)); 1037 }, 1038 1039 _onAddMappingInputBlur: function(event) 1040 { 1041 var inputElements = Object.values(this._addInputElements); 1042 if (inputElements.indexOf(event.relatedTarget) !== -1) 1043 return; 1044 1045 if (!this._hasChanges(null)) 1046 return; 1047 1048 if (!this._validateEdit(null)) 1049 return; 1050 1051 this._editHandler(null, this._data(null)); 1052 var columns = this.columns(); 1053 for (var i = 0; i < columns.length; ++i) { 1054 var columnId = columns[i]; 1055 var inputElement = this._addInputElements[columnId]; 1056 inputElement.value = ""; 1057 } 1058 }, 1059 1060 __proto__: WebInspector.SettingsList.prototype 1061} 1062 1063WebInspector._settingsController = new WebInspector.SettingsController(); 1064