1 /* 2 * Copyright (C) 2012 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 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS 17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. 20 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 /** 30 * @constructor 31 * @extends {WebInspector.VBox} 32 */ 33 WebInspector.NavigatorView = function() 34 { 35 WebInspector.VBox.call(this); 36 this.registerRequiredCSS("navigatorView.css"); 37 38 var scriptsTreeElement = document.createElement("ol"); 39 this._scriptsTree = new WebInspector.NavigatorTreeOutline(scriptsTreeElement); 40 41 var scriptsOutlineElement = document.createElement("div"); 42 scriptsOutlineElement.classList.add("outline-disclosure"); 43 scriptsOutlineElement.classList.add("navigator"); 44 scriptsOutlineElement.appendChild(scriptsTreeElement); 45 46 this.element.classList.add("navigator-container"); 47 this.element.appendChild(scriptsOutlineElement); 48 this.setDefaultFocusedElement(this._scriptsTree.element); 49 50 /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.NavigatorUISourceCodeTreeNode>} */ 51 this._uiSourceCodeNodes = new Map(); 52 /** @type {!Map.<!WebInspector.NavigatorTreeNode, !StringMap.<!WebInspector.NavigatorFolderTreeNode>>} */ 53 this._subfolderNodes = new Map(); 54 55 this._rootNode = new WebInspector.NavigatorRootTreeNode(this); 56 this._rootNode.populate(); 57 58 this.element.addEventListener("contextmenu", this.handleContextMenu.bind(this), false); 59 } 60 61 WebInspector.NavigatorView.Events = { 62 ItemSelected: "ItemSelected", 63 ItemRenamed: "ItemRenamed", 64 } 65 66 /** 67 * @param {string} type 68 * @return {string} 69 */ 70 WebInspector.NavigatorView.iconClassForType = function(type) 71 { 72 if (type === WebInspector.NavigatorTreeOutline.Types.Domain) 73 return "navigator-domain-tree-item"; 74 if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem) 75 return "navigator-folder-tree-item"; 76 return "navigator-folder-tree-item"; 77 } 78 79 WebInspector.NavigatorView.prototype = { 80 setWorkspace: function(workspace) 81 { 82 this._workspace = workspace; 83 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this); 84 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this); 85 this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved.bind(this), this); 86 }, 87 88 wasShown: function() 89 { 90 if (this._loaded) 91 return; 92 this._loaded = true; 93 this._workspace.uiSourceCodes().forEach(this._addUISourceCode.bind(this)); 94 }, 95 96 /** 97 * @param {!WebInspector.UISourceCode} uiSourceCode 98 * @return {boolean} 99 */ 100 accept: function(uiSourceCode) 101 { 102 return !uiSourceCode.project().isServiceProject(); 103 }, 104 105 /** 106 * @param {!WebInspector.UISourceCode} uiSourceCode 107 */ 108 _addUISourceCode: function(uiSourceCode) 109 { 110 if (!this.accept(uiSourceCode)) 111 return; 112 var projectNode = this._projectNode(uiSourceCode.project()); 113 var folderNode = this._folderNode(projectNode, uiSourceCode.parentPath()); 114 var uiSourceCodeNode = new WebInspector.NavigatorUISourceCodeTreeNode(this, uiSourceCode); 115 this._uiSourceCodeNodes.put(uiSourceCode, uiSourceCodeNode); 116 folderNode.appendChild(uiSourceCodeNode); 117 }, 118 119 /** 120 * @param {!WebInspector.Event} event 121 */ 122 _uiSourceCodeAdded: function(event) 123 { 124 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 125 this._addUISourceCode(uiSourceCode); 126 }, 127 128 /** 129 * @param {!WebInspector.Event} event 130 */ 131 _uiSourceCodeRemoved: function(event) 132 { 133 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 134 this._removeUISourceCode(uiSourceCode); 135 }, 136 137 /** 138 * @param {!WebInspector.Event} event 139 */ 140 _projectRemoved: function(event) 141 { 142 var project = /** @type {!WebInspector.Project} */ (event.data); 143 var uiSourceCodes = project.uiSourceCodes(); 144 for (var i = 0; i < uiSourceCodes.length; ++i) 145 this._removeUISourceCode(uiSourceCodes[i]); 146 }, 147 148 /** 149 * @param {!WebInspector.Project} project 150 * @return {!WebInspector.NavigatorTreeNode} 151 */ 152 _projectNode: function(project) 153 { 154 if (!project.displayName()) 155 return this._rootNode; 156 157 var projectNode = this._rootNode.child(project.id()); 158 if (!projectNode) { 159 var type = project.type() === WebInspector.projectTypes.FileSystem ? WebInspector.NavigatorTreeOutline.Types.FileSystem : WebInspector.NavigatorTreeOutline.Types.Domain; 160 projectNode = new WebInspector.NavigatorFolderTreeNode(this, project, project.id(), type, "", project.displayName()); 161 this._rootNode.appendChild(projectNode); 162 } 163 return projectNode; 164 }, 165 166 /** 167 * @param {!WebInspector.NavigatorTreeNode} projectNode 168 * @param {string} folderPath 169 * @return {!WebInspector.NavigatorTreeNode} 170 */ 171 _folderNode: function(projectNode, folderPath) 172 { 173 if (!folderPath) 174 return projectNode; 175 176 var subfolderNodes = this._subfolderNodes.get(projectNode); 177 if (!subfolderNodes) { 178 subfolderNodes = /** @type {!StringMap.<!WebInspector.NavigatorFolderTreeNode>} */ (new StringMap()); 179 this._subfolderNodes.put(projectNode, subfolderNodes); 180 } 181 182 var folderNode = subfolderNodes.get(folderPath); 183 if (folderNode) 184 return folderNode; 185 186 var parentNode = projectNode; 187 var index = folderPath.lastIndexOf("/"); 188 if (index !== -1) 189 parentNode = this._folderNode(projectNode, folderPath.substring(0, index)); 190 191 var name = folderPath.substring(index + 1); 192 folderNode = new WebInspector.NavigatorFolderTreeNode(this, null, name, WebInspector.NavigatorTreeOutline.Types.Folder, folderPath, name); 193 subfolderNodes.put(folderPath, folderNode); 194 parentNode.appendChild(folderNode); 195 return folderNode; 196 }, 197 198 /** 199 * @param {!WebInspector.UISourceCode} uiSourceCode 200 * @param {boolean=} select 201 */ 202 revealUISourceCode: function(uiSourceCode, select) 203 { 204 var node = this._uiSourceCodeNodes.get(uiSourceCode); 205 if (!node) 206 return; 207 if (this._scriptsTree.selectedTreeElement) 208 this._scriptsTree.selectedTreeElement.deselect(); 209 this._lastSelectedUISourceCode = uiSourceCode; 210 node.reveal(select); 211 }, 212 213 /** 214 * @param {!WebInspector.UISourceCode} uiSourceCode 215 * @param {boolean} focusSource 216 */ 217 _sourceSelected: function(uiSourceCode, focusSource) 218 { 219 this._lastSelectedUISourceCode = uiSourceCode; 220 var data = { uiSourceCode: uiSourceCode, focusSource: focusSource}; 221 this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemSelected, data); 222 }, 223 224 /** 225 * @param {!WebInspector.UISourceCode} uiSourceCode 226 */ 227 sourceDeleted: function(uiSourceCode) 228 { 229 }, 230 231 /** 232 * @param {!WebInspector.UISourceCode} uiSourceCode 233 */ 234 _removeUISourceCode: function(uiSourceCode) 235 { 236 var node = this._uiSourceCodeNodes.get(uiSourceCode); 237 if (!node) 238 return; 239 240 var projectNode = this._projectNode(uiSourceCode.project()); 241 var subfolderNodes = this._subfolderNodes.get(projectNode); 242 var parentNode = node.parent; 243 this._uiSourceCodeNodes.remove(uiSourceCode); 244 parentNode.removeChild(node); 245 node = parentNode; 246 247 while (node) { 248 parentNode = node.parent; 249 if (!parentNode || !node.isEmpty()) 250 break; 251 if (subfolderNodes) 252 subfolderNodes.remove(node._folderPath); 253 parentNode.removeChild(node); 254 node = parentNode; 255 } 256 }, 257 258 /** 259 * @param {!WebInspector.UISourceCode} uiSourceCode 260 */ 261 _updateIcon: function(uiSourceCode) 262 { 263 var node = this._uiSourceCodeNodes.get(uiSourceCode); 264 node.updateIcon(); 265 }, 266 267 reset: function() 268 { 269 var nodes = this._uiSourceCodeNodes.values(); 270 for (var i = 0; i < nodes.length; ++i) 271 nodes[i].dispose(); 272 273 this._scriptsTree.removeChildren(); 274 this._uiSourceCodeNodes.clear(); 275 this._subfolderNodes.clear(); 276 this._rootNode.reset(); 277 }, 278 279 /** 280 * @param {?Event} event 281 */ 282 handleContextMenu: function(event) 283 { 284 var contextMenu = new WebInspector.ContextMenu(event); 285 this._appendAddFolderItem(contextMenu); 286 contextMenu.show(); 287 }, 288 289 /** 290 * @param {!WebInspector.ContextMenu} contextMenu 291 */ 292 _appendAddFolderItem: function(contextMenu) 293 { 294 function addFolder() 295 { 296 WebInspector.isolatedFileSystemManager.addFileSystem(); 297 } 298 299 var addFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add folder to workspace" : "Add Folder to Workspace"); 300 contextMenu.appendItem(addFolderLabel, addFolder); 301 }, 302 303 /** 304 * @param {!WebInspector.Project} project 305 * @param {string} path 306 */ 307 _handleContextMenuRefresh: function(project, path) 308 { 309 project.refresh(path); 310 }, 311 312 /** 313 * @param {!WebInspector.Project} project 314 * @param {string} path 315 * @param {!WebInspector.UISourceCode=} uiSourceCode 316 */ 317 _handleContextMenuCreate: function(project, path, uiSourceCode) 318 { 319 this.create(project, path, uiSourceCode); 320 }, 321 322 /** 323 * @param {!WebInspector.UISourceCode} uiSourceCode 324 */ 325 _handleContextMenuRename: function(uiSourceCode) 326 { 327 this.rename(uiSourceCode, false); 328 }, 329 330 /** 331 * @param {!WebInspector.Project} project 332 * @param {string} path 333 */ 334 _handleContextMenuExclude: function(project, path) 335 { 336 var shouldExclude = window.confirm(WebInspector.UIString("Are you sure you want to exclude this folder?")); 337 if (shouldExclude) { 338 WebInspector.startBatchUpdate(); 339 project.excludeFolder(path); 340 WebInspector.endBatchUpdate(); 341 } 342 }, 343 344 /** 345 * @param {!WebInspector.UISourceCode} uiSourceCode 346 */ 347 _handleContextMenuDelete: function(uiSourceCode) 348 { 349 var shouldDelete = window.confirm(WebInspector.UIString("Are you sure you want to delete this file?")); 350 if (shouldDelete) 351 uiSourceCode.project().deleteFile(uiSourceCode.path()); 352 }, 353 354 /** 355 * @param {!Event} event 356 * @param {!WebInspector.UISourceCode} uiSourceCode 357 */ 358 handleFileContextMenu: function(event, uiSourceCode) 359 { 360 var contextMenu = new WebInspector.ContextMenu(event); 361 contextMenu.appendApplicableItems(uiSourceCode); 362 contextMenu.appendSeparator(); 363 364 var project = uiSourceCode.project(); 365 if (project.type() === WebInspector.projectTypes.FileSystem) { 366 var path = uiSourceCode.parentPath(); 367 contextMenu.appendItem(WebInspector.UIString("Rename\u2026"), this._handleContextMenuRename.bind(this, uiSourceCode)); 368 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Make a copy\u2026" : "Make a Copy\u2026"), this._handleContextMenuCreate.bind(this, project, path, uiSourceCode)); 369 contextMenu.appendItem(WebInspector.UIString("Delete"), this._handleContextMenuDelete.bind(this, uiSourceCode)); 370 contextMenu.appendSeparator(); 371 } 372 373 this._appendAddFolderItem(contextMenu); 374 contextMenu.show(); 375 }, 376 377 /** 378 * @param {!Event} event 379 * @param {!WebInspector.NavigatorFolderTreeNode} node 380 */ 381 handleFolderContextMenu: function(event, node) 382 { 383 var contextMenu = new WebInspector.ContextMenu(event); 384 var path = "/"; 385 var projectNode = node; 386 while (projectNode.parent !== this._rootNode) { 387 path = "/" + projectNode.id + path; 388 projectNode = projectNode.parent; 389 } 390 391 var project = projectNode._project; 392 393 if (project.type() === WebInspector.projectTypes.FileSystem) { 394 contextMenu.appendItem(WebInspector.UIString("Refresh"), this._handleContextMenuRefresh.bind(this, project, path)); 395 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "New file" : "New File"), this._handleContextMenuCreate.bind(this, project, path)); 396 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Exclude folder" : "Exclude Folder"), this._handleContextMenuExclude.bind(this, project, path)); 397 } 398 contextMenu.appendSeparator(); 399 this._appendAddFolderItem(contextMenu); 400 401 function removeFolder() 402 { 403 var shouldRemove = window.confirm(WebInspector.UIString("Are you sure you want to remove this folder?")); 404 if (shouldRemove) 405 project.remove(); 406 } 407 408 if (project.type() === WebInspector.projectTypes.FileSystem && node === projectNode) { 409 var removeFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove folder from workspace" : "Remove Folder from Workspace"); 410 contextMenu.appendItem(removeFolderLabel, removeFolder); 411 } 412 413 contextMenu.show(); 414 }, 415 416 /** 417 * @param {!WebInspector.UISourceCode} uiSourceCode 418 * @param {boolean} deleteIfCanceled 419 */ 420 rename: function(uiSourceCode, deleteIfCanceled) 421 { 422 var node = this._uiSourceCodeNodes.get(uiSourceCode); 423 console.assert(node); 424 node.rename(callback.bind(this)); 425 426 /** 427 * @this {WebInspector.NavigatorView} 428 * @param {boolean} committed 429 */ 430 function callback(committed) 431 { 432 if (!committed) { 433 if (deleteIfCanceled) 434 uiSourceCode.remove(); 435 return; 436 } 437 438 this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemRenamed, uiSourceCode); 439 this._updateIcon(uiSourceCode); 440 this._sourceSelected(uiSourceCode, true) 441 } 442 }, 443 444 /** 445 * @param {!WebInspector.Project} project 446 * @param {string} path 447 * @param {!WebInspector.UISourceCode=} uiSourceCodeToCopy 448 */ 449 create: function(project, path, uiSourceCodeToCopy) 450 { 451 var filePath; 452 var uiSourceCode; 453 454 /** 455 * @this {WebInspector.NavigatorView} 456 * @param {?string} content 457 */ 458 function contentLoaded(content) 459 { 460 createFile.call(this, content || ""); 461 } 462 463 if (uiSourceCodeToCopy) 464 uiSourceCodeToCopy.requestContent(contentLoaded.bind(this)); 465 else 466 createFile.call(this); 467 468 /** 469 * @this {WebInspector.NavigatorView} 470 * @param {string=} content 471 */ 472 function createFile(content) 473 { 474 project.createFile(path, null, content || "", fileCreated.bind(this)); 475 } 476 477 /** 478 * @this {WebInspector.NavigatorView} 479 * @param {?string} path 480 */ 481 function fileCreated(path) 482 { 483 if (!path) 484 return; 485 filePath = path; 486 uiSourceCode = project.uiSourceCode(filePath); 487 if (!uiSourceCode) { 488 console.assert(uiSourceCode) 489 return; 490 } 491 this._sourceSelected(uiSourceCode, false); 492 this.revealUISourceCode(uiSourceCode, true); 493 this.rename(uiSourceCode, true); 494 } 495 }, 496 497 __proto__: WebInspector.VBox.prototype 498 } 499 500 /** 501 * @constructor 502 * @extends {WebInspector.NavigatorView} 503 */ 504 WebInspector.SourcesNavigatorView = function() 505 { 506 WebInspector.NavigatorView.call(this); 507 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this); 508 } 509 510 WebInspector.SourcesNavigatorView.prototype = { 511 /** 512 * @override 513 * @param {!WebInspector.UISourceCode} uiSourceCode 514 * @return {boolean} 515 */ 516 accept: function(uiSourceCode) 517 { 518 if (!WebInspector.NavigatorView.prototype.accept(uiSourceCode)) 519 return false; 520 return uiSourceCode.project().type() !== WebInspector.projectTypes.ContentScripts && uiSourceCode.project().type() !== WebInspector.projectTypes.Snippets; 521 522 }, 523 524 /** 525 * @param {!WebInspector.Event} event 526 */ 527 _inspectedURLChanged: function(event) 528 { 529 var nodes = this._uiSourceCodeNodes.values(); 530 for (var i = 0; i < nodes.length; ++i) { 531 var uiSourceCode = nodes[i].uiSourceCode(); 532 if (uiSourceCode.url === WebInspector.resourceTreeModel.inspectedPageURL()) 533 this.revealUISourceCode(uiSourceCode, true); 534 } 535 }, 536 537 /** 538 * @param {!WebInspector.UISourceCode} uiSourceCode 539 */ 540 _addUISourceCode: function(uiSourceCode) 541 { 542 WebInspector.NavigatorView.prototype._addUISourceCode.call(this, uiSourceCode); 543 if (uiSourceCode.url === WebInspector.resourceTreeModel.inspectedPageURL()) 544 this.revealUISourceCode(uiSourceCode, true); 545 }, 546 547 __proto__: WebInspector.NavigatorView.prototype 548 } 549 550 /** 551 * @constructor 552 * @extends {WebInspector.NavigatorView} 553 */ 554 WebInspector.ContentScriptsNavigatorView = function() 555 { 556 WebInspector.NavigatorView.call(this); 557 } 558 559 WebInspector.ContentScriptsNavigatorView.prototype = { 560 /** 561 * @override 562 * @param {!WebInspector.UISourceCode} uiSourceCode 563 * @return {boolean} 564 */ 565 accept: function(uiSourceCode) 566 { 567 if (!WebInspector.NavigatorView.prototype.accept(uiSourceCode)) 568 return false; 569 return uiSourceCode.project().type() === WebInspector.projectTypes.ContentScripts; 570 }, 571 572 __proto__: WebInspector.NavigatorView.prototype 573 } 574 575 /** 576 * @constructor 577 * @extends {TreeOutline} 578 * @param {!Element} element 579 */ 580 WebInspector.NavigatorTreeOutline = function(element) 581 { 582 TreeOutline.call(this, element); 583 this.element = element; 584 585 this.comparator = WebInspector.NavigatorTreeOutline._treeElementsCompare; 586 } 587 588 WebInspector.NavigatorTreeOutline.Types = { 589 Root: "Root", 590 Domain: "Domain", 591 Folder: "Folder", 592 UISourceCode: "UISourceCode", 593 FileSystem: "FileSystem" 594 } 595 596 /** 597 * @param {!TreeElement} treeElement1 598 * @param {!TreeElement} treeElement2 599 * @return {number} 600 */ 601 WebInspector.NavigatorTreeOutline._treeElementsCompare = function compare(treeElement1, treeElement2) 602 { 603 // Insert in the alphabetical order, first domains, then folders, then scripts. 604 function typeWeight(treeElement) 605 { 606 var type = treeElement.type(); 607 if (type === WebInspector.NavigatorTreeOutline.Types.Domain) { 608 if (treeElement.titleText === WebInspector.resourceTreeModel.inspectedPageDomain()) 609 return 1; 610 return 2; 611 } 612 if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem) 613 return 3; 614 if (type === WebInspector.NavigatorTreeOutline.Types.Folder) 615 return 4; 616 return 5; 617 } 618 619 var typeWeight1 = typeWeight(treeElement1); 620 var typeWeight2 = typeWeight(treeElement2); 621 622 var result; 623 if (typeWeight1 > typeWeight2) 624 result = 1; 625 else if (typeWeight1 < typeWeight2) 626 result = -1; 627 else { 628 var title1 = treeElement1.titleText; 629 var title2 = treeElement2.titleText; 630 result = title1.compareTo(title2); 631 } 632 return result; 633 } 634 635 WebInspector.NavigatorTreeOutline.prototype = { 636 /** 637 * @return {!Array.<!WebInspector.UISourceCode>} 638 */ 639 scriptTreeElements: function() 640 { 641 var result = []; 642 if (this.children.length) { 643 for (var treeElement = this.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this, true)) { 644 if (treeElement instanceof WebInspector.NavigatorSourceTreeElement) 645 result.push(treeElement.uiSourceCode); 646 } 647 } 648 return result; 649 }, 650 651 __proto__: TreeOutline.prototype 652 } 653 654 /** 655 * @constructor 656 * @extends {TreeElement} 657 * @param {string} type 658 * @param {string} title 659 * @param {!Array.<string>} iconClasses 660 * @param {boolean} hasChildren 661 * @param {boolean=} noIcon 662 */ 663 WebInspector.BaseNavigatorTreeElement = function(type, title, iconClasses, hasChildren, noIcon) 664 { 665 this._type = type; 666 TreeElement.call(this, "", null, hasChildren); 667 this._titleText = title; 668 this._iconClasses = iconClasses; 669 this._noIcon = noIcon; 670 } 671 672 WebInspector.BaseNavigatorTreeElement.prototype = { 673 onattach: function() 674 { 675 this.listItemElement.removeChildren(); 676 if (this._iconClasses) { 677 for (var i = 0; i < this._iconClasses.length; ++i) 678 this.listItemElement.classList.add(this._iconClasses[i]); 679 } 680 681 var selectionElement = document.createElement("div"); 682 selectionElement.className = "selection"; 683 this.listItemElement.appendChild(selectionElement); 684 685 if (!this._noIcon) { 686 this.imageElement = document.createElement("img"); 687 this.imageElement.className = "icon"; 688 this.listItemElement.appendChild(this.imageElement); 689 } 690 691 this.titleElement = document.createElement("div"); 692 this.titleElement.className = "base-navigator-tree-element-title"; 693 this._titleTextNode = document.createTextNode(""); 694 this._titleTextNode.textContent = this._titleText; 695 this.titleElement.appendChild(this._titleTextNode); 696 this.listItemElement.appendChild(this.titleElement); 697 }, 698 699 updateIconClasses: function(iconClasses) 700 { 701 for (var i = 0; i < this._iconClasses.length; ++i) 702 this.listItemElement.classList.remove(this._iconClasses[i]); 703 this._iconClasses = iconClasses; 704 for (var i = 0; i < this._iconClasses.length; ++i) 705 this.listItemElement.classList.add(this._iconClasses[i]); 706 }, 707 708 onreveal: function() 709 { 710 if (this.listItemElement) 711 this.listItemElement.scrollIntoViewIfNeeded(true); 712 }, 713 714 /** 715 * @return {string} 716 */ 717 get titleText() 718 { 719 return this._titleText; 720 }, 721 722 set titleText(titleText) 723 { 724 if (this._titleText === titleText) 725 return; 726 this._titleText = titleText || ""; 727 if (this.titleElement) 728 this.titleElement.textContent = this._titleText; 729 }, 730 731 /** 732 * @return {string} 733 */ 734 type: function() 735 { 736 return this._type; 737 }, 738 739 __proto__: TreeElement.prototype 740 } 741 742 /** 743 * @constructor 744 * @extends {WebInspector.BaseNavigatorTreeElement} 745 * @param {!WebInspector.NavigatorView} navigatorView 746 * @param {string} type 747 * @param {string} title 748 */ 749 WebInspector.NavigatorFolderTreeElement = function(navigatorView, type, title) 750 { 751 var iconClass = WebInspector.NavigatorView.iconClassForType(type); 752 WebInspector.BaseNavigatorTreeElement.call(this, type, title, [iconClass], true); 753 this._navigatorView = navigatorView; 754 } 755 756 WebInspector.NavigatorFolderTreeElement.prototype = { 757 onpopulate: function() 758 { 759 this._node.populate(); 760 }, 761 762 onattach: function() 763 { 764 WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this); 765 this.collapse(); 766 this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 767 }, 768 769 /** 770 * @param {!WebInspector.NavigatorFolderTreeNode} node 771 */ 772 setNode: function(node) 773 { 774 this._node = node; 775 var paths = []; 776 while (node && !node.isRoot()) { 777 paths.push(node._title); 778 node = node.parent; 779 } 780 paths.reverse(); 781 this.tooltip = paths.join("/"); 782 }, 783 784 /** 785 * @param {?Event} event 786 */ 787 _handleContextMenuEvent: function(event) 788 { 789 if (!this._node) 790 return; 791 this.select(); 792 this._navigatorView.handleFolderContextMenu(/** @type {!Event} */ (event), this._node); 793 }, 794 795 __proto__: WebInspector.BaseNavigatorTreeElement.prototype 796 } 797 798 /** 799 * @constructor 800 * @extends {WebInspector.BaseNavigatorTreeElement} 801 * @param {!WebInspector.NavigatorView} navigatorView 802 * @param {!WebInspector.UISourceCode} uiSourceCode 803 * @param {string} title 804 */ 805 WebInspector.NavigatorSourceTreeElement = function(navigatorView, uiSourceCode, title) 806 { 807 this._navigatorView = navigatorView; 808 this._uiSourceCode = uiSourceCode; 809 WebInspector.BaseNavigatorTreeElement.call(this, WebInspector.NavigatorTreeOutline.Types.UISourceCode, title, this._calculateIconClasses(), false); 810 this.tooltip = uiSourceCode.originURL(); 811 } 812 813 WebInspector.NavigatorSourceTreeElement.prototype = { 814 /** 815 * @return {!WebInspector.UISourceCode} 816 */ 817 get uiSourceCode() 818 { 819 return this._uiSourceCode; 820 }, 821 822 /** 823 * @return {!Array.<string>} 824 */ 825 _calculateIconClasses: function() 826 { 827 return ["navigator-" + this._uiSourceCode.contentType().name() + "-tree-item"]; 828 }, 829 830 updateIcon: function() 831 { 832 this.updateIconClasses(this._calculateIconClasses()); 833 }, 834 835 onattach: function() 836 { 837 WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this); 838 this.listItemElement.draggable = true; 839 this.listItemElement.addEventListener("click", this._onclick.bind(this), false); 840 this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 841 this.listItemElement.addEventListener("mousedown", this._onmousedown.bind(this), false); 842 this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false); 843 }, 844 845 _onmousedown: function(event) 846 { 847 if (event.which === 1) // Warm-up data for drag'n'drop 848 this._uiSourceCode.requestContent(callback.bind(this)); 849 /** 850 * @param {?string} content 851 * @this {WebInspector.NavigatorSourceTreeElement} 852 */ 853 function callback(content) 854 { 855 this._warmedUpContent = content; 856 } 857 }, 858 859 _shouldRenameOnMouseDown: function() 860 { 861 if (!this._uiSourceCode.canRename()) 862 return false; 863 var isSelected = this === this.treeOutline.selectedTreeElement; 864 var isFocused = this.treeOutline.childrenListElement.isSelfOrAncestor(document.activeElement); 865 return isSelected && isFocused && !WebInspector.isBeingEdited(this.treeOutline.element); 866 }, 867 868 selectOnMouseDown: function(event) 869 { 870 if (event.which !== 1 || !this._shouldRenameOnMouseDown()) { 871 TreeElement.prototype.selectOnMouseDown.call(this, event); 872 return; 873 } 874 setTimeout(rename.bind(this), 300); 875 876 /** 877 * @this {WebInspector.NavigatorSourceTreeElement} 878 */ 879 function rename() 880 { 881 if (this._shouldRenameOnMouseDown()) 882 this._navigatorView.rename(this.uiSourceCode, false); 883 } 884 }, 885 886 _ondragstart: function(event) 887 { 888 event.dataTransfer.setData("text/plain", this._warmedUpContent); 889 event.dataTransfer.effectAllowed = "copy"; 890 return true; 891 }, 892 893 /** 894 * @return {boolean} 895 */ 896 onspace: function() 897 { 898 this._navigatorView._sourceSelected(this.uiSourceCode, true); 899 return true; 900 }, 901 902 /** 903 * @param {!Event} event 904 */ 905 _onclick: function(event) 906 { 907 this._navigatorView._sourceSelected(this.uiSourceCode, false); 908 }, 909 910 /** 911 * @override 912 * @return {boolean} 913 */ 914 ondblclick: function(event) 915 { 916 var middleClick = event.button === 1; 917 this._navigatorView._sourceSelected(this.uiSourceCode, !middleClick); 918 return false; 919 }, 920 921 /** 922 * @override 923 * @return {boolean} 924 */ 925 onenter: function() 926 { 927 this._navigatorView._sourceSelected(this.uiSourceCode, true); 928 return true; 929 }, 930 931 /** 932 * @override 933 * @return {boolean} 934 */ 935 ondelete: function() 936 { 937 this._navigatorView.sourceDeleted(this.uiSourceCode); 938 return true; 939 }, 940 941 /** 942 * @param {!Event} event 943 */ 944 _handleContextMenuEvent: function(event) 945 { 946 this.select(); 947 this._navigatorView.handleFileContextMenu(event, this._uiSourceCode); 948 }, 949 950 __proto__: WebInspector.BaseNavigatorTreeElement.prototype 951 } 952 953 /** 954 * @constructor 955 * @param {string} id 956 */ 957 WebInspector.NavigatorTreeNode = function(id) 958 { 959 this.id = id; 960 /** @type {!StringMap.<!WebInspector.NavigatorTreeNode>} */ 961 this._children = new StringMap(); 962 } 963 964 WebInspector.NavigatorTreeNode.prototype = { 965 /** 966 * @return {!TreeElement} 967 */ 968 treeElement: function() { throw "Not implemented"; }, 969 970 dispose: function() { }, 971 972 /** 973 * @return {boolean} 974 */ 975 isRoot: function() 976 { 977 return false; 978 }, 979 980 /** 981 * @return {boolean} 982 */ 983 hasChildren: function() 984 { 985 return true; 986 }, 987 988 populate: function() 989 { 990 if (this.isPopulated()) 991 return; 992 if (this.parent) 993 this.parent.populate(); 994 this._populated = true; 995 this.wasPopulated(); 996 }, 997 998 wasPopulated: function() 999 { 1000 var children = this.children(); 1001 for (var i = 0; i < children.length; ++i) 1002 this.treeElement().appendChild(children[i].treeElement()); 1003 }, 1004 1005 /** 1006 * @param {!WebInspector.NavigatorTreeNode} node 1007 */ 1008 didAddChild: function(node) 1009 { 1010 if (this.isPopulated()) 1011 this.treeElement().appendChild(node.treeElement()); 1012 }, 1013 1014 /** 1015 * @param {!WebInspector.NavigatorTreeNode} node 1016 */ 1017 willRemoveChild: function(node) 1018 { 1019 if (this.isPopulated()) 1020 this.treeElement().removeChild(node.treeElement()); 1021 }, 1022 1023 /** 1024 * @return {boolean} 1025 */ 1026 isPopulated: function() 1027 { 1028 return this._populated; 1029 }, 1030 1031 /** 1032 * @return {boolean} 1033 */ 1034 isEmpty: function() 1035 { 1036 return !this._children.size(); 1037 }, 1038 1039 /** 1040 * @param {string} id 1041 * @return {?WebInspector.NavigatorTreeNode} 1042 */ 1043 child: function(id) 1044 { 1045 return this._children.get(id) || null; 1046 }, 1047 1048 /** 1049 * @return {!Array.<!WebInspector.NavigatorTreeNode>} 1050 */ 1051 children: function() 1052 { 1053 return this._children.values(); 1054 }, 1055 1056 /** 1057 * @param {!WebInspector.NavigatorTreeNode} node 1058 */ 1059 appendChild: function(node) 1060 { 1061 this._children.put(node.id, node); 1062 node.parent = this; 1063 this.didAddChild(node); 1064 }, 1065 1066 /** 1067 * @param {!WebInspector.NavigatorTreeNode} node 1068 */ 1069 removeChild: function(node) 1070 { 1071 this.willRemoveChild(node); 1072 this._children.remove(node.id); 1073 delete node.parent; 1074 node.dispose(); 1075 }, 1076 1077 reset: function() 1078 { 1079 this._children.clear(); 1080 } 1081 } 1082 1083 /** 1084 * @constructor 1085 * @extends {WebInspector.NavigatorTreeNode} 1086 * @param {!WebInspector.NavigatorView} navigatorView 1087 */ 1088 WebInspector.NavigatorRootTreeNode = function(navigatorView) 1089 { 1090 WebInspector.NavigatorTreeNode.call(this, ""); 1091 this._navigatorView = navigatorView; 1092 } 1093 1094 WebInspector.NavigatorRootTreeNode.prototype = { 1095 /** 1096 * @return {boolean} 1097 */ 1098 isRoot: function() 1099 { 1100 return true; 1101 }, 1102 1103 /** 1104 * @return {!TreeOutline} 1105 */ 1106 treeElement: function() 1107 { 1108 return this._navigatorView._scriptsTree; 1109 }, 1110 1111 __proto__: WebInspector.NavigatorTreeNode.prototype 1112 } 1113 1114 /** 1115 * @constructor 1116 * @extends {WebInspector.NavigatorTreeNode} 1117 * @param {!WebInspector.NavigatorView} navigatorView 1118 * @param {!WebInspector.UISourceCode} uiSourceCode 1119 */ 1120 WebInspector.NavigatorUISourceCodeTreeNode = function(navigatorView, uiSourceCode) 1121 { 1122 WebInspector.NavigatorTreeNode.call(this, uiSourceCode.name()); 1123 this._navigatorView = navigatorView; 1124 this._uiSourceCode = uiSourceCode; 1125 this._treeElement = null; 1126 } 1127 1128 WebInspector.NavigatorUISourceCodeTreeNode.prototype = { 1129 /** 1130 * @return {!WebInspector.UISourceCode} 1131 */ 1132 uiSourceCode: function() 1133 { 1134 return this._uiSourceCode; 1135 }, 1136 1137 updateIcon: function() 1138 { 1139 if (this._treeElement) 1140 this._treeElement.updateIcon(); 1141 }, 1142 1143 /** 1144 * @return {!TreeElement} 1145 */ 1146 treeElement: function() 1147 { 1148 if (this._treeElement) 1149 return this._treeElement; 1150 1151 this._treeElement = new WebInspector.NavigatorSourceTreeElement(this._navigatorView, this._uiSourceCode, ""); 1152 this.updateTitle(); 1153 1154 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this); 1155 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 1156 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 1157 1158 return this._treeElement; 1159 }, 1160 1161 /** 1162 * @param {boolean=} ignoreIsDirty 1163 */ 1164 updateTitle: function(ignoreIsDirty) 1165 { 1166 if (!this._treeElement) 1167 return; 1168 1169 var titleText = this._uiSourceCode.displayName(); 1170 if (!ignoreIsDirty && (this._uiSourceCode.isDirty() || this._uiSourceCode.hasUnsavedCommittedChanges())) 1171 titleText = "*" + titleText; 1172 this._treeElement.titleText = titleText; 1173 }, 1174 1175 /** 1176 * @return {boolean} 1177 */ 1178 hasChildren: function() 1179 { 1180 return false; 1181 }, 1182 1183 dispose: function() 1184 { 1185 if (!this._treeElement) 1186 return; 1187 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this); 1188 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 1189 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 1190 }, 1191 1192 _titleChanged: function(event) 1193 { 1194 this.updateTitle(); 1195 }, 1196 1197 _workingCopyChanged: function(event) 1198 { 1199 this.updateTitle(); 1200 }, 1201 1202 _workingCopyCommitted: function(event) 1203 { 1204 this.updateTitle(); 1205 }, 1206 1207 /** 1208 * @param {boolean=} select 1209 */ 1210 reveal: function(select) 1211 { 1212 this.parent.populate(); 1213 this.parent.treeElement().expand(); 1214 this._treeElement.reveal(); 1215 if (select) 1216 this._treeElement.select(true); 1217 }, 1218 1219 /** 1220 * @param {function(boolean)=} callback 1221 */ 1222 rename: function(callback) 1223 { 1224 if (!this._treeElement) 1225 return; 1226 1227 // Tree outline should be marked as edited as well as the tree element to prevent search from starting. 1228 var treeOutlineElement = this._treeElement.treeOutline.element; 1229 WebInspector.markBeingEdited(treeOutlineElement, true); 1230 1231 /** 1232 * @param {!Element} element 1233 * @param {string} newTitle 1234 * @param {string} oldTitle 1235 * @this {WebInspector.NavigatorUISourceCodeTreeNode} 1236 */ 1237 function commitHandler(element, newTitle, oldTitle) 1238 { 1239 if (newTitle !== oldTitle) { 1240 this._treeElement.titleText = newTitle; 1241 this._uiSourceCode.rename(newTitle, renameCallback.bind(this)); 1242 return; 1243 } 1244 afterEditing.call(this, true); 1245 } 1246 1247 /** 1248 * @param {boolean} success 1249 * @this {WebInspector.NavigatorUISourceCodeTreeNode} 1250 */ 1251 function renameCallback(success) 1252 { 1253 if (!success) { 1254 WebInspector.markBeingEdited(treeOutlineElement, false); 1255 this.updateTitle(); 1256 this.rename(callback); 1257 return; 1258 } 1259 afterEditing.call(this, true); 1260 } 1261 1262 /** 1263 * @this {WebInspector.NavigatorUISourceCodeTreeNode} 1264 */ 1265 function cancelHandler() 1266 { 1267 afterEditing.call(this, false); 1268 } 1269 1270 /** 1271 * @param {boolean} committed 1272 * @this {WebInspector.NavigatorUISourceCodeTreeNode} 1273 */ 1274 function afterEditing(committed) 1275 { 1276 WebInspector.markBeingEdited(treeOutlineElement, false); 1277 this.updateTitle(); 1278 this._treeElement.treeOutline.childrenListElement.focus(); 1279 if (callback) 1280 callback(committed); 1281 } 1282 1283 var editingConfig = new WebInspector.InplaceEditor.Config(commitHandler.bind(this), cancelHandler.bind(this)); 1284 this.updateTitle(true); 1285 WebInspector.InplaceEditor.startEditing(this._treeElement.titleElement, editingConfig); 1286 window.getSelection().setBaseAndExtent(this._treeElement.titleElement, 0, this._treeElement.titleElement, 1); 1287 }, 1288 1289 __proto__: WebInspector.NavigatorTreeNode.prototype 1290 } 1291 1292 /** 1293 * @constructor 1294 * @extends {WebInspector.NavigatorTreeNode} 1295 * @param {!WebInspector.NavigatorView} navigatorView 1296 * @param {?WebInspector.Project} project 1297 * @param {string} id 1298 * @param {string} type 1299 * @param {string} folderPath 1300 * @param {string} title 1301 */ 1302 WebInspector.NavigatorFolderTreeNode = function(navigatorView, project, id, type, folderPath, title) 1303 { 1304 WebInspector.NavigatorTreeNode.call(this, id); 1305 this._navigatorView = navigatorView; 1306 this._project = project; 1307 this._type = type; 1308 this._folderPath = folderPath; 1309 this._title = title; 1310 } 1311 1312 WebInspector.NavigatorFolderTreeNode.prototype = { 1313 /** 1314 * @return {!TreeElement} 1315 */ 1316 treeElement: function() 1317 { 1318 if (this._treeElement) 1319 return this._treeElement; 1320 this._treeElement = this._createTreeElement(this._title, this); 1321 return this._treeElement; 1322 }, 1323 1324 /** 1325 * @return {!TreeElement} 1326 */ 1327 _createTreeElement: function(title, node) 1328 { 1329 var treeElement = new WebInspector.NavigatorFolderTreeElement(this._navigatorView, this._type, title); 1330 treeElement.setNode(node); 1331 return treeElement; 1332 }, 1333 1334 wasPopulated: function() 1335 { 1336 if (!this._treeElement || this._treeElement._node !== this) 1337 return; 1338 this._addChildrenRecursive(); 1339 }, 1340 1341 _addChildrenRecursive: function() 1342 { 1343 var children = this.children(); 1344 for (var i = 0; i < children.length; ++i) { 1345 var child = children[i]; 1346 this.didAddChild(child); 1347 if (child instanceof WebInspector.NavigatorFolderTreeNode) 1348 child._addChildrenRecursive(); 1349 } 1350 }, 1351 1352 _shouldMerge: function(node) 1353 { 1354 return this._type !== WebInspector.NavigatorTreeOutline.Types.Domain && node instanceof WebInspector.NavigatorFolderTreeNode; 1355 }, 1356 1357 didAddChild: function(node) 1358 { 1359 function titleForNode(node) 1360 { 1361 return node._title; 1362 } 1363 1364 if (!this._treeElement) 1365 return; 1366 1367 var children = this.children(); 1368 1369 if (children.length === 1 && this._shouldMerge(node)) { 1370 node._isMerged = true; 1371 this._treeElement.titleText = this._treeElement.titleText + "/" + node._title; 1372 node._treeElement = this._treeElement; 1373 this._treeElement.setNode(node); 1374 return; 1375 } 1376 1377 var oldNode; 1378 if (children.length === 2) 1379 oldNode = children[0] !== node ? children[0] : children[1]; 1380 if (oldNode && oldNode._isMerged) { 1381 delete oldNode._isMerged; 1382 var mergedToNodes = []; 1383 mergedToNodes.push(this); 1384 var treeNode = this; 1385 while (treeNode._isMerged) { 1386 treeNode = treeNode.parent; 1387 mergedToNodes.push(treeNode); 1388 } 1389 mergedToNodes.reverse(); 1390 var titleText = mergedToNodes.map(titleForNode).join("/"); 1391 1392 var nodes = []; 1393 treeNode = oldNode; 1394 do { 1395 nodes.push(treeNode); 1396 children = treeNode.children(); 1397 treeNode = children.length === 1 ? children[0] : null; 1398 } while (treeNode && treeNode._isMerged); 1399 1400 if (!this.isPopulated()) { 1401 this._treeElement.titleText = titleText; 1402 this._treeElement.setNode(this); 1403 for (var i = 0; i < nodes.length; ++i) { 1404 delete nodes[i]._treeElement; 1405 delete nodes[i]._isMerged; 1406 } 1407 return; 1408 } 1409 var oldTreeElement = this._treeElement; 1410 var treeElement = this._createTreeElement(titleText, this); 1411 for (var i = 0; i < mergedToNodes.length; ++i) 1412 mergedToNodes[i]._treeElement = treeElement; 1413 oldTreeElement.parent.appendChild(treeElement); 1414 1415 oldTreeElement.setNode(nodes[nodes.length - 1]); 1416 oldTreeElement.titleText = nodes.map(titleForNode).join("/"); 1417 oldTreeElement.parent.removeChild(oldTreeElement); 1418 this._treeElement.appendChild(oldTreeElement); 1419 if (oldTreeElement.expanded) 1420 treeElement.expand(); 1421 } 1422 if (this.isPopulated()) 1423 this._treeElement.appendChild(node.treeElement()); 1424 }, 1425 1426 willRemoveChild: function(node) 1427 { 1428 if (node._isMerged || !this.isPopulated()) 1429 return; 1430 this._treeElement.removeChild(node._treeElement); 1431 }, 1432 1433 __proto__: WebInspector.NavigatorTreeNode.prototype 1434 } 1435