Shift click selection logic

This commit is contained in:
Adina Țeudan 2023-02-08 17:20:13 +02:00
parent 610a88570b
commit baad3e1dac

View File

@ -19,9 +19,13 @@ export class ListingService<Class extends IListable<PrimaryKey>, PrimaryKey exte
readonly selectedEntities$: Observable<Class[]>;
readonly selectedLength$: Observable<number>;
readonly sortedDisplayedEntities$: Observable<Class[]>;
private _displayed: Class[] = [];
private readonly _selected$ = new BehaviorSubject<PrimaryKey[]>([]);
readonly #sortedDisplayedEntities$ = new BehaviorSubject<Class[]>([]);
private _anchor: Class | undefined = undefined;
private _focus: Class | undefined = undefined;
private _sortedDisplayed: Class[] = [];
constructor(
protected readonly _filterService: FilterService,
@ -98,6 +102,10 @@ export class ListingService<Class extends IListable<PrimaryKey>, PrimaryKey exte
setSelected(newEntities: Class[]): void {
const selectedIds = newEntities.map(e => e.id);
this._selected$.next(selectedIds);
if (this._anchor && !newEntities.includes(this._anchor)) {
this._anchor = undefined;
}
}
isSelected(entity: Class): boolean {
@ -121,13 +129,20 @@ export class ListingService<Class extends IListable<PrimaryKey>, PrimaryKey exte
select(entity: Class, withShift = false): void {
const currentlySelected = this.selected;
const currentEntityIdx = currentlySelected.indexOf(entity);
const isCurrentlySelected = currentEntityIdx !== -1;
if (currentEntityIdx === -1) {
// Entity is not previously selected, select it
this.setSelected([...currentlySelected, entity]);
if (!withShift) {
if (!isCurrentlySelected) {
this.setSelected([...currentlySelected, 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)));
this.#moveAnchorAfterDeselect(entity);
}
} else {
// Entity is previously selected, deselect it
this.setSelected(currentlySelected.slice(0, currentEntityIdx).concat(currentlySelected.slice(currentEntityIdx + 1)));
this.#shiftClick(entity);
}
}
@ -137,6 +152,52 @@ export class ListingService<Class extends IListable<PrimaryKey>, PrimaryKey exte
this.setSelected(this.selected.filter(el => !entitiesIds.includes(el.id)));
}
/** Move anchor & focus to next selected, or previous selected, or undefined */
#moveAnchorAfterDeselect(entity: Class): void {
const entityIdx = this._sortedDisplayed.indexOf(entity);
let newAnchorIdx = entityIdx + 1;
let increment = 1;
do {
if (this.isSelected(this._sortedDisplayed[newAnchorIdx])) {
break;
}
newAnchorIdx += increment;
if (newAnchorIdx === this._sortedDisplayed.length) {
newAnchorIdx = entityIdx - 1;
increment = -1;
}
} while (newAnchorIdx > 0);
this._anchor = this._sortedDisplayed[newAnchorIdx];
this._focus = this._sortedDisplayed[newAnchorIdx];
}
#shiftClick(entity: Class): void {
const entityIdx = this._sortedDisplayed.indexOf(entity);
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);
// Deselect entities between anchor and previous focus
const remove = this._sortedDisplayed.slice(Math.min(anchorIdx, focusIdx), Math.max(anchorIdx, focusIdx) + 1);
// Update focus
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 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));
this.setSelected(items);
@ -148,7 +209,7 @@ export class ListingService<Class extends IListable<PrimaryKey>, PrimaryKey exte
return this._sortingService.sortingOption$.pipe(
switchMap(() => sortedEntities$),
tap(sortedEntities => {
this.#sortedDisplayedEntities$.next(sortedEntities);
this._sortedDisplayed = sortedEntities;
}),
shareDistinctLast(),
);