feat(data): show skeleton screens before data is loaded

Closes #4
This commit is contained in:
Jovan Krunić
2019-04-24 09:11:24 +02:00
committed by Sebastian Lange
parent 88f87a2ce1
commit e1039aa226
10 changed files with 102 additions and 39 deletions

View File

@@ -17,7 +17,7 @@ import {HTTP_INTERCEPTORS, HttpClient,
import {Injectable} from '@angular/core';
import {SCIndexResponse, SCThingType} from '@openstapps/core';
import {Observable, of} from 'rxjs';
import {map} from 'rxjs/operators';
import {map, delay} from 'rxjs/operators';
import {SampleThings} from './data/sample-things';
const sampleIndexResponse: SCIndexResponse = {
@@ -150,12 +150,12 @@ export class FakeBackendInterceptor implements HttpInterceptor {
return this.sampleFetcher.getSampleThing(request.body.filter.arguments.value)
.pipe(map((sampleData: any) => {
return new HttpResponse({status: 200, body: {data: sampleData}});
}));
}), delay(1000)); // add delay for skeleton screens to be seen (see !16)
}
}
return this.sampleFetcher.getSampleThings().pipe(map((sampleData: any) => {
return new HttpResponse({status: 200, body: {data: sampleData}});
}));
}), delay(1000)); // add delay for skeleton screens to be seen (see !16)
}
}
return next.handle(request);

View File

@@ -60,6 +60,8 @@ import {SemesterDetailContentComponent} from './types/semester/semester-detail-c
import {SemesterListItem} from './types/semester/semester-list-item.component';
import {VideoDetailContentComponent} from './types/video/video-detail-content.component';
import {VideoListItem} from './types/video/video-list-item.component';
import {SkeletonListItem} from './elements/skeleton-list-item.component';
import {SkeletonSimpleCard} from './elements/skeleton-simple-card.component';
@NgModule({
declarations: [
@@ -69,6 +71,7 @@ import {VideoListItem} from './types/video/video-list-item.component';
ArticleDetailContentComponent,
ArticleListItem,
SimpleCardComponent,
SkeletonSimpleCard,
CatalogDetailContentComponent,
CatalogListItem,
DataDetailComponent,
@@ -96,6 +99,7 @@ import {VideoListItem} from './types/video/video-list-item.component';
PlaceListItem,
SemesterDetailContentComponent,
SemesterListItem,
SkeletonListItem,
VideoDetailContentComponent,
VideoListItem,
],

View File

@@ -28,36 +28,37 @@ export class DataDetailComponent {
dataProvider: DataProvider;
item: SCThing;
language: SCLanguageCode;
constructor(private route: ActivatedRoute, dataProvider: DataProvider, translateService: TranslateService) {
this.dataProvider = dataProvider;
this.language = translateService.currentLang as SCLanguageCode;
// alert(translateService.currentLang);
translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.language = event.lang as SCLanguageCode;
});
}
/**
* Provides data item with given UID
*
* @param uid Unique identifier of a thing
*/
async getItem(uid: SCUuid): Promise<SCThing> {
return (await this.dataProvider.get(uid, DataScope.Remote));
async getItem(uid: SCUuid): Promise<void> {
this.dataProvider.get(uid, DataScope.Remote).then((data) => {
this.item = data;
});
}
async ngOnInit() {
this.item = await this.getItem(this.route.snapshot.paramMap.get('uid') || '');
ngOnInit() {
this.getItem(this.route.snapshot.paramMap.get('uid') || '');
}
/**
* Updates the shown thing
*
* @param refresher Refresher component the triggers the update
* @param refresher Refresher component that triggers the update
*/
async refresh(refresher: IonRefresher) {
this.item = await this.getItem(this.item.uid);
await this.getItem(this.item.uid);
refresher.complete();
}
}

View File

@@ -7,12 +7,18 @@
<ion-title text-center>{{'data.detail.TITLE' | translate}}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding *ngIf="item">
<ion-content padding>
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
<ion-refresher-content pullingIcon="arrow-dropdown" pullingText="{{'data.REFRESH_ACTION' | translate}}"
refreshingText="{{'data.REFRESHING' | translate}}">
</ion-refresher-content>
</ion-refresher>
<stapps-data-list-item [item]="item"></stapps-data-list-item>
<stapps-data-detail-content [item]="item"></stapps-data-detail-content>
<ng-container *ngIf="!item">
<stapps-skeleton-list-item></stapps-skeleton-list-item>
<stapps-skeleton-simple-card></stapps-skeleton-simple-card>
</ng-container>
<ng-container *ngIf="item">
<stapps-data-list-item [item]="item"></stapps-data-list-item>
<stapps-data-detail-content [item]="item"></stapps-data-detail-content>
</ng-container>
</ion-content>

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component} from '@angular/core';
@Component({
selector: 'stapps-skeleton-list-item',
templateUrl: 'skeleton-list-item.html',
})
export class SkeletonListItem {
}

View File

@@ -0,0 +1,14 @@
<ion-item>
<ion-thumbnail slot="start">
<ion-skeleton-text animated></ion-skeleton-text>
</ion-thumbnail>
<ion-grid>
<ion-row>
<ion-col>
<h2 class="name"><ion-skeleton-text animated style="width: 80%"></ion-skeleton-text></h2>
<p><ion-skeleton-text animated style="width: 80%;"></ion-skeleton-text></p>
<ion-note><ion-skeleton-text animated style="width: 20%"></ion-skeleton-text></ion-note>
</ion-col>
</ion-row>
</ion-grid>
</ion-item>

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component} from '@angular/core';
@Component({
selector: 'stapps-skeleton-simple-card',
templateUrl: 'skeleton-simple-card.html',
})
export class SkeletonSimpleCard {
}

View File

@@ -0,0 +1,8 @@
<ion-card>
<ion-card-header>
<ion-skeleton-text animated style="width: 15%"></ion-skeleton-text>
</ion-card-header>
<ion-card-content>
<p><ion-skeleton-text animated style="width: 85%;"></ion-skeleton-text></p>
</ion-card-content>
</ion-card>

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component} from '@angular/core';
import {AlertController, LoadingController} from '@ionic/angular';
import {AlertController} from '@ionic/angular';
import {SCThing} from '@openstapps/core';
import {Subject} from 'rxjs';
import {debounceTime, distinctUntilChanged} from 'rxjs/operators';
@@ -25,8 +25,9 @@ import {DataProvider} from '../data.provider';
})
export class DataListComponent {
dataProvider: DataProvider;
items: SCThing[] = [];
items: SCThing[];
selectedItem: any;
loaded: boolean = false;
size: number = 30;
from: number = 0;
@@ -34,10 +35,7 @@ export class DataListComponent {
query: string;
queryChanged: Subject<string> = new Subject<string>();
loading: HTMLIonLoadingElement;
constructor(
private loadingController: LoadingController,
private alertController: AlertController,
dataProvider: DataProvider,
) {
@@ -49,31 +47,19 @@ export class DataListComponent {
.subscribe((model) => {
this.from = 0;
this.query = model;
this.items = [];
this.fetchItems();
});
this.fetchItems();
}
private async fetchItems(): Promise<any> {
if (this.from === 0) {
this.loading = await this.loadingController.create();
await this.loading.present();
}
return this.dataProvider.search({
from: this.from,
query: this.query,
size: this.size,
} as any).then((result) => {
result.data.forEach((item) => {
this.items.push(item);
});
if (this.from === 0) {
this.loading.dismiss();
}
this.items = result.data;
this.loaded = true;
}, async (err) => {
const alert: HTMLIonAlertElement = await this.alertController.create({
buttons: ['Dismiss'],
@@ -82,8 +68,6 @@ export class DataListComponent {
});
await alert.present();
await this.loading.dismiss();
});
}

View File

@@ -12,10 +12,12 @@
</ion-header>
<ion-content>
<ion-list *ngFor="let item of items">
<stapps-data-list-item [item]="item"></stapps-data-list-item>
<ion-list *ngIf="items">
<stapps-data-list-item [item]="item" *ngFor="let item of items"></stapps-data-list-item>
</ion-list>
<ion-list *ngIf="!items">
<stapps-skeleton-list-item *ngFor="let skeleton of [1, 2, 3, 4, 5]"></stapps-skeleton-list-item>
</ion-list>
<ion-infinite-scroll (ionInfinite)="loadMore($event)">
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>