Skip to content

Commit ea67cff

Browse files
committed
Add a loader in the author list
Signed-off-by: Jay Wang <[email protected]>
1 parent d7170d8 commit ea67cff

File tree

3 files changed

+74
-15
lines changed

3 files changed

+74
-15
lines changed

src/components/author-list/author-list.ts

+30-15
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import iconPerson from '../../images/icon-person.svg?raw';
1212
import componentCSS from './author-list.css?inline';
1313

1414
const AUTHORS_PER_PAGE = 100;
15+
let authorDetailAPITimeout: number | null = null;
1516

1617
/**
1718
* Author list element.
@@ -28,7 +29,6 @@ export class RecRecAuthorList extends LitElement {
2829
authorDetails: SemanticAuthorDetail[] = [];
2930

3031
authorStartIndex = 0;
31-
authorDetailAPIRetryID = 0;
3232

3333
//==========================================================================||
3434
// Lifecycle Methods ||
@@ -81,35 +81,50 @@ export class RecRecAuthorList extends LitElement {
8181
//==========================================================================||
8282
// Private Helpers ||
8383
//==========================================================================||
84-
async updateAuthorDetails(retry?: number, retryID?: number) {
84+
async updateAuthorDetails(retry = 3) {
8585
if (this.authors.length === 0) {
8686
this.authorDetails = [];
8787
return;
8888
}
8989

90-
const retryNum = retry || 3;
90+
this.updateIsSearching(true);
9191

92-
// Quit if a newer query has succeeded
93-
if (retryID && retryID != this.authorDetailAPIRetryID) {
94-
return;
95-
}
96-
97-
// Record this query as the newest query
98-
if (retryID === undefined) {
99-
this.authorDetailAPIRetryID += 1;
92+
if (authorDetailAPITimeout !== null) {
93+
clearTimeout(authorDetailAPITimeout);
10094
}
10195

10296
try {
10397
const data = await searchAuthorDetails(this.authors.map(d => d.authorId));
10498
this.authorDetails = data;
99+
this.updateIsSearching(false);
105100
} catch (e) {
106-
await new Promise<void>(resolve => {
107-
setTimeout(resolve, 1000);
108-
});
109-
await this.updateAuthorDetails(retryNum - 1, this.authorDetailAPIRetryID);
101+
// Try again after some delay
102+
console.error(
103+
`Author detail API failed with error (retry remain: ${retry}): ${e}`
104+
);
105+
106+
if (retry > 0) {
107+
authorDetailAPITimeout = setTimeout(() => {
108+
this.updateAuthorDetails(retry - 1).then(
109+
() => {},
110+
() => {}
111+
);
112+
}, 1000);
113+
} else {
114+
this.updateIsSearching(false);
115+
}
110116
}
111117
}
112118

119+
updateIsSearching(isSearching: boolean) {
120+
const event = new CustomEvent<boolean>('is-searching-changed', {
121+
bubbles: true,
122+
composed: true,
123+
detail: isSearching
124+
});
125+
this.dispatchEvent(event);
126+
}
127+
113128
//==========================================================================||
114129
// Templates and Styles ||
115130
//==========================================================================||

src/components/author-view/author-view.css

+31
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,34 @@ button {
111111
}
112112
}
113113
}
114+
115+
.loader {
116+
--stroke: 2px;
117+
118+
width: 128px;
119+
aspect-ratio: 1;
120+
border-radius: 50%;
121+
122+
background:
123+
radial-gradient(farthest-side, var(--blue-700) 94%, #0000) top/var(--stroke)
124+
var(--stroke) no-repeat,
125+
conic-gradient(#0000 30%, var(--blue-700));
126+
127+
-webkit-mask: radial-gradient(
128+
farthest-side,
129+
#0000 calc(100% - var(--stroke)),
130+
#000 0
131+
);
132+
animation: rotation 1200ms infinite linear;
133+
134+
&[is-hidden] {
135+
visibility: hidden;
136+
pointer-events: none;
137+
}
138+
}
139+
140+
@keyframes rotation {
141+
100% {
142+
transform: rotate(1turn);
143+
}
144+
}

src/components/author-view/author-view.ts

+13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
SemanticAuthorSearch,
99
SemanticAuthorDetail
1010
} from '../../types/common-types';
11+
import type { RecRecAuthorList } from '../author-list/author-list';
1112

1213
import '@shoelace-style/shoelace/dist/components/input/input.js';
1314
import '../header-bar/header-bar';
@@ -35,9 +36,15 @@ export class RecRecAuthorView extends LitElement {
3536
@state()
3637
showAuthorList = false;
3738

39+
@state()
40+
isSearching = false;
41+
3842
@query('sl-input')
3943
searchInputComponent: SlInput | undefined;
4044

45+
@query('recrec-author-list')
46+
authorListComponent: RecRecAuthorList | undefined;
47+
4148
//==========================================================================||
4249
// Lifecycle Methods ||
4350
//==========================================================================||
@@ -177,6 +184,9 @@ export class RecRecAuthorView extends LitElement {
177184
<div class="svg-icon search-icon" slot="prefix">
178185
${unsafeHTML(iconSearch)}
179186
</div>
187+
<div class="svg-icon search-icon" slot="suffix">
188+
<div class="loader" ?is-hidden=${!this.isSearching}></div>
189+
</div>
180190
</sl-input>
181191
</div>
182192
@@ -186,6 +196,9 @@ export class RecRecAuthorView extends LitElement {
186196
@author-row-clicked=${(e: CustomEvent<SemanticAuthorDetail>) => {
187197
this.authorRowClickedHandler(e);
188198
}}
199+
@is-searching-changed=${(e: CustomEvent<boolean>) => {
200+
this.isSearching = e.detail;
201+
}}
189202
></recrec-author-list>
190203
</div>
191204
</div>

0 commit comments

Comments
 (0)