Skip to content

Commit f01db4c

Browse files
authored
Merge pull request #70 from NuGetTrends/feat/loading-indicator
Add loading indicator
2 parents 96cfa43 + 9bdf1d6 commit f01db4c

10 files changed

+349
-11
lines changed

src/NuGetTrends.Spa/src/app/app.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@
2424
<router-outlet></router-outlet>
2525
</div>
2626
<app-footer></app-footer>
27+
<app-loading></app-loading>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export * from './loading-indicator/loading-indicator.component';
2+
export * from './loading-indicator/loading-indicator.interceptor';
3+
4+
export * from './package-list/package-list.component';
5+
export * from './search-input/search-input.component';
6+
export * from './search-period/search-period.component';
7+
export * from './search-type/search-type.component';
8+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<div class="loading-screen-wrapper">
2+
<div class="loading-screen-icon">
3+
<div class="loadchart">
4+
<div class="loadchart__bar"></div>
5+
<div class="loadchart__bar"></div>
6+
<div class="loadchart__bar"></div>
7+
<div class="loadchart__bar"></div>
8+
<div class="loadchart__bar"></div>
9+
<div class="loadchart__ball"></div>
10+
</div>
11+
</div>
12+
</div>
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
@import '../../../../colors';
2+
3+
.loading-screen-wrapper {
4+
background-color: rgba(51,51,51,0.8);
5+
position: fixed;
6+
top: 0;
7+
left: 0;
8+
width: 100%;
9+
height: 100%;
10+
z-index: 99999 !important;
11+
}
12+
13+
.loading-screen-icon {
14+
top: 50%;
15+
left: 50%;
16+
margin: 0;
17+
position: absolute;
18+
transform: translate(-50%, -50%);
19+
}
20+
21+
.loadchart {
22+
23+
position: relative;
24+
width: 75px;
25+
height: 100px;
26+
27+
&__bar {
28+
position: absolute;
29+
bottom: 0;
30+
width: 10px;
31+
height: 50%;
32+
background: $chart-loading-icon;
33+
transform-origin: center bottom;
34+
box-shadow: 1px 1px 0 rgba(0,0,0,.2);
35+
36+
@for $i from 1 through 5 {
37+
&:nth-child(#{$i}) {
38+
left: ($i - 1) * 15px;
39+
transform: scale(1,$i*.2);
40+
animation: barUp#{$i} 4s infinite;
41+
}
42+
}
43+
44+
}
45+
46+
&__ball {
47+
position: absolute;
48+
bottom: 10px;
49+
left: 0;
50+
width: 10px;
51+
height: 10px;
52+
background: $chart-loading-icon;
53+
border-radius: 50%;
54+
animation: ball 4s infinite;
55+
}
56+
}
57+
58+
@keyframes ball {
59+
0% {
60+
transform: translate(0, 0);
61+
}
62+
5% {
63+
transform: translate(8px, -14px);
64+
}
65+
10% {
66+
transform: translate(15px, -10px)
67+
}
68+
17% {
69+
transform: translate(23px, -24px)
70+
}
71+
20% {
72+
transform: translate(30px, -20px)
73+
}
74+
27% {
75+
transform: translate(38px, -34px)
76+
}
77+
30% {
78+
transform: translate(45px, -30px)
79+
}
80+
37% {
81+
transform: translate(53px, -44px)
82+
}
83+
40% {
84+
transform: translate(60px, -40px)
85+
}
86+
50% {
87+
transform: translate(60px, 0)
88+
}
89+
57% {
90+
transform: translate(53px, -14px)
91+
}
92+
60% {
93+
transform: translate(45px, -10px)
94+
}
95+
67% {
96+
transform: translate(37px, -24px)
97+
}
98+
70% {
99+
transform: translate(30px, -20px)
100+
}
101+
77% {
102+
transform: translate(22px, -34px)
103+
}
104+
80% {
105+
transform: translate(15px, -30px)
106+
}
107+
87% {
108+
transform: translate(7px, -44px)
109+
}
110+
90% {
111+
transform: translate(0, -40px)
112+
}
113+
100% {
114+
transform: translate(0, 0);
115+
}
116+
}
117+
118+
@keyframes barUp1 {
119+
0% {
120+
transform: scale(1, .2);
121+
}
122+
40%{
123+
transform: scale(1, .2);
124+
}
125+
50% {
126+
transform: scale(1, 1);
127+
}
128+
90% {
129+
transform: scale(1,1);
130+
}
131+
100% {
132+
transform: scale(1,.2);
133+
}
134+
}
135+
@keyframes barUp2 {
136+
0% {
137+
transform: scale(1, .4);
138+
}
139+
40% {
140+
transform: scale(1, .4);
141+
}
142+
50% {
143+
transform: scale(1, .8);
144+
}
145+
90% {
146+
transform: scale(1, .8);
147+
}
148+
100% {
149+
transform: scale(1, .4);
150+
}
151+
}
152+
@keyframes barUp3 {
153+
0% {
154+
transform: scale(1, .6);
155+
}
156+
100% {
157+
transform: scale(1, .6);
158+
}
159+
}
160+
@keyframes barUp4 {
161+
0% {
162+
transform: scale(1, .8);
163+
}
164+
40% {
165+
transform: scale(1, .8);
166+
}
167+
50% {
168+
transform: scale(1, .4);
169+
}
170+
90% {
171+
transform: scale(1, .4);
172+
}
173+
100% {
174+
transform: scale(1, .8);
175+
}
176+
}
177+
@keyframes barUp5 {
178+
0% {
179+
transform: scale(1, 1);
180+
}
181+
40% {
182+
transform: scale(1, 1);
183+
}
184+
50% {
185+
transform: scale(1, .2);
186+
}
187+
90% {
188+
transform: scale(1, .2);
189+
}
190+
100% {
191+
transform: scale(1, 1);
192+
}
193+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { LoadingIndicatorComponent } from './loading-indicator.component';
4+
5+
describe('LoadingIndicatorComponent', () => {
6+
let component: LoadingIndicatorComponent;
7+
let fixture: ComponentFixture<LoadingIndicatorComponent>;
8+
9+
beforeEach(async(() => {
10+
TestBed.configureTestingModule({
11+
declarations: [ LoadingIndicatorComponent ]
12+
})
13+
.compileComponents();
14+
}));
15+
16+
beforeEach(() => {
17+
fixture = TestBed.createComponent(LoadingIndicatorComponent);
18+
component = fixture.componentInstance;
19+
fixture.detectChanges();
20+
});
21+
22+
it('should create', () => {
23+
expect(component).toBeTruthy();
24+
});
25+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Component, OnDestroy, AfterViewInit, ElementRef, ChangeDetectorRef } from '@angular/core';
2+
import { Subscription } from 'rxjs';
3+
import { debounceTime } from 'rxjs/operators';
4+
5+
import { LoadingIndicatorService } from './loading-indicator.service';
6+
7+
@Component({
8+
selector: 'app-loading',
9+
templateUrl: './loading-indicator.component.html',
10+
styleUrls: ['./loading-indicator.component.scss']
11+
})
12+
export class LoadingIndicatorComponent implements AfterViewInit, OnDestroy {
13+
14+
loadingSubscription: Subscription;
15+
16+
constructor(
17+
private _elmRef: ElementRef,
18+
private _changeDetectorRef: ChangeDetectorRef,
19+
private loadingIndicatorService: LoadingIndicatorService) { }
20+
21+
ngAfterViewInit(): void {
22+
this._elmRef.nativeElement.style.display = 'none';
23+
this.loadingSubscription = this.loadingIndicatorService.loadingSubject$
24+
.pipe(debounceTime(200))
25+
.subscribe((isLoading: boolean) => {
26+
this._elmRef.nativeElement.style.display = isLoading ? 'block' : 'none';
27+
this._changeDetectorRef.detectChanges();
28+
});
29+
}
30+
31+
ngOnDestroy() {
32+
this.loadingSubscription.unsubscribe();
33+
}
34+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Injectable } from '@angular/core';
2+
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
3+
import { Observable } from 'rxjs';
4+
import { finalize } from 'rxjs/operators';
5+
6+
import { LoadingIndicatorService } from './loading-indicator.service';
7+
8+
9+
@Injectable()
10+
export class LoadingIndicatorInterceptor implements HttpInterceptor {
11+
12+
activeRequests = 0;
13+
14+
constructor(private loadingIndicatorService: LoadingIndicatorService) {
15+
}
16+
17+
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
18+
if (this.activeRequests === 0) {
19+
this.loadingIndicatorService.show();
20+
}
21+
22+
this.activeRequests++;
23+
return next.handle(request).pipe(
24+
finalize(() => {
25+
this.activeRequests--;
26+
if (this.activeRequests === 0) {
27+
this.loadingIndicatorService.hide();
28+
}
29+
})
30+
);
31+
}
32+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Injectable } from '@angular/core';
2+
import { Subject } from 'rxjs';
3+
4+
@Injectable({
5+
providedIn: 'root',
6+
})
7+
export class LoadingIndicatorService {
8+
9+
loadingSubject$ = new Subject<boolean>();
10+
show() {
11+
this.loadingSubject$.next(true);
12+
}
13+
hide() {
14+
this.loadingSubject$.next(false);
15+
}
16+
}

src/NuGetTrends.Spa/src/app/shared/shared.module.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ import { NgModule } from '@angular/core';
22
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
33
import { CommonModule } from '@angular/common';
44
import { MatAutocompleteModule } from '@angular/material/autocomplete';
5+
import { HTTP_INTERCEPTORS } from '@angular/common/http';
6+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
57
import { ToastrModule } from 'ngx-toastr';
68

7-
import { SearchInputComponent } from './components/search-input/search-input.component';
8-
import { PackageListComponent } from './components/package-list/package-list.component';
9-
import { SearchTypeComponent } from './components/search-type/search-type.component';
10-
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
11-
import { SearchPeriodComponent } from './components/search-period/search-period.component';
9+
import {
10+
SearchInputComponent,
11+
PackageListComponent,
12+
SearchTypeComponent,
13+
SearchPeriodComponent,
14+
LoadingIndicatorComponent,
15+
LoadingIndicatorInterceptor
16+
} from './components';
1217

1318
@NgModule({
1419
imports: [
@@ -20,14 +25,15 @@ import { SearchPeriodComponent } from './components/search-period/search-period.
2025
preventDuplicates: true,
2126
}),
2227
ReactiveFormsModule,
23-
BrowserAnimationsModule],
24-
28+
BrowserAnimationsModule
29+
],
2530
declarations: [
2631
SearchInputComponent,
2732
PackageListComponent,
2833
SearchTypeComponent,
29-
SearchPeriodComponent],
30-
34+
SearchPeriodComponent,
35+
LoadingIndicatorComponent
36+
],
3137
exports: [
3238
CommonModule,
3339
FormsModule,
@@ -36,7 +42,16 @@ import { SearchPeriodComponent } from './components/search-period/search-period.
3642
SearchInputComponent,
3743
PackageListComponent,
3844
SearchTypeComponent,
39-
SearchPeriodComponent]
45+
SearchPeriodComponent,
46+
LoadingIndicatorComponent
47+
],
48+
providers: [
49+
{
50+
provide: HTTP_INTERCEPTORS,
51+
useClass: LoadingIndicatorInterceptor,
52+
multi: true
53+
}
54+
],
4055
})
4156
export class SharedModule {
4257
}

0 commit comments

Comments
 (0)