Updated Catalog Store

This commit is contained in:
Lorenz Hilpert
2021-05-19 18:47:27 +02:00
parent 45c7e14d25
commit 06274408dd
2 changed files with 386 additions and 0 deletions

View File

@@ -0,0 +1,300 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { StringDictionary } from '@cmf/core';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { isEqual } from 'lodash';
import { combineLatest, Observable, Subscription } from 'rxjs';
import { first, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { DomainCatalogService } from '@domain/catalog';
import {
mapFilterArrayToStringDictionary,
mapInputArraysToFilterGroupArrayInsideInputGroup,
mapSelectedFilterToParams,
} from './article-search.mappings';
import { Filter } from '@ui/filter';
import { ItemDTO, QueryTokenDTO, OrderByDTO } from '@swagger/cat';
export interface ArticleSearchState {
searchState: 'empty' | 'fetching' | '';
params: {
query?: string;
filter?: string;
primaryFilter?: string;
orderBy?: string;
desc?: string;
[key: string]: string;
};
items?: ItemDTO[];
hits?: number;
history?: QueryTokenDTO[];
}
@Injectable()
export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
private queryParamsQuerySelector = (s: ArticleSearchState) => s.params.query;
readonly queryParamsQuery$ = this.select(this.queryParamsQuerySelector);
private itemsSelector = (s: ArticleSearchState) => s.items;
readonly items$ = this.select(this.itemsSelector);
private orderBySelector = (s: ArticleSearchState) => s.params.orderBy;
readonly queryParamsOrderBy$ = this.select(this.orderBySelector);
private descSelector = (s: ArticleSearchState) => s.params.desc;
readonly queryParamsDesc$ = this.select(this.descSelector);
private hitsSelector = (s: ArticleSearchState) => s.hits;
readonly hits$ = this.select(this.hitsSelector);
private queryParamsSelector = (s: ArticleSearchState) => s.params;
readonly queryParams$ = this.select(this.queryParamsSelector);
private queryParamsFilterSelector = (s: ArticleSearchState) => s.params?.filter;
readonly queryParamsFilter$ = this.select(this.queryParamsFilterSelector);
private queryParamsPrimaryFilterSelector = (s: ArticleSearchState) => s.params?.primaryFilter;
readonly queryParamsPrimaryFilter$ = this.select(this.queryParamsPrimaryFilterSelector);
readonly defaultFilter$ = this.catalog.getFilters().pipe(
map((ig) => {
const groups = mapInputArraysToFilterGroupArrayInsideInputGroup(ig);
return {
filter: groups.filter((f) => f.group === 'filter'),
main: groups.filter((f) => f.group === 'main'),
inputSelector: groups.filter((f) => f.group === 'input_selector'),
};
})
);
readonly orderByOptions$ = this.catalog.getOrderBy();
readonly filter$ = combineLatest([this.queryParamsFilter$, this.defaultFilter$]).pipe(
map(([selectedFilters, defaultFilters]) => {
const filters = [...defaultFilters.filter, ...defaultFilters.inputSelector];
const keys = selectedFilters.split(';');
filters?.forEach((filter) => {
filter?.input?.forEach((input) => {
input?.options?.forEach((option) => {
option.selected = keys.includes(String(option.key));
});
});
});
return filters.reduce((agg, filter) => [...agg, ...filter.input], []) as Filter[];
})
);
readonly primaryFilter$ = combineLatest([this.queryParamsPrimaryFilter$, this.defaultFilter$]).pipe(
map(([selectedFilters, defaultFilters]) => {
const filters = [...defaultFilters.main];
const keys = selectedFilters.split(';');
filters?.forEach((filter) => {
filter?.input?.forEach((input) => {
input?.options?.forEach((option) => {
option.selected = keys.includes(String(option.key));
});
});
});
return filters.reduce((agg, filter) => [...agg, ...filter.input], []) as Filter[];
})
);
readonly queryTokenFilter$ = this.filter$.pipe(map((filter) => mapFilterArrayToStringDictionary(filter)));
readonly queryTokenInput$ = combineLatest([this.queryParamsQuery$, this.primaryFilter$]).pipe(
map(([query, primaryFilter]) => {
const dic: StringDictionary<string> = {};
primaryFilter.forEach((filter) =>
filter.options.forEach((option) => {
if (option.selected) {
dic[option.key] = query;
}
})
);
if (Object.keys(dic).length === 0) {
dic['qs'] = query;
}
return dic;
})
);
readonly queryTokenOrderBy$ = combineLatest([this.queryParamsOrderBy$, this.queryParamsDesc$, this.orderByOptions$]).pipe(
map(([orderBy, desc, orderByOptions]) => orderByOptions.filter((opt) => opt.by === orderBy && !!opt.desc === Boolean(desc)))
);
readonly queryToken$ = combineLatest([this.queryTokenInput$, this.queryTokenFilter$, this.queryTokenOrderBy$]).pipe(
map(([input, filter, orderBy]) => {
const query = {
input,
orderBy,
filter,
returnStockData: false,
} as QueryTokenDTO;
const friendlyName = this.getFriendlyName(query);
if (!!friendlyName) {
query.friendlyName = friendlyName;
}
return query;
})
);
private connectedRouteSubscription: Subscription;
constructor(
private router: Router,
private catalog: DomainCatalogService,
private breadcrumb: BreadcrumbService,
private application: ApplicationService
) {
super({
searchState: '',
params: {},
});
}
search = this.effect((options$: Observable<{ clear?: boolean; reload?: boolean }>) =>
options$.pipe(
withLatestFrom(this.queryToken$, this.items$),
switchMap(([options, queryToken, items]) =>
this.catalog
.search({
queryToken: {
...queryToken,
skip: options.clear || options.reload ? 0 : items.length,
take: options.reload ? items.length : 25,
},
})
.pipe(
tapResponse(
(res) => {
if (options.clear || options.reload) {
this.patchState({ items: res.result, hits: res.hits });
} else {
this.patchState({ items: [...items, ...res.result], hits: res.hits });
}
},
(err) => {}
)
)
)
)
);
connect(route: ActivatedRoute) {
this.disconnect();
this.connectedRouteSubscription = route.queryParams.subscribe((params) => {
const current = this.get(this.queryParamsSelector);
if (!isEqual(current, params)) {
this.setQueryParams(params);
}
});
const queryParamsSub = this.queryParams$.subscribe((params) => {
this.updateQueryParams(params);
});
this.connectedRouteSubscription.add(queryParamsSub);
return { disconnect: () => this.disconnect() };
}
readonly loadHistory = this.effect(($) =>
$.pipe(
switchMap((_) =>
this.catalog.getSearchHistory({ take: 5 }).pipe(
tapResponse(
(history) => this.patchState({ history }),
(err) => {}
)
)
)
)
);
async updateBreadcrumbs(params: StringDictionary<string>) {
const crumbs = await this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.application.activatedProcessId, ['catalog', 'filter'])
.pipe(first())
.toPromise();
for (const crumb of crumbs) {
this.breadcrumb.patchBreadcrumb(crumb.id, { params });
}
}
async updateQueryParams(params: StringDictionary<string>) {
await this.updateBreadcrumbs(params);
await this.router.navigate([], {
queryParams: params,
});
}
getFriendlyName(query: QueryTokenDTO) {
return this.get((s) => s.history).find((history) =>
isEqual({ input: history.input, filter: history.filter }, { input: query.input, filter: query.filter })
)?.friendlyName;
}
disconnect() {
this.connectedRouteSubscription?.unsubscribe();
}
setQueryParams(params: StringDictionary<string>) {
this.patchState({ params });
}
setQuery(query: string) {
const queryParams = this.get(this.queryParamsSelector);
this.patchState({
params: {
...queryParams,
query,
},
});
}
setFilter(filter: Filter[]) {
const queryParams = this.get(this.queryParamsSelector);
this.patchState({
params: {
...queryParams,
filter: mapSelectedFilterToParams(filter),
},
});
}
setPrimaryFilter(filter: Filter[]) {
const queryParams = this.get(this.queryParamsSelector);
this.patchState({
params: {
...queryParams,
primaryFilter: mapSelectedFilterToParams(filter),
},
});
}
setOrderBy(orderBy: OrderByDTO) {
const queryParams = this.get(this.queryParamsSelector);
this.patchState({
params: {
...queryParams,
orderBy: orderBy?.by,
desc: !!orderBy ? String(orderBy.desc) : undefined,
},
});
}
}

View File

@@ -0,0 +1,86 @@
import { StringDictionary } from '@cmf/core';
import { InputGroupDTO, InputDTO, OptionDTO, InputType } from '@swagger/cat';
import { SelectFilterGroup, SelectFilter, SelectFilterOption, FilterType, Filter } from '@ui/filter';
export function mapInputArraysToFilterGroupArrayInsideInputGroup(source: InputGroupDTO[]): SelectFilterGroup[] {
const mappedArr = source.map((inputGroup) => {
return {
group: inputGroup.group || undefined,
input: inputGroup.input.map((input) => this.fromInputDto(input)),
label: inputGroup.label || undefined,
} as SelectFilterGroup;
});
return mappedArr;
}
export function concatNestedArrays(source: SelectFilterGroup[]): SelectFilter[] {
const arr: SelectFilter[] = [];
source.forEach((filter) => arr.push(...filter.input));
return arr;
}
export function fromInputDto(input: InputDTO): SelectFilter {
return {
key: input.key,
name: input.label,
max: input.options && input.options.max,
type: this.inputTypeToType(input.type),
target: input.target,
options: input.options && input.options.values.map((v) => this.fromOptionDto(v)),
} as SelectFilter;
}
export function fromOptionDto(option: OptionDTO): SelectFilterOption {
return {
name: option.label,
id: option.key || option.value,
selected: option.selected || false,
initial_selected_state: option.selected || false,
expanded: false,
options: option.values && option.values.map((v) => this.fromOptionDto(v)),
} as SelectFilterOption;
}
export function inputTypeToType(type: InputType): FilterType {
switch (type) {
case 1:
return 'text';
case 2:
return 'select';
case 4:
return 'checkbox';
case 8 || 16:
return 'date';
case 32 || 64:
return 'number';
default:
return 'select';
}
}
export function mapFilterArrayToStringDictionary(source: Filter[]): StringDictionary<string> {
const dict: StringDictionary<string> = {};
for (const filter of source) {
const selected = filter.options.filter((o) => o.selected);
if (selected.length > 0) {
dict[filter.key] = selected.map((o) => o.id).join(';');
}
}
return dict;
}
export function mapSelectedFilterToParams(source: Filter[]): string {
const dict: StringDictionary<string> = {};
for (const filter of source) {
const selected = filter.options.filter((o) => o.selected);
if (selected.length > 0) {
dict[filter.key] = selected.map((o) => o.id).join(';');
}
}
return [...Object.values(dict)].join(';');
}