mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Compare commits
4 Commits
hotfix/471
...
performanc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc949e27b9 | ||
|
|
ed110b443c | ||
|
|
f188ece61e | ||
|
|
14cc3f4895 |
@@ -16,7 +16,6 @@ export interface ArticleSearchState {
|
||||
hits: number;
|
||||
selectedBranch: BranchDTO;
|
||||
selectedItemIds: number[];
|
||||
scrollPosition: number;
|
||||
defaultSettings?: UISettingsDTO;
|
||||
}
|
||||
|
||||
@@ -44,12 +43,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
return this.get((s) => s.items);
|
||||
}
|
||||
|
||||
scrollPosition$ = this.select((s) => s.scrollPosition);
|
||||
|
||||
get scrollPosition() {
|
||||
return this.get((s) => s.scrollPosition);
|
||||
}
|
||||
|
||||
selectedBranch$ = this.select((s) => s.selectedBranch);
|
||||
|
||||
get selectedBranch() {
|
||||
@@ -92,7 +85,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
searchState: '',
|
||||
selectedItemIds: [],
|
||||
selectedBranch: undefined,
|
||||
scrollPosition: 0,
|
||||
});
|
||||
this.setDefaultFilter();
|
||||
}
|
||||
@@ -113,10 +105,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
|
||||
this.patchState({ selectedBranch });
|
||||
}
|
||||
|
||||
setScrollPosition(scrollPosition: number) {
|
||||
this.patchState({ scrollPosition });
|
||||
}
|
||||
|
||||
async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
|
||||
const defaultSettings = await this.catalog.getSettings().toPromise();
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<a
|
||||
<div
|
||||
class="page-search-result-item__item-card hover p-5 desktop-small:px-4 desktop-small:py-[0.625rem] h-[13.25rem] desktop-small:h-[11.3125rem] bg-white border border-solid border-transparent rounded"
|
||||
[class.page-search-result-item__item-card-primary]="primaryOutletActive"
|
||||
[routerLink]="detailsPath"
|
||||
[routerLinkActive]="!isTablet && !primaryOutletActive ? 'active' : ''"
|
||||
queryParamsHandling="preserve"
|
||||
(click)="isDesktopLarge ? scrollIntoView() : ''"
|
||||
[class.active]="isActive"
|
||||
>
|
||||
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[3.125rem] h-[4.9375rem]">
|
||||
<img
|
||||
@@ -122,4 +119,4 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostBinding, ElementRef } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostBinding } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
|
||||
@@ -54,6 +54,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
@Input()
|
||||
primaryOutletActive?: boolean = false;
|
||||
|
||||
@Input() isActive: boolean;
|
||||
|
||||
@Output()
|
||||
selectedChange = new EventEmitter<ItemDTO>();
|
||||
|
||||
@@ -82,11 +84,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
return this._environment.matchDesktopLarge();
|
||||
}
|
||||
|
||||
get detailsPath() {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: this.item?.id })
|
||||
.path;
|
||||
}
|
||||
|
||||
get resultsPath() {
|
||||
return this._navigationService.getArticleSearchResultsPath(this.applicationService.activatedProcessId).path;
|
||||
}
|
||||
@@ -141,7 +138,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
private _availability: DomainAvailabilityService,
|
||||
private _environment: EnvironmentService,
|
||||
private _navigationService: ProductCatalogNavigationService,
|
||||
private _elRef: ElementRef<HTMLElement>,
|
||||
private _store: Store
|
||||
) {
|
||||
super({
|
||||
@@ -150,10 +146,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
});
|
||||
}
|
||||
|
||||
scrollIntoView() {
|
||||
this._elRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
setSelected() {
|
||||
const isSelected = this._articleSearchService.selectedItemIds.includes(this.item?.id);
|
||||
this._articleSearchService.setSelected({ selected: !isSelected, itemId: this.item?.id });
|
||||
|
||||
@@ -46,37 +46,74 @@
|
||||
</shared-order-by-filter>
|
||||
</div>
|
||||
|
||||
<div class="h-full relative">
|
||||
<cdk-virtual-scroll-viewport
|
||||
#scrollContainer
|
||||
class="product-list h-full"
|
||||
[itemSize]="(primaryOutletActive$ | async) ? 98 : 181"
|
||||
[maxBufferPx]="maxBufferCdkScrollContainer$ | async"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
[class.page-search-results__result-item-primary]="primaryOutletActive$ | async"
|
||||
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selected]="isSelected(item)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[primaryOutletActive]="primaryOutletActive$ | async"
|
||||
></search-result-item>
|
||||
<page-search-result-item-loading
|
||||
[primaryOutletActive]="primaryOutletActive$ | async"
|
||||
*ngIf="fetching$ | async"
|
||||
></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<div class="actions z-sticky h-0">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
<ng-container *ngIf="primaryOutletActive$ | async; else sideOutlet">
|
||||
<div class="h-full relative">
|
||||
<cdk-virtual-scroll-viewport class="product-list h-full" itemSize="98" (scrolledIndexChange)="scrolledIndexChange($event)">
|
||||
<a
|
||||
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
|
||||
[routerLink]="getDetailsPath(item.id)"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
queryParamsHandling="preserve"
|
||||
(click)="scrollToItem(i)"
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item page-search-results__result-item-primary"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selected]="isSelected(item)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[primaryOutletActive]="true"
|
||||
[isActive]="rla.isActive"
|
||||
></search-result-item>
|
||||
</a>
|
||||
<page-search-result-item-loading [primaryOutletActive]="true" *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<div class="actions z-sticky h-0">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #sideOutlet>
|
||||
<div class="h-full relative">
|
||||
<cdk-virtual-scroll-viewport class="product-list h-full" itemSize="181" (scrolledIndexChange)="scrolledIndexChange($event)">
|
||||
<a
|
||||
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
|
||||
[routerLink]="getDetailsPath(item.id)"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
queryParamsHandling="preserve"
|
||||
(click)="scrollToItem(i)"
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
(selectedChange)="addToCart($event)"
|
||||
[selected]="isSelected(item)"
|
||||
[selectable]="isSelectable(item)"
|
||||
[item]="item"
|
||||
[primaryOutletActive]="false"
|
||||
[isActive]="rla.isActive"
|
||||
></search-result-item>
|
||||
</a>
|
||||
<page-search-result-item-loading [primaryOutletActive]="false" *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<div class="actions z-sticky h-0">
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -28,6 +28,7 @@ import { SearchResultItemComponent } from './search-result-item.component';
|
||||
import { ProductCatalogNavigationService } from '@shared/services';
|
||||
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
|
||||
import { DomainAvailabilityService, ItemData } from '@domain/availability';
|
||||
import { asapScheduler } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'page-search-results',
|
||||
@@ -37,7 +38,7 @@ import { DomainAvailabilityService, ItemData } from '@domain/availability';
|
||||
})
|
||||
export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
@ViewChildren(SearchResultItemComponent) listItems: QueryList<SearchResultItemComponent>;
|
||||
@ViewChild('scrollContainer', { static: true })
|
||||
@ViewChild(CdkVirtualScrollViewport, { static: false })
|
||||
scrollContainer: CdkVirtualScrollViewport;
|
||||
|
||||
@ViewChild(FilterInputGroupMainComponent, { static: false })
|
||||
@@ -59,6 +60,10 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
})
|
||||
);
|
||||
|
||||
getProcessId(): number {
|
||||
return this.application.activatedProcessId;
|
||||
}
|
||||
|
||||
loading$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private subscriptions = new Subscription();
|
||||
@@ -92,21 +97,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
return this._environment.matchDesktop$.pipe(map((matches) => matches && this.route.outlet === 'primary'));
|
||||
}
|
||||
|
||||
// Ticket #4169 Splitscreen
|
||||
// Render genug Artikel um bei Navigation auf Trefferliste | PDP zum angewählten Artikel zu Scrollen
|
||||
maxBufferCdkScrollContainer$ = this.results$.pipe(
|
||||
map((results) => {
|
||||
if (this._environment.matchDesktop() && this.route.outlet === 'side' && results?.length > 0) {
|
||||
// Splitscreen mode: Items Length * Item Pixel Height
|
||||
const maxBufferSize = results.length * 181;
|
||||
return maxBufferSize >= 1200 ? maxBufferSize : 1200;
|
||||
} else if (this._environment.matchTablet()) {
|
||||
return 500;
|
||||
} else {
|
||||
return 1200;
|
||||
}
|
||||
})
|
||||
);
|
||||
private readonly SCROLL_INDEX_TOKEN = 'CATALOG_RESULTS_LIST_SCROLL_INDEX';
|
||||
|
||||
constructor(
|
||||
public searchService: ArticleSearchService,
|
||||
@@ -157,9 +148,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
const cleanQueryParams = this.cleanupQueryParams(queryParams);
|
||||
|
||||
// Scroll to scroll_position in great result list
|
||||
if (!!queryParams?.scroll_position && this.route.outlet === 'primary') {
|
||||
this.scrollTop(Number(queryParams.scroll_position ?? 0));
|
||||
if (this.route.outlet === 'primary' && processChanged) {
|
||||
this.scrollToItem(this._getScrollIndexFromCache());
|
||||
}
|
||||
|
||||
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
|
||||
@@ -175,11 +165,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
) {
|
||||
this.search({ clear: true });
|
||||
} else {
|
||||
if (!this.isDesktopLarge || this.route.outlet === 'primary') {
|
||||
this.scrollTop(Number(queryParams.scroll_position ?? 0));
|
||||
} else {
|
||||
this.scrollItemIntoView();
|
||||
}
|
||||
const selectedItemIds: Array<string> = queryParams?.selected_item_ids?.split(',') ?? [];
|
||||
for (const id of selectedItemIds) {
|
||||
if (id) {
|
||||
@@ -272,7 +257,39 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.scrollItemIntoView();
|
||||
this.scrollToItem(this._getScrollIndexFromCache());
|
||||
}
|
||||
|
||||
private _addScrollIndexToCache(index: number): void {
|
||||
this.cache.set<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }, index);
|
||||
}
|
||||
|
||||
private _getScrollIndexFromCache(): number {
|
||||
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN });
|
||||
}
|
||||
|
||||
scrollToItem(i?: number) {
|
||||
let index = i;
|
||||
|
||||
if (!index) {
|
||||
index = this._getScrollIndexFromCache();
|
||||
} else {
|
||||
this._addScrollIndexToCache(index);
|
||||
}
|
||||
|
||||
asapScheduler.schedule(() => {
|
||||
this.scrollContainer.scrollToIndex(index, 'smooth');
|
||||
}, 150);
|
||||
}
|
||||
|
||||
scrolledIndexChange(index: number) {
|
||||
if (index && this.searchService.items.length <= this.scrollContainer?.getRenderedRange()?.end) {
|
||||
this.search({ clear: false });
|
||||
}
|
||||
|
||||
if (this.getProcessId() === this.searchService.processId) {
|
||||
this._addScrollIndexToCache(index);
|
||||
}
|
||||
}
|
||||
|
||||
async ngOnDestroy() {
|
||||
@@ -306,40 +323,14 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
this.searchService.search({ clear, orderBy, doNotTrack: true });
|
||||
}
|
||||
|
||||
scrollTop(scrollPos: number) {
|
||||
setTimeout(() => this.scrollContainer.scrollTo({ top: scrollPos }), 0);
|
||||
}
|
||||
|
||||
scrollItemIntoView() {
|
||||
setTimeout(() => {
|
||||
const item = this.listItems?.find((item) => item.item.id === Number(this.route?.snapshot?.params?.id));
|
||||
item?.scrollIntoView();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
async scrolledIndexChange(index: number) {
|
||||
alert('scrolledIndexChange');
|
||||
const results = await this.results$.pipe(first()).toPromise();
|
||||
const hits = await this.hits$.pipe(first()).toPromise();
|
||||
|
||||
if (results.length >= hits) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.route.outlet === 'primary') {
|
||||
this.searchService.setScrollPosition(this.scrollContainer.measureScrollOffset('top'));
|
||||
}
|
||||
|
||||
if (index >= results.length - 20 && results.length - 20 > 0) {
|
||||
this.search({ clear: false });
|
||||
}
|
||||
getDetailsPath(itemId: number) {
|
||||
return this._navigationService.getArticleDetailsPath({ processId: this.application.activatedProcessId, itemId }).path;
|
||||
}
|
||||
|
||||
async updateBreadcrumbs(
|
||||
processId: number = this.searchService.processId,
|
||||
queryParams: Record<string, string> = this.searchService.filter?.getQueryParams()
|
||||
) {
|
||||
const scroll_position = this.searchService.scrollPosition;
|
||||
const selected_item_ids = this.searchService?.selectedItemIds?.toString();
|
||||
|
||||
if (queryParams) {
|
||||
@@ -349,7 +340,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
.toPromise();
|
||||
|
||||
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
|
||||
const params = { ...queryParams, scroll_position, selected_item_ids };
|
||||
const params = { ...queryParams, selected_item_ids };
|
||||
|
||||
for (const crumb of crumbs) {
|
||||
this.breadcrumb.patchBreadcrumb(crumb.id, {
|
||||
@@ -402,7 +393,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
|
||||
|
||||
cleanupQueryParams(params: Record<string, string> = {}) {
|
||||
const clean = { ...params };
|
||||
delete clean['scroll_position'];
|
||||
delete clean['selected_item_ids'];
|
||||
|
||||
for (const key in clean) {
|
||||
|
||||
Reference in New Issue
Block a user