summaryrefslogtreecommitdiffstats
path: root/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
diff options
context:
space:
mode:
Diffstat (limited to 'polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts')
-rw-r--r--polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts1750
1 files changed, 1230 insertions, 520 deletions
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index b8932e53e0..3e766c91c2 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -16,6 +16,8 @@
*/
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+import '../../plugins/gr-endpoint-param/gr-endpoint-param';
+import '../../plugins/gr-endpoint-slot/gr-endpoint-slot';
import '../../shared/gr-account-chip/gr-account-chip';
import '../../shared/gr-textarea/gr-textarea';
import '../../shared/gr-button/gr-button';
@@ -25,13 +27,11 @@ import '../../shared/gr-account-list/gr-account-list';
import '../gr-label-scores/gr-label-scores';
import '../gr-thread-list/gr-thread-list';
import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-reply-dialog_html';
import {
GrReviewerSuggestionsProvider,
SUGGESTIONS_PROVIDERS_USERS_TYPES,
} from '../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider';
-import {appContext} from '../../../services/app-context';
+import {getAppContext} from '../../../services/app-context';
import {
ChangeStatus,
DraftsAction,
@@ -46,14 +46,14 @@ import {
} from '../../../utils/account-util';
import {IronA11yAnnouncer} from '@polymer/iron-a11y-announcer/iron-a11y-announcer';
import {TargetElement} from '../../../api/plugin';
-import {customElement, observe, property} from '@polymer/decorators';
import {FixIronA11yAnnouncer} from '../../../types/types';
import {
AccountAddition,
AccountInfoInput,
+ AccountInput,
+ AccountInputDetail,
GrAccountList,
GroupInfoInput,
- GroupObjectInput,
RawAccountInput,
} from '../../shared/gr-account-list/gr-account-list';
import {
@@ -62,32 +62,24 @@ import {
AttentionSetInput,
ChangeInfo,
CommentInput,
- EmailAddress,
- GroupId,
GroupInfo,
isAccount,
isDetailedLabelInfo,
isReviewerAccountSuggestion,
isReviewerGroupSuggestion,
- LabelNameToValueMap,
ParsedJSON,
PatchSetNum,
- ProjectInfo,
ReviewerInput,
- Reviewers,
ReviewInput,
ReviewResult,
ServerInfo,
+ SuggestedReviewerGroupInfo,
Suggestion,
} from '../../../types/common';
import {GrButton} from '../../shared/gr-button/gr-button';
import {GrLabelScores} from '../gr-label-scores/gr-label-scores';
import {GrLabelScoreRow} from '../gr-label-score-row/gr-label-score-row';
import {
- PolymerDeepPropertyChange,
- PolymerSpliceChange,
-} from '@polymer/polymer/interfaces';
-import {
areSetsEqual,
assertIsDefined,
containsAll,
@@ -116,6 +108,16 @@ import {StorageLocation} from '../../../services/storage/gr-storage';
import {Interaction, Timing} from '../../../constants/reporting';
import {getReplyByReason} from '../../../utils/attention-set-util';
import {addShortcut, Key, Modifier} from '../../../utils/dom-util';
+import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
+import {resolve} from '../../../models/dependency';
+import {changeModelToken} from '../../../models/change/change-model';
+import {ConfigInfo, LabelNameToValuesMap} from '../../../api/rest-api';
+import {css, html, PropertyValues, LitElement} from 'lit';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {when} from 'lit/directives/when';
+import {classMap} from 'lit/directives/class-map';
+import {BindValueChangeEvent, ValueChangedEvent} from '../../../types/events';
+import {customElement, property, state, query} from 'lit/decorators';
const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
@@ -151,24 +153,8 @@ const ButtonTooltips = {
const EMPTY_REPLY_MESSAGE = 'Cannot send an empty reply.';
-export interface GrReplyDialog {
- $: {
- reviewers: GrAccountList;
- ccs: GrAccountList;
- cancelButton: GrButton;
- sendButton: GrButton;
- labelScores: GrLabelScores;
- textarea: GrTextarea;
- reviewerConfirmationOverlay: GrOverlay;
- };
-}
-
@customElement('gr-reply-dialog')
-export class GrReplyDialog extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrReplyDialog extends LitElement {
/**
* Fired when a reply is successfully sent.
*
@@ -215,9 +201,9 @@ export class GrReplyDialog extends PolymerElement {
FocusTarget = FocusTarget;
- private readonly reporting = appContext.reportingService;
+ private readonly reporting = getAppContext().reportingService;
- private readonly changeService = appContext.changeService;
+ private readonly getChangeModel = resolve(this, changeModelToken);
@property({type: Object})
change?: ChangeInfo;
@@ -228,151 +214,437 @@ export class GrReplyDialog extends PolymerElement {
@property({type: Boolean})
canBeStarted = false;
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, reflect: true})
disabled = false;
- @property({
- type: Boolean,
- computed: '_computeHasDrafts(draft, draftCommentThreads.*)',
- })
- hasDrafts = false;
-
- @property({type: String, observer: '_draftChanged'})
- draft = '';
-
- @property({type: String})
- quote = '';
+ @property({type: Array})
+ draftCommentThreads: CommentThread[] | undefined;
@property({type: Object})
- filterReviewerSuggestion: (input: Suggestion) => boolean;
+ permittedLabels?: LabelNameToValuesMap;
@property({type: Object})
- filterCCSuggestion: (input: Suggestion) => boolean;
+ projectConfig?: ConfigInfo;
@property({type: Object})
- permittedLabels?: LabelNameToValueMap;
+ serverConfig?: ServerInfo;
- @property({type: Object})
- projectConfig?: ProjectInfo;
+ @query('#reviewers') reviewersList?: GrAccountList;
- @property({type: Object})
- serverConfig?: ServerInfo;
+ @query('#ccs') ccsList?: GrAccountList;
- @property({type: String})
+ @query('#cancelButton') cancelButton?: GrButton;
+
+ @query('#sendButton') sendButton?: GrButton;
+
+ @query('#labelScores') labelScores?: GrLabelScores;
+
+ @query('#textarea') textarea?: GrTextarea;
+
+ @query('#reviewerConfirmationOverlay')
+ reviewerConfirmationOverlay?: GrOverlay;
+
+ @state()
+ draft = '';
+
+ @state()
+ filterReviewerSuggestion: (input: Suggestion) => boolean;
+
+ @state()
+ filterCCSuggestion: (input: Suggestion) => boolean;
+
+ @state()
knownLatestState?: LatestPatchState;
- @property({type: Boolean})
+ @state()
underReview = true;
- @property({type: Object})
- _account?: AccountInfo;
+ @state()
+ account?: AccountInfo;
- @property({type: Array})
- _ccs: (AccountInfo | GroupInfo)[] = [];
+ @state()
+ ccs: (AccountInfoInput | GroupInfoInput)[] = [];
- @property({type: Number})
- _attentionCcsCount = 0;
+ @state()
+ attentionCcsCount = 0;
- @property({type: Object, observer: '_reviewerPendingConfirmationUpdated'})
- _ccPendingConfirmation: GroupObjectInput | null = null;
+ @state()
+ ccPendingConfirmation: SuggestedReviewerGroupInfo | null = null;
- @property({
- type: String,
- computed: '_computeMessagePlaceholder(canBeStarted)',
- })
- _messagePlaceholder?: string;
+ @state()
+ messagePlaceholder?: string;
- @property({type: Object})
- _owner?: AccountInfo;
+ @state()
+ owner?: AccountInfo;
- @property({type: Object, computed: '_computeUploader(change)'})
- _uploader?: AccountInfo;
+ @state()
+ uploader?: AccountInfo;
- @property({type: Object})
- _pendingConfirmationDetails: GroupObjectInput | null = null;
+ @state()
+ pendingConfirmationDetails: SuggestedReviewerGroupInfo | null = null;
- @property({type: Boolean})
- _includeComments = true;
+ @state()
+ includeComments = true;
- @property({type: Array})
- _reviewers: (AccountInfo | GroupInfo)[] = [];
+ @state() reviewers: AccountInput[] = [];
- @property({type: Object, observer: '_reviewerPendingConfirmationUpdated'})
- _reviewerPendingConfirmation: GroupObjectInput | null = null;
+ @state()
+ reviewerPendingConfirmation: SuggestedReviewerGroupInfo | null = null;
- @property({type: Boolean, observer: '_handleHeightChanged'})
- _previewFormatting = false;
+ @state()
+ previewFormatting = false;
- @property({type: String, computed: '_computeSendButtonLabel(canBeStarted)'})
- _sendButtonLabel?: string;
+ @state()
+ sendButtonLabel?: string;
- @property({type: Boolean})
- _savingComments = false;
+ @state()
+ savingComments = false;
- @property({type: Boolean})
- _reviewersMutated = false;
+ @state()
+ reviewersMutated = false;
/**
* Signifies that the user has changed their vote on a label or (if they have
* not yet voted on a label) if a selected vote is different from the default
* vote.
*/
- @property({type: Boolean})
- _labelsChanged = false;
+ @state()
+ labelsChanged = false;
- @property({type: String})
- readonly _saveTooltip: string = ButtonTooltips.SAVE;
+ @state()
+ readonly saveTooltip: string = ButtonTooltips.SAVE;
- @property({type: String})
- _pluginMessage = '';
+ @state()
+ pluginMessage = '';
- @property({type: Boolean})
- _commentEditing = false;
+ @state()
+ commentEditing = false;
- @property({type: Boolean})
- _attentionExpanded = false;
+ @state()
+ attentionExpanded = false;
- @property({type: Object})
- _currentAttentionSet: Set<AccountId> = new Set();
+ @state()
+ currentAttentionSet: Set<AccountId> = new Set();
- @property({type: Object})
- _newAttentionSet: Set<AccountId> = new Set();
-
- @property({
- type: Boolean,
- computed:
- '_computeSendButtonDisabled(canBeStarted, ' +
- 'draftCommentThreads, draft, _reviewersMutated, _labelsChanged, ' +
- '_includeComments, disabled, _commentEditing, change, _account)',
- observer: '_sendDisabledChanged',
- })
- _sendDisabled?: boolean;
-
- @property({type: Array, observer: '_handleHeightChanged'})
- draftCommentThreads: CommentThread[] | undefined;
+ @state()
+ newAttentionSet: Set<AccountId> = new Set();
- @property({type: Boolean})
- _isResolvedPatchsetLevelComment = true;
+ @state()
+ sendDisabled?: boolean;
+
+ @state()
+ isResolvedPatchsetLevelComment = true;
- @property({type: Array, computed: '_computeAllReviewers(_reviewers.*)'})
- _allReviewers: (AccountInfo | GroupInfo)[] = [];
+ @state()
+ allReviewers: (AccountInfo | GroupInfo)[] = [];
- private readonly restApiService = appContext.restApiService;
+ private readonly restApiService: RestApiService =
+ getAppContext().restApiService;
- private readonly storage = appContext.storageService;
+ private readonly storage = getAppContext().storageService;
- private readonly jsAPI = appContext.jsApiService;
+ private readonly jsAPI = getAppContext().jsApiService;
- private storeTask?: DelayedTask;
+ storeTask?: DelayedTask;
/** Called in disconnectedCallback. */
private cleanups: (() => void)[] = [];
+ static override styles = [
+ sharedStyles,
+ css`
+ :host {
+ background-color: var(--dialog-background-color);
+ display: block;
+ max-height: 90vh;
+ }
+ :host([disabled]) {
+ pointer-events: none;
+ }
+ :host([disabled]) .container {
+ opacity: 0.5;
+ }
+ section {
+ border-top: 1px solid var(--border-color);
+ flex-shrink: 0;
+ padding: var(--spacing-m) var(--spacing-xl);
+ width: 100%;
+ }
+ section.labelsContainer {
+ /* We want the :hover highlight to extend to the border of the dialog. */
+ padding: var(--spacing-m) 0;
+ }
+ .stickyBottom {
+ background-color: var(--dialog-background-color);
+ box-shadow: 0px 0px 8px 0px rgba(60, 64, 67, 0.15);
+ margin-top: var(--spacing-s);
+ bottom: 0;
+ position: sticky;
+ /* @see Issue 8602 */
+ z-index: 1;
+ }
+ .stickyBottom.newReplyDialog {
+ margin-top: unset;
+ }
+ .actions {
+ display: flex;
+ justify-content: space-between;
+ }
+ .actions .right gr-button {
+ margin-left: var(--spacing-l);
+ }
+ .peopleContainer,
+ .labelsContainer {
+ flex-shrink: 0;
+ }
+ .peopleContainer {
+ border-top: none;
+ display: table;
+ }
+ .peopleList {
+ display: flex;
+ }
+ .peopleListLabel {
+ color: var(--deemphasized-text-color);
+ margin-top: var(--spacing-xs);
+ min-width: 6em;
+ padding-right: var(--spacing-m);
+ }
+ gr-account-list {
+ display: flex;
+ flex-wrap: wrap;
+ flex: 1;
+ }
+ #reviewerConfirmationOverlay {
+ padding: var(--spacing-l);
+ text-align: center;
+ }
+ .reviewerConfirmationButtons {
+ margin-top: var(--spacing-l);
+ }
+ .groupName {
+ font-weight: var(--font-weight-bold);
+ }
+ .groupSize {
+ font-style: italic;
+ }
+ .textareaContainer {
+ min-height: 12em;
+ position: relative;
+ }
+ .newReplyDialog.textareaContainer {
+ min-height: unset;
+ }
+ textareaContainer,
+ #textarea,
+ gr-endpoint-decorator[name='reply-text'] {
+ display: flex;
+ width: 100%;
+ }
+ .newReplyDialog .textareaContainer,
+ #textarea,
+ gr-endpoint-decorator[name='reply-text'] {
+ display: block;
+ width: unset;
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-code);
+ line-height: calc(var(--font-size-code) + var(--spacing-s));
+ font-weight: var(--font-weight-normal);
+ }
+ .newReplyDialog#textarea {
+ padding: var(--spacing-m);
+ }
+ gr-endpoint-decorator[name='reply-text'] {
+ flex-direction: column;
+ }
+ #textarea {
+ flex: 1;
+ }
+ .previewContainer {
+ border-top: none;
+ }
+ .previewContainer gr-formatted-text {
+ background: var(--table-header-background-color);
+ padding: var(--spacing-l);
+ }
+ #checkingStatusLabel,
+ #notLatestLabel {
+ margin-left: var(--spacing-l);
+ }
+ #checkingStatusLabel {
+ color: var(--deemphasized-text-color);
+ font-style: italic;
+ }
+ #notLatestLabel,
+ #savingLabel {
+ color: var(--error-text-color);
+ }
+ #savingLabel {
+ display: none;
+ }
+ #savingLabel.saving {
+ display: inline;
+ }
+ #pluginMessage {
+ color: var(--deemphasized-text-color);
+ margin-left: var(--spacing-l);
+ margin-bottom: var(--spacing-m);
+ }
+ #pluginMessage:empty {
+ display: none;
+ }
+ .preview-formatting {
+ margin-left: var(--spacing-m);
+ }
+ .attention-icon {
+ width: 14px;
+ height: 14px;
+ vertical-align: top;
+ position: relative;
+ top: 3px;
+ --iron-icon-height: 24px;
+ --iron-icon-width: 24px;
+ }
+ .attention .edit-attention-button {
+ vertical-align: top;
+ --gr-button-padding: 0px 4px;
+ }
+ .attention .edit-attention-button iron-icon {
+ color: inherit;
+ }
+ .attention a,
+ .attention-detail a {
+ text-decoration: none;
+ }
+ .attentionSummary {
+ display: flex;
+ justify-content: space-between;
+ }
+ .attentionSummary {
+ /* The account label for selection is misbehaving currently: It consumes
+ 26px height instead of 20px, which is the default line-height and thus
+ the max that can be nicely fit into an inline layout flow. We
+ acknowledge that using a fixed 26px value here is a hack and not a
+ great solution. */
+ line-height: 26px;
+ }
+ .attentionSummary gr-account-label,
+ .attention-detail gr-account-label {
+ --account-max-length: 120px;
+ display: inline-block;
+ padding: var(--spacing-xs) var(--spacing-m);
+ user-select: none;
+ --label-border-radius: 8px;
+ }
+ .attentionSummary gr-account-label {
+ margin: 0 var(--spacing-xs);
+ line-height: var(--line-height-normal);
+ vertical-align: top;
+ }
+ .attention-detail .peopleListValues {
+ line-height: calc(var(--line-height-normal) + 10px);
+ }
+ .attention-detail gr-account-label {
+ line-height: var(--line-height-normal);
+ }
+ .attentionSummary gr-account-label:focus,
+ .attention-detail gr-account-label:focus {
+ outline: none;
+ }
+ .attentionSummary gr-account-label:hover,
+ .attention-detail gr-account-label:hover {
+ box-shadow: var(--elevation-level-1);
+ cursor: pointer;
+ }
+ .attention-detail .attentionDetailsTitle {
+ display: flex;
+ justify-content: space-between;
+ }
+ .attention-detail .selectUsers {
+ color: var(--deemphasized-text-color);
+ margin-bottom: var(--spacing-m);
+ }
+ .attentionTip {
+ padding: var(--spacing-m);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ margin-top: var(--spacing-m);
+ background-color: var(--assignee-highlight-color);
+ }
+ .attentionTip div iron-icon {
+ margin-right: var(--spacing-s);
+ }
+ .patchsetLevelContainer {
+ width: 80ch;
+ border-radius: var(--border-radius);
+ box-shadow: var(--elevation-level-2);
+ }
+ .patchsetLevelContainer.resolved {
+ background-color: var(--comment-background-color);
+ }
+ .patchsetLevelContainer.unresolved {
+ background-color: var(--unresolved-comment-background-color);
+ }
+ .labelContainer {
+ padding-left: var(--spacing-m);
+ padding-bottom: var(--spacing-m);
+ }
+ `,
+ ];
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('draft')) {
+ this.draftChanged(changedProperties.get('draft') as string);
+ }
+ if (changedProperties.has('ccPendingConfirmation')) {
+ this.pendingConfirmationUpdated(this.ccPendingConfirmation);
+ }
+ if (changedProperties.has('reviewerPendingConfirmation')) {
+ this.pendingConfirmationUpdated(this.reviewerPendingConfirmation);
+ }
+ if (changedProperties.has('change')) {
+ this.computeUploader();
+ this.changeUpdated();
+ }
+ if (changedProperties.has('canBeStarted')) {
+ this.computeMessagePlaceholder();
+ this.computeSendButtonLabel();
+ }
+ if (changedProperties.has('reviewFormatting')) {
+ this.handleHeightChanged();
+ }
+ if (changedProperties.has('draftCommentThreads')) {
+ this.handleHeightChanged();
+ }
+ if (changedProperties.has('reviewers')) {
+ this.computeAllReviewers();
+ }
+ if (changedProperties.has('sendDisabled')) {
+ this.sendDisabledChanged();
+ }
+ if (changedProperties.has('attentionExpanded')) {
+ this.onAttentionExpandedChange();
+ }
+ if (
+ changedProperties.has('account') ||
+ changedProperties.has('reviewers') ||
+ changedProperties.has('ccs') ||
+ changedProperties.has('change') ||
+ changedProperties.has('draftCommentThreads') ||
+ changedProperties.has('includeComments') ||
+ changedProperties.has('labelsChanged') ||
+ changedProperties.has('draft')
+ ) {
+ this.computeNewAttention();
+ }
+ }
+
constructor() {
super();
this.filterReviewerSuggestion =
- this._filterReviewerSuggestionGenerator(false);
- this.filterCCSuggestion = this._filterReviewerSuggestionGenerator(true);
+ this.filterReviewerSuggestionGenerator(false);
+ this.filterCCSuggestion = this.filterReviewerSuggestionGenerator(true);
+ this.jsAPI.addElement(TargetElement.REPLY_DIALOG, this);
}
override connectedCallback() {
@@ -380,23 +652,23 @@ export class GrReplyDialog extends PolymerElement {
(
IronA11yAnnouncer as unknown as FixIronA11yAnnouncer
).requestAvailability();
- this._getAccount().then(account => {
- if (account) this._account = account;
+ this.restApiService.getAccount().then(account => {
+ if (account) this.account = account;
});
this.cleanups.push(
addShortcut(this, {key: Key.ENTER, modifiers: [Modifier.CTRL_KEY]}, _ =>
- this._submit()
+ this.submit()
)
);
this.cleanups.push(
addShortcut(this, {key: Key.ENTER, modifiers: [Modifier.META_KEY]}, _ =>
- this._submit()
+ this.submit()
)
);
this.cleanups.push(addShortcut(this, {key: Key.ESC}, _ => this.cancel()));
this.addEventListener('comment-editing-changed', e => {
- this._commentEditing = (e as CustomEvent).detail;
+ this.commentEditing = (e as CustomEvent).detail;
});
// Plugins on reply-reviewers endpoint can take advantage of these
@@ -405,21 +677,17 @@ export class GrReplyDialog extends PolymerElement {
this.addEventListener('add-reviewer', e => {
// Only support account type, see more from:
// elements/shared/gr-account-list/gr-account-list.js#addAccountItem
- this.$.reviewers.addAccountItem({
+ this.reviewersList?.addAccountItem({
account: (e as CustomEvent).detail.reviewer,
+ count: 1,
});
});
this.addEventListener('remove-reviewer', e => {
- this.$.reviewers.removeAccount((e as CustomEvent).detail.reviewer);
+ this.reviewersList?.removeAccount((e as CustomEvent).detail.reviewer);
});
}
- override ready() {
- super.ready();
- this.jsAPI.addElement(TargetElement.REPLY_DIALOG, this);
- }
-
override disconnectedCallback() {
this.storeTask?.cancel();
for (const cleanup of this.cleanups) cleanup();
@@ -427,117 +695,602 @@ export class GrReplyDialog extends PolymerElement {
super.disconnectedCallback();
}
- open(focusTarget?: FocusTarget) {
+ override render() {
+ if (!this.change) return;
+ this.sendDisabled = this.computeSendButtonDisabled();
+ return html`
+ <div tabindex="-1">
+ <section class="peopleContainer">
+ <gr-endpoint-decorator name="reply-reviewers">
+ <gr-endpoint-param
+ name="change"
+ .value=${this.change}
+ ></gr-endpoint-param>
+ <gr-endpoint-param name="reviewers" .value=${this.allReviewers}>
+ </gr-endpoint-param>
+ ${this.renderReviewerList()}
+ <gr-endpoint-slot name="below"></gr-endpoint-slot>
+ </gr-endpoint-decorator>
+ ${this.renderCCList()} ${this.renderReviewConfirmation()}
+ </section>
+ <section class="labelsContainer">${this.renderLabels()}</section>
+ <section class="newReplyDialog textareaContainer">
+ ${this.renderReplyText()}
+ </section>
+ ${when(
+ this.previewFormatting,
+ () => html`
+ <section class="previewContainer">
+ <gr-formatted-text
+ .content=${this.draft}
+ .config=${this.projectConfig?.commentlinks}
+ ></gr-formatted-text>
+ </section>
+ `
+ )}
+ ${this.renderDraftsSection()}
+ <div class="stickyBottom newReplyDialog">
+ <gr-endpoint-decorator name="reply-bottom">
+ <gr-endpoint-param
+ name="change"
+ .value=${this.change}
+ ></gr-endpoint-param>
+ ${this.renderAttentionSummarySection()}
+ ${this.renderAttentionDetailsSection()}
+ <gr-endpoint-slot name="above-actions"></gr-endpoint-slot>
+ ${this.renderActionsSection()}
+ </gr-endpoint-decorator>
+ </div>
+ </div>
+ `;
+ }
+
+ private renderReviewerList() {
+ return html`
+ <div class="peopleList">
+ <div class="peopleListLabel">Reviewers</div>
+ <gr-account-list
+ id="reviewers"
+ .accounts=${this.getAccountListCopy(this.reviewers)}
+ @account-added=${this.accountAdded}
+ @accounts-changed=${this.handleReviewersChanged}
+ .removableValues=${this.change?.removable_reviewers}
+ .filter=${this.filterReviewerSuggestion}
+ .pendingConfirmation=${this.reviewerPendingConfirmation}
+ @pending-confirmation-changed=${this
+ .handleReviewersConfirmationChanged}
+ .placeholder=${'Add reviewer...'}
+ @account-text-changed=${this.handleAccountTextEntry}
+ .suggestionsProvider=${this.getReviewerSuggestionsProvider(
+ this.change
+ )}
+ >
+ </gr-account-list>
+ <gr-endpoint-slot name="right"></gr-endpoint-slot>
+ </div>
+ `;
+ }
+
+ private renderCCList() {
+ return html`
+ <div class="peopleList">
+ <div class="peopleListLabel">CC</div>
+ <gr-account-list
+ id="ccs"
+ .accounts=${this.getAccountListCopy(this.ccs)}
+ @account-added=${this.accountAdded}
+ @accounts-changed=${this.handleCcsChanged}
+ .removableValues=${this.change?.removable_reviewers}
+ .filter=${this.filterCCSuggestion}
+ .pendingConfirmation=${this.ccPendingConfirmation}
+ @pending-confirmation-changed=${this.handleCcsConfirmationChanged}
+ allow-any-input
+ .placeholder=${'Add CC...'}
+ @account-text-changed=${this.handleAccountTextEntry}
+ .suggestionsProvider=${this.getCcSuggestionsProvider(this.change)}
+ >
+ </gr-account-list>
+ </div>
+ `;
+ }
+
+ private renderReviewConfirmation() {
+ return html`
+ <gr-overlay
+ id="reviewerConfirmationOverlay"
+ @iron-overlay-canceled=${this.cancelPendingReviewer}
+ >
+ <div class="reviewerConfirmation">
+ Group
+ <span class="groupName">
+ ${this.pendingConfirmationDetails?.group.name}
+ </span>
+ has
+ <span class="groupSize">
+ ${this.pendingConfirmationDetails?.count}
+ </span>
+ members.
+ <br />
+ Are you sure you want to add them all?
+ </div>
+ <div class="reviewerConfirmationButtons">
+ <gr-button @click=${this.confirmPendingReviewer}>Yes</gr-button>
+ <gr-button @click=${this.cancelPendingReviewer}>No</gr-button>
+ </div>
+ </gr-overlay>
+ `;
+ }
+
+ private renderLabels() {
+ if (!this.change || !this.account || !this.permittedLabels) return;
+ return html`
+ <gr-endpoint-decorator name="reply-label-scores">
+ <gr-label-scores
+ id="labelScores"
+ .account=${this.account}
+ .change=${this.change}
+ @labels-changed=${this._handleLabelsChanged}
+ .permittedLabels=${this.permittedLabels}
+ ></gr-label-scores>
+ <gr-endpoint-param
+ name="change"
+ .value=${this.change}
+ ></gr-endpoint-param>
+ </gr-endpoint-decorator>
+ <div id="pluginMessage">${this.pluginMessage}</div>
+ `;
+ }
+
+ private renderReplyText() {
+ if (!this.change) return;
+ return html`
+ <div
+ class=${classMap({
+ patchsetLevelContainer: true,
+ [this.getUnresolvedPatchsetLevelClass(
+ this.isResolvedPatchsetLevelComment
+ )]: true,
+ })}
+ >
+ <gr-endpoint-decorator name="reply-text">
+ <gr-textarea
+ id="textarea"
+ class="message newReplyDialog"
+ .autocomplete=${'on'}
+ .placeholder=${this.messagePlaceholder}
+ monospace
+ ?disabled=${this.disabled}
+ .rows=${4}
+ .text=${this.draft}
+ @bind-value-changed=${(e: BindValueChangeEvent) => {
+ this.draft = e.detail.value;
+ this.handleHeightChanged();
+ }}
+ >
+ </gr-textarea>
+ <gr-endpoint-param name="change" .value=${this.change}>
+ </gr-endpoint-param>
+ </gr-endpoint-decorator>
+ <div class="labelContainer">
+ <label>
+ <input
+ id="resolvedPatchsetLevelCommentCheckbox"
+ type="checkbox"
+ ?checked=${this.isResolvedPatchsetLevelComment}
+ @change=${this.handleResolvedPatchsetLevelCommentCheckboxChanged}
+ />
+ Resolved
+ </label>
+ <label class="preview-formatting">
+ <input
+ type="checkbox"
+ ?checked=${this.previewFormatting}
+ @change=${this.handlePreviewFormattingChanged}
+ />
+ Preview formatting
+ </label>
+ </div>
+ </div>
+ `;
+ }
+
+ private renderDraftsSection() {
+ if (this.computeHideDraftList(this.draftCommentThreads)) return;
+ return html`
+ <section class="draftsContainer">
+ <div class="includeComments">
+ <input
+ type="checkbox"
+ id="includeComments"
+ @change=${this.handleIncludeCommentsChanged}
+ ?checked=${this.includeComments}
+ />
+ <label for="includeComments"
+ >Publish ${this.computeDraftsTitle(this.draftCommentThreads)}</label
+ >
+ </div>
+ ${when(
+ this.includeComments,
+ () => html`
+ <gr-thread-list
+ id="commentList"
+ .threads=${this.draftCommentThreads!}
+ hide-dropdown
+ >
+ </gr-thread-list>
+ `
+ )}
+ <span
+ id="savingLabel"
+ class=${this.computeSavingLabelClass(this.savingComments)}
+ >
+ Saving comments...
+ </span>
+ </section>
+ `;
+ }
+
+ private renderAttentionSummarySection() {
+ if (this.attentionExpanded) return;
+ return html`
+ <section class="attention">
+ <div class="attentionSummary">
+ <div>
+ ${when(
+ this.computeShowNoAttentionUpdate(),
+ () => html` <span>${this.computeDoNotUpdateMessage()}</span> `
+ )}
+ ${when(
+ !this.computeShowNoAttentionUpdate(),
+ () => html`
+ <span>Bring to attention of</span>
+ ${this.computeNewAttentionAccounts().map(
+ account => html`
+ <gr-account-label
+ .account=${account}
+ .forceAttention=${this.computeHasNewAttention(account)}
+ .selected=${this.computeHasNewAttention(account)}
+ .hideHovercard=${true}
+ .selectionChipStyle=${true}
+ @click=${this.handleAttentionClick}
+ ></gr-account-label>
+ `
+ )}
+ `
+ )}
+ <gr-tooltip-content
+ has-tooltip
+ title=${this.computeAttentionButtonTitle()}
+ >
+ <gr-button
+ class="edit-attention-button"
+ @click=${this.handleAttentionModify}
+ ?disabled=${this.sendDisabled}
+ link
+ position-below
+ data-label="Edit"
+ data-action-type="change"
+ data-action-key="edit"
+ role="button"
+ tabindex="0"
+ >
+ <iron-icon icon="gr-icons:edit"></iron-icon>
+ Modify
+ </gr-button>
+ </gr-tooltip-content>
+ </div>
+ <div>
+ <a
+ href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html"
+ target="_blank"
+ >
+ <iron-icon
+ icon="gr-icons:help-outline"
+ title="read documentation"
+ ></iron-icon>
+ </a>
+ </div>
+ </div>
+ </section>
+ `;
+ }
+
+ private renderAttentionDetailsSection() {
+ if (!this.attentionExpanded) return;
+ return html`
+ <section class="attention-detail">
+ <div class="attentionDetailsTitle">
+ <div>
+ <span>Modify attention to</span>
+ </div>
+ <div></div>
+ <div>
+ <a
+ href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html"
+ target="_blank"
+ >
+ <iron-icon
+ icon="gr-icons:help-outline"
+ title="read documentation"
+ ></iron-icon>
+ </a>
+ </div>
+ </div>
+ <div class="selectUsers">
+ <span
+ >Select chips to set who will be in the attention set after sending
+ this reply</span
+ >
+ </div>
+ <div class="peopleList">
+ <div class="peopleListLabel">Owner</div>
+ <div class="peopleListValues">
+ <gr-account-label
+ .account=${this.owner}
+ ?forceAttention=${this.computeHasNewAttention(this.owner)}
+ .selected=${this.computeHasNewAttention(this.owner)}
+ .hideHovercard=${true}
+ .selectionChipStyle=${true}
+ @click=${this.handleAttentionClick}
+ >
+ </gr-account-label>
+ </div>
+ </div>
+ ${when(
+ this.uploader,
+ () => html`
+ <div class="peopleList">
+ <div class="peopleListLabel">Uploader</div>
+ <div class="peopleListValues">
+ <gr-account-label
+ .account=${this.uploader}
+ ?forceAttention=${this.computeHasNewAttention(this.uploader)}
+ .selected=${this.computeHasNewAttention(this.uploader)}
+ .hideHovercard=${true}
+ .selectionChipStyle=${true}
+ @click=${this.handleAttentionClick}
+ >
+ </gr-account-label>
+ </div>
+ </div>
+ `
+ )}
+ <div class="peopleList">
+ <div class="peopleListLabel">Reviewers</div>
+ <div class="peopleListValues">
+ ${this.removeServiceUsers(this.reviewers).map(
+ account => html`
+ <gr-account-label
+ .account=${account}
+ ?forceAttention=${this.computeHasNewAttention(account)}
+ .selected=${this.computeHasNewAttention(account)}
+ .hideHovercard=${true}
+ .selectionChipStyle=${true}
+ @click=${this.handleAttentionClick}
+ >
+ </gr-account-label>
+ `
+ )}
+ </div>
+ </div>
+
+ ${when(
+ this.attentionCcsCount,
+ () => html`
+ <div class="peopleList">
+ <div class="peopleListLabel">CC</div>
+ <div class="peopleListValues">
+ ${this.removeServiceUsers(this.ccs).map(
+ account => html`
+ <gr-account-label
+ .account=${account}
+ ?forceAttention=${this.computeHasNewAttention(account)}
+ .selected=${this.computeHasNewAttention(account)}
+ .hideHovercard=${true}
+ .selectionChipStyle=${true}
+ @click=${this.handleAttentionClick}
+ >
+ </gr-account-label>
+ `
+ )}
+ </div>
+ </div>
+ `
+ )}
+ ${when(
+ this.computeShowAttentionTip(
+ this.account,
+ this.owner,
+ this.currentAttentionSet,
+ this.newAttentionSet
+ ),
+ () => html`
+ <div class="attentionTip">
+ <iron-icon
+ class="pointer"
+ icon="gr-icons:lightbulb-outline"
+ ></iron-icon>
+ Be mindful of requiring attention from too many users.
+ </div>
+ `
+ )}
+ </section>
+ `;
+ }
+
+ private renderActionsSection() {
+ return html`
+ <section class="actions">
+ <div class="left">
+ ${when(
+ this.knownLatestState === LatestPatchState.CHECKING,
+ () => html`
+ <span id="checkingStatusLabel">
+ Checking whether patch ${this.patchNum} is latest...
+ </span>
+ `
+ )}
+ ${when(
+ this.knownLatestState === LatestPatchState.NOT_LATEST,
+ () => html`
+ <span id="notLatestLabel">
+ ${this.computePatchSetWarning()}
+ <gr-button link @click=${this._reload}>Reload</gr-button>
+ </span>
+ `
+ )}
+ </div>
+ <div class="right">
+ <gr-button
+ link
+ id="cancelButton"
+ class="action cancel"
+ @click=${this.cancelTapHandler}
+ >Cancel</gr-button
+ >
+ ${when(
+ this.canBeStarted,
+ () => html`
+ <!-- Use 'Send' here as the change may only about reviewers / ccs
+ and when this button is visible, the next button will always
+ be 'Start review' -->
+ <gr-tooltip-content has-tooltip title=${this.saveTooltip}>
+ <gr-button
+ link
+ ?disabled=${this.knownLatestState ===
+ LatestPatchState.NOT_LATEST}
+ class="action save"
+ @click=${this.saveClickHandler}
+ >Send As WIP</gr-button
+ >
+ </gr-tooltip-content>
+ `
+ )}
+ <gr-tooltip-content
+ has-tooltip
+ title=${this.computeSendButtonTooltip(
+ this.canBeStarted,
+ this.commentEditing
+ )}
+ >
+ <gr-button
+ id="sendButton"
+ primary
+ ?disabled=${this.sendDisabled}
+ class="action send"
+ @click=${this.sendTapHandler}
+ >${this.sendButtonLabel}
+ </gr-button>
+ </gr-tooltip-content>
+ </div>
+ </section>
+ `;
+ }
+
+ /**
+ * Note that this method is not actually *opening* the dialog. Opening and
+ * showing the dialog is dealt with by the overlay. This method is used by the
+ * change view for initializing the dialog after opening the overlay. Maybe it
+ * should be called `onOpened()` or `initialize()`?
+ */
+ open(focusTarget?: FocusTarget, quote?: string) {
assertIsDefined(this.change, 'change');
this.knownLatestState = LatestPatchState.CHECKING;
- this.changeService.fetchChangeUpdates(this.change).then(result => {
- this.knownLatestState = result.isLatest
- ? LatestPatchState.LATEST
- : LatestPatchState.NOT_LATEST;
- });
+ this.getChangeModel()
+ .fetchChangeUpdates(this.change)
+ .then(result => {
+ this.knownLatestState = result.isLatest
+ ? LatestPatchState.LATEST
+ : LatestPatchState.NOT_LATEST;
+ });
- this._focusOn(focusTarget);
- if (this.quote && this.quote.length) {
- // If a reply quote has been provided, use it and clear the property.
- this.draft = this.quote;
- this.quote = '';
+ this.focusOn(focusTarget);
+ if (quote?.length) {
+ // If a reply quote has been provided, use it.
+ this.draft = quote;
} else {
// Otherwise, check for an unsaved draft in localstorage.
- this.draft = this._loadStoredDraft();
+ this.draft = this.loadStoredDraft();
}
if (this.restApiService.hasPendingDiffDrafts()) {
- this._savingComments = true;
+ this.savingComments = true;
this.restApiService.awaitPendingDiffDrafts().then(() => {
fireEvent(this, 'comment-refresh');
- this._savingComments = false;
+ this.savingComments = false;
});
}
}
- _computeHasDrafts(
- draft: string,
- draftCommentThreads: PolymerDeepPropertyChange<
- CommentThread[] | undefined,
- CommentThread[] | undefined
- >
- ) {
- if (draftCommentThreads.base === undefined) return false;
- return draft.length > 0 || draftCommentThreads.base.length > 0;
+ hasDrafts() {
+ if (this.draftCommentThreads === undefined) return false;
+ return this.draft.length > 0 || this.draftCommentThreads.length > 0;
}
override focus() {
- this._focusOn(FocusTarget.ANY);
+ this.focusOn(FocusTarget.ANY);
}
getFocusStops() {
- const end = this._sendDisabled ? this.$.cancelButton : this.$.sendButton;
+ const end = this.sendDisabled ? this.cancelButton : this.sendButton;
+ if (!this.reviewersList?.focusStart || !end) return undefined;
return {
- start: this.$.reviewers.focusStart,
+ start: this.reviewersList.focusStart,
end,
};
}
- setLabelValue(label: string, value: string) {
- const selectorEl = this.getLabelScores().shadowRoot?.querySelector(
- `gr-label-score-row[name="${label}"]`
- );
- if (!selectorEl) {
- return;
- }
- (selectorEl as GrLabelScoreRow).setSelectedValue(value);
+ private handleResolvedPatchsetLevelCommentCheckboxChanged(e: Event) {
+ if (!(e.target instanceof HTMLInputElement)) return;
+ this.isResolvedPatchsetLevelComment = e.target.checked;
}
- getLabelValue(label: string) {
- const selectorEl = this.getLabelScores().shadowRoot?.querySelector(
- `gr-label-score-row[name="${label}"]`
- );
- if (!selectorEl) {
- return null;
- }
-
- return (selectorEl as GrLabelScoreRow).selectedValue;
+ private handlePreviewFormattingChanged(e: Event) {
+ if (!(e.target instanceof HTMLInputElement)) return;
+ this.previewFormatting = e.target.checked;
}
- @observe('_ccs.splices')
- _ccsChanged(splices: PolymerSpliceChange<AccountInfo[] | GroupInfo[]>) {
- this._reviewerTypeChanged(splices, ReviewerType.CC);
+ private handleIncludeCommentsChanged(e: Event) {
+ if (!(e.target instanceof HTMLInputElement)) return;
+ this.includeComments = e.target.checked;
}
- @observe('_reviewers.splices')
- _reviewersChanged(splices: PolymerSpliceChange<AccountInfo[] | GroupInfo[]>) {
- this._reviewerTypeChanged(splices, ReviewerType.REVIEWER);
+ setLabelValue(label: string, value: string): void {
+ const selectorEl =
+ this.getLabelScores().shadowRoot?.querySelector<GrLabelScoreRow>(
+ `gr-label-score-row[name="${label}"]`
+ );
+ selectorEl?.setSelectedValue(value);
}
- _reviewerTypeChanged(
- splices: PolymerSpliceChange<AccountInfo[] | GroupInfo[]>,
- reviewerType: ReviewerType
- ) {
- if (splices && splices.indexSplices) {
- this._reviewersMutated = true;
- let key: AccountId | EmailAddress | GroupId | undefined;
- let index;
- let account;
+ getLabelValue(label: string) {
+ const selectorEl =
+ this.getLabelScores().shadowRoot?.querySelector<GrLabelScoreRow>(
+ `gr-label-score-row[name="${label}"]`
+ );
+ return selectorEl?.selectedValue;
+ }
+
+ accountAdded(e: CustomEvent<AccountInputDetail>) {
+ const account = e.detail.account;
+ const key = accountOrGroupKey(account);
+ const reviewerType =
+ (e.target as GrAccountList).getAttribute('id') === 'ccs'
+ ? ReviewerType.CC
+ : ReviewerType.REVIEWER;
+ const isReviewer = ReviewerType.REVIEWER === reviewerType;
+ const array = isReviewer ? this.ccs : this.reviewers;
+ const index = array.findIndex(
+ reviewer => accountOrGroupKey(reviewer) === key
+ );
+ if (index >= 0) {
// Remove any accounts that already exist as a CC for reviewer
// or vice versa.
- const isReviewer = ReviewerType.REVIEWER === reviewerType;
- for (const splice of splices.indexSplices) {
- for (let i = 0; i < splice.addedCount; i++) {
- account = splice.object[splice.index + i];
- key = accountOrGroupKey(account);
- const array = isReviewer ? this._ccs : this._reviewers;
- index = array.findIndex(
- account => accountOrGroupKey(account) === key
- );
- if (index >= 0) {
- this.splice(isReviewer ? '_ccs' : '_reviewers', index, 1);
- const moveFrom = isReviewer ? 'CC' : 'reviewer';
- const moveTo = isReviewer ? 'reviewer' : 'CC';
- const id = account.name || key;
- const message = `${id} moved from ${moveFrom} to ${moveTo}.`;
- fireAlert(this, message);
- }
- }
- }
+ array.splice(index, 1);
+ const moveFrom = isReviewer ? 'CC' : 'reviewer';
+ const moveTo = isReviewer ? 'reviewer' : 'CC';
+ const id = account.name || key;
+ const message = `${id} moved from ${moveFrom} to ${moveTo}.`;
+ fireAlert(this, message);
}
}
@@ -557,33 +1310,27 @@ export class GrReplyDialog extends PolymerElement {
reviewers.push(reviewer);
});
};
- addToReviewInput(this.$.reviewers.additions(), ReviewerState.REVIEWER);
- addToReviewInput(this.$.ccs.additions(), ReviewerState.CC);
+ addToReviewInput(this.reviewersList!.additions(), ReviewerState.REVIEWER);
+ addToReviewInput(this.ccsList!.additions(), ReviewerState.CC);
addToReviewInput(
- this.$.reviewers.removals().filter(
+ this.reviewersList!.removals().filter(
r =>
isReviewerOrCC(change, r) &&
// ignore removal from reviewer request if being added to CC
- !this.$.ccs
- .additions()
- .some(
- account =>
- mapReviewer(account).reviewer === mapReviewer(r).reviewer
- )
+ !this.ccsList!.additions().some(
+ account => mapReviewer(account).reviewer === mapReviewer(r).reviewer
+ )
),
ReviewerState.REMOVED
);
addToReviewInput(
- this.$.ccs.removals().filter(
+ this.ccsList!.removals().filter(
r =>
isReviewerOrCC(change, r) &&
// ignore removal from CC request if being added as reviewer
- !this.$.reviewers
- .additions()
- .some(
- account =>
- mapReviewer(account).reviewer === mapReviewer(r).reviewer
- )
+ !this.reviewersList!.additions().some(
+ account => mapReviewer(account).reviewer === mapReviewer(r).reviewer
+ )
),
ReviewerState.REMOVED
);
@@ -605,23 +1352,23 @@ export class GrReplyDialog extends PolymerElement {
reviewInput.ready = true;
}
- const reason = getReplyByReason(this._account, this.serverConfig);
+ const reason = getReplyByReason(this.account, this.serverConfig);
reviewInput.ignore_automatic_attention_set_rules = true;
reviewInput.add_to_attention_set = [];
- for (const user of this._newAttentionSet) {
- if (!this._currentAttentionSet.has(user)) {
+ for (const user of this.newAttentionSet) {
+ if (!this.currentAttentionSet.has(user)) {
reviewInput.add_to_attention_set.push({user, reason});
}
}
reviewInput.remove_from_attention_set = [];
- for (const user of this._currentAttentionSet) {
- if (!this._newAttentionSet.has(user)) {
+ for (const user of this.currentAttentionSet) {
+ if (!this.newAttentionSet.has(user)) {
reviewInput.remove_from_attention_set.push({user, reason});
}
}
this.reportAttentionSetChanges(
- this._attentionExpanded,
+ this.attentionExpanded,
reviewInput.add_to_attention_set,
reviewInput.remove_from_attention_set
);
@@ -629,7 +1376,7 @@ export class GrReplyDialog extends PolymerElement {
if (this.draft) {
const comment: CommentInput = {
message: this.draft,
- unresolved: !this._isResolvedPatchsetLevelComment,
+ unresolved: !this.isResolvedPatchsetLevelComment,
};
reviewInput.comments = {
[SpecialFilePath.PATCHSET_LEVEL_COMMENTS]: [comment],
@@ -640,8 +1387,8 @@ export class GrReplyDialog extends PolymerElement {
reviewInput.reviewers = this.computeReviewers(this.change);
this.disabled = true;
- const errFn = (r?: Response | null) => this._handle400Error(r);
- return this._saveReview(reviewInput, errFn)
+ const errFn = (r?: Response | null) => this.handle400Error(r);
+ return this.saveReview(reviewInput, errFn)
.then(response => {
if (!response) {
// Null or undefined response indicates that an error handler
@@ -654,7 +1401,7 @@ export class GrReplyDialog extends PolymerElement {
}
this.draft = '';
- this._includeComments = true;
+ this.includeComments = true;
this.dispatchEvent(
new CustomEvent('send', {
composed: true,
@@ -674,31 +1421,31 @@ export class GrReplyDialog extends PolymerElement {
});
}
- _focusOn(section?: FocusTarget) {
+ focusOn(section?: FocusTarget) {
// Safeguard- always want to focus on something.
if (!section || section === FocusTarget.ANY) {
- section = this._chooseFocusTarget();
+ section = this.chooseFocusTarget();
}
if (section === FocusTarget.BODY) {
const textarea = queryAndAssert<GrTextarea>(this, 'gr-textarea');
setTimeout(() => textarea.getNativeTextarea().focus());
} else if (section === FocusTarget.REVIEWERS) {
- const reviewerEntry = this.$.reviewers.focusStart;
- setTimeout(() => reviewerEntry.focus());
+ const reviewerEntry = this.reviewersList?.focusStart;
+ setTimeout(() => reviewerEntry?.focus());
} else if (section === FocusTarget.CCS) {
- const ccEntry = this.$.ccs.focusStart;
- setTimeout(() => ccEntry.focus());
+ const ccEntry = this.ccsList?.focusStart;
+ setTimeout(() => ccEntry?.focus());
}
}
- _chooseFocusTarget() {
+ chooseFocusTarget() {
// If we are the owner and the reviewers field is empty, focus on that.
if (
- this._account &&
+ this.account &&
this.change &&
this.change.owner &&
- this._account._account_id === this.change.owner._account_id &&
- (!this._reviewers || this._reviewers.length === 0)
+ this.account._account_id === this.change.owner._account_id &&
+ (!this.reviewers || this.reviewers?.length === 0)
) {
return FocusTarget.REVIEWERS;
}
@@ -707,15 +1454,15 @@ export class GrReplyDialog extends PolymerElement {
return FocusTarget.BODY;
}
- _isOwner(account?: AccountInfo, change?: ChangeInfo) {
+ isOwner(account?: AccountInfo, change?: ChangeInfo) {
if (!account || !change || !change.owner) return false;
return account._account_id === change.owner._account_id;
}
- _handle400Error(r?: Response | null) {
+ handle400Error(r?: Response | null) {
if (!r) throw new Error('Response is empty.');
let response: Response = r;
- // A call to _saveReview could fail with a server error if erroneous
+ // A call to saveReview could fail with a server error if erroneous
// reviewers were requested. This is signalled with a 400 Bad Request
// status. The default gr-rest-api error handling would result in a large
// JSON response body being displayed to the user in the gr-error-manager
@@ -750,45 +1497,42 @@ export class GrReplyDialog extends PolymerElement {
});
}
- _computeHideDraftList(draftCommentThreads?: CommentThread[]) {
+ computeHideDraftList(draftCommentThreads?: CommentThread[]) {
return !draftCommentThreads || draftCommentThreads.length === 0;
}
- _computeDraftsTitle(draftCommentThreads?: CommentThread[]) {
+ computeDraftsTitle(draftCommentThreads?: CommentThread[]) {
const total = draftCommentThreads ? draftCommentThreads.length : 0;
return pluralize(total, 'Draft');
}
- _computeMessagePlaceholder(canBeStarted: boolean) {
- return canBeStarted
+ computeMessagePlaceholder() {
+ this.messagePlaceholder = this.canBeStarted
? 'Add a note for your reviewers...'
: 'Say something nice...';
}
- @observe('change.reviewers.*', 'change.owner')
- _changeUpdated(
- changeRecord: PolymerDeepPropertyChange<Reviewers, Reviewers>,
- owner: AccountInfo
- ) {
- if (changeRecord === undefined || owner === undefined) return;
- this._rebuildReviewerArrays(changeRecord.base, owner);
+ changeUpdated() {
+ if (this.change === undefined) return;
+ this.rebuildReviewerArrays();
}
- _rebuildReviewerArrays(changeReviewers: Reviewers, owner: AccountInfo) {
- this._owner = owner;
+ rebuildReviewerArrays() {
+ if (!this.change?.owner || !this.change?.reviewers) return;
+ this.owner = this.change.owner;
const reviewers = [];
const ccs = [];
- if (changeReviewers) {
- for (const key of Object.keys(changeReviewers)) {
+ if (this.change.reviewers) {
+ for (const key of Object.keys(this.change.reviewers)) {
if (key !== 'REVIEWER' && key !== 'CC') {
this.reporting.error(new Error(`Unexpected reviewer state: ${key}`));
continue;
}
- if (!changeReviewers[key]) continue;
- for (const entry of changeReviewers[key]!) {
- if (entry._account_id === owner._account_id) {
+ if (!this.change.reviewers[key]) continue;
+ for (const entry of this.change.reviewers[key]!) {
+ if (entry._account_id === this.owner._account_id) {
continue;
}
switch (key) {
@@ -803,172 +1547,140 @@ export class GrReplyDialog extends PolymerElement {
}
}
- this._ccs = ccs;
- this._reviewers = reviewers;
+ this.ccs = ccs;
+ this.reviewers = reviewers;
}
- _handleAttentionModify() {
- this._attentionExpanded = true;
+ handleAttentionModify() {
+ this.attentionExpanded = true;
}
- @observe('_attentionExpanded')
- _onAttentionExpandedChange() {
+ onAttentionExpandedChange() {
// If the attention-detail section is expanded without dispatching this
// event, then the dialog may expand beyond the screen's bottom border.
fireEvent(this, 'iron-resize');
}
- _showAttentionSummary(attentionExpanded?: boolean) {
- return !attentionExpanded;
- }
-
- _showAttentionDetails(attentionExpanded?: boolean) {
- return attentionExpanded;
- }
-
- _computeAttentionButtonTitle(sendDisabled?: boolean) {
+ computeAttentionButtonTitle(sendDisabled?: boolean) {
return sendDisabled
? 'Modify the attention set by adding a comment or use the account ' +
'hovercard in the change page.'
: 'Edit attention set changes';
}
- _handleAttentionClick(e: Event) {
+ handleAttentionClick(e: Event) {
const id = (e.target as GrAccountChip)?.account?._account_id;
if (!id) return;
- const selfId = (this._account && this._account._account_id) || -1;
+ const selfId = (this.account && this.account._account_id) || -1;
const ownerId =
(this.change && this.change.owner && this.change.owner._account_id) || -1;
const self = id === selfId ? '_SELF' : '';
- const role = id === ownerId ? '_OWNER' : '_REVIEWER';
+ const role = id === ownerId ? 'OWNER' : '_REVIEWER';
- if (this._newAttentionSet.has(id)) {
- this._newAttentionSet.delete(id);
+ if (this.newAttentionSet.has(id)) {
+ this.newAttentionSet.delete(id);
this.reporting.reportInteraction(Interaction.ATTENTION_SET_CHIP, {
action: `REMOVE${self}${role}`,
});
} else {
- this._newAttentionSet.add(id);
+ this.newAttentionSet.add(id);
this.reporting.reportInteraction(Interaction.ATTENTION_SET_CHIP, {
action: `ADD${self}${role}`,
});
}
- // Ensure that Polymer picks up the change.
- this._newAttentionSet = new Set(this._newAttentionSet);
+ this.requestUpdate();
}
- _computeHasNewAttention(
- account?: AccountInfo,
- newAttention?: Set<AccountId>
- ) {
- return (
- newAttention &&
+ computeHasNewAttention(account?: AccountInfo) {
+ return !!(
account &&
account._account_id &&
- newAttention.has(account._account_id)
+ this.newAttentionSet?.has(account._account_id)
);
}
- @observe(
- '_account',
- '_reviewers.*',
- '_ccs.*',
- 'change',
- 'draftCommentThreads',
- '_includeComments',
- '_labelsChanged',
- 'hasDrafts'
- )
- _computeNewAttention(
- currentUser?: AccountInfo,
- reviewers?: PolymerDeepPropertyChange<
- AccountInfoInput[],
- AccountInfoInput[]
- >,
- ccs?: PolymerDeepPropertyChange<AccountInfoInput[], AccountInfoInput[]>,
- change?: ChangeInfo,
- draftCommentThreads?: CommentThread[],
- includeComments?: boolean,
- _labelsChanged?: boolean,
- hasDrafts?: boolean
- ) {
+ computeNewAttention() {
if (
- currentUser === undefined ||
- currentUser._account_id === undefined ||
- reviewers === undefined ||
- ccs === undefined ||
- change === undefined ||
- draftCommentThreads === undefined ||
- includeComments === undefined
+ this.account?._account_id === undefined ||
+ this.change === undefined ||
+ this.includeComments === undefined ||
+ this.draftCommentThreads === undefined
) {
return;
}
// The draft comments are only relevant for the attention set as long as the
// user actually plans to publish their drafts.
- draftCommentThreads = includeComments ? draftCommentThreads : [];
- const hasVote = !!_labelsChanged;
- const isOwner = this._isOwner(currentUser, change);
- const isUploader = this._uploader?._account_id === currentUser._account_id;
- this._attentionCcsCount = removeServiceUsers(ccs.base).length;
- this._currentAttentionSet = new Set(
- Object.keys(change.attention_set || {}).map(id => Number(id) as AccountId)
+ const draftCommentThreads = this.includeComments
+ ? this.draftCommentThreads
+ : [];
+ const hasVote = !!this.labelsChanged;
+ const isOwner = this.isOwner(this.account, this.change);
+ const isUploader = this.uploader?._account_id === this.account._account_id;
+ this.attentionCcsCount = removeServiceUsers(this.ccs).length;
+ this.currentAttentionSet = new Set(
+ Object.keys(this.change.attention_set || {}).map(
+ id => Number(id) as AccountId
+ )
);
- const newAttention = new Set(this._currentAttentionSet);
- if (change.status === ChangeStatus.NEW) {
+ const newAttention = new Set(this.currentAttentionSet);
+ if (this.change.status === ChangeStatus.NEW) {
// Add everyone that the user is replying to in a comment thread.
- this._computeCommentAccounts(draftCommentThreads).forEach(id =>
+ this.computeCommentAccounts(draftCommentThreads).forEach(id =>
newAttention.add(id)
);
// Remove the current user.
- newAttention.delete(currentUser._account_id);
+ newAttention.delete(this.account._account_id);
// Add all new reviewers, but not the current reviewer, if they are also
// sending a draft or a label vote.
const notIsReviewerAndHasDraftOrLabel = (r: AccountInfo) =>
- !(r._account_id === currentUser._account_id && (hasDrafts || hasVote));
- reviewers.base
- .filter(r => r._account_id)
+ !(
+ r._account_id === this.account!._account_id &&
+ (this.hasDrafts() || hasVote)
+ );
+ this.reviewers
+ .filter(r => isAccount(r))
.filter(r => r._pendingAdd || (this.canBeStarted && isOwner))
.filter(notIsReviewerAndHasDraftOrLabel)
- .forEach(r => newAttention.add(r._account_id!));
+ .forEach(r => newAttention.add((r as AccountInfo)._account_id!));
// Add owner and uploader, if someone else replies.
- if (hasDrafts || hasVote) {
- if (this._uploader?._account_id && !isUploader) {
- newAttention.add(this._uploader._account_id);
+ if (this.hasDrafts() || hasVote) {
+ if (this.uploader?._account_id && !isUploader) {
+ newAttention.add(this.uploader._account_id);
}
- if (change.owner?._account_id && !isOwner) {
- newAttention.add(change.owner._account_id);
+ if (this.change.owner?._account_id && !isOwner) {
+ newAttention.add(this.change.owner._account_id);
}
}
} else {
// The only reason for adding someone to the attention set for merged or
// abandoned changes is that someone makes a comment thread unresolved.
const hasUnresolvedDraft = draftCommentThreads.some(isUnresolved);
- if (change.owner && hasUnresolvedDraft) {
- // A change owner must have an _account_id.
- newAttention.add(change.owner._account_id!);
+ if (this.change.owner && hasUnresolvedDraft) {
+ // A change owner must have an account_id.
+ newAttention.add(this.change.owner._account_id!);
}
// Remove the current user.
- newAttention.delete(currentUser._account_id);
+ newAttention.delete(this.account._account_id);
}
// Finally make sure that everyone in the attention set is still active as
// owner, reviewer or cc.
- const allAccountIds = this._allAccounts()
+ const allAccountIds = this.allAccounts()
.map(a => a._account_id)
.filter(id => !!id);
- this._newAttentionSet = new Set(
+ this.newAttentionSet = new Set(
[...newAttention].filter(id => allAccountIds.includes(id))
);
- this._attentionExpanded = this._computeShowAttentionTip(
- currentUser,
- change.owner,
- this._currentAttentionSet,
- this._newAttentionSet
+ this.attentionExpanded = this.computeShowAttentionTip(
+ this.account,
+ this.change.owner,
+ this.currentAttentionSet,
+ this.newAttentionSet
);
}
- _computeShowAttentionTip(
+ computeShowAttentionTip(
currentUser?: AccountInfo,
owner?: AccountInfo,
currentAttentionSet?: Set<AccountId>,
@@ -983,7 +1695,7 @@ export class GrReplyDialog extends PolymerElement {
return isOwner && addedIds.length > 2;
}
- _computeCommentAccounts(threads: CommentThread[]) {
+ computeCommentAccounts(threads: CommentThread[]) {
const crLabel = this.change?.labels?.[StandardLabels.CODE_REVIEW];
const maxCrVoteAccountIds = getMaxAccounts(crLabel).map(a => a._account_id);
const accountIds = new Set<AccountId>();
@@ -991,7 +1703,7 @@ export class GrReplyDialog extends PolymerElement {
const unresolved = isUnresolved(thread);
thread.comments.forEach(comment => {
if (comment.author) {
- // A comment author must have an _account_id.
+ // A comment author must have an account_id.
const authorId = comment.author._account_id!;
const hasGivenMaxReviewVote = maxCrVoteAccountIds.includes(authorId);
if (unresolved || !hasGivenMaxReviewVote) accountIds.add(authorId);
@@ -1001,110 +1713,93 @@ export class GrReplyDialog extends PolymerElement {
return accountIds;
}
- _computeShowNoAttentionUpdate(
- config?: ServerInfo,
- currentAttentionSet?: Set<AccountId>,
- newAttentionSet?: Set<AccountId>,
- sendDisabled?: boolean
- ) {
- return (
- sendDisabled ||
- this._computeNewAttentionAccounts(
- config,
- currentAttentionSet,
- newAttentionSet
- ).length === 0
- );
+ computeShowNoAttentionUpdate() {
+ return this.sendDisabled || this.computeNewAttentionAccounts().length === 0;
}
- _computeDoNotUpdateMessage(
- currentAttentionSet?: Set<AccountId>,
- newAttentionSet?: Set<AccountId>,
- sendDisabled?: boolean
- ) {
- if (!currentAttentionSet || !newAttentionSet) return '';
- if (sendDisabled || areSetsEqual(currentAttentionSet, newAttentionSet)) {
+ computeDoNotUpdateMessage() {
+ if (!this.currentAttentionSet || !this.newAttentionSet) return '';
+ if (
+ this.sendDisabled ||
+ areSetsEqual(this.currentAttentionSet, this.newAttentionSet)
+ ) {
return 'No changes to the attention set.';
}
- if (containsAll(currentAttentionSet, newAttentionSet)) {
+ if (containsAll(this.currentAttentionSet, this.newAttentionSet)) {
return 'No additions to the attention set.';
}
this.reporting.error(
new Error(
- '_computeDoNotUpdateMessage()' +
+ 'computeDoNotUpdateMessage()' +
'should not be called when users were added to the attention set.'
)
);
return '';
}
- _computeNewAttentionAccounts(
- _?: ServerInfo,
- currentAttentionSet?: Set<AccountId>,
- newAttentionSet?: Set<AccountId>
- ) {
- if (currentAttentionSet === undefined || newAttentionSet === undefined) {
+ computeNewAttentionAccounts(): AccountInfo[] {
+ if (
+ this.currentAttentionSet === undefined ||
+ this.newAttentionSet === undefined
+ ) {
return [];
}
- return [...newAttentionSet]
- .filter(id => !currentAttentionSet.has(id))
- .map(id => this._findAccountById(id))
- .filter(account => !!account);
+ return [...this.newAttentionSet]
+ .filter(id => !this.currentAttentionSet.has(id))
+ .map(id => this.findAccountById(id))
+ .filter(account => !!account) as AccountInfo[];
}
- _findAccountById(accountId: AccountId) {
- return this._allAccounts().find(r => r._account_id === accountId);
+ findAccountById(accountId: AccountId) {
+ return this.allAccounts().find(r => r._account_id === accountId);
}
- _allAccounts() {
+ allAccounts() {
let allAccounts: (AccountInfoInput | GroupInfoInput)[] = [];
if (this.change && this.change.owner) allAccounts.push(this.change.owner);
- if (this._uploader) allAccounts.push(this._uploader);
- if (this._reviewers) allAccounts = [...allAccounts, ...this._reviewers];
- if (this._ccs) allAccounts = [...allAccounts, ...this._ccs];
+ if (this.uploader) allAccounts.push(this.uploader);
+ if (this.reviewers) allAccounts = [...allAccounts, ...this.reviewers];
+ if (this.ccs) allAccounts = [...allAccounts, ...this.ccs];
return removeServiceUsers(allAccounts.filter(isAccount));
}
- /**
- * The newAttentionSet param is only used to force re-computation.
- */
- _removeServiceUsers(accounts: AccountInfo[], _: Set<AccountId>) {
+ removeServiceUsers(accounts: AccountInfo[]) {
return removeServiceUsers(accounts);
}
- _computeUploader(change: ChangeInfo) {
+ computeUploader() {
if (
- !change ||
- !change.current_revision ||
- !change.revisions ||
- !change.revisions[change.current_revision]
+ !this.change?.current_revision ||
+ !this.change?.revisions?.[this.change.current_revision]
) {
- return undefined;
+ this.uploader = undefined;
+ return;
}
- const rev = change.revisions[change.current_revision];
+ const rev = this.change.revisions[this.change.current_revision];
if (
!rev.uploader ||
- change.owner._account_id === rev.uploader._account_id
+ this.change?.owner._account_id === rev.uploader._account_id
) {
- return undefined;
+ this.uploader = undefined;
+ return;
}
- return rev.uploader;
+ this.uploader = rev.uploader;
}
/**
* Generates a function to filter out reviewer/CC entries. When isCCs is
- * truthy, the function filters out entries that already exist in this._ccs.
- * When falsy, the function filters entries that exist in this._reviewers.
+ * truthy, the function filters out entries that already exist in this.ccs.
+ * When falsy, the function filters entries that exist in this.reviewers.
*/
- _filterReviewerSuggestionGenerator(
+ filterReviewerSuggestionGenerator(
isCCs: boolean
): (input: Suggestion) => boolean {
return suggestion => {
let entry: AccountInfo | GroupInfo;
if (isReviewerAccountSuggestion(suggestion)) {
entry = suggestion.account;
- if (entry._account_id === this._owner?._account_id) {
+ if (entry._account_id === this.owner?._account_id) {
return false;
}
} else if (isReviewerGroupSuggestion(suggestion)) {
@@ -1120,24 +1815,20 @@ export class GrReplyDialog extends PolymerElement {
const finder = (entry: AccountInfo | GroupInfo) =>
accountOrGroupKey(entry) === key;
if (isCCs) {
- return this._ccs.find(finder) === undefined;
+ return this.ccs.find(finder) === undefined;
}
- return this._reviewers.find(finder) === undefined;
+ return this.reviewers.find(finder) === undefined;
};
}
- _getAccount() {
- return this.restApiService.getAccount();
- }
-
- _cancelTapHandler(e: Event) {
+ cancelTapHandler(e: Event) {
e.preventDefault();
this.cancel();
}
cancel() {
assertIsDefined(this.change, 'change');
- if (!this._owner) throw new Error('missing required _owner property');
+ if (!this.owner) throw new Error('missing required owner property');
this.dispatchEvent(
new CustomEvent('cancel', {
composed: true,
@@ -1145,36 +1836,36 @@ export class GrReplyDialog extends PolymerElement {
})
);
queryAndAssert<GrTextarea>(this, 'gr-textarea').closeDropdown();
- this.$.reviewers.clearPendingRemovals();
- this._rebuildReviewerArrays(this.change.reviewers, this._owner);
+ this.reviewersList?.clearPendingRemovals();
+ this.rebuildReviewerArrays();
}
- _saveClickHandler(e: Event) {
+ saveClickHandler(e: Event) {
e.preventDefault();
- if (!this.$.ccs.submitEntryText()) {
+ if (!this.ccsList?.submitEntryText()) {
// Do not proceed with the save if there is an invalid email entry in
// the text field of the CC entry.
return;
}
- this.send(this._includeComments, false);
+ this.send(this.includeComments, false);
}
- _sendTapHandler(e: Event) {
+ sendTapHandler(e: Event) {
e.preventDefault();
- this._submit();
+ this.submit();
}
- _submit() {
- if (!this.$.ccs.submitEntryText()) {
+ submit() {
+ if (!this.ccsList?.submitEntryText()) {
// Do not proceed with the send if there is an invalid email entry in
// the text field of the CC entry.
return;
}
- if (this._sendDisabled) {
+ if (this.sendDisabled) {
fireAlert(this, EMPTY_REPLY_MESSAGE);
return;
}
- return this.send(this._includeComments, this.canBeStarted).catch(err => {
+ return this.send(this.includeComments, this.canBeStarted).catch(err => {
this.dispatchEvent(
new CustomEvent('show-error', {
bubbles: true,
@@ -1185,7 +1876,7 @@ export class GrReplyDialog extends PolymerElement {
});
}
- _saveReview(review: ReviewInput, errFn?: ErrorCallback) {
+ saveReview(review: ReviewInput, errFn?: ErrorCallback) {
assertIsDefined(this.change, 'change');
assertIsDefined(this.patchNum, 'patchNum');
return this.restApiService.saveChangeReview(
@@ -1196,43 +1887,43 @@ export class GrReplyDialog extends PolymerElement {
);
}
- _reviewerPendingConfirmationUpdated(reviewer: RawAccountInput | null) {
+ pendingConfirmationUpdated(reviewer: RawAccountInput | null) {
if (reviewer === null) {
- this.$.reviewerConfirmationOverlay.close();
+ this.reviewerConfirmationOverlay?.close();
} else {
- this._pendingConfirmationDetails =
- this._ccPendingConfirmation || this._reviewerPendingConfirmation;
- this.$.reviewerConfirmationOverlay.open();
+ this.pendingConfirmationDetails =
+ this.ccPendingConfirmation || this.reviewerPendingConfirmation;
+ this.reviewerConfirmationOverlay?.open();
}
}
- _confirmPendingReviewer() {
- if (this._ccPendingConfirmation) {
- this.$.ccs.confirmGroup(this._ccPendingConfirmation.group);
- this._focusOn(FocusTarget.CCS);
+ confirmPendingReviewer() {
+ if (this.ccPendingConfirmation) {
+ this.ccsList?.confirmGroup(this.ccPendingConfirmation.group);
+ this.focusOn(FocusTarget.CCS);
return;
}
- if (this._reviewerPendingConfirmation) {
- this.$.reviewers.confirmGroup(this._reviewerPendingConfirmation.group);
- this._focusOn(FocusTarget.REVIEWERS);
+ if (this.reviewerPendingConfirmation) {
+ this.reviewersList?.confirmGroup(this.reviewerPendingConfirmation.group);
+ this.focusOn(FocusTarget.REVIEWERS);
return;
}
this.reporting.error(
- new Error('_confirmPendingReviewer called without pending confirm')
+ new Error('confirmPendingReviewer called without pending confirm')
);
}
- _cancelPendingReviewer() {
- this._ccPendingConfirmation = null;
- this._reviewerPendingConfirmation = null;
+ cancelPendingReviewer() {
+ this.ccPendingConfirmation = null;
+ this.reviewerPendingConfirmation = null;
- const target = this._ccPendingConfirmation
+ const target = this.ccPendingConfirmation
? FocusTarget.CCS
: FocusTarget.REVIEWERS;
- this._focusOn(target);
+ this.focusOn(target);
}
- _getStorageLocation(): StorageLocation {
+ getStorageLocation(): StorageLocation {
assertIsDefined(this.change, 'change');
return {
changeNum: this.change._number,
@@ -1241,50 +1932,73 @@ export class GrReplyDialog extends PolymerElement {
};
}
- _loadStoredDraft() {
- const draft = this.storage.getDraftComment(this._getStorageLocation());
+ loadStoredDraft() {
+ const draft = this.storage.getDraftComment(this.getStorageLocation());
return draft?.message ?? '';
}
- _handleAccountTextEntry() {
+ handleAccountTextEntry() {
// When either of the account entries has input added to the autocomplete,
// it should trigger the save button to enable/
//
// Note: if the text is removed, the save button will not get disabled.
- this._reviewersMutated = true;
+ this.reviewersMutated = true;
}
- _draftChanged(newDraft: string, oldDraft?: string) {
+ draftChanged(oldDraft: string) {
this.storeTask = debounce(
this.storeTask,
() => {
- if (!newDraft.length && oldDraft) {
+ if (!this.draft.length && oldDraft) {
// If the draft has been modified to be empty, then erase the storage
// entry.
- this.storage.eraseDraftComment(this._getStorageLocation());
- } else if (newDraft.length) {
- this.storage.setDraftComment(this._getStorageLocation(), this.draft);
+ this.storage.eraseDraftComment(this.getStorageLocation());
+ } else if (this.draft.length) {
+ this.storage.setDraftComment(this.getStorageLocation(), this.draft);
}
},
STORAGE_DEBOUNCE_INTERVAL_MS
);
}
- _handleHeightChanged() {
+ handleHeightChanged() {
fireEvent(this, 'autogrow');
}
- getLabelScores() {
- return this.$.labelScores || queryAndAssert(this, 'gr-label-scores');
+ getLabelScores(): GrLabelScores {
+ return this.labelScores || queryAndAssert(this, 'gr-label-scores');
}
_handleLabelsChanged() {
- this._labelsChanged =
+ this.labelsChanged =
Object.keys(this.getLabelScores().getLabelValues(false)).length !== 0;
}
- _isState(knownLatestState?: LatestPatchState, value?: LatestPatchState) {
- return knownLatestState === value;
+ // To decouple account-list and reply dialog
+ getAccountListCopy(list: (AccountInfo | GroupInfo)[]) {
+ return list.slice();
+ }
+
+ handleReviewersChanged(e: ValueChangedEvent<(AccountInfo | GroupInfo)[]>) {
+ this.reviewers = e.detail.value.slice();
+ this.reviewersMutated = true;
+ }
+
+ handleCcsChanged(e: ValueChangedEvent<(AccountInfo | GroupInfo)[]>) {
+ this.ccs = e.detail.value.slice();
+ this.reviewersMutated = true;
+ }
+
+ handleReviewersConfirmationChanged(
+ e: ValueChangedEvent<SuggestedReviewerGroupInfo | null>
+ ) {
+ this.reviewerPendingConfirmation = e.detail.value;
+ }
+
+ handleCcsConfirmationChanged(
+ e: ValueChangedEvent<SuggestedReviewerGroupInfo | null>
+ ) {
+ this.ccPendingConfirmation = e.detail.value;
}
_reload() {
@@ -1292,82 +2006,77 @@ export class GrReplyDialog extends PolymerElement {
this.cancel();
}
- _computeSendButtonLabel(canBeStarted: boolean) {
- return canBeStarted
+ computeSendButtonLabel() {
+ this.sendButtonLabel = this.canBeStarted
? ButtonLabels.SEND + ' and ' + ButtonLabels.START_REVIEW
: ButtonLabels.SEND;
}
- _computeSendButtonTooltip(canBeStarted?: boolean, commentEditing?: boolean) {
+ computeSendButtonTooltip(canBeStarted?: boolean, commentEditing?: boolean) {
if (commentEditing) {
return ButtonTooltips.DISABLED_COMMENT_EDITING;
}
return canBeStarted ? ButtonTooltips.START_REVIEW : ButtonTooltips.SEND;
}
- _computeSavingLabelClass(savingComments: boolean) {
+ computeSavingLabelClass(savingComments: boolean) {
return savingComments ? 'saving' : '';
}
- _computeSendButtonDisabled(
- canBeStarted?: boolean,
- draftCommentThreads?: CommentThread[],
- text?: string,
- reviewersMutated?: boolean,
- labelsChanged?: boolean,
- includeComments?: boolean,
- disabled?: boolean,
- commentEditing?: boolean,
- change?: ChangeInfo,
- account?: AccountInfo
- ) {
+ computeSendButtonDisabled() {
if (
- canBeStarted === undefined ||
- draftCommentThreads === undefined ||
- text === undefined ||
- reviewersMutated === undefined ||
- labelsChanged === undefined ||
- includeComments === undefined ||
- disabled === undefined ||
- commentEditing === undefined ||
- change?.labels === undefined ||
- account === undefined
+ this.canBeStarted === undefined ||
+ this.draftCommentThreads === undefined ||
+ this.draft === undefined ||
+ this.reviewersMutated === undefined ||
+ this.labelsChanged === undefined ||
+ this.includeComments === undefined ||
+ this.disabled === undefined ||
+ this.commentEditing === undefined ||
+ this.change?.labels === undefined ||
+ this.account === undefined
) {
return undefined;
}
- if (commentEditing || disabled) {
+ if (this.commentEditing || this.disabled) {
return true;
}
- if (canBeStarted === true) {
+ if (this.canBeStarted === true) {
return false;
}
- const existingVote = Object.values(change.labels).some(
- label => isDetailedLabelInfo(label) && getApprovalInfo(label, account)
+ const existingVote = Object.values(this.change.labels).some(
+ label =>
+ isDetailedLabelInfo(label) && getApprovalInfo(label, this.account!)
);
- const revotingOrNewVote = labelsChanged || existingVote;
- const hasDrafts = includeComments && draftCommentThreads.length;
+ const revotingOrNewVote = this.labelsChanged || existingVote;
+ const hasDrafts =
+ this.includeComments && this.draftCommentThreads.length > 0;
return (
- !hasDrafts && !text.length && !reviewersMutated && !revotingOrNewVote
+ !hasDrafts &&
+ !this.draft.length &&
+ !this.reviewersMutated &&
+ !revotingOrNewVote
);
}
- _computePatchSetWarning(patchNum?: PatchSetNum, labelsChanged?: boolean) {
- let str = `Patch ${patchNum} is not latest.`;
- if (labelsChanged) {
+ computePatchSetWarning() {
+ let str = `Patch ${this.patchNum} is not latest.`;
+ if (this.labelsChanged) {
str += ' Voting may have no effect.';
}
return str;
}
setPluginMessage(message: string) {
- this._pluginMessage = message;
+ this.pluginMessage = message;
}
- _sendDisabledChanged() {
+ sendDisabledChanged() {
this.dispatchEvent(new CustomEvent('send-disabled-changed'));
}
- _getReviewerSuggestionsProvider(change: ChangeInfo) {
+ getReviewerSuggestionsProvider(change?: ChangeInfo) {
+ if (!change) return;
const provider = GrReviewerSuggestionsProvider.create(
this.restApiService,
change._number,
@@ -1377,7 +2086,8 @@ export class GrReplyDialog extends PolymerElement {
return provider;
}
- _getCcSuggestionsProvider(change: ChangeInfo) {
+ getCcSuggestionsProvider(change?: ChangeInfo) {
+ if (!change) return;
const provider = GrReviewerSuggestionsProvider.create(
this.restApiService,
change._number,
@@ -1395,24 +2105,24 @@ export class GrReplyDialog extends PolymerElement {
const actions = modified ? ['MODIFIED'] : ['NOT_MODIFIED'];
const ownerId =
(this.change && this.change.owner && this.change.owner._account_id) || -1;
- const selfId = (this._account && this._account._account_id) || -1;
+ const selfId = (this.account && this.account._account_id) || -1;
for (const added of addedSet || []) {
const addedId = added.user;
const self = addedId === selfId ? '_SELF' : '';
- const role = addedId === ownerId ? '_OWNER' : '_REVIEWER';
+ const role = addedId === ownerId ? 'OWNER' : '_REVIEWER';
actions.push('ADD' + self + role);
}
for (const removed of removedSet || []) {
const removedId = removed.user;
const self = removedId === selfId ? '_SELF' : '';
- const role = removedId === ownerId ? '_OWNER' : '_REVIEWER';
+ const role = removedId === ownerId ? 'OWNER' : '_REVIEWER';
actions.push('REMOVE' + self + role);
}
this.reporting.reportInteraction('attention-set-actions', {actions});
}
- _computeAllReviewers() {
- return [...this._reviewers];
+ computeAllReviewers() {
+ this.allReviewers = [...this.reviewers];
}
}