diff --git a/src/app/app.component.html b/src/app/app.component.html index 43a2167..6f5b51a 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,9 +1,9 @@ - + - Noded + {{ appName }} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c417418..81ccd5f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,15 +1,16 @@ import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; -import {AlertController, ModalController, Platform, PopoverController} from '@ionic/angular'; +import {AlertController, ModalController, Platform, PopoverController, LoadingController} from '@ionic/angular'; import { SplashScreen } from '@ionic-native/splash-screen/ngx'; import { StatusBar } from '@ionic-native/status-bar/ngx'; import { ApiService } from './service/api.service'; import { Router } from '@angular/router'; import {TREE_ACTIONS, TreeComponent} from '@circlon/angular-tree-component'; -import { Observable } from 'rxjs'; +import {BehaviorSubject, Observable} from 'rxjs'; import {OptionPickerComponent} from './components/option-picker/option-picker.component'; import {OptionMenuComponent} from './components/option-menu/option-menu.component'; import {SelectorComponent} from './components/sharing/selector/selector.component'; +import {SessionService} from './service/session.service'; @Component({ selector: 'app-root', @@ -18,6 +19,7 @@ import {SelectorComponent} from './components/sharing/selector/selector.componen }) export class AppComponent implements OnInit { @ViewChild('menuTree') menuTree: TreeComponent; + public readonly ready$: BehaviorSubject = new BehaviorSubject(false); public addChildTarget: any = false; public deleteTarget: any = false; public menuTarget: any = false; @@ -59,8 +61,14 @@ export class AppComponent implements OnInit { } } }; + public get appName(): string { + return this.session.appName || 'Noded'; + } public darkMode = false; + protected loader?: any; + + protected initialized$: BehaviorSubject = new BehaviorSubject(false); constructor( private platform: Platform, @@ -71,16 +79,35 @@ export class AppComponent implements OnInit { protected alerts: AlertController, protected popover: PopoverController, protected modal: ModalController, + protected session: SessionService, + protected loading: LoadingController, ) { this.initializeApp(); } - ngOnInit() { + _doInit() { this.reloadMenuItems().subscribe(() => { - this.menuTree.treeModel.expandAll(); + this.ready$.next(true); + setTimeout(() => { + this.loader.dismiss(); + this.menuTree.treeModel.expandAll(); + }, 10); }); } + ngOnInit() { + if ( !this.initialized$.getValue() ) { + const sub = this.initialized$.subscribe((didInit) => { + if (didInit) { + this._doInit(); + sub.unsubscribe(); + } + }); + } else { + this._doInit(); + } + } + showOptions($event) { this.popover.create({ event: $event, @@ -108,7 +135,6 @@ export class AppComponent implements OnInit { } async onNodeMenuClick($event) { - console.log(this.menuTarget) let canManage = this.menuTarget.data.level === 'manage'; if ( !canManage ) { if ( !this.menuTarget.data.level ) { @@ -270,11 +296,30 @@ export class AppComponent implements OnInit { }); } - initializeApp() { - this.platform.ready().then(() => { - this.statusBar.styleDefault(); - this.splashScreen.hide(); + async initializeApp() { + this.loader = await this.loading.create({ + message: 'Starting up...', + cssClass: 'noded-loading-mask', + showBackdrop: true, }); + + await this.loader.present(); + + await this.platform.ready(); + const stat: any = await this.session.stat(); + + if ( !stat.authenticated_user ) { + window.location.href = `${stat.system_base}start`; + return; + } + + this.session.appName = stat.app_name; + this.session.systemBase = stat.system_base; + + await this.session.initialize(); + await this.statusBar.styleDefault(); + await this.splashScreen.hide(); + this.initialized$.next(true); } toggleDark() { diff --git a/src/app/service/action.types.ts b/src/app/service/action.types.ts new file mode 100644 index 0000000..b006565 --- /dev/null +++ b/src/app/service/action.types.ts @@ -0,0 +1,10 @@ +export interface RedirectAction { + type: 'redirect'; + args: { + destination: string + blank?: boolean + appUrl?: boolean + }; +} + +export type Action = RedirectAction; diff --git a/src/app/service/actions.service.ts b/src/app/service/actions.service.ts new file mode 100644 index 0000000..622f672 --- /dev/null +++ b/src/app/service/actions.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { Action } from './action.types'; +import {SessionService} from './session.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ActionsService { + constructor( + protected readonly session: SessionService, + ) { } + + async perform(action: Action) { + if ( action.type === 'redirect' ) { + let destination = action.args.destination; + if ( action.args.appUrl ) { + destination = this.session.buildAppUrl(destination); + } + + if ( action.args.blank ) { + window.open(destination, '_blank'); + } else { + window.location.href = destination; + } + } + } +} diff --git a/src/app/service/api.service.ts b/src/app/service/api.service.ts index 095185b..2044848 100644 --- a/src/app/service/api.service.ts +++ b/src/app/service/api.service.ts @@ -9,6 +9,7 @@ import ApiResponse from '../structures/ApiResponse'; }) export class ApiService { protected baseEndpoint: string = environment.backendBase; + protected statUrl: string = environment.statUrl; constructor( protected http: HttpClient, @@ -23,6 +24,14 @@ export class ApiService { } public request(endpoint, params = {}, method: 'get'|'post' = 'get'): Observable { + return this._request(this._build_url(endpoint), params, method); + } + + public stat(): Observable { + return this._request(this.statUrl); + } + + protected _request(endpoint, params = {}, method: 'get'|'post' = 'get'): Observable { return new Observable(sub => { let data: any = {} if ( method === 'get' ) { @@ -31,7 +40,7 @@ export class ApiService { data = params; } - this.http[method](this._build_url(endpoint), data).subscribe({ + this.http[method](endpoint, data).subscribe({ next: (response: any) => { sub.next(new ApiResponse(response)); }, diff --git a/src/app/service/session.service.ts b/src/app/service/session.service.ts new file mode 100644 index 0000000..a2a9062 --- /dev/null +++ b/src/app/service/session.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; +import {ApiService} from './api.service'; + +@Injectable({ + providedIn: 'root' +}) +export class SessionService { + public appName!: string; + public systemBase!: string; + protected data: any = {}; + + constructor( + protected readonly api: ApiService, + ) { } + + async stat() { + return new Promise((res, rej) => { + this.api.stat().subscribe(response => { + res(response.data); + }); + }); + } + + async initialize() { + return new Promise((res, rej) => { + this.api.get('/session').subscribe(response => { + this.data = response.data; + res(); + }); + }); + } + + buildAppUrl(...parts: string[]): string { + parts = parts.map(x => { + if ( x.startsWith('/') ) { + x = x.slice(1); + } + + if ( x.endsWith('/') ) { + x = x.slice(0, -1); + } + + return x; + }); + + return `${this.systemBase}${this.systemBase.endsWith('/') ? '' : '/'}${parts.join('/')}`; + } +} diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index be8c9c6..cd33e36 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,4 +1,5 @@ export const environment = { production: true, backendBase: '/api/v1', + statUrl: '/stat', }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 8acd484..6bba5ee 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -5,6 +5,7 @@ export const environment = { production: false, backendBase: '/link_api/api/v1', + statUrl: '/link_api/stat', }; /* diff --git a/src/global.scss b/src/global.scss index d3222e1..9713d2e 100644 --- a/src/global.scss +++ b/src/global.scss @@ -33,3 +33,15 @@ div.picker-wrapper { border: 2px solid lightgrey !important; border-radius: 7px !important; } + +.noded-loading-mask { + //--background: #222; + //--spinner-color: #fff; + --backdrop-opacity: 0.7; + + ion-backdrop { + background: #333; + } + + //color: #fff; +}