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