Skip to content

Commit dcfbb35

Browse files
committed
feat: placeholders on the index page
1 parent f368ba2 commit dcfbb35

File tree

9 files changed

+152
-116
lines changed

9 files changed

+152
-116
lines changed

src/app/donate/vod/vod.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ <h1 i18n>3. Preview</h1>
9999
</div>
100100
</div>
101101

102-
<app-item-of-day *ngIf="item$ | async as item" [item]="item" [user]="itemOfDayUser$ | async"></app-item-of-day>
102+
<app-item-of-day [item$]="item$" [user$]="itemOfDayUser$" *ngIf="itemSelected$ | async"></app-item-of-day>
103103

104104
<h1 i18n>4. Make a donation</h1>
105105

src/app/donate/vod/vod.component.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {ActivatedRoute} from '@angular/router';
44
import {combineLatest, Observable, of} from 'rxjs';
55
import {AuthService} from '../../services/auth.service';
66
import {PageEnvService} from '../../services/page-env.service';
7-
import {debounceTime, distinctUntilChanged, switchMap, map, shareReplay} from 'rxjs/operators';
7+
import {distinctUntilChanged, switchMap, map, shareReplay} from 'rxjs/operators';
88
import {DonateService} from '../donate.service';
99
import {APIUser} from '@grpc/spec.pb';
1010
import {formatDate} from '@angular/common';
@@ -21,8 +21,7 @@ export class DonateVodComponent implements OnInit {
2121
public anonymous$ = combineLatest([
2222
this.route.queryParamMap.pipe(
2323
map((params) => params.get('anonymous')),
24-
distinctUntilChanged(),
25-
debounceTime(10)
24+
distinctUntilChanged()
2625
),
2726
this.user$,
2827
]).pipe(
@@ -33,16 +32,16 @@ export class DonateVodComponent implements OnInit {
3332
public date$ = this.route.queryParamMap.pipe(
3433
map((params) => params.get('date')),
3534
map((date) => (date ? date : null)),
36-
distinctUntilChanged(),
37-
debounceTime(10)
35+
distinctUntilChanged()
3836
);
3937

4038
private itemID$ = this.route.queryParamMap.pipe(
4139
map((params) => parseInt(params.get('item_id'), 10)),
42-
distinctUntilChanged(),
43-
debounceTime(10)
40+
distinctUntilChanged()
4441
);
4542

43+
public itemSelected$ = this.itemID$.pipe(map((itemID) => !!itemID));
44+
4645
public item$ = this.itemID$.pipe(
4746
switchMap((itemID) => {
4847
if (!itemID) {

src/app/index/brands/brands.component.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ <h4>
33
<small><a routerLink="/brands" i18n>all</a></small>
44
</h4>
55

6-
<ng-template #loading>
7-
<div class="spinner-border" role="status"><span class="visually-hidden" i18n>Loading…</span></div>
6+
{{ placeholderItems | json }}
7+
8+
<ng-template #placeholder>
9+
<p class="index-brands placeholder-glow">
10+
<span class="placeholder me-1" [style.width]="item + 'rem'" *ngFor="let item of placeholderItems"></span>
11+
</p>
812
</ng-template>
913

10-
<p class="index-brands" *ngIf="result$ | async as result; else loading">
14+
<p class="index-brands" *ngIf="result$ | async as result; else placeholder">
1115
<app-index-brands-brand [brand]="brand" *ngFor="let brand of result.brands"></app-index-brands-brand>
1216
<ng-container *ngIf="result.more > 0">
1317
<ng-container i18n>… and</ng-container>&#xa0;<a routerLink="/brands" i18n="@@more-n-companies"

src/app/index/brands/brands.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {map} from 'rxjs/operators';
1212
export class IndexBrandsComponent {
1313
constructor(private items: ItemsClient, private languageService: LanguageService) {}
1414

15+
public placeholderItems = Array.from({length: 60}, () => Math.round(3 + Math.random() * 5));
16+
1517
public result$ = this.items
1618
.getTopBrandsList(new GetTopBrandsListRequest({language: this.languageService.language}))
1719
.pipe(

src/app/index/index.component.html

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
<ng-template #loading xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
2-
<div class="spinner-border" role="status"><span class="visually-hidden" i18n>Loading…</span></div>
3-
</ng-template>
4-
<app-item-of-day
5-
class="d-block mb-4"
6-
[item]="itemOfDay[1].item"
7-
[user]="itemOfDay[0]"
8-
*ngIf="itemOfDay$ | async as itemOfDay; else loading"
9-
></app-item-of-day>
1+
<app-item-of-day class="d-block mb-4" [item$]="itemOfDayItem$" [user$]="itemOfDayUser$"></app-item-of-day>
102

113
<app-index-donate></app-index-donate>
124

src/app/index/index.component.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import {Component, OnInit} from '@angular/core';
22
import {PageEnvService} from '../services/page-env.service';
3-
import {APIItem} from '../services/item';
3+
import {APIItem, ItemOfDayItem} from '../services/item';
44
import {APIService} from '../services/api.service';
55
import {ItemsClient, UsersClient} from '@grpc/spec.pbsc';
6-
import {APIGetUserRequest, GetTopPersonsListRequest, PictureItemType} from '@grpc/spec.pb';
6+
import {APIGetUserRequest, APIUser, GetTopPersonsListRequest, PictureItemType} from '@grpc/spec.pb';
77
import {LanguageService} from '../services/language';
8-
import {switchMap} from 'rxjs/operators';
9-
import {combineLatest, of} from 'rxjs';
8+
import {map, shareReplay, switchMap} from 'rxjs/operators';
9+
import {combineLatest, Observable, of} from 'rxjs';
1010

1111
interface APIIndexItemOfDay {
1212
user_id: string;
13-
item: APIItem;
13+
item: ItemOfDayItem;
1414
}
1515

1616
@Component({
@@ -36,17 +36,22 @@ export class IndexComponent implements OnInit {
3636
name: $localize`Most heavy trucks`,
3737
},
3838
];
39-
public itemOfDay$ = this.api
39+
public itemOfDay$: Observable<{user: APIUser; item: APIItem}> = this.api
4040
.request<APIIndexItemOfDay>('GET', 'index/item-of-day')
4141
.pipe(
4242
switchMap((itemOfDay) =>
4343
combineLatest([
4444
itemOfDay.user_id ? this.usersClient.getUser(new APIGetUserRequest({userId: itemOfDay.user_id})) : of(null),
45-
of(itemOfDay),
45+
of(itemOfDay.item),
4646
])
47-
)
47+
),
48+
map(([user, item]) => ({user, item})),
49+
shareReplay(1)
4850
);
4951

52+
public itemOfDayItem$ = this.itemOfDay$.pipe(map((itemOfDay) => itemOfDay.item));
53+
public itemOfDayUser$ = this.itemOfDay$.pipe(map((itemOfDay) => itemOfDay.user));
54+
5055
public contentPersons$ = this.items.getTopPersonsList(
5156
new GetTopPersonsListRequest({
5257
language: this.languageService.language,
Lines changed: 94 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
1-
<ng-container *ngIf="item$ | async as item">
2-
<h2>
3-
<span [innerHtml]="item.name_html"></span>
1+
<h2 *ngIf="item$ | async as item; else h2Placeholder">
2+
<span [innerHtml]="item.name_html"></span>
3+
<small>
4+
<span i18n *ngIf="item.item_type_id === 1">vehicle of the day</span>
5+
<span i18n *ngIf="item.item_type_id !== 1">theme of the day</span>
6+
<span *ngIf="user$ | async as user">
7+
<span class="text-nowrap">by <app-user2 [user]="user"></app-user2></span>
8+
</span>
9+
</small>
10+
</h2>
11+
12+
<ng-template #h2Placeholder>
13+
<h2 class="placeholder-glow">
14+
<span class="placeholder w-25"></span>
415
<small>
5-
<span i18n *ngIf="item.item_type_id === 1">vehicle of the day</span>
6-
<span i18n *ngIf="item.item_type_id !== 1">theme of the day</span>
7-
<span *ngIf="user">
8-
<span style="white-space: nowrap">by <app-user2 [user]="user"></app-user2></span>
9-
</span>
16+
<span class="placeholder" style="width: 10%"></span>
1017
</small>
1118
</h2>
19+
</ng-template>
1220

13-
<div class="row" *ngIf="itemOfDayPictures$ | async as pictures">
14-
<div class="col-sm-6">
15-
<ng-container *ngFor="let picture of pictures.first">
16-
<a [routerLink]="picture.route" class="d-block rounded mb-4" *ngIf="picture">
21+
<div class="row" *ngIf="itemOfDayPictures$ | async as pictures; else placeholder">
22+
<div class="col-sm-6">
23+
<ng-container *ngFor="let picture of pictures.first">
24+
<a [routerLink]="picture.route" class="d-block rounded mb-4" *ngIf="picture">
25+
<img
26+
[src]="picture.thumb.src"
27+
[alt]="picture.name"
28+
[title]="picture.name"
29+
class="rounded w-100 border border-light"
30+
/>
31+
</a>
32+
</ng-container>
33+
</div>
34+
<div class="col-sm-6">
35+
<div class="row">
36+
<ng-container *ngFor="let picture of pictures.others">
37+
<a [routerLink]="picture.route" class="d-block rounded mb-3 col-6" *ngIf="picture">
1738
<img
1839
[src]="picture.thumb.src"
1940
[alt]="picture.name"
@@ -23,71 +44,80 @@ <h2>
2344
</a>
2445
</ng-container>
2546
</div>
47+
</div>
48+
</div>
49+
50+
<ng-template #placeholder>
51+
<div class="row placeholder-glow">
52+
<div class="col-sm-6">
53+
<div class="rounded ratio ratio-4x3 border border-light placeholder d-block rounded mb-4"></div>
54+
</div>
2655
<div class="col-sm-6">
2756
<div class="row">
28-
<ng-container *ngFor="let picture of pictures.others">
29-
<a [routerLink]="picture.route" class="d-block rounded mb-3 col-6" *ngIf="picture">
30-
<img
31-
[src]="picture.thumb.src"
32-
[alt]="picture.name"
33-
[title]="picture.name"
34-
class="rounded w-100 border border-light"
35-
/>
36-
</a>
37-
</ng-container>
57+
<div class="d-block rounded mb-3 col-6">
58+
<div class="rounded ratio ratio-4x3 border border-light placeholder"></div>
59+
</div>
60+
<div class="d-block rounded mb-3 col-6">
61+
<div class="rounded ratio ratio-4x3 border border-light placeholder"></div>
62+
</div>
63+
<div class="d-block rounded mb-3 col-6">
64+
<div class="rounded ratio ratio-4x3 border border-light placeholder"></div>
65+
</div>
66+
<div class="d-block rounded mb-3 col-6">
67+
<div class="rounded ratio ratio-4x3 border border-light placeholder"></div>
68+
</div>
3869
</div>
3970
</div>
4071
</div>
41-
<p>
42-
<ng-container *ngIf="item.item_type_id === 3">
43-
<span>
44-
<span class="bi bi-text-left" aria-hidden="true"></span>
45-
<a [routerLink]="['/category', item.catname]" i18n>details</a>
46-
</span>
72+
</ng-template>
4773

48-
<span *ngIf="item.accepted_pictures_count > 6">
49-
<span class="bi bi-grid-3x2-gap-fill" aria-hidden="true"></span>
50-
<a [routerLink]="['/category', item.catname, '/pictures']"
51-
><ng-container i18n>all pictures</ng-container> ({{ item.accepted_pictures_count }})</a
52-
>
53-
</span>
54-
</ng-container>
55-
56-
<span *ngIf="item.item_type_id === 5 && item.accepted_pictures_count > 6 && item.public_route">
57-
<span class="bi bi-grid-3x2-gap-fill" aria-hidden="true"></span>
58-
<a [routerLink]="item.public_route"
59-
><ng-container i18n>details</ng-container> ({{ item.accepted_pictures_count }})</a
60-
>
74+
<p *ngIf="item$ | async as item">
75+
<ng-container *ngIf="item.item_type_id === 3">
76+
<span>
77+
<span class="bi bi-text-left" aria-hidden="true"></span>
78+
<a [routerLink]="['/category', item.catname]" i18n>details</a>
6179
</span>
6280

63-
<span
64-
*ngIf="
65-
item.item_type_id !== 3 && item.item_type_id !== 5 && item.accepted_pictures_count > 6 && item.public_route
66-
"
67-
>
81+
<span *ngIf="item.accepted_pictures_count > 6">
6882
<span class="bi bi-grid-3x2-gap-fill" aria-hidden="true"></span>
69-
<a [routerLink]="item.public_route"
83+
<a [routerLink]="['/category', item.catname, '/pictures']"
7084
><ng-container i18n>all pictures</ng-container> ({{ item.accepted_pictures_count }})</a
7185
>
7286
</span>
87+
</ng-container>
7388

74-
<ng-container *ngIf="item.twins_groups">
75-
<span *ngFor="let group of item.twins_groups">
76-
<span class="bi bi-circle-half" aria-hidden="true"></span>
77-
<a [routerLink]="['/twins/group', group.id]" i18n>twins</a>
78-
</span>
79-
</ng-container>
89+
<span *ngIf="item.item_type_id === 5 && item.accepted_pictures_count > 6 && item.public_route">
90+
<span class="bi bi-grid-3x2-gap-fill" aria-hidden="true"></span>
91+
<a [routerLink]="item.public_route"
92+
><ng-container i18n>details</ng-container> ({{ item.accepted_pictures_count }})</a
93+
>
94+
</span>
8095

81-
<ng-container *ngIf="item.categories">
82-
<span *ngFor="let category of item.categories">
83-
<span class="bi bi-tag-fill" aria-hidden="true"></span>
84-
<a [routerLink]="['/category', category.catname]" [innerHTML]="category.name_html"></a>
85-
</span>
86-
</ng-container>
96+
<span
97+
*ngIf="item.item_type_id !== 3 && item.item_type_id !== 5 && item.accepted_pictures_count > 6 && item.public_route"
98+
>
99+
<span class="bi bi-grid-3x2-gap-fill" aria-hidden="true"></span>
100+
<a [routerLink]="item.public_route"
101+
><ng-container i18n>all pictures</ng-container> ({{ item.accepted_pictures_count }})</a
102+
>
103+
</span>
87104

88-
<span>
89-
<span class="bi bi-trophy" aria-hidden="true"></span>
90-
<a routerLink="/donate/vod" i18n>Want to choose next?</a>
105+
<ng-container *ngIf="item.twins_groups">
106+
<span *ngFor="let group of item.twins_groups">
107+
<span class="bi bi-circle-half" aria-hidden="true"></span>
108+
<a [routerLink]="['/twins/group', group.id]" i18n>twins</a>
91109
</span>
92-
</p>
93-
</ng-container>
110+
</ng-container>
111+
112+
<ng-container *ngIf="item.categories">
113+
<span *ngFor="let category of item.categories">
114+
<span class="bi bi-tag-fill" aria-hidden="true"></span>
115+
<a [routerLink]="['/category', category.catname]" [innerHTML]="category.name_html"></a>
116+
</span>
117+
</ng-container>
118+
119+
<span>
120+
<span class="bi bi-trophy" aria-hidden="true"></span>
121+
<a routerLink="/donate/vod" i18n>Want to choose next?</a>
122+
</span>
123+
</p>
Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
11
import {Component, Input} from '@angular/core';
2-
import {APIItem, APIItemOfDayPicture} from '../../services/item';
2+
import {APIItemOfDayPicture, ItemOfDayItem} from '../../services/item';
33
import {APIUser} from '@grpc/spec.pb';
4-
import {BehaviorSubject} from 'rxjs';
4+
import {Observable} from 'rxjs';
55
import {map} from 'rxjs/operators';
66

7-
interface ItemOfDayItem extends APIItem {
8-
public_route?: string;
9-
}
10-
117
@Component({
128
selector: 'app-item-of-day',
139
templateUrl: './item-of-day.component.html',
1410
styleUrls: ['./item-of-day.component.scss'],
1511
})
1612
export class ItemOfDayComponent {
17-
@Input() set item(item: ItemOfDayItem) {
18-
this.item$.next(item);
19-
}
20-
public item$ = new BehaviorSubject<ItemOfDayItem>(null);
21-
22-
@Input() user: APIUser;
13+
private _item$: Observable<ItemOfDayItem>;
2314

24-
public itemOfDayPictures$ = this.item$.pipe(
25-
map((item) => {
26-
if (!item) {
15+
@Input() public set item$(item$: Observable<ItemOfDayItem>) {
16+
this._item$ = item$;
17+
this.itemOfDayPictures$ = item$.pipe(
18+
map((item) => {
19+
if (!item) {
20+
return null;
21+
}
2722
return {
28-
first: [] as APIItemOfDayPicture[],
29-
others: [] as APIItemOfDayPicture[],
23+
first: item.item_of_day_pictures.slice(0, 1),
24+
others: item.item_of_day_pictures.slice(1, 5),
3025
};
31-
}
32-
return {
33-
first: item.item_of_day_pictures.slice(0, 1),
34-
others: item.item_of_day_pictures.slice(1, 5),
35-
};
36-
})
37-
);
26+
})
27+
);
28+
}
29+
public get item$() {
30+
return this._item$;
31+
}
32+
@Input() public user$: Observable<APIUser>;
33+
34+
public itemOfDayPictures$: Observable<{
35+
first: APIItemOfDayPicture[];
36+
others: APIItemOfDayPicture[];
37+
} | null>;
3838
}

src/app/services/item.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ export interface APIItem {
143143
other_names?: string[];
144144
}
145145

146+
export interface ItemOfDayItem extends APIItem {
147+
public_route?: string;
148+
}
149+
146150
export interface APIItemRelatedGroupItem {
147151
name: string;
148152
src: string;

0 commit comments

Comments
 (0)