Catalog Filter Store Refactor and Caching Service

This commit is contained in:
Nino Righi
2021-05-21 18:44:11 +02:00
parent 1eba91d308
commit aad2f47154
44 changed files with 878 additions and 471 deletions

View File

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

25
apps/core/cache/README.md vendored Normal file
View File

@@ -0,0 +1,25 @@
# Cache
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.1.2.
## Code scaffolding
Run `ng generate component component-name --project cache` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project cache`.
> Note: Don't forget to add `--project cache` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build cache` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build cache`, go to the dist folder `cd dist/cache` and run `npm publish`.
## Running unit tests
Run `ng test cache` 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 README](https://github.com/angular/angular-cli/blob/master/README.md).

32
apps/core/cache/karma.conf.js vendored Normal file
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/core/cache'),
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,
});
};

7
apps/core/cache/ng-package.json vendored Normal file
View File

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

11
apps/core/cache/package.json vendored Normal file
View File

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

View File

@@ -0,0 +1,4 @@
export interface CacheOptions {
ttl?: number;
persist?: boolean;
}

View File

@@ -0,0 +1,8 @@
import { NgModule } from '@angular/core';
@NgModule({
declarations: [],
imports: [],
exports: [],
})
export class CacheModule {}

View File

@@ -0,0 +1,73 @@
import { Injectable } from '@angular/core';
import { CacheOptions } from './cache-options';
import { Cached } from './cached';
import { sha1 } from 'object-hash';
@Injectable({
providedIn: 'root',
})
export class CacheService {
constructor() {}
set(token: Object, data: any, options?: CacheOptions) {
const persist = options?.persist;
const ttl = options?.ttl;
const cached: Cached = {
data,
};
if (ttl) {
cached.until = Date.now() + ttl;
}
if (persist) {
localStorage.setItem(this.getKey(token), this.serialize(cached));
} else {
sessionStorage.setItem(this.getKey(token), this.serialize(cached));
}
Object.freeze(cached);
return cached;
}
get<T = any>(token: Object, from: 'session' | 'persist' = 'session'): T {
let cached: Cached;
if (from === 'session') {
cached = this.deserialize(sessionStorage.getItem(this.getKey(token)));
} else if (from === 'persist') {
cached = this.deserialize(localStorage.getItem(this.getKey(token)));
}
if (!cached) {
return undefined;
}
if (cached.until < Date.now()) {
this.delete(token, from);
return undefined;
}
return cached.data;
}
private delete(token: Object, from: 'session' | 'persist' = 'session') {
if (from === 'session') {
sessionStorage.removeItem(this.getKey(token));
} else if (from === 'persist') {
localStorage.removeItem(this.getKey(token));
}
}
private getKey(token: Object) {
return sha1(token);
}
private serialize(data: Cached): string {
return JSON.stringify(data);
}
private deserialize(data: string): Cached {
return JSON.parse(data);
}
}

4
apps/core/cache/src/lib/cached.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
export interface Cached {
until?: number;
data?: any;
}

6
apps/core/cache/src/public-api.ts vendored Normal file
View File

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

24
apps/core/cache/src/test.ts vendored Normal file
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);

25
apps/core/cache/tsconfig.lib.json vendored Normal file
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"
]
}

10
apps/core/cache/tsconfig.lib.prod.json vendored Normal file
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
}
}

17
apps/core/cache/tsconfig.spec.json vendored Normal file
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"
]
}

17
apps/core/cache/tslint.json vendored Normal file
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

@@ -30,14 +30,17 @@ export class DomainCatalogService {
}
getSearchHistory({ take }: { take: number }) {
return this.searchService.SearchHistory(take ?? 10).pipe(map((res) => res.result));
return this.searchService.SearchHistory(take ?? 5).pipe(map((res) => res.result));
}
@memorize()
search({ queryToken }: { queryToken: QueryTokenDTO }) {
return this.searchService.SearchSearch({
queryToken,
stockId: null,
});
return this.searchService
.SearchSearch({
queryToken,
stockId: null,
})
.pipe(shareReplay());
}
getDetailsById({ id }: { id: number }) {

View File

@@ -4,9 +4,9 @@ import { StringDictionary } from '@cmf/core';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { cloneDeep, isEqual } from 'lodash';
import { combineLatest, Observable, Subscription } from 'rxjs';
import { first, map, tap, shareReplay, switchMap, throttleTime, withLatestFrom, debounceTime, take } from 'rxjs/operators';
import { isEqual } from 'lodash';
import { combineLatest, Observable, Subject, Subscription } from 'rxjs';
import { first, map, tap, switchMap, withLatestFrom, debounceTime, finalize } from 'rxjs/operators';
import { DomainCatalogService } from '@domain/catalog';
import {
fromInputDto,
@@ -36,6 +36,8 @@ export interface ArticleSearchState {
/* tslint:disable member-ordering */
@Injectable()
export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
readonly onSearch = new Subject<{ clear?: boolean; reload?: boolean }>();
private queryParamsQuerySelector = (s: ArticleSearchState) => (s.params?.query?.length ? decodeURI(s.params.query) : '');
readonly queryParamsQuery$ = this.select(this.queryParamsQuerySelector);
get query() {
@@ -43,7 +45,10 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
}
private itemsSelector = (s: ArticleSearchState) => s.items ?? [];
readonly items$ = this.select(this.itemsSelector).pipe(tap(console.log.bind(window, 'items')));
readonly items$ = this.select(this.itemsSelector);
get items() {
return this.get(this.itemsSelector);
}
private orderBySelector = (s: ArticleSearchState) => (s.params?.orderBy != undefined ? decodeURI(s.params.orderBy) : undefined);
readonly queryParamsOrderBy$ = this.select(this.orderBySelector);
@@ -61,7 +66,7 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
readonly searchState$ = this.select(this.searchStateSelector);
private queryParamsSelector = (s: ArticleSearchState) =>
Object.keys(s.params).reduce((dic, key) => ({ ...dic, [key]: encodeURI(s.params[key]) }), {} as StringDictionary<string>);
Object.keys(s.params).reduce((dic, key) => ({ ...dic, [key]: s.params[key] }), {} as StringDictionary<string>);
readonly queryParams$ = this.select(this.queryParamsSelector);
get queryParams() {
return this.get(this.queryParamsSelector);
@@ -84,30 +89,25 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
main: ig.find((g) => g.group === 'main')?.input.map(fromInputDto),
inputSelector: ig.find((g) => g.group === 'input_selector')?.input.map(fromInputDto),
};
}),
shareReplay()
})
);
readonly orderByOptions$ = this.catalog.getOrderBy();
readonly filter$ = combineLatest([this.queryParamsFilter$, this.defaultFilter$]).pipe(
map(([selectedFilters, defaultFilters]) => mapSelectedParamsToFilter(selectedFilters, defaultFilters.filter)),
shareReplay()
map(([selectedFilters, defaultFilters]) => mapSelectedParamsToFilter(selectedFilters, defaultFilters.filter))
);
readonly inputSelectorFilter$ = combineLatest([this.queryParamsInputSelector$, this.defaultFilter$]).pipe(
map(([selectedFilters, defaultFilters]) => mapSelectedParamsToFilter(selectedFilters, defaultFilters.inputSelector)),
shareReplay()
map(([selectedFilters, defaultFilters]) => mapSelectedParamsToFilter(selectedFilters, defaultFilters.inputSelector))
);
readonly mainFilter$ = combineLatest([this.queryParamsMain$, this.defaultFilter$]).pipe(
map(([selectedFilters, defaultFilters]) => mapSelectedParamsToFilter(selectedFilters, defaultFilters.main)),
shareReplay()
map(([selectedFilters, defaultFilters]) => mapSelectedParamsToFilter(selectedFilters, defaultFilters.main))
);
readonly queryTokenFilter$ = combineLatest([this.filter$, this.mainFilter$]).pipe(
map(([filter, mainFilter]) => mapFilterArrayToStringDictionary([...filter, ...mainFilter])),
shareReplay()
map(([filter, mainFilter]) => mapFilterArrayToStringDictionary([...filter, ...mainFilter]))
);
readonly queryTokenInput$ = combineLatest([this.queryParamsQuery$, this.inputSelectorFilter$]).pipe(
@@ -127,19 +127,14 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
}
return dic;
}),
shareReplay()
})
);
readonly orderBy$ = combineLatest([this.queryParamsOrderBy$, this.queryParamsDesc$, this.orderByOptions$]).pipe(
map(([orderBy, desc, orderByOptions]) => orderByOptions.find((opt) => opt.by === orderBy && !!opt.desc === Boolean(desc))),
shareReplay()
map(([orderBy, desc, orderByOptions]) => orderByOptions.find((opt) => opt.by === orderBy && !!opt.desc === Boolean(desc)))
);
readonly queryTokenOrderBy$ = this.orderBy$.pipe(
map((orderBy) => (orderBy ? [orderBy] : undefined)),
shareReplay()
);
readonly queryTokenOrderBy$ = this.orderBy$.pipe(map((orderBy) => (orderBy ? [orderBy] : undefined)));
readonly queryToken$ = combineLatest([
this.queryTokenInput$,
@@ -156,9 +151,7 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
returnStockData: false,
friendlyName,
} as QueryTokenDTO)
),
shareReplay(),
tap(console.log.bind(window, 'queryParams'))
)
);
private connectedRouteSubscription: Subscription;
@@ -176,9 +169,13 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
}
search = this.effect((options$: Observable<{ clear?: boolean; reload?: boolean }>) =>
combineLatest([options$, this.queryToken$, this.items$]).pipe(
options$.pipe(
debounceTime(250),
first(),
withLatestFrom(this.queryToken$, this.items$),
tap(([options]) => {
this.setSearchState({ searchState: 'fetching' });
this.onSearch.next(options);
}),
switchMap(([options, queryToken, items]) =>
this.catalog
.search({
@@ -191,6 +188,7 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
.pipe(
tapResponse(
(res) => {
this.setSearchState({ searchState: '' });
if (options.clear || options.reload) {
this.patchState({ items: res.result, hits: res.hits });
if (res.hits > 1) {
@@ -199,6 +197,7 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
this.navigateToDetails(res.result[0]);
} else {
this.setSearchState({ searchState: 'empty' });
this.navigateToMain();
}
} else {
this.patchState({ items: [...items, ...res.result], hits: res.hits });
@@ -216,7 +215,7 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
this.disconnect();
let connected = false;
this.connectedRouteSubscription = route.queryParams.subscribe((params) => {
this.connectedRouteSubscription = route.queryParams.pipe(finalize(() => options?.disconnected?.call(undefined))).subscribe((params) => {
const current = this.get(this.queryParamsSelector);
if (!isEqual(current, params)) {
@@ -225,14 +224,13 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
if (!connected) {
connected = true;
options?.connected?.call(undefined, params);
setTimeout(() => options?.connected?.call(undefined, params), 0);
}
});
return {
disconnect: () => {
this.disconnect();
options?.disconnected?.call(undefined);
},
};
}
@@ -275,6 +273,10 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
this.router.navigate(['/product/details', item.id]);
}
navigateToMain() {
this.router.navigate(['/product/search'], { queryParams: this.queryParams });
}
private setQueryParams({ params }: { params: StringDictionary<string> }) {
this.patchState({ params });
}
@@ -293,6 +295,7 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
setFilter({ filter }: { filter: Filter[] }) {
const queryParams = this.get(this.queryParamsSelector);
this.patchState({
params: {
...queryParams,
@@ -331,6 +334,10 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
this.patchState({ searchState });
}
setItems({ items }: { items: ItemDTO[] }) {
this.patchState({ items });
}
setOrderBy({ orderBy }: { orderBy: OrderByDTO }) {
const queryParams = this.get(this.queryParamsSelector);
this.patchState({

View File

@@ -1,4 +1,4 @@
<button class="filter" [class.active]="anyFiltersActive$ | async" (click)="filterActive$.next(true)">
<button class="filter" [class.active]="hasFilter$ | async" (click)="filterActive$.next(true)">
<ui-icon size="20px" icon="filter_alit"></ui-icon>
<span class="label">Filter</span>
</button>

View File

@@ -15,30 +15,12 @@ export class ArticleSearchComponent implements OnInit {
filterActive$ = new BehaviorSubject<boolean>(false);
showMainContent$ = this.getShowMainContent();
lastSelectedFilterCategory: Filter;
anyFiltersActive$: Observable<boolean>;
// readonly filters$ = this.articleSearchStore.selectFilter$;
hasFilter$: Observable<boolean> = this.store.queryParamsFilter$.pipe(map((filter) => filter !== ''));
constructor(private articleSearchStore: ArticleSearchStore) {}
constructor(private store: ArticleSearchStore) {}
ngOnInit() {
// this.articleSearchStore.loadInitialFilters();
// this.articleSearchStore.searchHistory();
// this.anyFiltersActive$ = this.filters$.pipe(map((filters) => this.anyFilterSet(filters)));
}
// TODO: Replace Logic
anyFilterSet(filters: Filter[]): boolean {
let anySelected = false;
for (const filter of filters) {
const options: FilterOption[] = filter.options;
const selected = options.filter((o) => o.selected);
if (selected?.length > 0) {
anySelected = true;
}
}
return anySelected;
}
ngOnInit() {}
getShowMainContent(animationDelayInMs: number = 500): Observable<boolean> {
return this.filterActive$.pipe(

View File

@@ -1,57 +1,27 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { UiCommonModule } from '@ui/common';
import { UiFilterModule } from '@ui/filter';
import { UiIconModule } from '@ui/icon';
import { UiSearchboxModule } from '@ui/searchbox';
import { ArticleSearchComponent } from './article-search.component';
import { ArticleSearchFilterComponent } from './search-filter/search-filter.component';
import { ArticleSearchMainComponent } from './search-main/search-main.component';
import { ArticleSearchboxComponent } from './containers/article-searchbox/article-searchbox.component';
import { InfoboxComponent } from './containers/article-searchbox/infobox/infobox.component';
import { ArticleSearchResultsComponent } from './search-results/search-results.component';
import { OrderByFilterComponent } from './search-results/order-by-filter/order-by-filter.component';
import { StockInfosPipe } from './search-results/order-by-filter/stick-infos.pipe';
import { DomainCatalogModule } from '@domain/catalog';
import { UiCheckboxModule } from '@ui/checkbox';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { SearchResultItemComponent } from './search-results/search-result-item.component';
import { FilterChipsModule } from './containers/filter-chips/filter-chips.module';
import { ArticleSearchboxModule } from './containers/article-searchbox/article-searchbox.module';
import { SearchResultsModule } from './search-results/search-results.module';
import { SearchMainModule } from './search-main/search-main.module';
import { SearchFilterModule } from './search-filter/search-filter.module';
@NgModule({
imports: [
CommonModule,
RouterModule,
FormsModule,
ReactiveFormsModule,
UiSearchboxModule,
UiCommonModule,
UiIconModule,
UiFilterModule,
UiSearchboxModule,
ReactiveFormsModule,
DomainCatalogModule,
ScrollingModule,
UiCheckboxModule,
UiFormControlModule,
UiInputModule,
],
exports: [ArticleSearchComponent, ArticleSearchMainComponent, ArticleSearchFilterComponent, ArticleSearchboxComponent, InfoboxComponent],
declarations: [
ArticleSearchComponent,
ArticleSearchMainComponent,
ArticleSearchFilterComponent,
ArticleSearchboxComponent,
InfoboxComponent,
ArticleSearchResultsComponent,
OrderByFilterComponent,
StockInfosPipe,
SearchResultItemComponent,
FilterChipsModule,
ArticleSearchboxModule,
SearchResultsModule,
SearchMainModule,
SearchFilterModule,
],
exports: [ArticleSearchComponent],
declarations: [ArticleSearchComponent],
providers: [],
})
export class ArticleSearchModule {}

View File

@@ -1,30 +1,4 @@
<div class="wrapper">
<div class="primary-filter-container">
<ng-container *ngIf="mainFilter$ | async; let mainFilter">
<ui-checkbox
[class.checked]="chip.options[0].selected"
[showCheckbox]="false"
[ngModel]="chip.options[0].selected"
(ngModelChange)="checkMainFilter($event, chip, mainFilter)"
[class.filter]="isFilter$ | async"
*ngFor="let chip of mainFilter"
>
{{ chip.name }}
</ui-checkbox>
</ng-container>
<ng-container *ngIf="inputFilter$ | async; let inputFilter">
<ui-checkbox
[class.checked]="chip.options[0].selected"
[showCheckbox]="false"
[ngModel]="chip.options[0].selected"
(ngModelChange)="checkInputFilter($event, chip, inputFilter)"
[class.filter]="isFilter$ | async"
*ngFor="let chip of inputFilter"
>
{{ chip.name }}
</ui-checkbox>
</ng-container>
</div>
<div [class.searchbox-wrapper]="isFilter$ | async">
<ui-searchbox>
<input
@@ -32,7 +6,7 @@
#input
[ngModel]="query$ | async"
(ngModelChange)="updateQuery($event)"
(inputChange)="autocompleteTrigger($event)"
(inputChange)="autocompleteQuery$.next($event)"
type="text"
uiSearchboxInput
placeholder="Titel, Autor, Verlag, Schlagwort, ..."
@@ -63,7 +37,7 @@
</ng-container>
</button>
<!-- <ui-searchbox-autocomplete uiClickOutside (clicked)="autocomplete.close()" #autocomplete>
<ui-searchbox-autocomplete uiClickOutside (clicked)="autocomplete.close()" #autocomplete>
<button
uiSearchboxAutocompleteOption
[value]="result?.query"
@@ -72,7 +46,7 @@
>
{{ result?.display }}
</button>
</ui-searchbox-autocomplete> -->
</ui-searchbox-autocomplete>
</ui-searchbox>
<page-searchbox-infobox *ngIf="isFilter$ | async"></page-searchbox-infobox>
</div>

View File

@@ -2,17 +2,6 @@
@apply text-dark-cerulean border border-solid border-inactive-customer;
}
ui-checkbox {
@apply mx-px-10 px-5 py-4 text-inactive-customer font-bold text-sm whitespace-nowrap;
border-radius: 27px;
border: 1px solid white;
background-color: #e9f0f8;
}
ui-checkbox.filter {
@apply bg-white;
}
.wrapper {
@apply flex flex-col;

View File

@@ -8,18 +8,16 @@ import {
Output,
ViewChild,
OnDestroy,
ElementRef,
AfterViewInit,
} from '@angular/core';
import { EnvironmentService } from '@core/environment';
import { AutocompleteDTO } from '@swagger/cat';
import { Filter } from '@ui/filter';
import { UiSearchboxAutocompleteComponent } from '@ui/searchbox';
import { BehaviorSubject, combineLatest, concat, Observable } from 'rxjs';
import { delay, first, map, shareReplay, tap, filter } from 'rxjs/operators';
import { map, shareReplay, tap, filter, switchMap, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { NativeContainerService } from 'native-container';
import { Subscription } from 'rxjs';
import { ArticleSearchStore } from '../../article-search-new.store';
import { DomainCatalogService } from '@domain/catalog';
@Component({
selector: 'page-article-searchbox',
@@ -27,21 +25,44 @@ import { ArticleSearchStore } from '../../article-search-new.store';
styleUrls: ['article-searchbox.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleSearchboxComponent implements OnInit, OnDestroy, AfterViewInit {
export class ArticleSearchboxComponent implements OnInit, OnDestroy {
@ViewChild(UiSearchboxAutocompleteComponent, {
read: UiSearchboxAutocompleteComponent,
static: false,
})
autocomplete: UiSearchboxAutocompleteComponent;
autocompleteResult$: Observable<AutocompleteDTO[]>;
readonly autocompleteQuery$ = new BehaviorSubject<string>('');
autocompleteResult$: Observable<AutocompleteDTO[]> = combineLatest([
this.autocompleteQuery$,
this.store.queryTokenFilter$,
this.store.queryParamsInputSelector$,
]).pipe(
debounceTime(200),
distinctUntilChanged(),
filter(([query]) => query.trim().length >= 3),
switchMap(([query, fil, inputSelector]) =>
this.catalog.searchComplete({
queryToken: {
filter: fil,
input: query,
take: 5,
type: inputSelector || 'qs',
catalogType: undefined,
},
})
),
tap((response) => {
if (response.hits > 0) {
this.autocomplete.open();
} else {
this.autocomplete.close();
}
}),
map((response) => response.result)
);
readonly inputFilter$ = this.articleSearchStore.inputSelectorFilter$;
readonly mainFilter$ = this.articleSearchStore.mainFilter$;
readonly query$ = this.articleSearchStore.queryParamsQuery$.pipe(shareReplay());
// readonly isHistory$ = this.articleSearchStore.query$.pipe(map((query) => query.history));
readonly searchState$ = this.articleSearchStore.searchState$;
// readonly autocomplete$ = this.articleSearchStore.selectAutocomplete$;
readonly query$ = this.store.queryParamsQuery$.pipe(shareReplay());
readonly searchState$ = this.store.searchState$;
isMobile: boolean;
subscriptions = new Subscription();
@@ -67,11 +88,6 @@ export class ArticleSearchboxComponent implements OnInit, OnDestroy, AfterViewIn
shareReplay()
);
isMain$ = this.mode$.pipe(
map((type) => type === 'main'),
shareReplay()
);
isFetching$ = this.searchState$.pipe(
map((searchState) => searchState === 'fetching'),
shareReplay()
@@ -85,13 +101,13 @@ export class ArticleSearchboxComponent implements OnInit, OnDestroy, AfterViewIn
constructor(
private environmentService: EnvironmentService,
private cdr: ChangeDetectorRef,
private articleSearchStore: ArticleSearchStore,
private nativeContainer: NativeContainerService
private store: ArticleSearchStore,
private nativeContainer: NativeContainerService,
private catalog: DomainCatalogService
) {}
ngOnInit() {
this.detectDevice();
this.initAutocomplete();
}
ngOnDestroy() {
@@ -100,15 +116,15 @@ export class ArticleSearchboxComponent implements OnInit, OnDestroy, AfterViewIn
startSearch() {
const isNative = this.nativeContainer.isUiWebview().isNative;
const query = this.articleSearchStore.query;
const query = this.store.query;
if (isNative && (query?.length ?? 0) === 0) {
return this.scan();
} else {
this.articleSearchStore.search({ clear: true });
this.store.search({ clear: true });
this.subscriptions.add(
this.articleSearchStore.searchState$.subscribe((state) => {
this.store.searchState$.subscribe((state) => {
if (state !== 'fetching' && state !== 'empty') {
this.closeFilterOverlay.emit();
}
@@ -122,11 +138,11 @@ export class ArticleSearchboxComponent implements OnInit, OnDestroy, AfterViewIn
.openScanner('scanBook')
.pipe(filter((message) => message.status !== 'IN_PROGRESS'))
.subscribe((result) => {
this.articleSearchStore.setQuery({ query: result?.data });
this.store.setQuery({ query: result?.data });
this.articleSearchStore.search({ clear: true });
this.store.search({ clear: true });
this.subscriptions.add(
this.articleSearchStore.searchState$.subscribe((state) => {
this.store.searchState$.subscribe((state) => {
if (state !== 'fetching' && state !== 'empty') {
this.closeFilterOverlay.emit();
}
@@ -139,76 +155,21 @@ export class ArticleSearchboxComponent implements OnInit, OnDestroy, AfterViewIn
}
reset() {
this.articleSearchStore.setSearchState({ searchState: '' });
this.articleSearchStore.setQuery({ query: '' });
this.store.setSearchState({ searchState: '' });
this.store.setQuery({ query: '' });
this.autocomplete.close();
}
resetSearchState() {
this.articleSearchStore.setSearchState({ searchState: '' });
}
// checkFilterChip(checked: boolean = false, changedFilterChip: Filter, filterChips: Filter[]) {
// changedFilterChip.options[0].selected = checked;
// if (changedFilterChip.target === 'filter') {
// const f = filterChips.filter((fil) => fil.target === 'filter');
// this.articleSearchStore.setInputFilter({ filter: f });
// } else if (changedFilterChip.target === 'input') {
// // this.articleSearchStore.setPrimaryFilter({ filter });
// }
// this.cdr.markForCheck();
// }
checkInputFilter(checked: boolean = false, changedFilterChip: Filter, inputFilter: Filter[]) {
changedFilterChip.options[0].selected = checked;
this.articleSearchStore.setInputSelectorFilter({ filter: inputFilter });
this.cdr.markForCheck();
}
checkMainFilter(checked: boolean = false, changedFilterChip: Filter, mainFilter: Filter[]) {
changedFilterChip.options[0].selected = checked;
this.articleSearchStore.setMainFilter({ filter: mainFilter });
this.cdr.markForCheck();
this.store.setSearchState({ searchState: '' });
}
updateQuery(query: string) {
this.articleSearchStore.setQuery({ query });
}
async autocompleteTrigger(queryAutocomplete: string) {
// await this.query$.pipe(first()).toPromise(); // Wait for setQuery from updateQuery()
// if (queryAutocomplete.length >= 3) {
// this.articleSearchStore.searchAutocomplete();
// }
}
initAutocomplete() {
// this.autocompleteResult$ = this.autocomplete$.pipe(
// tap((results) => {
// if (this.autocomplete) {
// if (results.length > 0) {
// this.autocomplete.open();
// } else {
// this.autocomplete.close();
// }
// }
// }),
// delay(1),
// tap(() => this.cdr.detectChanges())
// );
this.store.setQuery({ query });
}
async detectDevice() {
this.isMobile = await this.environmentService.isMobile();
this.cdr.detectChanges();
}
focus(input: HTMLInputElement): void {
setTimeout(() => {
input.focus();
this.cdr.detectChanges();
}, 1000);
}
ngAfterViewInit() {}
}

View File

@@ -0,0 +1,17 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { UiIconModule } from '@ui/icon';
import { UiInputModule } from '@ui/input';
import { UiSearchboxModule } from '@ui/searchbox';
import { ArticleSearchboxComponent } from './article-searchbox.component';
import { InfoboxComponent } from './infobox/infobox.component';
@NgModule({
imports: [CommonModule, FormsModule, UiIconModule, UiSearchboxModule, UiInputModule],
exports: [ArticleSearchboxComponent, InfoboxComponent],
declarations: [ArticleSearchboxComponent, InfoboxComponent],
providers: [],
})
export class ArticleSearchboxModule {}

View File

@@ -0,0 +1,12 @@
<div class="primary-filter-container">
<ui-checkbox
class="filter-chip-background"
[class.checked]="chip.options[0].selected"
[showCheckbox]="false"
[ngModel]="chip.options[0].selected"
(ngModelChange)="checkFilter($event, chip)"
*ngFor="let chip of filter"
>
{{ chip.name }}
</ui-checkbox>
</div>

View File

@@ -0,0 +1,30 @@
:host {
--filter-chips-background-color: #e9f0f8;
}
:host.filter {
--filter-chips-background-color: #fff;
}
.filter-chip-background {
background-color: var(--filter-chips-background-color);
}
.primary-filter-container {
@apply flex flex-row justify-center mb-8;
}
.checked {
@apply text-dark-cerulean border border-solid border-inactive-customer;
}
ui-checkbox {
@apply mx-px-10 px-5 py-4 text-inactive-customer font-bold text-sm whitespace-nowrap;
border-radius: 27px;
border: 1px solid white;
background-color: #e9f0f8;
}
ui-checkbox.filter {
@apply bg-white;
}

View File

@@ -0,0 +1,25 @@
import { Component, Input, OnInit, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
import { Filter } from '@ui/filter';
import { cloneDeep } from 'lodash';
@Component({
selector: 'page-filter-chips',
templateUrl: 'filter-chips.component.html',
styleUrls: ['filter-chips.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FilterChipsComponent implements OnInit {
@Output() filterChange = new EventEmitter<Filter[]>();
@Input() filter: Filter[];
constructor() {}
ngOnInit() {}
checkFilter(checked: boolean = false, changedFilterChip: Filter) {
const filter = cloneDeep(this.filter);
filter.find((f) => f.key === changedFilterChip.key).options[0].selected = checked;
this.filterChange.emit(filter);
}
}

View File

@@ -0,0 +1,14 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { UiCheckboxModule } from '@ui/checkbox';
import { FilterChipsComponent } from './filter-chips.component';
@NgModule({
imports: [CommonModule, FormsModule, UiCheckboxModule],
exports: [FilterChipsComponent],
declarations: [FilterChipsComponent],
providers: [],
})
export class FilterChipsModule {}

View File

@@ -1,3 +1,8 @@
<div class="filter-chips">
<page-filter-chips class="filter" (filterChange)="checkMainFilter($event)" [filter]="mainFilter$ | async"></page-filter-chips>
<page-filter-chips class="filter" (filterChange)="checkInputFilter($event)" [filter]="inputFilter$ | async"></page-filter-chips>
</div>
<page-article-searchbox mode="filter" (closeFilterOverlay)="closeOverlay()"></page-article-searchbox>
<ng-container *ngIf="filters$ | async; let selectedFilters">

View File

@@ -7,6 +7,10 @@
bottom: 30px;
}
.filter-chips {
@apply flex flex-row justify-center;
}
button.apply-filter {
@apply border-none bg-brand text-white rounded-full py-cta-y-l px-cta-x-l text-cta-l font-bold;
min-width: 201px;

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { Filter } from '@ui/filter';
import { BehaviorSubject, Subscription } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { first, map, tap } from 'rxjs/operators';
import { ArticleSearchStore } from '../article-search-new.store';
@Component({
@@ -18,6 +18,9 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
readonly initialFilters$ = this.articleSearchStore.defaultFilter$.pipe(map((filter) => filter.filter));
readonly searchState$ = this.articleSearchStore.searchState$;
readonly inputFilter$ = this.articleSearchStore.inputSelectorFilter$;
readonly mainFilter$ = this.articleSearchStore.mainFilter$;
/* @internal */
updateFilterCategory$ = new BehaviorSubject<Filter>(undefined);
@@ -32,23 +35,22 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
this.updateFilterCategory$.next(value);
}
filterChanges: Filter[];
initialFilter: Filter[];
searchStateSubscription: Subscription;
constructor(private cdr: ChangeDetectorRef, private articleSearchStore: ArticleSearchStore) {}
async ngOnInit() {
this.filterChanges = await this.filters$.pipe(first()).toPromise();
ngOnInit() {
// this.filterChanges = await this.filters$.pipe(first()).toPromise();
}
ngOnDestroy() {
const filter = this.filterChanges;
this.articleSearchStore.setFilter({ filter });
if (!!this.searchStateSubscription) {
this.searchStateSubscription.unsubscribe();
}
// const filter = this.filterChanges;
// this.articleSearchStore.setFilter({ filter });
// if (!!this.searchStateSubscription) {
// this.searchStateSubscription.unsubscribe();
// }
}
closeOverlay() {
@@ -56,7 +58,8 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
}
async applyFilters() {
this.filterChanges = await this.filters$.pipe(first()).toPromise();
// this.filterChanges = await this.filters$.pipe(first()).toPromise();
// console.log(this.filterChanges);
this.articleSearchStore.search({ clear: true });
this.searchStateSubscription = this.articleSearchStore.searchState$.subscribe((state) => {
@@ -66,6 +69,14 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
});
}
checkInputFilter(filter: Filter[]) {
this.articleSearchStore.setInputSelectorFilter({ filter });
}
checkMainFilter(filter: Filter[]) {
this.articleSearchStore.setMainFilter({ filter });
}
updateCategory(filter: Filter) {
this.lastSelectedFilterCategory.emit(filter);
this.cdr.markForCheck();

View File

@@ -0,0 +1,18 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { UiCommonModule } from '@ui/common';
import { UiFilterModule } from '@ui/filter';
import { UiIconModule } from '@ui/icon';
import { ArticleSearchboxModule } from '../containers/article-searchbox/article-searchbox.module';
import { FilterChipsModule } from '../containers/filter-chips/filter-chips.module';
import { ArticleSearchFilterComponent } from './search-filter.component';
@NgModule({
imports: [CommonModule, FilterChipsModule, ArticleSearchboxModule, UiIconModule, UiFilterModule],
exports: [ArticleSearchFilterComponent],
declarations: [ArticleSearchFilterComponent],
providers: [],
})
export class SearchFilterModule {}

View File

@@ -3,16 +3,20 @@
<p class="info">
Welchen Artikel suchen Sie?
</p>
<div class="filter-chips">
<page-filter-chips (filterChange)="checkMainFilter($event)" [filter]="mainFilter$ | async"></page-filter-chips>
<page-filter-chips (filterChange)="checkInputFilter($event)" [filter]="inputFilter$ | async"></page-filter-chips>
</div>
<page-article-searchbox mode="main"></page-article-searchbox>
<div class="recent-searches-wrapper">
<h3 class="recent-searches-header">Deine letzten Suchanfragen</h3>
<ul>
<!-- <li class="recent-searches-items" *ngFor="let recentQuery of recentQueries$ | async">
<button (click)="setQueryHistory(recentQuery)">
<li class="recent-searches-items" *ngFor="let recentQuery of history$ | async">
<button (click)="setQueryHistory(recentQuery.friendlyName)">
<ui-icon icon="search" size="15px"></ui-icon>
<p>{{ recentQuery.input }}</p>
<p>{{ recentQuery.friendlyName }}</p>
</button>
</li> -->
</li>
</ul>
</div>
</div>

View File

@@ -10,6 +10,10 @@
@apply text-2xl mt-1 mb-px-30;
}
.filter-chips {
@apply flex flex-row justify-center;
}
.card-search-article {
@apply bg-white rounded p-4 text-center;

View File

@@ -1,6 +1,9 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { first, tap } from 'rxjs/operators';
import { DomainCatalogService } from '@domain/catalog';
import { Filter } from '@ui/filter';
import { NEVER } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ArticleSearchStore } from '../article-search-new.store';
// import { ArticleSearchStore } from '../article-search.store';
@@ -11,18 +14,13 @@ import { ArticleSearchStore } from '../article-search-new.store';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleSearchMainComponent implements OnInit, OnDestroy {
// readonly recentQueries$ = this.articleSearchStore.history$;
readonly history$ = this.catalog.getSearchHistory({ take: 5 }).pipe(catchError(() => NEVER));
readonly inputFilter$ = this.articleSearchStore.inputSelectorFilter$;
readonly mainFilter$ = this.articleSearchStore.mainFilter$;
readonly filter$ = this.articleSearchStore.filter$;
// readonly primaryFilter$ = this.articleSearchStore.primaryFilter$;
constructor(private articleSearchStore: ArticleSearchStore, private route: ActivatedRoute) {}
constructor(private articleSearchStore: ArticleSearchStore, private catalog: DomainCatalogService, private route: ActivatedRoute) {}
ngOnInit() {
// const query = this.route.snapshot.queryParams.taskCalendarSearch;
// if (!!query) {
// this.articleSearchStore.setTaskCalendarArticleSearchQuery({ query });
// }
this.articleSearchStore.connect(this.route);
}
@@ -30,19 +28,15 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
this.articleSearchStore.disconnect();
}
async setQueryHistory(recentQuery: { input: string; filter: string; primaryFilter: string }) {
// const filter = await this.filter$.pipe(first()).toPromise();
// const primaryFilter = await this.primaryFilter$.pipe(first()).toPromise();
// this.articleSearchStore.setSelectedFilterBasedOnIdString(
// recentQuery.filter,
// filter.concat(primaryFilter.filter((pf) => pf.target === 'filter'))
// );
// if (recentQuery.primaryFilter !== 'qs') {
// this.articleSearchStore.setSelectedFilterBasedOnIdString(
// recentQuery.primaryFilter,
// primaryFilter.filter((pf) => pf.target === 'input')
// );
// }
// this.articleSearchStore.setQuery({ query: recentQuery.input, history: true });
checkInputFilter(filter: Filter[]) {
this.articleSearchStore.setInputSelectorFilter({ filter });
}
checkMainFilter(filter: Filter[]) {
this.articleSearchStore.setMainFilter({ filter });
}
setQueryHistory(recentQuery: string) {
this.articleSearchStore.setQuery({ query: recentQuery });
}
}

View File

@@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon';
import { ArticleSearchboxModule } from '../containers/article-searchbox/article-searchbox.module';
import { FilterChipsModule } from '../containers/filter-chips/filter-chips.module';
import { ArticleSearchMainComponent } from './search-main.component';
@NgModule({
imports: [CommonModule, UiIconModule, FilterChipsModule, ArticleSearchboxModule],
exports: [ArticleSearchMainComponent],
declarations: [ArticleSearchMainComponent],
providers: [],
})
export class SearchMainModule {}

View File

@@ -1,12 +1,12 @@
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, AfterContentInit, AfterViewInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { ItemDTO } from '@swagger/cat';
import { ScrollPositionService } from 'apps/ui/common/src/lib/scroll-position/scroll-position.service';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
import { first, map, withLatestFrom } from 'rxjs/operators';
import { CacheService } from 'apps/core/cache/src/public-api';
import { BehaviorSubject, Subscription } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { ArticleSearchStore } from '../article-search-new.store';
@Component({
@@ -15,54 +15,90 @@ import { ArticleSearchStore } from '../article-search-new.store';
styleUrls: ['search-results.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterViewInit {
export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterContentInit, AfterViewInit {
@ViewChild('scrollContainer', { static: false }) scrollContainer: CdkVirtualScrollViewport;
loading$ = new BehaviorSubject<boolean>(false);
results$ = this.store.items$;
fetching$ = this.store.searchState$.pipe(map((state) => state === 'fetching'));
private readonly subscriptions = new Subscription();
scrollPosSubscription: Subscription;
trackByItemId = (item: ItemDTO) => item.id;
constructor(
private store: ArticleSearchStore,
private route: ActivatedRoute,
private scrollPositionService: ScrollPositionService,
private router: Router,
private application: ApplicationService,
private breadcrumb: BreadcrumbService
private breadcrumb: BreadcrumbService,
private cache: CacheService
) {}
ngOnInit() {
this.store.connect(this.route, { connected: () => this.store.search({ reload: true }) });
this.store.connect(this.route, {
connected: () => {
this.route.queryParams.subscribe((params) => {
const cachedItems = this.cache.get(params);
if (cachedItems) {
this.store.setItems({ items: cachedItems });
}
this.store.search({ reload: true });
// if (params.scrollPos) {
// this.scrollTo(Number(params.scrollPos));
// }
});
},
disconnected: () => {
// this.store.setQueryParam({ key: 'scrollPos', value: this.scrollContainer.measureScrollOffset('top').toString() });
this.cache.set(this.store.queryParams, this.store.items);
},
});
const { query, queryParams, hits } = this.store;
this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this.application.activatedProcessId,
name: `${query} (${hits} Ergebnisse)`,
name: `${query} (${hits ? hits : 'Lade'} Ergebnisse)`,
path: '/product/search/results',
params: queryParams,
tags: ['catalog', 'filter', 'results'],
});
}
ngAfterViewInit() {
this.scrollPositionService.setScrollContainerRef(this.scrollContainer);
this.scrollPositionService.scrollToLastRememberedPositionVirtualScroll();
// this.scrollPosSubscription = this.store.onSearch.subscribe((options) => {
// if (options?.clear) {
// this.store.setQueryParam({ key: 'scrollPos', value: '0' });
// this.scrollTo(0);
// }
// });
}
ngOnDestroy() {
this.store.disconnect();
this.loading$.complete();
// this.subscriptions.unsubscribe();
// this.scrollPosSubscription.unsubscribe();
}
ngAfterContentInit() {
// const scrollPos = this.route?.snapshot?.queryParams?.scrollPos;
// if (scrollPos) {
// this.scrollTo(Number(scrollPos));
// }
}
ngAfterViewInit() {
// const scrollPos = this.route?.snapshot?.queryParams?.scrollPos;
// if (scrollPos) {
// this.scrollTo(Number(scrollPos));
// }
}
// scrollTo(scrollPos: number) {
// this.scrollContainer.scrollTo({ top: scrollPos });
// }
async scrolledIndexChange(index: number) {
// this.store.queryParamsChanges(); // Update Scrollposition
const results = await this.results$.pipe(first()).toPromise();
if (index >= results.length - 20 && results.length - 20 > 0) {
this.store.search({ clear: false });

View File

@@ -0,0 +1,19 @@
import { ScrollingModule } from '@angular/cdk/scrolling';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { DomainCatalogModule } from '@domain/catalog';
import { UiCommonModule } from '@ui/common';
import { UiIconModule } from '@ui/icon';
import { OrderByFilterComponent } from './order-by-filter/order-by-filter.component';
import { StockInfosPipe } from './order-by-filter/stick-infos.pipe';
import { SearchResultItemComponent } from './search-result-item.component';
import { ArticleSearchResultsComponent } from './search-results.component';
@NgModule({
imports: [CommonModule, RouterModule, DomainCatalogModule, UiCommonModule, UiIconModule, ScrollingModule],
exports: [ArticleSearchResultsComponent, SearchResultItemComponent, OrderByFilterComponent],
declarations: [ArticleSearchResultsComponent, SearchResultItemComponent, OrderByFilterComponent, StockInfosPipe],
providers: [],
})
export class SearchResultsModule {}

View File

@@ -1,12 +1,11 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { ScrollPositionService } from 'apps/ui/common/src/lib/scroll-position/scroll-position.service';
@Component({
selector: 'page-catalog',
templateUrl: 'page-catalog.component.html',
styleUrls: ['page-catalog.component.scss'],
providers: [ScrollPositionService],
providers: [],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageCatalogComponent implements OnInit {

View File

@@ -56,6 +56,6 @@ export class ArticleListModalComponent {
const taskCalendarSearch: string = articles.map((article: ArticleDTO) => article.ean).join(';');
this.modalRef.close('closeAll');
this.moduleSwitcherService.switchToCustomer();
this.router.navigate(['/product', 'search'], { queryParams: { taskCalendarSearch } });
this.router.navigate(['/product', 'search', 'results'], { queryParams: { query: taskCalendarSearch } });
}
}

View File

@@ -1,4 +1,4 @@
import { Directive, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, Input, Output, Renderer2 } from '@angular/core';
import { Directive, ElementRef, EventEmitter, forwardRef, HostListener, Input, Output, Renderer2 } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Directive({
@@ -16,7 +16,6 @@ export class UiSearchboxInputDirective implements ControlValueAccessor {
focused = false;
@Input()
@HostBinding('value')
value: string;
@Output()
@@ -77,5 +76,6 @@ export class UiSearchboxInputDirective implements ControlValueAccessor {
this.select.emit(value);
}
}
this.renderer.setProperty(this.elementRef.nativeElement, 'value', this.value);
}
}

367
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -64,12 +64,13 @@
"intersection-observer": "^0.11.0",
"lodash": "^4.17.21",
"ng-connection-service": "^1.0.4",
"ngx-device-detector": "^2.0.6",
"ng2-pdf-viewer": "^6.4.1",
"ngx-device-detector": "^2.0.6",
"ngx-infinite-scroll": "^7.2.0",
"ngx-perfect-scrollbar": "^7.2.1",
"ngx-toggle-switch": "^2.0.5",
"node-sass": "^4.14.1",
"object-hash": "^2.1.1",
"rxjs": "~6.6.3",
"smoothscroll-polyfill": "^0.4.4",
"socket.io": "^2.2.0",
@@ -92,6 +93,7 @@
"@types/jasminewd2": "~2.0.3",
"@types/lodash": "^4.14.168",
"@types/node": "^12.11.1",
"@types/object-hash": "^2.1.0",
"@types/uuid": "^8.3.0",
"codelyzer": "^5.1.2",
"husky": "^4.2.3",

View File

@@ -230,6 +230,10 @@
],
"@swagger/remi": [
"apps/swagger/remi/src/public-api.ts"
],
"@core/cache": [
"dist/core/cache/core-cache",
"dist/core/cache"
]
}
}