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