The change converts the following files to typescript: * elements/diff/gr-diff-host/gr-diff-host.ts Change-Id: Ie565ab753fea6909790729b8e42410d39c437612
468 lines
13 KiB
TypeScript
468 lines
13 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright (C) 2015 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.
|
|
*/
|
|
import '../../../styles/shared-styles';
|
|
import '../../shared/gr-dropdown-list/gr-dropdown-list';
|
|
import '../../shared/gr-select/gr-select';
|
|
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
|
|
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
|
|
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
|
|
import {PolymerElement} from '@polymer/polymer/polymer-element';
|
|
import {htmlTemplate} from './gr-patch-range-select_html';
|
|
import {GrCountStringFormatter} from '../../shared/gr-count-string-formatter/gr-count-string-formatter';
|
|
import {appContext} from '../../../services/app-context';
|
|
import {
|
|
computeLatestPatchNum,
|
|
findSortedIndex,
|
|
getParentIndex,
|
|
getRevisionByPatchNum,
|
|
isMergeParent,
|
|
patchNumEquals,
|
|
sortRevisions,
|
|
PatchSet,
|
|
} from '../../../utils/patch-set-util';
|
|
import {customElement, property, observe} from '@polymer/decorators';
|
|
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
|
|
import {hasOwnProperty} from '../../../utils/common-util';
|
|
import {
|
|
ParentPatchSetNum,
|
|
PatchSetNum,
|
|
RevisionInfo,
|
|
Timestamp,
|
|
} from '../../../types/common';
|
|
import {RevisionInfo as RevisionInfoClass} from '../../shared/revision-info/revision-info';
|
|
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
|
|
import {ChangeComments} from '../gr-comment-api/gr-comment-api';
|
|
import {
|
|
DropdownItem,
|
|
DropDownValueChangeEvent,
|
|
GrDropdownList,
|
|
} from '../../shared/gr-dropdown-list/gr-dropdown-list';
|
|
import {GeneratedWebLink} from '../../core/gr-navigation/gr-navigation';
|
|
|
|
// Maximum length for patch set descriptions.
|
|
const PATCH_DESC_MAX_LENGTH = 500;
|
|
|
|
export interface PatchRangeChangeDetail {
|
|
patchNum?: PatchSetNum;
|
|
basePatchNum?: PatchSetNum;
|
|
}
|
|
|
|
export type PatchRangeChangeEvent = CustomEvent<PatchRangeChangeDetail>;
|
|
|
|
export interface FilesWebLinks {
|
|
meta_a: GeneratedWebLink[];
|
|
meta_b: GeneratedWebLink[];
|
|
}
|
|
|
|
export interface GrPatchRangeSelect {
|
|
$: {
|
|
patchNumDropdown: GrDropdownList;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Fired when the patch range changes
|
|
*
|
|
* @event patch-range-change
|
|
*
|
|
* @property {string} patchNum
|
|
* @property {string} basePatchNum
|
|
* @extends PolymerElement
|
|
*/
|
|
@customElement('gr-patch-range-select')
|
|
export class GrPatchRangeSelect extends GestureEventListeners(
|
|
LegacyElementMixin(PolymerElement)
|
|
) {
|
|
static get template() {
|
|
return htmlTemplate;
|
|
}
|
|
|
|
@property({type: Array})
|
|
availablePatches?: PatchSet[];
|
|
|
|
@property({
|
|
type: Object,
|
|
computed:
|
|
'_computeBaseDropdownContent(availablePatches, patchNum,' +
|
|
'_sortedRevisions, changeComments, revisionInfo)',
|
|
})
|
|
_baseDropdownContent?: DropdownItem[];
|
|
|
|
@property({
|
|
type: Object,
|
|
computed:
|
|
'_computePatchDropdownContent(availablePatches,' +
|
|
'basePatchNum, _sortedRevisions, changeComments)',
|
|
})
|
|
_patchDropdownContent?: DropdownItem[];
|
|
|
|
@property({type: String})
|
|
changeNum?: string;
|
|
|
|
@property({type: Object})
|
|
changeComments?: ChangeComments;
|
|
|
|
@property({type: Object})
|
|
filesWeblinks?: FilesWebLinks;
|
|
|
|
@property({type: String})
|
|
patchNum?: PatchSetNum;
|
|
|
|
@property({type: String})
|
|
basePatchNum?: PatchSetNum;
|
|
|
|
@property({type: Object})
|
|
revisions?: RevisionInfo[];
|
|
|
|
@property({type: Object})
|
|
revisionInfo?: RevisionInfoClass;
|
|
|
|
@property({type: Array})
|
|
_sortedRevisions?: RevisionInfo[];
|
|
|
|
private readonly reporting: ReportingService = appContext.reportingService;
|
|
|
|
constructor() {
|
|
super();
|
|
this.reporting = appContext.reportingService;
|
|
}
|
|
|
|
_getShaForPatch(patch: PatchSet) {
|
|
return patch.sha.substring(0, 10);
|
|
}
|
|
|
|
_computeBaseDropdownContent(
|
|
availablePatches?: PatchSet[],
|
|
patchNum?: PatchSetNum,
|
|
_sortedRevisions?: RevisionInfo[],
|
|
changeComments?: ChangeComments,
|
|
revisionInfo?: RevisionInfoClass
|
|
): DropdownItem[] | undefined {
|
|
// Polymer 2: check for undefined
|
|
if (
|
|
availablePatches === undefined ||
|
|
patchNum === undefined ||
|
|
_sortedRevisions === undefined ||
|
|
changeComments === undefined ||
|
|
revisionInfo === undefined
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
const parentCounts = revisionInfo.getParentCountMap();
|
|
const currentParentCount = hasOwnProperty(parentCounts, patchNum)
|
|
? parentCounts[patchNum as number]
|
|
: 1;
|
|
const maxParents = revisionInfo.getMaxParents();
|
|
const isMerge = currentParentCount > 1;
|
|
|
|
const dropdownContent: DropdownItem[] = [];
|
|
for (const basePatch of availablePatches) {
|
|
const basePatchNum = basePatch.num;
|
|
const entry: DropdownItem = this._createDropdownEntry(
|
|
basePatchNum,
|
|
'Patchset ',
|
|
_sortedRevisions,
|
|
changeComments,
|
|
this._getShaForPatch(basePatch)
|
|
);
|
|
dropdownContent.push({
|
|
...entry,
|
|
disabled: this._computeLeftDisabled(
|
|
basePatch.num,
|
|
patchNum,
|
|
_sortedRevisions
|
|
),
|
|
});
|
|
}
|
|
|
|
dropdownContent.push({
|
|
text: isMerge ? 'Auto Merge' : 'Base',
|
|
value: 'PARENT',
|
|
});
|
|
|
|
for (let idx = 0; isMerge && idx < maxParents; idx++) {
|
|
dropdownContent.push({
|
|
disabled: idx >= currentParentCount,
|
|
triggerText: `Parent ${idx + 1}`,
|
|
text: `Parent ${idx + 1}`,
|
|
mobileText: `Parent ${idx + 1}`,
|
|
value: -(idx + 1),
|
|
});
|
|
}
|
|
|
|
return dropdownContent;
|
|
}
|
|
|
|
_computeMobileText(
|
|
patchNum: PatchSetNum,
|
|
changeComments: ChangeComments,
|
|
revisions: RevisionInfo[]
|
|
) {
|
|
return (
|
|
`${patchNum}` +
|
|
`${this._computePatchSetCommentsString(changeComments, patchNum)}` +
|
|
`${this._computePatchSetDescription(revisions, patchNum, true)}`
|
|
);
|
|
}
|
|
|
|
_computePatchDropdownContent(
|
|
availablePatches?: PatchSet[],
|
|
basePatchNum?: PatchSetNum,
|
|
_sortedRevisions?: RevisionInfo[],
|
|
changeComments?: ChangeComments
|
|
): DropdownItem[] | undefined {
|
|
// Polymer 2: check for undefined
|
|
if (
|
|
availablePatches === undefined ||
|
|
basePatchNum === undefined ||
|
|
_sortedRevisions === undefined ||
|
|
changeComments === undefined
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
const dropdownContent: DropdownItem[] = [];
|
|
for (const patch of availablePatches) {
|
|
const patchNum = patch.num;
|
|
const entry = this._createDropdownEntry(
|
|
patchNum,
|
|
patchNum === 'edit' ? '' : 'Patchset ',
|
|
_sortedRevisions,
|
|
changeComments,
|
|
this._getShaForPatch(patch)
|
|
);
|
|
dropdownContent.push({
|
|
...entry,
|
|
disabled: this._computeRightDisabled(
|
|
basePatchNum,
|
|
patchNum,
|
|
_sortedRevisions
|
|
),
|
|
});
|
|
}
|
|
return dropdownContent;
|
|
}
|
|
|
|
_computeText(
|
|
patchNum: PatchSetNum,
|
|
prefix: string,
|
|
changeComments: ChangeComments,
|
|
sha: string
|
|
) {
|
|
return (
|
|
`${prefix}${patchNum}` +
|
|
`${this._computePatchSetCommentsString(changeComments, patchNum)}` +
|
|
` | ${sha}`
|
|
);
|
|
}
|
|
|
|
_createDropdownEntry(
|
|
patchNum: PatchSetNum,
|
|
prefix: string,
|
|
sortedRevisions: RevisionInfo[],
|
|
changeComments: ChangeComments,
|
|
sha: string
|
|
) {
|
|
const entry: DropdownItem = {
|
|
triggerText: `${prefix}${patchNum}`,
|
|
text: this._computeText(patchNum, prefix, changeComments, sha),
|
|
mobileText: this._computeMobileText(
|
|
patchNum,
|
|
changeComments,
|
|
sortedRevisions
|
|
),
|
|
bottomText: `${this._computePatchSetDescription(
|
|
sortedRevisions,
|
|
patchNum
|
|
)}`,
|
|
value: patchNum,
|
|
};
|
|
const date = this._computePatchSetDate(sortedRevisions, patchNum);
|
|
if (date) {
|
|
entry.date = date;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
@observe('revisions.*')
|
|
_updateSortedRevisions(
|
|
revisionsRecord: PolymerDeepPropertyChange<RevisionInfo[], RevisionInfo[]>
|
|
) {
|
|
const revisions = revisionsRecord.base;
|
|
if (!revisions) return;
|
|
this._sortedRevisions = sortRevisions(Object.values(revisions));
|
|
}
|
|
|
|
/**
|
|
* The basePatchNum should always be <= patchNum -- because sortedRevisions
|
|
* is sorted in reverse order (higher patchset nums first), invalid base
|
|
* patch nums have an index greater than the index of patchNum.
|
|
*
|
|
* @param basePatchNum The possible base patch num.
|
|
* @param patchNum The current selected patch num.
|
|
*/
|
|
_computeLeftDisabled(
|
|
basePatchNum: PatchSetNum,
|
|
patchNum: PatchSetNum,
|
|
sortedRevisions: RevisionInfo[]
|
|
): boolean {
|
|
return (
|
|
findSortedIndex(basePatchNum, sortedRevisions) <=
|
|
findSortedIndex(patchNum, sortedRevisions)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The basePatchNum should always be <= patchNum -- because sortedRevisions
|
|
* is sorted in reverse order (higher patchset nums first), invalid patch
|
|
* nums have an index greater than the index of basePatchNum.
|
|
*
|
|
* In addition, if the current basePatchNum is 'PARENT', all patchNums are
|
|
* valid.
|
|
*
|
|
* If the current basePatchNum is a parent index, then only patches that have
|
|
* at least that many parents are valid.
|
|
*
|
|
* @param basePatchNum The current selected base patch num.
|
|
* @param patchNum The possible patch num.
|
|
*/
|
|
_computeRightDisabled(
|
|
basePatchNum: PatchSetNum,
|
|
patchNum: PatchSetNum,
|
|
sortedRevisions: RevisionInfo[]
|
|
): boolean {
|
|
if (patchNumEquals(basePatchNum, ParentPatchSetNum)) {
|
|
return false;
|
|
}
|
|
|
|
if (isMergeParent(basePatchNum)) {
|
|
if (!this.revisionInfo) {
|
|
return true;
|
|
}
|
|
// Note: parent indices use 1-offset.
|
|
return (
|
|
this.revisionInfo.getParentCount(patchNum) <
|
|
getParentIndex(basePatchNum)
|
|
);
|
|
}
|
|
|
|
return (
|
|
findSortedIndex(basePatchNum, sortedRevisions) <=
|
|
findSortedIndex(patchNum, sortedRevisions)
|
|
);
|
|
}
|
|
|
|
_computePatchSetCommentsString(
|
|
changeComments: ChangeComments,
|
|
patchNum: PatchSetNum
|
|
) {
|
|
if (!changeComments) {
|
|
return;
|
|
}
|
|
|
|
const commentCount = changeComments.computeCommentCount({patchNum});
|
|
const commentString = GrCountStringFormatter.computePluralString(
|
|
commentCount,
|
|
'comment'
|
|
);
|
|
|
|
const unresolvedCount = changeComments.computeUnresolvedNum({patchNum});
|
|
const unresolvedString = GrCountStringFormatter.computeString(
|
|
unresolvedCount,
|
|
'unresolved'
|
|
);
|
|
|
|
if (!commentString.length && !unresolvedString.length) {
|
|
return '';
|
|
}
|
|
|
|
return (
|
|
` (${commentString}` +
|
|
// Add a comma + space if both comments and unresolved
|
|
(commentString && unresolvedString ? ', ' : '') +
|
|
`${unresolvedString})`
|
|
);
|
|
}
|
|
|
|
_computePatchSetDescription(
|
|
revisions: RevisionInfo[],
|
|
patchNum: PatchSetNum,
|
|
addFrontSpace?: boolean
|
|
) {
|
|
const rev = getRevisionByPatchNum(revisions, patchNum);
|
|
return rev?.description
|
|
? (addFrontSpace ? ' ' : '') +
|
|
rev.description.substring(0, PATCH_DESC_MAX_LENGTH)
|
|
: '';
|
|
}
|
|
|
|
_computePatchSetDate(
|
|
revisions: RevisionInfo[],
|
|
patchNum: PatchSetNum
|
|
): Timestamp | undefined {
|
|
const rev = getRevisionByPatchNum(revisions, patchNum);
|
|
return rev ? rev.created : undefined;
|
|
}
|
|
|
|
/**
|
|
* Catches value-change events from the patchset dropdowns and determines
|
|
* whether or not a patch change event should be fired.
|
|
*/
|
|
_handlePatchChange(e: DropDownValueChangeEvent) {
|
|
const detail: PatchRangeChangeDetail = {
|
|
patchNum: this.patchNum,
|
|
basePatchNum: this.basePatchNum,
|
|
};
|
|
const target = (dom(e) as EventApi).localTarget;
|
|
const patchSetValue = e.detail.value as PatchSetNum;
|
|
const latestPatchNum = computeLatestPatchNum(this.availablePatches);
|
|
if (target === this.$.patchNumDropdown) {
|
|
if (detail.patchNum === e.detail.value) return;
|
|
this.reporting.reportInteraction('right-patchset-changed', {
|
|
previous: detail.patchNum,
|
|
current: e.detail.value,
|
|
latest: latestPatchNum,
|
|
commentCount: this.changeComments?.computeCommentCount({
|
|
patchNum: e.detail.value as PatchSetNum,
|
|
}),
|
|
});
|
|
detail.patchNum = patchSetValue;
|
|
} else {
|
|
if (patchNumEquals(detail.basePatchNum, patchSetValue)) return;
|
|
this.reporting.reportInteraction('left-patchset-changed', {
|
|
previous: detail.basePatchNum,
|
|
current: e.detail.value,
|
|
commentCount: this.changeComments?.computeCommentCount({
|
|
patchNum: patchSetValue,
|
|
}),
|
|
});
|
|
detail.basePatchNum = patchSetValue;
|
|
}
|
|
|
|
this.dispatchEvent(
|
|
new CustomEvent('patch-range-change', {detail, bubbles: false})
|
|
);
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'gr-patch-range-select': GrPatchRangeSelect;
|
|
}
|
|
}
|