add new listing service with listing related methods
This commit is contained in:
parent
916bf9bda3
commit
aed0b51a6d
@ -5,11 +5,11 @@ import { FilterService } from '../filtering';
|
|||||||
import { SortingService } from '../sorting';
|
import { SortingService } from '../sorting';
|
||||||
import { AutoUnsubscribe } from '../utils';
|
import { AutoUnsubscribe } from '../utils';
|
||||||
import { SearchService } from '../search';
|
import { SearchService } from '../search';
|
||||||
import { EntitiesService } from './services';
|
import { EntitiesService, ListingService } from './services';
|
||||||
import { IListable, ListingMode, ListingModes, TableColumnConfig } from './models';
|
import { IListable, ListingMode, ListingModes, TableColumnConfig } from './models';
|
||||||
|
|
||||||
export const DefaultListingServices = [FilterService, SearchService, EntitiesService, SortingService] as const;
|
export const DefaultListingServicesTmp = [FilterService, SearchService, SortingService, ListingService] as const;
|
||||||
export const DefaultListingServicesTmp = [FilterService, SearchService, SortingService] as const;
|
export const DefaultListingServices = [...DefaultListingServicesTmp, EntitiesService] as const;
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class ListingComponent<T extends IListable> extends AutoUnsubscribe implements OnDestroy {
|
export abstract class ListingComponent<T extends IListable> extends AutoUnsubscribe implements OnDestroy {
|
||||||
@ -17,6 +17,7 @@ export abstract class ListingComponent<T extends IListable> extends AutoUnsubscr
|
|||||||
readonly searchService = this._injector.get<SearchService<T>>(SearchService);
|
readonly searchService = this._injector.get<SearchService<T>>(SearchService);
|
||||||
readonly sortingService = this._injector.get<SortingService<T>>(SortingService);
|
readonly sortingService = this._injector.get<SortingService<T>>(SortingService);
|
||||||
readonly entitiesService = this._injector.get<EntitiesService<T>>(EntitiesService);
|
readonly entitiesService = this._injector.get<EntitiesService<T>>(EntitiesService);
|
||||||
|
readonly listingService = this._injector.get<ListingService<T>>(ListingService);
|
||||||
|
|
||||||
readonly noMatch$ = this._noMatch$;
|
readonly noMatch$ = this._noMatch$;
|
||||||
readonly noContent$ = this._noContent$;
|
readonly noContent$ = this._noContent$;
|
||||||
@ -46,30 +47,30 @@ export abstract class ListingComponent<T extends IListable> extends AutoUnsubscr
|
|||||||
|
|
||||||
private get _sortedDisplayedEntities$(): Observable<readonly T[]> {
|
private get _sortedDisplayedEntities$(): Observable<readonly T[]> {
|
||||||
const sort = (entities: T[]) => this.sortingService.defaultSort(entities);
|
const sort = (entities: T[]) => this.sortingService.defaultSort(entities);
|
||||||
const sortedEntities = () => this.entitiesService.displayed$.pipe(map(sort));
|
const sortedEntities = () => this.listingService.displayed$.pipe(map(sort));
|
||||||
return this.sortingService.sortingOption$.pipe(switchMap(sortedEntities));
|
return this.sortingService.sortingOption$.pipe(switchMap(sortedEntities));
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _noMatch$(): Observable<boolean> {
|
private get _noMatch$(): Observable<boolean> {
|
||||||
return combineLatest([this.entitiesService.allLength$, this.entitiesService.displayedLength$]).pipe(
|
return combineLatest([this.entitiesService.allLength$, this.listingService.displayedLength$]).pipe(
|
||||||
map(([hasEntities, hasDisplayedEntities]) => !!hasEntities && !hasDisplayedEntities),
|
map(([hasEntities, hasDisplayedEntities]) => !!hasEntities && !hasDisplayedEntities),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _noContent$(): Observable<boolean> {
|
private get _noContent$(): Observable<boolean> {
|
||||||
return combineLatest([this._noMatch$, this.entitiesService.noData$]).pipe(
|
return combineLatest([this._noMatch$, this.entitiesService.noData$]).pipe(
|
||||||
map(([noMatch, noData]) => noMatch || noData),
|
map(([noMatch, noData]) => noMatch || noData),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleEntitySelected(event: MouseEvent, entity: T): void {
|
toggleEntitySelected(event: MouseEvent, entity: T): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.entitiesService.select(entity);
|
this.listingService.select(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelected(entity: T): boolean {
|
isSelected(entity: T): boolean {
|
||||||
return this.entitiesService.isSelected(entity);
|
return this.listingService.isSelected(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
import { Inject, Injectable, InjectionToken, Injector, Optional } from '@angular/core';
|
import { Inject, Injectable, InjectionToken, Injector, Optional } from '@angular/core';
|
||||||
import { BehaviorSubject, combineLatest, Observable, pipe, Subject } from 'rxjs';
|
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||||
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||||
import { FilterService, getFilteredEntities } from '../../filtering';
|
|
||||||
import { SearchService } from '../../search';
|
|
||||||
import { IListable } from '../models';
|
import { IListable } from '../models';
|
||||||
import { GenericService } from '../../services';
|
import { GenericService } from '../../services';
|
||||||
|
import { getLength } from '../../utils';
|
||||||
const toLengthValue = (entities: unknown[]) => entities?.length ?? 0;
|
|
||||||
const getLength = pipe(map(toLengthValue), distinctUntilChanged());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should be removed when refactoring is done
|
* This should be removed when refactoring is done
|
||||||
*/
|
*/
|
||||||
const ENTITY_PATH = new InjectionToken<string>('This is here for compatibility while refactoring things.');
|
const ENTITY_PATH = new InjectionToken<string>('This is here for compatibility while refactoring things.');
|
||||||
|
const ENTITY_CLASS = new InjectionToken<string>('This is here for compatibility while refactoring things.');
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
/**
|
/**
|
||||||
@ -21,88 +18,27 @@ const ENTITY_PATH = new InjectionToken<string>('This is here for compatibility w
|
|||||||
* By default, if no interface is provided, I = E
|
* By default, if no interface is provided, I = E
|
||||||
*/
|
*/
|
||||||
export class EntitiesService<E extends IListable, I = E> extends GenericService<I> {
|
export class EntitiesService<E extends IListable, I = E> extends GenericService<I> {
|
||||||
readonly displayed$: Observable<E[]>;
|
|
||||||
readonly displayedLength$: Observable<number>;
|
|
||||||
readonly noData$: Observable<boolean>;
|
readonly noData$: Observable<boolean>;
|
||||||
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>;
|
|
||||||
readonly all$: Observable<E[]>;
|
readonly all$: Observable<E[]>;
|
||||||
readonly allLength$: Observable<number>;
|
readonly allLength$: Observable<number>;
|
||||||
readonly entityChanged$ = new Subject<E>();
|
readonly entityChanged$ = new Subject<E>();
|
||||||
private readonly _filterService = this._injector.get(FilterService);
|
|
||||||
private readonly _searchService = this._injector.get<SearchService<E>>(SearchService);
|
|
||||||
private readonly _all$ = new BehaviorSubject<E[]>([]);
|
private readonly _all$ = new BehaviorSubject<E[]>([]);
|
||||||
private _displayed: E[] = [];
|
|
||||||
private readonly _selected$ = new BehaviorSubject<(string | number)[]>([]);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly _injector: Injector,
|
protected readonly _injector: Injector,
|
||||||
|
@Optional() @Inject(ENTITY_CLASS) private readonly _entityClass: new (entityInterface: I) => E,
|
||||||
@Optional() @Inject(ENTITY_PATH) protected readonly _defaultModelPath = '',
|
@Optional() @Inject(ENTITY_PATH) protected readonly _defaultModelPath = '',
|
||||||
) {
|
) {
|
||||||
super(_injector, _defaultModelPath);
|
super(_injector, _defaultModelPath);
|
||||||
this.all$ = this._all$.asObservable();
|
this.all$ = this._all$.asObservable();
|
||||||
this.allLength$ = this._all$.pipe(getLength);
|
this.allLength$ = this._all$.pipe(getLength);
|
||||||
|
|
||||||
this.displayed$ = this._getDisplayed$;
|
|
||||||
this.displayedLength$ = this.displayed$.pipe(getLength);
|
|
||||||
|
|
||||||
this.selected$ = this._selected$.asObservable();
|
|
||||||
this.selectedEntities$ = this._selected$.asObservable().pipe(map(() => this.selected));
|
|
||||||
this.selectedLength$ = this._selected$.pipe(getLength);
|
|
||||||
|
|
||||||
this.noData$ = this._noData$;
|
this.noData$ = this._noData$;
|
||||||
this.areAllSelected$ = this._areAllSelected$;
|
|
||||||
this.areSomeSelected$ = this._areSomeSelected$;
|
|
||||||
this.notAllSelected$ = this._notAllSelected$;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get all(): E[] {
|
get all(): E[] {
|
||||||
return Object.values(this._all$.getValue());
|
return Object.values(this._all$.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
get selected(): E[] {
|
|
||||||
const selectedIds = Object.values(this._selected$.getValue());
|
|
||||||
return this.all.filter(a => selectedIds.indexOf(a.id) !== -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _getDisplayed$(): Observable<E[]> {
|
|
||||||
const { filterGroups$ } = this._filterService;
|
|
||||||
const { valueChanges$ } = this._searchService;
|
|
||||||
|
|
||||||
return combineLatest([this.all$, filterGroups$, valueChanges$]).pipe(
|
|
||||||
map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)),
|
|
||||||
map(entities => this._searchService.searchIn(entities)),
|
|
||||||
tap(displayed => {
|
|
||||||
this._displayed = displayed;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _areAllSelected$(): Observable<boolean> {
|
|
||||||
return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(
|
|
||||||
map(([displayedLength, selectedLength]) => !!displayedLength && displayedLength === selectedLength),
|
|
||||||
distinctUntilChanged(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _areSomeSelected$(): Observable<boolean> {
|
|
||||||
return this.selectedLength$.pipe(
|
|
||||||
map(length => !!length),
|
|
||||||
distinctUntilChanged(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _notAllSelected$(): Observable<boolean> {
|
|
||||||
return combineLatest([this.areAllSelected$, this.areSomeSelected$]).pipe(
|
|
||||||
map(([allAreSelected, someAreSelected]) => !allAreSelected && someAreSelected),
|
|
||||||
distinctUntilChanged(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _noData$(): Observable<boolean> {
|
private get _noData$(): Observable<boolean> {
|
||||||
return this.allLength$.pipe(
|
return this.allLength$.pipe(
|
||||||
map(length => length === 0),
|
map(length => length === 0),
|
||||||
@ -110,38 +46,23 @@ export class EntitiesService<E extends IListable, I = E> extends GenericService<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _allSelected() {
|
loadAll(): Observable<E[]> {
|
||||||
return this._displayed.length !== 0 && this._displayed.length === this.selected.length;
|
return this.getAll().pipe(
|
||||||
|
map((entities: I[]) => entities.map(entity => new this._entityClass(entity))),
|
||||||
|
tap((entities: E[]) => this.setEntities(entities)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAllIfNecessary(): Promise<unknown> | void {
|
||||||
|
if (!this.all.length) {
|
||||||
|
return this.loadAll().toPromise();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setEntities(newEntities: E[]): void {
|
setEntities(newEntities: E[]): void {
|
||||||
this._all$.next(newEntities);
|
this._all$.next(newEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelected(newEntities: E[]): void {
|
|
||||||
const selectedIds = newEntities.map(e => e.id);
|
|
||||||
this._selected$.next(selectedIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelected(entity: E): boolean {
|
|
||||||
return this.selected.indexOf(entity) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
find(id: string): E | undefined {
|
find(id: string): E | undefined {
|
||||||
return this.all.find(entity => entity.id === id);
|
return this.all.find(entity => entity.id === id);
|
||||||
}
|
}
|
||||||
@ -150,11 +71,6 @@ export class EntitiesService<E extends IListable, I = E> extends GenericService<
|
|||||||
return this.all.some(entity => entity.id === id);
|
return this.all.some(entity => entity.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelection(): void {
|
|
||||||
const items = this._displayed.filter(item => this.selected.includes(item));
|
|
||||||
this.setSelected(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
replace(newEntity: E): void {
|
replace(newEntity: E): void {
|
||||||
const all = this.all.filter(item => item.id !== newEntity.id);
|
const all = this.all.filter(item => item.id !== newEntity.id);
|
||||||
all.push(newEntity);
|
all.push(newEntity);
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from './entities.service';
|
export * from './entities.service';
|
||||||
|
export * from './listing.service';
|
||||||
|
|||||||
110
src/lib/listing/services/listing.service.ts
Normal file
110
src/lib/listing/services/listing.service.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
|
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||||
|
import { FilterService, getFilteredEntities } from '../../filtering';
|
||||||
|
import { SearchService } from '../../search';
|
||||||
|
import { IListable } from '../models';
|
||||||
|
import { EntitiesService, getLength } from '@iqser/common-ui';
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
this.selected$ = this._selected$.asObservable();
|
||||||
|
this.selectedEntities$ = this._selected$.asObservable().pipe(map(() => this.selected));
|
||||||
|
this.selectedLength$ = this._selected$.pipe(getLength);
|
||||||
|
|
||||||
|
this.areAllSelected$ = this._areAllSelected$;
|
||||||
|
this.areSomeSelected$ = this._areSomeSelected$;
|
||||||
|
this.notAllSelected$ = this._notAllSelected$;
|
||||||
|
}
|
||||||
|
|
||||||
|
get selected(): E[] {
|
||||||
|
const selectedIds = Object.values(this._selected$.getValue());
|
||||||
|
return this._entitiesService.all.filter(a => selectedIds.indexOf(a.id) !== -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _areAllSelected$(): Observable<boolean> {
|
||||||
|
return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(
|
||||||
|
map(([displayedLength, selectedLength]) => !!displayedLength && displayedLength === selectedLength),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _areSomeSelected$(): Observable<boolean> {
|
||||||
|
return this.selectedLength$.pipe(
|
||||||
|
map(length => !!length),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _notAllSelected$(): Observable<boolean> {
|
||||||
|
return combineLatest([this.areAllSelected$, this.areSomeSelected$]).pipe(
|
||||||
|
map(([allAreSelected, someAreSelected]) => !allAreSelected && someAreSelected),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.selected.indexOf(entity) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelection(): void {
|
||||||
|
const items = this._displayed.filter(item => this.selected.includes(item));
|
||||||
|
this.setSelected(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,13 @@
|
|||||||
<div [class.selection-enabled]="selectionEnabled" class="header-item">
|
<div [class.selection-enabled]="selectionEnabled" class="header-item">
|
||||||
<iqser-round-checkbox
|
<iqser-round-checkbox
|
||||||
(click)="entitiesService.selectAll()"
|
(click)="listingService.selectAll()"
|
||||||
*ngIf="selectionEnabled"
|
*ngIf="selectionEnabled"
|
||||||
[active]="entitiesService.areAllSelected$ | async"
|
[active]="listingService.areAllSelected$ | async"
|
||||||
[indeterminate]="entitiesService.notAllSelected$ | async"
|
[indeterminate]="listingService.notAllSelected$ | async"
|
||||||
></iqser-round-checkbox>
|
></iqser-round-checkbox>
|
||||||
|
|
||||||
<span class="all-caps-label">
|
<span class="all-caps-label">
|
||||||
{{ tableHeaderLabel | translate: {length: totalSize || (entitiesService.displayedLength$ | async)} }}
|
{{ tableHeaderLabel | translate: { length: totalSize || (listingService.displayedLength$ | async) } }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<ng-container [ngTemplateOutlet]="bulkActions"></ng-container>
|
<ng-container [ngTemplateOutlet]="bulkActions"></ng-container>
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, TemplateRef } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, TemplateRef } from '@angular/core';
|
||||||
import { Required } from '../../utils';
|
import { Required } from '../../utils';
|
||||||
import { FilterService } from '../../filtering';
|
import { FilterService } from '../../filtering';
|
||||||
import { EntitiesService } from '../services';
|
import { EntitiesService, ListingService } from '../services';
|
||||||
import { IListable, ListingMode, ListingModes, TableColumnConfig } from '../models';
|
import { IListable, ListingMode, ListingModes, TableColumnConfig } from '../models';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-table-header',
|
selector: 'iqser-table-header',
|
||||||
templateUrl: './table-header.component.html',
|
templateUrl: './table-header.component.html',
|
||||||
styleUrls: ['./table-header.component.scss'],
|
styleUrls: ['./table-header.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class TableHeaderComponent<T extends IListable> {
|
export class TableHeaderComponent<T extends IListable> {
|
||||||
readonly listingModes = ListingModes;
|
readonly listingModes = ListingModes;
|
||||||
@ -21,5 +21,9 @@ export class TableHeaderComponent<T extends IListable> {
|
|||||||
@Input() totalSize?: number;
|
@Input() totalSize?: number;
|
||||||
@Input() bulkActions?: TemplateRef<unknown>;
|
@Input() bulkActions?: TemplateRef<unknown>;
|
||||||
|
|
||||||
constructor(readonly entitiesService: EntitiesService<T>, readonly filterService: FilterService) {}
|
constructor(
|
||||||
|
readonly entitiesService: EntitiesService<T>,
|
||||||
|
readonly listingService: ListingService<T>,
|
||||||
|
readonly filterService: FilterService,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<iqser-empty-state
|
<iqser-empty-state
|
||||||
(action)="noDataAction.emit()"
|
(action)="noDataAction.emit()"
|
||||||
*ngIf="listingComponent.entitiesService.noData$ | async"
|
*ngIf="entitiesService.noData$ | async"
|
||||||
[buttonIcon]="noDataButtonIcon"
|
[buttonIcon]="noDataButtonIcon"
|
||||||
[buttonLabel]="noDataButtonLabel"
|
[buttonLabel]="noDataButtonLabel"
|
||||||
[icon]="noDataIcon"
|
[icon]="noDataIcon"
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
|||||||
import { Required } from '../../utils';
|
import { Required } from '../../utils';
|
||||||
import { IListable, ListingModes, TableColumnConfig } from '../models';
|
import { IListable, ListingModes, TableColumnConfig } from '../models';
|
||||||
import { ListingComponent } from '../listing-component.directive';
|
import { ListingComponent } from '../listing-component.directive';
|
||||||
|
import { EntitiesService } from '../services';
|
||||||
|
|
||||||
const SCROLLBAR_WIDTH = 11;
|
const SCROLLBAR_WIDTH = 11;
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ const SCROLLBAR_WIDTH = 11;
|
|||||||
selector: 'iqser-table',
|
selector: 'iqser-table',
|
||||||
templateUrl: './table.component.html',
|
templateUrl: './table.component.html',
|
||||||
styleUrls: ['./table.component.scss'],
|
styleUrls: ['./table.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class TableComponent<T extends IListable> implements OnInit {
|
export class TableComponent<T extends IListable> implements OnInit {
|
||||||
readonly listingModes = ListingModes;
|
readonly listingModes = ListingModes;
|
||||||
@ -49,14 +50,11 @@ export class TableComponent<T extends IListable> implements OnInit {
|
|||||||
@ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport;
|
@ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(forwardRef(() => ListingComponent)) private _parent: ListingComponent<T>,
|
@Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent<T>,
|
||||||
private readonly _hostRef: ViewContainerRef
|
private readonly _hostRef: ViewContainerRef,
|
||||||
|
readonly entitiesService: EntitiesService<T>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get listingComponent(): ListingComponent<T> {
|
|
||||||
return this._parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
get tableColumnConfigs(): readonly TableColumnConfig<T>[] {
|
get tableColumnConfigs(): readonly TableColumnConfig<T>[] {
|
||||||
return this.listingComponent.tableColumnConfigs;
|
return this.listingComponent.tableColumnConfigs;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<iqser-empty-state
|
<iqser-empty-state
|
||||||
(action)="noDataAction.emit()"
|
(action)="noDataAction.emit()"
|
||||||
*ngIf="listingComponent.entitiesService.noData$ | async"
|
*ngIf="entitiesService.noData$ | async"
|
||||||
[buttonIcon]="noDataButtonIcon"
|
[buttonIcon]="noDataButtonIcon"
|
||||||
[buttonLabel]="noDataButtonLabel"
|
[buttonLabel]="noDataButtonLabel"
|
||||||
[icon]="noDataIcon"
|
[icon]="noDataIcon"
|
||||||
@ -15,7 +15,7 @@
|
|||||||
[text]="noDataText"
|
[text]="noDataText"
|
||||||
></iqser-empty-state>
|
></iqser-empty-state>
|
||||||
|
|
||||||
<div *ngIf="(listingComponent.entitiesService.noData$ | async) === false" cdkDropListGroup class="columns-wrapper">
|
<div *ngIf="(entitiesService.noData$ | async) === false" cdkDropListGroup class="columns-wrapper">
|
||||||
<div *ngFor="let column of config.columns" [class.dragging]="dragging"
|
<div *ngFor="let column of config.columns" [class.dragging]="dragging"
|
||||||
[class.list-can-receive]="isReceiving(column)"
|
[class.list-can-receive]="isReceiving(column)"
|
||||||
[class.list-dragging]="isDragging(column)"
|
[class.list-dragging]="isDragging(column)"
|
||||||
@ -41,4 +41,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,8 @@ import { ListingComponent } from '../listing-component.directive';
|
|||||||
import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
|
import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
|
||||||
import { Required } from '../../utils';
|
import { Required } from '../../utils';
|
||||||
import { LoadingService } from '../../loading';
|
import { LoadingService } from '../../loading';
|
||||||
import {IListable} from "../models";
|
import { IListable } from '../models';
|
||||||
|
import { EntitiesService, ListingService } from '../services';
|
||||||
|
|
||||||
interface WorkflowColumn<T, K> {
|
interface WorkflowColumn<T, K> {
|
||||||
key: K;
|
key: K;
|
||||||
@ -60,12 +61,11 @@ export class WorkflowComponent<T extends IListable, K extends string> implements
|
|||||||
private _existingEntities: { [key: string]: T } = {};
|
private _existingEntities: { [key: string]: T } = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(forwardRef(() => ListingComponent)) private _parent: ListingComponent<T>,
|
@Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent<T>,
|
||||||
private readonly _loadingService: LoadingService
|
private readonly _loadingService: LoadingService,
|
||||||
) {}
|
readonly listingService: ListingService<T>,
|
||||||
|
readonly entitiesService: EntitiesService<T>,
|
||||||
get listingComponent(): ListingComponent<T> {
|
) {
|
||||||
return this._parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get tableHeaderLabel(): string | undefined {
|
get tableHeaderLabel(): string | undefined {
|
||||||
@ -90,7 +90,7 @@ export class WorkflowComponent<T extends IListable, K extends string> implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.listingComponent.entitiesService.displayed$.subscribe(entities => this._updateConfigItems(entities));
|
this.listingService.displayed$.subscribe(entities => this._updateConfigItems(entities));
|
||||||
}
|
}
|
||||||
|
|
||||||
canMoveTo(column: WorkflowColumn<T, K>): (item: CdkDrag<T>) => boolean {
|
canMoveTo(column: WorkflowColumn<T, K>): (item: CdkDrag<T>) => boolean {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { map } from 'rxjs/operators';
|
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
import { OperatorFunction } from 'rxjs';
|
import { OperatorFunction, pipe } from 'rxjs';
|
||||||
|
|
||||||
export function get<T>(predicate: (value: T, index: number) => boolean): OperatorFunction<T[], T | undefined> {
|
export function get<T>(predicate: (value: T, index: number) => boolean): OperatorFunction<T[], T | undefined> {
|
||||||
return map(entities => entities.find(predicate));
|
return map(entities => entities.find(predicate));
|
||||||
@ -8,3 +8,6 @@ export function get<T>(predicate: (value: T, index: number) => boolean): Operato
|
|||||||
export function any<T>(predicate: (value: T, index: number) => boolean): OperatorFunction<T[], boolean> {
|
export function any<T>(predicate: (value: T, index: number) => boolean): OperatorFunction<T[], boolean> {
|
||||||
return map(entities => entities.some(predicate));
|
return map(entities => entities.some(predicate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const toLengthValue = (entities: unknown[]): number => entities?.length ?? 0;
|
||||||
|
export const getLength = pipe(map(toLengthValue), distinctUntilChanged());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user