Commit 5c87a012 authored by Kevin Linne's avatar Kevin Linne

Merge branch '64-implement-loading-indicator' into 'develop'

Resolve "Implement loading indicator"

Closes #64

See merge request !78
parents 1cfa8f45 ab025331
Pipeline #17530 passed with stages
in 13 minutes and 28 seconds
......@@ -10,6 +10,9 @@
type="text"
[(ngModel)]="this.searchManager.searchTerm">
<div class="row mt-3">
<div class="col-6" *ngIf="loading">
<span class="fa fa-spinner"></span>
</div>
<div class="col-6" *ngFor="let model of searchManager.getFilteredData(selectionList)">
<ng-container *ngIf="!shouldHideObject(model)">
<a href="javascript:" (click)="addButtonClicked(model)">
......
......@@ -24,17 +24,27 @@ export class MultiModelInputComponent implements OnInit {
listSelectionExpanded: boolean;
constructor(private genericDataService: GenericDataService) {
loading: boolean;
constructor(public genericDataService: GenericDataService) {
this.listSelectionExpanded = false;
this.searchManager = new SearchManager();
this.selectionList = [];
this.typeAheadManager = new TypeAheadManager<string>(() => {
return this.selectionList.map((m) => m.toString());
});
this.loading = false;
}
async ngOnInit(): Promise<void> {
this.selectionList = await this.genericDataService.get(this.modelDefinition);
this.loading = true;
try {
this.selectionList = await this.genericDataService.get(this.modelDefinition);
this.loading = false;
} catch (err) {
this.loading = false;
return Promise.reject(err);
}
}
removeButtonClicked(index: number): void {
......
<div *ngIf="loading">
<span class="fa fa-spinner"></span>
</div>
<div ngbDropdown class="d-inline-block" *ngIf="model">
<button class="btn btn-outline-primary" id="dropdownBasic1" ngbDropdownToggle>
{{model.toString()}}
......
......@@ -15,16 +15,23 @@ export class SingleModelInputComponent implements OnInit {
@Input() modelDefinition: IModelDefinition<IModel>;
availableObjects: IModel[];
loading: boolean;
constructor(private genericDataLoader: GenericDataService) {
this.availableObjects = [];
this.loading = false;
}
async ngOnInit() {
this.availableObjects = await this.genericDataLoader.get(this.modelDefinition);
if (!this.model || !this.availableObjects.map((val) => val.getID()).includes(this.model.getID())) {
this.setObject(this.availableObjects[0]);
this.loading = true;
try {
this.availableObjects = await this.genericDataLoader.get(this.modelDefinition);
if (!this.model || !this.availableObjects.map((val) => val.getID()).includes(this.model.getID())) {
this.setObject(this.availableObjects[0]);
}
} finally {
this.loading = false;
}
}
......
......@@ -107,6 +107,12 @@
</div>
<div class="clear-top-bar">
<ng-container *ngIf="loading">
<div class="m-5 text-center">
<h1><span class="fa fa-spinner"></span></h1>
</div>
</ng-container>
<ng-container *ngIf="getFilteredAndPagedData().length > 0">
<table class="table table-striped">
<thead>
......
......@@ -22,6 +22,7 @@ import {Operation} from '../../../enums/Operation';
})
export class GenericDataTableComponent implements OnInit {
@Input() loading: boolean;
@Input() data: IModel[];
@Input() modelDefinition: IModelDefinition<IModel>;
@Output() openAddEvent: EventEmitter<void> = new EventEmitter<void>();
......
......@@ -48,6 +48,11 @@ export class BackupComponent {
this.downloadedData = {};
this.globalBackupState = BackupState.loading;
for (const model of this.models) {
model[1] = BackupState.nothing;
model[2] = '';
}
for (const model of this.models) {
model[1] = BackupState.loading;
try {
......
<app-generic-data-table #table [data]="data" [modelDefinition]="modelDefinition"></app-generic-data-table>
<app-generic-data-table #table [data]="data" [loading]="genericDataLoader.loading" [modelDefinition]="modelDefinition"></app-generic-data-table>
<app-generic-data-table #table [data]="data" [modelDefinition]="modelDefinition" (openAddEvent)="addCategory()" (openEditEvent)="editCategory($event)"></app-generic-data-table>
<app-generic-data-table #table [data]="data" [loading]="genericDataLoader.loading" [modelDefinition]="modelDefinition" (openAddEvent)="addCategory()" (openEditEvent)="editCategory($event)"></app-generic-data-table>
......@@ -31,10 +31,10 @@ export class CategoriesComponent implements AfterViewInit {
async ngAfterViewInit(): Promise<void> {
try {
this.data = await this.genericDataLoader.get(this.modelDefinition);
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditCategoryComponent);
} catch (err) {
this.table.handleError(err);
}
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditCategoryComponent);
}
addCategory() {
......
<app-generic-data-table #table [data]="data" [modelDefinition]="modelDefinition" (openAddEvent)="addContent()" (openEditEvent)="editContent($event)"></app-generic-data-table>
<app-generic-data-table #table [data]="data" [loading]="genericDataLoader.loading" [modelDefinition]="modelDefinition" (openAddEvent)="addContent()" (openEditEvent)="editContent($event)"></app-generic-data-table>
......@@ -35,10 +35,10 @@ export class ContentsComponent implements AfterViewInit {
async ngAfterViewInit(): Promise<void> {
try {
this.data = await this.genericDataLoader.get(this.modelDefinition);
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditContentComponent);
} catch (err) {
this.table.handleError(err);
}
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditContentComponent);
}
addContent() {
......
<app-generic-data-table #table [data]="data" [modelDefinition]="modelDefinition" (openAddEvent)="addEvent()" (openEditEvent)="editEvent($event)"></app-generic-data-table>
<app-generic-data-table #table [data]="data" [loading]="genericDataLoader.loading" [modelDefinition]="modelDefinition" (openAddEvent)="addEvent()" (openEditEvent)="editEvent($event)"></app-generic-data-table>
......@@ -36,10 +36,10 @@ export class EventsComponent implements AfterViewInit {
async ngAfterViewInit(): Promise<void> {
try {
this.data = await this.genericDataLoader.get(this.modelDefinition);
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditEventComponent);
} catch (err) {
this.table.handleError(err);
}
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditEventComponent);
}
addEvent() {
......
<app-generic-data-table #table [data]="data" [modelDefinition]="modelDefinition" (openAddEvent)="addPerson()" (openEditEvent)="editPerson($event)"></app-generic-data-table>
<app-generic-data-table #table [data]="data" [loading]="genericDataLoader.loading" [modelDefinition]="modelDefinition" (openAddEvent)="addPerson()" (openEditEvent)="editPerson($event)"></app-generic-data-table>
......@@ -35,10 +35,10 @@ export class PersonsComponent implements AfterViewInit {
async ngAfterViewInit(): Promise<void> {
try {
this.data = await this.genericDataLoader.get(this.modelDefinition);
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditPersonComponent);
} catch (err) {
this.table.handleError(err);
}
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditPersonComponent);
}
addPerson() {
......
<app-generic-data-table #table [data]="data" [modelDefinition]="modelDefinition" (openAddEvent)="addPoi()" (openEditEvent)="editPoi($event)"></app-generic-data-table>
<app-generic-data-table #table [data]="data" [loading]="genericDataLoader.loading" [modelDefinition]="modelDefinition" (openAddEvent)="addPoi()" (openEditEvent)="editPoi($event)"></app-generic-data-table>
......@@ -33,10 +33,10 @@ export class PoisComponent implements AfterViewInit {
async ngAfterViewInit(): Promise<void> {
try {
this.data = await this.genericDataLoader.get(this.modelDefinition);
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditPoiComponent);
} catch (err) {
this.table.handleError(err);
}
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditPoiComponent);
}
addPoi() {
......
<app-generic-data-table #table [data]="data" [modelDefinition]="modelDefinition" (openAddEvent)="addTag()" (openEditEvent)="editTag($event)"></app-generic-data-table>
<app-generic-data-table #table [data]="data" [loading]="genericDataLoader.loading" [modelDefinition]="modelDefinition" (openAddEvent)="addTag()" (openEditEvent)="editTag($event)"></app-generic-data-table>
......@@ -35,10 +35,10 @@ export class TagsComponent implements AfterViewInit {
async ngAfterViewInit(): Promise<void> {
try {
this.data = await this.genericDataLoader.get(this.modelDefinition);
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditTagComponent);
} catch (err) {
this.table.handleError(err);
}
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditTagComponent);
}
addTag() {
......
<app-generic-data-table #table [data]="data" [modelDefinition]="modelDefinition" (openAddEvent)="addUser()" (openEditEvent)="editUser($event)"></app-generic-data-table>
\ No newline at end of file
<app-generic-data-table #table [data]="data" [loading]="genericDataLoader.loading" [modelDefinition]="modelDefinition" (openAddEvent)="addUser()" (openEditEvent)="editUser($event)"></app-generic-data-table>
......@@ -36,10 +36,10 @@ export class UsersComponent implements AfterViewInit {
async ngAfterViewInit(): Promise<void> {
try {
this.data = await this.genericDataLoader.get(this.modelDefinition);
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditUserComponent);
} catch (err) {
this.table.handleError(err);
}
this.commonViewModelService.checkURLandOpenAddEditModal(this.modelDefinition, AddEditUserComponent);
}
addUser() {
......
......@@ -10,119 +10,177 @@ import {ContentRangeParser, IContentRange} from '../../classes/ContentRangeParse
@Injectable()
export class GenericDataService {
/**
* If loading is > 0, the service is loading data
*/
private loadingStack: number;
constructor(private http: HttpClient,
private authService: AuthService,
private backendService: BackendService) {
this.loadingStack = 0;
}
async get<T extends IModel>(modelDefinition: IModelDefinition<T>): Promise<T[]> {
const objects: T[] = [];
let contentRangeHeader: IContentRange;
let offset = 0;
const size = 100;
let counter = 0;
do {
const options: any = {
observe: 'response',
};
if (modelDefinition.endpoint === EndpointType.CMS) {
options.headers = this.authService.generateRequestHeadersLoggedIn();
options.params = new HttpParams().set('size', size.toString()).set('offset', offset.toString());
}
public get loading(): boolean {
return this.loadingStack > 0;
}
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
private pushLoading(): void {
setTimeout(() => { this.loadingStack += 1; }, 0);
}
const url = `${baseURL}/${ressourceURL}`;
const response = await this.http.get<any[]>(url, options).toPromise() as HttpResponse<any[]>;
private popLoading(): void {
setTimeout(() => { this.loadingStack -= 1; }, 0);
}
response.body
.map((row) => modelDefinition.constructor(row))
.forEach((row) => objects.push(row));
async get<T extends IModel>(modelDefinition: IModelDefinition<T>): Promise<T[]> {
this.pushLoading();
try {
const objects: T[] = [];
let contentRangeHeader: IContentRange;
let offset = 0;
const size = 100;
let counter = 0;
do {
const options: any = {
observe: 'response',
};
if (modelDefinition.endpoint === EndpointType.CMS) {
options.headers = this.authService.generateRequestHeadersLoggedIn();
options.params = new HttpParams().set('size', size.toString()).set('offset', offset.toString());
}
if (response.headers) {
contentRangeHeader = ContentRangeParser.parse(response.headers.get('Content-Range'));
if (contentRangeHeader) {
offset = contentRangeHeader.last + 1;
counter += 1;
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
const url = `${baseURL}/${ressourceURL}`;
const response = await this.http.get<any[]>(url, options).toPromise() as HttpResponse<any[]>;
response.body
.map((row) => modelDefinition.constructor(row))
.forEach((row) => objects.push(row));
if (response.headers) {
contentRangeHeader = ContentRangeParser.parse(response.headers.get('Content-Range'));
if (contentRangeHeader) {
offset = contentRangeHeader.last + 1;
counter += 1;
} else {
this.popLoading();
return objects;
}
} else {
this.popLoading();
return objects;
}
} else {
return objects;
}
}
while (contentRangeHeader.last < contentRangeHeader.length && counter < 1000);
while (contentRangeHeader.last < contentRangeHeader.length && counter < 1000);
return objects;
this.popLoading();
return objects;
} catch (err) {
this.popLoading();
return Promise.reject(err);
}
}
async getWithId<T extends IModel>(modelDefinition: IModelDefinition<T>, id: string): Promise<T> {
let options = {};
if (modelDefinition.endpoint === EndpointType.CMS) {
options = {
headers: this.authService.generateRequestHeadersLoggedIn(),
};
}
this.pushLoading();
try {
let options = {};
if (modelDefinition.endpoint === EndpointType.CMS) {
options = {
headers: this.authService.generateRequestHeadersLoggedIn(),
};
}
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
const obj = await this.http.get(`${baseURL}/${ressourceURL}/${id}`, options).toPromise();
const obj = await this.http.get(`${baseURL}/${ressourceURL}/${id}`, options).toPromise();
return modelDefinition.constructor(obj);
this.popLoading();
return modelDefinition.constructor(obj);
} catch (err) {
this.popLoading();
return Promise.reject(err);
}
}
async post<T extends IModel>(modelDefinition: IModelDefinition<T>, object: T): Promise<void> {
let options = {};
if (modelDefinition.endpoint === EndpointType.CMS) {
options = {
headers: this.authService.generateRequestHeadersLoggedIn(),
};
}
this.pushLoading();
try {
let options = {};
if (modelDefinition.endpoint === EndpointType.CMS) {
options = {
headers: this.authService.generateRequestHeadersLoggedIn(),
};
}
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
const ob = object.toPOSTObject();
await this.http.post(`${baseURL}/${ressourceURL}`, ob, options).toPromise();
const ob = object.toPOSTObject();
await this.http.post(`${baseURL}/${ressourceURL}`, ob, options).toPromise();
this.popLoading();
} catch (err) {
this.popLoading();
return Promise.reject(err);
}
}
async put<T extends IModel>(modelDefinition: IModelDefinition<T>, object: T): Promise<void> {
let options = {};
if (modelDefinition.endpoint === EndpointType.CMS) {
options = {
headers: this.authService.generateRequestHeadersLoggedIn(),
};
}
this.pushLoading();
try {
let options = {};
if (modelDefinition.endpoint === EndpointType.CMS) {
options = {
headers: this.authService.generateRequestHeadersLoggedIn(),
};
}
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
const ob = object.toPOSTObject();
await this.http.put(`${baseURL}/${ressourceURL}/${object.getID()}`, ob, options).toPromise();
}
const ob = object.toPOSTObject();
async delete<T extends IModel>(modelDefinition: IModelDefinition<T>, object: T): Promise<void> {
let headers = new HttpHeaders();
if (modelDefinition.endpoint === EndpointType.CMS) {
headers = this.authService.generateRequestHeadersLoggedIn();
await this.http.put(`${baseURL}/${ressourceURL}/${object.getID()}`, ob, options).toPromise();
this.popLoading();
} catch (err) {
this.popLoading();
return Promise.reject(err);
}
}
const id = object.getID();
const revision = object.getRevision().toString();
async delete<T extends IModel>(modelDefinition: IModelDefinition<T>, object: T): Promise<void> {
this.pushLoading();
try {
let headers = new HttpHeaders();
if (modelDefinition.endpoint === EndpointType.CMS) {
headers = this.authService.generateRequestHeadersLoggedIn();
}
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
const id = object.getID();
const revision = object.getRevision().toString();
let params = new HttpParams();
params = params.set('revision', revision);
const baseURL = this.backendService.getEndpointDefinitionForBackEndType(modelDefinition.endpoint).baseUrl;
const ressourceURL = modelDefinition.ressourcePathBackend;
await this.http.delete(`${baseURL}/${ressourceURL}/${id}`, {
headers,
params,
}).toPromise();
let params = new HttpParams();
params = params.set('revision', revision);
await this.http.delete(`${baseURL}/${ressourceURL}/${id}`, {
headers,
params,
}).toPromise();
this.popLoading();
} catch (err) {
this.popLoading();
return Promise.reject(err);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment