blob: 1ee898ab24817d03fb48ebeb4ea94f55eba7db7d [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001// Copyright 2020 The Chromium Authors
Copybara854996b2021-09-07 19:36:02 +00002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {LitElement, html, css} from 'lit-element';
6
7import 'elements/chops/chops-dialog/chops-dialog.js';
8import * as userV0 from 'reducers/userV0.js';
9import {SHARED_STYLES} from 'shared/shared-styles.js';
10import {connectStore} from 'reducers/base.js';
11
12/**
13 * `<mr-issue-hotlists-dialog>`
14 *
15 * The base dialog that <mr-move-issue-hotlists-dialog> and
16 * <mr-update-issue-hotlists-dialog> inherits common methods and behaviors from.
17 * <mr-update-issue-hotlists-dialog> is used across multiple pages where as
18 * <mr-move-issue-hotlists-dialog> is largely used within Hotlists.
19 *
20 * Important: The `render` method should be overridden by child classes.
21 */
22export class MrIssueHotlistsDialog extends connectStore(LitElement) {
23 /** @override */
24 static get styles() {
25 return [
26 SHARED_STYLES,
27 css`
28 :host {
29 font-size: var(--chops-main-font-size);
30 --chops-dialog-max-width: 500px;
31 }
32 .error {
33 max-width: 100%;
34 color: red;
35 margin-bottom: 1px;
36 }
37 select,
38 input {
39 box-sizing: border-box;
40 width: var(--mr-edit-field-width);
41 padding: var(--mr-edit-field-padding);
42 font-size: var(--chops-main-font-size);
43 }
44 input#filter {
45 margin-top: 4px;
46 width: 85%;
47 max-width: 240px;
48 }
49 .user-hotlists {
50 max-height: 240px;
51 overflow: auto;
52 }
53 .hotlist.filter-fail {
54 display: none;
55 }
56 i.material-icons {
57 font-size: 20px;
58 margin-right: 4px;
59 vertical-align: bottom;
60 }
61 `,
62 ];
63 }
64
65 /** @override */
66 render() {
67 return html`
68 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
69 <chops-dialog closeOnOutsideClick>
70 ${this.renderHeader()}
71 ${this.renderContent()}
72 </chops-dialog>
73 `;
74 }
75
76 /**
77 * Renders the dialog header.
78 * @return {TemplateResult}
79 */
80 renderHeader() {
81 return html`
82 <h3 class="medium-heading">Dialog elements below:</h3>
83 `;
84 }
85
86 /**
87 * Renders the dialog content.
88 * @return {TemplateResult}
89 */
90 renderContent() {
91 return html`
92 ${this.renderFilter()}
93 ${this.renderHotlists()}
94 ${this.renderError()}
95 `;
96 }
97
98 /**
99 * Renders the Hotlist filter.
100 * @return {TemplateResult}
101 */
102 renderFilter() {
103 return html`
104 <input id="filter" type="text" @keyup=${this.filterHotlists}>
105 <i class="material-icons">search</i>
106 `;
107 }
108
109 /**
110 * Renders the user's Hotlists.
111 * @return {TemplateResult}
112 */
113 renderHotlists() {
114 return html`
115 <div class="user-hotlists">
116 ${this.filteredHotlists.length ?
117 this.filteredHotlists.map(this.renderFilteredHotlist, this) : ''}
118 </div>
119 `;
120 }
121
122 /**
123 * Renders a user's filtered Hotlist.
124 * @param {HotlistV0} hotlist The user Hotlist to render.
125 * @return {TemplateResult}
126 */
127 renderFilteredHotlist(hotlist) {
128 return html`
129 <div
130 class="hotlist"
131 data-hotlist-name="${hotlist.name}"
132 >
133 ${hotlist.name}
134 </div>`;
135 }
136
137 /**
138 * Renders dialog error.
139 * @return {TemplateResult}
140 */
141 renderError() {
142 return html`
143 <br>
144 ${this.error ? html`
145 <div class="error">${this.error}</div>
146 `: ''}
147 `;
148 }
149
150 /** @override */
151 static get properties() {
152 return {
153 // Populated from Redux.
154 userHotlists: {type: Array},
155 filteredHotlists: {type: Array},
156 issueRefs: {type: Array},
157 error: {type: String},
158 };
159 }
160
161 /** @override */
162 stateChanged(state) {
163 this.userHotlists = userV0.currentUser(state).hotlists;
164 // TODO(https://crbug.com/monorail/7778): Switch to users.js and use V3 API
165 // to make a call to GatherHotlistsForUser.
166 }
167
168 /** @override */
169 constructor() {
170 super();
171
172 /** @type {Array} */
173 this.userHotlists = [];
174
175 /** @type {Array} */
176 this.filteredHotlists = this.userHotlists;
177
178 /** @type {Array<IssueRef>} */
179 this.issueRefs = [];
180
181 /** @type {string} */
182 this.error = '';
183 }
184
185 /**
186 * Opens the dialog.
187 */
188 open() {
189 this.reset();
190 this.shadowRoot.querySelector('chops-dialog').open();
191 }
192
193 /**
194 * Resets any changes to the form and error.
195 */
196 reset() {
197 this.error = '';
198 const filter = this.shadowRoot.querySelector('#filter');
199 filter.value = '';
200 this.filterHotlists();
201 }
202
203 /**
204 * Closes the dialog.
205 */
206 close() {
207 this.shadowRoot.querySelector('chops-dialog').close();
208 }
209
210 /**
211 * Filters the visible Hotlists with the given user input.
212 * Requires filter to be an input element with its id as "filter".
213 */
214 filterHotlists() {
215 const input = this.shadowRoot.querySelector('#filter');
216 if (!input) {
217 // Short circuit because there's no filter.
218 this.filteredHotlists = this.userHotlists;
219 } else {
220 const filter = input.value.toLowerCase();
221 const visibleHotlists = [];
222 this.userHotlists.forEach((hotlist) => {
223 const hotlistName = hotlist.name.toLowerCase();
224 if (hotlistName.includes(filter)) {
225 visibleHotlists.push(hotlist);
226 }
227 });
228 this.filteredHotlists = visibleHotlists;
229 }
230 }
231}
232
233customElements.define('mr-issue-hotlists-dialog', MrIssueHotlistsDialog);