blob: 8000c222e01b7340e47a87ce4666d375840e29e6 [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
Ben Rohlfs94fcbbc2022-05-27 10:45:03 +02003 * Copyright 2015 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04005 */
Dmitrii Filippov8814de12020-10-10 16:31:21 +02006import '../gr-change-list/gr-change-list';
7import '../gr-repo-header/gr-repo-header';
8import '../gr-user-header/gr-user-header';
Dmitrii Filippov8814de12020-10-10 16:31:21 +02009import {page} from '../../../utils/page-wrapper-utils';
Dmitrii Filippov8814de12020-10-10 16:31:21 +020010import {
11 AccountDetailInfo,
12 AccountId,
Dmitrii Filippov8814de12020-10-10 16:31:21 +020013 ChangeInfo,
14 EmailAddress,
Dmitrii Filippov8814de12020-10-10 16:31:21 +020015 PreferencesInput,
Paladox none5554d952021-08-11 03:56:51 +000016 RepoName,
Dmitrii Filippov8814de12020-10-10 16:31:21 +020017} from '../../../types/common';
Dmitrii Filippov8814de12020-10-10 16:31:21 +020018import {ChangeStarToggleStarDetail} from '../../shared/gr-change-star/gr-change-star';
Ben Rohlfs5a1bd472022-09-09 13:27:22 +020019import {fireAlert, fireEvent, fireTitleChange} from '../../../utils/event-util';
Chris Poucetc6e880b2021-11-15 19:57:06 +010020import {getAppContext} from '../../../services/app-context';
Paladox none142add02021-12-21 21:06:09 +000021import {sharedStyles} from '../../../styles/shared-styles';
Ben Rohlfs42d29da2022-09-19 12:42:40 +020022import {LitElement, PropertyValues, html, css, nothing} from 'lit';
23import {customElement, state, query} from 'lit/decorators.js';
Ben Rohlfs42d29da2022-09-19 12:42:40 +020024import {
25 createSearchUrl,
26 searchViewModelToken,
Ben Rohlfs42d29da2022-09-19 12:42:40 +020027} from '../../../models/views/search';
28import {resolve} from '../../../models/dependency';
29import {subscribe} from '../../lit/subscription-controller';
Logan Hanks29693ac2017-07-12 10:11:21 -070030
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010031const LIMIT_OPERATOR_PATTERN = /\blimit:(\d+)/i;
32
Dmitrii Filippov8814de12020-10-10 16:31:21 +020033@customElement('gr-change-list-view')
Paladox none142add02021-12-21 21:06:09 +000034export class GrChangeListView extends LitElement {
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +010035 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010036 * Fired when the title of the page should change.
37 *
38 * @event title-change
Tao Zhou9a076812019-12-17 09:59:28 +010039 */
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010040
Paladox none142add02021-12-21 21:06:09 +000041 @query('#prevArrow') protected prevArrow?: HTMLAnchorElement;
Andrew Bonventre78792e82016-03-04 17:48:22 -050042
Paladox none142add02021-12-21 21:06:09 +000043 @query('#nextArrow') protected nextArrow?: HTMLAnchorElement;
44
Ben Rohlfs42d29da2022-09-19 12:42:40 +020045 // private but used in test
46 @state() account?: AccountDetailInfo;
47
48 // private but used in test
49 @state() loggedIn = false;
50
51 // private but used in test
52 @state() preferences?: PreferencesInput;
Andrew Bonventre78792e82016-03-04 17:48:22 -050053
Paladox none142add02021-12-21 21:06:09 +000054 // private but used in test
55 @state() changesPerPage?: number;
Kasper Nilsson8e6a64d2018-04-04 15:33:19 -070056
Paladox none142add02021-12-21 21:06:09 +000057 // private but used in test
58 @state() query = '';
Urs Wolfer54a5a462016-03-09 21:04:22 +010059
Paladox none142add02021-12-21 21:06:09 +000060 // private but used in test
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000061 @state() offset = 0;
Andrew Bonventre78792e82016-03-04 17:48:22 -050062
Paladox none142add02021-12-21 21:06:09 +000063 // private but used in test
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000064 @state() changes: ChangeInfo[] = [];
Andrew Bonventre78792e82016-03-04 17:48:22 -050065
Paladox none142add02021-12-21 21:06:09 +000066 // private but used in test
67 @state() loading = true;
Andrew Bonventre78792e82016-03-04 17:48:22 -050068
Paladox none142add02021-12-21 21:06:09 +000069 // private but used in test
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000070 @state() userId?: AccountId | EmailAddress;
Wyatt Allen5c3a3cd2017-08-30 13:43:37 -070071
Paladox none142add02021-12-21 21:06:09 +000072 // private but used in test
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000073 @state() repo?: RepoName;
Dhruv7c72da52022-06-09 11:43:15 +020074
Chris Poucetc6e880b2021-11-15 19:57:06 +010075 private readonly restApiService = getAppContext().restApiService;
Ben Rohlfs43935a42020-12-01 19:14:09 +010076
Chris Poucetc6e880b2021-11-15 19:57:06 +010077 private reporting = getAppContext().reportingService;
Dhruv Srivastava5841dd82021-05-17 11:03:19 +020078
Ben Rohlfs42d29da2022-09-19 12:42:40 +020079 private userModel = getAppContext().userModel;
80
81 private readonly getViewModel = resolve(this, searchViewModelToken);
82
Ben Rohlfsf7f1e8e2021-03-12 14:36:40 +010083 constructor() {
84 super();
Paladox none142add02021-12-21 21:06:09 +000085 this.addEventListener('next-page', () => this.handleNextPage());
86 this.addEventListener('previous-page', () => this.handlePreviousPage());
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000087
Ben Rohlfs42d29da2022-09-19 12:42:40 +020088 subscribe(
89 this,
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000090 () => this.getViewModel().query$,
91 x => (this.query = x)
92 );
93 subscribe(
94 this,
95 () => this.getViewModel().offsetNumber$,
96 x => (this.offset = x)
97 );
98 subscribe(
99 this,
100 () => this.getViewModel().loading$,
101 x => (this.loading = x)
102 );
103 subscribe(
104 this,
105 () => this.getViewModel().changes$,
106 x => (this.changes = x)
107 );
108 subscribe(
109 this,
110 () => this.getViewModel().userId$,
111 x => (this.userId = x)
112 );
113 subscribe(
114 this,
115 () => this.getViewModel().repo$,
116 x => (this.repo = x)
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200117 );
118 subscribe(
119 this,
120 () => this.userModel.account$,
121 x => (this.account = x)
122 );
123 subscribe(
124 this,
125 () => this.userModel.loggedIn$,
126 x => (this.loggedIn = x)
127 );
128 subscribe(
129 this,
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +0000130 () => this.userModel.preferenceChangesPerPage$,
131 x => (this.changesPerPage = x)
Ben Rohlfsc8a096d2022-09-23 10:41:45 +0200132 );
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +0000133 subscribe(
134 this,
135 () => this.userModel.preferences$,
136 x => (this.preferences = x)
137 );
Ben Rohlfs27e95692022-09-28 12:57:47 +0000138 }
139
Paladox none142add02021-12-21 21:06:09 +0000140 static override get styles() {
141 return [
142 sharedStyles,
143 css`
144 :host {
145 display: block;
146 }
147 .loading {
148 color: var(--deemphasized-text-color);
149 padding: var(--spacing-l);
150 }
151 gr-change-list {
152 width: 100%;
153 }
154 gr-user-header,
155 gr-repo-header {
156 border-bottom: 1px solid var(--border-color);
157 }
158 nav {
159 align-items: center;
160 display: flex;
161 height: 3rem;
162 justify-content: flex-end;
163 margin-right: 20px;
Paladox none142add02021-12-21 21:06:09 +0000164 color: var(--deemphasized-text-color);
165 }
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200166 gr-icon {
Chris Poucetcf512542022-07-12 09:03:42 +0200167 font-size: 1.85rem;
Paladox none142add02021-12-21 21:06:09 +0000168 margin-left: 16px;
Paladox none142add02021-12-21 21:06:09 +0000169 }
170 .hide {
171 display: none;
172 }
173 @media only screen and (max-width: 50em) {
174 .loading,
175 .error {
176 padding: 0 var(--spacing-l);
177 }
178 }
179 `,
180 ];
181 }
182
183 override render() {
Dhruv Srivastava45166a12022-03-02 15:30:35 +0100184 // In case of an internal reload we want the ChangeList section components
185 // to remain in the DOM so that the Bulk Actions Model associated with them
186 // is not recreated after the reload resulting in user selections being lost
Paladox none142add02021-12-21 21:06:09 +0000187 return html`
Dhruv Srivastava45166a12022-03-02 15:30:35 +0100188 <div class="loading" ?hidden=${!this.loading}>Loading...</div>
189 <div ?hidden=${this.loading}>
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200190 ${this.renderRepoHeader()} ${this.renderUserHeader()}
Paladox none142add02021-12-21 21:06:09 +0000191 <gr-change-list
192 .account=${this.account}
193 .changes=${this.changes}
194 .preferences=${this.preferences}
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200195 .showStar=${this.loggedIn}
Paladox none142add02021-12-21 21:06:09 +0000196 @toggle-star=${(e: CustomEvent<ChangeStarToggleStarDetail>) => {
197 this.handleToggleStar(e);
198 }}
Chris Poucet12100f12022-07-06 15:11:27 +0200199 .usp=${'search'}
Paladox none142add02021-12-21 21:06:09 +0000200 ></gr-change-list>
201 ${this.renderChangeListViewNav()}
202 </div>
203 `;
204 }
205
206 private renderRepoHeader() {
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200207 if (!this.repo) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000208
209 return html` <gr-repo-header .repo=${this.repo}></gr-repo-header> `;
210 }
211
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200212 private renderUserHeader() {
213 if (!this.userId) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000214
215 return html`
216 <gr-user-header
217 .userId=${this.userId}
218 showDashboardLink
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200219 .loggedIn=${this.loggedIn}
Paladox none142add02021-12-21 21:06:09 +0000220 ></gr-user-header>
221 `;
222 }
223
224 private renderChangeListViewNav() {
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200225 if (this.loading || !this.changes || !this.changes.length) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000226
227 return html`
228 <nav>
229 Page ${this.computePage()} ${this.renderPrevArrow()}
230 ${this.renderNextArrow()}
231 </nav>
232 `;
233 }
234
235 private renderPrevArrow() {
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200236 if (this.offset === 0) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000237
238 return html`
Ben Rohlfs8003bd32022-04-05 18:24:42 +0200239 <a id="prevArrow" href=${this.computeNavLink(-1)}>
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200240 <gr-icon icon="chevron_left" aria-label="Older"></gr-icon>
Paladox none142add02021-12-21 21:06:09 +0000241 </a>
242 `;
243 }
244
245 private renderNextArrow() {
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200246 const changesCount = this.changes?.length ?? 0;
247 if (changesCount === 0) return nothing;
248 if (!this.changes?.[changesCount - 1]._more_changes) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000249
250 return html`
Ben Rohlfs8003bd32022-04-05 18:24:42 +0200251 <a id="nextArrow" href=${this.computeNavLink(1)}>
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200252 <gr-icon icon="chevron_right" aria-label="Newer"></gr-icon>
Paladox none142add02021-12-21 21:06:09 +0000253 </a>
254 `;
255 }
256
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +0000257 override updated(changedProperties: PropertyValues) {
258 if (changedProperties.has('query')) {
259 fireTitleChange(this, this.query);
Paladox none142add02021-12-21 21:06:09 +0000260 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100261 }
262
Paladox none142add02021-12-21 21:06:09 +0000263 // private but used in test
264 limitFor(query: string, defaultLimit?: number) {
265 if (defaultLimit === undefined) return 0;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100266 const match = query.match(LIMIT_OPERATOR_PATTERN);
267 if (!match) {
268 return defaultLimit;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100269 }
Dhruv Srivastavab8edee92020-10-19 10:20:07 +0200270 return Number(match[1]);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100271 }
Kasper Nilsson97240922018-02-08 14:57:27 -0800272
Paladox none142add02021-12-21 21:06:09 +0000273 // private but used in test
274 computeNavLink(direction: number) {
275 const offset = this.offset ?? 0;
276 const limit = this.limitFor(this.query, this.changesPerPage);
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200277 const newOffset = Math.max(0, offset + limit * direction);
Ben Rohlfs39aa0572022-09-14 22:57:17 +0200278 return createSearchUrl({query: this.query, offset: newOffset});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100279 }
280
Paladox none142add02021-12-21 21:06:09 +0000281 // private but used in test
282 handleNextPage() {
283 if (!this.nextArrow || !this.changesPerPage) return;
284 page.show(this.computeNavLink(1));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100285 }
286
Paladox none142add02021-12-21 21:06:09 +0000287 // private but used in test
288 handlePreviousPage() {
289 if (!this.prevArrow || !this.changesPerPage) return;
290 page.show(this.computeNavLink(-1));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100291 }
292
Paladox none142add02021-12-21 21:06:09 +0000293 // private but used in test
294 computePage() {
295 if (this.offset === undefined || this.changesPerPage === undefined) return;
296 return this.offset / this.changesPerPage + 1;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100297 }
298
Ben Rohlfs5a1bd472022-09-09 13:27:22 +0200299 private async handleToggleStar(e: CustomEvent<ChangeStarToggleStarDetail>) {
Dhruv Srivastava5841dd82021-05-17 11:03:19 +0200300 if (e.detail.starred) {
301 this.reporting.reportInteraction('change-starred-from-change-list');
302 }
Ben Rohlfs5a1bd472022-09-09 13:27:22 +0200303 const msg = e.detail.starred
304 ? 'Starring change...'
305 : 'Unstarring change...';
306 fireAlert(this, msg);
307 await this.restApiService.saveChangeStarred(
Ben Rohlfs43935a42020-12-01 19:14:09 +0100308 e.detail.change._number,
309 e.detail.starred
310 );
Ben Rohlfs5a1bd472022-09-09 13:27:22 +0200311 fireEvent(this, 'hide-alert');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100312 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100313}
314
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200315declare global {
316 interface HTMLElementTagNameMap {
317 'gr-change-list-view': GrChangeListView;
318 }
319}