Fix Artikelsuche Prozesswechsel und Breadcrumb

Co-authored-by: schickinger <schickinger@users.noreply.github.com>
This commit is contained in:
Lorenz Hilpert
2021-05-27 17:53:15 +02:00
parent c8e0457b67
commit 65ae3ef6e7
4 changed files with 120 additions and 133 deletions

View File

@@ -59,13 +59,20 @@ export class BreadcrumbService {
return this.store.select(selectors.selectBreadcrumbsByKeyAndTags, { key, tags });
}
async removeBreadcrumbsAfter(breadcrumbId: number) {
async removeBreadcrumbsAfter(breadcrumbId: number, withTags: string[] = []) {
const breadcrumb = await this.getBreadcrumbById$(breadcrumbId).pipe(take(1)).toPromise();
if (!breadcrumb) {
return;
}
const breadcrumbs = await this.getBreadcrumbByKey$(breadcrumb.key).pipe(take(1)).toPromise();
let breadcrumbs: Breadcrumb[];
if (withTags?.length > 0) {
breadcrumbs = await this.getBreadcrumbsByKeyAndTags$(breadcrumb.key, withTags).pipe(take(1)).toPromise();
} else {
breadcrumbs = await this.getBreadcrumbByKey$(breadcrumb.key).pipe(take(1)).toPromise();
}
if (!breadcrumbs?.length) {
return;

View File

@@ -18,6 +18,7 @@ import { Filter } from '@ui/filter';
import { ItemDTO, QueryTokenDTO, OrderByDTO } from '@swagger/cat';
export interface ArticleSearchState {
processId?: number;
searchState: 'empty' | 'fetching' | '';
params: {
query?: string;
@@ -38,6 +39,11 @@ export interface ArticleSearchState {
export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
readonly onSearch = new Subject<{ clear?: boolean; reload?: boolean }>();
private processIdSelector = (s: ArticleSearchState) => s.processId;
get processId() {
return this.get(this.processIdSelector);
}
private queryParamsQuerySelector = (s: ArticleSearchState) => (s.params?.query?.length ? decodeURI(s.params.query) : '');
readonly queryParamsQuery$ = this.select(this.queryParamsQuerySelector);
get query() {
@@ -154,14 +160,7 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
)
);
private connectedRouteSubscription: Subscription;
constructor(
private router: Router,
private catalog: DomainCatalogService,
private breadcrumb: BreadcrumbService,
private application: ApplicationService
) {
constructor(private router: Router, private catalog: DomainCatalogService, private breadcrumb: BreadcrumbService) {
super({
searchState: '',
params: {},
@@ -211,58 +210,9 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
)
);
connect(
route: ActivatedRoute,
options?: {
beforeParamsChange?: (
current: StringDictionary<string>,
previous: StringDictionary<string>
) => StringDictionary<string> | Promise<StringDictionary<string>>;
afterParamsChange?: (params: StringDictionary<string>, original?: StringDictionary<string>) => void | Promise<void>;
connected?: (params: StringDictionary<string>) => void | Promise<void>;
disconnected?: () => void;
}
) {
this.disconnect();
let connected = false;
this.connectedRouteSubscription = route.queryParams
.pipe(finalize(() => options?.disconnected?.call(undefined)))
.subscribe(async (params) => {
let next = { ...params };
let previous = this.get(this.queryParamsSelector);
if (typeof options?.beforeParamsChange === 'function') {
next = await options.beforeParamsChange(params, previous);
}
if (!isEqual(previous, next)) {
this.setQueryParams({ params: next });
}
if (typeof options?.afterParamsChange === 'function') {
await options.afterParamsChange(next, params);
}
if (!connected) {
connected = true;
setTimeout(() => options?.connected?.call(undefined, next), 0);
}
});
return {
disconnect: () => {
this.disconnect();
},
};
}
async updateBreadcrumbs() {
const params = this.get(this.queryParamsSelector);
const crumbs = await this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.application.activatedProcessId, ['catalog', 'filter'])
.pipe(first())
.toPromise();
const crumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(this.processId, ['catalog', 'filter']).pipe(first()).toPromise();
const { hits, query } = this;
@@ -283,23 +233,28 @@ export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
});
}
disconnect() {
this.connectedRouteSubscription?.unsubscribe();
}
navigateToResults() {
this.router.navigate(['/product/search/results'], { queryParams: this.queryParams });
const path = '/product/search/results';
if (!this.router.isActive(path, false)) {
this.router.navigate([path], { queryParams: this.queryParams });
}
}
navigateToDetails(item: ItemDTO) {
this.router.navigate(['/product/details', item.id]);
const path = '/product/details';
if (!this.router.isActive(path, false)) {
this.router.navigate([path, item.id]);
}
}
navigateToMain() {
this.router.navigate(['/product/search'], { queryParams: this.queryParams });
const path = '/product/search';
if (!this.router.isActive(path, false)) {
this.router.navigate([path], { queryParams: this.queryParams });
}
}
private setQueryParams({ params }: { params: StringDictionary<string> }) {
setQueryParams({ params }: { params: StringDictionary<string> }) {
this.patchState({ params });
}

View File

@@ -4,9 +4,10 @@ import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { DomainCatalogService } from '@domain/catalog';
import { Filter } from '@ui/filter';
import { NEVER, Subscription } from 'rxjs';
import { catchError, first, switchMap } from 'rxjs/operators';
import { combineLatest, NEVER, Subscription } from 'rxjs';
import { catchError, debounceTime, first, switchMap } from 'rxjs/operators';
import { ArticleSearchStore } from '../article-search-new.store';
import { isEqual } from 'lodash';
@Component({
selector: 'page-article-search-main',
@@ -16,13 +17,13 @@ import { ArticleSearchStore } from '../article-search-new.store';
})
export class ArticleSearchMainComponent implements OnInit, OnDestroy {
readonly history$ = this.catalog.getSearchHistory({ take: 5 }).pipe(catchError(() => NEVER));
readonly inputFilter$ = this.articleSearchStore.inputSelectorFilter$;
readonly mainFilter$ = this.articleSearchStore.mainFilter$;
readonly inputFilter$ = this.store.inputSelectorFilter$;
readonly mainFilter$ = this.store.mainFilter$;
subscriptions = new Subscription();
constructor(
private articleSearchStore: ArticleSearchStore,
private store: ArticleSearchStore,
private catalog: DomainCatalogService,
private route: ActivatedRoute,
private breadcrumb: BreadcrumbService,
@@ -30,30 +31,44 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
this.articleSearchStore.connect(this.route);
this.subscriptions.add(
this.application.activatedProcessId$
.pipe(switchMap((processId) => this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'main']).pipe(first())))
.subscribe((crumbs) => {
crumbs.forEach((crumb) => this.breadcrumb.removeBreadcrumbsAfter(crumb.id));
combineLatest([this.application.activatedProcessId$, this.route.queryParams])
.pipe(debounceTime(0))
.subscribe(async ([processId, queryParams]) => {
// Setzen des aktuellen Prozesses
if (this.store?.processId !== processId) {
this.store.patchState({ processId });
}
// Updaten der QueryParams wenn diese sich ändern
if (!isEqual(this.store.queryParams, queryParams)) {
const params = { ...queryParams };
delete params.scrollPos;
this.store.setQueryParams({ params });
}
const crumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'main']).pipe(first()).toPromise();
for (const crumb of crumbs) {
this.breadcrumb.removeBreadcrumbsAfter(crumb.id, ['catalog']);
}
})
);
}
ngOnDestroy() {
this.articleSearchStore.disconnect();
this.subscriptions.unsubscribe();
}
checkInputFilter(filter: Filter[]) {
this.articleSearchStore.setInputSelectorFilter({ filter });
this.store.setInputSelectorFilter({ filter });
}
checkMainFilter(filter: Filter[]) {
this.articleSearchStore.setMainFilter({ filter });
this.store.setMainFilter({ filter });
}
setQueryHistory(recentQuery: string) {
this.articleSearchStore.setQuery({ query: recentQuery });
this.store.setQuery({ query: recentQuery });
}
}

View File

@@ -6,8 +6,9 @@ import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { ItemDTO } from '@swagger/cat';
import { CacheService } from 'apps/core/cache/src/public-api';
import { BehaviorSubject } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, finalize, first, map, withLatestFrom } from 'rxjs/operators';
import { ArticleSearchStore } from '../article-search-new.store';
@Component({
@@ -27,6 +28,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
trackByItemId = (item: ItemDTO) => item.id;
private subscriptions = new Subscription();
constructor(
private store: ArticleSearchStore,
private route: ActivatedRoute,
@@ -36,58 +39,53 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
let previousProcessId: number;
this.store.connect(this.route, {
beforeParamsChange: async (current, previous) => {
if (previousProcessId) {
this.cache.set(previous, this.store.items);
await this.updateBreadcrumbs(previousProcessId, {
...previous,
scrollPos: String(this.scrollContainer.measureScrollOffset('top')),
this.subscriptions.add(
combineLatest([this.application.activatedProcessId$, this.route.queryParams])
.pipe(debounceTime(0))
.subscribe(async ([processId, queryParams]) => {
// Wenn ein Prozess bereits zugewiesen ist und der Prozess sich ändert
// Speicher Ergebnisse in den Cache und Update Breadcrumb Params
if (this.store?.processId !== processId) {
this.cacheCurrentItems();
await this.updateBreadcrumbs();
}
// Setzen des aktuellen Prozesses
this.store.patchState({ processId });
// Updaten der QueryParams wenn diese sich ändern
// scrollPos muss entfernt werden um die items anhand der QueryParams zu cachen
if (!isEqual(this.store.queryParams, queryParams)) {
const params = { ...queryParams };
delete params.scrollPos;
const items = this.cache.get<ItemDTO[]>(params);
this.store.setQueryParams({ params });
this.store.setItems({ items });
this.store.search({ reload: true });
}
// Nach dem setzen der Items im store an die letzte Position scrollen
this.scrollTop(Number(queryParams.scrollPos ?? 0));
// Fügt Breadcrumb hinzu falls dieser noch nicht vorhanden ist
this.breadcrumb.addBreadcrumbIfNotExists({
key: processId,
name: `${this.store.query} (Lade Ergebnisse)`,
path: '/product/search/results',
params: queryParams,
tags: ['catalog', 'filter', 'results'],
});
}
previousProcessId = this.application.activatedProcessId;
const params = { ...current };
delete params['scrollPos'];
return params;
},
afterParamsChange: async (params, original) => {
const items = this.cache.get<ItemDTO[]>(params);
if (items) {
this.store.setItems({ items });
}
this.store.search({ reload: true });
const scrollPos = Number(original?.scrollPos);
if (scrollPos !== null) {
this.scrollTop(scrollPos);
}
},
connected: () => {
const { query, queryParams, hits } = this.store;
this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this.application.activatedProcessId,
name: `${query} (${hits ? hits : 'Lade'} Ergebnisse)`,
path: '/product/search/results',
params: queryParams,
tags: ['catalog', 'filter', 'results'],
});
},
disconnected: () => {
this.cache.set(this.store.queryParams, this.store.items);
this.updateBreadcrumbs(previousProcessId, {
...this.store.queryParams,
scrollPos: String(this.scrollContainer.measureScrollOffset('top')),
});
},
});
})
);
}
ngOnDestroy() {
this.store.disconnect();
this.loading$.complete();
this.subscriptions?.unsubscribe();
this.cacheCurrentItems();
this.updateBreadcrumbs();
}
scrollTop(scrollPos: number) {
@@ -101,11 +99,23 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
}
}
async updateBreadcrumbs(processId: number, params: StringDictionary<string>) {
async updateBreadcrumbs() {
const scrollPos = this.scrollContainer.measureScrollOffset('top');
const processId = this.store.processId;
const queryParams = { ...this.store.queryParams };
console.log('updateBreadcrumbs', { scrollPos, processId });
const crumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'filter', 'results']).pipe(first()).toPromise();
const params = { ...queryParams, scrollPos };
for (const crumb of crumbs) {
this.breadcrumb.patchBreadcrumb(crumb.id, { params: { ...params } });
this.breadcrumb.patchBreadcrumb(crumb.id, { params });
}
}
cacheCurrentItems() {
console.log('cacheCurrentItems');
this.cache.set(this.store.queryParams, this.store.items);
}
}