diff --git a/projects/assets-library/assets/images/loader-expandable-row.gif b/projects/assets-library/assets/images/loader-expandable-row.gif
new file mode 100644
index 000000000..385eef56c
Binary files /dev/null and b/projects/assets-library/assets/images/loader-expandable-row.gif differ
diff --git a/projects/assets-library/assets/images/loader-page.gif b/projects/assets-library/assets/images/loader-page.gif
new file mode 100644
index 000000000..eaa47e252
Binary files /dev/null and b/projects/assets-library/assets/images/loader-page.gif differ
diff --git a/projects/assets-library/assets/images/loader-spinner.gif b/projects/assets-library/assets/images/loader-spinner.gif
new file mode 100644
index 000000000..21c09945a
Binary files /dev/null and b/projects/assets-library/assets/images/loader-spinner.gif differ
diff --git a/projects/assets-library/src/images/image-type.ts b/projects/assets-library/src/images/image-type.ts
new file mode 100644
index 000000000..bd2b6f98a
--- /dev/null
+++ b/projects/assets-library/src/images/image-type.ts
@@ -0,0 +1,12 @@
+export const enum ImagesAssetPath {
+ ErrorPage = 'assets/images/error-page.svg',
+ LoaderSpinner = 'assets/images/loader-spinner.gif',
+ LoaderPage = 'assets/images/loader-page.gif',
+ LoaderExpandableRow = 'assets/images/loader-expandable-row.gif'
+}
+
+export const enum LoaderType {
+ Spinner = 'spinner',
+ ExpandableRow = 'expandable-row',
+ Page = 'page'
+}
diff --git a/projects/assets-library/src/public-api.ts b/projects/assets-library/src/public-api.ts
index 1a6918a79..f42929a4f 100644
--- a/projects/assets-library/src/public-api.ts
+++ b/projects/assets-library/src/public-api.ts
@@ -6,3 +6,4 @@ export * from './icons/icon-type';
export * from './icons/icon-registry.service';
export * from './icons/icon-library.module';
export * from './icons/testing/icon-library-testing.module';
+export * from './images/image-type';
diff --git a/projects/components/src/load-async/load-async.directive.test.ts b/projects/components/src/load-async/load-async.directive.test.ts
index 901d5a2fc..1912327b1 100644
--- a/projects/components/src/load-async/load-async.directive.test.ts
+++ b/projects/components/src/load-async/load-async.directive.test.ts
@@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { fakeAsync, tick } from '@angular/core/testing';
-import { IconLibraryTestingModule, IconType } from '@hypertrace/assets-library';
+import { IconLibraryTestingModule, IconType, ImagesAssetPath, LoaderType } from '@hypertrace/assets-library';
import { CustomError, NavigationService } from '@hypertrace/common';
import { createDirectiveFactory, mockProvider, SpectatorDirective } from '@ngneat/spectator/jest';
import { EMPTY, Observable, of, throwError } from 'rxjs';
@@ -25,7 +25,7 @@ describe('Load Async directive', () => {
beforeEach(() => {
spectator = createDirective(`
-
+
{{ data }}
`);
@@ -35,7 +35,12 @@ describe('Load Async directive', () => {
spectator.setHostInput({
data$: of('content').pipe(delay(0))
});
+
expect(spectator.query(LoaderComponent)).toExist();
+ expect(spectator.query('.ht-loader img')).toExist();
+ expect(spectator.query('.ht-loader img')).toHaveClass(LoaderType.Page);
+ expect(spectator.query('.ht-loader img')).toHaveAttribute('src', ImagesAssetPath.LoaderPage);
+
expect(spectator.query('.test-data')).not.toExist();
tick();
spectator.detectChanges();
diff --git a/projects/components/src/load-async/load-async.directive.ts b/projects/components/src/load-async/load-async.directive.ts
index 3feff95d0..decc58602 100644
--- a/projects/components/src/load-async/load-async.directive.ts
+++ b/projects/components/src/load-async/load-async.directive.ts
@@ -9,6 +9,7 @@ import {
TemplateRef,
ViewContainerRef
} from '@angular/core';
+import { LoaderType } from '@hypertrace/assets-library';
import { Observable, ReplaySubject } from 'rxjs';
import { LoadAsyncContext, LoadAsyncService } from './load-async.service';
import {
@@ -23,8 +24,11 @@ import {
export class LoadAsyncDirective implements OnChanges, OnDestroy {
@Input('htLoadAsync')
public data$?: Observable
;
+ @Input('htLoadAsyncLoaderType')
+ public loaderType?: LoaderType;
+
private readonly wrapperParamsSubject: ReplaySubject = new ReplaySubject(1);
- private readonly wrapperInjector: Injector;
+ private readonly wrapperInjector!: Injector;
private wrapperView?: ComponentRef;
public constructor(
@@ -49,6 +53,7 @@ export class LoadAsyncDirective implements OnChanges, OnDestroy {
this.wrapperView = this.wrapperView || this.buildWrapperView();
this.wrapperParamsSubject.next({
state$: this.loadAsyncService.mapObservableState(this.data$),
+ loaderType: this.loaderType,
content: this.templateRef
});
} else {
diff --git a/projects/components/src/load-async/loader/loader.component.scss b/projects/components/src/load-async/loader/loader.component.scss
index c09c207c2..f9c88ac07 100644
--- a/projects/components/src/load-async/loader/loader.component.scss
+++ b/projects/components/src/load-async/loader/loader.component.scss
@@ -7,4 +7,19 @@
flex-direction: column;
justify-content: center;
align-items: center;
+
+ .page {
+ height: 50px;
+ width: 50px;
+ }
+
+ .spinner {
+ height: 20px;
+ width: 20px;
+ }
+
+ .expandable-row {
+ height: 20px;
+ width: auto;
+ }
}
diff --git a/projects/components/src/load-async/loader/loader.component.test.ts b/projects/components/src/load-async/loader/loader.component.test.ts
new file mode 100644
index 000000000..5e7594b92
--- /dev/null
+++ b/projects/components/src/load-async/loader/loader.component.test.ts
@@ -0,0 +1,49 @@
+import { CommonModule } from '@angular/common';
+import { ImagesAssetPath, LoaderType } from '@hypertrace/assets-library';
+import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
+import { LoaderComponent } from './loader.component';
+
+describe('Loader component', () => {
+ let spectator: SpectatorHost;
+
+ const createHost = createHostFactory({
+ component: LoaderComponent,
+ imports: [CommonModule]
+ });
+
+ test('Loader component when loader type is page', () => {
+ spectator = createHost(``);
+
+ expect(spectator.query('.ht-loader')).toExist();
+ expect(spectator.query('.ht-loader img')).toExist();
+ expect(spectator.query('.ht-loader img')).toHaveClass(LoaderType.Page);
+ expect(spectator.query('.ht-loader img')).toHaveAttribute('src', ImagesAssetPath.LoaderPage);
+ });
+
+ test('Loader component when loader type is not passed', () => {
+ spectator = createHost(``);
+
+ expect(spectator.query('.ht-loader')).toExist();
+ expect(spectator.query('.ht-loader img')).toExist();
+ expect(spectator.query('.ht-loader img')).toHaveClass(LoaderType.Spinner);
+ expect(spectator.query('.ht-loader img')).toHaveAttribute('src', ImagesAssetPath.LoaderSpinner);
+ });
+
+ test('Loader component when loader type is spinner', () => {
+ spectator = createHost(``);
+
+ expect(spectator.query('.ht-loader')).toExist();
+ expect(spectator.query('.ht-loader img')).toExist();
+ expect(spectator.query('.ht-loader img')).toHaveClass(LoaderType.Spinner);
+ expect(spectator.query('.ht-loader img')).toHaveAttribute('src', ImagesAssetPath.LoaderSpinner);
+ });
+
+ test('Loader component loader type is expandable row', () => {
+ spectator = createHost(``);
+
+ expect(spectator.query('.ht-loader')).toExist();
+ expect(spectator.query('.ht-loader img')).toExist();
+ expect(spectator.query('.ht-loader img')).toHaveClass(LoaderType.ExpandableRow);
+ expect(spectator.query('.ht-loader img')).toHaveAttribute('src', ImagesAssetPath.LoaderExpandableRow);
+ });
+});
diff --git a/projects/components/src/load-async/loader/loader.component.ts b/projects/components/src/load-async/loader/loader.component.ts
index 6834f7f3d..1c3ad5195 100644
--- a/projects/components/src/load-async/loader/loader.component.ts
+++ b/projects/components/src/load-async/loader/loader.component.ts
@@ -1,15 +1,37 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { IconType } from '@hypertrace/assets-library';
-import { IconSize } from '../../icon/icon-size';
+import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
+import { ImagesAssetPath, LoaderType } from '@hypertrace/assets-library';
+import { assertUnreachable } from '@hypertrace/common';
@Component({
selector: 'ht-loader',
styleUrls: ['./loader.component.scss'],
template: `
-
+
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class LoaderComponent {}
+export class LoaderComponent implements OnChanges {
+ @Input()
+ public type: LoaderType = LoaderType.Spinner;
+
+ public imagePath: ImagesAssetPath = ImagesAssetPath.LoaderSpinner;
+
+ public ngOnChanges(): void {
+ this.imagePath = this.getImagePathFromType();
+ }
+
+ private getImagePathFromType(): ImagesAssetPath {
+ switch (this.type) {
+ case LoaderType.ExpandableRow:
+ return ImagesAssetPath.LoaderExpandableRow;
+ case LoaderType.Page:
+ return ImagesAssetPath.LoaderPage;
+ case LoaderType.Spinner:
+ return ImagesAssetPath.LoaderSpinner;
+ default:
+ return assertUnreachable(this.type);
+ }
+ }
+}
diff --git a/projects/components/src/load-async/wrapper/load-async-wrapper.component.ts b/projects/components/src/load-async/wrapper/load-async-wrapper.component.ts
index 8fe07ed85..2cf5a88f7 100644
--- a/projects/components/src/load-async/wrapper/load-async-wrapper.component.ts
+++ b/projects/components/src/load-async/wrapper/load-async-wrapper.component.ts
@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, Inject, InjectionToken, TemplateRef } from '@angular/core';
-import { IconType } from '@hypertrace/assets-library';
-import { Observable } from 'rxjs';
+import { IconType, LoaderType } from '@hypertrace/assets-library';
+import { BehaviorSubject, Observable } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
import { LoadAsyncStateType } from '../load-async-state.type';
import { AsyncState, ErrorAsyncState, LoadAsyncContext } from '../load-async.service';
@@ -8,13 +8,14 @@ import { AsyncState, ErrorAsyncState, LoadAsyncContext } from '../load-async.ser
export const ASYNC_WRAPPER_PARAMETERS$ = new InjectionToken>(
'ASYNC_WRAPPER_PARAMETERS$'
);
-
@Component({
selector: 'ht-load-async-wrapper',
template: `
-
+
+
+
@@ -39,9 +40,15 @@ export class LoadAsyncWrapperComponent {
public content?: TemplateRef;
+ private readonly loaderTypeSubject: BehaviorSubject = new BehaviorSubject<
+ LoaderType | undefined
+ >(undefined);
+ public readonly loaderType$: Observable = this.loaderTypeSubject.asObservable();
+
public constructor(@Inject(ASYNC_WRAPPER_PARAMETERS$) parameters$: Observable) {
this.state$ = parameters$.pipe(
tap(params => (this.content = params.content)),
+ tap(params => this.loaderTypeSubject.next(params.loaderType)),
switchMap(parameter => parameter.state$),
tap(state => this.updateMessage(state.type, (state as Partial).description))
);
@@ -65,5 +72,6 @@ export class LoadAsyncWrapperComponent {
export interface LoadAsyncWrapperParameters {
state$: Observable;
+ loaderType?: LoaderType;
content: TemplateRef;
}
diff --git a/projects/components/src/not-found/not-found.component.ts b/projects/components/src/not-found/not-found.component.ts
index b5963a383..79f59add9 100644
--- a/projects/components/src/not-found/not-found.component.ts
+++ b/projects/components/src/not-found/not-found.component.ts
@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { ImagesAssetPath } from '@hypertrace/assets-library';
import { NavigationService } from '@hypertrace/common';
import { ButtonRole, ButtonStyle } from '../button/button';
-
@Component({
selector: 'ht-not-found',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -9,7 +9,7 @@ import { ButtonRole, ButtonStyle } from '../button/button';
template: `
-

+