Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
602cd22
feat: added skeleton component
Jan 13, 2022
3a3d19b
feat: added skeleton component
Jan 13, 2022
cba76a7
fix: integrating skeleton component with loader
Jan 13, 2022
e81d1df
style: adding loader types and fixing display
Jan 13, 2022
e879a4e
feat: added skeleton shapes and styling
Jan 14, 2022
24015a1
fix: linting errors
Jan 14, 2022
3ef1fe2
style: minor style adjustments to skeletons
Jan 14, 2022
5fd455a
feat: added donut skeleton shape
Jan 14, 2022
7f1cbcc
Merge branch 'main' into skeleton-loader
Jan 14, 2022
91a4832
fix: requested changes
Jan 14, 2022
bf2b846
fix: linting fixes
Jan 14, 2022
a6f5b7e
fix: default isOldLoaderFlag to true
Jan 16, 2022
1048339
fix: requested changes
Jan 16, 2022
3d048dd
test: updated for loader component changes
Jan 17, 2022
39ed6a3
Merge branch 'main' into skeleton-loader
Jan 17, 2022
dbdd5cf
fix: appeasing linter
Jan 17, 2022
6d90a76
test: skeleton component testing
Jan 17, 2022
d416769
Merge branch 'main' into skeleton-loader
Jan 17, 2022
91c7214
refactor: requested changes
Jan 18, 2022
17b08ee
fix: appeasing linter
Jan 18, 2022
86212a4
refactor: tests to account for requested changes
Jan 18, 2022
8f19a6d
refactor: some of requested changes
Jan 19, 2022
2e2048d
Merge branch 'main' into skeleton-loader
Jan 19, 2022
48418e0
refactor: to appease linter with switch
Jan 19, 2022
c99cebe
refactor: requested changes
Jan 19, 2022
429fc03
refactor: linter
Jan 19, 2022
12f0047
refactor: requested changes
Jan 19, 2022
6a07033
refactor: naming change
Jan 19, 2022
65a867e
refactor: name placement to after class definition
Jan 19, 2022
0b7c6b4
Merge branch 'main' into skeleton-loader
Jan 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion projects/components/src/load-async/load-async.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { IconModule } from '../icon/icon.module';
import { MessageDisplayModule } from '../message-display/message-display.module';
import { SkeletonModule } from '../skeleton/skeleton.module';
import { LoadAsyncDirective } from './load-async.directive';
import { LoaderComponent } from './loader/loader.component';
import { LoadAsyncWrapperComponent } from './wrapper/load-async-wrapper.component';

@NgModule({
declarations: [LoadAsyncDirective, LoadAsyncWrapperComponent, LoaderComponent],
imports: [CommonModule, IconModule, MessageDisplayModule],
imports: [CommonModule, IconModule, MessageDisplayModule, SkeletonModule],
exports: [LoadAsyncDirective]
})
export class LoadAsyncModule {}
5 changes: 4 additions & 1 deletion projects/components/src/load-async/load-async.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IconType } from '@hypertrace/assets-library';
import { CustomError } from '@hypertrace/common';
import { Observable, of } from 'rxjs';
import { catchError, defaultIfEmpty, map, startWith } from 'rxjs/operators';
import { SkeletonType } from '../skeleton/skeleton.component';
import { LoadAsyncStateType } from './load-async-state.type';

@Injectable({ providedIn: 'root' })
Expand Down Expand Up @@ -59,7 +60,9 @@ export interface LoadAsyncConfig {

export type AsyncState = LoadingAsyncState | SuccessAsyncState | NoDataOrErrorAsyncState;

export const enum LoaderType {
export type LoaderType = ImgLoaderType | SkeletonType;

export const enum ImgLoaderType {
Spinner = 'spinner',
ExpandableRow = 'expandable-row',
Page = 'page'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
.ht-loader {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

.page {
height: 50px;
Expand All @@ -23,3 +19,10 @@
width: auto;
}
}

.flex-centered {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
107 changes: 99 additions & 8 deletions projects/components/src/load-async/loader/loader.component.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,141 @@
import { CommonModule } from '@angular/common';
import { ImagesAssetPath } from '@hypertrace/assets-library';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { LoaderType } from '../load-async.service';
import { MockComponent } from 'ng-mocks';
import { SkeletonComponent, SkeletonType } from '../../skeleton/skeleton.component';
import { ImgLoaderType } from '../load-async.service';
import { LoaderComponent } from './loader.component';

describe('Loader component', () => {
let spectator: SpectatorHost<LoaderComponent>;

const createHost = createHostFactory({
component: LoaderComponent,
declarations: [MockComponent(SkeletonComponent)],
imports: [CommonModule]
});

test('Loader component when loader type is page', () => {
spectator = createHost(`<ht-loader [loaderType]="'${LoaderType.Page}'"></ht-loader>`);
spectator = createHost(`<ht-loader [loaderType]="'${ImgLoaderType.Page}'"></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).toHaveClass('flex-centered');
expect(spectator.query('.ht-loader img')).toExist();
expect(spectator.query('.ht-loader img')).toHaveClass(LoaderType.Page);
expect(spectator.query('.ht-loader img')).toHaveClass(ImgLoaderType.Page);
expect(spectator.query('.ht-loader img')).toHaveAttribute('src', ImagesAssetPath.LoaderPage);
});

test('Loader component when loader type is not passed', () => {
spectator = createHost(`<ht-loader></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).toHaveClass('flex-centered');
expect(spectator.query('.ht-loader img')).toExist();
expect(spectator.query('.ht-loader img')).toHaveClass(LoaderType.Spinner);
expect(spectator.query('.ht-loader img')).toHaveClass(ImgLoaderType.Spinner);
expect(spectator.query('.ht-loader img')).toHaveAttribute('src', ImagesAssetPath.LoaderSpinner);
});

test('Loader component when loader type is spinner', () => {
spectator = createHost(`<ht-loader [loaderType]="'${LoaderType.Spinner}'"></ht-loader>`);
spectator = createHost(`<ht-loader [loaderType]="'${ImgLoaderType.Spinner}'"></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).toHaveClass('flex-centered');
expect(spectator.query('.ht-loader img')).toExist();
expect(spectator.query('.ht-loader img')).toHaveClass(LoaderType.Spinner);
expect(spectator.query('.ht-loader img')).toHaveClass(ImgLoaderType.Spinner);
expect(spectator.query('.ht-loader img')).toHaveAttribute('src', ImagesAssetPath.LoaderSpinner);
});

test('Loader component loader type is expandable row', () => {
spectator = createHost(`<ht-loader [loaderType]="'${LoaderType.ExpandableRow}'"></ht-loader>`);
spectator = createHost(`<ht-loader [loaderType]="'${ImgLoaderType.ExpandableRow}'"></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).toHaveClass('flex-centered');
expect(spectator.query('.ht-loader img')).toExist();
expect(spectator.query('.ht-loader img')).toHaveClass(LoaderType.ExpandableRow);
expect(spectator.query('.ht-loader img')).toHaveClass(ImgLoaderType.ExpandableRow);
expect(spectator.query('.ht-loader img')).toHaveAttribute('src', ImagesAssetPath.LoaderExpandableRow);
});

test('Should use old loader type by default', () => {
spectator = createHost(`<ht-loader></ht-loader>`);

expect(spectator.component.isOldLoaderType).toBe(true);
expect(spectator.query(SkeletonComponent)).not.toExist();
});

test('Should use corresponding skeleton component for loader type rectangle', () => {
spectator = createHost(`<ht-loader [loaderType]="'${SkeletonType.Rectangle}'" ></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).not.toHaveClass('flex-centered');

const skeletonComponent = spectator.query(SkeletonComponent);
expect(skeletonComponent).toExist();
expect(skeletonComponent).toHaveAttribute('shapeStyle', SkeletonType.Rectangle);
});

test('Should use corresponding skeleton component for loader type rectangle text', () => {
spectator = createHost(`<ht-loader [loaderType]="'${SkeletonType.Text}'" ></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).not.toHaveClass('flex-centered');

const skeletonComponent = spectator.query(SkeletonComponent);
expect(skeletonComponent).toExist();
expect(skeletonComponent).toHaveAttribute('shapeStyle', SkeletonType.Text);
});

test('Should use corresponding skeleton component for loader type circle', () => {
spectator = createHost(`<ht-loader [loaderType]="'${SkeletonType.Circle}'" ></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).not.toHaveClass('flex-centered');

const skeletonComponent = spectator.query(SkeletonComponent);
expect(skeletonComponent).toExist();
expect(skeletonComponent).toHaveAttribute('shapeStyle', SkeletonType.Circle);
});

test('Should use corresponding skeleton component for loader type square', () => {
spectator = createHost(`<ht-loader [loaderType]="'${SkeletonType.Square}'" ></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).not.toHaveClass('flex-centered');

const skeletonComponent = spectator.query(SkeletonComponent);
expect(skeletonComponent).toExist();
expect(skeletonComponent).toHaveAttribute('shapeStyle', SkeletonType.Square);
});

test('Should use corresponding skeleton component for loader type table row', () => {
spectator = createHost(`<ht-loader [loaderType]="'${SkeletonType.TableRow}'" ></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).not.toHaveClass('flex-centered');

const skeletonComponent = spectator.query(SkeletonComponent);
expect(skeletonComponent).toExist();
expect(skeletonComponent).toHaveAttribute('shapeStyle', SkeletonType.TableRow);
});

test('Should use corresponding skeleton component for loader type donut', () => {
spectator = createHost(`<ht-loader [loaderType]="'${SkeletonType.Donut}'" ></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).not.toHaveClass('flex-centered');

const skeletonComponent = spectator.query(SkeletonComponent);
expect(skeletonComponent).toExist();
expect(skeletonComponent).toHaveAttribute('shapeStyle', SkeletonType.Donut);
});

test('Should use corresponding skeleton component for loader type list item', () => {
spectator = createHost(`<ht-loader [loaderType]="'${SkeletonType.ListItem}'" ></ht-loader>`);

expect(spectator.query('.ht-loader')).toExist();
expect(spectator.query('.ht-loader')).not.toHaveClass('flex-centered');

const skeletonComponent = spectator.query(SkeletonComponent);
expect(skeletonComponent).toExist();
expect(skeletonComponent).toHaveAttribute('shapeStyle', SkeletonType.ListItem);
});
});
59 changes: 50 additions & 9 deletions projects/components/src/load-async/loader/loader.component.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,75 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { ImagesAssetPath } from '@hypertrace/assets-library';
import { LoaderType } from '../load-async.service';
import { assertUnreachable } from '@hypertrace/common';
import { SkeletonType } from '../../skeleton/skeleton.component';
import { ImgLoaderType, LoaderType } from '../load-async.service';

@Component({
selector: 'ht-loader',
styleUrls: ['./loader.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="ht-loader">
<img [ngClass]="[this.currentLoaderType]" [src]="this.getImagePathFromType(this.currentLoaderType)" />
<div class="ht-loader" [ngClass]="{ 'flex-centered': this.isOldLoaderType }">
<ng-container *ngIf="!this.isOldLoaderType; else oldLoaderTemplate">
<ht-skeleton [shapeStyle]="this.skeletonType"></ht-skeleton>
</ng-container>

<ng-template #oldLoaderTemplate>
<img [ngClass]="[this.currentLoaderType]" [src]="this.imagePath" />
</ng-template>
</div>
`
})
export class LoaderComponent implements OnChanges {
@Input()
public loaderType?: LoaderType;

public currentLoaderType: LoaderType = LoaderType.Spinner;
public skeletonType: SkeletonType = SkeletonType.Rectangle;

public currentLoaderType: LoaderType = ImgLoaderType.Spinner;

public imagePath: ImagesAssetPath = ImagesAssetPath.LoaderSpinner;

public isOldLoaderType: boolean = true;

public ngOnChanges(): void {
this.currentLoaderType = this.loaderType ?? LoaderType.Spinner;
this.currentLoaderType = this.loaderType ?? ImgLoaderType.Spinner;

if (this.determineIfOldLoaderType(this.currentLoaderType)) {
this.isOldLoaderType = true;
this.imagePath = this.getImagePathFromType(this.currentLoaderType as ImgLoaderType);
} else {
this.isOldLoaderType = false;
this.skeletonType = this.currentLoaderType as SkeletonType;
}
}

public determineIfOldLoaderType(loaderType: LoaderType): boolean {
switch (loaderType) {
case ImgLoaderType.Spinner:
case ImgLoaderType.ExpandableRow:
case ImgLoaderType.Page:
return true;
case SkeletonType.Circle:
case SkeletonType.Text:
case SkeletonType.ListItem:
case SkeletonType.Rectangle:
case SkeletonType.Square:
case SkeletonType.TableRow:
case SkeletonType.Donut:
return false;
default:
return assertUnreachable(loaderType);
}
}

public getImagePathFromType(loaderType: LoaderType): ImagesAssetPath {
public getImagePathFromType(loaderType: ImgLoaderType): ImagesAssetPath {
switch (loaderType) {
case LoaderType.ExpandableRow:
case ImgLoaderType.ExpandableRow:
return ImagesAssetPath.LoaderExpandableRow;
case LoaderType.Page:
case ImgLoaderType.Page:
return ImagesAssetPath.LoaderPage;
case LoaderType.Spinner:
case ImgLoaderType.Spinner:
default:
return ImagesAssetPath.LoaderSpinner;
}
Expand Down
3 changes: 3 additions & 0 deletions projects/components/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ export * from './load-async/load-async.module';
export * from './load-async/load-async.service';
export * from './load-async/load-async-state.type';

// Skeleton
export * from './skeleton/skeleton.component';

// Message Display
export { MessageDisplayComponent } from './message-display/message-display.component';
export { MessageDisplayModule } from './message-display/message-display.module';
Expand Down
Loading