diff --git a/src/lib/listing/services/listing.service.ts b/src/lib/listing/services/listing.service.ts index e291712..9a82e97 100644 --- a/src/lib/listing/services/listing.service.ts +++ b/src/lib/listing/services/listing.service.ts @@ -3,13 +3,18 @@ import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map, switchMap, tap } from 'rxjs/operators'; import { FilterService, getFilteredEntities } from '../../filtering'; import { SearchService } from '../../search'; +import { SortingService } from '../../sorting'; +import { getLength, shareDistinctLast, shareLast, some } from '../../utils'; import { Id, IListable } from '../models'; import { EntitiesService } from './entities.service'; -import { getLength, shareDistinctLast, shareLast, some } from '../../utils'; -import { SortingService } from '../../sorting'; @Injectable() export class ListingService, PrimaryKey extends Id = Class['id']> { + #displayed: Class[] = []; + readonly #selected$ = new BehaviorSubject([]); + #anchor: Class | undefined = undefined; + #focus: Class | undefined = undefined; + #sortedDisplayed: Class[] = []; readonly displayed$: Observable; readonly displayedLength$: Observable; readonly areAllSelected$: Observable; @@ -20,13 +25,6 @@ export class ListingService, PrimaryKey exte readonly selectedLength$: Observable; readonly sortedDisplayedEntities$: Observable; - private _displayed: Class[] = []; - private readonly _selected$ = new BehaviorSubject([]); - - private _anchor: Class | undefined = undefined; - private _focus: Class | undefined = undefined; - private _sortedDisplayed: Class[] = []; - constructor( protected readonly _filterService: FilterService, protected readonly _searchService: SearchService, @@ -36,12 +34,12 @@ export class ListingService, PrimaryKey exte this.displayed$ = this._getDisplayed$; this.displayedLength$ = this.displayed$.pipe(getLength, shareDistinctLast()); - this.selected$ = this._selected$.asObservable().pipe(shareDistinctLast()); - this.selectedEntities$ = combineLatest([this._selected$.asObservable(), this._entitiesService.all$]).pipe( + this.selected$ = this.#selected$.asObservable().pipe(shareDistinctLast()); + this.selectedEntities$ = combineLatest([this.#selected$.asObservable(), this._entitiesService.all$]).pipe( map(([selectedIds, all]) => all.filter(a => selectedIds.includes(a.id))), shareLast(), ); - this.selectedLength$ = this._selected$.pipe(getLength, shareDistinctLast()); + this.selectedLength$ = this.#selected$.pipe(getLength, shareDistinctLast()); this.areAllSelected$ = this._areAllSelected$; this.areSomeSelected$ = this._areSomeSelected$; @@ -56,7 +54,7 @@ export class ListingService, PrimaryKey exte } get selectedIds(): PrimaryKey[] { - return this._selected$.getValue(); + return this.#selected$.getValue(); } private get _getDisplayed$(): Observable { @@ -67,7 +65,7 @@ export class ListingService, PrimaryKey exte map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)), map(entities => this._searchService.searchIn(entities)), tap(displayed => { - this._displayed = displayed; + this.#displayed = displayed; this._updateSelection(); }), shareLast(), @@ -96,15 +94,16 @@ export class ListingService, PrimaryKey exte } private get _allSelected() { - return this._displayed.length !== 0 && this._displayed.length === this.selected.length; + return this.#displayed.length !== 0 && this.#displayed.length === this.selected.length; } setSelected(newEntities: Class[]): void { + console.trace('setSelected', newEntities); const selectedIds = newEntities.map(e => e.id); - this._selected$.next(selectedIds); + this.#selected$.next(selectedIds); - if (this._anchor && !newEntities.includes(this._anchor)) { - this._anchor = undefined; + if (this.#anchor && !newEntities.includes(this.#anchor)) { + this.#anchor = undefined; } } @@ -113,7 +112,7 @@ export class ListingService, PrimaryKey exte } isSelected$(entity: Class): Observable { - return this._selected$.pipe( + return this.#selected$.pipe( some(selectedId => selectedId === entity.id), shareLast(), ); @@ -123,7 +122,7 @@ export class ListingService, PrimaryKey exte if (this._allSelected) { return this.setSelected([]); } - this.setSelected(this._displayed); + this.setSelected(this.#displayed); } select(entity: Class, withShift = false): void { @@ -134,8 +133,8 @@ export class ListingService, PrimaryKey exte if (!withShift) { if (!isCurrentlySelected) { this.setSelected([...currentlySelected, entity]); - this._anchor = entity; - this._focus = entity; + this.#anchor = entity; + this.#focus = entity; } else { // Entity is previously selected, deselect it this.setSelected(currentlySelected.slice(0, currentEntityIdx).concat(currentlySelected.slice(currentEntityIdx + 1))); @@ -154,52 +153,52 @@ export class ListingService, PrimaryKey exte /** Move anchor & focus to next selected, or previous selected, or undefined */ #moveAnchorAfterDeselect(entity: Class): void { - const entityIdx = this._sortedDisplayed.indexOf(entity); + const entityIdx = this.#sortedDisplayed.indexOf(entity); let newAnchorIdx = entityIdx + 1; let increment = 1; do { - if (this.isSelected(this._sortedDisplayed[newAnchorIdx])) { + if (this.isSelected(this.#sortedDisplayed[newAnchorIdx])) { break; } newAnchorIdx += increment; - if (newAnchorIdx === this._sortedDisplayed.length) { + if (newAnchorIdx === this.#sortedDisplayed.length) { newAnchorIdx = entityIdx - 1; increment = -1; } } while (newAnchorIdx > 0); - this._anchor = this._sortedDisplayed[newAnchorIdx]; - this._focus = this._sortedDisplayed[newAnchorIdx]; + this.#anchor = this.#sortedDisplayed[newAnchorIdx]; + this.#focus = this.#sortedDisplayed[newAnchorIdx]; } #shiftClick(entity: Class): void { - const entityIdx = this._sortedDisplayed.indexOf(entity); + const entityIdx = this.#sortedDisplayed.indexOf(entity); - if (!this._anchor || !this._focus) { - this._anchor = this._sortedDisplayed[0]; - this._focus = this._sortedDisplayed[0]; + if (!this.#anchor || !this.#focus) { + this.#anchor = this.#sortedDisplayed[0]; + this.#focus = this.#sortedDisplayed[0]; } - const anchorIdx = this._sortedDisplayed.indexOf(this._anchor); - const focusIdx = this._sortedDisplayed.indexOf(this._focus); + const anchorIdx = this.#sortedDisplayed.indexOf(this.#anchor); + const focusIdx = this.#sortedDisplayed.indexOf(this.#focus); // Deselect entities between anchor and previous focus - const remove = this._sortedDisplayed.slice(Math.min(anchorIdx, focusIdx), Math.max(anchorIdx, focusIdx) + 1); + const remove = this.#sortedDisplayed.slice(Math.min(anchorIdx, focusIdx), Math.max(anchorIdx, focusIdx) + 1); // Update focus - this._focus = entity; + this.#focus = entity; // Select entities between anchor and new focus - const intervalEntities = this._sortedDisplayed.slice(Math.min(anchorIdx, entityIdx), Math.max(anchorIdx, entityIdx) + 1); + const intervalEntities = this.#sortedDisplayed.slice(Math.min(anchorIdx, entityIdx), Math.max(anchorIdx, entityIdx) + 1); const newSelected = [...intervalEntities, ...this.selected.filter(e => !intervalEntities.includes(e) && !remove.includes(e))]; this.setSelected(newSelected); } private _updateSelection(): void { - const items = this._displayed.filter(item => this.selected.includes(item)); + const items = this.#displayed.filter(item => this.selected.includes(item)); this.setSelected(items); } @@ -209,7 +208,7 @@ export class ListingService, PrimaryKey exte return this._sortingService.sortingOption$.pipe( switchMap(() => sortedEntities$), tap(sortedEntities => { - this._sortedDisplayed = sortedEntities; + this.#sortedDisplayed = sortedEntities; }), shareDistinctLast(), );