update lint rules, fix lint errors
This commit is contained in:
parent
0bb0e4454f
commit
908256aaca
@ -4,7 +4,18 @@
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"extends": ["plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates"],
|
||||
"extends": [
|
||||
"plugin:@nrwl/nx/angular",
|
||||
"plugin:@angular-eslint/template/process-inline-templates",
|
||||
"plugin:@angular-eslint/recommended",
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
"plugin:@angular-eslint/recommended--extra",
|
||||
"airbnb-typescript/base",
|
||||
"prettier",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": ["libs/common-ui/tsconfig.*?.json"]
|
||||
},
|
||||
@ -25,13 +36,48 @@
|
||||
"style": "kebab-case"
|
||||
}
|
||||
],
|
||||
"no-param-reassign": "error"
|
||||
"@angular-eslint/prefer-on-push-component-change-detection": "error",
|
||||
"@angular-eslint/use-lifecycle-interface": "error",
|
||||
"@angular-eslint/no-input-prefix": "error",
|
||||
"@angular-eslint/no-input-rename": "error",
|
||||
"@angular-eslint/no-output-on-prefix": "error",
|
||||
"@angular-eslint/no-output-rename": "error",
|
||||
"@angular-eslint/prefer-output-readonly": "error",
|
||||
"@typescript-eslint/unbound-method": "error",
|
||||
"@typescript-eslint/lines-between-class-members": "off",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
"selector": "memberLike",
|
||||
"modifiers": ["private"],
|
||||
"format": ["camelCase"],
|
||||
"leadingUnderscore": "require"
|
||||
},
|
||||
{
|
||||
"selector": "memberLike",
|
||||
"modifiers": ["protected"],
|
||||
"format": ["camelCase"],
|
||||
"leadingUnderscore": "require"
|
||||
},
|
||||
{
|
||||
"selector": "memberLike",
|
||||
"modifiers": ["private"],
|
||||
"format": ["UPPER_CASE", "camelCase"],
|
||||
"leadingUnderscore": "require"
|
||||
}
|
||||
],
|
||||
"import/prefer-default-export": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-param-reassign": "error",
|
||||
"consistent-return": "off",
|
||||
"class-methods-use-this": "off"
|
||||
},
|
||||
"plugins": ["@angular-eslint/eslint-plugin", "@typescript-eslint"]
|
||||
},
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"extends": ["plugin:@nrwl/nx/angular-template"],
|
||||
"extends": ["plugin:@nrwl/nx/angular-template", "plugin:@angular-eslint/template/recommended"],
|
||||
"plugins": ["prettier"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
|
||||
@ -24,18 +24,18 @@ export class CircleButtonComponent implements OnInit {
|
||||
@Input() isSubmit = false;
|
||||
@Input() size = 34;
|
||||
@Input() iconSize = 14;
|
||||
@Output() action = new EventEmitter<MouseEvent>();
|
||||
@Output() readonly action = new EventEmitter<MouseEvent>();
|
||||
|
||||
@ViewChild(MatTooltip) private readonly _matTooltip!: MatTooltip;
|
||||
|
||||
constructor(private readonly _elementRef: ElementRef) {}
|
||||
|
||||
ngOnInit() {
|
||||
this._elementRef.nativeElement.style.setProperty('--size', this.size + 'px');
|
||||
this._elementRef.nativeElement.style.setProperty('--iconSize', this.iconSize + 'px');
|
||||
ngOnInit(): void {
|
||||
(this._elementRef.nativeElement as HTMLElement).style.setProperty('--size', `${this.size}px`);
|
||||
(this._elementRef.nativeElement as HTMLElement).style.setProperty('--iconSize', `${this.iconSize}px`);
|
||||
}
|
||||
|
||||
performAction($event: MouseEvent) {
|
||||
performAction($event: MouseEvent): void {
|
||||
if (this.disabled) return;
|
||||
|
||||
if (this.removeTooltip) {
|
||||
|
||||
@ -16,5 +16,5 @@ export class IconButtonComponent {
|
||||
@Input() showDot = false;
|
||||
@Input() disabled = false;
|
||||
@Input() type: IconButtonType = IconButtonTypes.default;
|
||||
@Output() action = new EventEmitter<MouseEvent>();
|
||||
@Output() readonly action = new EventEmitter<MouseEvent>();
|
||||
}
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IconButtonComponent } from './buttons/icon-button/icon-button.component';
|
||||
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { CircleButtonComponent } from './buttons/circle-button/circle-button.component';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IconButtonComponent } from './buttons/icon-button/icon-button.component';
|
||||
import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component';
|
||||
import { CircleButtonComponent } from './buttons/circle-button/circle-button.component';
|
||||
import { RoundCheckboxComponent } from './inputs/round-checkbox/round-checkbox.component';
|
||||
import { SortByPipe } from './sorting/sort-by.pipe';
|
||||
import { HumanizePipe } from './utils/pipes/humanize.pipe';
|
||||
import { TableColumnNameComponent } from './tables/table-column-name/table-column-name.component';
|
||||
import { QuickFiltersComponent } from './filtering/quick-filters/quick-filters.component';
|
||||
import { TableHeaderComponent } from './tables/table-header/table-header.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SyncWidthDirective } from './tables/sync-width.directive';
|
||||
import { StatusBarComponent } from './misc/status-bar/status-bar.component';
|
||||
import { EditableInputComponent } from './inputs/editable-input/editable-input.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent];
|
||||
|
||||
@ -40,8 +40,8 @@ export class CommonUiModule {
|
||||
constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) {
|
||||
const icons = ['arrow-down', 'check', 'close', 'edit', 'sort-asc', 'sort-desc'];
|
||||
|
||||
for (const icon of icons) {
|
||||
icons.forEach(icon => {
|
||||
_iconRegistry.addSvgIconInNamespace('iqser', icon, _sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/${icon}.svg`));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +1,22 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { NestedFilter } from './models/nested-filter.model';
|
||||
import { FilterGroup } from './models/filter-group.model';
|
||||
import { Filter } from './models/filter.model';
|
||||
|
||||
export function processFilters(oldFilters: NestedFilter[], newFilters: NestedFilter[]) {
|
||||
copySettings(oldFilters, newFilters);
|
||||
if (newFilters) {
|
||||
newFilters.forEach(filter => {
|
||||
handleCheckedValue(filter);
|
||||
});
|
||||
}
|
||||
return newFilters;
|
||||
}
|
||||
|
||||
function copySettings(oldFilters: NestedFilter[], newFilters: NestedFilter[]) {
|
||||
if (oldFilters && newFilters) {
|
||||
for (const oldFilter of oldFilters) {
|
||||
const newFilter = newFilters.find(f => f.key === oldFilter.key);
|
||||
if (newFilter) {
|
||||
newFilter.checked = oldFilter.checked;
|
||||
newFilter.indeterminate = oldFilter.indeterminate;
|
||||
if (oldFilter.children && newFilter.children) copySettings(oldFilter.children, newFilter.children);
|
||||
}
|
||||
if (!oldFilters || !newFilters) return;
|
||||
|
||||
oldFilters.forEach(filter => {
|
||||
const newFilter = newFilters.find(f => f.key === filter.key);
|
||||
if (newFilter) {
|
||||
newFilter.checked = filter.checked;
|
||||
newFilter.indeterminate = filter.indeterminate;
|
||||
if (filter.children && newFilter.children) copySettings(filter.children, newFilter.children);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function handleCheckedValue(filter: NestedFilter) {
|
||||
export function handleCheckedValue(filter: NestedFilter): void {
|
||||
if (filter.children && filter.children.length) {
|
||||
filter.checked = filter.children.reduce<boolean>((acc, next) => acc && !!next.checked, true);
|
||||
if (filter.checked) {
|
||||
@ -38,38 +29,54 @@ export function handleCheckedValue(filter: NestedFilter) {
|
||||
}
|
||||
}
|
||||
|
||||
export function checkFilter(entity: any, filters: NestedFilter[], validate: Function, validateArgs: any = [], matchAll: boolean = false) {
|
||||
export function processFilters(oldFilters: NestedFilter[], newFilters: NestedFilter[]): NestedFilter[] {
|
||||
copySettings(oldFilters, newFilters);
|
||||
if (newFilters) {
|
||||
newFilters.forEach(filter => {
|
||||
handleCheckedValue(filter);
|
||||
});
|
||||
}
|
||||
return newFilters;
|
||||
}
|
||||
|
||||
export function checkFilter(
|
||||
entity: unknown,
|
||||
filters: NestedFilter[],
|
||||
validate?: (...args: unknown[]) => boolean,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
validateArgs: any = [],
|
||||
matchAll = false
|
||||
): boolean {
|
||||
const hasChecked = filters.find(f => f.checked);
|
||||
|
||||
if (!hasChecked) return true;
|
||||
|
||||
let filterMatched = matchAll;
|
||||
for (const filter of filters) {
|
||||
filters.forEach(filter => {
|
||||
if (filter.checked) {
|
||||
if (matchAll) {
|
||||
filterMatched = filterMatched && validate(entity, filter, ...validateArgs);
|
||||
filterMatched = filterMatched && !!validate?.(entity, filter, ...validateArgs);
|
||||
} else {
|
||||
filterMatched = filterMatched || validate(entity, filter, ...validateArgs);
|
||||
filterMatched = filterMatched || !!validate?.(entity, filter, ...validateArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return filterMatched;
|
||||
}
|
||||
|
||||
export const keyChecker = (key: string) => (entity: any, filter: NestedFilter) => entity[key] === filter.key;
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const keyChecker = (key: string) => (entity: Record<string, string>, filter: NestedFilter) => entity[key] === filter.key;
|
||||
|
||||
export function getFilteredEntities<T>(entities: T[], filters: FilterGroup[]) {
|
||||
export function getFilteredEntities<T>(entities: T[], filters: FilterGroup[]): T[] {
|
||||
const filteredEntities: T[] = [];
|
||||
for (const entity of entities) {
|
||||
entities.forEach(entity => {
|
||||
let add = true;
|
||||
for (const filter of filters) {
|
||||
filters.forEach(filter => {
|
||||
add = add && checkFilter(entity, filter.filters, filter.checker, filter.checkerArgs, filter.matchAll);
|
||||
}
|
||||
if (add) {
|
||||
filteredEntities.push(entity);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (add) filteredEntities.push(entity);
|
||||
});
|
||||
return filteredEntities;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { processFilters, toFlatFilters } from './filter-utils';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
|
||||
import { processFilters, toFlatFilters } from './filter-utils';
|
||||
import { FilterGroup } from './models/filter-group.model';
|
||||
import { NestedFilter } from './models/nested-filter.model';
|
||||
|
||||
@ -36,7 +36,7 @@ export class FilterService {
|
||||
if (!filters) return console.error(`Cannot find filter group "${filterGroupSlug}"`);
|
||||
|
||||
let found = filters.find(f => f.key === key);
|
||||
if (!found) found = filters.map(f => f.children?.find(ff => ff.key === key))[0];
|
||||
if (!found) [found] = filters.map(f => f.children?.find(ff => ff.key === key));
|
||||
if (!found) return console.error(`Cannot find filter with key "${key}" in group "${filterGroupSlug}"`);
|
||||
|
||||
found.checked = !found.checked;
|
||||
@ -48,8 +48,8 @@ export class FilterService {
|
||||
const oldFilters = this.getGroup(value.slug)?.filters;
|
||||
if (!oldFilters) return this._filterGroups$.next([...this.filterGroups, value]);
|
||||
|
||||
value.filters = processFilters(oldFilters, value.filters);
|
||||
this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== value.slug), value]);
|
||||
const newGroup = { ...value, filters: processFilters(oldFilters, value.filters) };
|
||||
this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== newGroup.slug), newGroup]);
|
||||
}
|
||||
|
||||
getGroup(slug: string): FilterGroup | undefined {
|
||||
@ -67,9 +67,14 @@ export class FilterService {
|
||||
reset(): void {
|
||||
this.filterGroups.forEach(group => {
|
||||
group.filters.forEach(filter => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
filter.checked = false;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
filter.indeterminate = false;
|
||||
filter.children?.forEach(f => (f.checked = false));
|
||||
filter.children?.forEach(f => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
f.checked = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { NestedFilter } from './nested-filter.model';
|
||||
import { TemplateRef } from '@angular/core';
|
||||
import { NestedFilter } from './nested-filter.model';
|
||||
|
||||
export interface FilterGroup {
|
||||
filters: NestedFilter[];
|
||||
readonly slug: string;
|
||||
readonly label?: string;
|
||||
readonly icon?: string;
|
||||
readonly filterTemplate?: TemplateRef<any>;
|
||||
readonly filterTemplate?: TemplateRef<unknown>;
|
||||
readonly hide?: boolean;
|
||||
readonly checker?: Function;
|
||||
readonly checker?: (...args: unknown[]) => boolean;
|
||||
readonly matchAll?: boolean;
|
||||
readonly checkerArgs?: any[];
|
||||
readonly checkerArgs?: unknown[];
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ export class EditableInputComponent {
|
||||
@Input() class?: string;
|
||||
@Input() showPreview = true;
|
||||
@Input() buttonsType?: CircleButtonType;
|
||||
@Output() save = new EventEmitter<string>();
|
||||
@Output() readonly save = new EventEmitter<string>();
|
||||
newValue = '';
|
||||
|
||||
private _editing = false;
|
||||
@ -32,7 +32,7 @@ export class EditableInputComponent {
|
||||
this.newValue = this.value;
|
||||
}
|
||||
|
||||
saveValue() {
|
||||
saveValue(): void {
|
||||
this.save.emit(this.newValue);
|
||||
this.editing = false;
|
||||
}
|
||||
|
||||
@ -15,6 +15,6 @@ export class RoundCheckboxComponent implements OnInit {
|
||||
@ViewChild('wrapper', { static: true }) private readonly _wrapper!: ElementRef;
|
||||
|
||||
ngOnInit(): void {
|
||||
this._wrapper.nativeElement.style.setProperty('--size', this.size + 'px');
|
||||
(this._wrapper.nativeElement as HTMLElement).style.setProperty('--size', `${this.size}px`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ const controlsConfig = {
|
||||
type FormControls = { [key in KeysOf<typeof controlsConfig>]: string };
|
||||
|
||||
@Injectable()
|
||||
export class SearchService<T extends object> {
|
||||
export class SearchService<T> {
|
||||
readonly searchForm = this._formBuilder.group(controlsConfig);
|
||||
readonly valueChanges$ = this.searchForm.valueChanges.pipe(
|
||||
startWith(''),
|
||||
@ -21,14 +21,14 @@ export class SearchService<T extends object> {
|
||||
constructor(private readonly _formBuilder: FormBuilder) {}
|
||||
|
||||
get searchValue(): string {
|
||||
return this.searchForm.get('query')?.value;
|
||||
return this.searchForm.get('query')?.value as string;
|
||||
}
|
||||
|
||||
set searchValue(value: string) {
|
||||
this.searchForm.patchValue({ query: value });
|
||||
}
|
||||
|
||||
searchIn(entities: T[]) {
|
||||
searchIn(entities: T[]): T[] {
|
||||
if (!this._searchKey) return entities;
|
||||
|
||||
const searchValue = this.searchValue.toLowerCase();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { SortingOrder } from './sorting-order.type';
|
||||
import { KeysOf } from '../../utils/types/utility-types';
|
||||
|
||||
export interface SortingOption<T extends object> {
|
||||
export interface SortingOption<T> {
|
||||
readonly order: SortingOrder;
|
||||
readonly column: KeysOf<T>;
|
||||
}
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import { NonFunctionKeys } from '../../utils/types/utility-types';
|
||||
|
||||
function inverseOf(order?: SortingOrder) {
|
||||
if (order === undefined) return SortingOrders.asc;
|
||||
return order === SortingOrders.asc ? SortingOrders.desc : SortingOrders.asc;
|
||||
}
|
||||
|
||||
export const SortingOrders = {
|
||||
asc: 'asc',
|
||||
desc: 'desc',
|
||||
inverseOf: inverseOf
|
||||
inverseOf: (order?: 'asc' | 'desc') => {
|
||||
if (order === undefined) return 'asc';
|
||||
return order === 'asc' ? 'desc' : 'asc';
|
||||
}
|
||||
} as const;
|
||||
|
||||
export type SortingOrder = NonFunctionKeys<typeof SortingOrders>;
|
||||
|
||||
@ -5,7 +5,7 @@ import { KeysOf } from '../utils/types/utility-types';
|
||||
|
||||
@Pipe({ name: 'sortBy' })
|
||||
export class SortByPipe implements PipeTransform {
|
||||
transform<T extends object>(values: T[], order: SortingOrder, column: KeysOf<T>): T[] {
|
||||
transform<T>(values: T[], order: SortingOrder, column: KeysOf<T>): T[] {
|
||||
return SortingService.sort(values, order, column);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { SortingOrder, SortingOrders } from './models/sorting-order.type';
|
||||
import { KeysOf } from '../utils/types/utility-types';
|
||||
|
||||
@Injectable()
|
||||
export class SortingService<T extends object> {
|
||||
export class SortingService<T> {
|
||||
private readonly _sortingOption$ = new BehaviorSubject<SortingOption<T> | undefined>(undefined);
|
||||
readonly sortingOption$ = this._sortingOption$.asObservable();
|
||||
|
||||
@ -14,7 +14,7 @@ export class SortingService<T extends object> {
|
||||
return this._sortingOption$.getValue();
|
||||
}
|
||||
|
||||
static sort<T extends object>(values: T[], order?: SortingOrder, column?: KeysOf<T>): T[] {
|
||||
static sort<T>(values: T[], order?: SortingOrder, column?: KeysOf<T>): T[] {
|
||||
if (!values || values.length <= 1 || !order) return values;
|
||||
|
||||
if (!column) {
|
||||
|
||||
@ -9,7 +9,7 @@ const toLengthValue = (entities: unknown[]) => entities?.length ?? 0;
|
||||
const getLength = pipe(map(toLengthValue), distinctUntilChanged());
|
||||
|
||||
@Injectable()
|
||||
export class EntitiesService<T extends object> {
|
||||
export class EntitiesService<T> {
|
||||
private readonly _all$ = new BehaviorSubject<T[]>([]);
|
||||
readonly all$ = this._all$.asObservable();
|
||||
readonly allLength$ = this._all$.pipe(getLength);
|
||||
@ -38,13 +38,15 @@ export class EntitiesService<T extends object> {
|
||||
}
|
||||
|
||||
private get _getDisplayed$(): Observable<T[]> {
|
||||
const filterGroups$ = this._filterService.filterGroups$;
|
||||
const searchValue$ = this._searchService.valueChanges$;
|
||||
const { filterGroups$ } = this._filterService;
|
||||
const { valueChanges$ } = this._searchService;
|
||||
|
||||
return combineLatest([this.all$, filterGroups$, searchValue$]).pipe(
|
||||
return combineLatest([this.all$, filterGroups$, valueChanges$]).pipe(
|
||||
map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)),
|
||||
map(entities => this._searchService.searchIn(entities)),
|
||||
tap(displayed => (this._displayed = displayed))
|
||||
tap(displayed => {
|
||||
this._displayed = displayed;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import { EntitiesService } from './entities.service';
|
||||
export const DefaultListingServices = [FilterService, SearchService, EntitiesService, SortingService] as const;
|
||||
|
||||
@Directive()
|
||||
export abstract class ListingComponent<T extends object> extends AutoUnsubscribe implements OnDestroy {
|
||||
export abstract class ListingComponent<T> extends AutoUnsubscribe implements OnDestroy {
|
||||
readonly filterService = this._injector.get(FilterService);
|
||||
readonly searchService = this._injector.get<SearchService<T>>(SearchService);
|
||||
readonly sortingService = this._injector.get<SortingService<T>>(SortingService);
|
||||
@ -53,7 +53,7 @@ export abstract class ListingComponent<T extends object> extends AutoUnsubscribe
|
||||
);
|
||||
}
|
||||
|
||||
setInitialConfig() {
|
||||
setInitialConfig(): void {
|
||||
this.sortingService.setSortingOption({
|
||||
column: this._primaryKey,
|
||||
order: SortingOrders.asc
|
||||
@ -61,9 +61,9 @@ export abstract class ListingComponent<T extends object> extends AutoUnsubscribe
|
||||
this.searchService.setSearchKey(this._primaryKey);
|
||||
}
|
||||
|
||||
toggleEntitySelected(event: MouseEvent, entity: T) {
|
||||
toggleEntitySelected(event: MouseEvent, entity: T): void {
|
||||
event.stopPropagation();
|
||||
return this.entitiesService.select(entity);
|
||||
this.entitiesService.select(entity);
|
||||
}
|
||||
|
||||
isSelected(entity: T): boolean {
|
||||
@ -71,7 +71,7 @@ export abstract class ListingComponent<T extends object> extends AutoUnsubscribe
|
||||
}
|
||||
|
||||
@Bind()
|
||||
trackByPrimaryKey(index: number, item: T) {
|
||||
trackByPrimaryKey(index: number, item: T): unknown {
|
||||
return item[this._primaryKey];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { KeysOf } from '../../utils/types/utility-types';
|
||||
|
||||
export interface TableColumnConfig<T extends object> {
|
||||
export interface TableColumnConfig<T> {
|
||||
readonly label: string;
|
||||
readonly sortByKey?: KeysOf<T>;
|
||||
readonly class?: string;
|
||||
|
||||
@ -18,36 +18,40 @@ export class SyncWidthDirective implements OnDestroy {
|
||||
}
|
||||
|
||||
private _matchWidth() {
|
||||
const headerItems = this._elementRef.nativeElement.children;
|
||||
const tableRows = this._elementRef.nativeElement.parentElement.parentElement.getElementsByClassName(this.iqserSyncWidth);
|
||||
const headerItems = (this._elementRef.nativeElement as HTMLElement).children;
|
||||
const tableRows = (this._elementRef.nativeElement as HTMLElement).parentElement?.parentElement?.getElementsByClassName(
|
||||
this.iqserSyncWidth
|
||||
);
|
||||
|
||||
if (!tableRows || !tableRows.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._elementRef.nativeElement.setAttribute('synced', true);
|
||||
(this._elementRef.nativeElement as HTMLElement).setAttribute('synced', 'true');
|
||||
|
||||
const { tableRow, length } = this._sampleRow(tableRows);
|
||||
if (!tableRow) return;
|
||||
|
||||
const hasExtraColumns = headerItems.length !== length ? 1 : 0;
|
||||
|
||||
for (let idx = 0; idx < length - hasExtraColumns - 1; ++idx) {
|
||||
if (headerItems[idx]) {
|
||||
headerItems[idx].style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`;
|
||||
headerItems[idx].style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`;
|
||||
for (let idx = 0; idx < length - hasExtraColumns - 1; idx += 1) {
|
||||
const item = headerItems[idx] as HTMLElement;
|
||||
if (item) {
|
||||
item.style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`;
|
||||
item.style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`;
|
||||
}
|
||||
}
|
||||
|
||||
for (let idx = length - hasExtraColumns - 1; idx < headerItems.length; ++idx) {
|
||||
if (headerItems[idx]) {
|
||||
headerItems[idx].style.minWidth = `0`;
|
||||
for (let idx = length - hasExtraColumns - 1; idx < headerItems.length; idx += 1) {
|
||||
const item = headerItems[idx] as HTMLElement;
|
||||
if (item) {
|
||||
item.style.minWidth = `0`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:resize')
|
||||
onResize() {
|
||||
onResize(): void {
|
||||
this._matchWidth();
|
||||
}
|
||||
|
||||
@ -58,7 +62,7 @@ export class SyncWidthDirective implements OnDestroy {
|
||||
let length = 0;
|
||||
let tableRow: Element | undefined;
|
||||
|
||||
for (let idx = 0; idx < tableRows.length; ++idx) {
|
||||
for (let idx = 0; idx < tableRows.length; idx += 1) {
|
||||
const row = tableRows.item(idx);
|
||||
if (row && row.children.length > length) {
|
||||
length = row.children.length;
|
||||
|
||||
@ -4,7 +4,7 @@ import { Required } from '../../utils/decorators/required.decorator';
|
||||
import { KeysOf } from '../../utils/types/utility-types';
|
||||
import { SortingService } from '../../sorting/sorting.service';
|
||||
|
||||
const ifHasRightIcon = <T extends object>(thisArg: TableColumnNameComponent<T>) => !!thisArg.rightIcon;
|
||||
const ifHasRightIcon = <T>(thisArg: TableColumnNameComponent<T>) => !!thisArg.rightIcon;
|
||||
|
||||
@Component({
|
||||
selector: 'iqser-table-column-name',
|
||||
@ -12,7 +12,7 @@ const ifHasRightIcon = <T extends object>(thisArg: TableColumnNameComponent<T>)
|
||||
styleUrls: ['./table-column-name.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TableColumnNameComponent<T extends object> {
|
||||
export class TableColumnNameComponent<T> {
|
||||
readonly sortingOrders = SortingOrders;
|
||||
|
||||
@Input() @Required() label!: string;
|
||||
|
||||
@ -10,12 +10,12 @@ import { EntitiesService } from '../entities.service';
|
||||
styleUrls: ['./table-header.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TableHeaderComponent<T extends object> {
|
||||
export class TableHeaderComponent<T> {
|
||||
@Input() @Required() tableHeaderLabel!: string;
|
||||
@Input() @Required() tableColumnConfigs!: readonly TableColumnConfig<T>[];
|
||||
@Input() hasEmptyColumn = false;
|
||||
@Input() selectionEnabled = false;
|
||||
@Input() bulkActions?: TemplateRef<any>;
|
||||
@Input() bulkActions?: TemplateRef<unknown>;
|
||||
|
||||
readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters');
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Directive, OnDestroy } from "@angular/core";
|
||||
import { Subscription } from "rxjs";
|
||||
import { Directive, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Inherit this class when you need to subscribe to observables in your components
|
||||
|
||||
@ -6,20 +6,16 @@
|
||||
*
|
||||
*/
|
||||
|
||||
export function Bind() {
|
||||
return function _bind<T extends Function>(
|
||||
_target: Object,
|
||||
propertyKey: PropertyKey,
|
||||
descriptor: TypedPropertyDescriptor<T>
|
||||
): TypedPropertyDescriptor<T> | never {
|
||||
export function Bind(): MethodDecorator {
|
||||
return function _bind(_target: unknown, propertyKey: PropertyKey, descriptor: PropertyDescriptor): PropertyDescriptor | never {
|
||||
if (typeof descriptor.value !== 'function') {
|
||||
throw new TypeError(`@Bind() decorator can only be applied to methods, not to ${typeof descriptor.value}`);
|
||||
}
|
||||
|
||||
return {
|
||||
configurable: true,
|
||||
get(): T {
|
||||
const boundMethod: T = descriptor.value?.bind(this);
|
||||
get() {
|
||||
const boundMethod = (descriptor.value as (...args: unknown[]) => unknown).bind(this);
|
||||
Object.defineProperty(this, propertyKey, {
|
||||
value: boundMethod,
|
||||
configurable: true,
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
export function Debounce(delay = 300): MethodDecorator {
|
||||
return function (target: Object, propertyKey: PropertyKey, descriptor: PropertyDescriptor) {
|
||||
return function _debounce(_target: unknown, propertyKey: PropertyKey, descriptor: PropertyDescriptor) {
|
||||
let timeout: number | undefined;
|
||||
|
||||
const original = descriptor.value;
|
||||
const descriptorCopy = { ...descriptor };
|
||||
|
||||
descriptor.value = function (...args: unknown[]) {
|
||||
descriptorCopy.value = function _new(...args: unknown[]) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => original.apply(this, args), delay);
|
||||
timeout = setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay);
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
return descriptorCopy;
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
export type Condition<T> = (thisArg: T) => boolean;
|
||||
|
||||
export function Required<T>(condition: Condition<T> = () => true): PropertyDecorator {
|
||||
return function (target: Object, propertyKey: PropertyKey) {
|
||||
return function _required(target: unknown, propertyKey: PropertyKey) {
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
get() {
|
||||
if (condition(this)) throw new Error(`Attribute ${String(propertyKey)} is required`);
|
||||
},
|
||||
set(value) {
|
||||
set(value: unknown) {
|
||||
Object.defineProperty(this, propertyKey, {
|
||||
value,
|
||||
writable: true
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
* // Expect: "name | setName | someKeys | someFn"
|
||||
* type Keys = KeysOf<Object>;
|
||||
*/
|
||||
export type KeysOf<T extends object> = {
|
||||
export type KeysOf<T> = {
|
||||
[K in keyof T]: K;
|
||||
}[keyof T];
|
||||
|
||||
@ -20,7 +20,7 @@ export type KeysOf<T extends object> = {
|
||||
* // Expect: "some bar | some foo"
|
||||
* type Values = ValuesOf<Object>;
|
||||
*/
|
||||
export type ValuesOf<T extends object> = T[KeysOf<T>];
|
||||
export type ValuesOf<T> = T[KeysOf<T>];
|
||||
|
||||
/**
|
||||
* NonUndefined
|
||||
@ -40,7 +40,8 @@ export type NonUndefined<T> = T extends undefined ? never : T;
|
||||
* // Expect: "setName | someFn"
|
||||
* type Keys = FunctionKeys<MixedProps>;
|
||||
*/
|
||||
export type FunctionKeys<T extends object> = {
|
||||
export type FunctionKeys<T> = {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
[K in keyof T]-?: NonUndefined<T[K]> extends Function ? K : never;
|
||||
}[keyof T];
|
||||
|
||||
@ -53,6 +54,7 @@ export type FunctionKeys<T extends object> = {
|
||||
* // Expect: "name | someKey"
|
||||
* type Keys = NonFunctionKeys<MixedProps>;
|
||||
*/
|
||||
export type NonFunctionKeys<T extends object> = {
|
||||
export type NonFunctionKeys<T> = {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
[K in keyof T]-?: NonUndefined<T[K]> extends Function ? never : K;
|
||||
}[keyof T];
|
||||
|
||||
@ -1 +1,2 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import 'jest-preset-angular/setup-jest';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user