| <!-- | |
| Copyright (C) 2016 The Android Open Source Project | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| --> | |
| <link rel="import" href="../../../bower_components/polymer/polymer.html"> | |
| <link rel="import" href="../gr-diff-comment-thread/gr-diff-comment-thread.html"> | |
| <link rel="import" href="../gr-diff-processor/gr-diff-processor.html"> | |
| <link rel="import" href="../gr-ranged-comment-layer/gr-ranged-comment-layer.html"> | |
| <link rel="import" href="../gr-syntax-layer/gr-syntax-layer.html"> | |
| <dom-module id="gr-diff-builder"> | |
| <template> | |
| <div class="contentWrapper"> | |
| <content></content> | |
| </div> | |
| <gr-ranged-comment-layer | |
| id="rangeLayer" | |
| comments="[[comments]]"></gr-ranged-comment-layer> | |
| <gr-syntax-layer | |
| id="syntaxLayer" | |
| diff="[[diff]]"></gr-syntax-layer> | |
| <gr-diff-processor | |
| id="processor" | |
| groups="{{_groups}}"></gr-diff-processor> | |
| </template> | |
| <script src="../gr-diff/gr-diff-line.js"></script> | |
| <script src="../gr-diff/gr-diff-group.js"></script> | |
| <script src="../gr-diff-highlight/gr-annotation.js"></script> | |
| <script src="gr-diff-builder.js"></script> | |
| <script src="gr-diff-builder-side-by-side.js"></script> | |
| <script src="gr-diff-builder-unified.js"></script> | |
| <script src="gr-diff-builder-image.js"></script> | |
| <script> | |
| (function() { | |
| 'use strict'; | |
| var DiffViewMode = { | |
| SIDE_BY_SIDE: 'SIDE_BY_SIDE', | |
| UNIFIED: 'UNIFIED_DIFF', | |
| }; | |
| var TimingLabel = { | |
| TOTAL: 'Diff Total Render', | |
| CONTENT: 'Diff Content Render', | |
| SYNTAX: 'Diff Syntax Render', | |
| }; | |
| Polymer({ | |
| is: 'gr-diff-builder', | |
| /** | |
| * Fired when the diff is rendered. | |
| * | |
| * @event render | |
| */ | |
| properties: { | |
| diff: Object, | |
| viewMode: String, | |
| comments: Object, | |
| isImageDiff: Boolean, | |
| baseImage: Object, | |
| revisionImage: Object, | |
| _builder: Object, | |
| _groups: Array, | |
| _layers: Array, | |
| }, | |
| get diffElement() { | |
| return this.queryEffectiveChildren('#diffTable'); | |
| }, | |
| observers: [ | |
| '_groupsChanged(_groups.splices)', | |
| ], | |
| attached: function() { | |
| // Setup annotation layers. | |
| this._layers = [ | |
| this.$.syntaxLayer, | |
| this._createIntralineLayer(), | |
| this.$.rangeLayer, | |
| ]; | |
| this.async(function() { | |
| this._preRenderThread(); | |
| }); | |
| }, | |
| render: function(comments, prefs) { | |
| this.$.syntaxLayer.enabled = prefs.syntax_highlighting; | |
| // Stop the processor (if it's running). | |
| this.$.processor.cancel(); | |
| this.$.syntaxLayer.cancel(); | |
| this._builder = this._getDiffBuilder(this.diff, comments, prefs); | |
| this.$.processor.context = prefs.context; | |
| this.$.processor.keyLocations = this._getCommentLocations(comments); | |
| this._clearDiffContent(); | |
| console.time(TimingLabel.TOTAL); | |
| console.time(TimingLabel.CONTENT); | |
| return this.$.processor.process(this.diff.content).then(function() { | |
| if (this.isImageDiff) { | |
| this._builder.renderDiffImages(); | |
| } | |
| console.timeEnd(TimingLabel.CONTENT); | |
| console.time(TimingLabel.SYNTAX); | |
| this.$.syntaxLayer.process().then(function() { | |
| console.timeEnd(TimingLabel.SYNTAX); | |
| console.timeEnd(TimingLabel.TOTAL); | |
| }); | |
| this.fire('render'); | |
| }.bind(this)); | |
| }, | |
| getLineElByChild: function(node) { | |
| while (node) { | |
| if (node instanceof Element) { | |
| if (node.classList.contains('lineNum')) { | |
| return node; | |
| } | |
| if (node.classList.contains('section')) { | |
| return null; | |
| } | |
| } | |
| node = node.previousSibling || node.parentElement; | |
| } | |
| return null; | |
| }, | |
| getLineNumberByChild: function(node) { | |
| var lineEl = this.getLineElByChild(node); | |
| return lineEl ? | |
| parseInt(lineEl.getAttribute('data-value'), 10) : null; | |
| }, | |
| renderLineRange: function(startLine, endLine, opt_side) { | |
| var groups = | |
| this._builder.getGroupsByLineRange(startLine, endLine, opt_side); | |
| groups.forEach(function(group) { | |
| var newElement = this._builder.buildSectionElement(group); | |
| var oldElement = group.element; | |
| // Transfer comment threads from existing section to new one. | |
| var threads = Polymer.dom(newElement).querySelectorAll( | |
| 'gr-diff-comment-thread'); | |
| threads.forEach(function(threadEl) { | |
| var lineEl = this.getLineElByChild(threadEl, oldElement); | |
| if (!lineEl) { // New comment thread. | |
| return; | |
| } | |
| var side = this.getSideByLineEl(lineEl); | |
| var line = lineEl.getAttribute('data-value'); | |
| var oldThreadEl = | |
| this.getCommentThreadByLine(line, side, oldElement); | |
| threadEl.parentNode.replaceChild(oldThreadEl, threadEl); | |
| }, this); | |
| // Replace old group elements with new ones. | |
| group.element.parentNode.replaceChild(newElement, group.element); | |
| group.element = newElement; | |
| }, this); | |
| this.async(function() { | |
| this.fire('render'); | |
| }, 1); | |
| }, | |
| getContentByLine: function(lineNumber, opt_side, opt_root) { | |
| return this._builder.getContentByLine(lineNumber, opt_side, opt_root); | |
| }, | |
| getContentByLineEl: function(lineEl) { | |
| var root = Polymer.dom(lineEl.parentElement); | |
| var side = this.getSideByLineEl(lineEl); | |
| var line = lineEl.getAttribute('data-value'); | |
| return this.getContentByLine(line, side, root); | |
| }, | |
| getLineElByNumber: function(lineNumber, opt_side) { | |
| var sideSelector = !!opt_side ? ('.' + opt_side) : ''; | |
| return this.diffElement.querySelector( | |
| '.lineNum[data-value="' + lineNumber + '"]' + sideSelector); | |
| }, | |
| getContentsByLineRange: function(startLine, endLine, opt_side) { | |
| var result = []; | |
| this._builder.findLinesByRange(startLine, endLine, opt_side, null, | |
| result); | |
| return result; | |
| }, | |
| getCommentThreadByLine: function(lineNumber, opt_side, opt_root) { | |
| var content = this.getContentByLine(lineNumber, opt_side, opt_root); | |
| return this.getCommentThreadByContentEl(content); | |
| }, | |
| getCommentThreadByContentEl: function(contentEl) { | |
| if (contentEl.classList.contains('contentText')) { | |
| contentEl = contentEl.parentElement; | |
| } | |
| return contentEl.querySelector('gr-diff-comment-thread'); | |
| }, | |
| getSideByLineEl: function(lineEl) { | |
| return lineEl.classList.contains(GrDiffBuilder.Side.RIGHT) ? | |
| GrDiffBuilder.Side.RIGHT : GrDiffBuilder.Side.LEFT; | |
| }, | |
| createCommentThread: function(changeNum, patchNum, path, side, | |
| projectConfig) { | |
| return this._builder.createCommentThread(changeNum, patchNum, path, | |
| side, projectConfig); | |
| }, | |
| emitGroup: function(group, sectionEl) { | |
| this._builder.emitGroup(group, sectionEl); | |
| }, | |
| showContext: function(newGroups, sectionEl) { | |
| var groups = this._builder.groups; | |
| // TODO(viktard): Polyfill findIndex for IE10. | |
| var contextIndex = groups.findIndex(function(group) { | |
| return group.element == sectionEl; | |
| }); | |
| groups.splice.apply(groups, [contextIndex, 1].concat(newGroups)); | |
| newGroups.forEach(function(newGroup) { | |
| this._builder.emitGroup(newGroup, sectionEl); | |
| }, this); | |
| sectionEl.parentNode.removeChild(sectionEl); | |
| this.async(function() { | |
| this.fire('render'); | |
| }, 1); | |
| }, | |
| _getDiffBuilder: function(diff, comments, prefs) { | |
| if (this.isImageDiff) { | |
| return new GrDiffBuilderImage(diff, comments, prefs, | |
| this.diffElement, this.baseImage, this.revisionImage); | |
| } else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) { | |
| return new GrDiffBuilderSideBySide( | |
| diff, comments, prefs, this.diffElement, this._layers); | |
| } else if (this.viewMode === DiffViewMode.UNIFIED) { | |
| return new GrDiffBuilderUnified( | |
| diff, comments, prefs, this.diffElement, this._layers); | |
| } | |
| throw Error('Unsupported diff view mode: ' + this.viewMode); | |
| }, | |
| _clearDiffContent: function() { | |
| this.diffElement.innerHTML = null; | |
| }, | |
| _getCommentLocations: function(comments) { | |
| var result = { | |
| left: {}, | |
| right: {}, | |
| }; | |
| for (var side in comments) { | |
| if (side !== GrDiffBuilder.Side.LEFT && | |
| side !== GrDiffBuilder.Side.RIGHT) { | |
| continue; | |
| } | |
| comments[side].forEach(function(c) { | |
| result[side][c.line || GrDiffLine.FILE] = true; | |
| }); | |
| } | |
| return result; | |
| }, | |
| _groupsChanged: function(changeRecord) { | |
| if (!changeRecord) { return; } | |
| changeRecord.indexSplices.forEach(function(splice) { | |
| var group; | |
| for (var i = 0; i < splice.addedCount; i++) { | |
| group = splice.object[splice.index + i]; | |
| this._builder.groups.push(group); | |
| this._builder.emitGroup(group); | |
| } | |
| }, this); | |
| }, | |
| _createIntralineLayer: function() { | |
| return { | |
| addListener: function() {}, | |
| // Take a DIV.contentText element and a line object with intraline | |
| // differences to highlight and apply them to the element as | |
| // annotations. | |
| annotate: function(el, line) { | |
| var HL_CLASS = 'style-scope gr-diff intraline'; | |
| line.highlights.forEach(function(highlight) { | |
| // The start and end indices could be the same if a highlight is | |
| // meant to start at the end of a line and continue onto the | |
| // next one. Ignore it. | |
| if (highlight.startIndex === highlight.endIndex) { return; } | |
| // If endIndex isn't present, continue to the end of the line. | |
| var endIndex = highlight.endIndex === undefined ? | |
| line.text.length : highlight.endIndex; | |
| GrAnnotation.annotateElement( | |
| el, | |
| highlight.startIndex, | |
| endIndex - highlight.startIndex, | |
| HL_CLASS); | |
| }); | |
| }, | |
| }; | |
| }, | |
| /** | |
| * In pages with large diffs, creating the first comment thread can be | |
| * slow because nested Polymer elements (particularly | |
| * iron-autogrow-textarea) add style elements to the document head, | |
| * which, in turn, triggers a reflow on the page. Create a hidden | |
| * thread, attach it to the page, and remove it so the stylesheet will | |
| * already exist and the user's comment will be quick to load. | |
| * @see https://gerrit-review.googlesource.com/c/82213/ | |
| */ | |
| _preRenderThread: function() { | |
| var thread = document.createElement('gr-diff-comment-thread'); | |
| thread.setAttribute('hidden', true); | |
| thread.addDraft(); | |
| var parent = Polymer.dom(this.root); | |
| parent.appendChild(thread); | |
| Polymer.dom.flush(); | |
| parent.removeChild(thread); | |
| }, | |
| }); | |
| })(); | |
| </script> | |
| </dom-module> |