From 63616184b5a462bfc2fce486950fba5af88c3287 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 21 Jul 2022 13:48:51 +0300 Subject: [PATCH 1/3] RED-3687 - Remove AutoUnsubscribe directive --- src/lib/dialog/base-dialog.component.ts | 21 ++- src/lib/form/base-form.component.ts | 4 +- .../table-content.component.html | 10 +- .../table-content/table-content.component.ts | 40 ++++-- src/lib/listing/table/table.component.ts | 7 +- .../column-header.component.html | 72 +++++----- .../column-header/column-header.component.ts | 32 +++-- .../listing/workflow/workflow.component.html | 130 +++++++++--------- .../listing/workflow/workflow.component.ts | 21 ++- src/lib/utils/context.component.ts | 17 +++ src/lib/utils/index.ts | 1 + 11 files changed, 206 insertions(+), 149 deletions(-) create mode 100644 src/lib/utils/context.component.ts diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts index 4afa6ec..444d789 100644 --- a/src/lib/dialog/base-dialog.component.ts +++ b/src/lib/dialog/base-dialog.component.ts @@ -1,10 +1,10 @@ -import { Directive, HostListener, inject, OnInit } from '@angular/core'; +import { Directive, HostListener, inject, OnDestroy, OnInit } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { AutoUnsubscribe, hasFormChanged, IqserEventTarget } from '../utils'; +import { hasFormChanged, IqserEventTarget } from '../utils'; import { ConfirmOptions } from '.'; import { ConfirmationDialogService } from './confirmation-dialog.service'; -import { firstValueFrom } from 'rxjs'; +import { firstValueFrom, Subscription } from 'rxjs'; import { LoadingService } from '../loading'; import { Toaster } from '../services'; @@ -26,7 +26,7 @@ export interface SaveOptions { * Make sure to remove the (submit)="save()" property from the form and to set type="button" on the save button * (otherwise the save request will be triggered twice). * */ -export abstract class BaseDialogComponent extends AutoUnsubscribe implements OnInit { +export abstract class BaseDialogComponent implements OnDestroy { form!: UntypedFormGroup; initialFormValue!: Record; protected readonly _formBuilder = inject(UntypedFormBuilder); @@ -34,10 +34,11 @@ export abstract class BaseDialogComponent extends AutoUnsubscribe implements OnI protected readonly _toaster = inject(Toaster); readonly #confirmationDialogService = inject(ConfirmationDialogService); readonly #dialog = inject(MatDialog); + #backdropClickSubscription = this._dialogRef.backdropClick().subscribe(() => { + this.close(); + }); - protected constructor(protected readonly _dialogRef: MatDialogRef, private readonly _isInEditMode?: boolean) { - super(); - } + protected constructor(protected readonly _dialogRef: MatDialogRef, private readonly _isInEditMode?: boolean) {} get valid(): boolean { return this.form.valid; @@ -53,10 +54,8 @@ export abstract class BaseDialogComponent extends AutoUnsubscribe implements OnI abstract save(options?: SaveOptions): void; - ngOnInit(): void { - this.addSubscription = this._dialogRef.backdropClick().subscribe(() => { - this.close(); - }); + ngOnDestroy(): void { + this.#backdropClickSubscription.unsubscribe(); } close(): void { diff --git a/src/lib/form/base-form.component.ts b/src/lib/form/base-form.component.ts index d253d2a..ab9dc1c 100644 --- a/src/lib/form/base-form.component.ts +++ b/src/lib/form/base-form.component.ts @@ -1,9 +1,9 @@ import { Directive } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { AutoUnsubscribe, hasFormChanged } from '../utils'; +import { hasFormChanged } from '../utils'; @Directive() -export abstract class BaseFormComponent extends AutoUnsubscribe { +export abstract class BaseFormComponent { form!: UntypedFormGroup; initialFormValue!: Record; diff --git a/src/lib/listing/table-content/table-content.component.html b/src/lib/listing/table-content/table-content.component.html index 6185939..25772de 100644 --- a/src/lib/listing/table-content/table-content.component.html +++ b/src/lib/listing/table-content/table-content.component.html @@ -1,20 +1,20 @@ -
+
- +
void, + lastScrolledIndex: number, + hasScrollbarDirective: () => void, + noContent: boolean, + sortedDisplayedEntities: any[], + isHelpModeActive: boolean, +} + @Component({ selector: 'iqser-table-content', templateUrl: './table-content.component.html', styleUrls: ['./table-content.component.scss'], }) -export class TableContentComponent extends AutoUnsubscribe implements OnDestroy, AfterViewInit { +export class TableContentComponent extends ContextComponent implements AfterViewInit { @Input() itemSize!: number; @Input() itemMouseEnterFn?: (entity: T) => void; @Input() itemMouseLeaveFn?: (entity: T) => void; @@ -28,17 +37,18 @@ export class TableContentComponent extends AutoUnsubscribe private _lastScrolledIndex = 0; private _multiSelectActive$ = new BehaviorSubject(false); + readonly #checkViewportSize$ = this.listingComponent.noContent$.pipe(tap(() => { + setTimeout(() => { + this.scrollViewport?.checkViewportSize(); + }, 0); + })); + constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, readonly listingService: ListingService, readonly helpModeService: HelpModeService, ) { super(); - this.addSubscription = this.listingComponent.noContent$.subscribe(() => { - setTimeout(() => { - this.scrollViewport?.checkViewportSize(); - }, 0); - }); } multiSelect(entity: T, $event: MouseEvent): void { @@ -49,13 +59,21 @@ export class TableContentComponent extends AutoUnsubscribe } ngAfterViewInit(): void { - this.addSubscription = this.scrollViewport.scrolledIndexChange.pipe(tap(index => (this._lastScrolledIndex = index))).subscribe(); - this.addSubscription = this.listingService.displayedLength$ + const lastScrolledIndex$ = this.scrollViewport.scrolledIndexChange.pipe(tap(index => (this._lastScrolledIndex = index))) + const hasScrollbarDirective$ = this.listingService.displayedLength$ .pipe( delay(100), tap(() => this.hasScrollbarDirective.process()), ) - .subscribe(); + + super._initContext({ + checkViewportSize: this.#checkViewportSize$, + lastScrolledIndex: lastScrolledIndex$, + hasScrollbarDirective: hasScrollbarDirective$, + noContent: this.listingComponent.noContent$, + sortedDisplayedEntities: this.listingComponent.sortedDisplayedEntities$, + isHelpModeActive: this.helpModeService.isHelpModeActive$, + }) } scrollToLastIndex(): void { diff --git a/src/lib/listing/table/table.component.ts b/src/lib/listing/table/table.component.ts index 27ae052..c8e49b1 100644 --- a/src/lib/listing/table/table.component.ts +++ b/src/lib/listing/table/table.component.ts @@ -13,7 +13,6 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; -import { AutoUnsubscribe } from '../../utils'; import { IListable, ListingModes, TableColumnConfig } from '../models'; import { ListingComponent } from '../listing-component.directive'; import { EntitiesService } from '../services'; @@ -26,7 +25,7 @@ const SCROLLBAR_WIDTH = 11; templateUrl: './table.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TableComponent extends AutoUnsubscribe implements OnChanges { +export class TableComponent implements OnChanges { readonly listingModes = ListingModes; @Input() tableColumnConfigs!: readonly TableColumnConfig[]; @@ -56,9 +55,7 @@ export class TableComponent extends AutoUnsubscribe impleme private readonly _hostRef: ViewContainerRef, private readonly _changeRef: ChangeDetectorRef, readonly entitiesService: EntitiesService, - ) { - super(); - } + ) {} get tableHeaderLabel(): string | undefined { return this.listingComponent.tableHeaderLabel; diff --git a/src/lib/listing/workflow/column-header/column-header.component.html b/src/lib/listing/workflow/column-header/column-header.component.html index c379a6b..d5bc6ed 100644 --- a/src/lib/listing/workflow/column-header/column-header.component.html +++ b/src/lib/listing/workflow/column-header/column-header.component.html @@ -1,41 +1,43 @@ - -
- {{ column.label | translate }} ({{ entities.length || 0 }}) - -
- - -
-
-
-
- - + + +
+ {{ column.label | translate }} ({{ entities.length || 0 }}) +
+ + +
+
+
+ -
- + +
+ +
+ +
+ +
- - -
+
diff --git a/src/lib/listing/workflow/column-header/column-header.component.ts b/src/lib/listing/workflow/column-header/column-header.component.ts index aaae18a..72f8d9b 100644 --- a/src/lib/listing/workflow/column-header/column-header.component.ts +++ b/src/lib/listing/workflow/column-header/column-header.component.ts @@ -14,17 +14,25 @@ import { combineLatest, Observable } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; import { CircleButtonTypes } from '../../../buttons'; import { IListable } from '../../models'; -import { AutoUnsubscribe, Debounce } from '../../../utils'; +import { Debounce, ContextComponent } from '../../../utils'; import { ListingService } from '../../services'; import { WorkflowColumn } from '../models/workflow-column.model'; +interface ColumnHeaderTemplate { + entities: IListable[], + allSelected: boolean, + indeterminate: boolean, + selectedLength: number, + disableSelection: () => void, +} + @Component({ selector: 'iqser-column-header [column] [selectionColumn]', templateUrl: './column-header.component.html', styleUrls: ['./column-header.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ColumnHeaderComponent extends AutoUnsubscribe implements OnInit { +export class ColumnHeaderComponent extends ContextComponent implements OnInit { readonly circleButtonTypes = CircleButtonTypes; @Input() column!: WorkflowColumn; @@ -32,9 +40,6 @@ export class ColumnHeaderComponent extend @Input() bulkActions?: TemplateRef; @Output() readonly selectionColumnChange = new EventEmitter | undefined>(); - allSelected$!: Observable; - indeterminate$!: Observable; - bulkActionsContainerWidth?: number; @ViewChild('bulkActionsContainer') bulkActionsContainer?: ElementRef; @@ -48,18 +53,25 @@ export class ColumnHeaderComponent extend } ngOnInit(): void { - this.allSelected$ = combineLatest([this.listingService.selectedLength$, this.column.entities]).pipe( + const allSelected$ = combineLatest([this.listingService.selectedLength$, this.column.entities]).pipe( map(([length, columnEntities]) => length > 0 && length === columnEntities.length), ); - this.indeterminate$ = combineLatest([this.listingService.selectedLength$, this.column.entities]).pipe( + const indeterminate$ = combineLatest([this.listingService.selectedLength$, this.column.entities]).pipe( map(([length, columnEntities]) => length > 0 && length !== columnEntities.length), ); - this.addSubscription = this.column.entities + const disableSelection$ = this.column.entities .pipe( filter(entities => entities.length <= 1), tap(() => this.disableSelection()), - ) - .subscribe(); + ); + + super._initContext({ + entities: this.column.entities, + allSelected: allSelected$, + indeterminate: indeterminate$, + selectedLength: this.listingService.selectedLength$, + disableSelection: disableSelection$, + }) } toggleSelectAll(): void { diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index e5cac1b..76c22bf 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -1,77 +1,79 @@ - - - + + + + - + -
-
+
- - - - - -
-
- - - -
- -
+
+ + - -
-
- + +
+
+ + + +
+ +
+
+
+
+ +
+ +
-
+
diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts index ec291fd..05b7ea0 100644 --- a/src/lib/listing/workflow/workflow.component.ts +++ b/src/lib/listing/workflow/workflow.component.ts @@ -15,7 +15,7 @@ import { } from '@angular/core'; import { ListingComponent } from '../listing-component.directive'; import { CdkDragDrop, CdkDragStart, CdkDropList } from '@angular/cdk/drag-drop'; -import { AutoUnsubscribe, Debounce, trackByFactory } from '../../utils'; +import { Debounce, trackByFactory, ContextComponent } from '../../utils'; import { IListable } from '../models'; import { EntitiesService, ListingService } from '../services'; import { BehaviorSubject } from 'rxjs'; @@ -24,13 +24,18 @@ import { WorkflowConfig } from './models/workflow-config.model'; import { WorkflowColumn } from './models/workflow-column.model'; import { EntityWrapper } from './models/entity-wrapper.model'; +interface WorkflowTemplate { + updateConfigItems: unknown; + setupResizeObserver: unknown; +} + @Component({ selector: 'iqser-workflow [itemTemplate] [config] [addElementIcon]', templateUrl: './workflow.component.html', styleUrls: ['./workflow.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class WorkflowComponent extends AutoUnsubscribe implements OnInit { +export class WorkflowComponent extends ContextComponent implements OnInit { @Input() headerTemplate?: TemplateRef; @Input() itemTemplate!: TemplateRef; @Input() config!: WorkflowConfig; @@ -81,14 +86,18 @@ export class WorkflowComponent extends Au for (const column of this.config.columns) { column.entities = new BehaviorSubject([]); } - this.addSubscription = this.listingService.displayed$.pipe(tap(entities => this._updateConfigItems(entities))).subscribe(); - this.addSubscription = this.entitiesService.noData$ + const updateConfigItems$ = this.listingService.displayed$.pipe(tap(entities => this._updateConfigItems(entities))) + const setupResizeObserver$ = this.entitiesService.noData$ .pipe( filter(noData => noData), tap(() => this._setupResizeObserver()), - ) - .subscribe(); + ); this._setupResizeObserver(); + + super._initContext({ + updateConfigItems: updateConfigItems$, + setupResizeObserver: setupResizeObserver$ + }) } canMoveTo(column: WorkflowColumn): () => boolean { diff --git a/src/lib/utils/context.component.ts b/src/lib/utils/context.component.ts new file mode 100644 index 0000000..c52fce1 --- /dev/null +++ b/src/lib/utils/context.component.ts @@ -0,0 +1,17 @@ +import { combineLatest, Observable, of } from 'rxjs'; +import { map, startWith } from 'rxjs/operators'; +import { ValuesOf } from './types/utility-types'; + +export class ContextComponent { + componentContext$: Observable | null = of({} as T); + + protected _initContext(context: Record>>): void { + const observables = Object.values(context).map(obs => obs.pipe(startWith(null))); + const keys = Object.keys(context); + this.componentContext$ = combineLatest(observables).pipe(map(values => this._mapKeysToObs(keys, values))); + } + + protected _mapKeysToObs(keys: string[], observables: (ValuesOf | null) []): T { + return keys.reduce((acc, key, index) => ({ ...acc, [key]: observables[index] }), {} as T); + } +} diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 7dc38fc..6fc3c99 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -15,3 +15,4 @@ export * from './types/iqser-types'; export * from './pruning-translation-loader'; export * from './custom-route-reuse.strategy'; export * from './headers-configuration'; +export * from './context.component'; From f785646dbe7f6f0010ddbd79edba26bbbf7844cd Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 21 Jul 2022 15:27:55 +0300 Subject: [PATCH 2/3] RED-3687 - reverted table-content component updates --- .../table-content.component.html | 10 ++--- .../table-content/table-content.component.ts | 40 +++++-------------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/lib/listing/table-content/table-content.component.html b/src/lib/listing/table-content/table-content.component.html index 25772de..6185939 100644 --- a/src/lib/listing/table-content/table-content.component.html +++ b/src/lib/listing/table-content/table-content.component.html @@ -1,20 +1,20 @@ -
+
- +
void, - lastScrolledIndex: number, - hasScrollbarDirective: () => void, - noContent: boolean, - sortedDisplayedEntities: any[], - isHelpModeActive: boolean, -} - @Component({ selector: 'iqser-table-content', templateUrl: './table-content.component.html', styleUrls: ['./table-content.component.scss'], }) -export class TableContentComponent extends ContextComponent implements AfterViewInit { +export class TableContentComponent extends AutoUnsubscribe implements OnDestroy, AfterViewInit { @Input() itemSize!: number; @Input() itemMouseEnterFn?: (entity: T) => void; @Input() itemMouseLeaveFn?: (entity: T) => void; @@ -37,18 +28,17 @@ export class TableContentComponent extends ContextComponent private _lastScrolledIndex = 0; private _multiSelectActive$ = new BehaviorSubject(false); - readonly #checkViewportSize$ = this.listingComponent.noContent$.pipe(tap(() => { - setTimeout(() => { - this.scrollViewport?.checkViewportSize(); - }, 0); - })); - constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, readonly listingService: ListingService, readonly helpModeService: HelpModeService, ) { super(); + this.addSubscription = this.listingComponent.noContent$.subscribe(() => { + setTimeout(() => { + this.scrollViewport?.checkViewportSize(); + }, 0); + }); } multiSelect(entity: T, $event: MouseEvent): void { @@ -59,21 +49,13 @@ export class TableContentComponent extends ContextComponent } ngAfterViewInit(): void { - const lastScrolledIndex$ = this.scrollViewport.scrolledIndexChange.pipe(tap(index => (this._lastScrolledIndex = index))) - const hasScrollbarDirective$ = this.listingService.displayedLength$ + this.addSubscription = this.scrollViewport.scrolledIndexChange.pipe(tap(index => (this._lastScrolledIndex = index))).subscribe(); + this.addSubscription = this.listingService.displayedLength$ .pipe( delay(100), tap(() => this.hasScrollbarDirective.process()), ) - - super._initContext({ - checkViewportSize: this.#checkViewportSize$, - lastScrolledIndex: lastScrolledIndex$, - hasScrollbarDirective: hasScrollbarDirective$, - noContent: this.listingComponent.noContent$, - sortedDisplayedEntities: this.listingComponent.sortedDisplayedEntities$, - isHelpModeActive: this.helpModeService.isHelpModeActive$, - }) + .subscribe(); } scrollToLastIndex(): void { From 767837c0deb93c475afe30150b3c169726168277 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 22 Jul 2022 16:24:42 +0300 Subject: [PATCH 3/3] RED-3686 - updated interfaces names --- .../listing/workflow/column-header/column-header.component.ts | 4 ++-- src/lib/listing/workflow/workflow.component.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/listing/workflow/column-header/column-header.component.ts b/src/lib/listing/workflow/column-header/column-header.component.ts index 72f8d9b..1ac92d9 100644 --- a/src/lib/listing/workflow/column-header/column-header.component.ts +++ b/src/lib/listing/workflow/column-header/column-header.component.ts @@ -18,7 +18,7 @@ import { Debounce, ContextComponent } from '../../../utils'; import { ListingService } from '../../services'; import { WorkflowColumn } from '../models/workflow-column.model'; -interface ColumnHeaderTemplate { +interface ColumnHeaderContext { entities: IListable[], allSelected: boolean, indeterminate: boolean, @@ -32,7 +32,7 @@ interface ColumnHeaderTemplate { styleUrls: ['./column-header.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ColumnHeaderComponent extends ContextComponent implements OnInit { +export class ColumnHeaderComponent extends ContextComponent implements OnInit { readonly circleButtonTypes = CircleButtonTypes; @Input() column!: WorkflowColumn; diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts index 05b7ea0..f134c11 100644 --- a/src/lib/listing/workflow/workflow.component.ts +++ b/src/lib/listing/workflow/workflow.component.ts @@ -24,7 +24,7 @@ import { WorkflowConfig } from './models/workflow-config.model'; import { WorkflowColumn } from './models/workflow-column.model'; import { EntityWrapper } from './models/entity-wrapper.model'; -interface WorkflowTemplate { +interface WorkflowContext { updateConfigItems: unknown; setupResizeObserver: unknown; } @@ -35,7 +35,7 @@ interface WorkflowTemplate { styleUrls: ['./workflow.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class WorkflowComponent extends ContextComponent implements OnInit { +export class WorkflowComponent extends ContextComponent implements OnInit { @Input() headerTemplate?: TemplateRef; @Input() itemTemplate!: TemplateRef; @Input() config!: WorkflowConfig;