Skip to content

Commit a697fe2

Browse files
committed
Issue-1420 Create a boot splash screen
1 parent bced470 commit a697fe2

File tree

10 files changed

+164
-39
lines changed

10 files changed

+164
-39
lines changed

e2e/src/app.e2e-spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ describe('Strongbox', () => {
88
page = new AppPage();
99
});
1010

11-
it('should display logo', () => {
11+
it('should display boot splash screen', () => {
1212
page.navigateTo('/');
1313
browser.sleep(2000);
14-
expect(page.getLogo().getText()).toBe('Strongbox');
14+
expect(page.getBootsplashScreen().getText()).toBe('Strongbox');
1515
});
1616
});

e2e/src/app.po.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export class AppPage {
55
return browser.get(path);
66
}
77

8-
getLogo() {
9-
return element(by.id('logo'));
8+
getBootsplashScreen() {
9+
return element(by.id('fullscreen-splash')).element(by.className('logo-text'));
1010
}
1111
}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"antlr4ts": "^0.4.1-alpha.0",
5050
"class-transformer": "~0.2.3",
5151
"core-js": "^2.6.3",
52+
"dayjs": "~1.8.15",
5253
"fonts-raleway": "0.0.4",
5354
"ionicons": "^4.4.8",
5455
"ngx-toastr": "~10.0.4",

src/app/modules/core/auth/state/session.state.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {HttpErrorResponse} from '@angular/common/http';
2-
import {Action, createSelector, NgxsOnInit, Selector, State, StateContext, Store} from '@ngxs/store';
2+
import {Action, createSelector, Selector, State, StateContext, Store} from '@ngxs/store';
33
import {Navigate} from '@ngxs/router-plugin';
44
import {catchError, tap} from 'rxjs/operators';
55
import {of} from 'rxjs';
@@ -61,7 +61,7 @@ function initialSessionState() {
6161
name: 'session',
6262
defaults: initialSessionState()
6363
})
64-
export class SessionState implements NgxsOnInit {
64+
export class SessionState {
6565

6666
@Selector()
6767
static token(session: SessionStateModel) {
@@ -116,10 +116,6 @@ export class SessionState implements NgxsOnInit {
116116
constructor(private auth: AuthService, private store: Store) {
117117
}
118118

119-
ngxsOnInit(ctx: StateContext<SessionStateModel>) {
120-
this.store.dispatch(new CheckCredentialsAction());
121-
}
122-
123119
@Action(CheckCredentialsAction)
124120
checkCredentials() {
125121
this.auth.checkCredentials().subscribe();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {TestBed} from '@angular/core/testing';
2+
3+
import {HttpClientTestingModule} from '@angular/common/http/testing';
4+
import {RouterTestingModule} from '@angular/router/testing';
5+
import {NgxsModule} from '@ngxs/store';
6+
7+
import {BootProgressService} from './boot-progress.service';
8+
9+
describe('BootProgressService', () => {
10+
beforeEach(() => TestBed.configureTestingModule({
11+
imports: [
12+
HttpClientTestingModule,
13+
RouterTestingModule,
14+
NgxsModule.forRoot()
15+
]
16+
}));
17+
18+
it('should be created', () => {
19+
const service: BootProgressService = TestBed.get(BootProgressService);
20+
expect(service).toBeTruthy();
21+
});
22+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {Inject, Injectable} from '@angular/core';
2+
import {Store} from '@ngxs/store';
3+
import {DOCUMENT} from '@angular/common';
4+
5+
import {environment} from '../../../../environments/environment';
6+
import * as dayjs from 'dayjs';
7+
import {CheckCredentialsAction} from '../auth/state/auth.actions';
8+
9+
@Injectable({
10+
providedIn: 'root'
11+
})
12+
export class BootProgressService {
13+
14+
constructor(private store: Store, @Inject(DOCUMENT) private document: Document) {
15+
}
16+
17+
start() {
18+
const baseUrl = environment.strongboxUrl ? location.protocol + '//' + environment.strongboxUrl : '';
19+
20+
try {
21+
const splashScreen = document.querySelector('#fullscreen-splash');
22+
23+
if (splashScreen !== null) {
24+
const messageElement = splashScreen.querySelector('.message');
25+
const spinnerContainer = splashScreen.querySelector('.spinner-container');
26+
27+
// is the server online or still booting?
28+
let source = new EventSource(baseUrl + '/api/ping');
29+
let messages = 0;
30+
let startTime = dayjs();
31+
32+
33+
source.addEventListener('message', (e) => {
34+
++messages;
35+
messageElement.innerHTML = 'Loading ' + e.data + '...';
36+
splashScreen.querySelector('.bootStatus').removeAttribute('style');
37+
});
38+
39+
source.addEventListener('booted', (e) => {
40+
// longer timeout for when booting (i.e. started/restarted)
41+
const timeout = messages > 0 ? 1600 : 350;
42+
43+
// Better loading.
44+
if (messages > 0) {
45+
messageElement.innerHTML = 'Let\'s get started!';
46+
messageElement.setAttribute('style', 'color: white !important;');
47+
48+
spinnerContainer.setAttribute('class', 'spinner-container loaded');
49+
console.log('Booted in ' + dayjs().diff(startTime, 's') + ' seconds');
50+
}
51+
52+
// The server is up
53+
setTimeout(() => {
54+
if (splashScreen) {
55+
splashScreen.setAttribute('class', 'loaded');
56+
57+
document.querySelector('app-strongbox').removeAttribute('style');
58+
59+
setTimeout(function () {
60+
splashScreen.remove();
61+
}, 680);
62+
63+
this.store.dispatch(new CheckCredentialsAction());
64+
}
65+
}, timeout);
66+
67+
source.close();
68+
});
69+
}
70+
} catch (e) {
71+
console.error(e);
72+
}
73+
}
74+
}

src/app/state/app.state.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {Action, NgxsOnInit, Select, Selector, State, StateContext} from '@ngxs/s
44
import {Navigate, RouterNavigation} from '@ngxs/router-plugin';
55
import {filter, take} from 'rxjs/operators';
66
import {MatDialogRef} from '@angular/material';
7+
import {NavigationStart, Router} from '@angular/router';
78

89
import {
910
CloseLoginDialogAction,
@@ -18,7 +19,7 @@ import {
1819
import {LoginDialogComponent} from '../modules/core/dialogs/login/login.dialog.component';
1920
import {AppStateModel, defaultAppState, SideNavStateModel, ViewPortStateModel} from './app.state.interfaces';
2021
import {LogoutAction} from '../modules/core/auth/state/auth.actions';
21-
import {NavigationStart, Router} from '@angular/router';
22+
import {BootProgressService} from '../modules/core/services/boot-progress.service';
2223

2324
@State<AppStateModel>({
2425
name: 'app',
@@ -64,7 +65,8 @@ export class AppState implements NgxsOnInit {
6465
return state.isHomepage;
6566
}
6667

67-
constructor(private dialog: MatDialog,
68+
constructor(private bootProgress: BootProgressService,
69+
private dialog: MatDialog,
6870
private mediaObserver: MediaObserver,
6971
private router: Router) {
7072
}
@@ -88,6 +90,8 @@ export class AppState implements NgxsOnInit {
8890
.subscribe((event: NavigationStart) => {
8991
ctx.patchState({...ctx.getState(), isHomepage: event.url === '/' || event.url === ''});
9092
});
93+
94+
this.bootProgress.start();
9195
}
9296

9397
@Action(RouterNavigation)

src/index.html

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
</div>
2525
</div>
2626
</div>
27+
<div class="bootStatus">
28+
<span class="message"></span>
29+
</div>
2730
</div>
2831

2932
<app-strongbox style="display: none;"></app-strongbox>

src/main.ts

-12
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,4 @@ if (environment.production) {
1010

1111
platformBrowserDynamic()
1212
.bootstrapModule(AppModule)
13-
.then(() => {
14-
setTimeout(function(){
15-
const splashScreen = document.getElementById('fullscreen-splash');
16-
if (splashScreen) {
17-
splashScreen.setAttribute('class', 'loaded');
18-
document.getElementsByTagName('app-strongbox').item(0).removeAttribute('style');
19-
setTimeout(function () {
20-
splashScreen.remove();
21-
}, 680);
22-
}
23-
}, 400);
24-
})
2513
.catch(err => console.log(err));

src/sass/layout/splash-screen.scss

+52-15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#fullscreen-splash {
55
height: 100vh;
66
width: 100vw;
7+
color: white;
78
background: $body-background-color;
89
background: linear-gradient($body-gradient-degrees, $body-gradient-0 0%, $body-gradient-1 100%); /* common. */
910
background: -o-linear-gradient($body-gradient-degrees, $body-gradient-0 0%, $body-gradient-1 100%); /* opera 11.10+ */
@@ -28,10 +29,8 @@
2829
left: 0px;
2930

3031
.container {
31-
color: white;
3232
overflow: hidden;
3333
opacity: 1;
34-
position: absolute;
3534
z-index: 999;
3635
}
3736

@@ -41,51 +40,89 @@
4140
transition: visibility 0s 0.18s, opacity 0.18s linear;
4241
}
4342

44-
@include logo-text(2.8rem, 0.65rem 1rem 0px 1rem);
43+
@include logo-text(2.8rem, 0rem 1rem 0px 1rem);
4544

4645
.spinner-container {
4746
position: relative;
4847
overflow: hidden;
49-
height: 100px;
48+
height: 35px;
49+
padding: 17px 0px 17px 0px;
50+
51+
&.loaded {
52+
visibility: hidden;
53+
opacity: 0;
54+
transition: visibility 0s 0.18s, opacity 0.18s linear;
55+
}
5056
}
5157

5258
.spinner div {
53-
width: 5px;
54-
height: 5px;
59+
width: 4px;
60+
height: 4px;
5561
position: absolute;
5662
left: -50px;
57-
top: 30px;
5863
background-color: #fff;
59-
border-radius: 40%;
64+
border-radius: 30%;
6065
//animation: spinner-move 3s infinite cubic-bezier(.32, 1, .88, .27);
61-
animation: spinner-move 3s infinite cubic-bezier(0.5, 0.04, 0.08, 0.8);
66+
animation: spinner-move 3.5s infinite cubic-bezier(0.4, 0.02, 0.08, 0.8);
67+
68+
}
69+
70+
.spinner div:nth-child(1) {
71+
animation-delay: 200ms;
6272
}
6373

6474
.spinner div:nth-child(2) {
65-
animation-delay: 150ms;
75+
animation-delay: calc(200ms + 150ms);
6676
}
6777

6878
.spinner div:nth-child(3) {
69-
animation-delay: 300ms;
79+
animation-delay: calc(200ms + 300ms);
7080
}
7181

7282
.spinner div:nth-child(4) {
73-
animation-delay: 450ms;
83+
animation-delay: calc(200ms + 450ms);
7484
}
7585

7686
.spinner div:nth-child(5) {
77-
animation-delay: 600ms;
87+
animation-delay: calc(200ms + 600ms);
7888
}
7989

8090
@keyframes spinner-move {
8191
0% {
82-
left: -50%;
92+
opacity: 0;
93+
left: -40%;
94+
}
95+
30% {
96+
opacity: 0;
97+
left: 0%;
98+
}
99+
70% {
100+
opacity: 1;
83101
}
84102
80% {
103+
opacity: 0.9;
85104
left: 50%;
86105
}
106+
82% {
107+
opacity: 0.5;
108+
}
109+
90% {
110+
opacity: 0;
111+
left: 100%;
112+
}
87113
100% {
114+
opacity: 0;
88115
left: 150%;
89116
}
90117
}
91-
}
118+
119+
.bootStatus {
120+
font-family: $font-family;
121+
font-weight: 300;
122+
font-size: 0.98rem;
123+
letter-spacing: 0.065rem;
124+
transition: opacity 0.5s ease-in-out;
125+
text-align: center;
126+
color: rgba(255, 255, 255, 0.6);
127+
}
128+
}

0 commit comments

Comments
 (0)