Merged PR 836: #2161 Ui Scroll Container Implementation

#2161 Ui Scroll Container Implementation
This commit is contained in:
Nino Righi
2021-09-15 11:35:43 +00:00
committed by Andreas Schickinger
parent 7ad7177f89
commit 6c52fcd555
76 changed files with 913 additions and 342 deletions

View File

@@ -3056,6 +3056,86 @@
}
}
}
},
"@ui/scroll-container": {
"projectType": "library",
"root": "apps/ui/scroll-container",
"sourceRoot": "apps/ui/scroll-container/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/ui/scroll-container/tsconfig.lib.json",
"project": "apps/ui/scroll-container/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/ui/scroll-container/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/ui/scroll-container/src/test.ts",
"tsConfig": "apps/ui/scroll-container/tsconfig.spec.json",
"karmaConfig": "apps/ui/scroll-container/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/ui/scroll-container/tsconfig.lib.json",
"apps/ui/scroll-container/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@ui/loader": {
"projectType": "library",
"root": "apps/ui/loader",
"sourceRoot": "apps/ui/loader/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/ui/loader/tsconfig.lib.json",
"project": "apps/ui/loader/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/ui/loader/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/ui/loader/src/test.ts",
"tsConfig": "apps/ui/loader/tsconfig.spec.json",
"karmaConfig": "apps/ui/loader/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/ui/loader/tsconfig.lib.json",
"apps/ui/loader/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "sales"

View File

@@ -31,6 +31,7 @@ import { CustomerOrderItemCardComponent } from './customer-order-details/order-i
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { HistoryModule } from '@modal/history';
import { CantSelectGuestModalModule } from '../modals/cant-select-guest/cant-select-guest-modal.module';
import { UiScrollContainerModule } from '@ui/scroll-container';
@NgModule({
imports: [
CommonModule,
@@ -44,6 +45,7 @@ import { CantSelectGuestModalModule } from '../modals/cant-select-guest/cant-sel
FormsModule,
UiRadioModule,
UiCommonModule,
UiScrollContainerModule,
UiSpinnerModule,
HistoryModule,
CantSelectGuestModalModule,

View File

@@ -2,7 +2,12 @@
<div class="hits">{{ hits$ | async }} Titel</div>
</div>
<div class="scroll-container" *ngIf="!(listEmpty$ | async); else emptyMessage">
<ui-scroll-container
*ngIf="!(listEmpty$ | async); else emptyMessage"
[loading]="loading$ | async"
[deltaEnd]="150"
[itemLength]="(items$ | async).length"
>
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; let lastOrderNumber = last">
<ng-container
@@ -27,8 +32,7 @@
<div class="divider" *ngIf="!lastOrderNumber"></div>
</ng-container>
</shared-goods-in-out-order-group>
<div *ngIf="loading$ | async; let loading" [uiLoader]="loading"><div class="loading-text">Inhalte werden geladen</div></div>
</div>
</ui-scroll-container>
<ng-template #emptyMessage>
<div class="empty-message">

View File

@@ -10,19 +10,10 @@
}
}
.scroll-container {
@apply h-full overflow-y-scroll relative grid grid-flow-row gap-3;
max-height: calc(100vh - 350px);
}
.empty-message {
@apply bg-white text-center font-semibold text-inactive-branch py-10 rounded-card;
}
.loading-text {
@apply mt-2 text-center font-semibold text-base text-inactive-branch;
}
.divider {
@apply h-px-2;
}

View File

@@ -19,11 +19,11 @@ import { GoodsInCleanupListStore } from './goods-in-cleanup-list.store';
export class GoodsInCleanupListComponent implements OnInit, OnDestroy {
@ViewChildren(GoodsInOutOrderGroupItemComponent) listItems: QueryList<GoodsInOutOrderGroupItemComponent>;
items$ = this._store.results$;
items$ = this._store.results$.pipe(shareReplay());
hits$ = this._store.hits$;
loading$ = this._store.fetching$;
loading$ = this._store.fetching$.pipe(shareReplay());
selectedOrderItemSubsetIds$ = this._store.selectedOrderItemSubsetIds$;

View File

@@ -3,11 +3,12 @@ import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { GoodsInOutOrderGroupModule } from '@shared/goods-in-out';
import { UiCommonModule } from '@ui/common';
import { UiScrollContainerModule } from '@ui/scroll-container';
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { GoodsInCleanupListComponent } from './goods-in-cleanup-list.component';
@NgModule({
imports: [CommonModule, UiCommonModule, GoodsInOutOrderGroupModule, UiSpinnerModule, RouterModule],
imports: [CommonModule, UiCommonModule, GoodsInOutOrderGroupModule, UiSpinnerModule, UiScrollContainerModule, RouterModule],
exports: [GoodsInCleanupListComponent],
declarations: [GoodsInCleanupListComponent],
providers: [],

View File

@@ -1,11 +0,0 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'goods-in-list-item-loading',
templateUrl: 'goods-in-list-item-loading.component.html',
styleUrls: ['goods-in-list-item-loading.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInListItemLoadingComponent {
constructor() {}
}

View File

@@ -6,12 +6,11 @@ import { UiInputModule } from '@ui/input';
import { FormsModule } from '@angular/forms';
import { PipesModule } from 'apps/shared/goods-in-out/src/lib/pipes/pipes.module';
import { GoodsInListItemComponent } from './goods-in-list-item.component';
import { GoodsInListItemLoadingComponent } from './goods-in-list-item-loading/goods-in-list-item-loading.component';
@NgModule({
imports: [CommonModule, PipesModule, UiIconModule, ProductImageModule, FormsModule, UiInputModule],
exports: [GoodsInListItemComponent, GoodsInListItemLoadingComponent],
declarations: [GoodsInListItemComponent, GoodsInListItemLoadingComponent],
exports: [GoodsInListItemComponent],
declarations: [GoodsInListItemComponent],
providers: [],
})
export class GoodsInListItemModule {}

View File

@@ -1,55 +0,0 @@
import { Injectable } from '@angular/core';
import { DomainGoodsService } from '@domain/oms';
import { ListResponseArgsOfOrderItemListItemDTO } from '@swagger/oms';
import { BehaviorSubject, Subject } from 'rxjs';
import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
@Injectable()
export class GoodsInListService {
loading$ = new BehaviorSubject<boolean>(false);
searchResponse$ = new BehaviorSubject<ListResponseArgsOfOrderItemListItemDTO>(undefined);
private search$ = new Subject<boolean>();
constructor(private domainGoodsInService: DomainGoodsService) {
this.initSearch();
}
private initSearch() {
this.search$
.pipe(
tap(() => this.loading$.next(true)),
withLatestFrom(this.searchResponse$),
switchMap(([refresh, searchResponse]) =>
this.domainGoodsInService
.goodsInList({
take: refresh ? searchResponse?.result?.length || 20 : 20,
skip: refresh ? 0 : searchResponse?.result?.length || 0,
})
.pipe(
map((response) => {
if (refresh) {
return response;
} else {
return searchResponse?.result
? ({
...response,
result: [...searchResponse.result, ...response.result],
} as ListResponseArgsOfOrderItemListItemDTO)
: response;
}
})
)
),
tap(() => {
this.loading$.next(false);
})
)
.subscribe(this.searchResponse$);
}
search(refresh?: boolean) {
this.search$.next(!!refresh);
}
}

View File

@@ -1,23 +1,11 @@
<div class="list-count">{{ hits$ | async }} Titel</div>
<div class="list-main">
<div class="scroll-container">
<ng-container *ngFor="let item of items$ | async; let last = last">
<goods-in-list-item-loading *ngIf="loading$ | async"></goods-in-list-item-loading>
<goods-in-list-item
*ngIf="!(loading$ | async)"
uiIsInViewport
[options]="viewportEnterOptions"
(viewportEntered)="checkIfReload($event)"
[class.last]="last"
[item]="item"
[editSsc]="editSsc"
(refresh)="refreshList()"
></goods-in-list-item>
<hr />
</ng-container>
<goods-in-list-item-loading *ngIf="loading$ | async"></goods-in-list-item-loading>
</div>
</div>
<ui-scroll-container [loading]="loading$ | async" (reachEnd)="loadMore()" [deltaEnd]="150" [itemLength]="(items$ | async).length">
<ng-container *ngFor="let item of items$ | async">
<goods-in-list-item [item]="item" [editSsc]="editSsc" (refresh)="refreshList()"></goods-in-list-item>
<hr />
</ng-container>
</ui-scroll-container>
<div class="actions">
<button *ngIf="!editSsc" class="cta-edit-ssc cta-action-primary" (click)="editSsc = true">

View File

@@ -6,21 +6,8 @@
@apply text-active-branch text-right mb-3 font-semibold text-base;
}
.list-main {
@apply bg-white rounded-t-card pb-5 shadow-card;
height: calc(100vh - 310px);
.list-main-title {
@apply text-2xl font-bold text-center;
}
.list-main-paragraph {
@apply text-2xl mb-12 text-center;
}
.scroll-container {
@apply h-full overflow-y-scroll relative;
}
::ng-deep page-goods-in-list ui-scroll-container .scroll-container {
@apply bg-white;
}
hr {

View File

@@ -1,26 +1,27 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainOmsService } from '@domain/oms';
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { first, map, shareReplay, takeUntil } from 'rxjs/operators';
import { map, shareReplay, takeUntil } from 'rxjs/operators';
import { GoodsInListItemComponent } from './goods-in-list-item/goods-in-list-item.component';
import { GoodsInListService } from './goods-in-list-service';
import { GoodsInListStore } from './goods-in-list.store';
@Component({
selector: 'page-goods-in-list',
templateUrl: 'goods-in-list.component.html',
styleUrls: ['goods-in-list.component.scss'],
providers: [GoodsInListService],
providers: [GoodsInListStore],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChildren(GoodsInListItemComponent) listItems: QueryList<GoodsInListItemComponent>;
items$ = this._goodsInListService.searchResponse$.pipe(map((res) => res?.result || []));
items$ = this._store.results$.pipe(shareReplay());
hits$ = this._goodsInListService.searchResponse$.pipe(map((res) => res?.hits || 0));
hits$ = this._store.hits$;
loading$ = this._goodsInListService.loading$.pipe(shareReplay());
loading$ = this._store.loading$.pipe(shareReplay());
editSsc: boolean;
@@ -28,34 +29,31 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
showSaveSscSpinner$ = new BehaviorSubject<boolean>(false);
protected readonly viewportEnterOptions: IntersectionObserverInit = {
threshold: 0.75,
};
private _onDestroy$ = new Subject();
constructor(
private _goodsInListService: GoodsInListService,
private _breadcrumb: BreadcrumbService,
private _domainOmsService: DomainOmsService
private _domainOmsService: DomainOmsService,
private _store: GoodsInListStore,
private _router: Router
) {}
ngOnInit() {
this._goodsInListService.search();
this.initInitialSearch();
this.updateBreadcrumb();
}
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
}
ngAfterViewInit(): void {
this.registerEditSscDisabled();
this.listItems.changes.pipe(takeUntil(this._onDestroy$)).subscribe(() => this.registerEditSscDisabled());
}
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
}
async updateBreadcrumb() {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: 'goods-in',
@@ -72,11 +70,23 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
);
}
async checkIfReload(target: HTMLElement) {
const hits = await this.hits$.pipe(first()).toPromise();
const itemLength = (await this.items$.pipe(first()).toPromise())?.length;
if (target.classList.contains('last') && itemLength < hits) {
this._goodsInListService.search();
initInitialSearch() {
if (this._store.hits === 0) {
this._store.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.hits === 0) {
await this._router.navigate(['/goods/in']);
} else {
await this.updateBreadcrumb();
}
});
}
this._store.search();
}
async loadMore() {
if (this._store.hits > this._store.results.length && !this._store.loading) {
this._store.search();
}
}
@@ -106,6 +116,6 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
}
refreshList() {
this._goodsInListService.search(true);
this._store.reload();
}
}

View File

@@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiCommonModule } from '@ui/common';
import { UiIconModule } from '@ui/icon';
import { UiScrollContainerModule } from '@ui/scroll-container';
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { GoodsInListItemModule } from './goods-in-list-item/goods-in-list-item.module';
import { GoodsInListReorderModalModule } from './goods-in-list-reorder-modal/goods-in-list-reorder-modal.module';
@@ -9,7 +10,15 @@ import { GoodsInListReorderModalModule } from './goods-in-list-reorder-modal/goo
import { GoodsInListComponent } from './goods-in-list.component';
@NgModule({
imports: [CommonModule, UiCommonModule, UiIconModule, GoodsInListItemModule, GoodsInListReorderModalModule, UiSpinnerModule],
imports: [
CommonModule,
UiCommonModule,
UiIconModule,
GoodsInListItemModule,
GoodsInListReorderModalModule,
UiScrollContainerModule,
UiSpinnerModule,
],
exports: [],
declarations: [GoodsInListComponent],
providers: [],

View File

@@ -1,63 +0,0 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
import { RouterTestingModule } from '@angular/router/testing';
import { GoodsInListService } from './goods-in-list-service';
import { DomainGoodsService } from '@domain/oms';
import { of } from 'rxjs';
import { ListResponseArgsOfOrderItemListItemDTO } from '@swagger/oms';
import { first } from 'rxjs/operators';
describe('GoodsInListService', () => {
let spectator: SpectatorService<GoodsInListService>;
let domainGoodsInServiceMock: jasmine.SpyObj<DomainGoodsService>;
const createService = createServiceFactory({
service: GoodsInListService,
imports: [RouterTestingModule],
mocks: [DomainGoodsService],
});
beforeEach(() => {
spectator = createService();
domainGoodsInServiceMock = spectator.inject(DomainGoodsService);
});
it('should create the service', () => {
expect(spectator.service).toBeTruthy();
});
describe('search()', () => {
it('should call next on search$ subject', () => {
spectator.service['search$'].next = jasmine.createSpy();
spectator.service.search();
expect(spectator.service['search$'].next).toHaveBeenCalled();
});
it('should set loading$ to true', () => {
spectator.service.loading$.next = jasmine.createSpy();
spectator.service.search();
expect(spectator.service.loading$.next).toHaveBeenCalledWith(true);
});
it('should call domainGoodsInService.goodsInList() with take 20 and result 0 when searchResponse is not set', () => {
domainGoodsInServiceMock.goodsInList = jasmine.createSpy().and.returnValue(of({}));
spectator.service.search();
expect(domainGoodsInServiceMock.goodsInList).toHaveBeenCalledWith({ take: 20, skip: 0 });
});
it('should call domainGoodsInService.goodsInList() with take 20 and result length when searchResponse is set', () => {
spectator.service.searchResponse$.next({ result: new Array(20) } as ListResponseArgsOfOrderItemListItemDTO);
domainGoodsInServiceMock.goodsInList = jasmine.createSpy().and.returnValue(of({}));
spectator.service.search();
expect(domainGoodsInServiceMock.goodsInList).toHaveBeenCalledWith({ take: 20, skip: 20 });
});
it('should add the response to the current searchResponse', async () => {
domainGoodsInServiceMock.goodsInList = jasmine.createSpy().and.returnValue(of({ result: new Array(20) }));
spectator.service.search();
spectator.service.search();
const response = await spectator.service.searchResponse$.pipe(first()).toPromise();
expect(response.result.length).toBe(40);
});
});
});

View File

@@ -0,0 +1,131 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainGoodsService } from '@domain/oms';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ListResponseArgsOfOrderItemListItemDTO, OrderItemListItemDTO, QueryTokenDTO } from '@swagger/oms';
import { isResponseArgs } from '@utils/object';
import { Subject } from 'rxjs';
import { switchMap, tap, withLatestFrom } from 'rxjs/operators';
export interface GoodsInListState {
message?: string;
loading: boolean;
hits: number;
results: OrderItemListItemDTO[];
}
@Injectable()
export class GoodsInListStore extends ComponentStore<GoodsInListState> {
get results() {
return this.get((s) => s.results);
}
readonly results$ = this.select((s) => s.results);
get hits() {
return this.get((s) => s.hits);
}
readonly hits$ = this.select((s) => s.hits);
get loading() {
return this.get((s) => s.loading);
}
readonly loading$ = this.select((s) => s.loading);
private _searchResultSubject = new Subject<ListResponseArgsOfOrderItemListItemDTO>();
readonly searchResult$ = this._searchResultSubject.asObservable();
constructor(private _domainGoodsInService: DomainGoodsService) {
super({
loading: false,
hits: 0,
results: [],
});
}
search = this.effect(($) =>
$.pipe(
tap((_) => this.patchState({ loading: true })),
withLatestFrom(this.results$),
switchMap(([_, _results]) =>
this.searchRequest().pipe(
tapResponse(
(res) => {
const results = [...(_results ?? []), ...(res.result ?? [])];
this.patchState({
hits: res.hits,
results,
loading: false,
});
this._searchResultSubject.next(res);
},
(err: Error) => {
if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
this._searchResultSubject.next(err.error);
} else {
this._searchResultSubject.next({
error: true,
message: err.message,
});
}
this.patchState({ loading: false });
console.error('GoodsInListStore.search()', err);
}
)
)
)
)
);
reload = this.effect(($) =>
$.pipe(
tap((_) => this.patchState({ loading: true })),
withLatestFrom(this.results$),
switchMap(([_, results]) =>
this.searchRequest({ take: results?.length ?? 0, skip: 0 }).pipe(
tapResponse(
(res) => {
this.patchState({
hits: res.hits,
results: res.result,
loading: false,
});
this._searchResultSubject.next(res);
},
(err: Error) => {
if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
this._searchResultSubject.next(err.error);
} else {
this._searchResultSubject.next({
error: true,
message: err.message,
});
}
this.patchState({ loading: false });
console.error('GoodsInListStore.reload()', err);
}
)
)
)
)
);
clearResults() {
this.patchState({
loading: false,
hits: 0,
message: undefined,
results: [],
});
}
searchRequest(options?: { take?: number; skip?: number }) {
const queryToken: QueryTokenDTO = {
skip: options?.skip ?? this.results.length,
take: options?.take ?? 20,
};
return this._domainGoodsInService.goodsInList(queryToken);
}
}

View File

@@ -2,7 +2,7 @@
<div class="hits">{{ hits$ | async }} Titel</div>
</div>
<div class="scroll-container" uiScrollContainer (reachEnd)="loadMore()" [deltaEnd]="150">
<ui-scroll-container [loading]="loading$ | async" (reachEnd)="loadMore()" [deltaEnd]="150" [itemLength]="(items$ | async).length">
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; let lastOrderNumber = last">
<ng-container
@@ -28,8 +28,7 @@
<div class="divider" *ngIf="!lastOrderNumber"></div>
</ng-container>
</shared-goods-in-out-order-group>
<div *ngIf="loading$ | async; let loading" [uiLoader]="loading"><div class="loading-text">Inhalte werden geladen</div></div>
</div>
</ui-scroll-container>
<div class="actions" *ngIf="actions$ | async; let actions">
<button

View File

@@ -10,15 +10,6 @@
}
}
.scroll-container {
@apply h-full overflow-y-scroll relative grid grid-flow-row gap-3;
max-height: calc(100vh - 350px);
}
.loading-text {
@apply mt-2 text-center font-semibold text-base text-inactive-branch;
}
.divider {
@apply h-px-2;
}

View File

@@ -19,11 +19,11 @@ import { GoodsInReservationStore } from './goods-in-reservation.store';
export class GoodsInReservationComponent implements OnInit, OnDestroy {
@ViewChildren(GoodsInOutOrderGroupItemComponent) listItems: QueryList<GoodsInOutOrderGroupItemComponent>;
items$ = this._store.results$;
items$ = this._store.results$.pipe(shareReplay());
hits$ = this._store.hits$;
loading$ = this._store.loading$;
loading$ = this._store.loading$.pipe(shareReplay());
takeAwayAvailabilities$ = this._store.takeAwayAvailabilities$.pipe(shareReplay());

View File

@@ -2,12 +2,13 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { GoodsInOutOrderGroupModule } from '@shared/goods-in-out';
import { UiCommonModule } from '@ui/common';
import { UiScrollContainerModule } from 'apps/ui/scroll-container/src/public-api';
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { GoodsInReservationComponent } from './goods-in-reservation.component';
@NgModule({
imports: [CommonModule, UiCommonModule, GoodsInOutOrderGroupModule, UiSpinnerModule],
imports: [CommonModule, UiCommonModule, UiScrollContainerModule, GoodsInOutOrderGroupModule, UiSpinnerModule],
exports: [GoodsInReservationComponent],
declarations: [GoodsInReservationComponent],
providers: [],

View File

@@ -1,5 +1,5 @@
<div class="hits">{{ hits$ | async }} Titel</div>
<div class="scroll-container" uiScrollContainer (reachEnd)="loadMore()" [deltaEnd]="150">
<ui-scroll-container [loading]="loading$ | async" (reachEnd)="loadMore()" [deltaEnd]="150" [itemLength]="(items$ | async).length">
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; let lastOrderNumber = last">
<ng-container
@@ -22,5 +22,4 @@
<div class="divider" *ngIf="!lastOrderNumber"></div>
</ng-container>
</shared-goods-in-out-order-group>
<div *ngIf="loading$ | async; let loading" [uiLoader]="loading"><div class="loading-text">Inhalte werden geladen</div></div>
</div>
</ui-scroll-container>

View File

@@ -6,15 +6,6 @@
@apply text-active-branch text-right mb-3 font-semibold text-base;
}
.scroll-container {
@apply h-full overflow-y-scroll relative grid grid-flow-row gap-3;
max-height: calc(100vh - 280px);
}
.loading-text {
@apply mt-2 text-center font-semibold text-base text-inactive-branch;
}
.divider {
height: 2px;
}

View File

@@ -1,5 +1,5 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
import { first, takeUntil } from 'rxjs/operators';
import { first, shareReplay, takeUntil } from 'rxjs/operators';
import { OrderItemListItemDTO } from '@swagger/oms';
import { ActivatedRoute, Router } from '@angular/router';
import { GoodsInSearchStore } from '../goods-in-search.store';
@@ -13,11 +13,11 @@ import { BreadcrumbService } from '@core/breadcrumb';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsInSearchResultsComponent implements OnInit, OnDestroy {
items$ = this._goodsInSearchStore.results$;
items$ = this._goodsInSearchStore.results$.pipe(shareReplay());
hits$ = this._goodsInSearchStore.hits$;
loading$ = this._goodsInSearchStore.fetching$;
loading$ = this._goodsInSearchStore.fetching$.pipe(shareReplay());
byBuyerNumberFn = (item: OrderItemListItemDTO) => item.buyerNumber;

View File

@@ -4,9 +4,10 @@ import { CommonModule } from '@angular/common';
import { GoodsInSearchResultsComponent } from './goods-in-search-results.component';
import { UiCommonModule } from '@ui/common';
import { GoodsInOutOrderGroupModule } from '@shared/goods-in-out';
import { UiScrollContainerModule } from '@ui/scroll-container';
@NgModule({
imports: [CommonModule, GoodsInOutOrderGroupModule, UiCommonModule],
imports: [CommonModule, GoodsInOutOrderGroupModule, UiCommonModule, UiScrollContainerModule],
exports: [GoodsInSearchResultsComponent],
declarations: [GoodsInSearchResultsComponent],
})

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UiLoaderComponent } from './ui-loader.component';
import { UiIconModule } from '@ui/icon';
@NgModule({
imports: [CommonModule, UiIconModule],
exports: [UiLoaderComponent],
declarations: [UiLoaderComponent],
})
export class UiLoaderModule {}

View File

@@ -1,5 +1,5 @@
<div class="hits">{{ hits$ | async }} Titel</div>
<div class="scroll-container" uiScrollContainer (reachEnd)="loadMore()" [deltaEnd]="150">
<ui-scroll-container [loading]="loading$ | async" (reachEnd)="loadMore()" [deltaEnd]="150" [itemLength]="(items$ | async).length">
<shared-goods-in-out-order-group *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; let lastOrderNumber = last">
<ng-container
@@ -24,8 +24,7 @@
<div class="divider" *ngIf="!lastOrderNumber"></div>
</ng-container>
</shared-goods-in-out-order-group>
<div *ngIf="loading$ | async; let loading" [uiLoader]="loading"><div class="loading-text">Inhalte werden geladen</div></div>
</div>
</ui-scroll-container>
<button
class="cta-fetched"

View File

@@ -6,15 +6,6 @@
@apply text-active-customer text-right mb-3 font-semibold text-base;
}
.scroll-container {
@apply h-full overflow-y-scroll relative grid grid-flow-row gap-3;
max-height: calc(100vh - 280px);
}
.loading-text {
@apply mt-2 text-center font-semibold text-base text-inactive-customer;
}
.divider {
height: 2px;
}

View File

@@ -20,11 +20,11 @@ export interface GoodsOutSearchResultsState {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GoodsOutSearchResultsComponent extends ComponentStore<GoodsOutSearchResultsState> implements OnInit, OnDestroy {
items$ = this._goodsOutSearchStore.results$;
items$ = this._goodsOutSearchStore.results$.pipe(shareReplay());
hits$ = this._goodsOutSearchStore.hits$;
loading$ = this._goodsOutSearchStore.fetching$;
loading$ = this._goodsOutSearchStore.fetching$.pipe(shareReplay());
loadingFetchedActionButton$ = new BehaviorSubject<boolean>(false);
selectedOrderItemSubsetIds$ = this.select((s) => s.selectedOrderItemSubsetIds);

View File

@@ -7,9 +7,10 @@ import { UiCommonModule } from '@ui/common';
import { GoodsOutItemSelectablePipe } from './goods-out-item-selectable.pipe';
import { GoodsOutItemSelectedPipe } from './goods-out-item-selectede.pipe';
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { UiScrollContainerModule } from '@ui/scroll-container';
@NgModule({
imports: [CommonModule, GoodsInOutOrderGroupModule, UiCommonModule, UiSpinnerModule],
imports: [CommonModule, GoodsInOutOrderGroupModule, UiCommonModule, UiSpinnerModule, UiScrollContainerModule],
exports: [GoodsOutSearchResultsComponent],
declarations: [GoodsOutSearchResultsComponent, GoodsOutItemSelectablePipe, GoodsOutItemSelectedPipe],
})

View File

@@ -93,9 +93,9 @@ export class GoodsInOutOrderGroupItemComponent extends ComponentStore<GoodsInOut
return 0;
}
const stockInfo = this.showInStock.find((stock) => stock.ean === ean);
const stockInfo = this.showInStock?.find((stock) => stock?.ean === ean);
if (stockInfo) {
return stockInfo.inStock - stockInfo.removedFromStock;
return stockInfo?.inStock - (stockInfo?.removedFromStock || 0);
} else {
return 0;
}

View File

@@ -7,18 +7,10 @@ import { IsInViewportDirective } from './is-in-viewport.directive';
import { GroupByPipe } from './pipes/group-by.pipe';
import { StripHtmlTagsPipe } from './pipes/strip-html-tags.pipe';
import { SubstrPipe } from './pipes/substr.pipe';
import { ScrollContainerDirective } from './scroll-container.directive';
import { SkeletonLoaderComponent } from './skeleton-loader';
const components = [SkeletonLoaderComponent];
const directives = [
UiClickOutsideDirective,
IsInViewportDirective,
ScrollContainerDirective,
BlobImageDirective,
UiAutofocusDirective,
UiFocusDirective,
];
const directives = [UiClickOutsideDirective, IsInViewportDirective, BlobImageDirective, UiAutofocusDirective, UiFocusDirective];
const pipes = [StripHtmlTagsPipe, SubstrPipe, GroupByPipe];
@NgModule({
imports: [],

View File

@@ -2,7 +2,6 @@
export * from './click-outside.directive';
export * from './common.module';
export * from './is-in-viewport.directive';
export * from './scroll-container.directive';
export * from './date';
export * from './skeleton-loader';
export * from './defs';

25
apps/ui/loader/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Loader
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.4.
## Code scaffolding
Run `ng generate component component-name --project loader` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project loader`.
> Note: Don't forget to add `--project loader` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build loader` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build loader`, go to the dist folder `cd dist/loader` and run `npm publish`.
## Running unit tests
Run `ng test loader` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/ui/loader'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/ui/loader",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@ui/loader",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^10.2.4",
"@angular/core": "^10.2.4"
},
"dependencies": {
"tslib": "^2.0.0"
}
}

View File

@@ -1,6 +1,6 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator';
import { UiIconModule } from '@ui/icon';
import { UiButtonLoaderComponent, UiLoaderComponent } from './ui-loader.component';
import { UiButtonLoaderComponent, UiLoaderComponent } from './loader.component';
describe('UiLoaderComponent', () => {
let spectator: SpectatorHost<UiLoaderComponent>;

View File

@@ -7,8 +7,8 @@ interface IUiLoader {
@Component({
selector: '[uiLoader]:not(button)',
templateUrl: 'ui-loader.component.html',
styleUrls: ['ui-loader.component.scss'],
templateUrl: 'loader.component.html',
styleUrls: ['loader.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UiLoaderComponent implements IUiLoader {
@@ -23,8 +23,8 @@ export class UiLoaderComponent implements IUiLoader {
@Component({
selector: 'button[uiLoader]',
templateUrl: 'ui-loader.component.html',
styleUrls: ['ui-loader.component.scss'],
templateUrl: 'loader.component.html',
styleUrls: ['loader.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UiButtonLoaderComponent implements IUiLoader {

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UiLoaderComponent } from './loader.component';
import { UiIconModule } from '@ui/icon';
import { UiButtonLoaderComponent } from '@ui/loader';
@NgModule({
imports: [CommonModule, UiIconModule],
exports: [UiLoaderComponent, UiButtonLoaderComponent],
declarations: [UiLoaderComponent, UiButtonLoaderComponent],
})
export class UiLoaderModule {}

View File

@@ -0,0 +1,6 @@
/*
* Public API Surface of loader
*/
export * from './lib/loader.component';
export * from './lib/loader.module';

View File

@@ -0,0 +1,24 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(
path: string,
deep?: boolean,
filter?: RegExp
): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,25 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"enableIvy": false
}
}

View File

@@ -0,0 +1,17 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"lib",
"camelCase"
],
"component-selector": [
true,
"element",
"lib",
"kebab-case"
]
}
}

View File

@@ -0,0 +1,25 @@
# ScrollContainer
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.4.
## Code scaffolding
Run `ng generate component component-name --project scroll-container` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project scroll-container`.
> Note: Don't forget to add `--project scroll-container` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build scroll-container` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build scroll-container`, go to the dist folder `cd dist/scroll-container` and run `npm publish`.
## Running unit tests
Run `ng test scroll-container` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/ui/scroll-container'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/ui/scroll-container",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@ui/scroll-container",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^10.2.4",
"@angular/core": "^10.2.4"
},
"dependencies": {
"tslib": "^2.0.0"
}
}

View File

@@ -0,0 +1 @@
<div *ngIf="loading" [uiLoader]="loading"><div class="loading-text">Inhalte werden geladen</div></div>

View File

@@ -0,0 +1,7 @@
.loading-text {
@apply mt-2 text-center font-semibold text-base text-inactive-branch;
}
::ng-deep .customer ui-content-loader .loading-text {
@apply text-inactive-customer;
}

View File

@@ -0,0 +1,15 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'ui-content-loader',
templateUrl: 'content-loader.component.html',
styleUrls: ['content-loader.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UiContentLoaderComponent implements OnInit {
@Input() loading: boolean;
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,3 @@
// start:ng42.barrel
export * from './content-loader.component';
// end:ng42.barrel

View File

@@ -0,0 +1,3 @@
// start:ng42.barrel
export * from './scroll-container.directive';
// end:ng42.barrel

View File

@@ -3,7 +3,7 @@ import { Subject } from 'rxjs';
import { filter } from 'rxjs/operators';
@Directive({ selector: '[uiScrollContainer]' })
export class ScrollContainerDirective implements OnChanges, OnInit {
export class UiScrollContainerDirective implements OnChanges, OnInit {
@Input('uiScrollContainer')
direction: 'horizontal' | 'vertical' = 'vertical';

View File

@@ -0,0 +1,25 @@
<div
#scrollContainer
class="scroll-container"
[class.scroll-bar]="showScrollbar"
[class.scrollbar-gap]="scrollPersantage > 99"
[uiScrollContainer]="direction"
(reachEnd)="reachedEnd()"
(reachStart)="reachedStart()"
[deltaEnd]="deltaEnd"
>
<ng-content *ngIf="!loading; else loader"></ng-content>
<ng-template #loader>
<ng-container *ngIf="useLoadAnimation; else contentLoader">
<ui-skeleton-loader></ui-skeleton-loader>
<ui-skeleton-loader *ngFor="let skeletons of createSkeletons()"></ui-skeleton-loader>
</ng-container>
<ng-template #contentLoader>
<ui-content-loader [loading]="loading"></ui-content-loader>
</ng-template>
</ng-template>
<div *ngIf="!loading" class="spacer"></div>
</div>

View File

@@ -0,0 +1,20 @@
.scroll-container {
@apply h-full overflow-y-scroll relative grid grid-flow-row gap-3;
max-height: calc(100vh - 280px);
}
.spacer {
height: 140px;
}
::ng-deep ui-scroll-container .scroll-bar::-webkit-scrollbar {
background-color: transparent !important;
}
::ng-deep ui-scroll-container .scroll-bar::-webkit-scrollbar-track {
background-color: white !important;
}
.scrollbar-gap::-webkit-scrollbar-track {
margin-bottom: 10rem;
}

View File

@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ScrollContainerComponent } from './scroll-container.component';
describe('ScrollContainerComponent', () => {
let component: ScrollContainerComponent;
let fixture: ComponentFixture<ScrollContainerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ScrollContainerComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ScrollContainerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,60 @@
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
@Component({
selector: 'ui-scroll-container',
templateUrl: 'scroll-container.component.html',
styleUrls: ['scroll-container.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UiScrollContainerComponent {
@ViewChild('scrollContainer', { read: ElementRef, static: false })
scrollContainer: ElementRef;
@Output() reachEnd = new EventEmitter<void>();
@Output() reachStart = new EventEmitter<void>();
@Input()
direction: 'horizontal' | 'vertical' = 'vertical';
@Input() deltaStart = 0;
@Input() deltaEnd = 0;
@Input() loading = false;
@Input() itemLength: number;
@Input() showScrollbar = true;
@Input() useLoadAnimation = true;
constructor() {}
createSkeletons() {
if (this.itemLength && this.itemLength !== 0) {
return Array.from(Array(this.itemLength - 1), (_, i) => i);
} else {
return [];
}
}
reachedEnd() {
this.reachEnd.emit();
}
reachedStart() {
this.reachStart.emit();
}
get scrollPersantage() {
const scrollHeight = this.scrollContainer?.nativeElement?.scrollHeight - this.scrollContainer?.nativeElement?.clientHeight;
if (scrollHeight === 0) {
return 0;
}
const scrollTop = this.scrollContainer?.nativeElement?.scrollTop;
return (100 / scrollHeight) * scrollTop;
}
}

View File

@@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon';
import { UiLoaderModule } from '@ui/loader';
import { UiContentLoaderComponent } from '@ui/scroll-container';
import { UiScrollContainerDirective } from './directives';
import { UiScrollContainerComponent } from './scroll-container.component';
import { UiSkeletonLoaderComponent } from './skeleton-loader';
@NgModule({
declarations: [UiScrollContainerComponent, UiSkeletonLoaderComponent, UiContentLoaderComponent, UiScrollContainerDirective],
imports: [CommonModule, UiIconModule, UiLoaderModule],
exports: [UiScrollContainerComponent, UiScrollContainerDirective],
})
export class UiScrollContainerModule {}

View File

@@ -0,0 +1,3 @@
// start:ng42.barrel
export * from './skeleton-loader.component';
// end:ng42.barrel

View File

@@ -56,3 +56,27 @@
opacity: 0;
}
}
::ng-deep .customer ui-skeleton-loader {
.header {
.left {
@apply bg-inactive-customer !important;
}
.right {
@apply bg-inactive-customer !important;
}
}
.thumbnail {
@apply bg-inactive-customer;
}
.item {
@apply bg-inactive-customer;
}
.title {
@apply bg-inactive-customer;
}
}

View File

@@ -0,0 +1,11 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'ui-skeleton-loader',
templateUrl: 'skeleton-loader.component.html',
styleUrls: ['skeleton-loader.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UiSkeletonLoaderComponent {
constructor() {}
}

View File

@@ -0,0 +1,9 @@
/*
* Public API Surface of scroll-container
*/
export * from './lib/directives';
export * from './lib/skeleton-loader';
export * from './lib/content-loader';
export * from './lib/scroll-container.component';
export * from './lib/scroll-container.module';

View File

@@ -0,0 +1,24 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(
path: string,
deep?: boolean,
filter?: RegExp
): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,25 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"enableIvy": false
}
}

View File

@@ -0,0 +1,17 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"lib",
"camelCase"
],
"component-selector": [
true,
"element",
"lib",
"kebab-case"
]
}
}

83
package-lock.json generated
View File

@@ -4819,6 +4819,30 @@
"requires": {
"is-extendable": "^0.1.0"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
}
}
},
@@ -7644,29 +7668,6 @@
"schema-utils": "^2.6.5"
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel/npm/registry/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel/npm/registry/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -9396,26 +9397,6 @@
"integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==",
"dev": true
},
"is-number": {
"version": "3.0.0",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"is-number-object": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz",
@@ -18478,22 +18459,6 @@
"safe-regex": "^1.1.0"
}
},
"to-regex-range": {
"version": "2.1.1",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel@Local/npm/registry/to-regex-range/-/to-regex-range-2.1.1.tgz",
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
"dev": true,
"requires": {
"repeat-string": "^1.6.1"
},
"dependencies": {
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
}
}
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",

View File

@@ -265,7 +265,13 @@
"apps/ui/select-bullet/src/public-api.ts"
],
"@page/goods-out": [
"apps/page/goods-out//src/public-api.ts"
"apps/page/goods-out/src/public-api.ts"
],
"@ui/scroll-container": [
"apps/ui/scroll-container/src/public-api.ts"
],
"@ui/loader": [
"apps/ui/loader/src/public-api.ts"
]
}
}