Shift click selection logic
This commit is contained in:
parent
610a88570b
commit
baad3e1dac
@ -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(),
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user