128 lines
4.6 KiB
TypeScript
128 lines
4.6 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
|
import { map, tap } from 'rxjs/operators';
|
|
import { FilterService, getFilteredEntities } from '../../filtering';
|
|
import { SearchService } from '../../search';
|
|
import { IListable } from '../models';
|
|
import { EntitiesService } from './entities.service';
|
|
import { any, getLength, shareDistinctLast, shareLast } from '../../utils';
|
|
|
|
@Injectable()
|
|
export class ListingService<E extends IListable> {
|
|
readonly displayed$: Observable<E[]>;
|
|
readonly displayedLength$: Observable<number>;
|
|
readonly areAllSelected$: Observable<boolean>;
|
|
readonly areSomeSelected$: Observable<boolean>;
|
|
readonly notAllSelected$: Observable<boolean>;
|
|
readonly selected$: Observable<(string | number)[]>;
|
|
readonly selectedEntities$: Observable<E[]>;
|
|
readonly selectedLength$: Observable<number>;
|
|
private _displayed: E[] = [];
|
|
private readonly _selected$ = new BehaviorSubject<(string | number)[]>([]);
|
|
|
|
constructor(
|
|
private readonly _filterService: FilterService,
|
|
private readonly _searchService: SearchService<E>,
|
|
private readonly _entitiesService: EntitiesService<E>,
|
|
) {
|
|
this.displayed$ = this._getDisplayed$;
|
|
this.displayedLength$ = this.displayed$.pipe(getLength, shareDistinctLast());
|
|
|
|
this.selected$ = this._selected$.asObservable().pipe(shareDistinctLast());
|
|
this.selectedEntities$ = this._selected$.asObservable().pipe(
|
|
map(() => this.selected),
|
|
shareLast(),
|
|
);
|
|
this.selectedLength$ = this._selected$.pipe(getLength, shareDistinctLast());
|
|
|
|
this.areAllSelected$ = this._areAllSelected$;
|
|
this.areSomeSelected$ = this._areSomeSelected$;
|
|
this.notAllSelected$ = this._notAllSelected$;
|
|
}
|
|
|
|
get selected(): E[] {
|
|
const selectedIds = this.selectedIds;
|
|
return this._entitiesService.all.filter(a => selectedIds.includes(a.id));
|
|
}
|
|
|
|
get selectedIds(): (string | number)[] {
|
|
return this._selected$.getValue();
|
|
}
|
|
|
|
private get _getDisplayed$(): Observable<E[]> {
|
|
const { filterGroups$ } = this._filterService;
|
|
const { valueChanges$ } = this._searchService;
|
|
|
|
return combineLatest([this._entitiesService.all$, filterGroups$, valueChanges$]).pipe(
|
|
map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)),
|
|
map(entities => this._searchService.searchIn(entities)),
|
|
tap(displayed => {
|
|
this._displayed = displayed;
|
|
this._updateSelection();
|
|
}),
|
|
shareLast(),
|
|
);
|
|
}
|
|
|
|
private get _areAllSelected$(): Observable<boolean> {
|
|
return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(
|
|
map(([displayedLength, selectedLength]) => !!displayedLength && displayedLength === selectedLength),
|
|
shareDistinctLast(),
|
|
);
|
|
}
|
|
|
|
private get _areSomeSelected$(): Observable<boolean> {
|
|
return this.selectedLength$.pipe(
|
|
map(length => !!length),
|
|
shareDistinctLast(),
|
|
);
|
|
}
|
|
|
|
private get _notAllSelected$(): Observable<boolean> {
|
|
return combineLatest([this.areAllSelected$, this.areSomeSelected$]).pipe(
|
|
map(([allAreSelected, someAreSelected]) => !allAreSelected && someAreSelected),
|
|
shareDistinctLast(),
|
|
);
|
|
}
|
|
|
|
private get _allSelected() {
|
|
return this._displayed.length !== 0 && this._displayed.length === this.selected.length;
|
|
}
|
|
|
|
setSelected(newEntities: E[]): void {
|
|
const selectedIds = newEntities.map(e => e.id);
|
|
this._selected$.next(selectedIds);
|
|
}
|
|
|
|
isSelected(entity: E): boolean {
|
|
return this.selectedIds.indexOf(entity.id) !== -1;
|
|
}
|
|
|
|
isSelected$(entity: E): Observable<boolean> {
|
|
return this._selected$.pipe(
|
|
any(selectedId => selectedId === entity.id),
|
|
shareLast(),
|
|
);
|
|
}
|
|
|
|
selectAll(): void {
|
|
if (this._allSelected) {
|
|
return this.setSelected([]);
|
|
}
|
|
this.setSelected(this._displayed);
|
|
}
|
|
|
|
select(entity: E): void {
|
|
const currentEntityIdx = this.selected.indexOf(entity);
|
|
if (currentEntityIdx === -1) {
|
|
return this.setSelected([...this.selected, entity]);
|
|
}
|
|
this.setSelected(this.selected.filter((el, idx) => idx !== currentEntityIdx));
|
|
}
|
|
|
|
private _updateSelection(): void {
|
|
const items = this._displayed.filter(item => this.selected.includes(item));
|
|
this.setSelected(items);
|
|
}
|
|
}
|