diff --git a/src/assets/styles/common-utilities.scss b/src/assets/styles/common-utilities.scss index 955c96b..7234cf6 100644 --- a/src/assets/styles/common-utilities.scss +++ b/src/assets/styles/common-utilities.scss @@ -42,3 +42,23 @@ $sides: (top, bottom, left, right); flex: #{$n}; } } + +/* SKELETON LINES */ + +/* sk-w-${n} (in pixels) */ + +$skeleton-widths: (120, 200, 250, 340); +@each $w in $skeleton-widths { + .sk-w-#{$w} { + max-width: #{$w}px; + } +} + +/* sk-h-${n} */ + +$skeleton-heights: (12, 20, 24); +@each $h in $skeleton-heights { + .sk-h-#{$h} { + height: #{$h}px; + } +} diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts index 79c94d5..92950e1 100644 --- a/src/lib/common-ui.module.ts +++ b/src/lib/common-ui.module.ts @@ -5,7 +5,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner'; import { SortByPipe } from './sorting'; import { CommonUiOptions, IqserAppConfig, ModuleWithOptions } from './utils'; -import { HiddenActionComponent, LogoComponent, ToastComponent } from './shared'; +import { HiddenActionComponent, ToastComponent } from './shared'; import { ConnectionStatusComponent, FullPageErrorComponent } from './error'; import { IqserListingModule } from './listing'; import { IqserFiltersModule } from './filtering'; @@ -24,6 +24,7 @@ import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/le import { ApiPathInterceptor, IqserConfigService, IqserUserPreferenceService } from './services'; import { DefaultUserPreferenceService } from './services/default-user-preference.service'; import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; +import { IqserSkeletonModule } from './skeleton/skeleton.module'; const matModules = [ MatIconModule, @@ -42,16 +43,10 @@ const modules = [ IqserInputsModule, IqserScrollbarModule, IqserEmptyStatesModule, + IqserSkeletonModule, HttpClientModule, ]; -const components = [ - ConnectionStatusComponent, - FullPageErrorComponent, - LogoComponent, - HiddenActionComponent, - ConfirmationDialogComponent, - ToastComponent, -]; +const components = [ConnectionStatusComponent, FullPageErrorComponent, HiddenActionComponent, ConfirmationDialogComponent, ToastComponent]; const pipes = [SortByPipe]; diff --git a/src/lib/services/composite-route.guard.ts b/src/lib/services/composite-route.guard.ts index 148d03a..f102b82 100644 --- a/src/lib/services/composite-route.guard.ts +++ b/src/lib/services/composite-route.guard.ts @@ -2,15 +2,20 @@ import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } fro import { Injectable, InjectionToken, Injector } from '@angular/core'; import { firstValueFrom, from, of } from 'rxjs'; import { LoadingService } from '../loading'; +import { SkeletonService } from './skeleton.service'; @Injectable({ providedIn: 'root', }) export class CompositeRouteGuard implements CanActivate { - constructor(protected readonly _injector: Injector, private readonly _loadingService: LoadingService) {} + constructor( + protected readonly _injector: Injector, + private readonly _loadingService: LoadingService, + private readonly _skeletonService: SkeletonService, + ) {} async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - this._loadingService.start(); + this.#preChecks(route); const routeGuards = []>route.data['routeGuards']; @@ -27,14 +32,30 @@ export class CompositeRouteGuard implements CanActivate { const result = await firstValueFrom(canActivateResult); if (!result) { - this._loadingService.stop(); + this.#postChecks(route); return false; } } } - this._loadingService.stop(); - + this.#postChecks(route); return true; } + + #preChecks(route: ActivatedRouteSnapshot): void { + const skeleton = route.data['skeleton']; + if (skeleton) { + this._skeletonService.setType(skeleton); + } else { + this._loadingService.start(); + } + } + + #postChecks(route: ActivatedRouteSnapshot): void { + if (route.data['skeleton']) { + this._skeletonService.clear(); + } else { + this._loadingService.stop(); + } + } } diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index f7c3d27..a1d9cdc 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -9,3 +9,4 @@ export * from './iqser-user-preference.service'; export * from './language.service'; export * from './iqser-config.service'; export * from './api-path.interceptor'; +export * from './skeleton.service'; diff --git a/src/lib/services/skeleton.service.ts b/src/lib/services/skeleton.service.ts new file mode 100644 index 0000000..b56fe21 --- /dev/null +++ b/src/lib/services/skeleton.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class SkeletonService { + type$ = new BehaviorSubject(null); + + clear() { + this.type$.next(null); + } + + setType(type: string) { + this.type$.next(type); + } +} diff --git a/src/lib/shared/shared.module.ts b/src/lib/shared/shared.module.ts index c9ce789..30804d1 100644 --- a/src/lib/shared/shared.module.ts +++ b/src/lib/shared/shared.module.ts @@ -1,13 +1,14 @@ import { NgModule } from '@angular/core'; -import { SideNavComponent, SmallChipComponent, StatusBarComponent } from './index'; +import { LogoComponent, SideNavComponent, SmallChipComponent, StatusBarComponent } from './index'; import { CommonModule } from '@angular/common'; import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip'; +import { IqserIconsModule } from '../icons'; -const components = [SmallChipComponent, StatusBarComponent, SideNavComponent]; +const components = [SmallChipComponent, StatusBarComponent, SideNavComponent, LogoComponent]; @NgModule({ declarations: [...components], exports: [...components], - imports: [CommonModule, MatTooltipModule], + imports: [CommonModule, MatTooltipModule, IqserIconsModule], }) export class IqserSharedModule {} diff --git a/src/lib/skeleton/skeleton.module.ts b/src/lib/skeleton/skeleton.module.ts new file mode 100644 index 0000000..d68c94f --- /dev/null +++ b/src/lib/skeleton/skeleton.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SkeletonComponent } from './skeleton/skeleton.component'; +import { IqserSharedModule } from '../shared'; +import { IqserUsersModule } from '../users'; + +@NgModule({ + declarations: [SkeletonComponent], + exports: [SkeletonComponent], + imports: [CommonModule, IqserSharedModule, IqserUsersModule], +}) +export class IqserSkeletonModule {} diff --git a/src/lib/skeleton/skeleton/skeleton.component.html b/src/lib/skeleton/skeleton/skeleton.component.html new file mode 100644 index 0000000..a07bbfc --- /dev/null +++ b/src/lib/skeleton/skeleton/skeleton.component.html @@ -0,0 +1,3 @@ + + + diff --git a/src/lib/skeleton/skeleton/skeleton.component.scss b/src/lib/skeleton/skeleton/skeleton.component.scss new file mode 100644 index 0000000..187e4e4 --- /dev/null +++ b/src/lib/skeleton/skeleton/skeleton.component.scss @@ -0,0 +1,5 @@ +:host { + position: absolute; + width: 100vw; + z-index: 2; +} diff --git a/src/lib/skeleton/skeleton/skeleton.component.ts b/src/lib/skeleton/skeleton/skeleton.component.ts new file mode 100644 index 0000000..a93a858 --- /dev/null +++ b/src/lib/skeleton/skeleton/skeleton.component.ts @@ -0,0 +1,29 @@ +import { ChangeDetectionStrategy, Component, HostBinding, Input, TemplateRef } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { SkeletonService } from '../../services'; + +@Component({ + selector: 'iqser-skeleton [templates]', + templateUrl: './skeleton.component.html', + styleUrls: ['./skeleton.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SkeletonComponent { + x$ = new BehaviorSubject('Dashboard'); + @HostBinding('style.display') display = 'block'; + + @Input() templates: Record> = {}; + + // @HostBinding('style.display') display = 'none'; + + constructor(private readonly _skeletonService: SkeletonService) { + // this._skeletonService.type$.subscribe(type => { + // this.display = type ? 'block' : 'none'; + // }); + } + + get type$(): BehaviorSubject { + return this.x$; + // return this._skeletonService.type$; + } +}