blob: 96b01e1d5f08b5b5ff3f68379950006f11ea28a3 [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 {
10 AccountDetailInfo,
11 AccountId,
Dmitrii Filippov8814de12020-10-10 16:31:21 +020012 ChangeInfo,
13 EmailAddress,
Dmitrii Filippov8814de12020-10-10 16:31:21 +020014 PreferencesInput,
Paladox none5554d952021-08-11 03:56:51 +000015 RepoName,
Dmitrii Filippov8814de12020-10-10 16:31:21 +020016} from '../../../types/common';
Dmitrii Filippov8814de12020-10-10 16:31:21 +020017import {ChangeStarToggleStarDetail} from '../../shared/gr-change-star/gr-change-star';
Ben Rohlfs44f01042023-02-18 13:27:57 +010018import {fireAlert, fire, fireTitleChange} from '../../../utils/event-util';
Chris Poucetc6e880b2021-11-15 19:57:06 +010019import {getAppContext} from '../../../services/app-context';
Paladox none142add02021-12-21 21:06:09 +000020import {sharedStyles} from '../../../styles/shared-styles';
Ben Rohlfs42d29da2022-09-19 12:42:40 +020021import {LitElement, PropertyValues, html, css, nothing} from 'lit';
22import {customElement, state, query} from 'lit/decorators.js';
Ben Rohlfs42d29da2022-09-19 12:42:40 +020023import {
24 createSearchUrl,
25 searchViewModelToken,
Ben Rohlfs42d29da2022-09-19 12:42:40 +020026} from '../../../models/views/search';
27import {resolve} from '../../../models/dependency';
28import {subscribe} from '../../lit/subscription-controller';
Chris Poucetbb0cf832022-10-24 12:32:10 +020029import {userModelToken} from '../../../models/user/user-model';
Ben Rohlfs118aa9f2023-01-24 18:10:35 +010030import {navigationToken} from '../../core/gr-navigation/gr-navigation';
Logan Hanks29693ac2017-07-12 10:11:21 -070031
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010032const LIMIT_OPERATOR_PATTERN = /\blimit:(\d+)/i;
33
Dmitrii Filippov8814de12020-10-10 16:31:21 +020034@customElement('gr-change-list-view')
Paladox none142add02021-12-21 21:06:09 +000035export class GrChangeListView extends LitElement {
Paladox none142add02021-12-21 21:06:09 +000036 @query('#prevArrow') protected prevArrow?: HTMLAnchorElement;
Andrew Bonventre78792e82016-03-04 17:48:22 -050037
Paladox none142add02021-12-21 21:06:09 +000038 @query('#nextArrow') protected nextArrow?: HTMLAnchorElement;
39
Ben Rohlfs42d29da2022-09-19 12:42:40 +020040 // private but used in test
41 @state() account?: AccountDetailInfo;
42
43 // private but used in test
44 @state() loggedIn = false;
45
46 // private but used in test
47 @state() preferences?: PreferencesInput;
Andrew Bonventre78792e82016-03-04 17:48:22 -050048
Paladox none142add02021-12-21 21:06:09 +000049 // private but used in test
50 @state() changesPerPage?: number;
Kasper Nilsson8e6a64d2018-04-04 15:33:19 -070051
Paladox none142add02021-12-21 21:06:09 +000052 // private but used in test
53 @state() query = '';
Urs Wolfer54a5a462016-03-09 21:04:22 +010054
Paladox none142add02021-12-21 21:06:09 +000055 // private but used in test
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000056 @state() offset = 0;
Andrew Bonventre78792e82016-03-04 17:48:22 -050057
Paladox none142add02021-12-21 21:06:09 +000058 // private but used in test
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000059 @state() changes: ChangeInfo[] = [];
Andrew Bonventre78792e82016-03-04 17:48:22 -050060
Paladox none142add02021-12-21 21:06:09 +000061 // private but used in test
62 @state() loading = true;
Andrew Bonventre78792e82016-03-04 17:48:22 -050063
Paladox none142add02021-12-21 21:06:09 +000064 // private but used in test
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000065 @state() userId?: AccountId | EmailAddress;
Wyatt Allen5c3a3cd2017-08-30 13:43:37 -070066
Paladox none142add02021-12-21 21:06:09 +000067 // private but used in test
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000068 @state() repo?: RepoName;
Dhruv7c72da52022-06-09 11:43:15 +020069
Chris Poucetc6e880b2021-11-15 19:57:06 +010070 private readonly restApiService = getAppContext().restApiService;
Ben Rohlfs43935a42020-12-01 19:14:09 +010071
Chris Poucetc6e880b2021-11-15 19:57:06 +010072 private reporting = getAppContext().reportingService;
Dhruv Srivastava5841dd82021-05-17 11:03:19 +020073
Chris Poucetbb0cf832022-10-24 12:32:10 +020074 private readonly getUserModel = resolve(this, userModelToken);
Ben Rohlfs42d29da2022-09-19 12:42:40 +020075
76 private readonly getViewModel = resolve(this, searchViewModelToken);
77
Ben Rohlfs118aa9f2023-01-24 18:10:35 +010078 private readonly getNavigation = resolve(this, navigationToken);
79
Ben Rohlfsf7f1e8e2021-03-12 14:36:40 +010080 constructor() {
81 super();
Paladox none142add02021-12-21 21:06:09 +000082 this.addEventListener('next-page', () => this.handleNextPage());
83 this.addEventListener('previous-page', () => this.handlePreviousPage());
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000084
Ben Rohlfs42d29da2022-09-19 12:42:40 +020085 subscribe(
86 this,
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +000087 () => this.getViewModel().query$,
88 x => (this.query = x)
89 );
90 subscribe(
91 this,
92 () => this.getViewModel().offsetNumber$,
93 x => (this.offset = x)
94 );
95 subscribe(
96 this,
97 () => this.getViewModel().loading$,
98 x => (this.loading = x)
99 );
100 subscribe(
101 this,
102 () => this.getViewModel().changes$,
103 x => (this.changes = x)
104 );
105 subscribe(
106 this,
107 () => this.getViewModel().userId$,
108 x => (this.userId = x)
109 );
110 subscribe(
111 this,
112 () => this.getViewModel().repo$,
113 x => (this.repo = x)
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200114 );
115 subscribe(
116 this,
Chris Poucetbb0cf832022-10-24 12:32:10 +0200117 () => this.getUserModel().account$,
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200118 x => (this.account = x)
119 );
120 subscribe(
121 this,
Chris Poucetbb0cf832022-10-24 12:32:10 +0200122 () => this.getUserModel().loggedIn$,
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200123 x => (this.loggedIn = x)
124 );
125 subscribe(
126 this,
Chris Poucetbb0cf832022-10-24 12:32:10 +0200127 () => this.getUserModel().preferenceChangesPerPage$,
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +0000128 x => (this.changesPerPage = x)
Ben Rohlfsc8a096d2022-09-23 10:41:45 +0200129 );
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +0000130 subscribe(
131 this,
Chris Poucetbb0cf832022-10-24 12:32:10 +0200132 () => this.getUserModel().preferences$,
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +0000133 x => (this.preferences = x)
134 );
Ben Rohlfs27e95692022-09-28 12:57:47 +0000135 }
136
Paladox none142add02021-12-21 21:06:09 +0000137 static override get styles() {
138 return [
139 sharedStyles,
140 css`
141 :host {
142 display: block;
143 }
144 .loading {
145 color: var(--deemphasized-text-color);
146 padding: var(--spacing-l);
147 }
148 gr-change-list {
149 width: 100%;
150 }
151 gr-user-header,
152 gr-repo-header {
153 border-bottom: 1px solid var(--border-color);
154 }
155 nav {
156 align-items: center;
157 display: flex;
158 height: 3rem;
159 justify-content: flex-end;
160 margin-right: 20px;
Paladox none142add02021-12-21 21:06:09 +0000161 color: var(--deemphasized-text-color);
162 }
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200163 gr-icon {
Chris Poucetcf512542022-07-12 09:03:42 +0200164 font-size: 1.85rem;
Paladox none142add02021-12-21 21:06:09 +0000165 margin-left: 16px;
Paladox none142add02021-12-21 21:06:09 +0000166 }
167 .hide {
168 display: none;
169 }
170 @media only screen and (max-width: 50em) {
171 .loading,
172 .error {
173 padding: 0 var(--spacing-l);
174 }
175 }
176 `,
177 ];
178 }
179
180 override render() {
Dhruv Srivastava45166a12022-03-02 15:30:35 +0100181 // In case of an internal reload we want the ChangeList section components
182 // to remain in the DOM so that the Bulk Actions Model associated with them
183 // is not recreated after the reload resulting in user selections being lost
Paladox none142add02021-12-21 21:06:09 +0000184 return html`
Dhruv Srivastava45166a12022-03-02 15:30:35 +0100185 <div class="loading" ?hidden=${!this.loading}>Loading...</div>
186 <div ?hidden=${this.loading}>
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200187 ${this.renderRepoHeader()} ${this.renderUserHeader()}
Paladox none142add02021-12-21 21:06:09 +0000188 <gr-change-list
189 .account=${this.account}
190 .changes=${this.changes}
191 .preferences=${this.preferences}
Paladox none142add02021-12-21 21:06:09 +0000192 @toggle-star=${(e: CustomEvent<ChangeStarToggleStarDetail>) => {
193 this.handleToggleStar(e);
194 }}
Chris Poucet12100f12022-07-06 15:11:27 +0200195 .usp=${'search'}
Paladox none142add02021-12-21 21:06:09 +0000196 ></gr-change-list>
197 ${this.renderChangeListViewNav()}
198 </div>
199 `;
200 }
201
202 private renderRepoHeader() {
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200203 if (!this.repo) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000204
205 return html` <gr-repo-header .repo=${this.repo}></gr-repo-header> `;
206 }
207
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200208 private renderUserHeader() {
209 if (!this.userId) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000210
211 return html`
212 <gr-user-header
213 .userId=${this.userId}
214 showDashboardLink
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200215 .loggedIn=${this.loggedIn}
Paladox none142add02021-12-21 21:06:09 +0000216 ></gr-user-header>
217 `;
218 }
219
220 private renderChangeListViewNav() {
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200221 if (this.loading || !this.changes || !this.changes.length) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000222
223 return html`
224 <nav>
225 Page ${this.computePage()} ${this.renderPrevArrow()}
226 ${this.renderNextArrow()}
227 </nav>
228 `;
229 }
230
231 private renderPrevArrow() {
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200232 if (this.offset === 0) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000233
234 return html`
Ben Rohlfs8003bd32022-04-05 18:24:42 +0200235 <a id="prevArrow" href=${this.computeNavLink(-1)}>
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200236 <gr-icon icon="chevron_left" aria-label="Older"></gr-icon>
Paladox none142add02021-12-21 21:06:09 +0000237 </a>
238 `;
239 }
240
241 private renderNextArrow() {
Ben Rohlfs42d29da2022-09-19 12:42:40 +0200242 const changesCount = this.changes?.length ?? 0;
243 if (changesCount === 0) return nothing;
244 if (!this.changes?.[changesCount - 1]._more_changes) return nothing;
Paladox none142add02021-12-21 21:06:09 +0000245
246 return html`
Ben Rohlfs8003bd32022-04-05 18:24:42 +0200247 <a id="nextArrow" href=${this.computeNavLink(1)}>
Chris Poucetb7e9bb12022-07-22 14:11:03 +0200248 <gr-icon icon="chevron_right" aria-label="Newer"></gr-icon>
Paladox none142add02021-12-21 21:06:09 +0000249 </a>
250 `;
251 }
252
Ben Rohlfs7aeecaa2022-10-11 15:26:45 +0000253 override updated(changedProperties: PropertyValues) {
254 if (changedProperties.has('query')) {
Ben Rohlfsef65cba2023-02-28 21:13:33 +0100255 fireTitleChange(this.query);
Paladox none142add02021-12-21 21:06:09 +0000256 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100257 }
258
Paladox none142add02021-12-21 21:06:09 +0000259 // private but used in test
260 limitFor(query: string, defaultLimit?: number) {
261 if (defaultLimit === undefined) return 0;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100262 const match = query.match(LIMIT_OPERATOR_PATTERN);
263 if (!match) {
264 return defaultLimit;
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100265 }
Dhruv Srivastavab8edee92020-10-19 10:20:07 +0200266 return Number(match[1]);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100267 }
Kasper Nilsson97240922018-02-08 14:57:27 -0800268
Paladox none142add02021-12-21 21:06:09 +0000269 // private but used in test
270 computeNavLink(direction: number) {
271 const offset = this.offset ?? 0;
272 const limit = this.limitFor(this.query, this.changesPerPage);
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200273 const newOffset = Math.max(0, offset + limit * direction);
Ben Rohlfs39aa0572022-09-14 22:57:17 +0200274 return createSearchUrl({query: this.query, offset: newOffset});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100275 }
276
Paladox none142add02021-12-21 21:06:09 +0000277 // private but used in test
278 handleNextPage() {
279 if (!this.nextArrow || !this.changesPerPage) return;
Ben Rohlfs118aa9f2023-01-24 18:10:35 +0100280 this.getNavigation().setUrl(this.computeNavLink(1));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100281 }
282
Paladox none142add02021-12-21 21:06:09 +0000283 // private but used in test
284 handlePreviousPage() {
285 if (!this.prevArrow || !this.changesPerPage) return;
Ben Rohlfs118aa9f2023-01-24 18:10:35 +0100286 this.getNavigation().setUrl(this.computeNavLink(-1));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100287 }
288
Paladox none142add02021-12-21 21:06:09 +0000289 // private but used in test
290 computePage() {
291 if (this.offset === undefined || this.changesPerPage === undefined) return;
Chris Poucet9cc6ad92022-11-25 12:08:26 +0100292 // We use Math.ceil in case the offset is not divisible by changesPerPage.
293 // If we did not do this, you'd have page '1.2' and then when pressing left
294 // arrow 'Page 1'. This way page '1.2' becomes page '2'.
295 return (
296 Math.ceil(this.offset / this.limitFor(this.query, this.changesPerPage)) +
297 1
298 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100299 }
300
Ben Rohlfs5a1bd472022-09-09 13:27:22 +0200301 private async handleToggleStar(e: CustomEvent<ChangeStarToggleStarDetail>) {
Dhruv Srivastava5841dd82021-05-17 11:03:19 +0200302 if (e.detail.starred) {
303 this.reporting.reportInteraction('change-starred-from-change-list');
304 }
Ben Rohlfs5a1bd472022-09-09 13:27:22 +0200305 const msg = e.detail.starred
306 ? 'Starring change...'
307 : 'Unstarring change...';
308 fireAlert(this, msg);
309 await this.restApiService.saveChangeStarred(
Ben Rohlfs43935a42020-12-01 19:14:09 +0100310 e.detail.change._number,
311 e.detail.starred
312 );
Ben Rohlfs44f01042023-02-18 13:27:57 +0100313 fire(this, 'hide-alert', {});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100314 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100315}
316
Dmitrii Filippov8814de12020-10-10 16:31:21 +0200317declare global {
318 interface HTMLElementTagNameMap {
319 'gr-change-list-view': GrChangeListView;
320 }
321}