diff --git a/src/lib/filtering/filter-utils.ts b/src/lib/filtering/filter-utils.ts index daf1362..0353ccf 100644 --- a/src/lib/filtering/filter-utils.ts +++ b/src/lib/filtering/filter-utils.ts @@ -3,8 +3,7 @@ import { INestedFilter } from './models/nested-filter.model'; import { IFilterGroup } from './models/filter-group.model'; import { IFilter } from './models/filter.model'; import { NestedFilter } from './models/nested-filter'; -import { IListable } from '../listing'; -import { Id } from '../listing/models/trackable'; +import { Id, IListable } from '../listing'; function copySettings(oldFilters: INestedFilter[], newFilters: INestedFilter[]) { if (!oldFilters || !newFilters) { diff --git a/src/lib/listing/listing-component.directive.ts b/src/lib/listing/listing-component.directive.ts index 75d31aa..327019d 100644 --- a/src/lib/listing/listing-component.directive.ts +++ b/src/lib/listing/listing-component.directive.ts @@ -1,13 +1,12 @@ import { Directive, inject, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; import { combineLatest, Observable } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { FilterService } from '../filtering'; import { SortingService } from '../sorting'; import { AutoUnsubscribe, shareDistinctLast } from '../utils'; import { SearchService } from '../search'; import { EntitiesService, ListingService } from './services'; -import { IListable, TableColumnConfig } from './models'; -import { Id } from './models/trackable'; +import { Id, IListable, TableColumnConfig } from './models'; @Directive() export abstract class ListingComponent, PrimaryKey extends Id = Class['id']> @@ -24,26 +23,14 @@ export abstract class ListingComponent, Prim readonly noMatch$ = this.#noMatch$; // TODO: Move to table content component readonly noContent$ = this.#noContent$; - readonly sortedDisplayedEntities$ = this.#sortedDisplayedEntities$; - abstract readonly tableColumnConfigs: readonly TableColumnConfig[]; abstract readonly tableHeaderLabel: string; - @ViewChild('tableItemTemplate') readonly tableItemTemplate?: TemplateRef; get allEntities(): Class[] { return this.entitiesService.all; } - get #sortedDisplayedEntities$(): Observable { - const sort = (entities: Class[]) => this.sortingService.defaultSort(entities); - const sortedEntities$ = this.listingService.displayed$.pipe(map(sort)); - return this.sortingService.sortingOption$.pipe( - switchMap(() => sortedEntities$), - shareDistinctLast(), - ); - } - get #noMatch$(): Observable { return combineLatest([this.entitiesService.allLength$, this.listingService.displayedLength$]).pipe( map(([hasEntities, hasDisplayedEntities]) => !!hasEntities && !hasDisplayedEntities), diff --git a/src/lib/listing/services/entities.service.ts b/src/lib/listing/services/entities.service.ts index 2243f36..7500a44 100644 --- a/src/lib/listing/services/entities.service.ts +++ b/src/lib/listing/services/entities.service.ts @@ -1,10 +1,9 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { filter, map, startWith, tap } from 'rxjs/operators'; -import { IListable } from '../models'; +import { Id, IListable } from '../models'; import { GenericService, QueryParam } from '../../services'; import { getLength, List, mapEach, shareDistinctLast, shareLast } from '../../utils'; -import { Id } from '../models/trackable'; @Injectable() /** diff --git a/src/lib/listing/services/listing.service.ts b/src/lib/listing/services/listing.service.ts index 1909bee..de765de 100644 --- a/src/lib/listing/services/listing.service.ts +++ b/src/lib/listing/services/listing.service.ts @@ -1,12 +1,12 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; +import { map, switchMap, tap } from 'rxjs/operators'; import { FilterService, getFilteredEntities } from '../../filtering'; import { SearchService } from '../../search'; -import { IListable } from '../models'; +import { Id, IListable } from '../models'; import { EntitiesService } from './entities.service'; import { any, getLength, shareDistinctLast, shareLast } from '../../utils'; -import { Id } from '../models/trackable'; +import { SortingService } from '../../sorting'; @Injectable() export class ListingService, PrimaryKey extends Id = Class['id']> { @@ -18,13 +18,16 @@ export class ListingService, PrimaryKey exte readonly selected$: Observable; readonly selectedEntities$: Observable; readonly selectedLength$: Observable; + readonly sortedDisplayedEntities$: Observable; private _displayed: Class[] = []; private readonly _selected$ = new BehaviorSubject([]); + readonly #sortedDisplayedEntities$ = new BehaviorSubject([]); constructor( protected readonly _filterService: FilterService, protected readonly _searchService: SearchService, protected readonly _entitiesService: EntitiesService, + protected readonly _sortingService: SortingService, ) { this.displayed$ = this._getDisplayed$; this.displayedLength$ = this.displayed$.pipe(getLength, shareDistinctLast()); @@ -39,6 +42,8 @@ export class ListingService, PrimaryKey exte this.areAllSelected$ = this._areAllSelected$; this.areSomeSelected$ = this._areSomeSelected$; this.notAllSelected$ = this._notAllSelected$; + + this.sortedDisplayedEntities$ = this.#getSortedDisplayedEntities$(); } get selected(): Class[] { @@ -113,12 +118,17 @@ export class ListingService, PrimaryKey exte this.setSelected(this._displayed); } - select(entity: Class): void { - const currentEntityIdx = this.selected.indexOf(entity); + select(entity: Class, withShift = false): void { + const currentlySelected = this.selected; + const currentEntityIdx = currentlySelected.indexOf(entity); + if (currentEntityIdx === -1) { - return this.setSelected([...this.selected, entity]); + // Entity is not previously selected, select it + this.setSelected([...currentlySelected, entity]); + } else { + // Entity is previously selected, deselect it + this.setSelected(currentlySelected.slice(0, currentEntityIdx).concat(currentlySelected.slice(currentEntityIdx + 1))); } - this.setSelected(this.selected.filter((_el, idx) => idx !== currentEntityIdx)); } deselect(entities: Class | Class[]) { @@ -131,4 +141,16 @@ export class ListingService, PrimaryKey exte const items = this._displayed.filter(item => this.selected.includes(item)); this.setSelected(items); } + + #getSortedDisplayedEntities$(): Observable { + const sort = (entities: Class[]) => this._sortingService.defaultSort(entities); + const sortedEntities$ = this.displayed$.pipe(map(sort)); + return this._sortingService.sortingOption$.pipe( + switchMap(() => sortedEntities$), + tap(sortedEntities => { + this.#sortedDisplayedEntities$.next(sortedEntities); + }), + shareDistinctLast(), + ); + } } diff --git a/src/lib/listing/table-column-name/table-column-name.component.ts b/src/lib/listing/table-column-name/table-column-name.component.ts index f3aa055..5e0d4e6 100644 --- a/src/lib/listing/table-column-name/table-column-name.component.ts +++ b/src/lib/listing/table-column-name/table-column-name.component.ts @@ -1,8 +1,7 @@ import { ChangeDetectionStrategy, Component, Input, Optional } from '@angular/core'; import { SortingOrders, SortingService } from '../../sorting'; import { KeysOf, Required } from '../../utils'; -import { IListable } from '../models'; -import { Id } from '../models/trackable'; +import { Id, IListable } from '../models'; const ifHasRightIcon = (thisArg: TableColumnNameComponent) => !!thisArg.rightIcon; diff --git a/src/lib/listing/table-content/table-content.component.html b/src/lib/listing/table-content/table-content.component.html index 68e8083..fab1445 100644 --- a/src/lib/listing/table-content/table-content.component.html +++ b/src/lib/listing/table-content/table-content.component.html @@ -7,7 +7,7 @@ id="virtual-scroll" iqserHasScrollbar > - +
implements OnChanges { this.#entityChanged$.next(this.entity); } - toggleEntitySelected(event: MouseEvent, entity: T): void { - event.stopPropagation(); - this.listingService.select(entity); + toggleEntitySelected($event: MouseEvent, entity: T): void { + $event.stopPropagation(); + this.listingService.select(entity, $event.shiftKey); } } diff --git a/src/lib/listing/table-header/table-header.component.ts b/src/lib/listing/table-header/table-header.component.ts index c06befb..7c0212b 100644 --- a/src/lib/listing/table-header/table-header.component.ts +++ b/src/lib/listing/table-header/table-header.component.ts @@ -1,8 +1,7 @@ import { ChangeDetectionStrategy, Component, Input, TemplateRef } from '@angular/core'; import { FilterService } from '../../filtering'; import { EntitiesService, ListingService } from '../services'; -import { IListable, ListingMode, ListingModes, TableColumnConfig } from '../models'; -import { Id } from '../models/trackable'; +import { Id, IListable, ListingMode, ListingModes, TableColumnConfig } from '../models'; @Component({ selector: 'iqser-table-header [tableHeaderLabel]', diff --git a/src/lib/listing/table/table.component.ts b/src/lib/listing/table/table.component.ts index e8e694d..384015a 100644 --- a/src/lib/listing/table/table.component.ts +++ b/src/lib/listing/table/table.component.ts @@ -13,11 +13,10 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; -import { IListable, ListingModes, TableColumnConfig } from '../models'; +import { Id, IListable, ListingModes, TableColumnConfig } from '../models'; import { ListingComponent } from '../listing-component.directive'; import { EntitiesService } from '../services'; import { TableContentComponent } from '../table-content/table-content.component'; -import { Id } from '../models/trackable'; const SCROLLBAR_WIDTH = 11; diff --git a/src/lib/listing/utils.ts b/src/lib/listing/utils.ts index ffce109..2af5f32 100644 --- a/src/lib/listing/utils.ts +++ b/src/lib/listing/utils.ts @@ -4,8 +4,7 @@ import { SortingService } from '../sorting'; import { EntitiesService, ListingService } from './services'; import { forwardRef, Provider, Type } from '@angular/core'; import { ListingComponent } from './listing-component.directive'; -import { IListable } from './models'; -import { Id } from './models/trackable'; +import { Id, IListable } from './models'; export const DefaultListingServices: readonly Provider[] = [FilterService, SearchService, SortingService, ListingService] as const; diff --git a/src/lib/search/search.service.ts b/src/lib/search/search.service.ts index f454349..125b289 100644 --- a/src/lib/search/search.service.ts +++ b/src/lib/search/search.service.ts @@ -1,8 +1,7 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; -import { IListable } from '../listing'; +import { Id, IListable } from '../listing'; import { shareDistinctLast } from '../utils'; -import { Id } from '../listing/models/trackable'; @Injectable() export class SearchService, PrimaryKey extends Id = T['id']> { diff --git a/src/lib/services/entities-map.service.ts b/src/lib/services/entities-map.service.ts index 6f79db8..b7b9379 100644 --- a/src/lib/services/entities-map.service.ts +++ b/src/lib/services/entities-map.service.ts @@ -1,9 +1,8 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { filter, map, startWith } from 'rxjs/operators'; -import { Entity } from '../listing'; +import { Entity, Id } from '../listing'; import { List, RequiredParam, shareLast, Validate } from '../utils'; -import { Id } from '../listing/models/trackable'; import { isArray } from '../permissions'; @Injectable() diff --git a/src/lib/sorting/sorting.service.ts b/src/lib/sorting/sorting.service.ts index 83214a2..9cee406 100644 --- a/src/lib/sorting/sorting.service.ts +++ b/src/lib/sorting/sorting.service.ts @@ -3,9 +3,8 @@ import { BehaviorSubject } from 'rxjs'; import { SortingOption } from './models/sorting-option.model'; import { SortingOrder, SortingOrders } from './models/sorting-order.type'; import { KeysOf, shareDistinctLast } from '../utils'; -import { IListable } from '../listing'; +import { Id, IListable } from '../listing'; import { orderBy } from 'lodash-es'; -import { Id } from '../listing/models/trackable'; @Injectable() export class SortingService, PrimaryKey extends Id = T['id']> {