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 31 /** 32 * @constructor 33 * @extends {WebInspector.DialogDelegate} 34 * @param {string} fileSystemPath 35 */ 36 WebInspector.EditFileSystemDialog = function(fileSystemPath) 37 { 38 WebInspector.DialogDelegate.call(this); 39 this._fileSystemPath = fileSystemPath; 40 41 this.element = document.createElementWithClass("div", "dialog-contents"); 42 43 var header = this.element.createChild("div", "header"); 44 var headerText = header.createChild("span"); 45 headerText.textContent = WebInspector.UIString("Edit file system"); 46 47 var closeButton = header.createChild("div", "close-button-gray done-button"); 48 closeButton.addEventListener("click", this._onDoneClick.bind(this), false); 49 50 var contents = this.element.createChild("div", "contents"); 51 52 WebInspector.isolatedFileSystemManager.mapping().addEventListener(WebInspector.FileSystemMapping.Events.FileMappingAdded, this._fileMappingAdded, this); 53 WebInspector.isolatedFileSystemManager.mapping().addEventListener(WebInspector.FileSystemMapping.Events.FileMappingRemoved, this._fileMappingRemoved, this); 54 WebInspector.isolatedFileSystemManager.mapping().addEventListener(WebInspector.FileSystemMapping.Events.ExcludedFolderAdded, this._excludedFolderAdded, this); 55 WebInspector.isolatedFileSystemManager.mapping().addEventListener(WebInspector.FileSystemMapping.Events.ExcludedFolderRemoved, this._excludedFolderRemoved, this); 56 57 var blockHeader = contents.createChild("div", "block-header"); 58 blockHeader.textContent = WebInspector.UIString("Mappings"); 59 this._fileMappingsSection = contents.createChild("div", "section"); 60 this._fileMappingsListContainer = this._fileMappingsSection.createChild("div", "settings-list-container"); 61 var entries = WebInspector.isolatedFileSystemManager.mapping().mappingEntries(this._fileSystemPath); 62 63 var urlColumn = { id: "url", placeholder: WebInspector.UIString("URL prefix") }; 64 var pathColumn = { id: "path", placeholder: WebInspector.UIString("Folder path") }; 65 66 this._fileMappingsList = new WebInspector.EditableSettingsList([urlColumn, pathColumn], this._fileMappingValuesProvider.bind(this), this._fileMappingValidate.bind(this), this._fileMappingEdit.bind(this)); 67 this._fileMappingsList.addEventListener(WebInspector.SettingsList.Events.Removed, this._fileMappingRemovedfromList.bind(this)); 68 69 this._fileMappingsList.element.classList.add("file-mappings-list"); 70 this._fileMappingsListContainer.appendChild(this._fileMappingsList.element); 71 72 this._entries = {}; 73 for (var i = 0; i < entries.length; ++i) 74 this._addMappingRow(entries[i]); 75 76 blockHeader = contents.createChild("div", "block-header"); 77 blockHeader.textContent = WebInspector.UIString("Excluded folders"); 78 this._excludedFolderListSection = contents.createChild("div", "section excluded-folders-section"); 79 this._excludedFolderListContainer = this._excludedFolderListSection.createChild("div", "settings-list-container"); 80 var excludedFolderEntries = WebInspector.isolatedFileSystemManager.mapping().excludedFolders(fileSystemPath); 81 82 this._excludedFolderList = new WebInspector.EditableSettingsList([pathColumn], this._excludedFolderValueProvider.bind(this), this._excludedFolderValidate.bind(this), this._excludedFolderEdit.bind(this)); 83 this._excludedFolderList.addEventListener(WebInspector.SettingsList.Events.Removed, this._excludedFolderRemovedfromList.bind(this)); 84 this._excludedFolderList.element.classList.add("excluded-folders-list"); 85 this._excludedFolderListContainer.appendChild(this._excludedFolderList.element); 86 this._excludedFolderEntries = new StringMap(); 87 for (var i = 0; i < excludedFolderEntries.length; ++i) 88 this._addExcludedFolderRow(excludedFolderEntries[i]); 89 90 this.element.tabIndex = 0; 91 this._hasMappingChanges = false; 92 } 93 94 WebInspector.EditFileSystemDialog.show = function(element, fileSystemPath) 95 { 96 WebInspector.Dialog.show(element, new WebInspector.EditFileSystemDialog(fileSystemPath)); 97 var glassPane = document.getElementById("glass-pane"); 98 glassPane.classList.add("settings-glass-pane"); 99 } 100 101 WebInspector.EditFileSystemDialog.prototype = { 102 /** 103 * @param {!Element} element 104 */ 105 show: function(element) 106 { 107 this._dialogElement = element; 108 element.appendChild(this.element); 109 element.classList.add("settings-dialog", "settings-tab"); 110 }, 111 112 _resize: function() 113 { 114 if (!this._dialogElement || !this._relativeToElement) 115 return; 116 117 const minWidth = 200; 118 const minHeight = 150; 119 var maxHeight = this._relativeToElement.offsetHeight - 10; 120 maxHeight = Math.max(minHeight, maxHeight); 121 var maxWidth = Math.min(540, this._relativeToElement.offsetWidth - 10); 122 maxWidth = Math.max(minWidth, maxWidth); 123 this._dialogElement.style.maxHeight = maxHeight + "px"; 124 this._dialogElement.style.width = maxWidth + "px"; 125 126 WebInspector.DialogDelegate.prototype.position(this._dialogElement, this._relativeToElement); 127 }, 128 129 /** 130 * @param {!Element} element 131 * @param {!Element} relativeToElement 132 */ 133 position: function(element, relativeToElement) 134 { 135 this._relativeToElement = relativeToElement; 136 this._resize(); 137 }, 138 139 willHide: function(event) 140 { 141 if (!this._hasMappingChanges) 142 return; 143 if (window.confirm(WebInspector.UIString("It is recommended to restart DevTools after making these changes. Would you like to restart it?"))) 144 WebInspector.reload(); 145 }, 146 147 _fileMappingAdded: function(event) 148 { 149 var entry = /** @type {!WebInspector.FileSystemMapping.Entry} */ (event.data); 150 this._addMappingRow(entry); 151 }, 152 153 _fileMappingRemoved: function(event) 154 { 155 var entry = /** @type {!WebInspector.FileSystemMapping.Entry} */ (event.data); 156 if (this._fileSystemPath !== entry.fileSystemPath) 157 return; 158 delete this._entries[entry.urlPrefix]; 159 if (this._fileMappingsList.itemForId(entry.urlPrefix)) 160 this._fileMappingsList.removeItem(entry.urlPrefix); 161 this._resize(); 162 }, 163 164 /** 165 * @param {string} itemId 166 * @param {string} columnId 167 * @return {string} 168 */ 169 _fileMappingValuesProvider: function(itemId, columnId) 170 { 171 if (!itemId) 172 return ""; 173 var entry = this._entries[itemId]; 174 switch (columnId) { 175 case "url": 176 return entry.urlPrefix; 177 case "path": 178 return entry.pathPrefix; 179 default: 180 console.assert("Should not be reached."); 181 } 182 return ""; 183 }, 184 185 /** 186 * @param {?string} itemId 187 * @param {!Object} data 188 */ 189 _fileMappingValidate: function(itemId, data) 190 { 191 var oldPathPrefix = itemId ? this._entries[itemId].pathPrefix : null; 192 return this._validateMapping(data["url"], itemId, data["path"], oldPathPrefix); 193 }, 194 195 /** 196 * @param {?string} itemId 197 * @param {!Object} data 198 */ 199 _fileMappingEdit: function(itemId, data) 200 { 201 if (itemId) { 202 var urlPrefix = itemId; 203 var pathPrefix = this._entries[itemId].pathPrefix; 204 var fileSystemPath = this._entries[itemId].fileSystemPath; 205 WebInspector.isolatedFileSystemManager.mapping().removeFileMapping(fileSystemPath, urlPrefix, pathPrefix); 206 } 207 this._addFileMapping(data["url"], data["path"]); 208 }, 209 210 /** 211 * @param {string} urlPrefix 212 * @param {?string} allowedURLPrefix 213 * @param {string} path 214 * @param {?string} allowedPathPrefix 215 */ 216 _validateMapping: function(urlPrefix, allowedURLPrefix, path, allowedPathPrefix) 217 { 218 var columns = []; 219 if (!this._checkURLPrefix(urlPrefix, allowedURLPrefix)) 220 columns.push("url"); 221 if (!this._checkPathPrefix(path, allowedPathPrefix)) 222 columns.push("path"); 223 return columns; 224 }, 225 226 /** 227 * @param {!WebInspector.Event} event 228 */ 229 _fileMappingRemovedfromList: function(event) 230 { 231 var urlPrefix = /** @type{?string} */ (event.data); 232 if (!urlPrefix) 233 return; 234 235 var entry = this._entries[urlPrefix]; 236 WebInspector.isolatedFileSystemManager.mapping().removeFileMapping(entry.fileSystemPath, entry.urlPrefix, entry.pathPrefix); 237 this._hasMappingChanges = true; 238 }, 239 240 /** 241 * @param {string} urlPrefix 242 * @param {string} pathPrefix 243 * @return {boolean} 244 */ 245 _addFileMapping: function(urlPrefix, pathPrefix) 246 { 247 var normalizedURLPrefix = this._normalizePrefix(urlPrefix); 248 var normalizedPathPrefix = this._normalizePrefix(pathPrefix); 249 WebInspector.isolatedFileSystemManager.mapping().addFileMapping(this._fileSystemPath, normalizedURLPrefix, normalizedPathPrefix); 250 this._hasMappingChanges = true; 251 this._fileMappingsList.selectItem(normalizedURLPrefix); 252 return true; 253 }, 254 255 /** 256 * @param {string} prefix 257 * @return {string} 258 */ 259 _normalizePrefix: function(prefix) 260 { 261 if (!prefix) 262 return ""; 263 return prefix + (prefix[prefix.length - 1] === "/" ? "" : "/"); 264 }, 265 266 _addMappingRow: function(entry) 267 { 268 var fileSystemPath = entry.fileSystemPath; 269 var urlPrefix = entry.urlPrefix; 270 if (!this._fileSystemPath || this._fileSystemPath !== fileSystemPath) 271 return; 272 273 this._entries[urlPrefix] = entry; 274 var fileMappingListItem = this._fileMappingsList.addItem(urlPrefix, null); 275 this._resize(); 276 }, 277 278 _excludedFolderAdded: function(event) 279 { 280 var entry = /** @type {!WebInspector.FileSystemMapping.ExcludedFolderEntry} */ (event.data); 281 this._addExcludedFolderRow(entry); 282 }, 283 284 _excludedFolderRemoved: function(event) 285 { 286 var entry = /** @type {!WebInspector.FileSystemMapping.ExcludedFolderEntry} */ (event.data); 287 var fileSystemPath = entry.fileSystemPath; 288 if (!fileSystemPath || this._fileSystemPath !== fileSystemPath) 289 return; 290 delete this._excludedFolderEntries[entry.path]; 291 if (this._excludedFolderList.itemForId(entry.path)) 292 this._excludedFolderList.removeItem(entry.path); 293 }, 294 295 /** 296 * @param {string} itemId 297 * @param {string} columnId 298 * @return {string} 299 */ 300 _excludedFolderValueProvider: function(itemId, columnId) 301 { 302 return itemId; 303 }, 304 305 /** 306 * @param {?string} itemId 307 * @param {!Object} data 308 */ 309 _excludedFolderValidate: function(itemId, data) 310 { 311 var fileSystemPath = this._fileSystemPath; 312 var columns = []; 313 if (!this._validateExcludedFolder(data["path"], itemId)) 314 columns.push("path"); 315 return columns; 316 }, 317 318 /** 319 * @param {string} path 320 * @param {?string} allowedPath 321 * @return {boolean} 322 */ 323 _validateExcludedFolder: function(path, allowedPath) 324 { 325 return !!path && (path === allowedPath || !this._excludedFolderEntries.has(path)); 326 }, 327 328 /** 329 * @param {?string} itemId 330 * @param {!Object} data 331 */ 332 _excludedFolderEdit: function(itemId, data) 333 { 334 var fileSystemPath = this._fileSystemPath; 335 if (itemId) 336 WebInspector.isolatedFileSystemManager.mapping().removeExcludedFolder(fileSystemPath, itemId); 337 var excludedFolderPath = data["path"]; 338 WebInspector.isolatedFileSystemManager.mapping().addExcludedFolder(fileSystemPath, excludedFolderPath); 339 }, 340 341 /** 342 * @param {!WebInspector.Event} event 343 */ 344 _excludedFolderRemovedfromList: function(event) 345 { 346 var itemId = /** @type{?string} */ (event.data); 347 if (!itemId) 348 return; 349 WebInspector.isolatedFileSystemManager.mapping().removeExcludedFolder(this._fileSystemPath, itemId); 350 }, 351 352 /** 353 * @param {!WebInspector.FileSystemMapping.ExcludedFolderEntry} entry 354 */ 355 _addExcludedFolderRow: function(entry) 356 { 357 var fileSystemPath = entry.fileSystemPath; 358 if (!fileSystemPath || this._fileSystemPath !== fileSystemPath) 359 return; 360 var path = entry.path; 361 this._excludedFolderEntries.set(path, entry); 362 this._excludedFolderList.addItem(path, null); 363 this._resize(); 364 }, 365 366 /** 367 * @param {string} value 368 * @param {?string} allowedPrefix 369 * @return {boolean} 370 */ 371 _checkURLPrefix: function(value, allowedPrefix) 372 { 373 var prefix = this._normalizePrefix(value); 374 return !!prefix && (prefix === allowedPrefix || !this._entries[prefix]); 375 }, 376 377 /** 378 * @param {string} value 379 * @param {?string} allowedPrefix 380 * @return {boolean} 381 */ 382 _checkPathPrefix: function(value, allowedPrefix) 383 { 384 var prefix = this._normalizePrefix(value); 385 if (!prefix) 386 return false; 387 if (prefix === allowedPrefix) 388 return true; 389 for (var urlPrefix in this._entries) { 390 var entry = this._entries[urlPrefix]; 391 if (urlPrefix && entry.pathPrefix === prefix) 392 return false; 393 } 394 return true; 395 }, 396 397 focus: function() 398 { 399 WebInspector.setCurrentFocusElement(this.element); 400 }, 401 402 _onDoneClick: function() 403 { 404 WebInspector.Dialog.hide(); 405 }, 406 407 onEnter: function() 408 { 409 }, 410 411 __proto__: WebInspector.DialogDelegate.prototype 412 } 413