From c7a15d2a93bcf5a8f09981a42b8c172b7a05569a Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 5 Sep 2023 17:34:48 +0300 Subject: [PATCH 001/201] DM-358 - WIP on implementing Help Mode-IDs for DocuMine --- src/assets/styles/common-help-mode.scss | 2 ++ src/lib/help-mode/utils/constants.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/assets/styles/common-help-mode.scss b/src/assets/styles/common-help-mode.scss index 95b824f..baa5564 100644 --- a/src/assets/styles/common-help-mode.scss +++ b/src/assets/styles/common-help-mode.scss @@ -7,6 +7,8 @@ .help-highlight, .help-mode:hover { background: rgba(92, 229, 148, 0.5); + //background: rgba(253,189,0, 0.5); box-shadow: 0 0 0 2px var(--iqser-helpmode-primary) inset; + //box-shadow: 0 0 0 2px var(--iqser-yellow-2) inset; cursor: help; } diff --git a/src/lib/help-mode/utils/constants.ts b/src/lib/help-mode/utils/constants.ts index d97a3cf..0f9190e 100644 --- a/src/lib/help-mode/utils/constants.ts +++ b/src/lib/help-mode/utils/constants.ts @@ -4,6 +4,7 @@ export const OVERLAPPING_DROPDOWNS_IDS = { USER_MENU: 'user-menu-items', WORKLOAD_FILTER: 'workload-filters', DOCUMENT_INFO: 'document-info', + BREADCRUMBS_MENU: 'breadcrumbs-menu-items', }; export const SCROLL_BUTTONS_IDS = ['scroll-up', 'scroll-down']; export const PDF_TRON_IFRAME_ID = 'webviewer-1'; @@ -25,6 +26,7 @@ export const OverlappingElements = { USER_MENU: 'USER_MENU', WORKLOAD_FILTER: 'WORKLOAD_FILTER', DOCUMENT_INFO: 'DOCUMENT_INFO', + BREADCRUMBS_MENU: 'BREADCRUMBS_MENU', } as const; export type OverlappingElement = keyof typeof OverlappingElements; From d4d593d24ac4f99dc9455d05906ae37db3c19ac9 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 6 Sep 2023 18:53:28 +0300 Subject: [PATCH 002/201] DM-358 - WIP on implementing Help Mode-IDs for DocuMine --- src/lib/listing/page-header/page-header.component.html | 2 +- src/lib/listing/page-header/page-header.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/listing/page-header/page-header.component.html b/src/lib/listing/page-header/page-header.component.html index 4952155..aff6a0b 100644 --- a/src/lib/listing/page-header/page-header.component.html +++ b/src/lib/listing/page-header/page-header.component.html @@ -74,7 +74,7 @@ *ngIf="showCloseButton" [class.ml-6]="actionConfigs" [icon]="'iqser:close'" - [attr.help-mode-key]="'edit_dossier_in_dossier'" + [attr.help-mode-key]="'close_dossier'" [tooltip]="'common.close' | translate" > diff --git a/src/lib/listing/page-header/page-header.component.ts b/src/lib/listing/page-header/page-header.component.ts index b91c0af..6b8e793 100644 --- a/src/lib/listing/page-header/page-header.component.ts +++ b/src/lib/listing/page-header/page-header.component.ts @@ -40,7 +40,7 @@ export class PageHeaderComponent { ) {} get filterHelpModeKey() { - return this.helpModeKey ? `filter_${this.helpModeKey}_list` : ''; + return this.helpModeKey ? (this.helpModeKey === 'dossier' ? 'filter_dossier_list' : 'filter_documents') : ''; } get #showResetFilters$(): Observable { From 523f2c999e05ed66749d9abc1d43a750246dc7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 6 Sep 2023 20:31:45 +0300 Subject: [PATCH 003/201] DM-414: Base dialog change detection --- src/lib/dialog/base-dialog.component.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts index b17e202..b2f058e 100644 --- a/src/lib/dialog/base-dialog.component.ts +++ b/src/lib/dialog/base-dialog.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Directive, HostListener, inject, OnDestroy } from '@angular/core'; +import { AfterViewInit, Directive, HostListener, inject, OnDestroy, signal } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { hasFormChanged, IqserEventTarget } from '../utils'; @@ -28,12 +28,7 @@ export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { protected readonly _subscriptions: Subscription = new Subscription(); readonly #confirmationDialogService = inject(ConfirmationDialogService); readonly #dialog = inject(MatDialog); - #hasErrors = false; - - protected constructor( - protected readonly _dialogRef: MatDialogRef, - private readonly _isInEditMode = false, - ) {} + readonly #hasErrors = signal(true); get valid(): boolean { return !this.form || this.form.valid; @@ -44,16 +39,23 @@ export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { } get disabled(): boolean { - return !this.valid || !this.changed || this.#hasErrors; + return !this.valid || !this.changed || this.#hasErrors(); } + protected constructor( + protected readonly _dialogRef: MatDialogRef, + private readonly _isInEditMode = false, + ) {} + ngAfterViewInit() { this._subscriptions.add(this._dialogRef.backdropClick().subscribe(() => this.close())); const valueChanges = this.form?.valueChanges ?? of(null); const events = [fromEvent(window, 'keyup'), fromEvent(window, 'input'), valueChanges]; const events$ = merge(...events).pipe( debounceTime(10), - tap(() => (this.#hasErrors = !!document.getElementsByClassName('ng-invalid')[0])), + tap(() => { + this.#hasErrors.set(!!document.getElementsByClassName('ng-invalid')[0]); + }), ); this._subscriptions.add(events$.subscribe()); } From da0e0577218fa268bd14e06db889f74b23508f75 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 7 Sep 2023 19:23:36 +0300 Subject: [PATCH 004/201] DM-358 - added all keys --- src/lib/help-mode/help-mode.service.ts | 6 ++---- src/lib/help-mode/utils/constants.ts | 9 +++++++-- .../inputs/editable-input/editable-input.component.html | 1 + .../inputs/editable-input/editable-input.component.ts | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index 287a047..f8b2ec8 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -5,16 +5,15 @@ import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; import { HELP_MODE_KEYS, MANUAL_BASE_URL } from './tokens'; import { - ANNOTATIONS_LIST_ID, HELP_HIGHLIGHT_CLASS, HELP_MODE_CLASS, OVERLAPPING_DROPDOWNS_IDS, OverlappingElement, PDF_TRON_IFRAME_ID, SCROLL_BUTTONS_IDS, + SCROLLABLE_PARENT_VIEWS_IDS, ScrollableParentView, ScrollableParentViews, - VIRTUAL_SCROLL_ID, WEB_VIEWER_ELEMENTS, } from './utils/constants'; @@ -174,8 +173,7 @@ export class HelpModeService { } if (helper.scrollableParentView) { - const scrollableElementId = - helper.scrollableParentView === ScrollableParentViews.VIRTUAL_SCROLL ? VIRTUAL_SCROLL_ID : ANNOTATIONS_LIST_ID; + const scrollableElementId = SCROLLABLE_PARENT_VIEWS_IDS[helper.scrollableParentView]; const scrollableElement: HTMLElement = document.getElementById(scrollableElementId); if (!scrollableElement) { diff --git a/src/lib/help-mode/utils/constants.ts b/src/lib/help-mode/utils/constants.ts index 0f9190e..64b132f 100644 --- a/src/lib/help-mode/utils/constants.ts +++ b/src/lib/help-mode/utils/constants.ts @@ -1,5 +1,3 @@ -export const VIRTUAL_SCROLL_ID = 'virtual-scroll'; -export const ANNOTATIONS_LIST_ID = 'annotations-list'; export const OVERLAPPING_DROPDOWNS_IDS = { USER_MENU: 'user-menu-items', WORKLOAD_FILTER: 'workload-filters', @@ -18,6 +16,13 @@ export const WEB_VIEWER_ELEMENTS = [ export const ScrollableParentViews = { VIRTUAL_SCROLL: 'VIRTUAL_SCROLL', ANNOTATIONS_LIST: 'ANNOTATIONS_LIST', + SCM_EDIT_DIALOG: 'SCM_EDIT_DIALOG', +} as const; + +export const SCROLLABLE_PARENT_VIEWS_IDS = { + VIRTUAL_SCROLL: 'virtual-scroll', + ANNOTATIONS_LIST: 'annotations-list', + SCM_EDIT_DIALOG: 'scm-edit', } as const; export type ScrollableParentView = keyof typeof ScrollableParentViews; diff --git a/src/lib/inputs/editable-input/editable-input.component.html b/src/lib/inputs/editable-input/editable-input.component.html index 1cb24cb..fe31cc8 100644 --- a/src/lib/inputs/editable-input/editable-input.component.html +++ b/src/lib/inputs/editable-input/editable-input.component.html @@ -9,6 +9,7 @@ (action)="editing = true" [tooltip]="editTooltip" [type]="buttonsType" + [attr.help-mode-key]="helpModeKey" class="edit-button" icon="iqser:edit" > diff --git a/src/lib/inputs/editable-input/editable-input.component.ts b/src/lib/inputs/editable-input/editable-input.component.ts index bec7a5a..b7deeb9 100644 --- a/src/lib/inputs/editable-input/editable-input.component.ts +++ b/src/lib/inputs/editable-input/editable-input.component.ts @@ -23,6 +23,7 @@ export class EditableInputComponent implements OnChanges { @Input() showPreview = true; @Input() canEdit = true; @Input() buttonsType: CircleButtonType = CircleButtonTypes.default; + @Input() helpModeKey: string = ''; @Output() readonly save = new EventEmitter(); parentDimensions?: { width: number; height: number }; newValue = ''; From c83eb568db039e60db49d96b5a99af204b0bdd99 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 7 Sep 2023 23:15:22 +0300 Subject: [PATCH 005/201] DM-358 - added documine theme for helpers --- src/assets/styles/common-help-mode.scss | 7 +++++-- src/lib/help-mode/help-mode.service.ts | 6 ++++++ src/lib/help-mode/utils/constants.ts | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/assets/styles/common-help-mode.scss b/src/assets/styles/common-help-mode.scss index baa5564..b74c0a3 100644 --- a/src/assets/styles/common-help-mode.scss +++ b/src/assets/styles/common-help-mode.scss @@ -7,8 +7,11 @@ .help-highlight, .help-mode:hover { background: rgba(92, 229, 148, 0.5); - //background: rgba(253,189,0, 0.5); box-shadow: 0 0 0 2px var(--iqser-helpmode-primary) inset; - //box-shadow: 0 0 0 2px var(--iqser-yellow-2) inset; cursor: help; + + &.documine-theme { + background: rgba(253,189,0, 0.5); + box-shadow: 0 0 0 2px var(--iqser-yellow-2) inset; + } } diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index f8b2ec8..04f12a5 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -5,6 +5,7 @@ import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; import { HELP_MODE_KEYS, MANUAL_BASE_URL } from './tokens'; import { + DOCUMINE_THEME_CLASS, HELP_HIGHLIGHT_CLASS, HELP_MODE_CLASS, OVERLAPPING_DROPDOWNS_IDS, @@ -16,6 +17,7 @@ import { ScrollableParentViews, WEB_VIEWER_ELEMENTS, } from './utils/constants'; +import { getConfig } from '../services'; export interface Helper { readonly element: HTMLElement; @@ -42,6 +44,7 @@ export class HelpModeService { readonly #helpModeDialogIsOpened$ = new BehaviorSubject(false); readonly helpModeDialogIsOpened$ = this.#helpModeDialogIsOpened$.asObservable(); readonly #renderer: Renderer2; + readonly #isDocumine = getConfig().IS_DOCUMINE; #helpers: Record = {}; #dialogMode = false; @@ -137,6 +140,9 @@ export class HelpModeService { this.#renderer.setAttribute(helperElement, 'href', this.generateDocsLink(key)); this.#renderer.setAttribute(helperElement, 'target', '_blank'); this.#renderer.addClass(helperElement, HELP_MODE_CLASS); + if (this.#isDocumine) { + this.#renderer.addClass(helperElement, DOCUMINE_THEME_CLASS); + } return helperElement; } diff --git a/src/lib/help-mode/utils/constants.ts b/src/lib/help-mode/utils/constants.ts index 64b132f..8cc6c8d 100644 --- a/src/lib/help-mode/utils/constants.ts +++ b/src/lib/help-mode/utils/constants.ts @@ -37,4 +37,5 @@ export const OverlappingElements = { export type OverlappingElement = keyof typeof OverlappingElements; export const HELP_MODE_CLASS = 'help-mode'; +export const DOCUMINE_THEME_CLASS = 'documine-theme'; export const HELP_HIGHLIGHT_CLASS = 'help-highlight'; From 4f4123cd5d1780a962d9df350137cc4b244b66c9 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 8 Sep 2023 15:48:27 +0300 Subject: [PATCH 006/201] DM-358 - set some keys depending on user roles --- src/assets/styles/common-help-mode.scss | 2 +- src/lib/help-mode/utils/constants.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/assets/styles/common-help-mode.scss b/src/assets/styles/common-help-mode.scss index b74c0a3..211e3b2 100644 --- a/src/assets/styles/common-help-mode.scss +++ b/src/assets/styles/common-help-mode.scss @@ -11,7 +11,7 @@ cursor: help; &.documine-theme { - background: rgba(253,189,0, 0.5); + background: rgba(253, 189, 0, 0.5); box-shadow: 0 0 0 2px var(--iqser-yellow-2) inset; } } diff --git a/src/lib/help-mode/utils/constants.ts b/src/lib/help-mode/utils/constants.ts index 8cc6c8d..5804db5 100644 --- a/src/lib/help-mode/utils/constants.ts +++ b/src/lib/help-mode/utils/constants.ts @@ -17,12 +17,14 @@ export const ScrollableParentViews = { VIRTUAL_SCROLL: 'VIRTUAL_SCROLL', ANNOTATIONS_LIST: 'ANNOTATIONS_LIST', SCM_EDIT_DIALOG: 'SCM_EDIT_DIALOG', + WORKFLOW_VIEW: 'WORKFLOW_VIEW', } as const; export const SCROLLABLE_PARENT_VIEWS_IDS = { VIRTUAL_SCROLL: 'virtual-scroll', ANNOTATIONS_LIST: 'annotations-list', SCM_EDIT_DIALOG: 'scm-edit', + WORKFLOW_VIEW: 'workflow-view', } as const; export type ScrollableParentView = keyof typeof ScrollableParentViews; From d1df30b56ea5abd03b0c8623f68fc96db9fef271 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 11 Sep 2023 13:13:07 +0300 Subject: [PATCH 007/201] updated 'error' and ' success' toast colors to be always red and green, no matter the app --- src/assets/styles/common-toasts.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/styles/common-toasts.scss b/src/assets/styles/common-toasts.scss index 5517639..cfeb9ca 100644 --- a/src/assets/styles/common-toasts.scss +++ b/src/assets/styles/common-toasts.scss @@ -84,11 +84,11 @@ $toast-width: 400px; } .toast-success { - background-color: var(--iqser-green-2); + background-color: #5ce594; } .toast-error { - background-color: var(--iqser-red-1); + background-color: #dd4d50; color: var(--iqser-white); } From a4132b82f52e92025f2348fb83587680790ca3a8 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 15 Sep 2023 13:53:09 +0300 Subject: [PATCH 008/201] add css class --- src/assets/styles/common-dialogs.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/assets/styles/common-dialogs.scss b/src/assets/styles/common-dialogs.scss index 65e1490..04dbde9 100644 --- a/src/assets/styles/common-dialogs.scss +++ b/src/assets/styles/common-dialogs.scss @@ -15,6 +15,10 @@ } } +.use-backslash-n-as-line-break { + white-space: pre-line !important; +} + .dialog { position: relative; min-height: 80px; From 9fba181eb9be2be41cdb7e2c22ad52aa34389a51 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 15 Sep 2023 19:07:19 +0300 Subject: [PATCH 009/201] updates --- src/lib/listing/services/listing.service.ts | 75 ++++++++++----------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/src/lib/listing/services/listing.service.ts b/src/lib/listing/services/listing.service.ts index e291712..9a82e97 100644 --- a/src/lib/listing/services/listing.service.ts +++ b/src/lib/listing/services/listing.service.ts @@ -3,13 +3,18 @@ import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map, switchMap, tap } from 'rxjs/operators'; import { FilterService, getFilteredEntities } from '../../filtering'; import { SearchService } from '../../search'; +import { SortingService } from '../../sorting'; +import { getLength, shareDistinctLast, shareLast, some } from '../../utils'; import { Id, IListable } from '../models'; import { EntitiesService } from './entities.service'; -import { getLength, shareDistinctLast, shareLast, some } from '../../utils'; -import { SortingService } from '../../sorting'; @Injectable() export class ListingService, PrimaryKey extends Id = Class['id']> { + #displayed: Class[] = []; + readonly #selected$ = new BehaviorSubject([]); + #anchor: Class | undefined = undefined; + #focus: Class | undefined = undefined; + #sortedDisplayed: Class[] = []; readonly displayed$: Observable; readonly displayedLength$: Observable; readonly areAllSelected$: Observable; @@ -20,13 +25,6 @@ export class ListingService, PrimaryKey exte readonly selectedLength$: Observable; readonly sortedDisplayedEntities$: Observable; - private _displayed: Class[] = []; - private readonly _selected$ = new BehaviorSubject([]); - - private _anchor: Class | undefined = undefined; - private _focus: Class | undefined = undefined; - private _sortedDisplayed: Class[] = []; - constructor( protected readonly _filterService: FilterService, protected readonly _searchService: SearchService, @@ -36,12 +34,12 @@ export class ListingService, PrimaryKey exte this.displayed$ = this._getDisplayed$; this.displayedLength$ = this.displayed$.pipe(getLength, shareDistinctLast()); - this.selected$ = this._selected$.asObservable().pipe(shareDistinctLast()); - this.selectedEntities$ = combineLatest([this._selected$.asObservable(), this._entitiesService.all$]).pipe( + this.selected$ = this.#selected$.asObservable().pipe(shareDistinctLast()); + this.selectedEntities$ = combineLatest([this.#selected$.asObservable(), this._entitiesService.all$]).pipe( map(([selectedIds, all]) => all.filter(a => selectedIds.includes(a.id))), shareLast(), ); - this.selectedLength$ = this._selected$.pipe(getLength, shareDistinctLast()); + this.selectedLength$ = this.#selected$.pipe(getLength, shareDistinctLast()); this.areAllSelected$ = this._areAllSelected$; this.areSomeSelected$ = this._areSomeSelected$; @@ -56,7 +54,7 @@ export class ListingService, PrimaryKey exte } get selectedIds(): PrimaryKey[] { - return this._selected$.getValue(); + return this.#selected$.getValue(); } private get _getDisplayed$(): Observable { @@ -67,7 +65,7 @@ export class ListingService, PrimaryKey exte map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)), map(entities => this._searchService.searchIn(entities)), tap(displayed => { - this._displayed = displayed; + this.#displayed = displayed; this._updateSelection(); }), shareLast(), @@ -96,15 +94,16 @@ export class ListingService, PrimaryKey exte } private get _allSelected() { - return this._displayed.length !== 0 && this._displayed.length === this.selected.length; + return this.#displayed.length !== 0 && this.#displayed.length === this.selected.length; } setSelected(newEntities: Class[]): void { + console.trace('setSelected', newEntities); const selectedIds = newEntities.map(e => e.id); - this._selected$.next(selectedIds); + this.#selected$.next(selectedIds); - if (this._anchor && !newEntities.includes(this._anchor)) { - this._anchor = undefined; + if (this.#anchor && !newEntities.includes(this.#anchor)) { + this.#anchor = undefined; } } @@ -113,7 +112,7 @@ export class ListingService, PrimaryKey exte } isSelected$(entity: Class): Observable { - return this._selected$.pipe( + return this.#selected$.pipe( some(selectedId => selectedId === entity.id), shareLast(), ); @@ -123,7 +122,7 @@ export class ListingService, PrimaryKey exte if (this._allSelected) { return this.setSelected([]); } - this.setSelected(this._displayed); + this.setSelected(this.#displayed); } select(entity: Class, withShift = false): void { @@ -134,8 +133,8 @@ export class ListingService, PrimaryKey exte if (!withShift) { if (!isCurrentlySelected) { this.setSelected([...currentlySelected, entity]); - this._anchor = entity; - this._focus = entity; + this.#anchor = entity; + this.#focus = entity; } else { // Entity is previously selected, deselect it this.setSelected(currentlySelected.slice(0, currentEntityIdx).concat(currentlySelected.slice(currentEntityIdx + 1))); @@ -154,52 +153,52 @@ export class ListingService, PrimaryKey exte /** Move anchor & focus to next selected, or previous selected, or undefined */ #moveAnchorAfterDeselect(entity: Class): void { - const entityIdx = this._sortedDisplayed.indexOf(entity); + const entityIdx = this.#sortedDisplayed.indexOf(entity); let newAnchorIdx = entityIdx + 1; let increment = 1; do { - if (this.isSelected(this._sortedDisplayed[newAnchorIdx])) { + if (this.isSelected(this.#sortedDisplayed[newAnchorIdx])) { break; } newAnchorIdx += increment; - if (newAnchorIdx === this._sortedDisplayed.length) { + if (newAnchorIdx === this.#sortedDisplayed.length) { newAnchorIdx = entityIdx - 1; increment = -1; } } while (newAnchorIdx > 0); - this._anchor = this._sortedDisplayed[newAnchorIdx]; - this._focus = this._sortedDisplayed[newAnchorIdx]; + this.#anchor = this.#sortedDisplayed[newAnchorIdx]; + this.#focus = this.#sortedDisplayed[newAnchorIdx]; } #shiftClick(entity: Class): void { - const entityIdx = this._sortedDisplayed.indexOf(entity); + const entityIdx = this.#sortedDisplayed.indexOf(entity); - if (!this._anchor || !this._focus) { - this._anchor = this._sortedDisplayed[0]; - this._focus = this._sortedDisplayed[0]; + if (!this.#anchor || !this.#focus) { + this.#anchor = this.#sortedDisplayed[0]; + this.#focus = this.#sortedDisplayed[0]; } - const anchorIdx = this._sortedDisplayed.indexOf(this._anchor); - const focusIdx = this._sortedDisplayed.indexOf(this._focus); + const anchorIdx = this.#sortedDisplayed.indexOf(this.#anchor); + const focusIdx = this.#sortedDisplayed.indexOf(this.#focus); // Deselect entities between anchor and previous focus - const remove = this._sortedDisplayed.slice(Math.min(anchorIdx, focusIdx), Math.max(anchorIdx, focusIdx) + 1); + const remove = this.#sortedDisplayed.slice(Math.min(anchorIdx, focusIdx), Math.max(anchorIdx, focusIdx) + 1); // Update focus - this._focus = entity; + this.#focus = entity; // Select entities between anchor and new focus - const intervalEntities = this._sortedDisplayed.slice(Math.min(anchorIdx, entityIdx), Math.max(anchorIdx, entityIdx) + 1); + const intervalEntities = this.#sortedDisplayed.slice(Math.min(anchorIdx, entityIdx), Math.max(anchorIdx, entityIdx) + 1); const newSelected = [...intervalEntities, ...this.selected.filter(e => !intervalEntities.includes(e) && !remove.includes(e))]; this.setSelected(newSelected); } private _updateSelection(): void { - const items = this._displayed.filter(item => this.selected.includes(item)); + const items = this.#displayed.filter(item => this.selected.includes(item)); this.setSelected(items); } @@ -209,7 +208,7 @@ export class ListingService, PrimaryKey exte return this._sortingService.sortingOption$.pipe( switchMap(() => sortedEntities$), tap(sortedEntities => { - this._sortedDisplayed = sortedEntities; + this.#sortedDisplayed = sortedEntities; }), shareDistinctLast(), ); From 6cb63fcf43fbbf522b847f57467156808bbe72a1 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 15 Sep 2023 19:09:30 +0300 Subject: [PATCH 010/201] remove console.trace --- src/lib/listing/services/listing.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/listing/services/listing.service.ts b/src/lib/listing/services/listing.service.ts index 9a82e97..77537e0 100644 --- a/src/lib/listing/services/listing.service.ts +++ b/src/lib/listing/services/listing.service.ts @@ -98,7 +98,6 @@ export class ListingService, PrimaryKey exte } setSelected(newEntities: Class[]): void { - console.trace('setSelected', newEntities); const selectedIds = newEntities.map(e => e.id); this.#selected$.next(selectedIds); From 8d4a68f92cc74c54382ff898c39fab0cd8e0f72c Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Tue, 19 Sep 2023 11:36:25 +0300 Subject: [PATCH 011/201] RED-7605: Fixed page header overlap. --- src/assets/styles/common-layout.scss | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/assets/styles/common-layout.scss b/src/assets/styles/common-layout.scss index 1d981f9..9dd2185 100644 --- a/src/assets/styles/common-layout.scss +++ b/src/assets/styles/common-layout.scss @@ -86,17 +86,13 @@ section.settings { } .fullscreen { - .page-header { - position: absolute; - top: 0; - } - .content-inner { height: calc(100% - 50px); } - .overlay-shadow { - top: 50px; + .right-container { + transform: translateY(61px); + height: calc(100% - 61px); } } From 2f2ee530b15e9e84dc28c3a23340cfb8b8b94319 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 21 Sep 2023 11:13:05 +0300 Subject: [PATCH 012/201] update dialog service --- src/lib/dialog/iqser-dialog.service.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/dialog/iqser-dialog.service.ts b/src/lib/dialog/iqser-dialog.service.ts index 599d65b..806c5b8 100644 --- a/src/lib/dialog/iqser-dialog.service.ts +++ b/src/lib/dialog/iqser-dialog.service.ts @@ -13,10 +13,11 @@ export class IqserDialog { open< Component extends IqserDialogComponent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars Data extends Component[DATA_TYPE] = Component[DATA_TYPE], Return extends Component[RETURN_TYPE] = Component[RETURN_TYPE], - >(dialog: ComponentType, config?: MatDialogConfig) { - const ref = this._dialog.open(dialog, config); + >(dialog: ComponentType, config?: MatDialogConfig) { + const ref = this._dialog.open(dialog, config); return { ...ref, result() { @@ -29,7 +30,7 @@ export class IqserDialog { Component extends IqserDialogComponent, Data extends Component[DATA_TYPE] = Component[DATA_TYPE], Return extends Component[RETURN_TYPE] = Component[RETURN_TYPE], - >(dialog: ComponentType, config?: MatDialogConfig) { + >(dialog: ComponentType, config?: MatDialogConfig) { return this.open(dialog, { ...largeDialogConfig, ...config }); } @@ -37,7 +38,7 @@ export class IqserDialog { Component extends IqserDialogComponent, Data extends Component[DATA_TYPE] = Component[DATA_TYPE], Return extends Component[RETURN_TYPE] = Component[RETURN_TYPE], - >(dialog: ComponentType, config?: MatDialogConfig) { + >(dialog: ComponentType, config?: MatDialogConfig) { return this.open(dialog, { ...defaultDialogConfig, ...config }); } } From 890cf417a1900eb882c61d335276360a48c9a54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 21 Sep 2023 18:30:13 +0300 Subject: [PATCH 013/201] RED-3800: Functional composite route guard, skeleton fixes --- src/lib/services/composite-route.guard.ts | 25 ++++++++++++++++--- src/lib/shared/skeleton/skeleton.component.ts | 6 ++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/lib/services/composite-route.guard.ts b/src/lib/services/composite-route.guard.ts index 99f58ec..5f425c9 100644 --- a/src/lib/services/composite-route.guard.ts +++ b/src/lib/services/composite-route.guard.ts @@ -1,6 +1,6 @@ -import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router'; -import { Injectable, InjectionToken, Injector } from '@angular/core'; -import { firstValueFrom, from, of } from 'rxjs'; +import { ActivatedRouteSnapshot, CanActivate, CanActivateFn, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { inject, Injectable, InjectionToken, Injector, runInInjectionContext } from '@angular/core'; +import { concatMap, firstValueFrom, from, last, of, takeWhile } from 'rxjs'; import { LoadingService } from '../loading'; import { SkeletonService } from './skeleton.service'; @@ -62,3 +62,22 @@ export class CompositeRouteGuard implements CanActivate { } } } + +export type AsyncGuard = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Promise; + +export function orderedAsyncGuards(guards: Array): CanActivateFn { + return (route, state) => { + const injector = inject(Injector); + + return from(guards).pipe( + // For each guard, fire canActivate and wait for it + // to complete. + concatMap(guard => runInInjectionContext(injector, () => guard(route, state))), + // Don't execute the next guard if the current guard's + // result is not true. + takeWhile(value => value === true, /* inclusive */ true), + // Return the last guard's result. + last(), + ); + }; +} diff --git a/src/lib/shared/skeleton/skeleton.component.ts b/src/lib/shared/skeleton/skeleton.component.ts index 3f9a950..b9b74f4 100644 --- a/src/lib/shared/skeleton/skeleton.component.ts +++ b/src/lib/shared/skeleton/skeleton.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, HostBinding, inject, Input, TemplateRef } from '@angular/core'; import { SkeletonService } from '../../services'; -import { getCurrentUser } from '../../users'; +import { IqserUserService } from '../../users'; import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; import { tap } from 'rxjs/operators'; @@ -16,10 +16,10 @@ export class SkeletonComponent { @Input() templates!: Record>; @HostBinding('style.display') display = 'none'; - readonly #currentUser = getCurrentUser(); + readonly iqserUserService = inject(IqserUserService); readonly type$ = inject(SkeletonService).type$.pipe( tap(type => { - this.display = type && this.#currentUser ? 'block' : 'none'; + this.display = type && this.iqserUserService.currentUser ? 'block' : 'none'; }), ); } From 1b76808917fcb3ff2ab6fe377599bb1583515499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Sun, 24 Sep 2023 12:19:55 +0300 Subject: [PATCH 014/201] RED-3800: Redo HasScrollbar directive --- src/lib/directives/has-scrollbar.directive.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/lib/directives/has-scrollbar.directive.ts b/src/lib/directives/has-scrollbar.directive.ts index 3b94ed5..eefd625 100644 --- a/src/lib/directives/has-scrollbar.directive.ts +++ b/src/lib/directives/has-scrollbar.directive.ts @@ -1,27 +1,33 @@ -import { ChangeDetectorRef, Directive, ElementRef, HostBinding, HostListener, OnChanges, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Directive, ElementRef, HostBinding, OnDestroy, OnInit } from '@angular/core'; @Directive({ selector: '[iqserHasScrollbar]', standalone: true, }) -export class HasScrollbarDirective implements OnInit, OnChanges { +export class HasScrollbarDirective implements OnInit, OnDestroy { @HostBinding('class') class = ''; - - constructor( - protected readonly _elementRef: ElementRef, - protected readonly _changeDetector: ChangeDetectorRef, - ) {} + private readonly _resizeObserver: ResizeObserver; get hasScrollbar() { const element = this._elementRef?.nativeElement as HTMLElement; return element.clientHeight < element.scrollHeight; } + constructor( + protected readonly _elementRef: ElementRef, + protected readonly _changeDetector: ChangeDetectorRef, + ) { + this._resizeObserver = new ResizeObserver(entry => { + this.process(); + }); + + this._resizeObserver.observe(this._elementRef.nativeElement); + } + ngOnInit() { setTimeout(() => this.process(), 0); } - @HostListener('window:resize') process() { const newClass = this.hasScrollbar ? 'has-scrollbar' : ''; if (this.class !== newClass) { @@ -30,7 +36,7 @@ export class HasScrollbarDirective implements OnInit, OnChanges { } } - ngOnChanges() { - this.process(); + ngOnDestroy() { + this._resizeObserver.unobserve(this._elementRef.nativeElement); } } From 6dc910c4ca6182fd4f1d6746b1114ad5210229a8 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Mon, 25 Sep 2023 17:54:17 +0300 Subject: [PATCH 015/201] RED-3800: greyed out disabled select value. --- src/assets/styles/common-select.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/assets/styles/common-select.scss b/src/assets/styles/common-select.scss index c82a587..19f2d34 100644 --- a/src/assets/styles/common-select.scss +++ b/src/assets/styles/common-select.scss @@ -46,6 +46,12 @@ color: var(--iqser-text); } +.mat-form-field-disabled { + .mat-mdc-select-value { + color: var(--iqser-grey-3); + } +} + .mat-mdc-option .mat-mdc-option-pseudo-checkbox { display: none; } From 14f4f0089850700284ba7f44a5fe80639f3cd68f Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Fri, 29 Sep 2023 14:39:20 +0300 Subject: [PATCH 016/201] RED-7649: Added raw error toaster. --- src/lib/services/toaster.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/services/toaster.service.ts b/src/lib/services/toaster.service.ts index 726cc7d..959f8f3 100644 --- a/src/lib/services/toaster.service.ts +++ b/src/lib/services/toaster.service.ts @@ -65,6 +65,10 @@ export class Toaster { return this._toastr.error(resultedMsg, options?.title, options); } + rawError(message: string) { + return this._toastr.error(message); + } + info(message: string, options?: Partial): ActiveToast { return this.#showToastNotification(message, NotificationType.INFO, options); } From a9d6e5e7286b7655140a3ed19c9f0b9c5464b0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 2 Oct 2023 15:20:36 +0300 Subject: [PATCH 017/201] Fixed checkbox & toggle --- src/assets/styles/common-checkbox.scss | 6 +----- src/assets/styles/common-toggle.scss | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/assets/styles/common-checkbox.scss b/src/assets/styles/common-checkbox.scss index 5c45ae0..cee4a1e 100644 --- a/src/assets/styles/common-checkbox.scss +++ b/src/assets/styles/common-checkbox.scss @@ -24,10 +24,6 @@ $ripple-size: 26px; --mdc-checkbox-selected-hover-state-layer-color: var(--iqser-primary); --mdc-checkbox-selected-pressed-state-layer-color: var(--iqser-primary); - input[type='checkbox'] { - margin-top: calc($checkbox-size * -1.5); - } - .mdc-form-field > label { padding-left: 8px; line-height: 24px; @@ -54,7 +50,7 @@ $ripple-size: 26px; .mat-mdc-checkbox-touch-target { height: $ripple-size; width: $ripple-size; - transform: translate(calc(($checkbox-size - $ripple-size) / 2), calc(($checkbox-size - $ripple-size) / 2)); + transform: translate(-50%, -50%); } } diff --git a/src/assets/styles/common-toggle.scss b/src/assets/styles/common-toggle.scss index f463999..1b48bef 100644 --- a/src/assets/styles/common-toggle.scss +++ b/src/assets/styles/common-toggle.scss @@ -1,6 +1,7 @@ .mat-mdc-slide-toggle { .mdc-switch { --mdc-switch-handle-elevation: none; + --mdc-switch-handle-elevation-shadow: none; --mdc-switch-selected-track-color: var(--iqser-primary); --mdc-switch-selected-hover-track-color: var(--iqser-primary); From bd532cd28fe9b7fb57c3e92253df490f6efae6a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 5 Oct 2023 16:43:25 +0300 Subject: [PATCH 018/201] Moved switch tenant logic to common-ui --- .../tenants/services/keycloak-status.service.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lib/tenants/services/keycloak-status.service.ts b/src/lib/tenants/services/keycloak-status.service.ts index 8b7b082..9c7055d 100644 --- a/src/lib/tenants/services/keycloak-status.service.ts +++ b/src/lib/tenants/services/keycloak-status.service.ts @@ -31,6 +31,21 @@ export class KeycloakStatusService { } } + async switchTenant(tenantId?: string) { + let redirectUri: string; + + if (tenantId) { + redirectUri = this.#keycloakService.getKeycloakInstance().createLoginUrl({ + redirectUri: this.createLoginUrl(tenantId), + idpHint: this.#config.OAUTH_IDP_HINT, + }); + } else { + redirectUri = window.location.origin + this.#baseHref; + } + + await this.#keycloakService.logout(redirectUri); + } + createLoginUrl(tenant?: string) { if (tenant && window.location.href.indexOf('/' + tenant + '/') > 0) { return window.location.href; From df01d0a910fe443a11904b74055495ebc5ffc647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 11 Oct 2023 11:46:02 +0300 Subject: [PATCH 019/201] Select: active item color --- src/assets/styles/common-select.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/assets/styles/common-select.scss b/src/assets/styles/common-select.scss index 19f2d34..3aa1012 100644 --- a/src/assets/styles/common-select.scss +++ b/src/assets/styles/common-select.scss @@ -12,6 +12,7 @@ background-color: var(--iqser-background); @include common-mixins.scroll-bar; @include common-mixins.drop-shadow; + --mat-option-selected-state-label-text-color: var(--iqser-primary); } .mat-mdc-select-arrow-wrapper { From bfaae5adf6522a099a55c7f3546dee3b542c4c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 19 Oct 2023 21:58:09 +0300 Subject: [PATCH 020/201] Use disabled instead of canEditInput in dynamic input --- .../inputs/dynamic-input/dynamic-input.component.html | 9 +++------ src/lib/inputs/dynamic-input/dynamic-input.component.ts | 8 +++----- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/lib/inputs/dynamic-input/dynamic-input.component.html b/src/lib/inputs/dynamic-input/dynamic-input.component.html index c9c45b5..6f53cb6 100644 --- a/src/lib/inputs/dynamic-input/dynamic-input.component.html +++ b/src/lib/inputs/dynamic-input/dynamic-input.component.html @@ -5,10 +5,9 @@ @@ -22,9 +21,8 @@ (ngModelChange)="onChange($event)" *ngIf="isText" [(ngModel)]="input" - [disabled]="!canEditInput" + [disabled]="disabled" [id]="id" - [name]="name" [placeholder]="placeholder || ''" iqserStopPropagation type="text" @@ -34,9 +32,8 @@ (ngModelChange)="onChange($event)" *ngIf="isNumber" [(ngModel)]="input" - [disabled]="!canEditInput" + [disabled]="disabled" [id]="id" - [name]="name" iqserStopPropagation type="number" /> diff --git a/src/lib/inputs/dynamic-input/dynamic-input.component.ts b/src/lib/inputs/dynamic-input/dynamic-input.component.ts index 9521fcb..c4fd2c3 100644 --- a/src/lib/inputs/dynamic-input/dynamic-input.component.ts +++ b/src/lib/inputs/dynamic-input/dynamic-input.component.ts @@ -19,7 +19,7 @@ export type InputType = keyof typeof InputTypes; type DynamicInput = number | string | Date; @Component({ - selector: 'iqser-dynamic-input [type]', + selector: 'iqser-dynamic-input', templateUrl: './dynamic-input.component.html', styleUrls: ['./dynamic-input.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, @@ -40,13 +40,11 @@ type DynamicInput = number | string | Date; }) export class DynamicInputComponent extends FormFieldComponent { @Input() label?: string; - @Input() type!: InputType; + @Input({ required: true }) type!: InputType; @Input() placeholder?: string; @Input() id?: string; - @Input() name?: string; - @Input() classList?: string = ''; + @Input() classList = ''; @Input() input!: DynamicInput; - @Input() canEditInput = true; @Output() readonly closedDatepicker = new EventEmitter(); get isDate() { From a6383c1dbc840115897a31567c3f5633ba78b43a Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Fri, 20 Oct 2023 15:34:51 +0300 Subject: [PATCH 021/201] RED-6893: Stop loading after error is handled. --- src/lib/error/server-error-interceptor.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/error/server-error-interceptor.ts b/src/lib/error/server-error-interceptor.ts index 98f4b46..565d5cf 100644 --- a/src/lib/error/server-error-interceptor.ts +++ b/src/lib/error/server-error-interceptor.ts @@ -8,13 +8,12 @@ import { HttpStatusCode, } from '@angular/common/http'; import { Inject, Injectable, Optional } from '@angular/core'; -import { MonoTypeOperatorFunction, Observable, retry, throwError, timer } from 'rxjs'; +import { finalize, MonoTypeOperatorFunction, Observable, retry, throwError, timer } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; import { MAX_RETRIES_ON_SERVER_ERROR, SERVER_ERROR_SKIP_PATHS } from './tokens'; import { ErrorService } from './error.service'; -import { KeycloakService } from 'keycloak-angular'; -import { IqserConfigService } from '../services'; import { KeycloakStatusService } from '../tenants'; +import { LoadingService } from '../loading'; function updateSeconds(seconds: number) { if (seconds === 0 || seconds === 1) { @@ -56,8 +55,7 @@ export class ServerErrorInterceptor implements HttpInterceptor { constructor( private readonly _errorService: ErrorService, - private readonly _keycloakService: KeycloakService, - private readonly _configService: IqserConfigService, + private readonly _loadingService: LoadingService, private readonly _keycloakStatusService: KeycloakStatusService, @Optional() @Inject(MAX_RETRIES_ON_SERVER_ERROR) private readonly _maxRetries: number, @Optional() @Inject(SERVER_ERROR_SKIP_PATHS) private readonly _skippedPaths: string[], @@ -84,7 +82,11 @@ export class ServerErrorInterceptor implements HttpInterceptor { this._urlsWithError.add(req.url); } - return throwError(() => error); + return throwError(() => error).pipe( + finalize(() => { + this._loadingService.stop(); + }), + ); }), backoffOnServerError(this._maxRetries, this._skippedPaths), ); From 85fba4a1dd57ecbb133d938552d75fceffa1099c Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 30 Oct 2023 15:00:54 +0200 Subject: [PATCH 022/201] RED-7619 add config to toaster rawError method --- src/lib/services/toaster.service.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/services/toaster.service.ts b/src/lib/services/toaster.service.ts index 959f8f3..bd22d05 100644 --- a/src/lib/services/toaster.service.ts +++ b/src/lib/services/toaster.service.ts @@ -1,12 +1,12 @@ +import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { ActiveToast, ToastrService } from 'ngx-toastr'; -import { IndividualConfig } from 'ngx-toastr/toastr/toastr-config'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NavigationStart, Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; +import { ActiveToast, ToastrService } from 'ngx-toastr'; +import { IndividualConfig } from 'ngx-toastr/toastr/toastr-config'; import { filter, tap } from 'rxjs/operators'; import { ErrorMessageService } from './error-message.service'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; const enum NotificationType { SUCCESS = 'SUCCESS', @@ -65,8 +65,8 @@ export class Toaster { return this._toastr.error(resultedMsg, options?.title, options); } - rawError(message: string) { - return this._toastr.error(message); + rawError(message: string, config?: Partial>) { + return this._toastr.error(message, undefined, config); } info(message: string, options?: Partial): ActiveToast { From f09ef68442d987c1db2d7712e209b25a3161a129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Tue, 31 Oct 2023 16:48:13 +0200 Subject: [PATCH 023/201] Fixed infinite loop in tenants service --- src/lib/tenants/services/tenants.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/tenants/services/tenants.service.ts b/src/lib/tenants/services/tenants.service.ts index 9fdbfd0..5114aa0 100644 --- a/src/lib/tenants/services/tenants.service.ts +++ b/src/lib/tenants/services/tenants.service.ts @@ -67,13 +67,14 @@ export class TenantsService { const is90DaysOld = diff >= 90; if (is90DaysOld) { this.#logger.warn(`[TENANTS] Saved tenant ${s.tenantId} is 90 days old, delete it`); - this.removeStored(s.tenantId); continue; } validStoredTenants.push(s); } + this.#storageReference.setItem(STORED_TENANTS_KEY, JSON.stringify(validStoredTenants)); + return validStoredTenants; } From 54d60b6c973ffe959485bc125dc748cb6e9a4806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Tue, 31 Oct 2023 18:10:22 +0200 Subject: [PATCH 024/201] Editable input form width --- src/lib/inputs/editable-input/editable-input.component.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/inputs/editable-input/editable-input.component.scss b/src/lib/inputs/editable-input/editable-input.component.scss index 3e7a471..dd6e41a 100644 --- a/src/lib/inputs/editable-input/editable-input.component.scss +++ b/src/lib/inputs/editable-input/editable-input.component.scss @@ -18,3 +18,7 @@ textarea { margin: 0; min-height: 0; } + +form { + width: 100%; +} From 6eef9aba4bddf5c46bb1bc1dd2b0e83f6c01cb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 1 Nov 2023 00:56:59 +0200 Subject: [PATCH 025/201] Updated hasRoleGuard --- src/lib/users/guards/has-roles.guard.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib/users/guards/has-roles.guard.ts b/src/lib/users/guards/has-roles.guard.ts index 53227eb..e550e59 100644 --- a/src/lib/users/guards/has-roles.guard.ts +++ b/src/lib/users/guards/has-roles.guard.ts @@ -1,9 +1,10 @@ -import { CanActivateFn, Router } from '@angular/router'; +import { Router } from '@angular/router'; import { inject } from '@angular/core'; import { IqserUserService } from '../services/iqser-user.service'; import { TenantsService } from '../../tenants'; +import { AsyncGuard } from '../../services'; -export function hasAnyRoleGuard(): CanActivateFn { +export function doesNotHaveAnyRoleGuard(): AsyncGuard { return async () => { const router = inject(Router); const activeTenantId = inject(TenantsService).activeTenantId; @@ -15,3 +16,16 @@ export function hasAnyRoleGuard(): CanActivateFn { return true; }; } + +export function hasAnyRoleGuard(): AsyncGuard { + return async () => { + const router = inject(Router); + const activeTenantId = inject(TenantsService).activeTenantId; + const user = await inject(IqserUserService).loadCurrentUser(); + if (!user?.hasAnyRole) { + await router.navigate([`/${activeTenantId}/auth-error`]); + return false; + } + return true; + }; +} From a6f8a3576522614b2d5bcc95b8a59ec0315e3a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 1 Nov 2023 00:58:52 +0200 Subject: [PATCH 026/201] Renamed roles guards --- src/lib/users/guards/{has-roles.guard.ts => roles.guard.ts} | 4 ++-- src/lib/users/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/lib/users/guards/{has-roles.guard.ts => roles.guard.ts} (90%) diff --git a/src/lib/users/guards/has-roles.guard.ts b/src/lib/users/guards/roles.guard.ts similarity index 90% rename from src/lib/users/guards/has-roles.guard.ts rename to src/lib/users/guards/roles.guard.ts index e550e59..3b6a75e 100644 --- a/src/lib/users/guards/has-roles.guard.ts +++ b/src/lib/users/guards/roles.guard.ts @@ -4,7 +4,7 @@ import { IqserUserService } from '../services/iqser-user.service'; import { TenantsService } from '../../tenants'; import { AsyncGuard } from '../../services'; -export function doesNotHaveAnyRoleGuard(): AsyncGuard { +export function doesNotHaveAnyRole(): AsyncGuard { return async () => { const router = inject(Router); const activeTenantId = inject(TenantsService).activeTenantId; @@ -17,7 +17,7 @@ export function doesNotHaveAnyRoleGuard(): AsyncGuard { }; } -export function hasAnyRoleGuard(): AsyncGuard { +export function hasAnyRole(): AsyncGuard { return async () => { const router = inject(Router); const activeTenantId = inject(TenantsService).activeTenantId; diff --git a/src/lib/users/index.ts b/src/lib/users/index.ts index 744b89f..be19ace 100644 --- a/src/lib/users/index.ts +++ b/src/lib/users/index.ts @@ -11,6 +11,6 @@ export * from './services/default-user.service'; export * from './iqser-users.module'; export * from './guards/iqser-auth-guard.service'; export * from './guards/iqser-role-guard.service'; -export * from './guards/has-roles.guard'; +export * from './guards/roles.guard'; export * from './components/user-button/user-button.component'; export * from './components/initials-avatar/initials-avatar.component'; From 99b2c83d61c82b058ecb664cae81463077e0aa02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 1 Nov 2023 18:32:02 +0200 Subject: [PATCH 027/201] Moved pagination component to common ui --- src/lib/pagination/pagination.component.html | 26 ++++++++ src/lib/pagination/pagination.component.scss | 27 ++++++++ src/lib/pagination/pagination.component.ts | 65 ++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 src/lib/pagination/pagination.component.html create mode 100644 src/lib/pagination/pagination.component.scss create mode 100644 src/lib/pagination/pagination.component.ts diff --git a/src/lib/pagination/pagination.component.html b/src/lib/pagination/pagination.component.html new file mode 100644 index 0000000..8a0db3f --- /dev/null +++ b/src/lib/pagination/pagination.component.html @@ -0,0 +1,26 @@ +
+| +
+ {{ displayValue(page) }} +
+| +
diff --git a/src/lib/pagination/pagination.component.scss b/src/lib/pagination/pagination.component.scss new file mode 100644 index 0000000..fae36a8 --- /dev/null +++ b/src/lib/pagination/pagination.component.scss @@ -0,0 +1,27 @@ +:host { + display: flex; + + > *:not(:last-child) { + margin-right: 12px; + } + + .disabled, + span { + opacity: 0.5; + pointer-events: none; + } + + .page { + cursor: pointer; + + &.disabled, + &.dots { + cursor: default; + } + + &.active { + color: var(--iqser-primary); + font-weight: bold; + } + } +} diff --git a/src/lib/pagination/pagination.component.ts b/src/lib/pagination/pagination.component.ts new file mode 100644 index 0000000..82ac382 --- /dev/null +++ b/src/lib/pagination/pagination.component.ts @@ -0,0 +1,65 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { NgForOf } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; + +@Component({ + selector: 'iqser-pagination', + templateUrl: './pagination.component.html', + styleUrls: ['./pagination.component.scss'], + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [NgForOf, TranslateModule], +}) +export class PaginationComponent { + displayedPages: (number | string)[] = []; + @Output() pageChanged = new EventEmitter(); + + private _currentPage: number = 0; + + get currentPage() { + return this._currentPage; + } + + private _totalPages: number = 0; + + get totalPages() { + return this._totalPages; + } + + @Input() + set settings(value: { currentPage: number; totalPages: number }) { + this._currentPage = value.currentPage; + this._totalPages = value.totalPages; + this._updatePagesArray(); + } + + selectPage(page: number | string) { + if (page !== '...') { + this.pageChanged.emit(page as number); + } + } + + displayValue(page: number | string) { + return page === '...' ? page : (page as number) + 1; + } + + isNumber(page: number | string) { + return Number.isInteger(page); + } + + private _updatePagesArray() { + this.displayedPages = [0]; + if (Math.max(1, this.currentPage - 1) > 1) { + this.displayedPages.push('...'); + } + for (let page = Math.max(1, this.currentPage - 1); page <= Math.min(this.currentPage + 1, this.totalPages - 1); ++page) { + this.displayedPages.push(page); + } + if (Math.min(this.currentPage + 1, this.totalPages - 1) !== this.totalPages - 1) { + if (this.currentPage + 1 < this.totalPages - 2) { + this.displayedPages.push('...'); + } + this.displayedPages.push(this.totalPages - 1); + } + } +} From 5f0904e6a9a320df539f99d0c363a83f9f7d84e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 1 Nov 2023 18:56:22 +0200 Subject: [PATCH 028/201] Paginated entities updates --- src/assets/styles/_common-variables.scss | 1 + src/assets/styles/common-side-nav.scss | 2 +- .../services/paginated-entities.service.ts | 39 ++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/assets/styles/_common-variables.scss b/src/assets/styles/_common-variables.scss index f887b94..8007393 100644 --- a/src/assets/styles/_common-variables.scss +++ b/src/assets/styles/_common-variables.scss @@ -62,6 +62,7 @@ body { --iqser-font-family: Inter, sans-serif; --iqser-app-name-font-family: Inter, sans-serif; --iqser-circle-button-radius: 50%; + --iqser-side-nav-item-radius: 20px; } $required-variables: 'iqser-primary'; diff --git a/src/assets/styles/common-side-nav.scss b/src/assets/styles/common-side-nav.scss index 2fde61d..9e40736 100644 --- a/src/assets/styles/common-side-nav.scss +++ b/src/assets/styles/common-side-nav.scss @@ -19,7 +19,7 @@ iqser-side-nav { .item { margin-bottom: 4px; - border-radius: 20px; + border-radius: var(--iqser-side-nav-item-radius); padding: 9px 16px; cursor: pointer; transition: background-color 0.2s; diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index 05a374d..2861950 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -5,6 +5,20 @@ import { Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { mapEach } from '../../utils'; +interface PaginatedResponse { + data: Interface[]; + page: number; + pageSize: number; + totalHits: number; +} + +interface PaginatedConfig { + readonly options: Options; + readonly page: number; + readonly pageSize: number; + readonly totalHits: number; +} + @Injectable() /** * By default, if no implementation (class) is provided, Class = Interface & IListable @@ -15,11 +29,34 @@ export class PaginatedEntitiesService< Options, PrimaryKey extends Id = Class['id'], > extends EntitiesService { + protected _currentConfig: PaginatedConfig = { options: {} as Options, page: 0, pageSize: 0, totalHits: 0 }; + + get config(): PaginatedConfig { + return this._currentConfig; + } + loadPage(options: Options, page = 0, size = 100): Observable { - return super._post<{ data: Interface[] }>({ page, size, options }).pipe( + return super._post>({ page, size, options }).pipe( + tap( + response => + (this._currentConfig = { + options, + page: response.page, + pageSize: response.pageSize, + totalHits: response.totalHits, + }), + ), map(response => response.data), mapEach(entity => (this._entityClass ? new this._entityClass(entity) : (entity as unknown as Class))), tap((entities: Class[]) => this.setEntities(entities)), ); } + + loadNextPage(): Observable { + return this.loadPage(this._currentConfig.options, this._currentConfig.page + 1, this._currentConfig.pageSize); + } + + loadPreviousPage(): Observable { + return this.loadPage(this._currentConfig.options, this._currentConfig.page - 1, this._currentConfig.pageSize); + } } From ecb5fd63ac0655d369dd201af08622de61d771a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 2 Nov 2023 05:24:09 +0200 Subject: [PATCH 029/201] Paginated entities update --- src/lib/listing/services/paginated-entities.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index 2861950..53c898b 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -35,7 +35,11 @@ export class PaginatedEntitiesService< return this._currentConfig; } - loadPage(options: Options, page = 0, size = 100): Observable { + reloadPage(): Observable { + return this.loadPage(this._currentConfig.options, this._currentConfig.page, this._currentConfig.pageSize); + } + + loadPage(options: Options = {} as Options, page = 0, size = 100): Observable { return super._post>({ page, size, options }).pipe( tap( response => From 052bf3a5d76fa5bb1697a01a532af553c96922b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 2 Nov 2023 21:13:27 +0200 Subject: [PATCH 030/201] Input with action updates --- .../input-with-action/input-with-action.component.html | 3 +-- .../input-with-action/input-with-action.component.scss | 5 +++-- .../inputs/input-with-action/input-with-action.component.ts | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/inputs/input-with-action/input-with-action.component.html b/src/lib/inputs/input-with-action/input-with-action.component.html index 135dec8..ac3e561 100644 --- a/src/lib/inputs/input-with-action/input-with-action.component.html +++ b/src/lib/inputs/input-with-action/input-with-action.component.html @@ -3,6 +3,7 @@ (ngModelChange)="valueChange.emit($event)" [(ngModel)]="value" [autocomplete]="autocomplete" + [disabled]="disabled" [id]="inputId" [ngModelOptions]="{ standalone: true }" [placeholder]="placeholder" @@ -18,7 +19,6 @@ (action)="reset()" *ngIf="isSearch && hasContent" [buttonId]="inputId + '-clear'" - [size]="25" icon="iqser:close" > @@ -28,6 +28,5 @@ [buttonId]="actionButtonId" [disabled]="!hasContent" [icon]="icon!" - [size]="25" > diff --git a/src/lib/inputs/input-with-action/input-with-action.component.scss b/src/lib/inputs/input-with-action/input-with-action.component.scss index f2c22fd..6152c26 100644 --- a/src/lib/inputs/input-with-action/input-with-action.component.scss +++ b/src/lib/inputs/input-with-action/input-with-action.component.scss @@ -9,6 +9,7 @@ mat-icon.disabled { iqser-circle-button { position: absolute; - top: 4px; - right: 5px; + --circle-button-size: calc(var(--iqser-inputs-height) * 0.7) !important; + top: calc((var(--iqser-inputs-height) - var(--circle-button-size)) / 2 - 1px); + right: calc((var(--iqser-inputs-height) - var(--circle-button-size)) / 2 - 1px); } diff --git a/src/lib/inputs/input-with-action/input-with-action.component.ts b/src/lib/inputs/input-with-action/input-with-action.component.ts index cd58098..810ca83 100644 --- a/src/lib/inputs/input-with-action/input-with-action.component.ts +++ b/src/lib/inputs/input-with-action/input-with-action.component.ts @@ -22,6 +22,7 @@ export class InputWithActionComponent { @Input() icon?: string; @Input() autocomplete: 'on' | 'off' = 'on'; @Input() value = ''; + @Input() disabled = false; @Output() readonly action = new EventEmitter(); @Output() readonly valueChange = new EventEmitter(); From 4b656564feac73936431b2615507415af9691298 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 10 Nov 2023 19:12:00 +0200 Subject: [PATCH 031/201] DM-536 - drawn an arrow down in circle button component to look like a dropdown button when it's the case --- .../buttons/circle-button/circle-button.component.html | 2 ++ .../buttons/circle-button/circle-button.component.scss | 8 ++++++++ src/lib/buttons/circle-button/circle-button.component.ts | 1 + 3 files changed, 11 insertions(+) diff --git a/src/lib/buttons/circle-button/circle-button.component.html b/src/lib/buttons/circle-button/circle-button.component.html index 5969c74..747d15d 100644 --- a/src/lib/buttons/circle-button/circle-button.component.html +++ b/src/lib/buttons/circle-button/circle-button.component.html @@ -17,4 +17,6 @@
+ +
diff --git a/src/lib/buttons/circle-button/circle-button.component.scss b/src/lib/buttons/circle-button/circle-button.component.scss index 0320fef..817a2d1 100644 --- a/src/lib/buttons/circle-button/circle-button.component.scss +++ b/src/lib/buttons/circle-button/circle-button.component.scss @@ -1,4 +1,12 @@ :host > div { width: var(--circle-button-size); height: var(--circle-button-size); + + .arrow-down { + border: 5px solid transparent; + border-top-color: black; + position: fixed; + margin-left: 12px; + margin-top: -8px; + } } diff --git a/src/lib/buttons/circle-button/circle-button.component.ts b/src/lib/buttons/circle-button/circle-button.component.ts index 840379a..03dfe97 100644 --- a/src/lib/buttons/circle-button/circle-button.component.ts +++ b/src/lib/buttons/circle-button/circle-button.component.ts @@ -33,6 +33,7 @@ export class CircleButtonComponent implements OnInit { @Input() helpModeButton = false; @Input() removeTooltip = false; @Input() isSubmit = false; + @Input() dropdownButton = false; @Input() size = 34; @Input() iconSize = 14; @Output() readonly action = new EventEmitter(); From 63df71a6aa5a4e3984c2b7807750098b5fe74e87 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 10 Nov 2023 20:06:32 +0200 Subject: [PATCH 032/201] DM-536 - added disabled property --- src/lib/buttons/circle-button/circle-button.component.html | 2 +- src/lib/buttons/circle-button/circle-button.component.scss | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/buttons/circle-button/circle-button.component.html b/src/lib/buttons/circle-button/circle-button.component.html index 747d15d..fd481e3 100644 --- a/src/lib/buttons/circle-button/circle-button.component.html +++ b/src/lib/buttons/circle-button/circle-button.component.html @@ -18,5 +18,5 @@
-
+
diff --git a/src/lib/buttons/circle-button/circle-button.component.scss b/src/lib/buttons/circle-button/circle-button.component.scss index 817a2d1..66ab031 100644 --- a/src/lib/buttons/circle-button/circle-button.component.scss +++ b/src/lib/buttons/circle-button/circle-button.component.scss @@ -8,5 +8,9 @@ position: fixed; margin-left: 12px; margin-top: -8px; + + &.disabled { + border-top-color: var(--iqser-grey-3); + } } } From 8ba271bb6c8fdcbbcd2269458137b91fb3db7ff3 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 13 Nov 2023 13:23:31 +0200 Subject: [PATCH 033/201] RED-6960 - Enter should always confirm a modal window --- src/lib/dialog/iqser-dialog-component.directive.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/dialog/iqser-dialog-component.directive.ts b/src/lib/dialog/iqser-dialog-component.directive.ts index 98c866f..64fb377 100644 --- a/src/lib/dialog/iqser-dialog-component.directive.ts +++ b/src/lib/dialog/iqser-dialog-component.directive.ts @@ -1,7 +1,7 @@ import { Directive, HostListener, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { hasFormChanged, IqserEventTarget } from '../utils'; +import { hasFormChanged } from '../utils'; import { FormGroup } from '@angular/forms'; const DATA_TYPE_SYMBOL = Symbol.for('DATA_TYPE'); @@ -10,7 +10,6 @@ const RETURN_TYPE_SYMBOL = Symbol.for('RETURN_TYPE'); export type DATA_TYPE = typeof DATA_TYPE_SYMBOL; export type RETURN_TYPE = typeof RETURN_TYPE_SYMBOL; -const TARGET_NODE = 'mat-dialog-container'; @Directive() export abstract class IqserDialogComponent { @@ -53,8 +52,7 @@ export abstract class IqserDialogComponent @HostListener('window:keydown.Enter', ['$event']) onEnter(event: KeyboardEvent): void { event?.stopImmediatePropagation(); - const node = (event.target as IqserEventTarget).localName?.trim()?.toLowerCase(); - if (this.onEnterValidator(event) && node === TARGET_NODE) { + if (this.onEnterValidator(event)) { this.close(); } } From 87e1c8845276faa17b2f5d29c9bf4b3601aae0c0 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 13 Nov 2023 13:27:08 +0200 Subject: [PATCH 034/201] lint --- src/lib/dialog/iqser-dialog-component.directive.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/dialog/iqser-dialog-component.directive.ts b/src/lib/dialog/iqser-dialog-component.directive.ts index 64fb377..8757ff8 100644 --- a/src/lib/dialog/iqser-dialog-component.directive.ts +++ b/src/lib/dialog/iqser-dialog-component.directive.ts @@ -10,7 +10,6 @@ const RETURN_TYPE_SYMBOL = Symbol.for('RETURN_TYPE'); export type DATA_TYPE = typeof DATA_TYPE_SYMBOL; export type RETURN_TYPE = typeof RETURN_TYPE_SYMBOL; - @Directive() export abstract class IqserDialogComponent { readonly [DATA_TYPE_SYMBOL]!: DataType; From 2bb459961af80944c5cd56bac8bff1fc786dbebc Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 17 Nov 2023 11:43:32 +0200 Subject: [PATCH 035/201] updates --- src/lib/dialog/base-dialog.component.ts | 36 +++++++++---------- .../confirmation-dialog.component.ts | 13 +++---- src/lib/users/services/iqser-user.service.ts | 23 ++++++------ 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts index b2f058e..eb9ebec 100644 --- a/src/lib/dialog/base-dialog.component.ts +++ b/src/lib/dialog/base-dialog.component.ts @@ -1,14 +1,14 @@ import { AfterViewInit, Directive, HostListener, inject, OnDestroy, signal } from '@angular/core'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { hasFormChanged, IqserEventTarget } from '../utils'; -import { ConfirmOptions } from '.'; -import { ConfirmationDialogService } from './confirmation-dialog.service'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { debounceTime, firstValueFrom, fromEvent, merge, of, Subscription } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { ConfirmOptions } from '.'; +import { IconButtonTypes } from '../buttons'; import { LoadingService } from '../loading'; import { Toaster } from '../services'; -import { IconButtonTypes } from '../buttons'; -import { tap } from 'rxjs/operators'; +import { hasFormChanged, IqserEventTarget } from '../utils'; +import { ConfirmationDialogService } from './confirmation-dialog.service'; const TARGET_NODE = 'mat-dialog-container'; @@ -19,16 +19,21 @@ export interface SaveOptions { @Directive() export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { - readonly iconButtonTypes = IconButtonTypes; - form?: UntypedFormGroup; - initialFormValue!: Record; - protected readonly _formBuilder = inject(UntypedFormBuilder); - protected readonly _loadingService = inject(LoadingService); - protected readonly _toaster = inject(Toaster); - protected readonly _subscriptions: Subscription = new Subscription(); readonly #confirmationDialogService = inject(ConfirmationDialogService); readonly #dialog = inject(MatDialog); readonly #hasErrors = signal(true); + protected readonly _formBuilder = inject(UntypedFormBuilder); + protected readonly _loadingService = inject(LoadingService); + protected readonly _toaster = inject(Toaster); + protected readonly _subscriptions = new Subscription(); + readonly iconButtonTypes = IconButtonTypes; + form?: UntypedFormGroup; + initialFormValue!: Record; + + protected constructor( + protected readonly _dialogRef: MatDialogRef, + private readonly _isInEditMode = false, + ) {} get valid(): boolean { return !this.form || this.form.valid; @@ -42,11 +47,6 @@ export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { return !this.valid || !this.changed || this.#hasErrors(); } - protected constructor( - protected readonly _dialogRef: MatDialogRef, - private readonly _isInEditMode = false, - ) {} - ngAfterViewInit() { this._subscriptions.add(this._dialogRef.backdropClick().subscribe(() => this.close())); const valueChanges = this.form?.valueChanges ?? of(null); diff --git a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts index 0f1c635..694473b 100644 --- a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts +++ b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts @@ -1,12 +1,12 @@ -import { ChangeDetectionStrategy, Component, HostListener, inject, TemplateRef } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { TranslateModule, TranslateService } from '@ngx-translate/core'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { CircleButtonComponent, IconButtonComponent, IconButtonTypes } from '../../buttons'; import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; -import { MatIconModule } from '@angular/material/icon'; +import { ChangeDetectionStrategy, Component, HostListener, inject, TemplateRef } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { CircleButtonComponent, IconButtonComponent, IconButtonTypes } from '../../buttons'; import { ValuesOf } from '../../utils'; export const TitleColors = { @@ -18,6 +18,7 @@ export type TitleColor = ValuesOf; export const ConfirmOptions = { CONFIRM: 1, + // TODO: this should be renamed to CONFIRM_WITH_ACTION SECOND_CONFIRM: 2, DISCARD_CHANGES: 3, } as const; diff --git a/src/lib/users/services/iqser-user.service.ts b/src/lib/users/services/iqser-user.service.ts index 3672efc..76800f1 100644 --- a/src/lib/users/services/iqser-user.service.ts +++ b/src/lib/users/services/iqser-user.service.ts @@ -1,29 +1,29 @@ +import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { KeycloakService } from 'keycloak-angular'; +import { KeycloakProfile } from 'keycloak-js'; import { BehaviorSubject, firstValueFrom, Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; -import { BASE_HREF, List, mapEach } from '../../utils'; -import { QueryParam, Toaster } from '../../services'; +import { IProfile } from '../../../../../red-domain/src'; import { CacheApiService } from '../../caching'; import { EntitiesService } from '../../listing'; -import { IIqserUser } from '../types/user.response'; +import { IqserPermissionsService, IqserRolesService } from '../../permissions'; +import { QueryParam, Toaster } from '../../services'; +import { KeycloakStatusService } from '../../tenants'; +import { BASE_HREF, List, mapEach } from '../../utils'; +import { IqserUser } from '../iqser-user.model'; import { ICreateUserRequest } from '../types/create-user.request'; -import { IResetPasswordRequest } from '../types/reset-password.request'; import { IMyProfileUpdateRequest } from '../types/my-profile-update.request'; import { IProfileUpdateRequest } from '../types/profile-update.request'; -import { KeycloakProfile } from 'keycloak-js'; -import { IqserUser } from '../iqser-user.model'; -import { IqserPermissionsService, IqserRolesService } from '../../permissions'; -import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { KeycloakStatusService } from '../../tenants'; +import { IResetPasswordRequest } from '../types/reset-password.request'; +import { IIqserUser } from '../types/user.response'; @Injectable() export abstract class IqserUserService< Interface extends IIqserUser = IIqserUser, Class extends IqserUser & Interface = IqserUser & Interface, > extends EntitiesService { - readonly currentUser$: Observable; protected abstract readonly _defaultModelPath: string; protected abstract readonly _permissionsFilter: (role: string) => boolean; protected abstract readonly _rolesFilter: (role: string) => boolean; @@ -37,6 +37,7 @@ export abstract class IqserUserService< protected readonly _rolesService = inject(IqserRolesService, { optional: true }); protected readonly _baseHref = inject(BASE_HREF); protected readonly _serviceName: string = 'tenant-user-management'; + readonly currentUser$: Observable; constructor() { super(); From 59fbd1f78fcf10c11bc5d5e6172f9c8b895f4912 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 29 Nov 2023 17:05:59 +0200 Subject: [PATCH 036/201] updates --- .../initials-avatar.component.ts | 22 +++++++++---------- src/lib/users/name.pipe.ts | 14 +++++------- tsconfig.json | 3 +-- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/lib/users/components/initials-avatar/initials-avatar.component.ts b/src/lib/users/components/initials-avatar/initials-avatar.component.ts index 892781e..0e37f98 100644 --- a/src/lib/users/components/initials-avatar/initials-avatar.component.ts +++ b/src/lib/users/components/initials-avatar/initials-avatar.component.ts @@ -1,8 +1,8 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, Input, OnChanges, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { IqserUser } from '../../iqser-user.model'; import { IqserUserService } from '../../services/iqser-user.service'; import { NamePipeOptions } from '../../types/name-pipe-options'; -import { IqserUser } from '../../iqser-user.model'; import { IIqserUser } from '../../types/user.response'; @Component({ @@ -14,20 +14,18 @@ import { IIqserUser } from '../../types/user.response'; export class InitialsAvatarComponent implements OnInit, OnChanges { + readonly #translateService = inject(TranslateService); @Input() color = 'lightgray'; @Input() size: 'small' | 'large' = 'small'; @Input() withName = false; @Input() showYou = false; @Input() tooltipPosition: 'below' | 'above' = 'above'; - @Input() defaultValue: string = this._translateService.instant('initials-avatar.unassigned'); + @Input() defaultValue: string = this.#translateService.instant('initials-avatar.unassigned'); @Input() showTooltip = true; colorClass?: string; namePipeOptions?: NamePipeOptions; - constructor( - private readonly _userService: IqserUserService, - private readonly _translateService: TranslateService, - ) {} + constructor(private readonly _userService: IqserUserService) {} _user?: Class; @@ -45,14 +43,14 @@ export class InitialsAvatarComponent(user: T) => boolean = user => user.isSpecial; ngOnChanges(): void { - if (this._isSystemUser) { + if (this.#isSystemUser) { this.colorClass = 'primary-white primary'; return; } - this.colorClass = this._colorClass; + this.colorClass = this.#colorClass; } ngOnInit(): void { diff --git a/src/lib/users/name.pipe.ts b/src/lib/users/name.pipe.ts index c48cf83..353cf2d 100644 --- a/src/lib/users/name.pipe.ts +++ b/src/lib/users/name.pipe.ts @@ -1,8 +1,8 @@ -import { Pipe, PipeTransform } from '@angular/core'; +import { inject, Pipe, PipeTransform } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { IqserUser } from './iqser-user.model'; import { IqserUserService } from './services/iqser-user.service'; import { NamePipeOptions } from './types/name-pipe-options'; -import { IqserUser } from './iqser-user.model'; function getInitials(name: string) { if (name.toLowerCase() === 'system') { @@ -20,16 +20,14 @@ function getInitials(name: string) { name: 'name', }) export class NamePipe implements PipeTransform { + readonly #translateService = inject(TranslateService); protected readonly _defaultOptions: Required = { - defaultValue: this._translateService.instant('unknown') as string, + defaultValue: this.#translateService.instant('unknown') as string, showYou: false, showInitials: false, }; - constructor( - private readonly _userService: IqserUserService, - private readonly _translateService: TranslateService, - ) {} + constructor(private readonly _userService: IqserUserService) {} transform(value: IqserUser | string, options: NamePipeOptions = this._defaultOptions): string { if (!value || value === 'undefined') { @@ -43,7 +41,7 @@ export class NamePipe implements PipeTransform { } if (options.showYou && this._isCurrentUser(value)) { - name = `${name} (${this._translateService.instant('initials-avatar.you')})`; + name = `${name} (${this.#translateService.instant('initials-avatar.you')})`; } return options.showInitials ? getInitials(name) : name; diff --git a/tsconfig.json b/tsconfig.json index b132006..85a2090 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,6 @@ "noFallthroughCasesInSwitch": true, "noPropertyAccessFromIndexSignature": true, "sourceMap": true, - "downlevelIteration": true, "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, @@ -24,7 +23,7 @@ "@biesbjerg/ngx-translate-extract-marker": ["src/lib/translations/ngx-translate-extract-marker"] } }, - "include": ["./**/*"], + "include": ["./src/**/*"], "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, From d1c0559099be64cb608b87afde6b4ee57de2604f Mon Sep 17 00:00:00 2001 From: George Date: Wed, 29 Nov 2023 17:06:17 +0200 Subject: [PATCH 037/201] RED-3800, refactor multitenancy --- src/lib/listing/listing.module.ts | 2 -- .../table-content.component.html | 4 +-- src/lib/services/api-path.interceptor.ts | 6 ++-- .../services/iqser-user-preference.service.ts | 7 +++-- src/lib/tenants/index.ts | 1 - src/lib/tenants/keycloak-initializer.ts | 5 ++-- .../services/keycloak-status.service.ts | 17 +++++------ .../tenant-select/tenant-select.component.ts | 6 ++-- src/lib/tenants/tenant.pipe.ts | 19 ------------ src/lib/users/services/iqser-user.service.ts | 9 +++--- src/lib/utils/tokens.ts | 29 ++++--------------- 11 files changed, 32 insertions(+), 73 deletions(-) delete mode 100644 src/lib/tenants/tenant.pipe.ts diff --git a/src/lib/listing/listing.module.ts b/src/lib/listing/listing.module.ts index 575ac93..bd16572 100644 --- a/src/lib/listing/listing.module.ts +++ b/src/lib/listing/listing.module.ts @@ -21,7 +21,6 @@ import { CircleButtonComponent, IconButtonComponent } from '../buttons'; import { MatIconModule } from '@angular/material/icon'; import { EmptyStateComponent } from '../empty-state'; import { InputWithActionComponent, RoundCheckboxComponent } from '../inputs'; -import { TenantPipe } from '../tenants/tenant.pipe'; const matModules = [MatTooltipModule, MatIconModule]; const components = [ @@ -51,7 +50,6 @@ const modules = [DragDropModule, TranslateModule, IqserFiltersModule, ScrollingM RoundCheckboxComponent, InputWithActionComponent, SyncWidthDirective, - TenantPipe, ], }) export class IqserListingModule {} diff --git a/src/lib/listing/table-content/table-content.component.html b/src/lib/listing/table-content/table-content.component.html index ea8775c..b086b4d 100644 --- a/src/lib/listing/table-content/table-content.component.html +++ b/src/lib/listing/table-content/table-content.component.html @@ -17,7 +17,7 @@ [class.help-mode-active]="helpModeService?.isHelpModeActive$ | async" [id]="'item-' + entity.id" [ngClass]="getTableItemClasses(entity)" - [routerLink]="entity.routerLink | tenant" + [routerLink]="entity.routerLink" > , next: HttpHandler): Observable> { if (!req.url.startsWith('/assets')) { @@ -15,7 +15,7 @@ export class ApiPathInterceptor implements HttpInterceptor { return next.handle(req.clone({ url: apiUrl })); } - const url = this.#baseHref + req.url; + const url = this.#convertPath(req.url); return next.handle(req.clone({ url })); } } diff --git a/src/lib/services/iqser-user-preference.service.ts b/src/lib/services/iqser-user-preference.service.ts index 104b4cf..8aa443a 100644 --- a/src/lib/services/iqser-user-preference.service.ts +++ b/src/lib/services/iqser-user-preference.service.ts @@ -1,7 +1,8 @@ import { inject, Injectable } from '@angular/core'; import { firstValueFrom } from 'rxjs'; -import { BASE_HREF, List } from '../utils'; +import { List } from '../utils'; import { GenericService } from './generic.service'; +import { APP_BASE_HREF } from '@angular/common'; export type UserAttributes = Record; @@ -12,9 +13,9 @@ const KEYS = { @Injectable() export abstract class IqserUserPreferenceService extends GenericService { - #userAttributes: UserAttributes = {}; protected abstract readonly _devFeaturesEnabledKey: string; protected readonly _serviceName: string = 'tenant-user-management'; + #userAttributes: UserAttributes = {}; get userAttributes(): UserAttributes { return this.#userAttributes; @@ -72,7 +73,7 @@ export abstract class IqserUserPreferenceService extends GenericService 0) { - return window.location.href; + return window.location.href + '/main'; } - const url = window.location.origin + this.#baseHref; + const origin = window.location.origin; if (tenant) { - return url + '/' + tenant; + return origin + '/ui/' + tenant + '/main'; } - return url; + return origin + '/ui'; } } diff --git a/src/lib/tenants/tenant-select/tenant-select.component.ts b/src/lib/tenants/tenant-select/tenant-select.component.ts index 4909b81..5133617 100644 --- a/src/lib/tenants/tenant-select/tenant-select.component.ts +++ b/src/lib/tenants/tenant-select/tenant-select.component.ts @@ -5,10 +5,10 @@ import { KeycloakService } from 'keycloak-angular'; import { NGXLogger } from 'ngx-logger'; import { LoadingService } from '../../loading'; import { getConfig } from '../../services'; -import { BASE_HREF } from '../../utils'; import { getKeycloakOptions } from '../keycloak-initializer'; import { IStoredTenantId, TenantsService } from '../services'; import { KeycloakStatusService } from '../services/keycloak-status.service'; +import { APP_BASE_HREF } from '@angular/common'; @Component({ templateUrl: './tenant-select.component.html', @@ -16,7 +16,8 @@ import { KeycloakStatusService } from '../services/keycloak-status.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class TenantSelectComponent { - protected readonly baseHref = inject(BASE_HREF); + @Input() isLoggedOut = false; + protected readonly baseHref = inject(APP_BASE_HREF); protected readonly logger = inject(NGXLogger); protected readonly tenantsService = inject(TenantsService); protected storedTenants: IStoredTenantId[] = []; @@ -29,7 +30,6 @@ export class TenantSelectComponent { // eslint-disable-next-line @typescript-eslint/unbound-method tenantId: ['', Validators.required], }); - @Input() isLoggedOut = false; constructor() { this.#loadStoredTenants(); diff --git a/src/lib/tenants/tenant.pipe.ts b/src/lib/tenants/tenant.pipe.ts deleted file mode 100644 index 8b581b7..0000000 --- a/src/lib/tenants/tenant.pipe.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { inject, Pipe, PipeTransform } from '@angular/core'; -import { TenantsService } from './services'; - -@Pipe({ - name: 'tenant', - pure: true, - standalone: true, -}) -export class TenantPipe implements PipeTransform { - readonly #tenantsService = inject(TenantsService); - - transform(value: string | string[]): string | undefined { - if (!value) { - return undefined; - } - const _value = Array.isArray(value) ? value.join('/') : value; - return '/' + this.#tenantsService.activeTenantId + _value; - } -} diff --git a/src/lib/users/services/iqser-user.service.ts b/src/lib/users/services/iqser-user.service.ts index 76800f1..2e598fc 100644 --- a/src/lib/users/services/iqser-user.service.ts +++ b/src/lib/users/services/iqser-user.service.ts @@ -5,13 +5,12 @@ import { KeycloakService } from 'keycloak-angular'; import { KeycloakProfile } from 'keycloak-js'; import { BehaviorSubject, firstValueFrom, Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; -import { IProfile } from '../../../../../red-domain/src'; import { CacheApiService } from '../../caching'; import { EntitiesService } from '../../listing'; import { IqserPermissionsService, IqserRolesService } from '../../permissions'; import { QueryParam, Toaster } from '../../services'; import { KeycloakStatusService } from '../../tenants'; -import { BASE_HREF, List, mapEach } from '../../utils'; +import { List, mapEach, UI_ROOT } from '../../utils'; import { IqserUser } from '../iqser-user.model'; import { ICreateUserRequest } from '../types/create-user.request'; import { IMyProfileUpdateRequest } from '../types/my-profile-update.request'; @@ -24,6 +23,7 @@ export abstract class IqserUserService< Interface extends IIqserUser = IIqserUser, Class extends IqserUser & Interface = IqserUser & Interface, > extends EntitiesService { + readonly currentUser$: Observable; protected abstract readonly _defaultModelPath: string; protected abstract readonly _permissionsFilter: (role: string) => boolean; protected abstract readonly _rolesFilter: (role: string) => boolean; @@ -35,9 +35,8 @@ export abstract class IqserUserService< protected readonly _keycloakStatusService = inject(KeycloakStatusService); protected readonly _permissionsService = inject(IqserPermissionsService, { optional: true }); protected readonly _rolesService = inject(IqserRolesService, { optional: true }); - protected readonly _baseHref = inject(BASE_HREF); protected readonly _serviceName: string = 'tenant-user-management'; - readonly currentUser$: Observable; + readonly #uiRoot = inject(UI_ROOT); constructor() { super(); @@ -63,7 +62,7 @@ export abstract class IqserUserService< try { await this._keycloakService.loadUserProfile(true); await this._cacheApiService.wipeCaches(); - const redirectUri = window.location.origin + this._baseHref + '/?isLoggedOut=true'; + const redirectUri = window.location.origin + this.#uiRoot + '/?isLoggedOut=true'; await this._keycloakService.logout(redirectUri); } catch (e) { console.log('Logout failed: ', e); diff --git a/src/lib/utils/tokens.ts b/src/lib/utils/tokens.ts index a7cef86..cf59db3 100644 --- a/src/lib/utils/tokens.ts +++ b/src/lib/utils/tokens.ts @@ -1,35 +1,16 @@ import { inject, InjectionToken } from '@angular/core'; -import { PlatformLocation } from '@angular/common'; -export const BASE_HREF = new InjectionToken('BASE_HREF', { +export const UI_ROOT = new InjectionToken('UI path root - different from BASE_HREF'); +export const UI_ROOT_PATH_FN = new InjectionToken<(path: string) => string>('Append UI root to path', { factory: () => { - const baseUrl = inject(PlatformLocation).getBaseHrefFromDOM(); - if (!baseUrl) { - return ''; - } - - if (baseUrl[baseUrl.length - 1] === '/') { - return baseUrl.substring(0, baseUrl.length - 1); - } - - console.log('Base URL:', baseUrl); - - return baseUrl; - }, -}); - -export type BaseHrefFn = (path: string) => string; - -export const BASE_HREF_FN = new InjectionToken('Convert path function', { - factory: () => { - const baseUrl = inject(BASE_HREF); + const root = inject(UI_ROOT); return (path: string) => { if (path[0] === '/') { - return baseUrl + path; + return root + path; } - return baseUrl + '/' + path; + return root + '/' + path; }; }, }); From fa574115aa9074c0bf0856780d4ec79c525a0989 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 29 Nov 2023 17:26:43 +0200 Subject: [PATCH 038/201] update path --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 85a2090..6345cec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,7 @@ "lib": ["ES2022", "dom"], "allowSyntheticDefaultImports": true, "paths": { - "@biesbjerg/ngx-translate-extract-marker": ["src/lib/translations/ngx-translate-extract-marker"] + "@biesbjerg/ngx-translate-extract-marker": ["./src/lib/translations/ngx-translate-extract-marker"] } }, "include": ["./src/**/*"], From 986d552e49dc4db2973d722d7e8849733748578d Mon Sep 17 00:00:00 2001 From: George Date: Mon, 4 Dec 2023 10:57:35 +0200 Subject: [PATCH 039/201] RED-3800 - add get tenant function token. --- .../services/permissions-guard.service.ts | 8 +++----- src/lib/users/guards/iqser-role-guard.service.ts | 7 +++---- src/lib/utils/tokens.ts | 13 +++++++++++++ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/lib/permissions/services/permissions-guard.service.ts b/src/lib/permissions/services/permissions-guard.service.ts index c02444d..95d3738 100644 --- a/src/lib/permissions/services/permissions-guard.service.ts +++ b/src/lib/permissions/services/permissions-guard.service.ts @@ -17,7 +17,6 @@ import { IqserPermissionsService } from './permissions.service'; import { IqserRolesService } from './roles.service'; import { isArray, isFunction, isRedirectWithParameters, isString, transformPermission } from '../utils'; import { List } from '../../utils'; -import { TenantsService } from '../../tenants'; import { NGXLogger } from 'ngx-logger'; export interface IqserPermissionsData { @@ -31,7 +30,6 @@ export interface IqserPermissionsData { export class IqserPermissionsGuard implements CanActivate, CanMatch, CanActivateChild { constructor( private readonly _permissionsService: IqserPermissionsService, - private readonly _tenantsService: TenantsService, private readonly _rolesService: IqserRolesService, private readonly _router: Router, private readonly _logger: NGXLogger, @@ -93,7 +91,7 @@ export class IqserPermissionsGuard implements CanActivate, CanMatch, CanActivate ' with extras ', navigationExtras, ); - return this._router.navigate([this._tenantsService.activeTenantId, ...navigationCommands], navigationExtras); + return this._router.navigate([...navigationCommands], navigationExtras); } if (Array.isArray(_redirectTo)) { @@ -105,7 +103,7 @@ export class IqserPermissionsGuard implements CanActivate, CanMatch, CanActivate '. Redirecting to ', _redirectTo, ); - return this._router.navigate([this._tenantsService.activeTenantId, ..._redirectTo]); + return this._router.navigate([..._redirectTo]); } this._logger.warn( @@ -116,7 +114,7 @@ export class IqserPermissionsGuard implements CanActivate, CanMatch, CanActivate '. Redirecting to ', _redirectTo, ); - return this._router.navigate([`${this._tenantsService.activeTenantId}${_redirectTo}`]); + return this._router.navigate([_redirectTo]); } #checkRedirect(permissions: IqserPermissionsData, route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) { diff --git a/src/lib/users/guards/iqser-role-guard.service.ts b/src/lib/users/guards/iqser-role-guard.service.ts index 0c071bc..7879b89 100644 --- a/src/lib/users/guards/iqser-role-guard.service.ts +++ b/src/lib/users/guards/iqser-role-guard.service.ts @@ -2,19 +2,18 @@ import { inject, Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { LoadingService } from '../../loading'; import { IqserUserService } from '../services/iqser-user.service'; -import { TenantsService } from '../../tenants'; @Injectable() export class IqserRoleGuard implements CanActivate { protected readonly _router = inject(Router); - protected readonly _tenantsService = inject(TenantsService); protected readonly _loadingService = inject(LoadingService); protected readonly _userService = inject(IqserUserService); - async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async canActivate(route: ActivatedRouteSnapshot, _: RouterStateSnapshot) { const currentUser = this._userService.currentUser; if (!currentUser || !currentUser.hasAnyRole) { - await this._router.navigate([`/${this._tenantsService.activeTenantId}/auth-error`]); + await this._router.navigate(['/auth-error']); this._loadingService.stop(); return false; } diff --git a/src/lib/utils/tokens.ts b/src/lib/utils/tokens.ts index cf59db3..5a7ecdf 100644 --- a/src/lib/utils/tokens.ts +++ b/src/lib/utils/tokens.ts @@ -14,3 +14,16 @@ export const UI_ROOT_PATH_FN = new InjectionToken<(path: string) => string>('App }; }, }); + +export const GET_TENANT_FROM_PATH_FN = new InjectionToken<() => string>('Parse tenant from path considering UI root', { + factory: () => { + const root = inject(UI_ROOT); + + return () => { + const pathSegments = location.pathname.split('/').filter(Boolean); + const rootPathIndex = pathSegments.indexOf(root.replace('/', '')); + const tenant = pathSegments[rootPathIndex + 1]; + return tenant ?? ''; + }; + }, +}); From 68dbdfb8e7ade5348f490b660faaab7df9856237 Mon Sep 17 00:00:00 2001 From: George Date: Mon, 4 Dec 2023 14:19:04 +0200 Subject: [PATCH 040/201] RED-8012 - fixed blank page when reloading. --- src/lib/tenants/keycloak-initializer.ts | 7 +++---- src/lib/tenants/tenant-select/tenant-select.component.ts | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib/tenants/keycloak-initializer.ts b/src/lib/tenants/keycloak-initializer.ts index 6cf0d4c..8430f6d 100644 --- a/src/lib/tenants/keycloak-initializer.ts +++ b/src/lib/tenants/keycloak-initializer.ts @@ -1,11 +1,10 @@ -import { IqserAppConfig } from '../utils'; +import { IqserAppConfig, UI_ROOT } from '../utils'; import { KeycloakOptions, KeycloakService } from 'keycloak-angular'; import { KeycloakStatusService } from './services/keycloak-status.service'; import { inject } from '@angular/core'; import { getConfig } from '../services'; import { NGXLogger } from 'ngx-logger'; import { Router } from '@angular/router'; -import { APP_BASE_HREF } from '@angular/common'; export function getKeycloakOptions(baseUrl: string, config: IqserAppConfig, tenant: string): KeycloakOptions { let oauthUrl = config.OAUTH_URL; @@ -49,10 +48,10 @@ export async function keycloakInitializer(tenant: string) { const router = inject(Router); const keycloakService = inject(KeycloakService); const keycloakStatusService = inject(KeycloakStatusService); - const baseHref = inject(APP_BASE_HREF); + const uiRoot = inject(UI_ROOT); const config = getConfig(); - const keycloakOptions = getKeycloakOptions(baseHref, config, tenant); + const keycloakOptions = getKeycloakOptions(uiRoot, config, tenant); try { await keycloakService.init(keycloakOptions); } catch (error) { diff --git a/src/lib/tenants/tenant-select/tenant-select.component.ts b/src/lib/tenants/tenant-select/tenant-select.component.ts index 5133617..4dddadf 100644 --- a/src/lib/tenants/tenant-select/tenant-select.component.ts +++ b/src/lib/tenants/tenant-select/tenant-select.component.ts @@ -8,7 +8,7 @@ import { getConfig } from '../../services'; import { getKeycloakOptions } from '../keycloak-initializer'; import { IStoredTenantId, TenantsService } from '../services'; import { KeycloakStatusService } from '../services/keycloak-status.service'; -import { APP_BASE_HREF } from '@angular/common'; +import { UI_ROOT } from '../../utils'; @Component({ templateUrl: './tenant-select.component.html', @@ -17,7 +17,6 @@ import { APP_BASE_HREF } from '@angular/common'; }) export class TenantSelectComponent { @Input() isLoggedOut = false; - protected readonly baseHref = inject(APP_BASE_HREF); protected readonly logger = inject(NGXLogger); protected readonly tenantsService = inject(TenantsService); protected storedTenants: IStoredTenantId[] = []; @@ -30,6 +29,7 @@ export class TenantSelectComponent { // eslint-disable-next-line @typescript-eslint/unbound-method tenantId: ['', Validators.required], }); + readonly #uiRoot = inject(UI_ROOT); constructor() { this.#loadStoredTenants(); @@ -48,7 +48,7 @@ export class TenantSelectComponent { async select(tenantId: string) { try { this.logger.info('[KEYCLOAK] Initializing keycloak for tenant', tenantId); - await this.keycloakService.init(getKeycloakOptions(this.baseHref, this.config, tenantId)); + await this.keycloakService.init(getKeycloakOptions(this.#uiRoot, this.config, tenantId)); } catch (e) { this.logger.info('[KEYCLOAK] Init failed. Logout'); return this.keycloakService.logout(); From c4bdba4683447f8c107c82eafd71a149e9fa2676 Mon Sep 17 00:00:00 2001 From: George Date: Mon, 4 Dec 2023 15:35:46 +0200 Subject: [PATCH 041/201] RED-3800 - fix tenant switch. --- .../tenants/services/keycloak-status.service.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/lib/tenants/services/keycloak-status.service.ts b/src/lib/tenants/services/keycloak-status.service.ts index 65d01a1..082d6ff 100644 --- a/src/lib/tenants/services/keycloak-status.service.ts +++ b/src/lib/tenants/services/keycloak-status.service.ts @@ -1,16 +1,16 @@ import { inject, Injectable } from '@angular/core'; import { KeycloakService } from 'keycloak-angular'; import { getConfig } from '../../services'; -import { TenantsService } from '../index'; +import { getKeycloakOptions, TenantsService } from '../index'; import { NGXLogger } from 'ngx-logger'; -import { APP_BASE_HREF } from '@angular/common'; +import { UI_ROOT } from '../../utils'; @Injectable({ providedIn: 'root' }) export class KeycloakStatusService { readonly #keycloakService = inject(KeycloakService); readonly #config = getConfig(); readonly #tenantsService = inject(TenantsService); - readonly #baseHref = inject(APP_BASE_HREF); + readonly #uiRoot = inject(UI_ROOT); readonly #logger = inject(NGXLogger); createLoginUrlAndExecute(username?: string | null) { @@ -34,12 +34,15 @@ export class KeycloakStatusService { let redirectUri: string; if (tenantId) { - redirectUri = this.#keycloakService.getKeycloakInstance().createLoginUrl({ + await this.#tenantsService.selectTenant(tenantId); + const newKCInstance = new KeycloakService(); + await newKCInstance.init(getKeycloakOptions(this.#uiRoot, this.#config, tenantId)); + redirectUri = newKCInstance.getKeycloakInstance().createLoginUrl({ redirectUri: this.createLoginUrl(tenantId), idpHint: this.#config.OAUTH_IDP_HINT, }); } else { - redirectUri = window.location.origin + this.#baseHref; + redirectUri = window.location.origin + this.#uiRoot; } await this.#keycloakService.logout(redirectUri); @@ -47,7 +50,7 @@ export class KeycloakStatusService { createLoginUrl(tenant?: string) { if (tenant && window.location.href.indexOf('/' + tenant + '/') > 0) { - return window.location.href + '/main'; + return window.location.href; } const origin = window.location.origin; From 7f09e572488d1836aa24df7a9600f0c657b4a6be Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Thu, 14 Dec 2023 16:38:14 +0200 Subject: [PATCH 042/201] RED-7919: fixed calendar icon alignment. --- src/assets/styles/common-inputs.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/styles/common-inputs.scss b/src/assets/styles/common-inputs.scss index 25b4f44..5f17fe8 100644 --- a/src/assets/styles/common-inputs.scss +++ b/src/assets/styles/common-inputs.scss @@ -248,7 +248,7 @@ iqser-dynamic-input { .mat-datepicker-toggle { position: absolute; right: 0; - bottom: 1px; + bottom: -4px; color: var(--iqser-accent); &.mat-datepicker-toggle-active { From da086cdaa648d1d35a9eb16ee23cb223e1de2316 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 20 Dec 2023 10:32:18 +0200 Subject: [PATCH 043/201] RED-6960 - Enter should always confirm a modal window --- src/lib/dialog/base-dialog.component.ts | 4 ++-- .../confirmation-dialog.component.html | 2 +- .../confirmation-dialog.component.ts | 11 ++++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts index eb9ebec..c1cb094 100644 --- a/src/lib/dialog/base-dialog.component.ts +++ b/src/lib/dialog/base-dialog.component.ts @@ -84,9 +84,9 @@ export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { @HostListener('window:keydown.Enter', ['$event']) onEnter(event: KeyboardEvent): void { - event?.stopImmediatePropagation(); const node = (event.target as IqserEventTarget).localName?.trim()?.toLowerCase(); - if (this.valid && !this.disabled && this.changed && node === TARGET_NODE) { + if (this.valid && !this.disabled && this.changed && node === TARGET_NODE && this.#dialog.openDialogs.length === 1) { + event?.stopImmediatePropagation(); this.save(); } } diff --git a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html index 015944e..1123f7e 100644 --- a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html +++ b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html @@ -37,7 +37,7 @@ > ; export const ConfirmOptions = { CONFIRM: 1, - // TODO: this should be renamed to CONFIRM_WITH_ACTION - SECOND_CONFIRM: 2, + CONFIRM_WITH_ACTION: 2, DISCARD_CHANGES: 3, } as const; @@ -111,7 +110,7 @@ export class ConfirmationDialogComponent { get confirmOption(): ConfirmOption { if (!this.config.checkboxesValidation && this.config.checkboxes[0]?.value) { - return ConfirmOptions.SECOND_CONFIRM; + return ConfirmOptions.CONFIRM_WITH_ACTION; } return ConfirmOptions.CONFIRM; } @@ -156,4 +155,10 @@ export class ConfirmationDialogComponent { Object.assign(obj, { [key]: value }); }); } + + @HostListener('window:keydown.Enter', ['$event']) + onEnter(event: KeyboardEvent): void { + event?.stopImmediatePropagation(); + this.confirm(ConfirmOptions.CONFIRM); + } } From bd77067dc7a10bad1b21a3e0807f4ea6413da4ad Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Fri, 5 Jan 2024 13:06:49 +0200 Subject: [PATCH 044/201] RED-8164: fixed newly created users cannot login. --- src/lib/users/guards/roles.guard.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/users/guards/roles.guard.ts b/src/lib/users/guards/roles.guard.ts index 3b6a75e..68ca869 100644 --- a/src/lib/users/guards/roles.guard.ts +++ b/src/lib/users/guards/roles.guard.ts @@ -1,16 +1,14 @@ import { Router } from '@angular/router'; import { inject } from '@angular/core'; import { IqserUserService } from '../services/iqser-user.service'; -import { TenantsService } from '../../tenants'; import { AsyncGuard } from '../../services'; export function doesNotHaveAnyRole(): AsyncGuard { return async () => { const router = inject(Router); - const activeTenantId = inject(TenantsService).activeTenantId; const user = await inject(IqserUserService).loadCurrentUser(); if (user?.hasAnyRole) { - await router.navigate([`/${activeTenantId}/main`]); + await router.navigate(['main']); return false; } return true; @@ -20,10 +18,10 @@ export function doesNotHaveAnyRole(): AsyncGuard { export function hasAnyRole(): AsyncGuard { return async () => { const router = inject(Router); - const activeTenantId = inject(TenantsService).activeTenantId; const user = await inject(IqserUserService).loadCurrentUser(); + if (!user?.hasAnyRole) { - await router.navigate([`/${activeTenantId}/auth-error`]); + await router.navigate(['auth-error']); return false; } return true; From 9ce0ec27caee5d7d6fc3d48af5b5653e19f5a120 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Wed, 17 Jan 2024 10:41:36 +0200 Subject: [PATCH 045/201] RED-8109: decreased focus shadow size. --- src/assets/styles/common-inputs.scss | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/assets/styles/common-inputs.scss b/src/assets/styles/common-inputs.scss index 5f17fe8..081d7b0 100644 --- a/src/assets/styles/common-inputs.scss +++ b/src/assets/styles/common-inputs.scss @@ -265,6 +265,17 @@ iqser-dynamic-input { width: 14px; } + button.cdk-focused, button:hover { + span { + &::before { + width: 32px; + height: 32px; + top: 10px; + left: 8px; + } + } + } + .mat-mdc-icon-button svg { width: unset; height: unset; From 773171f4afce02892fc609c4594fde26d30500b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 31 Jan 2024 11:40:09 +0200 Subject: [PATCH 046/201] Get tenant details --- src/lib/tenants/index.ts | 1 + src/lib/tenants/services/tenants.service.ts | 11 ++++++++++- src/lib/tenants/types/index.ts | 1 + src/lib/tenants/types/tenant.ts | 7 +++++++ 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/lib/tenants/types/index.ts create mode 100644 src/lib/tenants/types/tenant.ts diff --git a/src/lib/tenants/index.ts b/src/lib/tenants/index.ts index 8119f9a..e3064d8 100644 --- a/src/lib/tenants/index.ts +++ b/src/lib/tenants/index.ts @@ -3,3 +3,4 @@ export * from './services'; export * from './tenants.module'; export * from './tenant-select/tenant-select.component'; export * from './services/keycloak-status.service'; +export * from './types'; diff --git a/src/lib/tenants/services/tenants.service.ts b/src/lib/tenants/services/tenants.service.ts index 5114aa0..7fcf40e 100644 --- a/src/lib/tenants/services/tenants.service.ts +++ b/src/lib/tenants/services/tenants.service.ts @@ -2,6 +2,9 @@ import { inject, Injectable, signal } from '@angular/core'; import dayjs from 'dayjs'; import { NGXLogger } from 'ngx-logger'; import { List } from '../../utils'; +import { GenericService } from '../../services'; +import { Tenant } from '../types'; +import { Observable } from 'rxjs'; export interface IStoredTenantId { readonly tenantId: string; @@ -13,7 +16,7 @@ export type StoredTenantIds = List; const STORED_TENANTS_KEY = 'red-stored-tenants'; @Injectable({ providedIn: 'root' }) -export class TenantsService { +export class TenantsService extends GenericService { readonly #logger = inject(NGXLogger); readonly #storageReference: Storage = { length: localStorage.length, @@ -24,6 +27,8 @@ export class TenantsService { key: localStorage.key.bind(localStorage), }; readonly #activeTenantId = signal(''); + + protected readonly _defaultModelPath = 'tenants'; protected readonly _serviceName: string = 'tenant-user-management'; get activeTenantId() { @@ -96,6 +101,10 @@ export class TenantsService { this.#logger.info('[TENANTS] Stored tenants at logout: ', storedTenants); } + getActiveTenant>(): Observable> { + return this._getOne([this.activeTenantId]); + } + #setActiveTenantId(tenantId: string) { this.#logger.info('[TENANTS] Set current tenant id: ', tenantId); this.#activeTenantId.set(tenantId); diff --git a/src/lib/tenants/types/index.ts b/src/lib/tenants/types/index.ts new file mode 100644 index 0000000..94a1c2b --- /dev/null +++ b/src/lib/tenants/types/index.ts @@ -0,0 +1 @@ +export * from './tenant'; diff --git a/src/lib/tenants/types/tenant.ts b/src/lib/tenants/types/tenant.ts new file mode 100644 index 0000000..756e100 --- /dev/null +++ b/src/lib/tenants/types/tenant.ts @@ -0,0 +1,7 @@ +export type TenantDetails = Record; + +export interface Tenant { + tenantId: string; + displayName: string; + details: TD; +} From 6d97f7588ba8cbd46b94152094144e3ba8c81f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 31 Jan 2024 12:46:51 +0200 Subject: [PATCH 047/201] Fix --- src/lib/tenants/services/tenants.service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/tenants/services/tenants.service.ts b/src/lib/tenants/services/tenants.service.ts index 7fcf40e..bc5908b 100644 --- a/src/lib/tenants/services/tenants.service.ts +++ b/src/lib/tenants/services/tenants.service.ts @@ -3,7 +3,7 @@ import dayjs from 'dayjs'; import { NGXLogger } from 'ngx-logger'; import { List } from '../../utils'; import { GenericService } from '../../services'; -import { Tenant } from '../types'; +import { Tenant, TenantDetails } from '../types'; import { Observable } from 'rxjs'; export interface IStoredTenantId { @@ -27,7 +27,6 @@ export class TenantsService extends GenericService { key: localStorage.key.bind(localStorage), }; readonly #activeTenantId = signal(''); - protected readonly _defaultModelPath = 'tenants'; protected readonly _serviceName: string = 'tenant-user-management'; @@ -35,7 +34,7 @@ export class TenantsService extends GenericService { return this.#activeTenantId(); } - async selectTenant(tenantId: string) { + async selectTenant(tenantId: string): Promise { this.#mutateStorage(tenantId); this.#setActiveTenantId(tenantId); return true; @@ -101,7 +100,7 @@ export class TenantsService extends GenericService { this.#logger.info('[TENANTS] Stored tenants at logout: ', storedTenants); } - getActiveTenant>(): Observable> { + getActiveTenant(): Observable> { return this._getOne([this.activeTenantId]); } From 3f23c6d66eb8d9610f54b15369b3e002721d0b45 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 31 Jan 2024 11:51:49 +0100 Subject: [PATCH 048/201] move if not logged in guard --- src/lib/tenants/guards/guards-utils.ts | 5 +++ .../tenants/guards/if-not-logged-in.guard.ts | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/lib/tenants/guards/guards-utils.ts create mode 100644 src/lib/tenants/guards/if-not-logged-in.guard.ts diff --git a/src/lib/tenants/guards/guards-utils.ts b/src/lib/tenants/guards/guards-utils.ts new file mode 100644 index 0000000..250ecba --- /dev/null +++ b/src/lib/tenants/guards/guards-utils.ts @@ -0,0 +1,5 @@ +export function getRouteTenant() { + const pathParams = location.pathname.split('/').filter(Boolean); + const uiPathIndex = pathParams.indexOf('ui'); + return pathParams[uiPathIndex + 1]; +} diff --git a/src/lib/tenants/guards/if-not-logged-in.guard.ts b/src/lib/tenants/guards/if-not-logged-in.guard.ts new file mode 100644 index 0000000..280daa0 --- /dev/null +++ b/src/lib/tenants/guards/if-not-logged-in.guard.ts @@ -0,0 +1,36 @@ +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { KeycloakService } from 'keycloak-angular'; +import { NGXLogger } from 'ngx-logger'; +import { getRouteTenant } from './guards-utils'; + +export function ifNotLoggedIn(): CanActivateFn { + return async () => { + const logger = inject(NGXLogger); + const router = inject(Router); + const keycloakService = inject(KeycloakService); + if (!keycloakService.getKeycloakInstance()) { + const tenant = getRouteTenant(); + if (tenant) { + logger.warn('[ROUTES] Tenant ' + tenant + ' found in route, redirecting to /main'); + await router.navigate(['main']); + return false; + } + } + + if (!keycloakService.isLoggedIn()) { + logger.info('[ROUTES] Not logged in, continuing to selected route'); + return true; + } + + const tenant = keycloakService.getKeycloakInstance().realm; + if (!tenant) { + logger.error('[ROUTES] Tenant not found in route or keycloak realm'); + return false; + } + + logger.warn('[ROUTES] Is logged in for ' + tenant + ', redirecting to /' + tenant); + await router.navigate(['/main']); + return false; + }; +} From 03d4bd5c8baffed7c3cbe5329bad28b4ad4ecb59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 31 Jan 2024 17:05:40 +0200 Subject: [PATCH 049/201] Fixed missing await --- src/lib/tenants/guards/if-not-logged-in.guard.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/tenants/guards/if-not-logged-in.guard.ts b/src/lib/tenants/guards/if-not-logged-in.guard.ts index 280daa0..3e44d3b 100644 --- a/src/lib/tenants/guards/if-not-logged-in.guard.ts +++ b/src/lib/tenants/guards/if-not-logged-in.guard.ts @@ -18,7 +18,9 @@ export function ifNotLoggedIn(): CanActivateFn { } } - if (!keycloakService.isLoggedIn()) { + const isLoggedIn = await keycloakService.isLoggedIn(); + + if (!isLoggedIn) { logger.info('[ROUTES] Not logged in, continuing to selected route'); return true; } From dadafa6117788b241f406ec31bd085925b89966a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 31 Jan 2024 17:49:23 +0200 Subject: [PATCH 050/201] Nevermind --- src/lib/tenants/guards/if-not-logged-in.guard.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/tenants/guards/if-not-logged-in.guard.ts b/src/lib/tenants/guards/if-not-logged-in.guard.ts index 3e44d3b..280daa0 100644 --- a/src/lib/tenants/guards/if-not-logged-in.guard.ts +++ b/src/lib/tenants/guards/if-not-logged-in.guard.ts @@ -18,9 +18,7 @@ export function ifNotLoggedIn(): CanActivateFn { } } - const isLoggedIn = await keycloakService.isLoggedIn(); - - if (!isLoggedIn) { + if (!keycloakService.isLoggedIn()) { logger.info('[ROUTES] Not logged in, continuing to selected route'); return true; } From 6dbc7a46214e7d356d19af6eab3a745319cb06fc Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Thu, 1 Feb 2024 12:10:24 +0200 Subject: [PATCH 051/201] RED-8249: fixed the blurry logo in safari. --- src/assets/styles/common-base-screen.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/styles/common-base-screen.scss b/src/assets/styles/common-base-screen.scss index 474d1a1..a90ebbc 100644 --- a/src/assets/styles/common-base-screen.scss +++ b/src/assets/styles/common-base-screen.scss @@ -49,7 +49,7 @@ font-family: var(--iqser-app-name-font-family); font-size: var(--iqser-app-name-font-size); color: var(--iqser-app-name-color); - font-weight: 800; + font-weight: normal; white-space: nowrap; } From 7c7cdfda51edc8896061a633814cfd36fc1b5a16 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 1 Feb 2024 11:12:03 +0100 Subject: [PATCH 052/201] fix translation key --- src/lib/users/name.pipe.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/users/name.pipe.ts b/src/lib/users/name.pipe.ts index 353cf2d..22e302a 100644 --- a/src/lib/users/name.pipe.ts +++ b/src/lib/users/name.pipe.ts @@ -1,4 +1,5 @@ import { inject, Pipe, PipeTransform } from '@angular/core'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { TranslateService } from '@ngx-translate/core'; import { IqserUser } from './iqser-user.model'; import { IqserUserService } from './services/iqser-user.service'; @@ -41,7 +42,7 @@ export class NamePipe implements PipeTransform { } if (options.showYou && this._isCurrentUser(value)) { - name = `${name} (${this.#translateService.instant('initials-avatar.you')})`; + name = `${name} (${this.#translateService.instant(_('initials-avatar.you'))})`; } return options.showInitials ? getInitials(name) : name; From 37c9d7a65d86cc5262e257a9ece09ab31181fcaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Sun, 4 Feb 2024 10:37:23 +0200 Subject: [PATCH 053/201] Moved some icons to common-ui --- src/assets/icons/resize.svg | 26 ++++++++++++++++++++++++++ src/assets/icons/thumb-down.svg | 9 +++++++++ src/assets/icons/thumb-up.svg | 9 +++++++++ src/lib/utils/constants.ts | 3 +++ 4 files changed, 47 insertions(+) create mode 100644 src/assets/icons/resize.svg create mode 100644 src/assets/icons/thumb-down.svg create mode 100644 src/assets/icons/thumb-up.svg diff --git a/src/assets/icons/resize.svg b/src/assets/icons/resize.svg new file mode 100644 index 0000000..d0bd1e4 --- /dev/null +++ b/src/assets/icons/resize.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icons/thumb-down.svg b/src/assets/icons/thumb-down.svg new file mode 100644 index 0000000..30dc2d8 --- /dev/null +++ b/src/assets/icons/thumb-down.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/assets/icons/thumb-up.svg b/src/assets/icons/thumb-up.svg new file mode 100644 index 0000000..bcbc3ae --- /dev/null +++ b/src/assets/icons/thumb-up.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/lib/utils/constants.ts b/src/lib/utils/constants.ts index 1fb4ef8..2568bc4 100644 --- a/src/lib/utils/constants.ts +++ b/src/lib/utils/constants.ts @@ -31,12 +31,15 @@ export const ICONS = new Set([ 'radio-indeterminate', 'radio-selected', 'refresh', + 'resize', 'search', 'settings', 'sort-asc', 'sort-desc', 'status-collapse', 'status-expand', + 'thumb-up', + 'thumb-down', 'trash', 'upload', ]); From b4007f3fe5ff4b82dfad5f4aa83298889fd12570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Sun, 4 Feb 2024 10:38:15 +0200 Subject: [PATCH 054/201] RED-3800: Lint --- src/assets/styles/common-inputs.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/styles/common-inputs.scss b/src/assets/styles/common-inputs.scss index 081d7b0..55dac03 100644 --- a/src/assets/styles/common-inputs.scss +++ b/src/assets/styles/common-inputs.scss @@ -265,7 +265,8 @@ iqser-dynamic-input { width: 14px; } - button.cdk-focused, button:hover { + button.cdk-focused, + button:hover { span { &::before { width: 32px; From 0885ad776d4ca9a45de3067e3cd139b264c37cbf Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 6 Feb 2024 20:13:52 +0200 Subject: [PATCH 055/201] RED-6960 - Enter should confirm a modal window by default --- src/lib/dialog/base-dialog.component.ts | 16 +++++++++++++--- .../dialog/iqser-dialog-component.directive.ts | 12 +++++++++--- src/lib/utils/types/events.type.ts | 1 + 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts index c1cb094..e89d880 100644 --- a/src/lib/dialog/base-dialog.component.ts +++ b/src/lib/dialog/base-dialog.component.ts @@ -10,7 +10,8 @@ import { Toaster } from '../services'; import { hasFormChanged, IqserEventTarget } from '../utils'; import { ConfirmationDialogService } from './confirmation-dialog.service'; -const TARGET_NODE = 'mat-dialog-container'; +const DIALOG_CONTAINER = 'mat-dialog-container'; +const TEXT_INPUT = 'text'; export interface SaveOptions { closeAfterSave?: boolean; @@ -84,8 +85,17 @@ export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { @HostListener('window:keydown.Enter', ['$event']) onEnter(event: KeyboardEvent): void { - const node = (event.target as IqserEventTarget).localName?.trim()?.toLowerCase(); - if (this.valid && !this.disabled && this.changed && node === TARGET_NODE && this.#dialog.openDialogs.length === 1) { + const target = event.target as IqserEventTarget; + const isDialogSelected = target.localName?.trim()?.toLowerCase() === DIALOG_CONTAINER; + const isTextInputSelected = target.type?.trim()?.toLowerCase() === TEXT_INPUT; + + if ( + this.valid && + !this.disabled && + this.changed && + this.#dialog.openDialogs.length === 1 && + (isDialogSelected || isTextInputSelected) + ) { event?.stopImmediatePropagation(); this.save(); } diff --git a/src/lib/dialog/iqser-dialog-component.directive.ts b/src/lib/dialog/iqser-dialog-component.directive.ts index 8757ff8..9a2e4f2 100644 --- a/src/lib/dialog/iqser-dialog-component.directive.ts +++ b/src/lib/dialog/iqser-dialog-component.directive.ts @@ -1,9 +1,10 @@ import { Directive, HostListener, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { hasFormChanged } from '../utils'; +import { hasFormChanged, IqserEventTarget } from '../utils'; import { FormGroup } from '@angular/forms'; +const DIALOG_CONTAINER = 'mat-dialog-container'; const DATA_TYPE_SYMBOL = Symbol.for('DATA_TYPE'); const RETURN_TYPE_SYMBOL = Symbol.for('RETURN_TYPE'); @@ -21,7 +22,7 @@ export abstract class IqserDialogComponent readonly form?: FormGroup; initialFormValue: Record = {}; - constructor() { + constructor(private readonly _editMode = false) { this.dialogRef .backdropClick() .pipe(takeUntilDestroyed()) @@ -57,7 +58,12 @@ export abstract class IqserDialogComponent } onEnterValidator(event: KeyboardEvent) { - return this.valid && !this.disabled && this.changed; + const targetElement = (event.target as IqserEventTarget).localName?.trim()?.toLowerCase(); + const canClose = targetElement === DIALOG_CONTAINER && this.valid; + if (this._editMode) { + return canClose && this.changed; + } + return canClose; } close(dialogResult?: ReturnType) { diff --git a/src/lib/utils/types/events.type.ts b/src/lib/utils/types/events.type.ts index 2d5c97d..93dea19 100644 --- a/src/lib/utils/types/events.type.ts +++ b/src/lib/utils/types/events.type.ts @@ -1,3 +1,4 @@ export interface IqserEventTarget extends EventTarget { localName: string; + type: string; } From 67485fdac2f2130b2b0d2e8b729867d9416628cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Sat, 10 Feb 2024 11:13:15 +0200 Subject: [PATCH 056/201] Add loading indicator to orderedAsyncGuards --- src/lib/services/composite-route.guard.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/services/composite-route.guard.ts b/src/lib/services/composite-route.guard.ts index 5f425c9..2204906 100644 --- a/src/lib/services/composite-route.guard.ts +++ b/src/lib/services/composite-route.guard.ts @@ -3,6 +3,7 @@ import { inject, Injectable, InjectionToken, Injector, runInInjectionContext } f import { concatMap, firstValueFrom, from, last, of, takeWhile } from 'rxjs'; import { LoadingService } from '../loading'; import { SkeletonService } from './skeleton.service'; +import { tap } from 'rxjs/operators'; @Injectable({ providedIn: 'root', @@ -68,8 +69,10 @@ export type AsyncGuard = (route: ActivatedRouteSnapshot, state: RouterStateSnaps export function orderedAsyncGuards(guards: Array): CanActivateFn { return (route, state) => { const injector = inject(Injector); + const loadingService = inject(LoadingService); return from(guards).pipe( + tap(() => loadingService.start()), // For each guard, fire canActivate and wait for it // to complete. concatMap(guard => runInInjectionContext(injector, () => guard(route, state))), @@ -78,6 +81,7 @@ export function orderedAsyncGuards(guards: Array): CanActivateFn { takeWhile(value => value === true, /* inclusive */ true), // Return the last guard's result. last(), + tap(() => loadingService.stop()), ); }; } From 42cc494e2f3ff18182198bced1320b40c8b41d5e Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 13 Feb 2024 17:19:09 +0200 Subject: [PATCH 057/201] RED-8161 - Hard to read Document Versions in Read-Only Mode --- src/assets/styles/common-chips.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/assets/styles/common-chips.scss b/src/assets/styles/common-chips.scss index d0a3079..d6fcf12 100644 --- a/src/assets/styles/common-chips.scss +++ b/src/assets/styles/common-chips.scss @@ -39,6 +39,10 @@ mat-chip-listbox { &.mat-mdc-chip-selected { background-color: var(--iqser-btn-bg); + .mdc-evolution-chip__text-label { + color: #212121; + } + .selected-mark { display: block; } From 82b11d2ebad8d49a2b784e830c8da511b1e7dc3e Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 15 Feb 2024 13:25:15 +0200 Subject: [PATCH 058/201] RED-8342 - Component Editor not showing all values for a multi-value-component --- .../editable-input.component.html | 4 ++-- .../editable-input.component.ts | 22 ++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/lib/inputs/editable-input/editable-input.component.html b/src/lib/inputs/editable-input/editable-input.component.html index fe31cc8..22945ee 100644 --- a/src/lib/inputs/editable-input/editable-input.component.html +++ b/src/lib/inputs/editable-input/editable-input.component.html @@ -35,8 +35,8 @@ [placeholder]="placeholder" [id]="id" name="name" - [style.width]="this.parentDimensions.width + 'px'" - [style.height]="this.parentDimensions.height + 'px'" + [style.width]="this.textArea.width + 'px'" + [style.height]="this.textArea.height + 'px'" > diff --git a/src/lib/inputs/editable-input/editable-input.component.ts b/src/lib/inputs/editable-input/editable-input.component.ts index b7deeb9..f21854a 100644 --- a/src/lib/inputs/editable-input/editable-input.component.ts +++ b/src/lib/inputs/editable-input/editable-input.component.ts @@ -24,8 +24,9 @@ export class EditableInputComponent implements OnChanges { @Input() canEdit = true; @Input() buttonsType: CircleButtonType = CircleButtonTypes.default; @Input() helpModeKey: string = ''; + @Input() lastChild = false; @Output() readonly save = new EventEmitter(); - parentDimensions?: { width: number; height: number }; + textAreaSize?: { width: number; height: number }; newValue = ''; private _editing = false; @@ -45,13 +46,24 @@ export class EditableInputComponent implements OnChanges { this.editing = false; } if (changes['parentId']?.currentValue) { - setTimeout(() => { - const parent = document.getElementById(this.parentId as string) as HTMLElement; - this.parentDimensions = { width: parent.offsetWidth - 98, height: parent.offsetHeight - 16 }; - }, 20); + this.setTextAreaSize(); } } + setTextAreaSize() { + setTimeout(() => { + const element = document.getElementById(this.id as string) as HTMLElement; + const parentElement = document.getElementById(this.parentId as string) as HTMLElement; + const width = parentElement.offsetWidth - 98; + let height = (this.lastChild ? parentElement.offsetHeight : element.offsetHeight) - 16; + if (this.lastChild) { + const lastChildIndex = Number(this.id?.split('-')[2]); + height = height - lastChildIndex * element.offsetHeight; + } + this.textAreaSize = { width, height }; + }, 50); + } + saveValue(): void { this.save.emit(this.newValue); this.editing = false; From 6f255d68b49e921ff458a59056c8bad6cede4abd Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 16 Feb 2024 17:48:47 +0200 Subject: [PATCH 059/201] fix var name --- src/lib/inputs/editable-input/editable-input.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/inputs/editable-input/editable-input.component.ts b/src/lib/inputs/editable-input/editable-input.component.ts index f21854a..fbc24f2 100644 --- a/src/lib/inputs/editable-input/editable-input.component.ts +++ b/src/lib/inputs/editable-input/editable-input.component.ts @@ -26,7 +26,7 @@ export class EditableInputComponent implements OnChanges { @Input() helpModeKey: string = ''; @Input() lastChild = false; @Output() readonly save = new EventEmitter(); - textAreaSize?: { width: number; height: number }; + textArea?: { width: number; height: number }; newValue = ''; private _editing = false; @@ -60,7 +60,7 @@ export class EditableInputComponent implements OnChanges { const lastChildIndex = Number(this.id?.split('-')[2]); height = height - lastChildIndex * element.offsetHeight; } - this.textAreaSize = { width, height }; + this.textArea = { width, height }; }, 50); } From 124ecee0bfc46f7477e3f42cf3587e3a7dd67ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 19 Feb 2024 11:19:36 +0200 Subject: [PATCH 060/201] Remove trailing slash from generic service _getOne (if empty path) --- src/lib/services/generic.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/services/generic.service.ts b/src/lib/services/generic.service.ts index 5e1c872..54a9cf7 100644 --- a/src/lib/services/generic.service.ts +++ b/src/lib/services/generic.service.ts @@ -112,7 +112,7 @@ export abstract class GenericService { protected _getOne(path: List, modelPath = this._defaultModelPath, queryParams?: List): Observable { const entityPath = path.map(item => encodeURIComponent(item)).join('/'); - return this._http.get(`/${this._serviceName}/${encodeURI(modelPath)}/${entityPath}`, { + return this._http.get(`/${this._serviceName}/${encodeURI(modelPath)}/${entityPath}`.replace(/\/+$/, ''), { headers: HeadersConfiguration.getHeaders({ contentType: false }), params: this._queryParams(queryParams), observe: 'body', From a37287f13c708ce5a7386b4144d7db5441358883 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 20 Feb 2024 14:54:23 +0200 Subject: [PATCH 061/201] RED-7874 - Integrate "user has no roles" message in sign-in page --- src/lib/tenants/tenant-select/tenant-select.component.html | 4 ++-- src/lib/tenants/tenant-select/tenant-select.component.ts | 3 +++ src/lib/users/guards/roles.guard.ts | 6 +++--- src/lib/users/services/iqser-user.service.ts | 5 +++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lib/tenants/tenant-select/tenant-select.component.html b/src/lib/tenants/tenant-select/tenant-select.component.html index 2916df5..6a7c697 100644 --- a/src/lib/tenants/tenant-select/tenant-select.component.html +++ b/src/lib/tenants/tenant-select/tenant-select.component.html @@ -8,8 +8,8 @@ - -
+ +
diff --git a/src/lib/tenants/tenant-select/tenant-select.component.ts b/src/lib/tenants/tenant-select/tenant-select.component.ts index 4dddadf..8030209 100644 --- a/src/lib/tenants/tenant-select/tenant-select.component.ts +++ b/src/lib/tenants/tenant-select/tenant-select.component.ts @@ -9,6 +9,7 @@ import { getKeycloakOptions } from '../keycloak-initializer'; import { IStoredTenantId, TenantsService } from '../services'; import { KeycloakStatusService } from '../services/keycloak-status.service'; import { UI_ROOT } from '../../utils'; +import { selectTenantTranslations } from '../../../../../../apps/red-ui/src/app/translations/select-tenant-translations'; @Component({ templateUrl: './tenant-select.component.html', @@ -17,6 +18,7 @@ import { UI_ROOT } from '../../utils'; }) export class TenantSelectComponent { @Input() isLoggedOut = false; + @Input() noRoleLogOut = false; protected readonly logger = inject(NGXLogger); protected readonly tenantsService = inject(TenantsService); protected storedTenants: IStoredTenantId[] = []; @@ -29,6 +31,7 @@ export class TenantSelectComponent { // eslint-disable-next-line @typescript-eslint/unbound-method tenantId: ['', Validators.required], }); + protected readonly translations = selectTenantTranslations; readonly #uiRoot = inject(UI_ROOT); constructor() { diff --git a/src/lib/users/guards/roles.guard.ts b/src/lib/users/guards/roles.guard.ts index 68ca869..fcfda12 100644 --- a/src/lib/users/guards/roles.guard.ts +++ b/src/lib/users/guards/roles.guard.ts @@ -17,11 +17,11 @@ export function doesNotHaveAnyRole(): AsyncGuard { export function hasAnyRole(): AsyncGuard { return async () => { - const router = inject(Router); - const user = await inject(IqserUserService).loadCurrentUser(); + const userService = inject(IqserUserService); + const user = await userService.loadCurrentUser(); if (!user?.hasAnyRole) { - await router.navigate(['auth-error']); + await userService.logout(true); return false; } return true; diff --git a/src/lib/users/services/iqser-user.service.ts b/src/lib/users/services/iqser-user.service.ts index 2e598fc..730ffce 100644 --- a/src/lib/users/services/iqser-user.service.ts +++ b/src/lib/users/services/iqser-user.service.ts @@ -58,11 +58,12 @@ export abstract class IqserUserService< await firstValueFrom(this.loadAll()); } - async logout() { + async logout(noRoleLogOut = false) { try { await this._keycloakService.loadUserProfile(true); await this._cacheApiService.wipeCaches(); - const redirectUri = window.location.origin + this.#uiRoot + '/?isLoggedOut=true'; + const logoutParam = noRoleLogOut ? 'noRoleLogOut' : 'isLoggedOut'; + const redirectUri = window.location.origin + this.#uiRoot + `/?${logoutParam}=true`; await this._keycloakService.logout(redirectUri); } catch (e) { console.log('Logout failed: ', e); From 5d31262fb3d09f02fb1bb7b30c587f689963e85a Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 20 Feb 2024 15:17:22 +0200 Subject: [PATCH 062/201] RED-7874 - moved 'select-tenant-translations' to common-ui --- src/lib/tenants/tenant-select/tenant-select.component.ts | 2 +- src/lib/translations/select-tenant-translations.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/lib/translations/select-tenant-translations.ts diff --git a/src/lib/tenants/tenant-select/tenant-select.component.ts b/src/lib/tenants/tenant-select/tenant-select.component.ts index 8030209..6344a6e 100644 --- a/src/lib/tenants/tenant-select/tenant-select.component.ts +++ b/src/lib/tenants/tenant-select/tenant-select.component.ts @@ -9,7 +9,7 @@ import { getKeycloakOptions } from '../keycloak-initializer'; import { IStoredTenantId, TenantsService } from '../services'; import { KeycloakStatusService } from '../services/keycloak-status.service'; import { UI_ROOT } from '../../utils'; -import { selectTenantTranslations } from '../../../../../../apps/red-ui/src/app/translations/select-tenant-translations'; +import { selectTenantTranslations } from '../../translations/select-tenant-translations'; @Component({ templateUrl: './tenant-select.component.html', diff --git a/src/lib/translations/select-tenant-translations.ts b/src/lib/translations/select-tenant-translations.ts new file mode 100644 index 0000000..506b305 --- /dev/null +++ b/src/lib/translations/select-tenant-translations.ts @@ -0,0 +1,6 @@ +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; + +export const selectTenantTranslations: { [key in string]: string } = { + IS_LOGGED_OUT: _('tenant-resolve.header.youre-logged-out'), + NO_ROLE_LOG_OUT: _('tenant-resolve.header.no-role-log-out'), +} as const; From 59f2dde9ec51c674a1ed50026a3b85dd389b7045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Tue, 20 Feb 2024 20:57:14 +0200 Subject: [PATCH 063/201] Fixed suspicious nullish coalescing --- src/lib/services/iqser-user-preference.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/services/iqser-user-preference.service.ts b/src/lib/services/iqser-user-preference.service.ts index 8aa443a..a841b8a 100644 --- a/src/lib/services/iqser-user-preference.service.ts +++ b/src/lib/services/iqser-user-preference.service.ts @@ -23,7 +23,7 @@ export abstract class IqserUserPreferenceService extends GenericService Date: Tue, 20 Feb 2024 23:30:21 +0200 Subject: [PATCH 064/201] RED-6960 - enter should confirm modal if it is changed or if it is not in edit mode --- src/lib/dialog/base-dialog.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts index e89d880..5fd2b88 100644 --- a/src/lib/dialog/base-dialog.component.ts +++ b/src/lib/dialog/base-dialog.component.ts @@ -92,7 +92,7 @@ export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { if ( this.valid && !this.disabled && - this.changed && + (this.changed || !this._isInEditMode) && this.#dialog.openDialogs.length === 1 && (isDialogSelected || isTextInputSelected) ) { From a2d30f0a652650325eba1c333a2790527d054c08 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Wed, 21 Feb 2024 10:08:02 +0200 Subject: [PATCH 065/201] RED-8565: fixed reset password redirect. --- src/lib/users/services/iqser-user.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/users/services/iqser-user.service.ts b/src/lib/users/services/iqser-user.service.ts index 730ffce..f8a0d8f 100644 --- a/src/lib/users/services/iqser-user.service.ts +++ b/src/lib/users/services/iqser-user.service.ts @@ -76,6 +76,10 @@ export abstract class IqserUserService< this._keycloakStatusService.createLoginUrlAndExecute(); } + async createResetPasswordAction() { + await this._keycloakService.login({ action: 'UPDATE_PASSWORD' }); + } + loadAll() { return this.getAll().pipe( mapEach(user => new this._entityClass(user, user.roles, user.userId)), From 94e0021ed4ec080aec24cf0e963321299513e890 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Wed, 28 Feb 2024 12:56:39 +0200 Subject: [PATCH 066/201] RED-8226: added support for raw toastr messages. --- src/lib/services/toaster.service.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/services/toaster.service.ts b/src/lib/services/toaster.service.ts index bd22d05..75d44fb 100644 --- a/src/lib/services/toaster.service.ts +++ b/src/lib/services/toaster.service.ts @@ -26,6 +26,7 @@ export interface ToasterOptions extends IndividualConfig { */ readonly params?: Record; readonly actions?: ToasterActions[]; + readonly useRaw?: boolean; } export interface ErrorToasterOptions extends ToasterOptions { @@ -86,16 +87,16 @@ export class Toaster { notificationType = NotificationType.INFO, options?: Partial, ): ActiveToast { - const translatedMsg = this._translateService.instant(message, options?.params) as string; + const msg = options?.useRaw ? message : (this._translateService.instant(message, options?.params) as string); switch (notificationType) { case NotificationType.SUCCESS: - return this._toastr.success(translatedMsg, options?.title, options); + return this._toastr.success(msg, options?.title, options); case NotificationType.WARNING: - return this._toastr.warning(translatedMsg, options?.title, options); + return this._toastr.warning(msg, options?.title, options); case NotificationType.INFO: default: - return this._toastr.info(translatedMsg, options?.title, options); + return this._toastr.info(msg, options?.title, options); } } } From 3ea4e45b87b94868370492c475864390d984f65d Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Fri, 1 Mar 2024 17:07:58 +0200 Subject: [PATCH 067/201] RED-8679: added support for custom table item id formats. --- src/lib/listing/listing.module.ts | 2 ++ .../table-content/table-content.component.html | 4 ++-- .../table-content/table-content.component.ts | 2 ++ src/lib/listing/table/table.component.html | 2 ++ src/lib/listing/table/table.component.ts | 2 ++ src/lib/pipes/snake-case.pipe.ts | 14 ++++++++++++++ 6 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 src/lib/pipes/snake-case.pipe.ts diff --git a/src/lib/listing/listing.module.ts b/src/lib/listing/listing.module.ts index bd16572..9192053 100644 --- a/src/lib/listing/listing.module.ts +++ b/src/lib/listing/listing.module.ts @@ -21,6 +21,7 @@ import { CircleButtonComponent, IconButtonComponent } from '../buttons'; import { MatIconModule } from '@angular/material/icon'; import { EmptyStateComponent } from '../empty-state'; import { InputWithActionComponent, RoundCheckboxComponent } from '../inputs'; +import { SnakeCasePipe } from '../pipes/snake-case.pipe'; const matModules = [MatTooltipModule, MatIconModule]; const components = [ @@ -50,6 +51,7 @@ const modules = [DragDropModule, TranslateModule, IqserFiltersModule, ScrollingM RoundCheckboxComponent, InputWithActionComponent, SyncWidthDirective, + SnakeCasePipe, ], }) export class IqserListingModule {} diff --git a/src/lib/listing/table-content/table-content.component.html b/src/lib/listing/table-content/table-content.component.html index b086b4d..114ded8 100644 --- a/src/lib/listing/table-content/table-content.component.html +++ b/src/lib/listing/table-content/table-content.component.html @@ -15,7 +15,7 @@ (mouseleave)="itemMouseLeaveFn && itemMouseLeaveFn(entity)" *ngIf="itemMouseEnterFn || itemMouseLeaveFn; else withoutMouseEvents" [class.help-mode-active]="helpModeService?.isHelpModeActive$ | async" - [id]="'item-' + entity.id" + [id]="rowIdPrefix + '-' + ((entity[namePropertyKey] | snakeCase) ?? entity.id)" [ngClass]="getTableItemClasses(entity)" [routerLink]="entity.routerLink" > @@ -29,7 +29,7 @@
diff --git a/src/lib/listing/table-content/table-content.component.ts b/src/lib/listing/table-content/table-content.component.ts index bd400bf..4056f50 100644 --- a/src/lib/listing/table-content/table-content.component.ts +++ b/src/lib/listing/table-content/table-content.component.ts @@ -23,6 +23,8 @@ export class TableContentComponent, PrimaryK @Input() itemMouseLeaveFn?: (entity: Class) => void; @Input() tableItemClasses?: Record boolean>; @Input() selectionEnabled!: boolean; + @Input() rowIdPrefix: string = 'item'; + @Input() namePropertyKey?: string; readonly trackBy = trackByFactory(); @ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport; diff --git a/src/lib/listing/table/table.component.html b/src/lib/listing/table/table.component.html index 4d430a1..8c77d8b 100644 --- a/src/lib/listing/table/table.component.html +++ b/src/lib/listing/table/table.component.html @@ -30,6 +30,8 @@ [itemSize]="itemSize" [selectionEnabled]="selectionEnabled" [tableItemClasses]="tableItemClasses" + [rowIdPrefix]="rowIdPrefix" + [namePropertyKey]="namePropertyKey" > , PrimaryKey exte @Input() helpModeKey?: string; @Input() headerHelpModeKey?: string; @Input() tableItemClasses?: Record boolean>; + @Input() rowIdPrefix: string = 'item'; + @Input() namePropertyKey?: string; @Input() itemMouseEnterFn?: (entity: Class) => void; @Input() itemMouseLeaveFn?: (entity: Class) => void; @Output() readonly noDataAction = new EventEmitter(); diff --git a/src/lib/pipes/snake-case.pipe.ts b/src/lib/pipes/snake-case.pipe.ts new file mode 100644 index 0000000..b2c247d --- /dev/null +++ b/src/lib/pipes/snake-case.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'snakeCase', + standalone: true, +}) +export class SnakeCasePipe implements PipeTransform { + transform(value: string): string | undefined { + if (!value) { + return undefined; + } + return value.toLowerCase().replaceAll(' ', '_'); + } +} From bf628b33a2ca095cb100288a2594b38f3b0f3ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 6 Mar 2024 17:02:44 +0200 Subject: [PATCH 068/201] Paginated entities service: search support --- .../services/paginated-entities.service.ts | 70 +++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index 53c898b..13a37b5 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -19,6 +19,8 @@ interface PaginatedConfig { readonly totalHits: number; } +const DEFAULT_PAGE_SIZE = 100; + @Injectable() /** * By default, if no implementation (class) is provided, Class = Interface & IListable @@ -26,41 +28,65 @@ interface PaginatedConfig { export class PaginatedEntitiesService< Interface, Class extends Interface & IListable, - Options, + SearchOptions, PrimaryKey extends Id = Class['id'], > extends EntitiesService { - protected _currentConfig: PaginatedConfig = { options: {} as Options, page: 0, pageSize: 0, totalHits: 0 }; + protected _currentConfig: PaginatedConfig = { + options: {} as SearchOptions, + page: 0, + pageSize: DEFAULT_PAGE_SIZE, + totalHits: 0, + }; - get config(): PaginatedConfig { + searchQuery = ''; + + get config(): PaginatedConfig { return this._currentConfig; } - reloadPage(): Observable { - return this.loadPage(this._currentConfig.options, this._currentConfig.page, this._currentConfig.pageSize); + constructor() { + super(); + console.log('PaginatedEntitiesService'); } - loadPage(options: Options = {} as Options, page = 0, size = 100): Observable { - return super._post>({ page, size, options }).pipe( - tap( - response => - (this._currentConfig = { - options, - page: response.page, - pageSize: response.pageSize, - totalHits: response.totalHits, - }), - ), - map(response => response.data), - mapEach(entity => (this._entityClass ? new this._entityClass(entity) : (entity as unknown as Class))), - tap((entities: Class[]) => this.setEntities(entities)), - ); + updateSearchOptionsAndReloadPage(options: SearchOptions): Observable { + this._currentConfig = { ...this._currentConfig, options }; + return this.loadPage(); + } + + reloadPage(): Observable { + return this.loadPage(this._currentConfig.page); + } + + loadPage(page = 0, pageSize?: number): Observable { + const options = this._currentConfig.options; + return super + ._post>({ + page, + size: pageSize ?? this._currentConfig.pageSize, + options, + }) + .pipe( + tap( + response => + (this._currentConfig = { + options, + page: response.page, + pageSize: response.pageSize, + totalHits: response.totalHits, + }), + ), + map(response => response.data), + mapEach(entity => (this._entityClass ? new this._entityClass(entity) : (entity as unknown as Class))), + tap((entities: Class[]) => this.setEntities(entities)), + ); } loadNextPage(): Observable { - return this.loadPage(this._currentConfig.options, this._currentConfig.page + 1, this._currentConfig.pageSize); + return this.loadPage(this._currentConfig.page + 1); } loadPreviousPage(): Observable { - return this.loadPage(this._currentConfig.options, this._currentConfig.page - 1, this._currentConfig.pageSize); + return this.loadPage(this._currentConfig.page - 1); } } From 9faca532c61dd143ae934f8095e83dd639a8fefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 6 Mar 2024 17:04:38 +0200 Subject: [PATCH 069/201] Rename, cleanup --- src/lib/listing/services/paginated-entities.service.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index 13a37b5..cf8b16c 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -44,14 +44,9 @@ export class PaginatedEntitiesService< return this._currentConfig; } - constructor() { - super(); - console.log('PaginatedEntitiesService'); - } - - updateSearchOptionsAndReloadPage(options: SearchOptions): Observable { + updateSearchOptionsAndReload(options: SearchOptions): Observable { this._currentConfig = { ...this._currentConfig, options }; - return this.loadPage(); + return this.loadPage(0); } reloadPage(): Observable { From 47e371a41e1904cf5ef61da056d7049d09851cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 6 Mar 2024 17:25:53 +0200 Subject: [PATCH 070/201] Refactor --- .../services/paginated-entities.service.ts | 59 ++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index cf8b16c..43da78d 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -25,7 +25,7 @@ const DEFAULT_PAGE_SIZE = 100; /** * By default, if no implementation (class) is provided, Class = Interface & IListable */ -export class PaginatedEntitiesService< +export abstract class PaginatedEntitiesService< Interface, Class extends Interface & IListable, SearchOptions, @@ -38,50 +38,41 @@ export class PaginatedEntitiesService< totalHits: 0, }; - searchQuery = ''; + protected abstract readonly _searchKey: keyof SearchOptions; + + get searchQuery(): string { + return this._currentConfig.options[this._searchKey] as string; + } get config(): PaginatedConfig { return this._currentConfig; } - updateSearchOptionsAndReload(options: SearchOptions): Observable { - this._currentConfig = { ...this._currentConfig, options }; - return this.loadPage(0); + updateSearchQueryAndReload(value: string): Observable { + return this.loadPage(0, this._currentConfig.pageSize, { [this._searchKey]: value } as SearchOptions); } reloadPage(): Observable { return this.loadPage(this._currentConfig.page); } - loadPage(page = 0, pageSize?: number): Observable { - const options = this._currentConfig.options; - return super - ._post>({ - page, - size: pageSize ?? this._currentConfig.pageSize, - options, - }) - .pipe( - tap( - response => - (this._currentConfig = { - options, - page: response.page, - pageSize: response.pageSize, - totalHits: response.totalHits, - }), - ), - map(response => response.data), - mapEach(entity => (this._entityClass ? new this._entityClass(entity) : (entity as unknown as Class))), - tap((entities: Class[]) => this.setEntities(entities)), - ); - } + loadPage(page = 0, pageSize?: number, searchOptions?: SearchOptions): Observable { + const options = searchOptions ?? this._currentConfig.options; + const size = pageSize ?? this._currentConfig.pageSize; - loadNextPage(): Observable { - return this.loadPage(this._currentConfig.page + 1); - } - - loadPreviousPage(): Observable { - return this.loadPage(this._currentConfig.page - 1); + return super._post>({ page, size, options }).pipe( + tap( + response => + (this._currentConfig = { + options, + page: response.page, + pageSize: response.pageSize, + totalHits: response.totalHits, + }), + ), + map(response => response.data), + mapEach(entity => (this._entityClass ? new this._entityClass(entity) : (entity as unknown as Class))), + tap((entities: Class[]) => this.setEntities(entities)), + ); } } From 9b427b0cac2facb514cb49f20453f77a529a74e5 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 7 Mar 2024 15:57:11 +0200 Subject: [PATCH 071/201] add a devInfo method to toaster service --- src/lib/services/toaster.service.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib/services/toaster.service.ts b/src/lib/services/toaster.service.ts index 75d44fb..55b2bc5 100644 --- a/src/lib/services/toaster.service.ts +++ b/src/lib/services/toaster.service.ts @@ -7,6 +7,7 @@ import { ActiveToast, ToastrService } from 'ngx-toastr'; import { IndividualConfig } from 'ngx-toastr/toastr/toastr-config'; import { filter, tap } from 'rxjs/operators'; import { ErrorMessageService } from './error-message.service'; +import { isIqserDevMode } from './iqser-user-preference.service'; const enum NotificationType { SUCCESS = 'SUCCESS', @@ -36,10 +37,19 @@ export interface ErrorToasterOptions extends ToasterOptions { readonly error?: HttpErrorResponse; } +const defaultDevToastOptions: Partial = { + timeOut: 10000, + easing: 'ease-in-out', + easeTime: 500, + useRaw: true, +}; + @Injectable({ providedIn: 'root', }) export class Toaster { + readonly #isIqserDevMode = isIqserDevMode(); + constructor( router: Router, private readonly _toastr: ToastrService, @@ -74,6 +84,14 @@ export class Toaster { return this.#showToastNotification(message, NotificationType.INFO, options); } + devInfo(message: string): ActiveToast | undefined { + if (!this.#isIqserDevMode) { + return; + } + + return this.info(message, defaultDevToastOptions); + } + success(message: string, options?: Partial): ActiveToast { return this.#showToastNotification(message, NotificationType.SUCCESS, options); } From ecf9c8912e366fdcc0a454d112c0f3252245666a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 7 Mar 2024 18:15:32 +0200 Subject: [PATCH 072/201] Upload file component updates --- src/assets/styles/common-file-upload.scss | 60 +++++++++++++++++++ src/assets/styles/common-styles.scss | 1 + .../upload-file/upload-file.component.html | 42 +++++++------ .../upload-file/upload-file.component.scss | 58 ------------------ src/lib/upload-file/upload-file.component.ts | 13 ++-- 5 files changed, 89 insertions(+), 85 deletions(-) create mode 100644 src/assets/styles/common-file-upload.scss delete mode 100644 src/lib/upload-file/upload-file.component.scss diff --git a/src/assets/styles/common-file-upload.scss b/src/assets/styles/common-file-upload.scss new file mode 100644 index 0000000..b324daf --- /dev/null +++ b/src/assets/styles/common-file-upload.scss @@ -0,0 +1,60 @@ +.iqser-upload-file { + .upload-area, + .file-area { + display: flex; + align-items: center; + border-radius: 8px; + width: 100%; + box-sizing: border-box; + background: var(--iqser-alt-background); + + &.drag-over { + background-color: var(--iqser-file-drop-drag-over); + } + } + + .upload-area { + gap: 16px; + height: 88px; + cursor: pointer; + padding: 0 32px; + + mat-icon, + div { + opacity: 0.5; + transition: 0.1s; + } + + div { + font-size: 16px; + font-weight: 500; + } + } + + .file-area { + gap: 10px; + height: 48px; + + mat-icon:first-child { + opacity: 0.5; + margin-left: 16px; + } + + mat-icon:last-child { + margin-left: auto; + margin-right: 16px; + cursor: pointer; + } + + mat-icon { + transform: scale(0.7); + } + + p { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: 490px; + } + } +} diff --git a/src/assets/styles/common-styles.scss b/src/assets/styles/common-styles.scss index 4e98daa..05d1501 100644 --- a/src/assets/styles/common-styles.scss +++ b/src/assets/styles/common-styles.scss @@ -28,6 +28,7 @@ @use 'common-toggle-button'; @use 'common-tooltips'; @use 'common-file-drop'; +@use 'common-file-upload'; @use 'common-side-nav'; @use 'common-color-picker'; @use 'common-skeleton'; diff --git a/src/lib/upload-file/upload-file.component.html b/src/lib/upload-file/upload-file.component.html index 4dd42fb..ae52c17 100644 --- a/src/lib/upload-file/upload-file.component.html +++ b/src/lib/upload-file/upload-file.component.html @@ -1,24 +1,22 @@ -
-
- +
+ -
-
-
- - -

{{ file.name }}

- - -
- - +
+
+ + +

{{ file.name }}

+ + +
+ + diff --git a/src/lib/upload-file/upload-file.component.scss b/src/lib/upload-file/upload-file.component.scss deleted file mode 100644 index 83d7b43..0000000 --- a/src/lib/upload-file/upload-file.component.scss +++ /dev/null @@ -1,58 +0,0 @@ -.upload-area, -.file-area { - display: flex; - align-items: center; - border-radius: 8px; - width: 100%; - box-sizing: border-box; - background: var(--iqser-alt-background); - - &.drag-over { - background-color: var(--iqser-file-drop-drag-over); - } -} - -.upload-area { - gap: 16px; - height: 88px; - cursor: pointer; - padding: 0 32px; - - mat-icon, - div { - opacity: 0.5; - transition: 0.1s; - } - - div { - font-size: 16px; - font-weight: 500; - } -} - -.file-area { - gap: 10px; - height: 48px; - - mat-icon:first-child { - opacity: 0.5; - margin-left: 16px; - } - - mat-icon:last-child { - margin-left: auto; - margin-right: 16px; - cursor: pointer; - } - - mat-icon { - transform: scale(0.7); - } - - p { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - max-width: 490px; - } -} diff --git a/src/lib/upload-file/upload-file.component.ts b/src/lib/upload-file/upload-file.component.ts index 864b0f7..e021dce 100644 --- a/src/lib/upload-file/upload-file.component.ts +++ b/src/lib/upload-file/upload-file.component.ts @@ -1,9 +1,11 @@ -import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; @Component({ selector: 'iqser-upload-file', templateUrl: './upload-file.component.html', - styleUrls: ['./upload-file.component.scss'], + // eslint-disable-next-line @angular-eslint/no-host-metadata-property + host: { '[class.iqser-upload-file]': 'true' }, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class UploadFileComponent { @ViewChild('attachFileInput', { static: true }) attachFileInput!: ElementRef; @@ -17,12 +19,13 @@ export class UploadFileComponent { this.attachFileInput.nativeElement.click(); } - attachFile(event: any) { - const files = event?.target?.files; + attachFile(event: Event) { + const target = event.target as HTMLInputElement; + const files = target?.files || []; this.file = files[0]; // input field needs to be set as empty in case the same file will be selected second time - event.target.value = ''; + target.value = ''; if (!this.file) { console.error('No file to import!'); From 55dcb40dcd9662897ec52d2a587fe221a991e6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Tue, 12 Mar 2024 22:13:01 +0200 Subject: [PATCH 073/201] Button styles --- src/assets/styles/common-buttons.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/assets/styles/common-buttons.scss b/src/assets/styles/common-buttons.scss index d6360fd..fbf8ee0 100644 --- a/src/assets/styles/common-buttons.scss +++ b/src/assets/styles/common-buttons.scss @@ -66,6 +66,7 @@ iqser-icon-button { --mdc-text-button-label-text-color: var(--iqser-text); padding: 0 14px; + width: 100%; &[disabled] { --mdc-text-button-disabled-label-text-color: rgba(var(--iqser-text-rgb), 0.3); From 292573f3b3766a2e2ffef4be6d6b702d779b259e Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Fri, 15 Mar 2024 15:03:58 +0200 Subject: [PATCH 074/201] RED-8728: made the required asterisk red. --- src/assets/styles/common-inputs.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/styles/common-inputs.scss b/src/assets/styles/common-inputs.scss index 55dac03..59fa46a 100644 --- a/src/assets/styles/common-inputs.scss +++ b/src/assets/styles/common-inputs.scss @@ -231,7 +231,7 @@ iqser-dynamic-input { &.required label:after { content: ' *'; - color: var(--iqser-primary); + color: var(--iqser-red-1); } &.datepicker-wrapper { From d67798bd603dc6fe8100729385ceff68f6902f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 18 Mar 2024 17:22:45 +0200 Subject: [PATCH 075/201] Pagination updates --- src/lib/listing/services/paginated-entities.service.ts | 8 ++++++++ src/lib/pagination/index.ts | 2 ++ src/lib/pagination/pagination-settings.ts | 4 ++++ src/lib/pagination/pagination.component.html | 2 +- src/lib/pagination/pagination.component.ts | 3 ++- 5 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/lib/pagination/index.ts create mode 100644 src/lib/pagination/pagination-settings.ts diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index 43da78d..c9f6011 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -4,6 +4,7 @@ import { EntitiesService } from './entities.service'; import { Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { mapEach } from '../../utils'; +import { PaginationSettings } from '../../pagination'; interface PaginatedResponse { data: Interface[]; @@ -48,6 +49,13 @@ export abstract class PaginatedEntitiesService< return this._currentConfig; } + get paginationSettings(): PaginationSettings { + return { + currentPage: this.config.page || 0, + totalPages: Math.ceil(this._currentConfig.totalHits / this._currentConfig.pageSize), + }; + } + updateSearchQueryAndReload(value: string): Observable { return this.loadPage(0, this._currentConfig.pageSize, { [this._searchKey]: value } as SearchOptions); } diff --git a/src/lib/pagination/index.ts b/src/lib/pagination/index.ts new file mode 100644 index 0000000..bb97fd9 --- /dev/null +++ b/src/lib/pagination/index.ts @@ -0,0 +1,2 @@ +export * from './pagination-settings'; +export * from './pagination.component'; diff --git a/src/lib/pagination/pagination-settings.ts b/src/lib/pagination/pagination-settings.ts new file mode 100644 index 0000000..6f8453c --- /dev/null +++ b/src/lib/pagination/pagination-settings.ts @@ -0,0 +1,4 @@ +export interface PaginationSettings { + currentPage: number; + totalPages: number; +} diff --git a/src/lib/pagination/pagination.component.html b/src/lib/pagination/pagination.component.html index 8a0db3f..1012cbb 100644 --- a/src/lib/pagination/pagination.component.html +++ b/src/lib/pagination/pagination.component.html @@ -20,7 +20,7 @@
diff --git a/src/lib/pagination/pagination.component.ts b/src/lib/pagination/pagination.component.ts index 82ac382..d4a4288 100644 --- a/src/lib/pagination/pagination.component.ts +++ b/src/lib/pagination/pagination.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { NgForOf } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; +import { PaginationSettings } from './pagination-settings'; @Component({ selector: 'iqser-pagination', @@ -27,7 +28,7 @@ export class PaginationComponent { } @Input() - set settings(value: { currentPage: number; totalPages: number }) { + set settings(value: PaginationSettings) { this._currentPage = value.currentPage; this._totalPages = value.totalPages; this._updatePagesArray(); From 48a5160d6fccfe72de2e024e4359a97cbc52c942 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Thu, 21 Mar 2024 17:01:37 +0200 Subject: [PATCH 076/201] RED-8817: added description support for extraOption. --- src/lib/inputs/details-radio/details-radio-option.ts | 1 + src/lib/inputs/details-radio/details-radio.component.html | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/lib/inputs/details-radio/details-radio-option.ts b/src/lib/inputs/details-radio/details-radio-option.ts index d123db7..1d043ad 100644 --- a/src/lib/inputs/details-radio/details-radio-option.ts +++ b/src/lib/inputs/details-radio/details-radio-option.ts @@ -3,6 +3,7 @@ interface ExtraOption { checked: boolean; hidden?: boolean; disabled?: boolean; + description?: string; } export interface DetailsRadioOption { diff --git a/src/lib/inputs/details-radio/details-radio.component.html b/src/lib/inputs/details-radio/details-radio.component.html index aae9694..0e0dfae 100644 --- a/src/lib/inputs/details-radio/details-radio.component.html +++ b/src/lib/inputs/details-radio/details-radio.component.html @@ -27,6 +27,11 @@ > {{ option.extraOption.label | translate }} +
From b6813bccf35d29bea40ca128d0b80670b86d507e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Fri, 22 Mar 2024 00:17:13 +0200 Subject: [PATCH 077/201] Added prevent default to stop propagation directive --- src/lib/directives/stop-propagation.directive.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/directives/stop-propagation.directive.ts b/src/lib/directives/stop-propagation.directive.ts index 211bfb3..51ac9b6 100644 --- a/src/lib/directives/stop-propagation.directive.ts +++ b/src/lib/directives/stop-propagation.directive.ts @@ -20,6 +20,7 @@ export class StopPropagationDirective { if (this.iqserStopPropagation) { this.#logger.info('[CLICK] iqserStopPropagation'); + $event.preventDefault(); $event.stopPropagation(); } } From 0dd210b04f805a862686a0114737946270d0a76e Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Mon, 25 Mar 2024 10:46:12 +0200 Subject: [PATCH 078/201] RED-8817: aligned description with option label. --- src/lib/inputs/details-radio/details-radio.component.html | 2 +- src/lib/inputs/details-radio/details-radio.component.scss | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/inputs/details-radio/details-radio.component.html b/src/lib/inputs/details-radio/details-radio.component.html index 0e0dfae..c39a5c1 100644 --- a/src/lib/inputs/details-radio/details-radio.component.html +++ b/src/lib/inputs/details-radio/details-radio.component.html @@ -30,7 +30,7 @@ diff --git a/src/lib/inputs/details-radio/details-radio.component.scss b/src/lib/inputs/details-radio/details-radio.component.scss index f9945fb..52f7bfa 100644 --- a/src/lib/inputs/details-radio/details-radio.component.scss +++ b/src/lib/inputs/details-radio/details-radio.component.scss @@ -42,6 +42,10 @@ label { color: var(--iqser-primary); } } + + .extra-option-description { + margin-left: 23px; + } } .row { From 957dc404a85626d9072da0b050aa0fbddbb30789 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Tue, 26 Mar 2024 15:52:04 +0200 Subject: [PATCH 079/201] RED-8817: decreased extra option description opacity. --- src/lib/inputs/details-radio/details-radio.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/inputs/details-radio/details-radio.component.scss b/src/lib/inputs/details-radio/details-radio.component.scss index 52f7bfa..760c50d 100644 --- a/src/lib/inputs/details-radio/details-radio.component.scss +++ b/src/lib/inputs/details-radio/details-radio.component.scss @@ -45,6 +45,7 @@ label { .extra-option-description { margin-left: 23px; + opacity: 0.49; } } From e08e28e09519c979acf1882ab865d8732cb8ce9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 4 Apr 2024 17:08:39 +0300 Subject: [PATCH 080/201] Fixed icon button hover ripple color --- src/assets/styles/common-buttons.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/assets/styles/common-buttons.scss b/src/assets/styles/common-buttons.scss index fbf8ee0..2241931 100644 --- a/src/assets/styles/common-buttons.scss +++ b/src/assets/styles/common-buttons.scss @@ -68,6 +68,13 @@ iqser-icon-button { padding: 0 14px; width: 100%; + &:hover:not([disabled]) { + .mat-mdc-button-persistent-ripple::before { + background-color: #000; + opacity: 0.04; + } + } + &[disabled] { --mdc-text-button-disabled-label-text-color: rgba(var(--iqser-text-rgb), 0.3); } From c12b6f4d35d646c7b9b6e9b8ef6559457f05be15 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Fri, 5 Apr 2024 13:07:32 +0300 Subject: [PATCH 081/201] RED-8622: implemented add entity functionality. --- src/lib/listing/services/entities.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/listing/services/entities.service.ts b/src/lib/listing/services/entities.service.ts index 7500a44..216803e 100644 --- a/src/lib/listing/services/entities.service.ts +++ b/src/lib/listing/services/entities.service.ts @@ -61,6 +61,12 @@ export class EntitiesService< return this._entityDeleted$.pipe(filter(entity => entity.id === entityId)); } + addEntity(entity: Class): void { + if (!this.find(entity.id)) { + this._all$.next([...this.all, entity]); + } + } + setEntities(entities: Class[]): void { const changedEntities: Class[] = []; const deletedEntities = this.all.filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id)); From 8a2033740ec93fd8c91e80f94d67e1b662bff121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Sun, 7 Apr 2024 21:32:38 +0300 Subject: [PATCH 082/201] Moved color-picker to common-ui --- src/assets/icons/color-picker.svg | 15 +++++++++++++++ src/lib/utils/constants.ts | 1 + 2 files changed, 16 insertions(+) create mode 100644 src/assets/icons/color-picker.svg diff --git a/src/assets/icons/color-picker.svg b/src/assets/icons/color-picker.svg new file mode 100644 index 0000000..7c1574d --- /dev/null +++ b/src/assets/icons/color-picker.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/src/lib/utils/constants.ts b/src/lib/utils/constants.ts index 2568bc4..fa14c78 100644 --- a/src/lib/utils/constants.ts +++ b/src/lib/utils/constants.ts @@ -7,6 +7,7 @@ export const ICONS = new Set([ 'check', 'close', 'collapse', + 'color-picker', 'copy', 'csv', 'document', From 301ea99abe1be09687cdbe6d0fbae3bec7eefc23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Tue, 9 Apr 2024 00:17:42 +0300 Subject: [PATCH 083/201] CSS updates --- src/assets/styles/common-select.scss | 5 +++-- .../input-with-action/input-with-action.component.scss | 2 +- src/lib/listing/services/paginated-entities.service.ts | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/assets/styles/common-select.scss b/src/assets/styles/common-select.scss index 3aa1012..41b2c05 100644 --- a/src/assets/styles/common-select.scss +++ b/src/assets/styles/common-select.scss @@ -1,9 +1,10 @@ @use 'common-mixins'; .mat-mdc-select { - padding: 0 11px; + padding: 0 calc(var(--iqser-inputs-height) - 25px); box-sizing: border-box; - --mat-select-trigger-text-line-height: 36px; + --mat-select-trigger-text-line-height: var(--iqser-inputs-height); + --mat-select-trigger-text-size: var(--iqser-inputs-font-size); } .mat-mdc-select-panel { diff --git a/src/lib/inputs/input-with-action/input-with-action.component.scss b/src/lib/inputs/input-with-action/input-with-action.component.scss index 6152c26..c09091c 100644 --- a/src/lib/inputs/input-with-action/input-with-action.component.scss +++ b/src/lib/inputs/input-with-action/input-with-action.component.scss @@ -1,5 +1,5 @@ :host { - display: block; + display: contents; } mat-icon.disabled { diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index c9f6011..b2e3146 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -57,7 +57,10 @@ export abstract class PaginatedEntitiesService< } updateSearchQueryAndReload(value: string): Observable { - return this.loadPage(0, this._currentConfig.pageSize, { [this._searchKey]: value } as SearchOptions); + return this.loadPage(0, this._currentConfig.pageSize, { + ...this._currentConfig.options, + [this._searchKey]: value, + } as SearchOptions); } reloadPage(): Observable { From cbd9fd055b2332554837bc78a75fc11eefcba9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 11 Apr 2024 15:14:58 +0300 Subject: [PATCH 084/201] RED-8731: Updated material & fixed toggle appearance --- src/assets/styles/common-toggle.scss | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/assets/styles/common-toggle.scss b/src/assets/styles/common-toggle.scss index 1b48bef..f0292ca 100644 --- a/src/assets/styles/common-toggle.scss +++ b/src/assets/styles/common-toggle.scss @@ -32,9 +32,19 @@ --mdc-switch-track-width: 30px; --mdc-switch-track-height: 16px; --mdc-switch-track-shape: 8px; - --mdc-switch-handle-width: 12px; - --mdc-switch-handle-height: 12px; + --mat-switch-with-icon-handle-size: 12px; --mdc-switch-handle-shape: 6px; + + --mat-switch-unselected-with-icon-handle-horizontal-margin: 0 2px; + --mat-switch-unselected-pressed-handle-horizontal-margin: 0 2px; + + --mat-switch-selected-with-icon-handle-horizontal-margin: 0 6px; + --mat-switch-selected-pressed-handle-horizontal-margin: 0 6px; + --mat-switch-selected-handle-horizontal-margin: 0 6px; + + --mat-switch-unselected-handle-size: 12px; + --mat-switch-selected-handle-size: 12px; + --mat-switch-pressed-handle-size: 12px; } .mdc-form-field > label { @@ -42,17 +52,6 @@ padding-left: 0; } - .mdc-switch__handle { - right: 8px; - left: unset; - } - - .mdc-switch--unselected { - .mdc-switch__handle { - right: 4px; - } - } - .mdc-switch__icons, .mdc-switch__ripple { display: none; From c12ed0c96829b0f95887b74440d7981789edfb53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 11 Apr 2024 15:29:28 +0300 Subject: [PATCH 085/201] Transform "active" boolean attribute in round-checkbox --- src/lib/inputs/round-checkbox/round-checkbox.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/inputs/round-checkbox/round-checkbox.component.ts b/src/lib/inputs/round-checkbox/round-checkbox.component.ts index 8001bdc..f3dc96c 100644 --- a/src/lib/inputs/round-checkbox/round-checkbox.component.ts +++ b/src/lib/inputs/round-checkbox/round-checkbox.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; +import { booleanAttribute, ChangeDetectionStrategy, Component, ElementRef, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { NgIf } from '@angular/common'; @@ -12,7 +12,7 @@ import { NgIf } from '@angular/common'; }) export class RoundCheckboxComponent implements OnInit { @Input() size = 20; - @Input() active = false; + @Input({ transform: booleanAttribute }) active = false; @Input() indeterminate = false; @Input() type: 'default' | 'with-bg' = 'default'; @HostBinding('class.disabled') From 005167487deaa999af6c2faf1646ee6bdf462183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 11 Apr 2024 15:43:33 +0300 Subject: [PATCH 086/201] Enable multiselect checkbox --- src/assets/styles/common-select.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/styles/common-select.scss b/src/assets/styles/common-select.scss index 41b2c05..6689547 100644 --- a/src/assets/styles/common-select.scss +++ b/src/assets/styles/common-select.scss @@ -54,6 +54,6 @@ } } -.mat-mdc-option .mat-mdc-option-pseudo-checkbox { +.mat-mdc-option:not(.mat-mdc-option-multiple) .mat-mdc-option-pseudo-checkbox { display: none; } From fc06bcc31df549e86799051c6d2ce953b9c815ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 11 Apr 2024 18:25:01 +0300 Subject: [PATCH 087/201] Use flex gap instead of margin right in tables --- src/assets/styles/common-tables.scss | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/assets/styles/common-tables.scss b/src/assets/styles/common-tables.scss index d50da06..2f56971 100644 --- a/src/assets/styles/common-tables.scss +++ b/src/assets/styles/common-tables.scss @@ -8,10 +8,7 @@ flex: 1; align-items: center; justify-content: flex-end; - - > *:not(:last-child) { - margin-right: 10px; - } + gap: 10px; } .header-item { @@ -23,6 +20,7 @@ border-bottom: 1px solid var(--iqser-separator); box-sizing: border-box; padding: 0 24px; + gap: 10px; .header-title { display: flex; @@ -34,17 +32,10 @@ padding: 0 24px 0 10px; } - > *:not(:last-child) { - margin-right: 10px; - } - .actions { display: flex; align-items: center; justify-content: flex-end; - - > *:not(:last-child) { - margin-right: 16px; - } + gap: 16px; } } From fae7d912d2aa3133b1f1cda15e01edd7abd01813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 17 Apr 2024 20:32:19 +0300 Subject: [PATCH 088/201] Fixed filter card input padding --- .../filter-card/filter-card.component.html | 16 +++++++++------- .../filter-card/filter-card.component.scss | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib/filtering/filter-card/filter-card.component.html b/src/lib/filtering/filter-card/filter-card.component.html index fae3b07..9b794a5 100644 --- a/src/lib/filtering/filter-card/filter-card.component.html +++ b/src/lib/filtering/filter-card/filter-card.component.html @@ -1,11 +1,13 @@ - +
+ +
diff --git a/src/lib/filtering/filter-card/filter-card.component.scss b/src/lib/filtering/filter-card/filter-card.component.scss index b5caea8..4a73124 100644 --- a/src/lib/filtering/filter-card/filter-card.component.scss +++ b/src/lib/filtering/filter-card/filter-card.component.scss @@ -32,7 +32,7 @@ padding-bottom: 8px; } -iqser-input-with-action { +.input-wrapper { padding: 0 8px 8px 8px; } From 4cf42c894313dc24806fdf71bf478d872a804a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 17 Apr 2024 20:32:51 +0300 Subject: [PATCH 089/201] Fixed filter card input padding --- src/lib/filtering/filter-card/filter-card.component.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/filtering/filter-card/filter-card.component.html b/src/lib/filtering/filter-card/filter-card.component.html index 9b794a5..a0a8f11 100644 --- a/src/lib/filtering/filter-card/filter-card.component.html +++ b/src/lib/filtering/filter-card/filter-card.component.html @@ -1,7 +1,6 @@ -
+
Date: Wed, 17 Apr 2024 21:01:08 +0300 Subject: [PATCH 090/201] Deprecated (close) -> (closed) --- src/lib/filtering/popup-filter/popup-filter.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/filtering/popup-filter/popup-filter.component.html b/src/lib/filtering/popup-filter/popup-filter.component.html index 815b4fe..85ddf2c 100644 --- a/src/lib/filtering/popup-filter/popup-filter.component.html +++ b/src/lib/filtering/popup-filter/popup-filter.component.html @@ -25,7 +25,7 @@ From 696e7f6f6d8c9e3028f6e3c9fff5d0c55dfdf92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 18 Apr 2024 00:22:45 +0300 Subject: [PATCH 091/201] Paginated entities service updates (sorting) --- .../services/paginated-entities.service.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index b2e3146..ff8058a 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -13,7 +13,12 @@ interface PaginatedResponse { totalHits: number; } -interface PaginatedConfig { +export interface SortOption { + sortDirection?: 'ASC' | 'DESC'; + sortProperty?: string; +} + +interface PaginatedConfig extends SortOption { readonly options: Options; readonly page: number; readonly pageSize: number; @@ -67,11 +72,15 @@ export abstract class PaginatedEntitiesService< return this.loadPage(this._currentConfig.page); } - loadPage(page = 0, pageSize?: number, searchOptions?: SearchOptions): Observable { + loadPage(page = 0, pageSize?: number, searchOptions?: SearchOptions, sortOptions?: SortOption): Observable { const options = searchOptions ?? this._currentConfig.options; const size = pageSize ?? this._currentConfig.pageSize; + const sOptions = sortOptions ?? { + sortDirection: this._currentConfig.sortDirection, + sortProperty: this._currentConfig.sortProperty, + }; - return super._post>({ page, size, options }).pipe( + return super._post>({ page, size, options, ...sOptions }).pipe( tap( response => (this._currentConfig = { @@ -79,6 +88,8 @@ export abstract class PaginatedEntitiesService< page: response.page, pageSize: response.pageSize, totalHits: response.totalHits, + sortDirection: sOptions.sortDirection, + sortProperty: sOptions.sortProperty, }), ), map(response => response.data), From e7a04dda9e2a5b41e8d0c2b70a81c0f14a7ec406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 29 Apr 2024 15:18:39 +0300 Subject: [PATCH 092/201] Loading indicator z-index, include ngx-toastr in common styles --- src/assets/styles/common-full-pages.scss | 4 ++-- src/assets/styles/common-styles.scss | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/assets/styles/common-full-pages.scss b/src/assets/styles/common-full-pages.scss index b83f25a..9c081b4 100644 --- a/src/assets/styles/common-full-pages.scss +++ b/src/assets/styles/common-full-pages.scss @@ -7,11 +7,11 @@ .full-page-section { opacity: 0.7; background: var(--iqser-background); - z-index: 900; + z-index: 1001; } .full-page-content { - z-index: 1000; + z-index: 1002; justify-content: center; align-items: center; flex-direction: column; diff --git a/src/assets/styles/common-styles.scss b/src/assets/styles/common-styles.scss index 05d1501..4d8e4af 100644 --- a/src/assets/styles/common-styles.scss +++ b/src/assets/styles/common-styles.scss @@ -1,3 +1,5 @@ +@use 'ngx-toastr/toastr'; + @use 'common-utilities'; @use 'common-inputs'; @use 'common-buttons'; From fb6951a7ba47338a1b89680230691154837dee2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 29 Apr 2024 16:59:35 +0300 Subject: [PATCH 093/201] Some styles, paginated get all ids --- src/assets/styles/_common-variables.scss | 2 + src/assets/styles/common-buttons.scss | 7 +++- src/assets/styles/common-inputs.scss | 4 +- .../chevron-button.component.scss | 2 +- .../services/paginated-entities.service.ts | 38 ++++++++++++++++++- 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/assets/styles/_common-variables.scss b/src/assets/styles/_common-variables.scss index 8007393..0635504 100644 --- a/src/assets/styles/_common-variables.scss +++ b/src/assets/styles/_common-variables.scss @@ -63,6 +63,8 @@ body { --iqser-app-name-font-family: Inter, sans-serif; --iqser-circle-button-radius: 50%; --iqser-side-nav-item-radius: 20px; + --iqser-dot-overlay-background: rgba(var(--iqser-primary-rgb), 0.1); + --iqser-chevron-button-bg: transparent; } $required-variables: 'iqser-primary'; diff --git a/src/assets/styles/common-buttons.scss b/src/assets/styles/common-buttons.scss index 2241931..f2279b0 100644 --- a/src/assets/styles/common-buttons.scss +++ b/src/assets/styles/common-buttons.scss @@ -37,7 +37,7 @@ } .overlay { - background: rgba(var(--iqser-primary-rgb), 0.1); + background: var(--iqser-dot-overlay-background); } } @@ -177,7 +177,6 @@ iqser-circle-button { iqser-chevron-button { @include buttonShape; @include ariaExpanded; - @include dotOverlay; @include labelNoWrap; display: block; @@ -185,10 +184,14 @@ iqser-chevron-button { .mat-mdc-button { @include iconSize14; + background-color: var(--iqser-chevron-button-bg); + &:not([disabled]) { --mdc-text-button-label-text-color: var(--iqser-text); } } + + @include dotOverlay; } iqser-user-button { diff --git a/src/assets/styles/common-inputs.scss b/src/assets/styles/common-inputs.scss index 59fa46a..d465acf 100644 --- a/src/assets/styles/common-inputs.scss +++ b/src/assets/styles/common-inputs.scss @@ -102,7 +102,7 @@ iqser-dynamic-input { } .mat-mdc-form-field { - margin-top: 3px; + margin-top: 0; input { margin-top: 0; @@ -220,7 +220,7 @@ iqser-dynamic-input { font-size: 11px; letter-spacing: 0; line-height: 14px; - margin-bottom: 2px; + margin-bottom: 5px; color: var(--iqser-text); &.mat-checkbox-layout { diff --git a/src/lib/buttons/chevron-button/chevron-button.component.scss b/src/lib/buttons/chevron-button/chevron-button.component.scss index d92b062..1271f66 100644 --- a/src/lib/buttons/chevron-button/chevron-button.component.scss +++ b/src/lib/buttons/chevron-button/chevron-button.component.scss @@ -1,5 +1,5 @@ button { - padding: 0 10px 0 14px; + --mat-text-button-with-icon-horizontal-padding: 10px 0 14px; mat-icon { width: 14px; diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index ff8058a..f348a9b 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Id, IListable } from '../models'; import { EntitiesService } from './entities.service'; -import { Observable } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { mapEach } from '../../utils'; import { PaginationSettings } from '../../pagination'; @@ -97,4 +97,40 @@ export abstract class PaginatedEntitiesService< tap((entities: Class[]) => this.setEntities(entities)), ); } + + async getAllIds(): Promise { + let page = 0; + const options = this._currentConfig.options; + const size = 1000; + const sOptions = { + sortDirection: this._currentConfig.sortDirection, + sortProperty: this._currentConfig.sortProperty, + }; + const allItems: PrimaryKey[] = []; + + // eslint-disable-next-line no-constant-condition + while (true) { + const response = await firstValueFrom( + this._post>({ + page, + size, + options, + ...sOptions, + }), + ); + allItems.push( + ...response.data + .map(entity => (this._entityClass ? new this._entityClass(entity) : (entity as unknown as Class))) + .map(entity => entity.id), + ); + + if ((response.page + 1) * response.pageSize >= response.totalHits) { + break; + } + + page = response.page + 1; + } + + return allItems; + } } From 02a9cb49e9810c10afadefaf5f70b597ba2745a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 29 Apr 2024 17:03:34 +0300 Subject: [PATCH 094/201] Inputs styles --- src/assets/styles/common-inputs.scss | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/assets/styles/common-inputs.scss b/src/assets/styles/common-inputs.scss index d465acf..5a65252 100644 --- a/src/assets/styles/common-inputs.scss +++ b/src/assets/styles/common-inputs.scss @@ -94,29 +94,16 @@ iqser-dynamic-input { input:not([type='checkbox']), textarea { box-sizing: border-box; - margin-top: 3px; min-height: var(--iqser-inputs-height); line-height: 32px; padding-left: calc((var(--iqser-inputs-height) - 14px) / 2); padding-right: calc((var(--iqser-inputs-height) - 14px) / 2); } - .mat-mdc-form-field { - margin-top: 0; - - input { - margin-top: 0; - } - } - - .mat-mdc-form-field-subscript-wrapper { - display: none; - } - .mdc-text-field--outlined { --mdc-outlined-text-field-focus-outline-width: 1px; - --mdc-shape-small: 8px; // border-radius - --mdc-outlined-text-field-container-shape: 8px; // border-radius + --mdc-shape-small: 8px; + --mdc-outlined-text-field-container-shape: 8px; border-bottom-left-radius: var(--mdc-shape-small); border-bottom-right-radius: var(--mdc-shape-small); } From 12c5c1cb4e844dd34291703eb76eba6ccc006f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 29 Apr 2024 17:06:07 +0300 Subject: [PATCH 095/201] Fix --- src/assets/styles/common-inputs.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/assets/styles/common-inputs.scss b/src/assets/styles/common-inputs.scss index 5a65252..2b5e2f7 100644 --- a/src/assets/styles/common-inputs.scss +++ b/src/assets/styles/common-inputs.scss @@ -100,6 +100,10 @@ iqser-dynamic-input { padding-right: calc((var(--iqser-inputs-height) - 14px) / 2); } + .mat-mdc-form-field-subscript-wrapper { + display: none; + } + .mdc-text-field--outlined { --mdc-outlined-text-field-focus-outline-width: 1px; --mdc-shape-small: 8px; From 61d4d4f5c6f738dd9ce1b27a421d8d6d53a070a9 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 30 Apr 2024 16:06:25 +0300 Subject: [PATCH 096/201] RED-8748 - updated filter service --- .../iqser-dialog-component.directive.ts | 2 + .../filter-card/filter-card.component.html | 13 ++- .../filter-card/filter-card.component.ts | 72 +++------------- src/lib/filtering/filter.service.ts | 84 ++++++++++++++++++- 4 files changed, 107 insertions(+), 64 deletions(-) diff --git a/src/lib/dialog/iqser-dialog-component.directive.ts b/src/lib/dialog/iqser-dialog-component.directive.ts index 9a2e4f2..0f41d3e 100644 --- a/src/lib/dialog/iqser-dialog-component.directive.ts +++ b/src/lib/dialog/iqser-dialog-component.directive.ts @@ -3,6 +3,7 @@ import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dial import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { hasFormChanged, IqserEventTarget } from '../utils'; import { FormGroup } from '@angular/forms'; +import { IconButtonTypes } from '../buttons'; const DIALOG_CONTAINER = 'mat-dialog-container'; const DATA_TYPE_SYMBOL = Symbol.for('DATA_TYPE'); @@ -16,6 +17,7 @@ export abstract class IqserDialogComponent readonly [DATA_TYPE_SYMBOL]!: DataType; readonly [RETURN_TYPE_SYMBOL]!: ReturnType; + readonly iconButtonTypes = IconButtonTypes; readonly dialogRef = inject(MatDialogRef); readonly data = inject(MAT_DIALOG_DATA); readonly dialog = inject(MatDialog); diff --git a/src/lib/filtering/filter-card/filter-card.component.html b/src/lib/filtering/filter-card/filter-card.component.html index a0a8f11..59400fb 100644 --- a/src/lib/filtering/filter-card/filter-card.component.html +++ b/src/lib/filtering/filter-card/filter-card.component.html @@ -55,7 +55,12 @@ iqserStopPropagation translate="actions.all" >
-
+
@@ -89,7 +94,11 @@
- + (f.checked = !!nestedFilter.checked)); - } - - this.filterService.refresh(); - this.#updateFiltersInLocalStorage(); + deactivateFilters() { + this.filterService.deactivateFilters({ primaryFiltersSlug: this.primaryFiltersSlug }); } activatePrimaryFilters(): void { - this._setFilters(this.primaryFiltersSlug, true); - } - - deactivateFilters(exceptedFilterId?: string): void { - this._setFilters(this.primaryFiltersSlug, false, exceptedFilterId); - if (this.secondaryFiltersSlug) { - this._setFilters(this.secondaryFiltersSlug, false, exceptedFilterId); - } + this.filterService.setFilters(this.primaryFiltersSlug, true); } toggleFilterExpanded(nestedFilter: INestedFilter): void { @@ -125,36 +107,4 @@ export class FilterCardComponent implements OnInit { nestedFilter.expanded = !nestedFilter.expanded; this.filterService.refresh(); } - - private _setFilters(filterGroup: string, checked = false, exceptedFilterId?: string) { - const filters = this.filterService.getGroup(filterGroup)?.filters; - filters?.forEach(f => { - if (f.id !== exceptedFilterId) { - // eslint-disable-next-line no-param-reassign - f.checked = checked; - // eslint-disable-next-line no-param-reassign - f.indeterminate = false; - // eslint-disable-next-line no-return-assign,no-param-reassign - f.children?.forEach(ff => (ff.checked = checked)); - } - }); - this.filterService.refresh(); - } - - #updateFiltersInLocalStorage(): void { - if (this.fileId) { - const primaryFilters = this.filterService.getGroup('primaryFilters'); - const secondaryFilters = this.filterService.getGroup('secondaryFilters'); - - const filters: LocalStorageFilters = { - primaryFilters: extractFilterValues(primaryFilters?.filters), - secondaryFilters: extractFilterValues(secondaryFilters?.filters), - }; - - const workloadFiltersString = localStorage.getItem('workload-filters') ?? '{}'; - const workloadFilters = JSON.parse(workloadFiltersString); - workloadFilters[this.fileId] = filters; - localStorage.setItem('workload-filters', JSON.stringify(workloadFilters)); - } - } } diff --git a/src/lib/filtering/filter.service.ts b/src/lib/filtering/filter.service.ts index f5c4f80..2969051 100644 --- a/src/lib/filtering/filter.service.ts +++ b/src/lib/filtering/filter.service.ts @@ -1,13 +1,28 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { map, startWith, switchMap } from 'rxjs/operators'; -import { processFilters, toFlatFilters } from './filter-utils'; +import { extractFilterValues, handleCheckedValue, processFilters, toFlatFilters } from './filter-utils'; import { IFilterGroup } from './models/filter-group.model'; import { INestedFilter } from './models/nested-filter.model'; import { get, shareDistinctLast, shareLast, some } from '../utils'; import { NestedFilter } from './models/nested-filter'; import { Filter } from './models/filter'; import { IFilter } from './models/filter.model'; +import { LocalStorageFilters } from './filter-card/filter-card.component'; + +export interface CheckboxClickedParams { + nestedFilter: INestedFilter; + filterGroup: IFilterGroup; + parent?: INestedFilter; + fileId?: string; + primaryFiltersSlug: string; +} + +export interface DeactivateFiltersParams { + primaryFiltersSlug: string; + secondaryFiltersSlug?: string; + exceptedFilterId?: string; +} @Injectable() export class FilterService { @@ -196,4 +211,71 @@ export class FilterService { this.addSingleFilter(filter); } } + + setFilters(filterGroup: string, checked = false, exceptedFilterId?: string) { + const filters = this.getGroup(filterGroup)?.filters; + filters?.forEach(f => { + if (f.id !== exceptedFilterId) { + // eslint-disable-next-line no-param-reassign + f.checked = checked; + // eslint-disable-next-line no-param-reassign + f.indeterminate = false; + // eslint-disable-next-line no-return-assign,no-param-reassign + f.children?.forEach(ff => (ff.checked = checked)); + } + }); + this.refresh(); + } + + deactivateFilters(params: DeactivateFiltersParams) { + const { primaryFiltersSlug, secondaryFiltersSlug, exceptedFilterId } = params; + this.setFilters(primaryFiltersSlug, false, exceptedFilterId); + if (secondaryFiltersSlug) { + this.setFilters(secondaryFiltersSlug, false, exceptedFilterId); + } + } + + filterCheckboxClicked(params: CheckboxClickedParams) { + const { filterGroup, nestedFilter, parent, fileId, primaryFiltersSlug } = params; + + if (filterGroup.singleSelect) { + this.deactivateFilters({ primaryFiltersSlug, exceptedFilterId: nestedFilter.id }); + } + + // eslint-disable-next-line no-param-reassign + nestedFilter.checked = !nestedFilter.checked; + + if (parent) { + handleCheckedValue(parent); + } else { + // eslint-disable-next-line no-param-reassign + if (nestedFilter.indeterminate) { + nestedFilter.checked = false; + } + // eslint-disable-next-line no-param-reassign + nestedFilter.indeterminate = false; + // eslint-disable-next-line no-return-assign,no-param-reassign + nestedFilter.children?.forEach(f => (f.checked = !!nestedFilter.checked)); + } + + this.refresh(); + this.#updateFiltersInLocalStorage(fileId); + } + + #updateFiltersInLocalStorage(fileId?: string) { + if (fileId) { + const primaryFilters = this.getGroup('primaryFilters'); + const secondaryFilters = this.getGroup('secondaryFilters'); + + const filters: LocalStorageFilters = { + primaryFilters: extractFilterValues(primaryFilters?.filters), + secondaryFilters: extractFilterValues(secondaryFilters?.filters), + }; + + const workloadFiltersString = localStorage.getItem('workload-filters') ?? '{}'; + const workloadFilters = JSON.parse(workloadFiltersString); + workloadFilters[fileId] = filters; + localStorage.setItem('workload-filters', JSON.stringify(workloadFilters)); + } + } } From 3b0b6542cd0cef82b1a85886ed4899b9033835ac Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 30 Apr 2024 16:09:34 +0300 Subject: [PATCH 097/201] lint --- .../filter-card/filter-card.component.html | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/lib/filtering/filter-card/filter-card.component.html b/src/lib/filtering/filter-card/filter-card.component.html index 59400fb..a0a8f11 100644 --- a/src/lib/filtering/filter-card/filter-card.component.html +++ b/src/lib/filtering/filter-card/filter-card.component.html @@ -55,12 +55,7 @@ iqserStopPropagation translate="actions.all" >
-
+
@@ -94,11 +89,7 @@
- + Date: Wed, 8 May 2024 13:37:47 +0300 Subject: [PATCH 098/201] RED-9097: fixed workflow file attributes not updating. --- src/lib/listing/workflow/workflow.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts index c3f1d5e..43345de 100644 --- a/src/lib/listing/workflow/workflow.component.ts +++ b/src/lib/listing/workflow/workflow.component.ts @@ -236,7 +236,11 @@ export class WorkflowComponent extends Co private _shouldUpdate(entity: T): boolean { const existingEntity = this.all[entity.id]?.entity; - return existingEntity && this.config.itemVersionFn(entity) !== this.config.itemVersionFn(existingEntity); + return ( + existingEntity && + (this.config.itemVersionFn(entity) !== this.config.itemVersionFn(existingEntity) || + JSON.stringify(entity) !== JSON.stringify(existingEntity)) + ); } private _shouldAdd(entity: T): boolean { From 786d235de0dd890c37baa8d2d8d854c76aa3d4f7 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 16 May 2024 18:46:25 +0300 Subject: [PATCH 099/201] RED-8882 - Help Mode design improvements --- src/assets/icons/help-outline-active.svg | 14 ++++ .../circle-button.component.html | 1 - .../circle-button/circle-button.component.ts | 1 - src/lib/buttons/types/circle-button.type.ts | 1 - .../help-button/help-button.component.html | 18 ++++-- .../help-button/help-button.component.scss | 64 +++++++++++++++++++ .../help-button/help-button.component.ts | 57 +++++++++++++---- .../help-mode-dialog.component.html | 3 + .../help-mode-dialog.component.ts | 25 ++++++-- src/lib/help-mode/help-mode.module.ts | 5 +- src/lib/help-mode/help-mode.service.ts | 19 +++--- .../help-mode/help-mode.component.html | 1 - .../help-mode/help-mode.component.ts | 5 -- 13 files changed, 170 insertions(+), 44 deletions(-) create mode 100644 src/assets/icons/help-outline-active.svg create mode 100644 src/lib/help-mode/help-button/help-button.component.scss diff --git a/src/assets/icons/help-outline-active.svg b/src/assets/icons/help-outline-active.svg new file mode 100644 index 0000000..c0271f7 --- /dev/null +++ b/src/assets/icons/help-outline-active.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/src/lib/buttons/circle-button/circle-button.component.html b/src/lib/buttons/circle-button/circle-button.component.html index fd481e3..ba7e816 100644 --- a/src/lib/buttons/circle-button/circle-button.component.html +++ b/src/lib/buttons/circle-button/circle-button.component.html @@ -3,7 +3,6 @@ (click)="performAction($event)" [class.dark-bg]="type === _circleButtonTypes.dark" [class.grey-selected]="greySelected" - [class.help]="type === _circleButtonTypes.help" [class.overlay]="showDot" [class.primary]="type === _circleButtonTypes.primary" [class.warn]="type === _circleButtonTypes.warn" diff --git a/src/lib/buttons/circle-button/circle-button.component.ts b/src/lib/buttons/circle-button/circle-button.component.ts index 03dfe97..3e8e6ca 100644 --- a/src/lib/buttons/circle-button/circle-button.component.ts +++ b/src/lib/buttons/circle-button/circle-button.component.ts @@ -30,7 +30,6 @@ export class CircleButtonComponent implements OnInit { @Input() disabled = false; @Input() type: CircleButtonType = CircleButtonTypes.default; @Input() greySelected = false; - @Input() helpModeButton = false; @Input() removeTooltip = false; @Input() isSubmit = false; @Input() dropdownButton = false; diff --git a/src/lib/buttons/types/circle-button.type.ts b/src/lib/buttons/types/circle-button.type.ts index dd4494c..349c720 100644 --- a/src/lib/buttons/types/circle-button.type.ts +++ b/src/lib/buttons/types/circle-button.type.ts @@ -3,7 +3,6 @@ export const CircleButtonTypes = { primary: 'primary', warn: 'warn', dark: 'dark', - help: 'help', } as const; export type CircleButtonType = keyof typeof CircleButtonTypes; diff --git a/src/lib/help-mode/help-button/help-button.component.html b/src/lib/help-mode/help-button/help-button.component.html index 45fbb64..9f53692 100644 --- a/src/lib/help-mode/help-button/help-button.component.html +++ b/src/lib/help-mode/help-button/help-button.component.html @@ -1,6 +1,12 @@ - + diff --git a/src/lib/help-mode/help-button/help-button.component.scss b/src/lib/help-mode/help-button/help-button.component.scss new file mode 100644 index 0000000..c8bbedf --- /dev/null +++ b/src/lib/help-mode/help-button/help-button.component.scss @@ -0,0 +1,64 @@ +:host { + display: flex; + align-items: center; + width: 60px; +} + +.help-mode-slide-toggle { + display: inline-block; + position: relative; + width: 60px; + height: 34px; + cursor: pointer; + + &.active, &.dialog-toggle { + z-index: 1100; + } + + .toggle-input { + display: none; + } + + .toggle-track { + position: absolute; + top: 50%; + left: 0; + width: 100%; + height: 34px; + background-color: var(--iqser-grey-4); + border-radius: 20px; + transform: translateY(-50%); + } + + .toggle-thumb { + position: absolute; + top: 50%; + left: 4px; + width: 25px; + height: 25px; + background-color: #fff; + border-radius: 50%; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + transform: translateY(-50%); + transition: left 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + + mat-icon { + transform: scale(0.6); + } + + .active-thumb { + color: var(--iqser-helpmode-primary);; + } + } + + .toggle-input:checked + .toggle-track { + background: var(--iqser-helpmode-primary); + } + + .toggle-input:checked + .toggle-track + .toggle-thumb { + left: calc(100% - 30px); + } +} diff --git a/src/lib/help-mode/help-button/help-button.component.ts b/src/lib/help-mode/help-button/help-button.component.ts index e924372..984c562 100644 --- a/src/lib/help-mode/help-button/help-button.component.ts +++ b/src/lib/help-mode/help-button/help-button.component.ts @@ -1,31 +1,60 @@ /* eslint-disable @angular-eslint/prefer-on-push-component-change-detection */ -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; import { HelpModeService } from '../index'; @Component({ selector: 'iqser-help-button', templateUrl: './help-button.component.html', + styleUrls: ['./help-button.component.scss'], }) -export class HelpButtonComponent implements OnInit, OnDestroy { - @Input() dialogButton = false; - @Input() helpButtonKey?: string; +export class HelpButtonComponent implements OnInit, AfterViewInit, OnDestroy { + @Input() dialogButton = true; + helpModeButton: HTMLElement; - constructor(private readonly _helpModeService: HelpModeService) {} + constructor( + private readonly _elementRef: ElementRef, + readonly helpModeService: HelpModeService, + ) {} - ngOnInit(): void { - this._helpModeService.helpButtonKey = this.helpButtonKey; + get buttonId() { + return `help-mode-button${this.dialogButton ? '-dialog' : ''}`; } - ngOnDestroy(): void { - this._helpModeService.helpButtonKey = undefined; + ngOnInit() { + if (this.dialogButton) { + const defaultButton = document.getElementById('help-mode-button') as HTMLElement; + defaultButton.style.setProperty('z-index', '100'); + } } - activateHelpMode(): void { - if (this.helpButtonKey) { - const url = this._helpModeService.generateDocsLink(this.helpButtonKey); - window.open(url, '_blank'); + ngAfterViewInit() { + const currentComponent = this._elementRef.nativeElement; + this.helpModeButton = currentComponent.querySelector('.help-mode-slide-toggle'); + + if (this.helpModeButton) { + setTimeout(() => { + const currentComponentRect = currentComponent.getBoundingClientRect(); + this.helpModeButton.style.setProperty('position', 'fixed'); + this.helpModeButton.style.setProperty('top', `${currentComponentRect.top}px`); + this.helpModeButton.style.setProperty('left', `${currentComponentRect.left}px`); + document.body.appendChild(this.helpModeButton); + }, 500); + } + } + + ngOnDestroy() { + document.body.removeChild(this.helpModeButton); + if (this.dialogButton) { + const defaultButton = document.getElementById('help-mode-button') as HTMLElement; + defaultButton.style.removeProperty('z-index'); + } + } + + toggleHelpMode(): void { + if (this.helpModeService.isHelpModeActive) { + this.helpModeService.deactivateHelpMode(); return; } - this._helpModeService.activateHelpMode(this.dialogButton); + this.helpModeService.activateHelpMode(this.dialogButton); } } diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html index 42884bb..7eef302 100644 --- a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html @@ -3,6 +3,9 @@

+ + {{ 'help-mode.options.do-not-show-again' | translate }} +
diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts index 8d25698..39cde83 100644 --- a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts @@ -1,23 +1,40 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { IqserDialogComponent } from '../../dialog'; const HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX = '1200'; const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; +interface HelpModeDialogData {} +interface HelpModeDialogResult {} + @Component({ templateUrl: './help-mode-dialog.component.html', styleUrls: ['./help-mode-dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class HelpModeDialogComponent implements OnInit, OnDestroy { +export class HelpModeDialogComponent + extends IqserDialogComponent + implements OnInit, OnDestroy +{ + protected doNotShowAgainOption = false; + + constructor() { + super(); + } + ngOnInit(): void { - this._setCdkOverlayContainerZindex(HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX); + this.#setCdkOverlayContainerZIndex(HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX); } ngOnDestroy(): void { - this._setCdkOverlayContainerZindex(DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX); + this.#setCdkOverlayContainerZIndex(DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX); } - private _setCdkOverlayContainerZindex(zIndex: string): void { + setDoNotShowAgainOption(checked: boolean): void { + this.doNotShowAgainOption = checked; + } + + #setCdkOverlayContainerZIndex(zIndex: string): void { const cdkOverlayContainer = document.querySelector('.cdk-overlay-container'); if (cdkOverlayContainer) { cdkOverlayContainer.style.zIndex = zIndex; diff --git a/src/lib/help-mode/help-mode.module.ts b/src/lib/help-mode/help-mode.module.ts index f0250c7..4fdd066 100644 --- a/src/lib/help-mode/help-mode.module.ts +++ b/src/lib/help-mode/help-mode.module.ts @@ -8,12 +8,15 @@ import { HelpModeKey, HelpModeService } from './help-mode.service'; import { MatDialogModule } from '@angular/material/dialog'; import { CircleButtonComponent } from '../buttons'; import { HELP_MODE_KEYS } from './tokens'; +import { MatSlideToggle } from '@angular/material/slide-toggle'; +import { MatIcon } from '@angular/material/icon'; +import { MatCheckbox } from '@angular/material/checkbox'; const components = [HelpModeComponent, HelpModeDialogComponent, HelpButtonComponent]; @NgModule({ declarations: [...components], - imports: [CommonModule, MatDialogModule, TranslateModule, CircleButtonComponent], + imports: [CommonModule, MatDialogModule, TranslateModule, CircleButtonComponent, MatSlideToggle, MatIcon, MatCheckbox], exports: [...components], }) export class IqserHelpModeModule { diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index 04f12a5..9c784a9 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -18,6 +18,7 @@ import { WEB_VIEWER_ELEMENTS, } from './utils/constants'; import { getConfig } from '../services'; +import { IqserDialog } from '../dialog'; export interface Helper { readonly element: HTMLElement; @@ -38,7 +39,6 @@ export interface HelpModeKey { @Injectable() export class HelpModeService { - helpButtonKey: string | undefined; readonly #isHelpModeActive$ = new BehaviorSubject(false); readonly isHelpModeActive$ = this.#isHelpModeActive$.asObservable(); readonly #helpModeDialogIsOpened$ = new BehaviorSubject(false); @@ -51,7 +51,7 @@ export class HelpModeService { constructor( @Inject(HELP_MODE_KEYS) private readonly _keys: HelpModeKey[], @Inject(MANUAL_BASE_URL) private readonly _manualBaseURL: string, - private readonly _dialog: MatDialog, + private readonly _iqserDialog: IqserDialog, private readonly _rendererFactory: RendererFactory2, private readonly _translateService: TranslateService, ) { @@ -66,17 +66,16 @@ export class HelpModeService { return this.#helpModeDialogIsOpened$.getValue(); } - openHelpModeDialog(): MatDialogRef { + openHelpModeDialog() { this.#helpModeDialogIsOpened$.next(true); - const ref = this._dialog.open(HelpModeDialogComponent, { + this._iqserDialog.open(HelpModeDialogComponent, { width: '600px', }); - firstValueFrom(ref.afterClosed()).then(() => { - this.#helpModeDialogIsOpened$.next(false); - }); - return ref; + // firstValueFrom(ref.afterClosed()).then(() => { + // this.#helpModeDialogIsOpened$.next(false); + // }); } activateHelpMode(dialogMode: boolean = false): void { @@ -137,7 +136,7 @@ export class HelpModeService { #getHelperElement(element: HTMLElement, key: string): HTMLElement { const helperElement = this.#renderer.createElement('a') as HTMLElement; - this.#renderer.setAttribute(helperElement, 'href', this.generateDocsLink(key)); + this.#renderer.setAttribute(helperElement, 'href', this.#generateDocsLink(key)); this.#renderer.setAttribute(helperElement, 'target', '_blank'); this.#renderer.addClass(helperElement, HELP_MODE_CLASS); if (this.#isDocumine) { @@ -150,7 +149,7 @@ export class HelpModeService { return Math.random().toString(36).substring(2, 9); } - generateDocsLink(key: string) { + #generateDocsLink(key: string) { const currentLang = this._translateService.currentLang; return `${this._manualBaseURL}/${currentLang}/index-${currentLang}.html?contextId=${key}`; } diff --git a/src/lib/help-mode/help-mode/help-mode.component.html b/src/lib/help-mode/help-mode/help-mode.component.html index 0f59d6e..f14bc48 100644 --- a/src/lib/help-mode/help-mode/help-mode.component.html +++ b/src/lib/help-mode/help-mode/help-mode.component.html @@ -11,7 +11,6 @@ (click)="helpModeService.deactivateHelpMode()" [iconSize]="10" [size]="20" - [type]="circleButtonTypes.help" class="ml-8" icon="iqser:close" > diff --git a/src/lib/help-mode/help-mode/help-mode.component.ts b/src/lib/help-mode/help-mode/help-mode.component.ts index 6ff53e3..ce4ae7e 100644 --- a/src/lib/help-mode/help-mode/help-mode.component.ts +++ b/src/lib/help-mode/help-mode/help-mode.component.ts @@ -30,11 +30,6 @@ export class HelpModeComponent { onHKeydownHandler(event: KeyboardEvent): void { const node = (event.target as IqserEventTarget).localName; if (!this.helpModeService.isHelpModeActive && node !== 'input' && node !== 'textarea') { - if (this.helpModeService.helpButtonKey) { - const url = this.helpModeService.generateDocsLink(this.helpModeService.helpButtonKey); - window.open(url, '_blank'); - return; - } const dialogMode = !!this._dialog.openDialogs.length; this.helpModeService.activateHelpMode(dialogMode); } From 252c2616211112e23a18151fafd9a127c800ca65 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 16 May 2024 18:47:35 +0300 Subject: [PATCH 100/201] lint --- src/lib/help-mode/help-button/help-button.component.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/help-mode/help-button/help-button.component.scss b/src/lib/help-mode/help-button/help-button.component.scss index c8bbedf..fad7ffe 100644 --- a/src/lib/help-mode/help-button/help-button.component.scss +++ b/src/lib/help-mode/help-button/help-button.component.scss @@ -11,7 +11,8 @@ height: 34px; cursor: pointer; - &.active, &.dialog-toggle { + &.active, + &.dialog-toggle { z-index: 1100; } @@ -50,7 +51,7 @@ } .active-thumb { - color: var(--iqser-helpmode-primary);; + color: var(--iqser-helpmode-primary); } } From 6f288516e3efa36ec9f1f9eb02d0374c988d3432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 16 May 2024 23:31:52 +0300 Subject: [PATCH 101/201] Minor UI fixes, base dialog improvements --- src/assets/styles/common-base-screen.scss | 8 ++------ src/assets/styles/common-components.scss | 1 + src/lib/dialog/base-dialog.component.ts | 19 ++++++++++--------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/assets/styles/common-base-screen.scss b/src/assets/styles/common-base-screen.scss index a90ebbc..764101c 100644 --- a/src/assets/styles/common-base-screen.scss +++ b/src/assets/styles/common-base-screen.scss @@ -56,15 +56,11 @@ .dev-mode { background-color: var(--iqser-primary); color: var(--iqser-white); - font-size: 22px; - line-height: 16px; - text-align: center; position: fixed; - top: 0; - z-index: 100; right: 0; height: var(--iqser-top-bar-height); - word-break: break-all; + writing-mode: vertical-rl; + text-orientation: upright; display: flex; justify-content: center; align-items: center; diff --git a/src/assets/styles/common-components.scss b/src/assets/styles/common-components.scss index b3907a6..0fb5ea4 100644 --- a/src/assets/styles/common-components.scss +++ b/src/assets/styles/common-components.scss @@ -18,6 +18,7 @@ &.large { height: 32px; width: 32px; + min-width: 32px; font-size: var(--iqser-font-size); } diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts index 5fd2b88..4a43972 100644 --- a/src/lib/dialog/base-dialog.component.ts +++ b/src/lib/dialog/base-dialog.component.ts @@ -15,21 +15,21 @@ const TEXT_INPUT = 'text'; export interface SaveOptions { closeAfterSave?: boolean; - addMembers?: boolean; + nextAction?: boolean; } @Directive() export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { - readonly #confirmationDialogService = inject(ConfirmationDialogService); - readonly #dialog = inject(MatDialog); - readonly #hasErrors = signal(true); + readonly iconButtonTypes = IconButtonTypes; + form?: UntypedFormGroup; + initialFormValue!: Record; + protected readonly _hasErrors = signal(true); protected readonly _formBuilder = inject(UntypedFormBuilder); protected readonly _loadingService = inject(LoadingService); protected readonly _toaster = inject(Toaster); protected readonly _subscriptions = new Subscription(); - readonly iconButtonTypes = IconButtonTypes; - form?: UntypedFormGroup; - initialFormValue!: Record; + readonly #confirmationDialogService = inject(ConfirmationDialogService); + readonly #dialog = inject(MatDialog); protected constructor( protected readonly _dialogRef: MatDialogRef, @@ -45,17 +45,18 @@ export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { } get disabled(): boolean { - return !this.valid || !this.changed || this.#hasErrors(); + return !this.valid || !this.changed || this._hasErrors(); } ngAfterViewInit() { this._subscriptions.add(this._dialogRef.backdropClick().subscribe(() => this.close())); const valueChanges = this.form?.valueChanges ?? of(null); const events = [fromEvent(window, 'keyup'), fromEvent(window, 'input'), valueChanges]; + this._hasErrors.set(!!document.getElementsByClassName('ng-invalid')[0]); const events$ = merge(...events).pipe( debounceTime(10), tap(() => { - this.#hasErrors.set(!!document.getElementsByClassName('ng-invalid')[0]); + this._hasErrors.set(!!document.getElementsByClassName('ng-invalid')[0]); }), ); this._subscriptions.add(events$.subscribe()); From 0021307c71c2d0b18de52dd3f14040b5730cd9c4 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 17 May 2024 17:50:32 +0300 Subject: [PATCH 102/201] RED-8882 - Help Mode design improvements --- src/lib/empty-state/empty-state.component.ts | 3 +- .../help-mode-dialog.component.html | 2 +- .../help-mode-dialog.component.ts | 29 ++++++++++--------- src/lib/help-mode/help-mode.module.ts | 8 ++--- src/lib/help-mode/help-mode.service.ts | 28 +++++++++++------- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/lib/empty-state/empty-state.component.ts b/src/lib/empty-state/empty-state.component.ts index 622c31b..f6c7c59 100644 --- a/src/lib/empty-state/empty-state.component.ts +++ b/src/lib/empty-state/empty-state.component.ts @@ -3,7 +3,6 @@ import { IconButtonComponent, IconButtonTypes } from '../buttons'; import { randomString } from '../utils'; import { NgIf, NgStyle } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; -import { IqserHelpModeModule } from '../help-mode'; @Component({ selector: 'iqser-empty-state [text]', @@ -11,7 +10,7 @@ import { IqserHelpModeModule } from '../help-mode'; styleUrls: ['./empty-state.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [NgStyle, MatIconModule, NgIf, IconButtonComponent, IqserHelpModeModule], + imports: [NgStyle, MatIconModule, NgIf, IconButtonComponent], }) export class EmptyStateComponent implements OnInit { readonly iconButtonTypes = IconButtonTypes; diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html index 7eef302..4fbffad 100644 --- a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html @@ -7,5 +7,5 @@ {{ 'help-mode.options.do-not-show-again' | translate }}
- + diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts index 39cde83..d3c0ef4 100644 --- a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts @@ -1,40 +1,43 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; -import { IqserDialogComponent } from '../../dialog'; +import { BaseDialogComponent } from '../../dialog'; +import { MatDialogRef } from '@angular/material/dialog'; const HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX = '1200'; const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; -interface HelpModeDialogData {} -interface HelpModeDialogResult {} - @Component({ templateUrl: './help-mode-dialog.component.html', styleUrls: ['./help-mode-dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class HelpModeDialogComponent - extends IqserDialogComponent - implements OnInit, OnDestroy -{ +export class HelpModeDialogComponent extends BaseDialogComponent implements OnInit, OnDestroy { protected doNotShowAgainOption = false; - constructor() { - super(); + constructor(protected readonly _dialogRef: MatDialogRef) { + super(_dialogRef); } ngOnInit(): void { - this.#setCdkOverlayContainerZIndex(HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX); + this._setCdkOverlayContainerZIndex(HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX); } ngOnDestroy(): void { - this.#setCdkOverlayContainerZIndex(DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX); + this._setCdkOverlayContainerZIndex(DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX); } setDoNotShowAgainOption(checked: boolean): void { this.doNotShowAgainOption = checked; } - #setCdkOverlayContainerZIndex(zIndex: string): void { + save() { + this.close(); + } + + close() { + return this._dialogRef.close(this.doNotShowAgainOption); + } + + private _setCdkOverlayContainerZIndex(zIndex: string): void { const cdkOverlayContainer = document.querySelector('.cdk-overlay-container'); if (cdkOverlayContainer) { cdkOverlayContainer.style.zIndex = zIndex; diff --git a/src/lib/help-mode/help-mode.module.ts b/src/lib/help-mode/help-mode.module.ts index 4fdd066..126cfee 100644 --- a/src/lib/help-mode/help-mode.module.ts +++ b/src/lib/help-mode/help-mode.module.ts @@ -1,22 +1,22 @@ import { ModuleWithProviders, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; -import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; import { HelpModeComponent } from './help-mode/help-mode.component'; import { HelpButtonComponent } from './help-button/help-button.component'; import { HelpModeKey, HelpModeService } from './help-mode.service'; -import { MatDialogModule } from '@angular/material/dialog'; import { CircleButtonComponent } from '../buttons'; import { HELP_MODE_KEYS } from './tokens'; import { MatSlideToggle } from '@angular/material/slide-toggle'; import { MatIcon } from '@angular/material/icon'; import { MatCheckbox } from '@angular/material/checkbox'; +import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; +import { MatDialogClose } from '@angular/material/dialog'; -const components = [HelpModeComponent, HelpModeDialogComponent, HelpButtonComponent]; +const components = [HelpModeComponent, HelpButtonComponent, HelpModeDialogComponent]; @NgModule({ declarations: [...components], - imports: [CommonModule, MatDialogModule, TranslateModule, CircleButtonComponent, MatSlideToggle, MatIcon, MatCheckbox], + imports: [CommonModule, TranslateModule, CircleButtonComponent, MatSlideToggle, MatIcon, MatCheckbox, MatDialogClose], exports: [...components], }) export class IqserHelpModeModule { diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index 9c784a9..0986478 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -1,5 +1,4 @@ import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; @@ -18,7 +17,8 @@ import { WEB_VIEWER_ELEMENTS, } from './utils/constants'; import { getConfig } from '../services'; -import { IqserDialog } from '../dialog'; +import { UserPreferenceService } from '../../../../../apps/red-ui/src/app/users/user-preference.service'; +import { MatDialog } from '@angular/material/dialog'; export interface Helper { readonly element: HTMLElement; @@ -51,9 +51,10 @@ export class HelpModeService { constructor( @Inject(HELP_MODE_KEYS) private readonly _keys: HelpModeKey[], @Inject(MANUAL_BASE_URL) private readonly _manualBaseURL: string, - private readonly _iqserDialog: IqserDialog, + private readonly _dialog: MatDialog, private readonly _rendererFactory: RendererFactory2, private readonly _translateService: TranslateService, + private readonly _userPreferencesService: UserPreferenceService, ) { this.#renderer = this._rendererFactory.createRenderer(null, null); } @@ -66,16 +67,21 @@ export class HelpModeService { return this.#helpModeDialogIsOpened$.getValue(); } - openHelpModeDialog() { - this.#helpModeDialogIsOpened$.next(true); + async openHelpModeDialog() { + if (!this._userPreferencesService.getHelpModeDialog()) { + this.#helpModeDialogIsOpened$.next(true); - this._iqserDialog.open(HelpModeDialogComponent, { - width: '600px', - }); + const ref = this._dialog.open(HelpModeDialogComponent, { + width: '600px', + }); - // firstValueFrom(ref.afterClosed()).then(() => { - // this.#helpModeDialogIsOpened$.next(false); - // }); + firstValueFrom(ref.afterClosed()).then(result => { + this.#helpModeDialogIsOpened$.next(false); + if (result) { + this._userPreferencesService.toggleHelpModeDialog(); + } + }); + } } activateHelpMode(dialogMode: boolean = false): void { From 217a44c2e0bd4fd2073aae8f7ed4f6626babbdfb Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Fri, 17 May 2024 18:59:48 +0300 Subject: [PATCH 103/201] removed not needed imports --- src/lib/help-mode/help-mode.module.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/help-mode/help-mode.module.ts b/src/lib/help-mode/help-mode.module.ts index 126cfee..f395c73 100644 --- a/src/lib/help-mode/help-mode.module.ts +++ b/src/lib/help-mode/help-mode.module.ts @@ -6,17 +6,15 @@ import { HelpButtonComponent } from './help-button/help-button.component'; import { HelpModeKey, HelpModeService } from './help-mode.service'; import { CircleButtonComponent } from '../buttons'; import { HELP_MODE_KEYS } from './tokens'; -import { MatSlideToggle } from '@angular/material/slide-toggle'; import { MatIcon } from '@angular/material/icon'; import { MatCheckbox } from '@angular/material/checkbox'; import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; -import { MatDialogClose } from '@angular/material/dialog'; const components = [HelpModeComponent, HelpButtonComponent, HelpModeDialogComponent]; @NgModule({ declarations: [...components], - imports: [CommonModule, TranslateModule, CircleButtonComponent, MatSlideToggle, MatIcon, MatCheckbox, MatDialogClose], + imports: [CommonModule, TranslateModule, CircleButtonComponent, MatCheckbox, MatIcon], exports: [...components], }) export class IqserHelpModeModule { From 5cc09f251ee172c8b539238d80f0ed251f1640ef Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 20 May 2024 10:39:36 +0300 Subject: [PATCH 104/201] fix build --- src/lib/dialog/base-dialog.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts index 4a43972..b4bffb3 100644 --- a/src/lib/dialog/base-dialog.component.ts +++ b/src/lib/dialog/base-dialog.component.ts @@ -16,6 +16,7 @@ const TEXT_INPUT = 'text'; export interface SaveOptions { closeAfterSave?: boolean; nextAction?: boolean; + addMembers?: boolean; } @Directive() From b7c8634407b3f2b4e071a22a9f338ddac010c814 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 20 May 2024 14:31:30 +0300 Subject: [PATCH 105/201] removed base dialog from help mode dialog --- .../help-mode-dialog/help-mode-dialog.component.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts index d3c0ef4..935eccf 100644 --- a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts @@ -1,6 +1,4 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; -import { BaseDialogComponent } from '../../dialog'; -import { MatDialogRef } from '@angular/material/dialog'; const HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX = '1200'; const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; @@ -10,12 +8,12 @@ const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; styleUrls: ['./help-mode-dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class HelpModeDialogComponent extends BaseDialogComponent implements OnInit, OnDestroy { +export class HelpModeDialogComponent implements OnInit, OnDestroy { protected doNotShowAgainOption = false; - constructor(protected readonly _dialogRef: MatDialogRef) { - super(_dialogRef); - } + // constructor(protected readonly _dialogRef: MatDialogRef) { + // super(_dialogRef); + // } ngOnInit(): void { this._setCdkOverlayContainerZIndex(HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX); @@ -34,7 +32,7 @@ export class HelpModeDialogComponent extends BaseDialogComponent implements OnIn } close() { - return this._dialogRef.close(this.doNotShowAgainOption); + // return this._dialogRef.close(this.doNotShowAgainOption); } private _setCdkOverlayContainerZIndex(zIndex: string): void { From 848e46cf6abc9cafbca5158540e34c65e8dde73d Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 20 May 2024 16:27:20 +0300 Subject: [PATCH 106/201] removed user preferences service from help mode service --- src/lib/help-mode/help-mode.service.ts | 30 ++++++++++++-------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index 0986478..059ab2a 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -17,7 +17,6 @@ import { WEB_VIEWER_ELEMENTS, } from './utils/constants'; import { getConfig } from '../services'; -import { UserPreferenceService } from '../../../../../apps/red-ui/src/app/users/user-preference.service'; import { MatDialog } from '@angular/material/dialog'; export interface Helper { @@ -54,7 +53,6 @@ export class HelpModeService { private readonly _dialog: MatDialog, private readonly _rendererFactory: RendererFactory2, private readonly _translateService: TranslateService, - private readonly _userPreferencesService: UserPreferenceService, ) { this.#renderer = this._rendererFactory.createRenderer(null, null); } @@ -68,20 +66,20 @@ export class HelpModeService { } async openHelpModeDialog() { - if (!this._userPreferencesService.getHelpModeDialog()) { - this.#helpModeDialogIsOpened$.next(true); - - const ref = this._dialog.open(HelpModeDialogComponent, { - width: '600px', - }); - - firstValueFrom(ref.afterClosed()).then(result => { - this.#helpModeDialogIsOpened$.next(false); - if (result) { - this._userPreferencesService.toggleHelpModeDialog(); - } - }); - } + // if (!this._userPreferencesService.getHelpModeDialog()) { + // this.#helpModeDialogIsOpened$.next(true); + // + // const ref = this._dialog.open(HelpModeDialogComponent, { + // width: '600px', + // }); + // + // firstValueFrom(ref.afterClosed()).then(result => { + // this.#helpModeDialogIsOpened$.next(false); + // if (result) { + // this._userPreferencesService.toggleHelpModeDialog(); + // } + // }); + // } } activateHelpMode(dialogMode: boolean = false): void { From 174d77d2eaedb17b2e729efdc29a07e7942e46e5 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 20 May 2024 17:10:25 +0300 Subject: [PATCH 107/201] RED-8882 - moved help mode dialog preference into iqser-user-preferences.service --- .../help-mode-dialog.component.ts | 12 ++++--- src/lib/help-mode/help-mode.service.ts | 31 ++++++++++--------- .../services/iqser-user-preference.service.ts | 12 ++++++- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts index 935eccf..f44e035 100644 --- a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts @@ -1,4 +1,6 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { BaseDialogComponent } from '../../dialog'; const HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX = '1200'; const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; @@ -8,12 +10,12 @@ const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; styleUrls: ['./help-mode-dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class HelpModeDialogComponent implements OnInit, OnDestroy { +export class HelpModeDialogComponent extends BaseDialogComponent implements OnInit, OnDestroy { protected doNotShowAgainOption = false; - // constructor(protected readonly _dialogRef: MatDialogRef) { - // super(_dialogRef); - // } + constructor(protected readonly _dialogRef: MatDialogRef) { + super(_dialogRef); + } ngOnInit(): void { this._setCdkOverlayContainerZIndex(HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX); @@ -32,7 +34,7 @@ export class HelpModeDialogComponent implements OnInit, OnDestroy { } close() { - // return this._dialogRef.close(this.doNotShowAgainOption); + return this._dialogRef.close(this.doNotShowAgainOption); } private _setCdkOverlayContainerZIndex(zIndex: string): void { diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index 059ab2a..36e8a65 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -16,7 +16,7 @@ import { ScrollableParentViews, WEB_VIEWER_ELEMENTS, } from './utils/constants'; -import { getConfig } from '../services'; +import { getConfig, IqserUserPreferenceService } from '../services'; import { MatDialog } from '@angular/material/dialog'; export interface Helper { @@ -53,6 +53,7 @@ export class HelpModeService { private readonly _dialog: MatDialog, private readonly _rendererFactory: RendererFactory2, private readonly _translateService: TranslateService, + private readonly _iqserUserPreferenceService: IqserUserPreferenceService, ) { this.#renderer = this._rendererFactory.createRenderer(null, null); } @@ -66,20 +67,20 @@ export class HelpModeService { } async openHelpModeDialog() { - // if (!this._userPreferencesService.getHelpModeDialog()) { - // this.#helpModeDialogIsOpened$.next(true); - // - // const ref = this._dialog.open(HelpModeDialogComponent, { - // width: '600px', - // }); - // - // firstValueFrom(ref.afterClosed()).then(result => { - // this.#helpModeDialogIsOpened$.next(false); - // if (result) { - // this._userPreferencesService.toggleHelpModeDialog(); - // } - // }); - // } + if (!this._iqserUserPreferenceService.getHelpModeDialog()) { + this.#helpModeDialogIsOpened$.next(true); + + const ref = this._dialog.open(HelpModeDialogComponent, { + width: '600px', + }); + + firstValueFrom(ref.afterClosed()).then(result => { + this.#helpModeDialogIsOpened$.next(false); + if (result) { + this._iqserUserPreferenceService.toggleHelpModeDialog(); + } + }); + } } activateHelpMode(dialogMode: boolean = false): void { diff --git a/src/lib/services/iqser-user-preference.service.ts b/src/lib/services/iqser-user-preference.service.ts index a841b8a..1bafc99 100644 --- a/src/lib/services/iqser-user-preference.service.ts +++ b/src/lib/services/iqser-user-preference.service.ts @@ -6,9 +6,10 @@ import { APP_BASE_HREF } from '@angular/common'; export type UserAttributes = Record; -const KEYS = { +export const KEYS = { language: 'Language', theme: 'Theme', + helpModeDialog: 'HelpModeDialog', } as const; @Injectable() @@ -43,6 +44,15 @@ export abstract class IqserUserPreferenceService extends GenericService { + const nextValue = (!this.getHelpModeDialog()).toString(); + await this.save(KEYS.helpModeDialog, nextValue); + } + toggleDevFeatures(): void { sessionStorage.setItem(this._devFeaturesEnabledKey, String(!this.isIqserDevMode)); window.location.reload(); From 4811d301e6980d3f7fb0125c673cb6b8b9b4f0cf Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 20 May 2024 19:06:58 +0300 Subject: [PATCH 108/201] RED-8882 - removed base dialog --- .../help-mode-dialog.component.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts index f44e035..e1908d1 100644 --- a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostListener, OnDestroy, OnInit } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; -import { BaseDialogComponent } from '../../dialog'; +import { Subscription } from 'rxjs'; const HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX = '1200'; const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; @@ -10,11 +10,18 @@ const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; styleUrls: ['./help-mode-dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class HelpModeDialogComponent extends BaseDialogComponent implements OnInit, OnDestroy { +export class HelpModeDialogComponent implements OnInit, OnDestroy { + #backdropClickSubscription: Subscription; protected doNotShowAgainOption = false; constructor(protected readonly _dialogRef: MatDialogRef) { - super(_dialogRef); + this.#backdropClickSubscription = this._dialogRef.backdropClick().subscribe(() => this.close()); + } + + @HostListener('window:keydown.Enter', ['$event']) + @HostListener('window:keydown.Escape', ['$event']) + close() { + return this._dialogRef.close(this.doNotShowAgainOption); } ngOnInit(): void { @@ -23,20 +30,13 @@ export class HelpModeDialogComponent extends BaseDialogComponent implements OnIn ngOnDestroy(): void { this._setCdkOverlayContainerZIndex(DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX); + this.#backdropClickSubscription.unsubscribe(); } setDoNotShowAgainOption(checked: boolean): void { this.doNotShowAgainOption = checked; } - save() { - this.close(); - } - - close() { - return this._dialogRef.close(this.doNotShowAgainOption); - } - private _setCdkOverlayContainerZIndex(zIndex: string): void { const cdkOverlayContainer = document.querySelector('.cdk-overlay-container'); if (cdkOverlayContainer) { From e8f5bc8f2c0716cd20beb9315b67807157c6c9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 22 May 2024 14:37:08 +0300 Subject: [PATCH 109/201] RED-6959: Added selected items count to table header --- src/lib/listing/table-header/table-header.component.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/listing/table-header/table-header.component.html b/src/lib/listing/table-header/table-header.component.html index 527378c..3ca9bd8 100644 --- a/src/lib/listing/table-header/table-header.component.html +++ b/src/lib/listing/table-header/table-header.component.html @@ -1,15 +1,18 @@
{{ tableHeaderLabel | translate: { length: totalSize || (listingService.displayedLength$ | async) } }} + + ({{ 'table-header.selected-count' | translate: { count: selectedItems } }}) +
@@ -33,12 +36,12 @@
From e7fca876bb0064ebceb6d18d721260c078a9a556 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 22 May 2024 21:31:22 +0300 Subject: [PATCH 110/201] RED-8882 - made help button smaller --- src/assets/styles/common-base-screen.scss | 1 + .../help-button/help-button.component.scss | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/assets/styles/common-base-screen.scss b/src/assets/styles/common-base-screen.scss index 764101c..874f031 100644 --- a/src/assets/styles/common-base-screen.scss +++ b/src/assets/styles/common-base-screen.scss @@ -32,6 +32,7 @@ .buttons { display: flex; margin-right: 8px; + align-items: center; > *:not(:last-child) { margin-right: 14px; diff --git a/src/lib/help-mode/help-button/help-button.component.scss b/src/lib/help-mode/help-button/help-button.component.scss index fad7ffe..1d5c87c 100644 --- a/src/lib/help-mode/help-button/help-button.component.scss +++ b/src/lib/help-mode/help-button/help-button.component.scss @@ -1,14 +1,15 @@ :host { display: flex; align-items: center; - width: 60px; + width: 40px; + height: 24px; } .help-mode-slide-toggle { display: inline-block; position: relative; - width: 60px; - height: 34px; + width: 40px; + height: 24px; cursor: pointer; &.active, @@ -25,7 +26,7 @@ top: 50%; left: 0; width: 100%; - height: 34px; + height: 25px; background-color: var(--iqser-grey-4); border-radius: 20px; transform: translateY(-50%); @@ -35,8 +36,8 @@ position: absolute; top: 50%; left: 4px; - width: 25px; - height: 25px; + width: 20px; + height: 20px; background-color: #fff; border-radius: 50%; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); @@ -60,6 +61,6 @@ } .toggle-input:checked + .toggle-track + .toggle-thumb { - left: calc(100% - 30px); + left: calc(100% - 22px); } } From 3a9f36e71b184e66eb791af19deeb258f8ec333d Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 29 May 2024 10:52:33 +0300 Subject: [PATCH 111/201] RED-8882 - Help Mode design improvements --- .../help-mode/help-button/help-button.component.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/help-mode/help-button/help-button.component.ts b/src/lib/help-mode/help-button/help-button.component.ts index 984c562..b086ab7 100644 --- a/src/lib/help-mode/help-button/help-button.component.ts +++ b/src/lib/help-mode/help-button/help-button.component.ts @@ -1,5 +1,5 @@ /* eslint-disable @angular-eslint/prefer-on-push-component-change-detection */ -import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, HostListener, Input, OnDestroy, OnInit } from '@angular/core'; import { HelpModeService } from '../index'; @Component({ @@ -34,7 +34,7 @@ export class HelpButtonComponent implements OnInit, AfterViewInit, OnDestroy { if (this.helpModeButton) { setTimeout(() => { const currentComponentRect = currentComponent.getBoundingClientRect(); - this.helpModeButton.style.setProperty('position', 'fixed'); + this.helpModeButton.style.setProperty('position', 'absolute'); this.helpModeButton.style.setProperty('top', `${currentComponentRect.top}px`); this.helpModeButton.style.setProperty('left', `${currentComponentRect.left}px`); document.body.appendChild(this.helpModeButton); @@ -57,4 +57,12 @@ export class HelpButtonComponent implements OnInit, AfterViewInit, OnDestroy { } this.helpModeService.activateHelpMode(this.dialogButton); } + + @HostListener('window:resize', ['$event']) + onResize() { + const currentComponent = this._elementRef.nativeElement; + const currentComponentRect = currentComponent.getBoundingClientRect(); + this.helpModeButton.style.setProperty('top', `${currentComponentRect.top}px`); + this.helpModeButton.style.setProperty('left', `${currentComponentRect.left}px`); + } } From 5b7dd6a55a9c90e34601068767a4785d23683434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Wed, 29 May 2024 21:01:39 +0300 Subject: [PATCH 112/201] Added clamp-5 mixin --- src/assets/styles/common-texts.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/assets/styles/common-texts.scss b/src/assets/styles/common-texts.scss index 0e9194e..9959674 100644 --- a/src/assets/styles/common-texts.scss +++ b/src/assets/styles/common-texts.scss @@ -149,6 +149,10 @@ pre { @include mixins.line-clamp(4); } +.clamp-5 { + @include mixins.line-clamp(5); +} + .no-wrap { white-space: nowrap; } From 5eef6d403a57c224eec836d9d98787c6a128c50a Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 6 Jun 2024 18:29:48 +0300 Subject: [PATCH 113/201] check for existing help mode button --- src/lib/help-mode/help-button/help-button.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/help-mode/help-button/help-button.component.ts b/src/lib/help-mode/help-button/help-button.component.ts index b086ab7..e7a949f 100644 --- a/src/lib/help-mode/help-button/help-button.component.ts +++ b/src/lib/help-mode/help-button/help-button.component.ts @@ -62,7 +62,7 @@ export class HelpButtonComponent implements OnInit, AfterViewInit, OnDestroy { onResize() { const currentComponent = this._elementRef.nativeElement; const currentComponentRect = currentComponent.getBoundingClientRect(); - this.helpModeButton.style.setProperty('top', `${currentComponentRect.top}px`); - this.helpModeButton.style.setProperty('left', `${currentComponentRect.left}px`); + this.helpModeButton?.style.setProperty('top', `${currentComponentRect.top}px`); + this.helpModeButton?.style.setProperty('left', `${currentComponentRect.left}px`); } } From 04eaca1600149e3f4e803fd2334c25465197dbf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 10 Jun 2024 16:22:48 +0300 Subject: [PATCH 114/201] Moved visibility icons to common-ui --- src/assets/icons/visibility-off.svg | 5 +++++ src/assets/icons/visibility.svg | 5 +++++ src/lib/utils/constants.ts | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 src/assets/icons/visibility-off.svg create mode 100644 src/assets/icons/visibility.svg diff --git a/src/assets/icons/visibility-off.svg b/src/assets/icons/visibility-off.svg new file mode 100644 index 0000000..8b04549 --- /dev/null +++ b/src/assets/icons/visibility-off.svg @@ -0,0 +1,5 @@ + + + + diff --git a/src/assets/icons/visibility.svg b/src/assets/icons/visibility.svg new file mode 100644 index 0000000..b0b727e --- /dev/null +++ b/src/assets/icons/visibility.svg @@ -0,0 +1,5 @@ + + + + diff --git a/src/lib/utils/constants.ts b/src/lib/utils/constants.ts index fa14c78..4335e61 100644 --- a/src/lib/utils/constants.ts +++ b/src/lib/utils/constants.ts @@ -43,4 +43,6 @@ export const ICONS = new Set([ 'thumb-down', 'trash', 'upload', + 'visibility', + 'visibility-off', ]); From 960b434ef6806391bdf6eb12b19c81880fcb758e Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Tue, 11 Jun 2024 18:50:41 +0300 Subject: [PATCH 115/201] RED-9321: made some components standalone, removed upload-file module. --- .../filter-card/filter-card.component.ts | 19 ++++++++++++++++++- src/lib/filtering/filters.module.ts | 4 +--- .../popup-filter/popup-filter.component.ts | 19 +++++++++++++++++++ src/lib/help-mode/help-mode.module.ts | 1 + src/lib/listing/listing.module.ts | 3 ++- .../drag-drop-file-upload.directive.ts | 1 + src/lib/upload-file/index.ts | 1 - src/lib/upload-file/upload-file.component.ts | 6 ++++++ src/lib/upload-file/upload-file.module.ts | 15 --------------- .../initials-avatar.component.ts | 5 +++++ src/lib/users/iqser-users.module.ts | 13 +++++++++++-- src/lib/users/name.pipe.ts | 1 + 12 files changed, 65 insertions(+), 23 deletions(-) delete mode 100644 src/lib/upload-file/upload-file.module.ts diff --git a/src/lib/filtering/filter-card/filter-card.component.ts b/src/lib/filtering/filter-card/filter-card.component.ts index b70fb55..7f1d26e 100644 --- a/src/lib/filtering/filter-card/filter-card.component.ts +++ b/src/lib/filtering/filter-card/filter-card.component.ts @@ -10,7 +10,12 @@ import { Filter } from '../models/filter'; import { map } from 'rxjs/operators'; import { shareDistinctLast, shareLast } from '../../utils'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox'; +import { MAT_CHECKBOX_DEFAULT_OPTIONS, MatCheckbox } from '@angular/material/checkbox'; +import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; +import { InputWithActionComponent } from '../../inputs'; +import { TranslateModule } from '@ngx-translate/core'; +import { MatIcon } from '@angular/material/icon'; +import { StopPropagationDirective } from '../../directives'; const areExpandable = (nestedFilter: INestedFilter) => !!nestedFilter?.children?.length; const atLeastOneIsExpandable = pipe( @@ -44,6 +49,18 @@ export interface LocalStorageFilters { }, }, ], + standalone: true, + imports: [ + AsyncPipe, + InputWithActionComponent, + NgTemplateOutlet, + TranslateModule, + MatIcon, + MatCheckbox, + StopPropagationDirective, + NgIf, + NgForOf, + ], }) export class FilterCardComponent implements OnInit { @Input() primaryFiltersSlug!: string; diff --git a/src/lib/filtering/filters.module.ts b/src/lib/filtering/filters.module.ts index e13a6e8..c774b1f 100644 --- a/src/lib/filtering/filters.module.ts +++ b/src/lib/filtering/filters.module.ts @@ -4,16 +4,14 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatMenuModule } from '@angular/material/menu'; import { TranslateModule } from '@ngx-translate/core'; import { ChevronButtonComponent, IconButtonComponent } from '../buttons'; -import { PopupFilterComponent } from './popup-filter/popup-filter.component'; import { QuickFiltersComponent } from './quick-filters/quick-filters.component'; import { IqserHelpModeModule } from '../help-mode'; import { SingleFilterComponent } from './single-filter/single-filter.component'; -import { FilterCardComponent } from './filter-card/filter-card.component'; import { MatIconModule } from '@angular/material/icon'; import { PreventDefaultDirective, StopPropagationDirective } from '../directives'; import { InputWithActionComponent } from '../inputs'; -const components = [QuickFiltersComponent, PopupFilterComponent, SingleFilterComponent, FilterCardComponent]; +const components = [QuickFiltersComponent, SingleFilterComponent]; @NgModule({ declarations: [...components], diff --git a/src/lib/filtering/popup-filter/popup-filter.component.ts b/src/lib/filtering/popup-filter/popup-filter.component.ts index 3434d39..5733893 100644 --- a/src/lib/filtering/popup-filter/popup-filter.component.ts +++ b/src/lib/filtering/popup-filter/popup-filter.component.ts @@ -5,12 +5,31 @@ import { shareDistinctLast, shareLast, some } from '../../utils'; import { FilterService } from '../filter.service'; import { IFilterGroup } from '../models/filter-group.model'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { ChevronButtonComponent, IconButtonComponent } from '../../buttons'; +import { AsyncPipe, NgIf } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { MatMenu, MatMenuContent, MatMenuTrigger } from '@angular/material/menu'; +import { FilterCardComponent } from '../filter-card/filter-card.component'; +import { StopPropagationDirective } from '../../directives'; @Component({ selector: 'iqser-popup-filter [primaryFiltersSlug]', templateUrl: './popup-filter.component.html', styleUrls: ['./popup-filter.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + IconButtonComponent, + AsyncPipe, + TranslateModule, + MatMenuTrigger, + ChevronButtonComponent, + MatMenu, + FilterCardComponent, + StopPropagationDirective, + MatMenuContent, + NgIf, + ], + standalone: true, }) export class PopupFilterComponent implements OnInit { @Input() primaryFiltersSlug!: string; diff --git a/src/lib/help-mode/help-mode.module.ts b/src/lib/help-mode/help-mode.module.ts index f395c73..3491e76 100644 --- a/src/lib/help-mode/help-mode.module.ts +++ b/src/lib/help-mode/help-mode.module.ts @@ -12,6 +12,7 @@ import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.com const components = [HelpModeComponent, HelpButtonComponent, HelpModeDialogComponent]; +// TODO: Get rid of this, make everything standalone. @NgModule({ declarations: [...components], imports: [CommonModule, TranslateModule, CircleButtonComponent, MatCheckbox, MatIcon], diff --git a/src/lib/listing/listing.module.ts b/src/lib/listing/listing.module.ts index 9192053..4e4595f 100644 --- a/src/lib/listing/listing.module.ts +++ b/src/lib/listing/listing.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; import { TableHeaderComponent } from './table-header/table-header.component'; -import { IqserFiltersModule } from '../filtering'; +import { IqserFiltersModule, PopupFilterComponent } from '../filtering'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TableColumnNameComponent } from './table-column-name/table-column-name.component'; import { ScrollButtonComponent } from './scroll-button/scroll-button.component'; @@ -52,6 +52,7 @@ const modules = [DragDropModule, TranslateModule, IqserFiltersModule, ScrollingM InputWithActionComponent, SyncWidthDirective, SnakeCasePipe, + PopupFilterComponent, ], }) export class IqserListingModule {} diff --git a/src/lib/upload-file/drag-drop-file-upload.directive.ts b/src/lib/upload-file/drag-drop-file-upload.directive.ts index fc73659..6a3383c 100644 --- a/src/lib/upload-file/drag-drop-file-upload.directive.ts +++ b/src/lib/upload-file/drag-drop-file-upload.directive.ts @@ -2,6 +2,7 @@ import { Directive, EventEmitter, HostBinding, HostListener, Output } from '@ang @Directive({ selector: '[iqserDragDropFileUpload]', + standalone: true, }) export class DragDropFileUploadDirective { @Output() readonly fileDropped = new EventEmitter(); diff --git a/src/lib/upload-file/index.ts b/src/lib/upload-file/index.ts index 2eeb4b4..3889b3c 100644 --- a/src/lib/upload-file/index.ts +++ b/src/lib/upload-file/index.ts @@ -1,3 +1,2 @@ export * from './drag-drop-file-upload.directive'; export * from './upload-file.component'; -export * from './upload-file.module'; diff --git a/src/lib/upload-file/upload-file.component.ts b/src/lib/upload-file/upload-file.component.ts index e021dce..5c089f2 100644 --- a/src/lib/upload-file/upload-file.component.ts +++ b/src/lib/upload-file/upload-file.component.ts @@ -1,4 +1,8 @@ import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { DragDropFileUploadDirective } from './drag-drop-file-upload.directive'; +import { MatIcon } from '@angular/material/icon'; +import { NgIf } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; @Component({ selector: 'iqser-upload-file', @@ -6,6 +10,8 @@ import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Ou // eslint-disable-next-line @angular-eslint/no-host-metadata-property host: { '[class.iqser-upload-file]': 'true' }, changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [DragDropFileUploadDirective, MatIcon, NgIf, TranslateModule], }) export class UploadFileComponent { @ViewChild('attachFileInput', { static: true }) attachFileInput!: ElementRef; diff --git a/src/lib/upload-file/upload-file.module.ts b/src/lib/upload-file/upload-file.module.ts deleted file mode 100644 index d2665ac..0000000 --- a/src/lib/upload-file/upload-file.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NgModule } from '@angular/core'; -import { UploadFileComponent } from './upload-file.component'; -import { DragDropFileUploadDirective } from './drag-drop-file-upload.directive'; -import { MatIconModule } from '@angular/material/icon'; -import { CommonModule } from '@angular/common'; -import { TranslateModule } from '@ngx-translate/core'; - -const components = [UploadFileComponent, DragDropFileUploadDirective]; - -@NgModule({ - declarations: [...components], - exports: [...components], - imports: [MatIconModule, CommonModule, TranslateModule], -}) -export class IqserUploadFileModule {} diff --git a/src/lib/users/components/initials-avatar/initials-avatar.component.ts b/src/lib/users/components/initials-avatar/initials-avatar.component.ts index 0e37f98..01300b5 100644 --- a/src/lib/users/components/initials-avatar/initials-avatar.component.ts +++ b/src/lib/users/components/initials-avatar/initials-avatar.component.ts @@ -4,12 +4,17 @@ import { IqserUser } from '../../iqser-user.model'; import { IqserUserService } from '../../services/iqser-user.service'; import { NamePipeOptions } from '../../types/name-pipe-options'; import { IIqserUser } from '../../types/user.response'; +import { NgIf } from '@angular/common'; +import { NamePipe } from '../../name.pipe'; +import { MatTooltip } from '@angular/material/tooltip'; @Component({ selector: 'iqser-initials-avatar', templateUrl: './initials-avatar.component.html', styleUrls: ['./initials-avatar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [NgIf, NamePipe, MatTooltip], }) export class InitialsAvatarComponent implements OnInit, OnChanges diff --git a/src/lib/users/iqser-users.module.ts b/src/lib/users/iqser-users.module.ts index 513c54f..95c3464 100644 --- a/src/lib/users/iqser-users.module.ts +++ b/src/lib/users/iqser-users.module.ts @@ -18,10 +18,19 @@ import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { TranslateModule } from '@ngx-translate/core'; -const components = [NamePipe, InitialsAvatarComponent, UserButtonComponent]; +const components = [UserButtonComponent]; @NgModule({ - imports: [KeycloakAngularModule, MatTooltipModule, CommonModule, MatIconModule, MatButtonModule, TranslateModule], + imports: [ + KeycloakAngularModule, + MatTooltipModule, + CommonModule, + MatIconModule, + MatButtonModule, + TranslateModule, + InitialsAvatarComponent, + NamePipe, + ], declarations: [...components], exports: [...components], }) diff --git a/src/lib/users/name.pipe.ts b/src/lib/users/name.pipe.ts index 22e302a..492606f 100644 --- a/src/lib/users/name.pipe.ts +++ b/src/lib/users/name.pipe.ts @@ -19,6 +19,7 @@ function getInitials(name: string) { @Pipe({ name: 'name', + standalone: true, }) export class NamePipe implements PipeTransform { readonly #translateService = inject(TranslateService); From a764ab1050bba1146f50b5c28d1d0670e7e6b5b6 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Fri, 14 Jun 2024 14:59:33 +0300 Subject: [PATCH 116/201] RED-9321: refactored help mode module. --- src/lib/filtering/filters.module.ts | 2 -- .../help-button/help-button.component.ts | 3 ++ .../help-mode-dialog.component.ts | 5 ++++ src/lib/help-mode/help-mode.module.ts | 28 ------------------- .../help-mode/help-mode.component.ts | 6 +++- src/lib/help-mode/index.ts | 1 - src/lib/help-mode/utils/help-mode.provider.ts | 6 ++++ src/lib/listing/listing.module.ts | 3 +- 8 files changed, 20 insertions(+), 34 deletions(-) delete mode 100644 src/lib/help-mode/help-mode.module.ts create mode 100644 src/lib/help-mode/utils/help-mode.provider.ts diff --git a/src/lib/filtering/filters.module.ts b/src/lib/filtering/filters.module.ts index c774b1f..7667ea0 100644 --- a/src/lib/filtering/filters.module.ts +++ b/src/lib/filtering/filters.module.ts @@ -5,7 +5,6 @@ import { MatMenuModule } from '@angular/material/menu'; import { TranslateModule } from '@ngx-translate/core'; import { ChevronButtonComponent, IconButtonComponent } from '../buttons'; import { QuickFiltersComponent } from './quick-filters/quick-filters.component'; -import { IqserHelpModeModule } from '../help-mode'; import { SingleFilterComponent } from './single-filter/single-filter.component'; import { MatIconModule } from '@angular/material/icon'; import { PreventDefaultDirective, StopPropagationDirective } from '../directives'; @@ -21,7 +20,6 @@ const components = [QuickFiltersComponent, SingleFilterComponent]; MatCheckboxModule, MatMenuModule, TranslateModule, - IqserHelpModeModule, IconButtonComponent, ChevronButtonComponent, MatIconModule, diff --git a/src/lib/help-mode/help-button/help-button.component.ts b/src/lib/help-mode/help-button/help-button.component.ts index e7a949f..b62663f 100644 --- a/src/lib/help-mode/help-button/help-button.component.ts +++ b/src/lib/help-mode/help-button/help-button.component.ts @@ -1,11 +1,14 @@ /* eslint-disable @angular-eslint/prefer-on-push-component-change-detection */ import { AfterViewInit, Component, ElementRef, HostListener, Input, OnDestroy, OnInit } from '@angular/core'; import { HelpModeService } from '../index'; +import { MatIcon } from '@angular/material/icon'; @Component({ selector: 'iqser-help-button', templateUrl: './help-button.component.html', styleUrls: ['./help-button.component.scss'], + standalone: true, + imports: [MatIcon], }) export class HelpButtonComponent implements OnInit, AfterViewInit, OnDestroy { @Input() dialogButton = true; diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts index e1908d1..7c7aac0 100644 --- a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts @@ -1,6 +1,9 @@ import { ChangeDetectionStrategy, Component, HostListener, OnDestroy, OnInit } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; import { Subscription } from 'rxjs'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { CircleButtonComponent } from '../../buttons'; +import { TranslateModule } from '@ngx-translate/core'; const HIGHER_CDK_OVERLAY_CONTAINER_ZINDEX = '1200'; const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; @@ -9,6 +12,8 @@ const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800'; templateUrl: './help-mode-dialog.component.html', styleUrls: ['./help-mode-dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [MatCheckbox, CircleButtonComponent, TranslateModule], }) export class HelpModeDialogComponent implements OnInit, OnDestroy { #backdropClickSubscription: Subscription; diff --git a/src/lib/help-mode/help-mode.module.ts b/src/lib/help-mode/help-mode.module.ts deleted file mode 100644 index 3491e76..0000000 --- a/src/lib/help-mode/help-mode.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ModuleWithProviders, NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { TranslateModule } from '@ngx-translate/core'; -import { HelpModeComponent } from './help-mode/help-mode.component'; -import { HelpButtonComponent } from './help-button/help-button.component'; -import { HelpModeKey, HelpModeService } from './help-mode.service'; -import { CircleButtonComponent } from '../buttons'; -import { HELP_MODE_KEYS } from './tokens'; -import { MatIcon } from '@angular/material/icon'; -import { MatCheckbox } from '@angular/material/checkbox'; -import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; - -const components = [HelpModeComponent, HelpButtonComponent, HelpModeDialogComponent]; - -// TODO: Get rid of this, make everything standalone. -@NgModule({ - declarations: [...components], - imports: [CommonModule, TranslateModule, CircleButtonComponent, MatCheckbox, MatIcon], - exports: [...components], -}) -export class IqserHelpModeModule { - static forRoot(helpModeKeys: HelpModeKey[]): ModuleWithProviders { - return { - ngModule: IqserHelpModeModule, - providers: [{ provide: HELP_MODE_KEYS, useValue: helpModeKeys }, HelpModeService], - }; - } -} diff --git a/src/lib/help-mode/help-mode/help-mode.component.ts b/src/lib/help-mode/help-mode/help-mode.component.ts index ce4ae7e..337a924 100644 --- a/src/lib/help-mode/help-mode/help-mode.component.ts +++ b/src/lib/help-mode/help-mode/help-mode.component.ts @@ -2,13 +2,17 @@ import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core' import { HelpModeService } from '../help-mode.service'; import { IqserEventTarget } from '../../utils'; import { MatDialog } from '@angular/material/dialog'; -import { CircleButtonTypes } from '../../buttons'; +import { CircleButtonComponent, CircleButtonTypes } from '../../buttons'; +import { AsyncPipe, NgIf } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; @Component({ selector: 'iqser-help-mode', templateUrl: './help-mode.component.html', styleUrls: ['./help-mode.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [AsyncPipe, TranslateModule, NgIf, CircleButtonComponent], }) export class HelpModeComponent { readonly circleButtonTypes = CircleButtonTypes; diff --git a/src/lib/help-mode/index.ts b/src/lib/help-mode/index.ts index ab6c4be..bfca8c0 100644 --- a/src/lib/help-mode/index.ts +++ b/src/lib/help-mode/index.ts @@ -1,5 +1,4 @@ export * from './tokens'; -export * from './help-mode.module'; export * from './help-mode.service'; export * from './help-mode/help-mode.component'; export * from './help-button/help-button.component'; diff --git a/src/lib/help-mode/utils/help-mode.provider.ts b/src/lib/help-mode/utils/help-mode.provider.ts new file mode 100644 index 0000000..9ae36fc --- /dev/null +++ b/src/lib/help-mode/utils/help-mode.provider.ts @@ -0,0 +1,6 @@ +import { HelpModeKey, HelpModeService } from '../help-mode.service'; +import { HELP_MODE_KEYS } from '../tokens'; + +export function provideHelpMode(helpModeKeys: HelpModeKey[]) { + return [{ provide: HELP_MODE_KEYS, useValue: helpModeKeys }, HelpModeService]; +} diff --git a/src/lib/listing/listing.module.ts b/src/lib/listing/listing.module.ts index 4e4595f..28da82c 100644 --- a/src/lib/listing/listing.module.ts +++ b/src/lib/listing/listing.module.ts @@ -13,7 +13,6 @@ import { RouterModule } from '@angular/router'; import { WorkflowComponent } from './workflow/workflow.component'; import { DragDropModule } from '@angular/cdk/drag-drop'; import { PageHeaderComponent } from './page-header/page-header.component'; -import { IqserHelpModeModule } from '../help-mode'; import { TableContentComponent } from './table-content/table-content.component'; import { TableItemComponent } from './table-content/table-item/table-item.component'; import { ColumnHeaderComponent } from './workflow/column-header/column-header.component'; @@ -35,7 +34,7 @@ const components = [ TableItemComponent, ColumnHeaderComponent, ]; -const modules = [DragDropModule, TranslateModule, IqserFiltersModule, ScrollingModule, RouterModule, IqserHelpModeModule]; +const modules = [DragDropModule, TranslateModule, IqserFiltersModule, ScrollingModule, RouterModule]; @NgModule({ declarations: [...components], From 40c6f881fc589bd2041939cc2f2c088f40a09e36 Mon Sep 17 00:00:00 2001 From: Nicoleta Panaghiu Date: Fri, 14 Jun 2024 15:06:29 +0300 Subject: [PATCH 117/201] RED-9321: add new file to index. --- src/lib/help-mode/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/help-mode/index.ts b/src/lib/help-mode/index.ts index bfca8c0..f8a16c0 100644 --- a/src/lib/help-mode/index.ts +++ b/src/lib/help-mode/index.ts @@ -4,3 +4,4 @@ export * from './help-mode/help-mode.component'; export * from './help-button/help-button.component'; export * from './help-mode-dialog/help-mode-dialog.component'; export * from './utils/constants'; +export * from './utils/help-mode.provider'; From aec5da15dfcaccda7ecac3d927170da0de4fac96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 17 Jun 2024 12:18:25 +0300 Subject: [PATCH 118/201] Simple popup filter --- src/lib/filtering/index.ts | 1 + .../simple-popup-filter.component.html | 53 +++++++++++++++ .../simple-popup-filter.component.scss | 24 +++++++ .../simple-popup-filter.component.ts | 67 +++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html create mode 100644 src/lib/filtering/simple-popup-filter/simple-popup-filter.component.scss create mode 100644 src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts diff --git a/src/lib/filtering/index.ts b/src/lib/filtering/index.ts index 75e5952..6e2c92f 100644 --- a/src/lib/filtering/index.ts +++ b/src/lib/filtering/index.ts @@ -11,3 +11,4 @@ export * from './models/nested-filter.model'; export * from './popup-filter/popup-filter.component'; export * from './quick-filters/quick-filters.component'; +export * from './simple-popup-filter/simple-popup-filter.component'; diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html new file mode 100644 index 0000000..4ff55ee --- /dev/null +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html @@ -0,0 +1,53 @@ + + + + + + + + + +
+ +
+ +
+ +
+
+
+
+
+
+
+ +
+
+ + {{ option.label }} + +
+
+
+
+
diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.scss b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.scss new file mode 100644 index 0000000..1ca41d8 --- /dev/null +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.scss @@ -0,0 +1,24 @@ +@use 'common-mixins'; + +.filter-menu-options, +.filter-menu-header { + display: flex; + justify-content: space-between; + padding: 8px 16px 16px 16px; + min-width: var(--filter-card-min-width); + + .actions { + display: flex; + gap: 8px; + } +} + +.input-wrapper { + padding: 0 8px 8px 8px; +} + +.filter-content { + max-height: 300px; + overflow: auto; + @include common-mixins.scroll-bar; +} diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts new file mode 100644 index 0000000..765990b --- /dev/null +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts @@ -0,0 +1,67 @@ +import { Component, computed, input, output, signal, untracked } from '@angular/core'; +import { MatMenuModule } from '@angular/material/menu'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; +import { ChevronButtonComponent, IconButtonComponent } from '../../buttons'; +import { StopPropagationDirective } from '../../directives'; +import { InputWithActionComponent } from '../../inputs'; + +@Component({ + selector: 'iqser-simple-popup-filter', + templateUrl: './simple-popup-filter.component.html', + styleUrls: ['./simple-popup-filter.component.scss'], + standalone: true, + imports: [ + CommonModule, + MatMenuModule, + IconButtonComponent, + ChevronButtonComponent, + StopPropagationDirective, + InputWithActionComponent, + TranslateModule, + MatCheckbox, + IconButtonComponent, + ChevronButtonComponent, + ], +}) +export class SimplePopupFilterComponent { + options = input.required(); + icon = input(); + label = input(); + filterPlaceholder = input.required(); + disabled = input(false); + selectionChanged = output(); + + readonly expanded = signal(false); + readonly selectedOptions = signal([]); + readonly hasActiveFilters = computed(() => this.selectedOptions().length > 0); + readonly searchValue = signal(''); + readonly displayedOptions = computed(() => + this.options().filter(option => option.label.toLowerCase().includes(this.searchValue().toLowerCase())), + ); + + constructor() { + toObservable(this.selectedOptions) + .pipe(takeUntilDestroyed()) + // eslint-disable-next-line rxjs/no-ignored-subscription + .subscribe(() => this.selectionChanged.emit(this.selectedOptions())); + } + + protected _selectAll(): void { + this.selectedOptions.set(untracked(this.options)); + } + + protected _clear(): void { + this.selectedOptions.set([]); + } + + protected _filterCheckboxClicked(option: T): void { + if (this.selectedOptions().includes(option)) { + this.selectedOptions.set(this.selectedOptions().filter(selectedOption => selectedOption !== option)); + } else { + this.selectedOptions.set([...this.selectedOptions(), option]); + } + } +} From 4c0437d7fc9526f5d914baedb56b7cd7e789b05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 17 Jun 2024 15:29:26 +0300 Subject: [PATCH 119/201] Simple popup filter improvements --- src/assets/icons/filter-list.svg | 3 ++ .../filtering/models/simple-filter-option.ts | 4 ++ .../simple-popup-filter.component.html | 50 +++++++++++-------- .../simple-popup-filter.component.ts | 48 ++++++++++++------ src/lib/utils/constants.ts | 1 + 5 files changed, 69 insertions(+), 37 deletions(-) create mode 100644 src/assets/icons/filter-list.svg create mode 100644 src/lib/filtering/models/simple-filter-option.ts diff --git a/src/assets/icons/filter-list.svg b/src/assets/icons/filter-list.svg new file mode 100644 index 0000000..914e2ad --- /dev/null +++ b/src/assets/icons/filter-list.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/lib/filtering/models/simple-filter-option.ts b/src/lib/filtering/models/simple-filter-option.ts new file mode 100644 index 0000000..3feee31 --- /dev/null +++ b/src/lib/filtering/models/simple-filter-option.ts @@ -0,0 +1,4 @@ +export interface SimpleFilterOption { + value: T; + label: string; +} diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html index 4ff55ee..e8d52be 100644 --- a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html @@ -1,25 +1,33 @@ - - - + - - - + + +
diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts index 765990b..5f85505 100644 --- a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts @@ -1,12 +1,12 @@ -import { Component, computed, input, output, signal, untracked } from '@angular/core'; +import { Component, computed, effect, input, output, signal, untracked } from '@angular/core'; import { MatMenuModule } from '@angular/material/menu'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; import { MatCheckbox } from '@angular/material/checkbox'; -import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; -import { ChevronButtonComponent, IconButtonComponent } from '../../buttons'; +import { ChevronButtonComponent, CircleButtonComponent, IconButtonComponent } from '../../buttons'; import { StopPropagationDirective } from '../../directives'; import { InputWithActionComponent } from '../../inputs'; +import { SimpleFilterOption } from '../models/simple-filter-option'; @Component({ selector: 'iqser-simple-popup-filter', @@ -24,18 +24,20 @@ import { InputWithActionComponent } from '../../inputs'; MatCheckbox, IconButtonComponent, ChevronButtonComponent, + CircleButtonComponent, ], }) -export class SimplePopupFilterComponent { - options = input.required(); - icon = input(); - label = input(); - filterPlaceholder = input.required(); - disabled = input(false); - selectionChanged = output(); +export class SimplePopupFilterComponent { + readonly options = input.required[]>(); + readonly icon = input(); + readonly label = input(); + readonly filterPlaceholder = input.required(); + readonly disabled = input(false); + readonly type = input<'text' | 'icon'>('text'); + readonly selectionChanged = output[]>(); readonly expanded = signal(false); - readonly selectedOptions = signal([]); + readonly selectedOptions = signal[]>([]); readonly hasActiveFilters = computed(() => this.selectedOptions().length > 0); readonly searchValue = signal(''); readonly displayedOptions = computed(() => @@ -43,10 +45,24 @@ export class SimplePopupFilterComponent { ); constructor() { - toObservable(this.selectedOptions) - .pipe(takeUntilDestroyed()) - // eslint-disable-next-line rxjs/no-ignored-subscription - .subscribe(() => this.selectionChanged.emit(this.selectedOptions())); + effect(() => { + this.selectionChanged.emit(this.selectedOptions()); + }); + + /** If the options change and the selected options are not in the new options, remove them. */ + effect( + () => { + const allOptions = this.options(); + const selectedOptions = untracked(this.selectedOptions); + + if (selectedOptions.some(selectedOption => !allOptions.find(o => o.value === selectedOption))) { + this.selectedOptions.set( + selectedOptions.filter(selectedOption => allOptions.find(o => o.value === selectedOption.value)), + ); + } + }, + { allowSignalWrites: true }, + ); } protected _selectAll(): void { @@ -57,7 +73,7 @@ export class SimplePopupFilterComponent { this.selectedOptions.set([]); } - protected _filterCheckboxClicked(option: T): void { + protected _filterCheckboxClicked(option: SimpleFilterOption): void { if (this.selectedOptions().includes(option)) { this.selectedOptions.set(this.selectedOptions().filter(selectedOption => selectedOption !== option)); } else { diff --git a/src/lib/utils/constants.ts b/src/lib/utils/constants.ts index 4335e61..b1e2bc5 100644 --- a/src/lib/utils/constants.ts +++ b/src/lib/utils/constants.ts @@ -15,6 +15,7 @@ export const ICONS = new Set([ 'edit', 'expand', 'failure', + 'filter-list', 'help-outline', 'lanes', 'list', From e165ba7500077075dff69ed679cfe7baa964566c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 17 Jun 2024 15:59:51 +0300 Subject: [PATCH 120/201] Clamp filter values --- .../simple-popup-filter/simple-popup-filter.component.html | 2 +- .../simple-popup-filter/simple-popup-filter.component.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html index e8d52be..726d322 100644 --- a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html @@ -52,7 +52,7 @@
- {{ option.label }} + {{ option.label }}
diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.scss b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.scss index 1ca41d8..6131c98 100644 --- a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.scss +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.scss @@ -19,6 +19,7 @@ .filter-content { max-height: 300px; + max-width: 500px; overflow: auto; @include common-mixins.scroll-bar; } From 213cabce7a65ac0cdf89d1578f442b304e5a97e1 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 17 Jun 2024 19:15:43 +0300 Subject: [PATCH 121/201] ng 18 --- src/lib/error/server-error-interceptor.ts | 10 +--------- src/lib/services/composite-route.guard.ts | 17 +++++++++-------- src/lib/translations/iqser-translate.module.ts | 12 +++--------- 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/lib/error/server-error-interceptor.ts b/src/lib/error/server-error-interceptor.ts index 565d5cf..813331b 100644 --- a/src/lib/error/server-error-interceptor.ts +++ b/src/lib/error/server-error-interceptor.ts @@ -1,12 +1,4 @@ -import { - HttpErrorResponse, - HttpEvent, - HttpHandler, - HttpInterceptor, - HttpRequest, - HttpResponse, - HttpStatusCode, -} from '@angular/common/http'; +import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse, HttpStatusCode } from '@angular/common/http'; import { Inject, Injectable, Optional } from '@angular/core'; import { finalize, MonoTypeOperatorFunction, Observable, retry, throwError, timer } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; diff --git a/src/lib/services/composite-route.guard.ts b/src/lib/services/composite-route.guard.ts index 2204906..d27db8a 100644 --- a/src/lib/services/composite-route.guard.ts +++ b/src/lib/services/composite-route.guard.ts @@ -1,9 +1,9 @@ -import { ActivatedRouteSnapshot, CanActivate, CanActivateFn, RouterStateSnapshot, UrlTree } from '@angular/router'; import { inject, Injectable, InjectionToken, Injector, runInInjectionContext } from '@angular/core'; -import { concatMap, firstValueFrom, from, last, of, takeWhile } from 'rxjs'; +import { ActivatedRouteSnapshot, CanActivate, CanActivateFn, GuardResult, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { concatMap, firstValueFrom, from, last, Observable, of, takeWhile } from 'rxjs'; +import { tap } from 'rxjs/operators'; import { LoadingService } from '../loading'; import { SkeletonService } from './skeleton.service'; -import { tap } from 'rxjs/operators'; @Injectable({ providedIn: 'root', @@ -23,12 +23,13 @@ export class CompositeRouteGuard implements CanActivate { if (routeGuards) { for (let i = 0; i < routeGuards.length; i++) { const routeGuard = this._injector.get(routeGuards[i]); - let canActivateResult = routeGuard.canActivate(route, state); - if (canActivateResult instanceof Promise) { - canActivateResult = from(canActivateResult); + const canActivate = routeGuard.canActivate(route, state); + let canActivateResult: Observable = of(true); + if (canActivate instanceof Promise) { + canActivateResult = from(canActivate); } - if (typeof canActivateResult === 'boolean' || canActivateResult instanceof UrlTree) { - canActivateResult = of(canActivateResult); + if (typeof canActivate === 'boolean' || canActivate instanceof UrlTree) { + canActivateResult = of(canActivate); } const result = await firstValueFrom(canActivateResult); diff --git a/src/lib/translations/iqser-translate.module.ts b/src/lib/translations/iqser-translate.module.ts index 2023cde..5cc74f1 100644 --- a/src/lib/translations/iqser-translate.module.ts +++ b/src/lib/translations/iqser-translate.module.ts @@ -4,14 +4,11 @@ import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-comp import { pruningTranslationLoaderFactory } from './http-loader-factory'; import { IqserTranslateModuleOptions } from './iqser-translate-module-options'; import { IqserTranslateParser } from './iqser-translate-parser.service'; -import { HttpClientModule } from '@angular/common/http'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; const translateLoaderToken = new InjectionToken('translateLoader'); -@NgModule({ - imports: [ - HttpClientModule, - TranslateModule.forRoot({ +@NgModule({ exports: [TranslateModule], imports: [TranslateModule.forRoot({ loader: { provide: TranslateLoader, useExisting: translateLoaderToken, @@ -24,10 +21,7 @@ const translateLoaderToken = new InjectionToken('translateLoader'); provide: TranslateParser, useClass: IqserTranslateParser, }, - }), - ], - exports: [TranslateModule], -}) + })], providers: [provideHttpClient(withInterceptorsFromDi())] }) export class IqserTranslateModule { constructor() { const translateLoader = inject(translateLoaderToken, { optional: true }); From 76771023c22f6b69686d4d783e328b909a56c789 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 17 Jun 2024 19:18:01 +0300 Subject: [PATCH 122/201] lint --- src/lib/error/server-error-interceptor.ts | 10 +++++++++- src/lib/translations/iqser-translate.module.ts | 10 ++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/error/server-error-interceptor.ts b/src/lib/error/server-error-interceptor.ts index 813331b..565d5cf 100644 --- a/src/lib/error/server-error-interceptor.ts +++ b/src/lib/error/server-error-interceptor.ts @@ -1,4 +1,12 @@ -import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse, HttpStatusCode } from '@angular/common/http'; +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpResponse, + HttpStatusCode, +} from '@angular/common/http'; import { Inject, Injectable, Optional } from '@angular/core'; import { finalize, MonoTypeOperatorFunction, Observable, retry, throwError, timer } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; diff --git a/src/lib/translations/iqser-translate.module.ts b/src/lib/translations/iqser-translate.module.ts index 5cc74f1..48d6627 100644 --- a/src/lib/translations/iqser-translate.module.ts +++ b/src/lib/translations/iqser-translate.module.ts @@ -8,7 +8,10 @@ import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' const translateLoaderToken = new InjectionToken('translateLoader'); -@NgModule({ exports: [TranslateModule], imports: [TranslateModule.forRoot({ +@NgModule({ + exports: [TranslateModule], + imports: [ + TranslateModule.forRoot({ loader: { provide: TranslateLoader, useExisting: translateLoaderToken, @@ -21,7 +24,10 @@ const translateLoaderToken = new InjectionToken('translateLoader'); provide: TranslateParser, useClass: IqserTranslateParser, }, - })], providers: [provideHttpClient(withInterceptorsFromDi())] }) + }), + ], + providers: [provideHttpClient(withInterceptorsFromDi())], +}) export class IqserTranslateModule { constructor() { const translateLoader = inject(translateLoaderToken, { optional: true }); From 0738d8c4ef7cbc03071124d2a76a70d428d11781 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 17 Jun 2024 21:26:02 +0300 Subject: [PATCH 123/201] migrate buttons to signal inputs --- .../chevron-button.component.html | 8 ++- .../chevron-button.component.ts | 17 +++--- .../circle-button.component.html | 28 +++++---- .../circle-button/circle-button.component.ts | 61 +++++++++++-------- .../icon-button/icon-button.component.html | 21 ++++--- .../icon-button/icon-button.component.ts | 43 +++++++------ 6 files changed, 100 insertions(+), 78 deletions(-) diff --git a/src/lib/buttons/chevron-button/chevron-button.component.html b/src/lib/buttons/chevron-button/chevron-button.component.html index 84abab7..6993a73 100644 --- a/src/lib/buttons/chevron-button/chevron-button.component.html +++ b/src/lib/buttons/chevron-button/chevron-button.component.html @@ -1,6 +1,8 @@ - -
+@if (showDot()) { +
+} diff --git a/src/lib/buttons/chevron-button/chevron-button.component.ts b/src/lib/buttons/chevron-button/chevron-button.component.ts index 1014673..e795c39 100644 --- a/src/lib/buttons/chevron-button/chevron-button.component.ts +++ b/src/lib/buttons/chevron-button/chevron-button.component.ts @@ -1,8 +1,7 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { randomString } from '../../utils'; -import { NgIf } from '@angular/common'; +import { booleanAttribute, ChangeDetectionStrategy, Component, input } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; +import { randomString } from '../../utils'; @Component({ selector: 'iqser-chevron-button', @@ -10,12 +9,12 @@ import { MatIconModule } from '@angular/material/icon'; styleUrls: ['./chevron-button.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [NgIf, MatIconModule, MatButtonModule], + imports: [MatIconModule, MatButtonModule], }) export class ChevronButtonComponent { - @Input({ required: true }) label!: string; - @Input() showDot = false; - @Input() primary = false; - @Input() disabled = false; - @Input() buttonId = `${randomString()}-chevron-button`; + readonly label = input.required(); + readonly showDot = input(false, { transform: booleanAttribute }); + readonly primary = input(false, { transform: booleanAttribute }); + readonly disabled = input(false, { transform: booleanAttribute }); + readonly buttonId = input(`${randomString()}-chevron-button`); } diff --git a/src/lib/buttons/circle-button/circle-button.component.html b/src/lib/buttons/circle-button/circle-button.component.html index ba7e816..61b3d2a 100644 --- a/src/lib/buttons/circle-button/circle-button.component.html +++ b/src/lib/buttons/circle-button/circle-button.component.html @@ -1,21 +1,25 @@ -
+
-
+ @if (showDot()) { +
+ } -
+ @if (dropdownButton()) { +
+ }
diff --git a/src/lib/buttons/circle-button/circle-button.component.ts b/src/lib/buttons/circle-button/circle-button.component.ts index 3e8e6ca..c727b04 100644 --- a/src/lib/buttons/circle-button/circle-button.component.ts +++ b/src/lib/buttons/circle-button/circle-button.component.ts @@ -1,5 +1,16 @@ -import { NgIf } from '@angular/common'; -import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, inject, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { + booleanAttribute, + ChangeDetectionStrategy, + Component, + effect, + ElementRef, + EventEmitter, + inject, + input, + numberAttribute, + Output, + viewChild, +} from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; @@ -14,37 +25,39 @@ import { CircleButtonType, CircleButtonTypes } from '../types/circle-button.type styleUrls: ['./circle-button.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [MatTooltipModule, MatIconModule, NgIf, MatButtonModule, StopPropagationDirective], + imports: [MatTooltipModule, MatIconModule, MatButtonModule, StopPropagationDirective], }) -export class CircleButtonComponent implements OnInit { +export class CircleButtonComponent { readonly #elementRef = inject(ElementRef); - @ViewChild(MatTooltip) private readonly _matTooltip!: MatTooltip; + protected readonly _matTooltip = viewChild.required(MatTooltip); protected readonly _circleButtonTypes = CircleButtonTypes; protected readonly _hasRouterLink = !!inject(RouterLink, { optional: true, host: true }); - @Input() buttonId = `${randomString()}-circle-button`; - @Input({ required: true }) icon!: string; - @Input() tooltip?: string; - @Input() tooltipClass?: string; - @Input() showDot = false; - @Input() tooltipPosition: IqserTooltipPosition = IqserTooltipPositions.above; - @Input() disabled = false; - @Input() type: CircleButtonType = CircleButtonTypes.default; - @Input() greySelected = false; - @Input() removeTooltip = false; - @Input() isSubmit = false; - @Input() dropdownButton = false; - @Input() size = 34; - @Input() iconSize = 14; + readonly buttonId = input(`${randomString()}-circle-button`); + readonly icon = input.required(); + readonly tooltip = input(''); + readonly tooltipClass = input(''); + readonly showDot = input(false, { transform: booleanAttribute }); + readonly tooltipPosition = input(IqserTooltipPositions.above); + readonly disabled = input(false, { transform: booleanAttribute }); + readonly type = input(CircleButtonTypes.default); + readonly greySelected = input(false, { transform: booleanAttribute }); + readonly removeTooltip = input(false, { transform: booleanAttribute }); + readonly isSubmit = input(false, { transform: booleanAttribute }); + readonly dropdownButton = input(false, { transform: booleanAttribute }); + readonly size = input(34, { transform: numberAttribute }); + readonly iconSize = input(14, { transform: numberAttribute }); @Output() readonly action = new EventEmitter(); - ngOnInit() { - this.#elementRef.nativeElement.style.setProperty('--circle-button-size', `${this.size}px`); - this.#elementRef.nativeElement.style.setProperty('--circle-button-icon-size', `${this.iconSize}px`); + constructor() { + effect(() => { + this.#elementRef.nativeElement.style.setProperty('--circle-button-size', `${this.size()}px`); + this.#elementRef.nativeElement.style.setProperty('--circle-button-icon-size', `${this.iconSize()}px`); + }); } performAction($event: MouseEvent) { - if (this.removeTooltip) { - this._matTooltip.hide(); + if (this.removeTooltip()) { + this._matTooltip().hide(); // Timeout to allow tooltip to disappear first, // useful when removing an item from the list without a confirmation dialog setTimeout(() => this.action.emit($event)); diff --git a/src/lib/buttons/icon-button/icon-button.component.html b/src/lib/buttons/icon-button/icon-button.component.html index d9d4967..703af59 100644 --- a/src/lib/buttons/icon-button/icon-button.component.html +++ b/src/lib/buttons/icon-button/icon-button.component.html @@ -1,14 +1,19 @@ -
+@if (showDot()) { +
+} diff --git a/src/lib/buttons/icon-button/icon-button.component.ts b/src/lib/buttons/icon-button/icon-button.component.ts index 6f2c018..940eda0 100644 --- a/src/lib/buttons/icon-button/icon-button.component.ts +++ b/src/lib/buttons/icon-button/icon-button.component.ts @@ -1,37 +1,36 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output } from '@angular/core'; -import { IconButtonType, IconButtonTypes } from '../types/icon-button.type'; -import { randomString } from '../../utils'; -import { NgClass, NgIf } from '@angular/common'; +import { NgClass } from '@angular/common'; +import { booleanAttribute, ChangeDetectionStrategy, Component, computed, EventEmitter, inject, input, Output } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; -import { StopPropagationDirective } from '../../directives'; import { RouterLink } from '@angular/router'; +import { StopPropagationDirective } from '../../directives'; +import { randomString } from '../../utils'; +import { IconButtonType, IconButtonTypes } from '../types/icon-button.type'; @Component({ selector: 'iqser-icon-button', templateUrl: './icon-button.component.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [NgClass, MatButtonModule, NgIf, MatIconModule, StopPropagationDirective], + imports: [NgClass, MatButtonModule, MatIconModule, StopPropagationDirective], }) export class IconButtonComponent { - @Input({ required: true }) label!: string; - @Input() buttonId = `${randomString()}-icon-button`; - @Input() icon?: string; - @Input() showDot = false; - @Input() active = false; - @Input() disabled = false; - @Input() submit = false; - @Input() type: IconButtonType = IconButtonTypes.default; - @Output() readonly action = new EventEmitter(); protected readonly _hasRouterLink = !!inject(RouterLink, { optional: true, host: true }); - - get classes(): Record { + readonly label = input.required(); + readonly buttonId = input(`${randomString()}-icon-button`); + readonly icon = input(); + readonly showDot = input(false, { transform: booleanAttribute }); + readonly active = input(false, { transform: booleanAttribute }); + readonly disabled = input(false, { transform: booleanAttribute }); + readonly submit = input(false, { transform: booleanAttribute }); + readonly type = input(IconButtonTypes.default); + protected readonly _classes = computed(() => { return { - overlay: this.showDot, - [this.type]: true, - 'has-icon': !!this.icon, - active: this.active, + overlay: this.showDot(), + [this.type()]: true, + 'has-icon': !!this.icon(), + active: this.active(), }; - } + }); + @Output() readonly action = new EventEmitter(); } From 0b64044f576f38e4bb79033563a20400a62c15b6 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 17 Jun 2024 21:27:16 +0300 Subject: [PATCH 124/201] lint --- src/lib/empty-state/empty-state.component.html | 2 +- src/lib/filtering/filter-card/filter-card.component.html | 4 ++-- src/lib/shared/status-bar/status-bar.component.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/empty-state/empty-state.component.html b/src/lib/empty-state/empty-state.component.html index 49e188f..3bb9aa4 100644 --- a/src/lib/empty-state/empty-state.component.html +++ b/src/lib/empty-state/empty-state.component.html @@ -2,7 +2,7 @@ [ngStyle]="{ 'padding-top': verticalPadding + 'px', 'padding-left': horizontalPadding + 'px', - 'padding-right': horizontalPadding + 'px' + 'padding-right': horizontalPadding + 'px', }" class="empty-state" > diff --git a/src/lib/filtering/filter-card/filter-card.component.html b/src/lib/filtering/filter-card/filter-card.component.html index a0a8f11..a29dcd4 100644 --- a/src/lib/filtering/filter-card/filter-card.component.html +++ b/src/lib/filtering/filter-card/filter-card.component.html @@ -16,7 +16,7 @@ [ngTemplateOutletContext]="{ filter: filter, filterGroup: primaryGroup, - atLeastOneIsExpandable: atLeastOneFilterIsExpandable$ | async + atLeastOneIsExpandable: atLeastOneFilterIsExpandable$ | async, }" [ngTemplateOutlet]="defaultFilterTemplate" > @@ -32,7 +32,7 @@ [ngTemplateOutletContext]="{ filter: filter, filterGroup: secondaryGroup, - atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable$ | async + atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable$ | async, }" [ngTemplateOutlet]="defaultFilterTemplate" > diff --git a/src/lib/shared/status-bar/status-bar.component.html b/src/lib/shared/status-bar/status-bar.component.html index 804d6d8..2234a81 100644 --- a/src/lib/shared/status-bar/status-bar.component.html +++ b/src/lib/shared/status-bar/status-bar.component.html @@ -3,7 +3,7 @@
From f60ea513ac88456f5003ea85c1158ff73fdd3d06 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Tue, 18 Jun 2024 10:22:27 +0300 Subject: [PATCH 125/201] migrate control flow --- .eslintrc.js | 2 +- .../confirmation-dialog.component.html | 77 ++++---- .../confirmation-dialog.component.ts | 4 +- .../empty-state/empty-state.component.html | 23 ++- src/lib/empty-state/empty-state.component.ts | 4 +- .../connection-status.component.html | 13 +- .../full-page-error.component.html | 12 +- .../filter-card/filter-card.component.html | 161 +++++++++------- .../popup-filter/popup-filter.component.html | 14 +- .../quick-filters.component.html | 25 +-- .../simple-popup-filter.component.html | 75 ++++---- .../simple-popup-filter.component.ts | 3 +- .../help-mode/help-mode.component.html | 38 ++-- .../details-radio.component.html | 94 ++++----- .../details-radio/details-radio.component.ts | 4 +- .../dynamic-input.component.html | 41 ++-- .../dynamic-input/dynamic-input.component.ts | 4 +- .../editable-input.component.html | 52 +++-- .../editable-input.component.ts | 3 +- .../input-with-action.component.html | 32 ++-- .../input-with-action.component.ts | 4 +- .../round-checkbox.component.html | 8 +- .../round-checkbox.component.ts | 3 +- .../page-header/page-header.component.html | 178 +++++++++--------- .../table-column-name.component.html | 26 ++- .../table-content.component.html | 35 ++-- .../table-item/table-item.component.html | 8 +- .../table-header/table-header.component.html | 76 ++++---- src/lib/listing/table/table.component.html | 42 +++-- .../column-header.component.html | 81 ++++---- .../listing/workflow/workflow.component.html | 137 +++++++------- ...full-page-loading-indicator.component.html | 14 +- .../progress-bar/progress-bar.component.ts | 4 +- .../progress-loading.component.html | 16 +- src/lib/pagination/pagination.component.html | 25 +-- src/lib/pagination/pagination.component.ts | 4 +- src/lib/permissions/README.md | 128 +++++++------ .../directives/deny.directive.spec.ts | 2 +- .../shared/skeleton/skeleton.component.html | 4 +- src/lib/shared/skeleton/skeleton.component.ts | 4 +- .../status-bar/status-bar.component.html | 25 +-- .../shared/status-bar/status-bar.component.ts | 4 +- src/lib/shared/toast/toast.component.html | 42 +++-- src/lib/shared/toast/toast.component.ts | 4 +- .../tenant-select.component.html | 48 ++--- .../upload-file/upload-file.component.html | 29 +-- .../initials-avatar.component.html | 27 +-- .../user-button/user-button.component.html | 24 ++- tsconfig.spec.json | 2 +- 49 files changed, 901 insertions(+), 784 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ce21309..b5dd005 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,7 @@ module.exports = { globals: { NodeJS: true, }, - ignorePatterns: ['!**/*'], + ignorePatterns: ['!**/*', 'jest.config.ts'], overrides: [ { files: ['*.ts'], diff --git a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html index 1123f7e..6a1ff38 100644 --- a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html +++ b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html @@ -1,30 +1,38 @@
-
-
- - - -
+ @if (showToast && config.toastMessage) { +
+
+ + + +
+ }

-

+ @if (config.details) { +

+ } -
- - -
+ @if (config.requireInput) { +
+ + +
+ } -
- - - {{ checkbox.label | translate: config.translateParams }} - - - -
+ @if (config.checkboxes.length > 0) { +
+ @for (checkbox of config.checkboxes; track checkbox) { + + {{ checkbox.label | translate: config.translateParams }} + + + } +
+ }
@@ -36,21 +44,26 @@ buttonId="confirm" > - + @if (config.alternativeConfirmationText) { + + } -
- {{ config.discardChangesText }} -
+ @if (config.discardChangesText) { +
+ {{ config.discardChangesText }} +
+ } -
- {{ config.denyText }} -
+ @if (!config.discardChangesText) { +
+ {{ config.denyText }} +
+ }
diff --git a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts index 8cc9279..456347f 100644 --- a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts +++ b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts @@ -1,4 +1,4 @@ -import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; +import { NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, HostListener, inject, TemplateRef } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -72,10 +72,8 @@ function getConfig(options?: IConfirmationDialogData): InternalConfirmationDialo changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ - NgIf, MatIconModule, FormsModule, - NgForOf, MatCheckboxModule, TranslateModule, NgTemplateOutlet, diff --git a/src/lib/empty-state/empty-state.component.html b/src/lib/empty-state/empty-state.component.html index 3bb9aa4..b5bef39 100644 --- a/src/lib/empty-state/empty-state.component.html +++ b/src/lib/empty-state/empty-state.component.html @@ -6,7 +6,9 @@ }" class="empty-state" > - + @if (icon) { + + }
@@ -14,13 +16,14 @@
- + @if (showButton) { + + }
diff --git a/src/lib/empty-state/empty-state.component.ts b/src/lib/empty-state/empty-state.component.ts index f6c7c59..92bc0e3 100644 --- a/src/lib/empty-state/empty-state.component.ts +++ b/src/lib/empty-state/empty-state.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { IconButtonComponent, IconButtonTypes } from '../buttons'; import { randomString } from '../utils'; -import { NgIf, NgStyle } from '@angular/common'; +import { NgStyle } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; @Component({ @@ -10,7 +10,7 @@ import { MatIconModule } from '@angular/material/icon'; styleUrls: ['./empty-state.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [NgStyle, MatIconModule, NgIf, IconButtonComponent], + imports: [NgStyle, MatIconModule, IconButtonComponent], }) export class EmptyStateComponent implements OnInit { readonly iconButtonTypes = IconButtonTypes; diff --git a/src/lib/error/connection-status/connection-status.component.html b/src/lib/error/connection-status/connection-status.component.html index 45b1490..93e83aa 100644 --- a/src/lib/error/connection-status/connection-status.component.html +++ b/src/lib/error/connection-status/connection-status.component.html @@ -1,8 +1,5 @@ -
- -
+@if (errorService.connectionStatus$ | async; as status) { +
+ +
+} diff --git a/src/lib/error/full-page-error/full-page-error.component.html b/src/lib/error/full-page-error/full-page-error.component.html index eb9b099..cf35e6b 100644 --- a/src/lib/error/full-page-error/full-page-error.component.html +++ b/src/lib/error/full-page-error/full-page-error.component.html @@ -1,13 +1,11 @@ - +@if (errorService.error$ | async; as error) {
-
-
- -
{{ error.message }}
- + @if (error.message) { +
{{ error.message }}
+ }
-
+} diff --git a/src/lib/filtering/filter-card/filter-card.component.html b/src/lib/filtering/filter-card/filter-card.component.html index a29dcd4..127487e 100644 --- a/src/lib/filtering/filter-card/filter-card.component.html +++ b/src/lib/filtering/filter-card/filter-card.component.html @@ -1,43 +1,47 @@ - -
- -
- - - -
- -
- -
-
-
+@if (primaryFilterGroup$ | async; as primaryGroup) { + @if (primaryGroup.filterceptionPlaceholder) { +
+
- - -
- + } + + @if (primaryFilters$ | async; as filters) { +
+ @for (filter of filters; track filter) { + + } +
+ } + @if (secondaryFilterGroup$ | async; as secondaryGroup) { +
+
+
+
+ @for (filter of secondaryGroup.filters; track filter) { + + } +
+ } +} {{ filter?.label }} @@ -45,30 +49,46 @@ -
-
-
-
-
+ @if (primaryFilterGroup$ | async; as primaryGroup) { +
+
+
+ @if (!primaryGroup.singleSelect) { +
+ } +
+
-
+ }
-
- - -
+ @if (filter.children?.length > 0) { +
+ @if (filter.expanded) { + + } + @if (!filter.expanded) { + + } +
+ } -
 
+ @if (atLeastOneIsExpandable && filter.children?.length === 0) { +
 
+ }
-
-
- - - - - + @if (filter.children?.length && filter.expanded) { +
+ @for (child of filter.children; track child) { +
+ + + + +
+ }
-
+ } diff --git a/src/lib/filtering/popup-filter/popup-filter.component.html b/src/lib/filtering/popup-filter/popup-filter.component.html index 85ddf2c..98b134f 100644 --- a/src/lib/filtering/popup-filter/popup-filter.component.html +++ b/src/lib/filtering/popup-filter/popup-filter.component.html @@ -1,5 +1,5 @@ - - +@if (primaryFilterGroup$ | async; as primaryGroup) { + @if (primaryGroup.icon) { - - - + } + @if (!primaryGroup.icon) { - - + }
- +} diff --git a/src/lib/filtering/quick-filters/quick-filters.component.html b/src/lib/filtering/quick-filters/quick-filters.component.html index 0352a69..1aee58d 100644 --- a/src/lib/filtering/quick-filters/quick-filters.component.html +++ b/src/lib/filtering/quick-filters/quick-filters.component.html @@ -1,12 +1,13 @@ - -
- {{ filter.label }} -
-
+@if (quickFilters$ | async; as filters) { + @for (filter of filters; track filter) { +
+ {{ filter.label }} +
+ } +} diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html index 726d322..6cfc7cd 100644 --- a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.html @@ -1,48 +1,51 @@ - +@if (type() === 'text' && icon()) { + +} - +@if (type() === 'text' && !icon()) { + +} - +@if (type() === 'icon') { + +}
-
+
@@ -50,11 +53,13 @@
-
- - {{ option.label }} - -
+ @for (option of displayedOptions(); track option) { +
+ + {{ option.label }} + +
+ }
diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts index 5f85505..47fa481 100644 --- a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts @@ -1,6 +1,6 @@ import { Component, computed, effect, input, output, signal, untracked } from '@angular/core'; import { MatMenuModule } from '@angular/material/menu'; -import { CommonModule } from '@angular/common'; + import { TranslateModule } from '@ngx-translate/core'; import { MatCheckbox } from '@angular/material/checkbox'; import { ChevronButtonComponent, CircleButtonComponent, IconButtonComponent } from '../../buttons'; @@ -14,7 +14,6 @@ import { SimpleFilterOption } from '../models/simple-filter-option'; styleUrls: ['./simple-popup-filter.component.scss'], standalone: true, imports: [ - CommonModule, MatMenuModule, IconButtonComponent, ChevronButtonComponent, diff --git a/src/lib/help-mode/help-mode/help-mode.component.html b/src/lib/help-mode/help-mode/help-mode.component.html index f14bc48..f9d996c 100644 --- a/src/lib/help-mode/help-mode/help-mode.component.html +++ b/src/lib/help-mode/help-mode/help-mode.component.html @@ -1,19 +1,23 @@ -
-
-
- {{ 'help-mode.bottom-text' | translate }} - - {{ 'help-mode.instructions' | translate }} - -
- (esc) - +@if (helpModeService.isHelpModeActive$ | async) { +
+
+
+ {{ 'help-mode.bottom-text' | translate }} + @if ((helpModeService.helpModeDialogIsOpened$ | async) === false) { + + {{ 'help-mode.instructions' | translate }} + + } +
+ (esc) + +
-
+} diff --git a/src/lib/inputs/details-radio/details-radio.component.html b/src/lib/inputs/details-radio/details-radio.component.html index c39a5c1..4a61fbd 100644 --- a/src/lib/inputs/details-radio/details-radio.component.html +++ b/src/lib/inputs/details-radio/details-radio.component.html @@ -1,50 +1,52 @@
-
-
- - -
- - {{ option.description | translate: option.descriptionParams | replaceNbsp }} - -
- - {{ option.extraOption.label | translate }} - - + @for (option of options; track option) { +
+ @if (option.icon) { +
+ +
+ + {{ option.description | translate: option.descriptionParams | replaceNbsp }} + @if (option.extraOption && !option.extraOption.hidden && isSelected(option)) { +
+ + {{ option.extraOption.label | translate }} + + @if (option.extraOption.description) { + + } +
+ } +
+ @if (isSelected(option)) { + + }
-
- - + } @else { +
+ + +
+ {{ option.description | translate }} + }
- - -
- - -
- - {{ option.description | translate }} -
-
+ }
diff --git a/src/lib/inputs/details-radio/details-radio.component.ts b/src/lib/inputs/details-radio/details-radio.component.ts index adff6b6..8d0f087 100644 --- a/src/lib/inputs/details-radio/details-radio.component.ts +++ b/src/lib/inputs/details-radio/details-radio.component.ts @@ -1,4 +1,4 @@ -import { NgClass, NgForOf, NgIf } from '@angular/common'; +import { NgClass } from '@angular/common'; import { booleanAttribute, Component, EventEmitter, Input, Output } from '@angular/core'; import { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -28,12 +28,10 @@ import { DetailsRadioOption } from './details-radio-option'; }, ], imports: [ - NgForOf, NgClass, RoundCheckboxComponent, TranslateModule, MatIconModule, - NgIf, FormsModule, MatCheckboxModule, ReactiveFormsModule, diff --git a/src/lib/inputs/dynamic-input/dynamic-input.component.html b/src/lib/inputs/dynamic-input/dynamic-input.component.html index 6f53cb6..f655490 100644 --- a/src/lib/inputs/dynamic-input/dynamic-input.component.html +++ b/src/lib/inputs/dynamic-input/dynamic-input.component.html @@ -1,7 +1,9 @@
- + @if (label) { + + } - + @if (isDate) { - + } - + @if (isText) { + + } - + @if (isNumber) { + + }
diff --git a/src/lib/inputs/dynamic-input/dynamic-input.component.ts b/src/lib/inputs/dynamic-input/dynamic-input.component.ts index c4fd2c3..8a8a645 100644 --- a/src/lib/inputs/dynamic-input/dynamic-input.component.ts +++ b/src/lib/inputs/dynamic-input/dynamic-input.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormFieldComponent } from '../form-field/form-field-component.directive'; -import { NgClass, NgIf } from '@angular/common'; +import { NgClass } from '@angular/common'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { StopPropagationDirective } from '../../directives'; import { MatIconModule } from '@angular/material/icon'; @@ -36,7 +36,7 @@ type DynamicInput = number | string | Date; useExisting: DynamicInputComponent, }, ], - imports: [NgClass, NgIf, FormsModule, MatDatepickerModule, StopPropagationDirective, MatIconModule, MatInputModule], + imports: [NgClass, FormsModule, MatDatepickerModule, StopPropagationDirective, MatIconModule, MatInputModule], }) export class DynamicInputComponent extends FormFieldComponent { @Input() label?: string; diff --git a/src/lib/inputs/editable-input/editable-input.component.html b/src/lib/inputs/editable-input/editable-input.component.html index 22945ee..581d1d3 100644 --- a/src/lib/inputs/editable-input/editable-input.component.html +++ b/src/lib/inputs/editable-input/editable-input.component.html @@ -1,34 +1,30 @@ - -
- {{ value }} -
- +@if (!editing) { + @if (showPreview) { +
+ {{ value }} +
+ }
- - + @if (canEdit) { + + }
-
+} - +@if (editing) {
- - + @if (!parentId) { + + } @else { - + }
-
-
-
+} diff --git a/src/lib/inputs/editable-input/editable-input.component.ts b/src/lib/inputs/editable-input/editable-input.component.ts index fbc24f2..942e2d2 100644 --- a/src/lib/inputs/editable-input/editable-input.component.ts +++ b/src/lib/inputs/editable-input/editable-input.component.ts @@ -1,4 +1,3 @@ -import { NgIf } from '@angular/common'; import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { CircleButtonComponent, CircleButtonType, CircleButtonTypes } from '../../buttons'; @@ -9,7 +8,7 @@ import { CircleButtonComponent, CircleButtonType, CircleButtonTypes } from '../. styleUrls: ['./editable-input.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [NgIf, CircleButtonComponent, FormsModule], + imports: [CircleButtonComponent, FormsModule], }) export class EditableInputComponent implements OnChanges { @Input() id?: string; diff --git a/src/lib/inputs/input-with-action/input-with-action.component.html b/src/lib/inputs/input-with-action/input-with-action.component.html index ac3e561..f574a68 100644 --- a/src/lib/inputs/input-with-action/input-with-action.component.html +++ b/src/lib/inputs/input-with-action/input-with-action.component.html @@ -11,22 +11,24 @@ type="text" /> - {{ hint }} + @if (hint) { + {{ hint }} + } - + @if (isSearch && !hasContent) { + + } - + @if (isSearch && hasContent) { + + } - + @if (!isSearch) { + + } diff --git a/src/lib/inputs/input-with-action/input-with-action.component.ts b/src/lib/inputs/input-with-action/input-with-action.component.ts index 810ca83..5e8d67f 100644 --- a/src/lib/inputs/input-with-action/input-with-action.component.ts +++ b/src/lib/inputs/input-with-action/input-with-action.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; import { randomString } from '../../utils'; import { FormsModule } from '@angular/forms'; -import { NgIf } from '@angular/common'; + import { CircleButtonComponent } from '../../buttons'; import { MatIconModule } from '@angular/material/icon'; @@ -11,7 +11,7 @@ import { MatIconModule } from '@angular/material/icon'; styleUrls: ['./input-with-action.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [FormsModule, NgIf, MatIconModule, CircleButtonComponent], + imports: [FormsModule, MatIconModule, CircleButtonComponent], }) export class InputWithActionComponent { @Input() inputId = `${randomString() + '-search-input'}`; diff --git a/src/lib/inputs/round-checkbox/round-checkbox.component.html b/src/lib/inputs/round-checkbox/round-checkbox.component.html index 39efd44..1d9ff37 100644 --- a/src/lib/inputs/round-checkbox/round-checkbox.component.html +++ b/src/lib/inputs/round-checkbox/round-checkbox.component.html @@ -6,6 +6,10 @@ [class.with-bg]="type === 'with-bg'" class="wrapper" > - - + @if (active && !indeterminate) { + + } + @if (indeterminate) { + + }
diff --git a/src/lib/inputs/round-checkbox/round-checkbox.component.ts b/src/lib/inputs/round-checkbox/round-checkbox.component.ts index f3dc96c..c54dab1 100644 --- a/src/lib/inputs/round-checkbox/round-checkbox.component.ts +++ b/src/lib/inputs/round-checkbox/round-checkbox.component.ts @@ -1,6 +1,5 @@ import { booleanAttribute, ChangeDetectionStrategy, Component, ElementRef, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; -import { NgIf } from '@angular/common'; @Component({ selector: 'iqser-round-checkbox', @@ -8,7 +7,7 @@ import { NgIf } from '@angular/common'; styleUrls: ['./round-checkbox.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [MatIconModule, NgIf], + imports: [MatIconModule], }) export class RoundCheckboxComponent implements OnInit { @Input() size = 20; diff --git a/src/lib/listing/page-header/page-header.component.html b/src/lib/listing/page-header/page-header.component.html index aff6a0b..b34ceb4 100644 --- a/src/lib/listing/page-header/page-header.component.html +++ b/src/lib/listing/page-header/page-header.component.html @@ -1,94 +1,98 @@ diff --git a/src/lib/listing/table-content/table-item/table-item.component.html b/src/lib/listing/table-content/table-item/table-item.component.html index 62a632b..8107390 100644 --- a/src/lib/listing/table-content/table-item/table-item.component.html +++ b/src/lib/listing/table-content/table-item/table-item.component.html @@ -1,6 +1,8 @@ -
- -
+@if (selectionEnabled) { +
+ +
+} diff --git a/src/lib/listing/table-header/table-header.component.html b/src/lib/listing/table-header/table-header.component.html index 3ca9bd8..ca13ae0 100644 --- a/src/lib/listing/table-header/table-header.component.html +++ b/src/lib/listing/table-header/table-header.component.html @@ -1,50 +1,56 @@
- + @if (selectionEnabled) { + + } {{ tableHeaderLabel | translate: { length: totalSize || (listingService.displayedLength$ | async) } }} - - ({{ 'table-header.selected-count' | translate: { count: selectedItems } }}) - + @if (listingService.selectedLength$ | async; as selectedItems) { + ({{ 'table-header.selected-count' | translate: { count: selectedItems } }}) + }
- + @if (quickFilters$ | async) { + + }
-
-
- - - -
- -
-
+@if (listingMode === listingModes.table) { +
+ @if (selectionEnabled) { +
+ } + @for (config of tableColumnConfigs; track config) { + + } + @if (hasEmptyColumn) { +
+ } +
+
+} diff --git a/src/lib/listing/table/table.component.html b/src/lib/listing/table/table.component.html index 8c77d8b..6327b1a 100644 --- a/src/lib/listing/table/table.component.html +++ b/src/lib/listing/table/table.component.html @@ -1,42 +1,46 @@ - +@if (entitiesService.noData$ | async) { + +} - +@if (listingComponent.noMatch$ | async) { + +} - +@if (hasScrollButton && tableContent?.scrollViewport) { + +} 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 85af253..ca89188 100644 --- a/src/lib/listing/workflow/column-header/column-header.component.html +++ b/src/lib/listing/workflow/column-header/column-header.component.html @@ -1,43 +1,46 @@ - - +@if (componentContext$ | async; as ctx) { + @if (ctx.entities; as entities) {
{{ column.label | translate }} ({{ entities.length || 0 }}) - -
- - -
+ @if (!activeSelection && !selectionColumn && entities.length > 1) { + + } + @if (activeSelection) { +
+ + +
+ }
-
-
- - - + @if (activeSelection) { +
+
+ + +
+
+ @if (bulkActionsContainerWidth) { + + } +
+
- -
- -
- - -
- - + } + } +} diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index 203f3b0..c43ee82 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -1,69 +1,78 @@ - +@if (componentContext$ | async; as ctx) { - - - -
-
- -
+ @if (ctx.noData) { + + } + @if (!ctx.noData) { +
+ @for (column of config.columns; track column) {
- - - - - -
-
- - - -
- -
-
-
+ + @if (column.entities | async; as entities) { +
+ @for (entity of entities; track trackBy($index, entity)) { +
+ @if (!ctx.draggingEntities.includes(entity)) { + + } + + @for (e of ctx.draggingEntities; track e) { +
+ } +
+ + @for (e of ctx.draggingEntities; track e) { +
+ +
+ } +
+
+ } + @if (column.key === addElementColumn) { +
+ +
+ } +
+ }
- -
- -
-
+ }
-
- + } +} diff --git a/src/lib/loading/full-page-loading-indicator/full-page-loading-indicator.component.html b/src/lib/loading/full-page-loading-indicator/full-page-loading-indicator.component.html index b451ba8..cf1bbfb 100644 --- a/src/lib/loading/full-page-loading-indicator/full-page-loading-indicator.component.html +++ b/src/lib/loading/full-page-loading-indicator/full-page-loading-indicator.component.html @@ -1,12 +1,12 @@ - +@if (loadingService.isLoading(); as config) {
- - - + @if (config.type === 'spinner') { + + } + @if (config.type === 'progress-bar') { - - + }
-
+} diff --git a/src/lib/loading/progress-bar/progress-bar.component.ts b/src/lib/loading/progress-bar/progress-bar.component.ts index 6192d9b..f3b6ccc 100644 --- a/src/lib/loading/progress-bar/progress-bar.component.ts +++ b/src/lib/loading/progress-bar/progress-bar.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, Optional } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, Optional, OnInit } from '@angular/core'; import { ProgressBarConfigModel } from './progress-bar-config.model'; import { FilterService, INestedFilter } from '../../filtering'; import { Observable, of } from 'rxjs'; @@ -11,7 +11,7 @@ import { map } from 'rxjs/operators'; styleUrls: ['./progress-bar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProgressBarComponent { +export class ProgressBarComponent implements OnInit { @Input() config!: ProgressBarConfigModel; @Input() filterKey?: string; diff --git a/src/lib/loading/progress-loading/progress-loading.component.html b/src/lib/loading/progress-loading/progress-loading.component.html index 158c70c..289ac9d 100644 --- a/src/lib/loading/progress-loading/progress-loading.component.html +++ b/src/lib/loading/progress-loading/progress-loading.component.html @@ -1,4 +1,6 @@ -

{{ config.title }}

+@if (config.title) { +

{{ config.title }}

+}
- {{ config.value }}% + @if (config.value) { + {{ config.value }}% + } - - + @if (config.value && config.remainingTime) { + - + } - {{ config.remainingTime }} remaining... + @if (config.remainingTime) { + {{ config.remainingTime }} remaining... + }
diff --git a/src/lib/pagination/pagination.component.html b/src/lib/pagination/pagination.component.html index 1012cbb..7e7d022 100644 --- a/src/lib/pagination/pagination.component.html +++ b/src/lib/pagination/pagination.component.html @@ -1,26 +1,27 @@
| -
- {{ displayValue(page) }} -
+@for (page of displayedPages; track page) { +
+ {{ displayValue(page) }} +
+} |
diff --git a/src/lib/pagination/pagination.component.ts b/src/lib/pagination/pagination.component.ts index d4a4288..1fe81dd 100644 --- a/src/lib/pagination/pagination.component.ts +++ b/src/lib/pagination/pagination.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; -import { NgForOf } from '@angular/common'; + import { TranslateModule } from '@ngx-translate/core'; import { PaginationSettings } from './pagination-settings'; @@ -9,7 +9,7 @@ import { PaginationSettings } from './pagination-settings'; styleUrls: ['./pagination.component.scss'], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [NgForOf, TranslateModule], + imports: [TranslateModule], }) export class PaginationComponent { displayedPages: (number | string)[] = []; diff --git a/src/lib/permissions/README.md b/src/lib/permissions/README.md index 40bd849..b8527ef 100644 --- a/src/lib/permissions/README.md +++ b/src/lib/permissions/README.md @@ -1,24 +1,23 @@ ```typescript -import { Component, OnInit } from "@angular/core"; -import { HttpClient } from "@angular/common/http"; -import { IqserPermissionsService } from "./permissions.service"; -import { IqserRolesService } from "./roles.service"; +import { Component, OnInit } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { IqserPermissionsService } from './permissions.service'; +import { IqserRolesService } from './roles.service'; @Component({ - templateUrl: "./app.component.html" + templateUrl: './app.component.html', }) export class AppComponent implements OnInit { constructor( private permissionsService: IqserPermissionsService, - private rolesService: IqserRolesService - ) { - } + private rolesService: IqserRolesService, + ) {} ngOnInit(): void { - const perm = ["can-edit-articles", "can-read-articles"]; + const perm = ['can-edit-articles', 'can-read-articles']; this.permissionsService.load(perm); - const roles = ["ADMIN", "EDITOR"]; + const roles = ['ADMIN', 'EDITOR']; this.rolesService.load(roles); } } @@ -82,21 +81,21 @@ export class AppComponent implements OnInit { ``` ```typescript -import { IqserRoute } from "./models/permissions-router-data.model"; -import { IqserPermissionsGuard } from "./permissions-guard.service"; +import { IqserRoute } from './models/permissions-router-data.model'; +import { IqserPermissionsGuard } from './permissions-guard.service'; const appRoutes: IqserRoute[] = [ { - path: "home", + path: 'home', component: HomeComponent, canActivate: [IqserPermissionsGuard], data: { permissions: { - allow: ["ADMIN", "MODERATOR"], - redirectTo: "/another-route" - } - } - } + allow: ['ADMIN', 'MODERATOR'], + redirectTo: '/another-route', + }, + }, + }, ]; const appRoutes1: IqserRoute[] = [ @@ -108,73 +107,73 @@ const appRoutes1: IqserRoute[] = [ permissions: { allow: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { if (route.params['id'] === 42) { - return ['MANAGER', "UTILS"] + return ['MANAGER', 'UTILS']; } else { - return 'ADMIN' + return 'ADMIN'; } - } - } - } - } + }, + }, + }, + }, ]; const appRoutes2: IqserRoute[] = [ { - path: "home", + path: 'home', component: HomeComponent, canActivate: [IqserPermissionsGuard], data: { permissions: { - allow: ["ADMIN", "MODERATOR"], + allow: ['ADMIN', 'MODERATOR'], redirectTo: { - navigationCommands: ["123"], + navigationCommands: ['123'], navigationExtras: { - skipLocationChange: true - } - } - } + skipLocationChange: true, + }, + }, + }, }, - } + }, ]; const appRoutes3: IqserRoute[] = [ { - path: "home", + path: 'home', component: HomeComponent, canActivate: [IqserPermissionsGuard], data: { permissions: { - allow: ["canReadAgenda", "canEditAgenda"], + allow: ['canReadAgenda', 'canEditAgenda'], redirectTo: { - canReadAgenda: "agendaList", - canEditAgenda: "dashboard", - default: "login" - } - } - } - } + canReadAgenda: 'agendaList', + canEditAgenda: 'dashboard', + default: 'login', + }, + }, + }, + }, ]; const appRoutes4: IqserRoute[] = [ { - path: "home", + path: 'home', component: HomeComponent, canActivate: [IqserPermissionsGuard], data: { permissions: { - allow: ["canEditAgenda"], + allow: ['canEditAgenda'], redirectTo: { canEditAgenda: { - navigationCommands: "dashboard", + navigationCommands: 'dashboard', navigationExtras: { - skipLocationChange: true - } + skipLocationChange: true, + }, }, - default: "login" - } - } - } - } + default: 'login', + }, + }, + }, + }, ]; const appRoutes5: IqserRoute[] = [ @@ -186,22 +185,29 @@ const appRoutes5: IqserRoute[] = [ permissions: { allow: ['canReadAgenda', 'canEditAgenda'], redirectTo: { - canReadAgenda: (rejectedPermissionName: string, activateRouteSnapshot: ActivatedRouteSnapshot, routeStateSnapshot: RouterStateSnapshot) => { + canReadAgenda: ( + rejectedPermissionName: string, + activateRouteSnapshot: ActivatedRouteSnapshot, + routeStateSnapshot: RouterStateSnapshot, + ) => { return 'dashboard'; }, - canEditAgenda: (rejectedPermissionName: string, activateRouteSnapshot: ActivatedRouteSnapshot, routeStateSnapshot: RouterStateSnapshot) => { + canEditAgenda: ( + rejectedPermissionName: string, + activateRouteSnapshot: ActivatedRouteSnapshot, + routeStateSnapshot: RouterStateSnapshot, + ) => { return { navigationCommands: ['/dashboard'], navigationExtras: { - skipLocationChange: true - } - } + skipLocationChange: true, + }, + }; }, - default: 'login' - } - } - } + default: 'login', + }, + }, + }, }, ]; - ``` diff --git a/src/lib/permissions/directives/deny.directive.spec.ts b/src/lib/permissions/directives/deny.directive.spec.ts index 46c3ce5..4ece03f 100644 --- a/src/lib/permissions/directives/deny.directive.spec.ts +++ b/src/lib/permissions/directives/deny.directive.spec.ts @@ -291,7 +291,7 @@ describe('Permission directive angular testing different async functions in role })); it('should hide the component when one returns falsy value', fakeAsync(() => { - let content = getFixtureContent(); + const content = getFixtureContent(); expect(content).toBeTruthy(); expect(content.innerHTML).toEqual('
123
'); diff --git a/src/lib/shared/skeleton/skeleton.component.html b/src/lib/shared/skeleton/skeleton.component.html index a07bbfc..e927ba3 100644 --- a/src/lib/shared/skeleton/skeleton.component.html +++ b/src/lib/shared/skeleton/skeleton.component.html @@ -1,3 +1,3 @@ - +@if (type$ | async; as type) { - +} diff --git a/src/lib/shared/skeleton/skeleton.component.ts b/src/lib/shared/skeleton/skeleton.component.ts index b9b74f4..f6d3629 100644 --- a/src/lib/shared/skeleton/skeleton.component.ts +++ b/src/lib/shared/skeleton/skeleton.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, HostBinding, inject, Input, TemplateRef } from '@angular/core'; import { SkeletonService } from '../../services'; import { IqserUserService } from '../../users'; -import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; import { tap } from 'rxjs/operators'; @Component({ @@ -10,7 +10,7 @@ import { tap } from 'rxjs/operators'; styleUrls: ['./skeleton.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [NgTemplateOutlet, NgIf, AsyncPipe, NgForOf], + imports: [NgTemplateOutlet, AsyncPipe], }) export class SkeletonComponent { @Input() templates!: Record>; diff --git a/src/lib/shared/status-bar/status-bar.component.html b/src/lib/shared/status-bar/status-bar.component.html index 2234a81..5132689 100644 --- a/src/lib/shared/status-bar/status-bar.component.html +++ b/src/lib/shared/status-bar/status-bar.component.html @@ -1,14 +1,17 @@
-
-
- -
- {{ config.label }} + @for (config of configs; track config) { +
+
+ @if (config.label) { +
+ {{ config.label }} +
+ }
-
+ }
diff --git a/src/lib/shared/status-bar/status-bar.component.ts b/src/lib/shared/status-bar/status-bar.component.ts index 00885a7..58f1071 100644 --- a/src/lib/shared/status-bar/status-bar.component.ts +++ b/src/lib/shared/status-bar/status-bar.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; import { StatusBarConfig } from './status-bar-config.model'; -import { NgClass, NgForOf, NgIf, NgStyle } from '@angular/common'; +import { NgClass, NgStyle } from '@angular/common'; import { MatTooltipModule } from '@angular/material/tooltip'; @Component({ @@ -10,7 +10,7 @@ import { MatTooltipModule } from '@angular/material/tooltip'; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [NgClass, NgStyle, NgForOf, MatTooltipModule, NgIf], + imports: [NgClass, NgStyle, MatTooltipModule], }) export class StatusBarComponent { @Input() configs: readonly StatusBarConfig[] = []; diff --git a/src/lib/shared/toast/toast.component.html b/src/lib/shared/toast/toast.component.html index 5da85b2..419f55f 100644 --- a/src/lib/shared/toast/toast.component.html +++ b/src/lib/shared/toast/toast.component.html @@ -1,23 +1,35 @@
-
- {{ title }} -
+ @if (title) { +
+ {{ title }} +
+ } -
+ @if (message && options.enableHtml) { +
+ } -
- {{ message }} -
+ @if (message && !options.enableHtml) { +
+ {{ message }} +
+ } - + @if (actions?.length) { +
+ @for (action of actions; track action) { + + {{ action.title }} + + } +
+ }
- - - + @if (options.closeButton) { + + + + }
diff --git a/src/lib/shared/toast/toast.component.ts b/src/lib/shared/toast/toast.component.ts index 3b2f6a7..596aa29 100644 --- a/src/lib/shared/toast/toast.component.ts +++ b/src/lib/shared/toast/toast.component.ts @@ -2,14 +2,14 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Toast } from 'ngx-toastr'; import { ToasterActions, ToasterOptions } from '../../services'; import { MatIconModule } from '@angular/material/icon'; -import { NgForOf, NgIf } from '@angular/common'; + import { StopPropagationDirective } from '../../directives'; @Component({ templateUrl: './toast.component.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [MatIconModule, NgIf, StopPropagationDirective, NgForOf], + imports: [MatIconModule, StopPropagationDirective], }) export class ToastComponent extends Toast { get actions(): ToasterActions[] { diff --git a/src/lib/tenants/tenant-select/tenant-select.component.html b/src/lib/tenants/tenant-select/tenant-select.component.html index 6a7c697..b1c5ff9 100644 --- a/src/lib/tenants/tenant-select/tenant-select.component.html +++ b/src/lib/tenants/tenant-select/tenant-select.component.html @@ -8,38 +8,40 @@ - + @if (isLoggedOut || noRoleLogOut) {
-
+ } -
+ @if (storedTenants.length) { +
+ } -
-
- - -
- {{ stored.tenantId }} -
- - -
- -
+ @if (storedTenants.length) { +
+ @for (stored of storedTenants; track stored) { +
+ +
+ {{ stored.tenantId }} +
+ +
+ +
+
+ }
-
+ } -
+ @if (storedTenants.length === 0) { +
+ } - + @if (storedTenants.length) {
-
+ }
diff --git a/src/lib/upload-file/upload-file.component.html b/src/lib/upload-file/upload-file.component.html index ae52c17..30278b5 100644 --- a/src/lib/upload-file/upload-file.component.html +++ b/src/lib/upload-file/upload-file.component.html @@ -1,22 +1,25 @@ -
- - -
-
-
- - -

{{ file.name }}

- - -
+@if (!file) { +
+ +
+
+} +@if (file) { +
+ +

{{ file.name }}

+ @if (!readonly) { + + } +
+} diff --git a/src/lib/users/components/initials-avatar/initials-avatar.component.html b/src/lib/users/components/initials-avatar/initials-avatar.component.html index 75bf3ae..46eadd3 100644 --- a/src/lib/users/components/initials-avatar/initials-avatar.component.html +++ b/src/lib/users/components/initials-avatar/initials-avatar.component.html @@ -1,13 +1,16 @@ -
-
- {{ _user | name: { showInitials: true } }} +@if (_user && _user | name: namePipeOptions; as userName) { +
+
+ {{ _user | name: { showInitials: true } }} +
+ @if (withName) { +
+ {{ userName }} +
+ }
- -
- {{ userName }} -
-
+} diff --git a/src/lib/users/components/user-button/user-button.component.html b/src/lib/users/components/user-button/user-button.component.html index 694c628..8d4e57b 100644 --- a/src/lib/users/components/user-button/user-button.component.html +++ b/src/lib/users/components/user-button/user-button.component.html @@ -1,14 +1,18 @@ -
- - - - +@if (showDot) { +
+} diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 423b1f2..dd570e4 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -5,5 +5,5 @@ "types": ["jest", "node"], "esModuleInterop": true }, - "include": ["./src/lib/**/*.spec.ts", "./src/lib/**/*.d.ts"] + "include": ["./src/lib/**/*.spec.ts", "./src/lib/**/*.d.ts", "jest.config.ts"] } From d595a22db164ec8669da572fdd49a28b464eb61a Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 19 Jun 2024 13:05:12 +0300 Subject: [PATCH 126/201] unfuck circular imports --- src/lib/caching/dynamic-cache.ts | 2 +- src/lib/dialog/base-dialog.component.ts | 21 +++--- src/lib/dialog/confirmation-dialog.service.ts | 10 ++- .../confirmation-dialog.component.ts | 6 +- src/lib/dialog/dialog.service.ts | 36 +++++++++- .../iqser-dialog-component.directive.ts | 7 +- .../filter-card/filter-card.component.ts | 38 ++++------ src/lib/filtering/filter-utils.ts | 7 +- src/lib/filtering/filter.service.ts | 10 +-- src/lib/filtering/models/filter.ts | 2 +- .../filtering/models/local-filters.model.ts | 10 +++ src/lib/filtering/models/nested-filter.ts | 2 +- .../popup-filter/popup-filter.component.ts | 17 ++--- .../help-button/help-button.component.ts | 2 +- src/lib/help-mode/help-mode.service.ts | 44 +++++------- src/lib/help-mode/tokens.ts | 4 +- src/lib/help-mode/types.ts | 9 +++ src/lib/help-mode/utils/help-mode.provider.ts | 3 +- .../listing/listing-component.directive.ts | 16 +++-- src/lib/listing/listing.module.ts | 69 ++++++++++--------- .../page-header/page-header.component.ts | 49 ++++++++----- .../scroll-button/scroll-button.component.ts | 8 ++- src/lib/listing/services/entities.service.ts | 14 ++-- src/lib/listing/services/listing.service.ts | 12 ++-- .../services/paginated-entities.service.ts | 9 +-- .../table-column-name.component.ts | 6 ++ .../table-content/table-content.component.ts | 39 ++++++++--- .../table-item/table-item.component.ts | 15 ++-- .../table-header/table-header.component.ts | 24 +++++-- src/lib/listing/table/table.component.ts | 11 ++- .../column-header/column-header.component.ts | 11 ++- .../listing/workflow/workflow.component.ts | 52 ++++++++++---- .../services/permissions-guard.service.ts | 11 +-- .../services/permissions.service.ts | 8 +-- src/lib/permissions/services/roles.service.ts | 4 +- src/lib/permissions/types.ts | 7 +- src/lib/permissions/utils.ts | 3 +- src/lib/search/search.service.ts | 7 +- src/lib/services/api-path.interceptor.ts | 2 +- src/lib/services/composite-route.guard.ts | 2 +- src/lib/services/entities-map.service.ts | 10 +-- src/lib/services/generic.service.ts | 9 +-- src/lib/services/iqser-config.service.ts | 5 +- .../services/iqser-user-preference.service.ts | 6 +- src/lib/services/stats.service.ts | 10 +-- src/lib/sorting/sorting.service.ts | 8 ++- src/lib/tenants/index.ts | 4 +- src/lib/tenants/keycloak-initializer.ts | 36 ++-------- src/lib/tenants/keycloak-options.ts | 27 ++++++++ .../services/keycloak-status.service.ts | 7 +- .../tenant-select/tenant-select.component.ts | 12 ++-- src/lib/tenants/tenants.module.ts | 10 +-- src/lib/utils/auto-unsubscribe.directive.ts | 3 +- src/lib/utils/custom-route-reuse.strategy.ts | 6 +- src/lib/utils/functions.ts | 2 +- src/lib/utils/headers-configuration.ts | 7 +- src/lib/utils/types/common-ui-options.ts | 3 +- 57 files changed, 471 insertions(+), 303 deletions(-) create mode 100644 src/lib/filtering/models/local-filters.model.ts create mode 100644 src/lib/help-mode/types.ts create mode 100644 src/lib/tenants/keycloak-options.ts diff --git a/src/lib/caching/dynamic-cache.ts b/src/lib/caching/dynamic-cache.ts index d9cc4c4..a3d61e2 100644 --- a/src/lib/caching/dynamic-cache.ts +++ b/src/lib/caching/dynamic-cache.ts @@ -1,4 +1,4 @@ -import { List } from '../utils'; +import { List } from '../utils/types/iqser-types'; export interface DynamicCache { readonly urls: List; diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts index b4bffb3..6849431 100644 --- a/src/lib/dialog/base-dialog.component.ts +++ b/src/lib/dialog/base-dialog.component.ts @@ -3,12 +3,13 @@ import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { debounceTime, firstValueFrom, fromEvent, merge, of, Subscription } from 'rxjs'; import { tap } from 'rxjs/operators'; -import { ConfirmOptions } from '.'; -import { IconButtonTypes } from '../buttons'; -import { LoadingService } from '../loading'; -import { Toaster } from '../services'; -import { hasFormChanged, IqserEventTarget } from '../utils'; +import { IconButtonTypes } from '../buttons/types/icon-button.type'; +import { LoadingService } from '../loading/loading.service'; +import { Toaster } from '../services/toaster.service'; +import { hasFormChanged } from '../utils/functions'; +import { IqserEventTarget } from '../utils/types/events.type'; import { ConfirmationDialogService } from './confirmation-dialog.service'; +import { ConfirmOptions } from './confirmation-dialog/confirmation-dialog.component'; const DIALOG_CONTAINER = 'mat-dialog-container'; const TEXT_INPUT = 'text'; @@ -21,16 +22,16 @@ export interface SaveOptions { @Directive() export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy { - readonly iconButtonTypes = IconButtonTypes; - form?: UntypedFormGroup; - initialFormValue!: Record; + readonly #confirmationDialogService = inject(ConfirmationDialogService); + readonly #dialog = inject(MatDialog); protected readonly _hasErrors = signal(true); protected readonly _formBuilder = inject(UntypedFormBuilder); protected readonly _loadingService = inject(LoadingService); protected readonly _toaster = inject(Toaster); protected readonly _subscriptions = new Subscription(); - readonly #confirmationDialogService = inject(ConfirmationDialogService); - readonly #dialog = inject(MatDialog); + readonly iconButtonTypes = IconButtonTypes; + form?: UntypedFormGroup; + initialFormValue!: Record; protected constructor( protected readonly _dialogRef: MatDialogRef, diff --git a/src/lib/dialog/confirmation-dialog.service.ts b/src/lib/dialog/confirmation-dialog.service.ts index dfba015..e7b3b2f 100644 --- a/src/lib/dialog/confirmation-dialog.service.ts +++ b/src/lib/dialog/confirmation-dialog.service.ts @@ -1,7 +1,13 @@ import { inject, Injectable } from '@angular/core'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { ConfirmationDialogComponent, ConfirmOption, defaultDialogConfig, IConfirmationDialogData, TitleColors } from '.'; import { MatDialog } from '@angular/material/dialog'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { + ConfirmationDialogComponent, + ConfirmOption, + IConfirmationDialogData, + TitleColors, +} from './confirmation-dialog/confirmation-dialog.component'; +import { defaultDialogConfig } from './dialog.service'; @Injectable({ providedIn: 'root', diff --git a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts index 456347f..67cc5c5 100644 --- a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts +++ b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.ts @@ -6,8 +6,10 @@ import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/materia import { MatIconModule } from '@angular/material/icon'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; -import { CircleButtonComponent, IconButtonComponent, IconButtonTypes } from '../../buttons'; -import { ValuesOf } from '../../utils'; +import { CircleButtonComponent } from '../../buttons/circle-button/circle-button.component'; +import { IconButtonComponent } from '../../buttons/icon-button/icon-button.component'; +import { IconButtonTypes } from '../../buttons/types/icon-button.type'; +import { ValuesOf } from '../../utils/types/utility-types'; export const TitleColors = { DEFAULT: 'default', diff --git a/src/lib/dialog/dialog.service.ts b/src/lib/dialog/dialog.service.ts index 7654395..8cb89e8 100644 --- a/src/lib/dialog/dialog.service.ts +++ b/src/lib/dialog/dialog.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@angular/core'; -import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog'; import { ComponentType } from '@angular/cdk/portal'; -import { mergeMap } from 'rxjs/operators'; +import { Injectable, Type } from '@angular/core'; +import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog'; import { from } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; export const largeDialogConfig: MatDialogConfig = { width: '90vw', @@ -64,4 +64,34 @@ export abstract class DialogService { return ref; } + + open( + type: Type, + data?: unknown, + config?: object, + cb?: (...params: unknown[]) => Promise | void, + finallyCb?: (...params: unknown[]) => void | Promise, + ): MatDialogRef { + const ref = this._dialog.open(type, { + ...defaultDialogConfig, + ...(config || {}), + data, + }); + + const fn = async (result: unknown) => { + if (result && cb) { + await cb(result); + } + + if (finallyCb) { + await finallyCb(result); + } + }; + + ref.afterClosed() + .pipe(mergeMap(result => from(fn(result)))) + .subscribe(); + + return ref; + } } diff --git a/src/lib/dialog/iqser-dialog-component.directive.ts b/src/lib/dialog/iqser-dialog-component.directive.ts index 0f41d3e..c983363 100644 --- a/src/lib/dialog/iqser-dialog-component.directive.ts +++ b/src/lib/dialog/iqser-dialog-component.directive.ts @@ -1,9 +1,10 @@ import { Directive, HostListener, inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { hasFormChanged, IqserEventTarget } from '../utils'; import { FormGroup } from '@angular/forms'; -import { IconButtonTypes } from '../buttons'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { IconButtonTypes } from '../buttons/types/icon-button.type'; +import { hasFormChanged } from '../utils/functions'; +import { IqserEventTarget } from '../utils/types/events.type'; const DIALOG_CONTAINER = 'mat-dialog-container'; const DATA_TYPE_SYMBOL = Symbol.for('DATA_TYPE'); diff --git a/src/lib/filtering/filter-card/filter-card.component.ts b/src/lib/filtering/filter-card/filter-card.component.ts index 7f1d26e..9e1db77 100644 --- a/src/lib/filtering/filter-card/filter-card.component.ts +++ b/src/lib/filtering/filter-card/filter-card.component.ts @@ -1,21 +1,20 @@ +import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, TemplateRef } from '@angular/core'; +import { MAT_CHECKBOX_DEFAULT_OPTIONS, MatCheckbox } from '@angular/material/checkbox'; +import { MatIcon } from '@angular/material/icon'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { TranslateModule } from '@ngx-translate/core'; import { combineLatest, Observable, pipe } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { StopPropagationDirective } from '../../directives/stop-propagation.directive'; +import { InputWithActionComponent } from '../../inputs/input-with-action/input-with-action.component'; +import { SearchService } from '../../search/search.service'; +import { shareDistinctLast, shareLast } from '../../utils/operators'; +import { FilterService } from '../filter.service'; +import { Filter } from '../models/filter'; +import { IFilterGroup } from '../models/filter-group.model'; import { IFilter } from '../models/filter.model'; import { INestedFilter } from '../models/nested-filter.model'; -import { IFilterGroup } from '../models/filter-group.model'; -import { extractFilterValues, handleCheckedValue } from '../filter-utils'; -import { FilterService } from '../filter.service'; -import { SearchService } from '../../search'; -import { Filter } from '../models/filter'; -import { map } from 'rxjs/operators'; -import { shareDistinctLast, shareLast } from '../../utils'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { MAT_CHECKBOX_DEFAULT_OPTIONS, MatCheckbox } from '@angular/material/checkbox'; -import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; -import { InputWithActionComponent } from '../../inputs'; -import { TranslateModule } from '@ngx-translate/core'; -import { MatIcon } from '@angular/material/icon'; -import { StopPropagationDirective } from '../../directives'; const areExpandable = (nestedFilter: INestedFilter) => !!nestedFilter?.children?.length; const atLeastOneIsExpandable = pipe( @@ -23,17 +22,6 @@ const atLeastOneIsExpandable = pipe( shareDistinctLast(), ); -export interface LocalStorageFilter { - id: string; - checked: boolean; - children?: LocalStorageFilter[] | null; -} - -export interface LocalStorageFilters { - primaryFilters: LocalStorageFilter[] | null; - secondaryFilters: LocalStorageFilter[] | null; -} - @Component({ selector: 'iqser-filter-card [primaryFiltersSlug]', templateUrl: './filter-card.component.html', diff --git a/src/lib/filtering/filter-utils.ts b/src/lib/filtering/filter-utils.ts index cf9abe7..4dc8729 100644 --- a/src/lib/filtering/filter-utils.ts +++ b/src/lib/filtering/filter-utils.ts @@ -1,10 +1,11 @@ /* eslint-disable no-param-reassign */ -import { INestedFilter } from './models/nested-filter.model'; +import { IListable } from '../listing/models/listable'; +import { Id } from '../listing/models/trackable'; import { IFilterGroup } from './models/filter-group.model'; import { IFilter } from './models/filter.model'; +import { LocalStorageFilter } from './models/local-filters.model'; import { NestedFilter } from './models/nested-filter'; -import { Id, IListable } from '../listing'; -import { LocalStorageFilter } from './filter-card/filter-card.component'; +import { INestedFilter } from './models/nested-filter.model'; function copySettings(oldFilters: INestedFilter[], newFilters: INestedFilter[]) { if (!oldFilters || !newFilters) { diff --git a/src/lib/filtering/filter.service.ts b/src/lib/filtering/filter.service.ts index 2969051..7215aac 100644 --- a/src/lib/filtering/filter.service.ts +++ b/src/lib/filtering/filter.service.ts @@ -1,14 +1,14 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { map, startWith, switchMap } from 'rxjs/operators'; -import { extractFilterValues, handleCheckedValue, processFilters, toFlatFilters } from './filter-utils'; -import { IFilterGroup } from './models/filter-group.model'; -import { INestedFilter } from './models/nested-filter.model'; import { get, shareDistinctLast, shareLast, some } from '../utils'; -import { NestedFilter } from './models/nested-filter'; +import { extractFilterValues, handleCheckedValue, processFilters, toFlatFilters } from './filter-utils'; import { Filter } from './models/filter'; +import { IFilterGroup } from './models/filter-group.model'; import { IFilter } from './models/filter.model'; -import { LocalStorageFilters } from './filter-card/filter-card.component'; +import { LocalStorageFilters } from './models/local-filters.model'; +import { NestedFilter } from './models/nested-filter'; +import { INestedFilter } from './models/nested-filter.model'; export interface CheckboxClickedParams { nestedFilter: INestedFilter; diff --git a/src/lib/filtering/models/filter.ts b/src/lib/filtering/models/filter.ts index 5469311..83f7775 100644 --- a/src/lib/filtering/models/filter.ts +++ b/src/lib/filtering/models/filter.ts @@ -1,5 +1,5 @@ +import { IListable } from '../../listing/models/listable'; import { IFilter } from './filter.model'; -import { IListable } from '../../listing'; export class Filter implements IFilter, IListable { readonly id: string; diff --git a/src/lib/filtering/models/local-filters.model.ts b/src/lib/filtering/models/local-filters.model.ts new file mode 100644 index 0000000..8ac4da0 --- /dev/null +++ b/src/lib/filtering/models/local-filters.model.ts @@ -0,0 +1,10 @@ +export interface LocalStorageFilter { + id: string; + checked: boolean; + children?: LocalStorageFilter[] | null; +} + +export interface LocalStorageFilters { + primaryFilters: LocalStorageFilter[] | null; + secondaryFilters: LocalStorageFilter[] | null; +} diff --git a/src/lib/filtering/models/nested-filter.ts b/src/lib/filtering/models/nested-filter.ts index add8a83..0087b85 100644 --- a/src/lib/filtering/models/nested-filter.ts +++ b/src/lib/filtering/models/nested-filter.ts @@ -1,4 +1,4 @@ -import { IListable } from '../../listing'; +import { IListable } from '../../listing/models/listable'; import { Filter } from './filter'; import { INestedFilter } from './nested-filter.model'; diff --git a/src/lib/filtering/popup-filter/popup-filter.component.ts b/src/lib/filtering/popup-filter/popup-filter.component.ts index 5733893..f506370 100644 --- a/src/lib/filtering/popup-filter/popup-filter.component.ts +++ b/src/lib/filtering/popup-filter/popup-filter.component.ts @@ -1,16 +1,17 @@ +import { AsyncPipe, NgIf } from '@angular/common'; import { ChangeDetectionStrategy, Component, Input, OnInit, TemplateRef } from '@angular/core'; +import { MatMenu, MatMenuContent, MatMenuTrigger } from '@angular/material/menu'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { TranslateModule } from '@ngx-translate/core'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { delay, map } from 'rxjs/operators'; -import { shareDistinctLast, shareLast, some } from '../../utils'; +import { ChevronButtonComponent } from '../../buttons/chevron-button/chevron-button.component'; +import { IconButtonComponent } from '../../buttons/icon-button/icon-button.component'; +import { StopPropagationDirective } from '../../directives/stop-propagation.directive'; +import { shareDistinctLast, shareLast, some } from '../../utils/operators'; +import { FilterCardComponent } from '../filter-card/filter-card.component'; import { FilterService } from '../filter.service'; import { IFilterGroup } from '../models/filter-group.model'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { ChevronButtonComponent, IconButtonComponent } from '../../buttons'; -import { AsyncPipe, NgIf } from '@angular/common'; -import { TranslateModule } from '@ngx-translate/core'; -import { MatMenu, MatMenuContent, MatMenuTrigger } from '@angular/material/menu'; -import { FilterCardComponent } from '../filter-card/filter-card.component'; -import { StopPropagationDirective } from '../../directives'; @Component({ selector: 'iqser-popup-filter [primaryFiltersSlug]', diff --git a/src/lib/help-mode/help-button/help-button.component.ts b/src/lib/help-mode/help-button/help-button.component.ts index b62663f..a331ddf 100644 --- a/src/lib/help-mode/help-button/help-button.component.ts +++ b/src/lib/help-mode/help-button/help-button.component.ts @@ -1,7 +1,7 @@ /* eslint-disable @angular-eslint/prefer-on-push-component-change-detection */ import { AfterViewInit, Component, ElementRef, HostListener, Input, OnDestroy, OnInit } from '@angular/core'; -import { HelpModeService } from '../index'; import { MatIcon } from '@angular/material/icon'; +import { HelpModeService } from '../help-mode.service'; @Component({ selector: 'iqser-help-button', diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index 36e8a65..3be3294 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -1,8 +1,12 @@ import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, firstValueFrom } from 'rxjs'; +import { getConfig } from '../services/iqser-config.service'; +import { IqserUserPreferenceService } from '../services/iqser-user-preference.service'; import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; import { HELP_MODE_KEYS, MANUAL_BASE_URL } from './tokens'; +import { HelpModeKey } from './types'; import { DOCUMINE_THEME_CLASS, HELP_HIGHLIGHT_CLASS, @@ -16,8 +20,6 @@ import { ScrollableParentViews, WEB_VIEWER_ELEMENTS, } from './utils/constants'; -import { getConfig, IqserUserPreferenceService } from '../services'; -import { MatDialog } from '@angular/material/dialog'; export interface Helper { readonly element: HTMLElement; @@ -28,24 +30,16 @@ export interface Helper { readonly iframeElement?: boolean; } -export interface HelpModeKey { - readonly elementKey: string; - readonly documentKey: string; - readonly scrollableParentView?: ScrollableParentView; - readonly overlappingElements?: OverlappingElement[]; - readonly dialogElement?: boolean; -} - @Injectable() export class HelpModeService { readonly #isHelpModeActive$ = new BehaviorSubject(false); - readonly isHelpModeActive$ = this.#isHelpModeActive$.asObservable(); readonly #helpModeDialogIsOpened$ = new BehaviorSubject(false); - readonly helpModeDialogIsOpened$ = this.#helpModeDialogIsOpened$.asObservable(); readonly #renderer: Renderer2; readonly #isDocumine = getConfig().IS_DOCUMINE; #helpers: Record = {}; #dialogMode = false; + readonly isHelpModeActive$ = this.#isHelpModeActive$.asObservable(); + readonly helpModeDialogIsOpened$ = this.#helpModeDialogIsOpened$.asObservable(); constructor( @Inject(HELP_MODE_KEYS) private readonly _keys: HelpModeKey[], @@ -106,6 +100,19 @@ export class HelpModeService { } } + highlightHelperElements(): void { + Object.values(this.#helpers).forEach(helper => { + this.#renderer.addClass(helper.helperElement, HELP_HIGHLIGHT_CLASS); + setTimeout(() => { + this.#renderer.removeClass(helper.helperElement, HELP_HIGHLIGHT_CLASS); + }, 500); + }); + } + + updateHelperElements() { + Object.values(this.#helpers).forEach(helper => this.#updateHelperElement(helper)); + } + #createHelpers() { for (const key of Object.values(this._keys)) { const elements = document.querySelectorAll(`[help-mode-key='${key.elementKey}']`); @@ -159,19 +166,6 @@ export class HelpModeService { return `${this._manualBaseURL}/${currentLang}/index-${currentLang}.html?contextId=${key}`; } - highlightHelperElements(): void { - Object.values(this.#helpers).forEach(helper => { - this.#renderer.addClass(helper.helperElement, HELP_HIGHLIGHT_CLASS); - setTimeout(() => { - this.#renderer.removeClass(helper.helperElement, HELP_HIGHLIGHT_CLASS); - }, 500); - }); - } - - updateHelperElements() { - Object.values(this.#helpers).forEach(helper => this.#updateHelperElement(helper)); - } - #isElementVisible(helper: Helper): boolean { if (helper.iframeElement && !this.#isFilePreviewPage()) { return false; diff --git a/src/lib/help-mode/tokens.ts b/src/lib/help-mode/tokens.ts index c842b24..46546ea 100644 --- a/src/lib/help-mode/tokens.ts +++ b/src/lib/help-mode/tokens.ts @@ -1,6 +1,6 @@ import { inject, InjectionToken } from '@angular/core'; -import { IqserConfigService } from '../services'; -import { HelpModeKey } from './help-mode.service'; +import { IqserConfigService } from '../services/iqser-config.service'; +import { HelpModeKey } from './types'; export const HELP_MODE_KEYS = new InjectionToken('Help mode keys'); export const MANUAL_BASE_URL = new InjectionToken('Base manual URL', { diff --git a/src/lib/help-mode/types.ts b/src/lib/help-mode/types.ts new file mode 100644 index 0000000..6be4340 --- /dev/null +++ b/src/lib/help-mode/types.ts @@ -0,0 +1,9 @@ +import { OverlappingElement, ScrollableParentView } from './utils/constants'; + +export interface HelpModeKey { + readonly elementKey: string; + readonly documentKey: string; + readonly scrollableParentView?: ScrollableParentView; + readonly overlappingElements?: OverlappingElement[]; + readonly dialogElement?: boolean; +} diff --git a/src/lib/help-mode/utils/help-mode.provider.ts b/src/lib/help-mode/utils/help-mode.provider.ts index 9ae36fc..cd1804b 100644 --- a/src/lib/help-mode/utils/help-mode.provider.ts +++ b/src/lib/help-mode/utils/help-mode.provider.ts @@ -1,5 +1,6 @@ -import { HelpModeKey, HelpModeService } from '../help-mode.service'; +import { HelpModeService } from '../help-mode.service'; import { HELP_MODE_KEYS } from '../tokens'; +import { HelpModeKey } from '../types'; export function provideHelpMode(helpModeKeys: HelpModeKey[]) { return [{ provide: HELP_MODE_KEYS, useValue: helpModeKeys }, HelpModeService]; diff --git a/src/lib/listing/listing-component.directive.ts b/src/lib/listing/listing-component.directive.ts index 327019d..8aea765 100644 --- a/src/lib/listing/listing-component.directive.ts +++ b/src/lib/listing/listing-component.directive.ts @@ -1,12 +1,16 @@ import { Directive, inject, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; import { combineLatest, Observable } from 'rxjs'; 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 { Id, IListable, TableColumnConfig } from './models'; +import { FilterService } from '../filtering/filter.service'; +import { SearchService } from '../search/search.service'; +import { SortingService } from '../sorting/sorting.service'; +import { AutoUnsubscribe } from '../utils/auto-unsubscribe.directive'; +import { shareDistinctLast } from '../utils/operators'; +import { IListable } from './models/listable'; +import { TableColumnConfig } from './models/table-column-config.model'; +import { Id } from './models/trackable'; +import { EntitiesService } from './services/entities.service'; +import { ListingService } from './services/listing.service'; @Directive() export abstract class ListingComponent, PrimaryKey extends Id = Class['id']> diff --git a/src/lib/listing/listing.module.ts b/src/lib/listing/listing.module.ts index 28da82c..e844f29 100644 --- a/src/lib/listing/listing.module.ts +++ b/src/lib/listing/listing.module.ts @@ -1,48 +1,55 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { TranslateModule } from '@ngx-translate/core'; -import { TableHeaderComponent } from './table-header/table-header.component'; -import { IqserFiltersModule, PopupFilterComponent } from '../filtering'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { TableColumnNameComponent } from './table-column-name/table-column-name.component'; -import { ScrollButtonComponent } from './scroll-button/scroll-button.component'; -import { TableComponent } from './table/table.component'; -import { HasScrollbarDirective, SyncWidthDirective } from '../directives'; -import { ScrollingModule } from '@angular/cdk/scrolling'; -import { RouterModule } from '@angular/router'; -import { WorkflowComponent } from './workflow/workflow.component'; import { DragDropModule } from '@angular/cdk/drag-drop'; -import { PageHeaderComponent } from './page-header/page-header.component'; -import { TableContentComponent } from './table-content/table-content.component'; -import { TableItemComponent } from './table-content/table-item/table-item.component'; -import { ColumnHeaderComponent } from './workflow/column-header/column-header.component'; -import { CircleButtonComponent, IconButtonComponent } from '../buttons'; +import { ScrollingModule } from '@angular/cdk/scrolling'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { CircleButtonComponent, IconButtonComponent } from '../buttons'; +import { HasScrollbarDirective, SyncWidthDirective } from '../directives'; import { EmptyStateComponent } from '../empty-state'; +import { IqserFiltersModule, PopupFilterComponent } from '../filtering'; import { InputWithActionComponent, RoundCheckboxComponent } from '../inputs'; import { SnakeCasePipe } from '../pipes/snake-case.pipe'; +import { PageHeaderComponent } from './page-header/page-header.component'; +import { ScrollButtonComponent } from './scroll-button/scroll-button.component'; +import { TableColumnNameComponent } from './table-column-name/table-column-name.component'; +import { TableContentComponent } from './table-content/table-content.component'; +import { TableItemComponent } from './table-content/table-item/table-item.component'; +import { TableHeaderComponent } from './table-header/table-header.component'; +import { TableComponent } from './table/table.component'; +import { ColumnHeaderComponent } from './workflow/column-header/column-header.component'; +import { WorkflowComponent } from './workflow/workflow.component'; const matModules = [MatTooltipModule, MatIconModule]; -const components = [ - TableHeaderComponent, - TableComponent, - WorkflowComponent, - TableColumnNameComponent, - ScrollButtonComponent, - PageHeaderComponent, - TableContentComponent, - TableItemComponent, - ColumnHeaderComponent, -]; const modules = [DragDropModule, TranslateModule, IqserFiltersModule, ScrollingModule, RouterModule]; @NgModule({ - declarations: [...components], - exports: [...components], + exports: [ + WorkflowComponent, + ScrollButtonComponent, + PageHeaderComponent, + TableContentComponent, + TableColumnNameComponent, + TableHeaderComponent, + TableComponent, + ColumnHeaderComponent, + TableItemComponent, + ], imports: [ CommonModule, ...modules, ...matModules, + WorkflowComponent, + ScrollButtonComponent, + PageHeaderComponent, + TableColumnNameComponent, + TableContentComponent, + TableComponent, + TableItemComponent, + ColumnHeaderComponent, + TableHeaderComponent, CircleButtonComponent, IconButtonComponent, EmptyStateComponent, diff --git a/src/lib/listing/page-header/page-header.component.ts b/src/lib/listing/page-header/page-header.component.ts index 6b8e793..623e2da 100644 --- a/src/lib/listing/page-header/page-header.component.ts +++ b/src/lib/listing/page-header/page-header.component.ts @@ -1,18 +1,37 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Optional, Output, TemplateRef } from '@angular/core'; -import { ActionConfig, ButtonConfig, SearchPosition, SearchPositions } from './models'; -import { distinctUntilChanged, map } from 'rxjs/operators'; +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; +import { ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output, TemplateRef } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; import { combineLatest, Observable, of } from 'rxjs'; -import { IListable } from '../models'; -import { IconButtonTypes } from '../../buttons'; -import { SearchService } from '../../search'; -import { FilterService } from '../../filtering'; -import { filterEach, List } from '../../utils'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { CircleButtonComponent } from '../../buttons/circle-button/circle-button.component'; +import { IconButtonComponent } from '../../buttons/icon-button/icon-button.component'; +import { IconButtonTypes } from '../../buttons/types/icon-button.type'; +import { FilterService } from '../../filtering/filter.service'; +import { IqserFiltersModule } from '../../filtering/filters.module'; +import { PopupFilterComponent } from '../../filtering/popup-filter/popup-filter.component'; +import { InputWithActionComponent } from '../../inputs/input-with-action/input-with-action.component'; +import { SearchService } from '../../search/search.service'; +import { filterEach } from '../../utils/operators'; +import { List } from '../../utils/types/iqser-types'; +import { IListable } from '../models/listable'; +import { ActionConfig, ButtonConfig, SearchPosition, SearchPositions } from './models'; @Component({ selector: 'iqser-page-header', templateUrl: './page-header.component.html', styleUrls: ['./page-header.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + AsyncPipe, + NgTemplateOutlet, + PopupFilterComponent, + IqserFiltersModule, + IconButtonComponent, + CircleButtonComponent, + TranslateModule, + InputWithActionComponent, + ], }) export class PageHeaderComponent { readonly searchPositions = SearchPositions; @@ -30,15 +49,11 @@ export class PageHeaderComponent { @Input() helpModeKey?: 'dossier' | 'document'; @Input() searchPosition: SearchPosition = SearchPositions.afterFilters; @Output() readonly closeAction = new EventEmitter(); - + readonly filterService = inject(FilterService, { optional: true }); + readonly searchService = inject>(SearchService, { optional: true }); readonly filters$ = this.filterService?.filterGroups$.pipe(filterEach(f => !!f.icon)); readonly showResetFilters$ = this.#showResetFilters$; - constructor( - @Optional() readonly filterService: FilterService, - @Optional() readonly searchService: SearchService, - ) {} - get filterHelpModeKey() { return this.helpModeKey ? (this.helpModeKey === 'dossier' ? 'filter_dossier_list' : 'filter_documents') : ''; } @@ -48,15 +63,15 @@ export class PageHeaderComponent { return of(false); } - return combineLatest([this.filterService.showResetFilters$, this.searchService.valueChanges$]).pipe( + return combineLatest([this.filterService.showResetFilters$, this.searchService?.valueChanges$ ?? of(undefined)]).pipe( map(([showResetFilters, searchValue]) => showResetFilters || !!searchValue), distinctUntilChanged(), ); } resetFilters(): void { - this.filterService.reset(); - this.searchService.reset(); + this.filterService?.reset(); + this.searchService?.reset(); } trackByLabel(_index: number, item: K): string | undefined { diff --git a/src/lib/listing/scroll-button/scroll-button.component.ts b/src/lib/listing/scroll-button/scroll-button.component.ts index 039b093..6d55bc6 100644 --- a/src/lib/listing/scroll-button/scroll-button.component.ts +++ b/src/lib/listing/scroll-button/scroll-button.component.ts @@ -1,7 +1,9 @@ -import { ChangeDetectionStrategy, Component, HostListener, Input, OnInit } from '@angular/core'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; -import { delay, map, startWith } from 'rxjs/operators'; +import { AsyncPipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, HostListener, Input, OnInit } from '@angular/core'; +import { MatIcon } from '@angular/material/icon'; import { combineLatest, fromEvent, Observable } from 'rxjs'; +import { delay, map, startWith } from 'rxjs/operators'; import { shareDistinctLast } from '../../utils'; const ButtonTypes = { @@ -16,6 +18,8 @@ type ButtonType = keyof typeof ButtonTypes; templateUrl: './scroll-button.component.html', styleUrls: ['./scroll-button.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [AsyncPipe, MatIcon], }) export class ScrollButtonComponent implements OnInit { readonly buttonType = ButtonTypes; diff --git a/src/lib/listing/services/entities.service.ts b/src/lib/listing/services/entities.service.ts index 216803e..130cb17 100644 --- a/src/lib/listing/services/entities.service.ts +++ b/src/lib/listing/services/entities.service.ts @@ -1,9 +1,11 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { filter, map, startWith, tap } from 'rxjs/operators'; -import { Id, IListable } from '../models'; -import { GenericService, QueryParam } from '../../services'; -import { getLength, List, mapEach, shareDistinctLast, shareLast } from '../../utils'; +import { GenericService, QueryParam } from '../../services/generic.service'; +import { getLength, mapEach, shareDistinctLast, shareLast } from '../../utils/operators'; +import { List } from '../../utils/types/iqser-types'; +import { IListable } from '../models/listable'; +import { Id } from '../models/trackable'; @Injectable() /** @@ -14,14 +16,14 @@ export class EntitiesService< Class extends Interface & IListable, PrimaryKey extends Id = Class['id'], > extends GenericService { - readonly noData$: Observable; - readonly all$: Observable; - readonly allLength$: Observable; protected readonly _defaultModelPath: string = ''; protected readonly _entityClass?: new (entityInterface: Interface, ...args: unknown[]) => Class; protected readonly _entityChanged$ = new Subject(); protected readonly _entityDeleted$ = new Subject(); protected readonly _all$ = new BehaviorSubject([]); + readonly noData$: Observable; + readonly all$: Observable; + readonly allLength$: Observable; constructor() { super(); diff --git a/src/lib/listing/services/listing.service.ts b/src/lib/listing/services/listing.service.ts index 77537e0..3bc91a6 100644 --- a/src/lib/listing/services/listing.service.ts +++ b/src/lib/listing/services/listing.service.ts @@ -1,11 +1,13 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map, switchMap, tap } from 'rxjs/operators'; -import { FilterService, getFilteredEntities } from '../../filtering'; -import { SearchService } from '../../search'; -import { SortingService } from '../../sorting'; -import { getLength, shareDistinctLast, shareLast, some } from '../../utils'; -import { Id, IListable } from '../models'; +import { getFilteredEntities } from '../../filtering/filter-utils'; +import { FilterService } from '../../filtering/filter.service'; +import { SearchService } from '../../search/search.service'; +import { SortingService } from '../../sorting/sorting.service'; +import { getLength, shareDistinctLast, shareLast, some } from '../../utils/operators'; +import { IListable } from '../models/listable'; +import { Id } from '../models/trackable'; import { EntitiesService } from './entities.service'; @Injectable() diff --git a/src/lib/listing/services/paginated-entities.service.ts b/src/lib/listing/services/paginated-entities.service.ts index f348a9b..836125a 100644 --- a/src/lib/listing/services/paginated-entities.service.ts +++ b/src/lib/listing/services/paginated-entities.service.ts @@ -1,10 +1,11 @@ import { Injectable } from '@angular/core'; -import { Id, IListable } from '../models'; -import { EntitiesService } from './entities.service'; import { firstValueFrom, Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; -import { mapEach } from '../../utils'; -import { PaginationSettings } from '../../pagination'; +import { PaginationSettings } from '../../pagination/pagination-settings'; +import { mapEach } from '../../utils/operators'; +import { IListable } from '../models/listable'; +import { Id } from '../models/trackable'; +import { EntitiesService } from './entities.service'; interface PaginatedResponse { data: Interface[]; 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 16f870c..9c13c64 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,4 +1,8 @@ +import { AsyncPipe, NgClass } from '@angular/common'; import { ChangeDetectionStrategy, Component, Input, Optional } from '@angular/core'; +import { MatIcon } from '@angular/material/icon'; +import { MatTooltip } from '@angular/material/tooltip'; +import { TranslateModule } from '@ngx-translate/core'; import { SortingOrders, SortingService } from '../../sorting'; import { KeysOf } from '../../utils'; import { Id, IListable } from '../models'; @@ -8,6 +12,8 @@ import { Id, IListable } from '../models'; templateUrl: './table-column-name.component.html', styleUrls: ['./table-column-name.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [MatIcon, MatTooltip, TranslateModule, AsyncPipe, NgClass], }) export class TableColumnNameComponent, PrimaryKey extends Id = T['id']> { readonly sortingOrders = SortingOrders; diff --git a/src/lib/listing/table-content/table-content.component.ts b/src/lib/listing/table-content/table-content.component.ts index 4056f50..77f2dfb 100644 --- a/src/lib/listing/table-content/table-content.component.ts +++ b/src/lib/listing/table-content/table-content.component.ts @@ -1,23 +1,44 @@ /* eslint-disable @angular-eslint/prefer-on-push-component-change-detection */ +import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; +import { AsyncPipe, NgClass } from '@angular/common'; import { AfterViewInit, Component, forwardRef, HostListener, Inject, Input, OnDestroy, Optional, ViewChild } from '@angular/core'; -import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; -import { delay, tap } from 'rxjs/operators'; -import { AutoUnsubscribe, trackByFactory } from '../../utils'; -import { Id, IListable } from '../models'; -import { ListingComponent, ListingService } from '../index'; +import { RouterLink } from '@angular/router'; import { BehaviorSubject } from 'rxjs'; -import { HelpModeService } from '../../help-mode'; -import { HasScrollbarDirective } from '../../directives'; +import { delay, tap } from 'rxjs/operators'; +import { HasScrollbarDirective } from '../../directives/has-scrollbar.directive'; +import { HelpModeService } from '../../help-mode/help-mode.service'; +import { SnakeCasePipe } from '../../pipes/snake-case.pipe'; +import { AutoUnsubscribe } from '../../utils/auto-unsubscribe.directive'; +import { trackByFactory } from '../../utils/functions'; +import { ListingComponent } from '../listing-component.directive'; +import { IListable } from '../models/listable'; +import { Id } from '../models/trackable'; +import { ListingService } from '../services/listing.service'; +import { TableItemComponent } from './table-item/table-item.component'; @Component({ selector: 'iqser-table-content', templateUrl: './table-content.component.html', styleUrls: ['./table-content.component.scss'], + standalone: true, + imports: [ + CdkVirtualScrollViewport, + AsyncPipe, + CdkFixedSizeVirtualScroll, + HasScrollbarDirective, + CdkVirtualForOf, + SnakeCasePipe, + NgClass, + RouterLink, + TableItemComponent, + ], }) export class TableContentComponent, PrimaryKey extends Id = Class['id']> extends AutoUnsubscribe implements OnDestroy, AfterViewInit { + private _lastScrolledIndex = 0; + private _multiSelectActive$ = new BehaviorSubject(false); @Input() itemSize!: number; @Input() itemMouseEnterFn?: (entity: Class) => void; @Input() itemMouseLeaveFn?: (entity: Class) => void; @@ -26,13 +47,9 @@ export class TableContentComponent, PrimaryK @Input() rowIdPrefix: string = 'item'; @Input() namePropertyKey?: string; readonly trackBy = trackByFactory(); - @ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport; @ViewChild(HasScrollbarDirective, { static: true }) readonly hasScrollbarDirective!: HasScrollbarDirective; - private _lastScrolledIndex = 0; - private _multiSelectActive$ = new BehaviorSubject(false); - constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, readonly listingService: ListingService, diff --git a/src/lib/listing/table-content/table-item/table-item.component.ts b/src/lib/listing/table-content/table-item/table-item.component.ts index 5ab456b..4fadc69 100644 --- a/src/lib/listing/table-content/table-item/table-item.component.ts +++ b/src/lib/listing/table-content/table-item/table-item.component.ts @@ -1,22 +1,25 @@ +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, forwardRef, Inject, Input, OnChanges } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; -import { ListingComponent } from '../..'; -import { IListable } from '../../models'; -import { ListingService } from '../../services'; import { switchMap } from 'rxjs/operators'; +import { RoundCheckboxComponent } from '../../../inputs/round-checkbox/round-checkbox.component'; +import { ListingComponent } from '../../listing-component.directive'; +import { IListable } from '../../models/listable'; +import { ListingService } from '../../services/listing.service'; @Component({ selector: 'iqser-table-item [entity]', templateUrl: './table-item.component.html', styleUrls: ['./table-item.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [RoundCheckboxComponent, AsyncPipe, NgTemplateOutlet], }) export class TableItemComponent implements OnChanges { @Input() entity!: T; - @Input() selectionEnabled = false; - - readonly isSelected$: Observable; readonly #entityChanged$ = new BehaviorSubject(this.entity); + @Input() selectionEnabled = false; + readonly isSelected$: Observable; constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, diff --git a/src/lib/listing/table-header/table-header.component.ts b/src/lib/listing/table-header/table-header.component.ts index 7c0212b..74cc476 100644 --- a/src/lib/listing/table-header/table-header.component.ts +++ b/src/lib/listing/table-header/table-header.component.ts @@ -1,13 +1,28 @@ -import { ChangeDetectionStrategy, Component, Input, TemplateRef } from '@angular/core'; -import { FilterService } from '../../filtering'; -import { EntitiesService, ListingService } from '../services'; +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; +import { ChangeDetectionStrategy, Component, inject, Input, TemplateRef } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { SyncWidthDirective } from '../../directives'; +import { FilterService, IqserFiltersModule } from '../../filtering'; +import { RoundCheckboxComponent } from '../../inputs'; import { Id, IListable, ListingMode, ListingModes, TableColumnConfig } from '../models'; +import { EntitiesService, ListingService } from '../services'; +import { TableColumnNameComponent } from '../table-column-name/table-column-name.component'; @Component({ selector: 'iqser-table-header [tableHeaderLabel]', templateUrl: './table-header.component.html', styleUrls: ['./table-header.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + RoundCheckboxComponent, + AsyncPipe, + TranslateModule, + NgTemplateOutlet, + IqserFiltersModule, + SyncWidthDirective, + TableColumnNameComponent, + ], }) export class TableHeaderComponent, PrimaryKey extends Id = T['id']> { readonly listingModes = ListingModes; @@ -21,11 +36,10 @@ export class TableHeaderComponent, PrimaryKey ex @Input() bulkActions?: TemplateRef; @Input() helpModeKey?: string; - readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters'); + readonly quickFilters$ = inject(FilterService).getFilterModels$('quickFilters'); constructor( readonly entitiesService: EntitiesService, readonly listingService: ListingService, - readonly filterService: FilterService, ) {} } diff --git a/src/lib/listing/table/table.component.ts b/src/lib/listing/table/table.component.ts index 124376e..0b08eac 100644 --- a/src/lib/listing/table/table.component.ts +++ b/src/lib/listing/table/table.component.ts @@ -1,3 +1,4 @@ +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -13,10 +14,13 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; -import { Id, IListable, ListingModes, TableColumnConfig } from '../models'; +import { EmptyStateComponent } from '../../empty-state'; import { ListingComponent } from '../listing-component.directive'; +import { Id, IListable, ListingModes, TableColumnConfig } from '../models'; +import { ScrollButtonComponent } from '../scroll-button/scroll-button.component'; import { EntitiesService } from '../services'; import { TableContentComponent } from '../table-content/table-content.component'; +import { TableHeaderComponent } from '../table-header/table-header.component'; const SCROLLBAR_WIDTH = 11; @@ -24,10 +28,12 @@ const SCROLLBAR_WIDTH = 11; selector: 'iqser-table [tableColumnConfigs] [itemSize]', templateUrl: './table.component.html', changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [TableHeaderComponent, NgTemplateOutlet, AsyncPipe, EmptyStateComponent, ScrollButtonComponent, TableContentComponent], }) export class TableComponent, PrimaryKey extends Id = Class['id']> implements OnChanges { + @ViewChild(TableContentComponent, { static: true }) private readonly _tableContent!: TableContentComponent; readonly listingModes = ListingModes; - @Input() tableColumnConfigs!: readonly TableColumnConfig[]; @Input() bulkActions?: TemplateRef; @Input() headerTemplate?: TemplateRef; @@ -51,7 +57,6 @@ export class TableComponent, PrimaryKey exte @Input() itemMouseEnterFn?: (entity: Class) => void; @Input() itemMouseLeaveFn?: (entity: Class) => void; @Output() readonly noDataAction = new EventEmitter(); - @ViewChild(TableContentComponent, { static: true }) private readonly _tableContent!: TableContentComponent; constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, 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 6320a4e..3de5eec 100644 --- a/src/lib/listing/workflow/column-header/column-header.component.ts +++ b/src/lib/listing/workflow/column-header/column-header.component.ts @@ -1,3 +1,4 @@ +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -10,11 +11,13 @@ import { TemplateRef, ViewChild, } from '@angular/core'; -import { combineLatest, Observable } from 'rxjs'; +import { TranslateModule } from '@ngx-translate/core'; +import { combineLatest } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; -import { CircleButtonTypes } from '../../../buttons'; +import { CircleButtonComponent, CircleButtonTypes } from '../../../buttons'; +import { RoundCheckboxComponent } from '../../../inputs'; +import { ContextComponent, Debounce } from '../../../utils'; import { IListable } from '../../models'; -import { Debounce, ContextComponent } from '../../../utils'; import { ListingService } from '../../services'; import { WorkflowColumn } from '../models/workflow-column.model'; @@ -31,6 +34,8 @@ interface ColumnHeaderContext { templateUrl: './column-header.component.html', styleUrls: ['./column-header.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [AsyncPipe, TranslateModule, RoundCheckboxComponent, NgTemplateOutlet, CircleButtonComponent], }) export class ColumnHeaderComponent extends ContextComponent implements OnInit { readonly circleButtonTypes = CircleButtonTypes; diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts index 43345de..89e8ac1 100644 --- a/src/lib/listing/workflow/workflow.component.ts +++ b/src/lib/listing/workflow/workflow.component.ts @@ -1,3 +1,13 @@ +import { + CdkDrag, + CdkDragDrop, + CdkDragPlaceholder, + CdkDragPreview, + CdkDragStart, + CdkDropList, + CdkDropListGroup, +} from '@angular/cdk/drag-drop'; +import { AsyncPipe, NgClass, NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -14,16 +24,19 @@ import { TemplateRef, ViewChildren, } from '@angular/core'; -import { ListingComponent } from '../listing-component.directive'; -import { CdkDragDrop, CdkDragStart, CdkDropList } from '@angular/cdk/drag-drop'; -import { ContextComponent, Debounce, trackByFactory } from '../../utils'; -import { IListable } from '../models'; -import { EntitiesService, ListingService } from '../services'; +import { MatIcon } from '@angular/material/icon'; import { BehaviorSubject } from 'rxjs'; import { filter, tap } from 'rxjs/operators'; -import { WorkflowConfig } from './models/workflow-config.model'; -import { WorkflowColumn } from './models/workflow-column.model'; +import { EmptyStateComponent } from '../../empty-state'; +import { ContextComponent, Debounce, trackByFactory } from '../../utils'; +import { ListingComponent } from '../listing-component.directive'; +import { IListable } from '../models'; +import { EntitiesService, ListingService } from '../services'; +import { TableHeaderComponent } from '../table-header/table-header.component'; +import { ColumnHeaderComponent } from './column-header/column-header.component'; import { EntityWrapper } from './models/entity-wrapper.model'; +import { WorkflowColumn } from './models/workflow-column.model'; +import { WorkflowConfig } from './models/workflow-config.model'; interface WorkflowContext { noData: boolean; @@ -37,8 +50,27 @@ interface WorkflowContext { templateUrl: './workflow.component.html', styleUrls: ['./workflow.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + TableHeaderComponent, + AsyncPipe, + EmptyStateComponent, + CdkDropListGroup, + ColumnHeaderComponent, + CdkDropList, + CdkDrag, + NgClass, + CdkDragPlaceholder, + NgTemplateOutlet, + CdkDragPreview, + MatIcon, + ], }) export class WorkflowComponent extends ContextComponent> implements OnInit { + @ViewChildren(CdkDropList) private readonly _dropLists!: QueryList; + private readonly _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { + this._updateItemSize(entries[0]); + }); @Input() config!: WorkflowConfig; @Input() itemClasses!: Record boolean>; @Input() addElementIcon!: string; @@ -51,9 +83,7 @@ export class WorkflowComponent extends Co @Input() bulkActions?: TemplateRef; @Output() readonly noDataAction = new EventEmitter(); @Output() readonly addElement = new EventEmitter(); - @ContentChild('workflowItemTemplate') readonly itemTemplate!: TemplateRef<{ entity: T }>; - readonly trackBy = trackByFactory(); itemHeight?: number; itemWidth?: number; @@ -62,10 +92,6 @@ export class WorkflowComponent extends Co selectionColumn?: WorkflowColumn; readonly draggingEntities$ = new BehaviorSubject([]); all: { [key: string]: EntityWrapper } = {}; - @ViewChildren(CdkDropList) private readonly _dropLists!: QueryList; - private readonly _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { - this._updateItemSize(entries[0]); - }); constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, diff --git a/src/lib/permissions/services/permissions-guard.service.ts b/src/lib/permissions/services/permissions-guard.service.ts index 95d3738..7051712 100644 --- a/src/lib/permissions/services/permissions-guard.service.ts +++ b/src/lib/permissions/services/permissions-guard.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, CanMatch, Route, Router, RouterStateSnapshot } from '@angular/router'; +import { NGXLogger } from 'ngx-logger'; import { firstValueFrom, from, of } from 'rxjs'; import { first, mergeMap } from 'rxjs/operators'; @@ -7,22 +8,16 @@ import { first, mergeMap } from 'rxjs/operators'; import { DEFAULT_REDIRECT_KEY, IqserActivatedRouteSnapshot, + IqserPermissionsData, IqserRoute, NavigationCommandsFn, NavigationExtrasFn, RedirectTo, RedirectToFn, } from '../types'; +import { isArray, isFunction, isRedirectWithParameters, isString, transformPermission } from '../utils'; import { IqserPermissionsService } from './permissions.service'; import { IqserRolesService } from './roles.service'; -import { isArray, isFunction, isRedirectWithParameters, isString, transformPermission } from '../utils'; -import { List } from '../../utils'; -import { NGXLogger } from 'ngx-logger'; - -export interface IqserPermissionsData { - readonly allow: string | List; - readonly redirectTo?: RedirectTo | RedirectToFn; -} @Injectable({ providedIn: 'root', diff --git a/src/lib/permissions/services/permissions.service.ts b/src/lib/permissions/services/permissions.service.ts index 948e2aa..41a4236 100644 --- a/src/lib/permissions/services/permissions.service.ts +++ b/src/lib/permissions/services/permissions.service.ts @@ -1,18 +1,18 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { List } from '../../utils/types/iqser-types'; +import { IqserPermissions, PermissionValidationFn } from '../types'; import { isArray, isString, toArray } from '../utils'; -import { IqserPermissions, PermissionValidationFn } from '../types'; -import { List } from '../../utils'; -import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root', }) export class IqserPermissionsService { - readonly permissions$: Observable; readonly #permissions$ = new BehaviorSubject({}); + readonly permissions$: Observable; constructor() { this.permissions$ = this.#permissions$.asObservable(); diff --git a/src/lib/permissions/services/roles.service.ts b/src/lib/permissions/services/roles.service.ts index 82bb1c6..d877c36 100644 --- a/src/lib/permissions/services/roles.service.ts +++ b/src/lib/permissions/services/roles.service.ts @@ -1,17 +1,17 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; +import { List } from '../../utils/types/iqser-types'; import { IqserRoles, RoleValidationFn } from '../types'; import { isArray, isBoolean, isString, toArray } from '../utils'; -import { List } from '../../utils'; import { IqserPermissionsService } from './permissions.service'; @Injectable({ providedIn: 'root', }) export class IqserRolesService { - readonly roles$: Observable; readonly #roles$ = new BehaviorSubject({}); + readonly roles$: Observable; constructor(private readonly _permissionsService: IqserPermissionsService) { this.roles$ = this.#roles$.asObservable(); diff --git a/src/lib/permissions/types.ts b/src/lib/permissions/types.ts index c2ba713..365b06c 100644 --- a/src/lib/permissions/types.ts +++ b/src/lib/permissions/types.ts @@ -1,5 +1,5 @@ import { ActivatedRouteSnapshot, NavigationExtras, Route, RouterStateSnapshot } from '@angular/router'; -import { List } from '../utils'; +import { List } from '../utils/types/iqser-types'; export type IqserPermissions = Record; export type IqserRoles = Record; @@ -9,6 +9,11 @@ export interface IqserPermissionsRouterData { redirectTo?: RedirectTo | RedirectToFn; } +export interface IqserPermissionsData { + readonly allow: string | List; + readonly redirectTo?: RedirectTo | RedirectToFn; +} + export interface IqserRedirectToNavigationParameters { navigationCommands: any[] | NavigationCommandsFn; navigationExtras?: NavigationExtras | NavigationExtrasFn; diff --git a/src/lib/permissions/utils.ts b/src/lib/permissions/utils.ts index da56331..57e3e37 100644 --- a/src/lib/permissions/utils.ts +++ b/src/lib/permissions/utils.ts @@ -1,7 +1,6 @@ -import { AllowFn, IqserPermissionsRouterData, IqserRedirectToNavigationParameters } from './types'; import { ActivatedRouteSnapshot, Route, RouterStateSnapshot } from '@angular/router'; -import { IqserPermissionsData } from './services/permissions-guard.service'; import { List } from '../utils'; +import { AllowFn, IqserPermissionsData, IqserPermissionsRouterData, IqserRedirectToNavigationParameters } from './types'; export function isFunction(value: unknown): value is T { return typeof value === 'function'; diff --git a/src/lib/search/search.service.ts b/src/lib/search/search.service.ts index 125b289..c6d0235 100644 --- a/src/lib/search/search.service.ts +++ b/src/lib/search/search.service.ts @@ -1,12 +1,13 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; -import { Id, IListable } from '../listing'; -import { shareDistinctLast } from '../utils'; +import { IListable } from '../listing/models/listable'; +import { Id } from '../listing/models/trackable'; +import { shareDistinctLast } from '../utils/operators'; @Injectable() export class SearchService, PrimaryKey extends Id = T['id']> { - skip = false; private readonly _query$ = new BehaviorSubject(''); + skip = false; readonly valueChanges$ = this._query$.asObservable().pipe(shareDistinctLast()); get searchValue(): string { diff --git a/src/lib/services/api-path.interceptor.ts b/src/lib/services/api-path.interceptor.ts index 79552ae..0eb3852 100644 --- a/src/lib/services/api-path.interceptor.ts +++ b/src/lib/services/api-path.interceptor.ts @@ -1,8 +1,8 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { Observable } from 'rxjs'; +import { UI_ROOT_PATH_FN } from '../utils/tokens'; import { getConfig } from './iqser-config.service'; -import { UI_ROOT_PATH_FN } from '../utils'; @Injectable() export class ApiPathInterceptor implements HttpInterceptor { diff --git a/src/lib/services/composite-route.guard.ts b/src/lib/services/composite-route.guard.ts index d27db8a..0d7f9d7 100644 --- a/src/lib/services/composite-route.guard.ts +++ b/src/lib/services/composite-route.guard.ts @@ -2,7 +2,7 @@ import { inject, Injectable, InjectionToken, Injector, runInInjectionContext } f import { ActivatedRouteSnapshot, CanActivate, CanActivateFn, GuardResult, RouterStateSnapshot, UrlTree } from '@angular/router'; import { concatMap, firstValueFrom, from, last, Observable, of, takeWhile } from 'rxjs'; import { tap } from 'rxjs/operators'; -import { LoadingService } from '../loading'; +import { LoadingService } from '../loading/loading.service'; import { SkeletonService } from './skeleton.service'; @Injectable({ diff --git a/src/lib/services/entities-map.service.ts b/src/lib/services/entities-map.service.ts index 5f2ea90..2e0565c 100644 --- a/src/lib/services/entities-map.service.ts +++ b/src/lib/services/entities-map.service.ts @@ -1,16 +1,18 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { filter, map, startWith } from 'rxjs/operators'; -import { Entity, Id } from '../listing'; -import { List, shareLast } from '../utils'; -import { isArray } from '../permissions'; +import { Entity } from '../listing/models/entity.model'; +import { Id } from '../listing/models/trackable'; +import { isArray } from '../permissions/utils'; +import { shareLast } from '../utils/operators'; +import { List } from '../utils/types/iqser-types'; @Injectable() export abstract class EntitiesMapService, PrimaryKey extends Id = Class['id']> { - protected readonly _map = new Map>(); readonly #entityChanged$ = new Subject(); readonly #entitiesChanged$ = new BehaviorSubject(false); readonly #entityDeleted$ = new Subject(); + protected readonly _map = new Map>(); get empty(): boolean { return this._map.size === 0; diff --git a/src/lib/services/generic.service.ts b/src/lib/services/generic.service.ts index 54a9cf7..8910755 100644 --- a/src/lib/services/generic.service.ts +++ b/src/lib/services/generic.service.ts @@ -1,18 +1,13 @@ import { HttpClient, HttpEvent, HttpParams } from '@angular/common/http'; import { inject } from '@angular/core'; import { Observable } from 'rxjs'; -import { HeadersConfiguration, List } from '../utils'; import { map } from 'rxjs/operators'; +import { HeadersConfiguration } from '../utils/headers-configuration'; +import { List } from '../utils/types/iqser-types'; export const ROOT_CHANGES_KEY = 'root'; export const LAST_CHECKED_OFFSET = 30000; -export interface HeaderOptions { - readonly authorization?: boolean; - readonly accept?: boolean; - readonly contentType?: boolean; -} - export interface QueryParam { readonly key: string; readonly value: string | number | boolean; diff --git a/src/lib/services/iqser-config.service.ts b/src/lib/services/iqser-config.service.ts index b0189b8..5b4636c 100644 --- a/src/lib/services/iqser-config.service.ts +++ b/src/lib/services/iqser-config.service.ts @@ -1,7 +1,8 @@ import { Inject, inject, Injectable } from '@angular/core'; import { Title } from '@angular/platform-browser'; -import { CacheApiService, wipeAllCaches } from '../caching'; -import { IqserAppConfig } from '../utils'; +import { CacheApiService } from '../caching/cache-api.service'; +import { wipeAllCaches } from '../caching/cache-utils'; +import { IqserAppConfig } from '../utils/iqser-app-config'; @Injectable() export class IqserConfigService { diff --git a/src/lib/services/iqser-user-preference.service.ts b/src/lib/services/iqser-user-preference.service.ts index 1bafc99..00aa140 100644 --- a/src/lib/services/iqser-user-preference.service.ts +++ b/src/lib/services/iqser-user-preference.service.ts @@ -1,8 +1,8 @@ +import { APP_BASE_HREF } from '@angular/common'; import { inject, Injectable } from '@angular/core'; import { firstValueFrom } from 'rxjs'; -import { List } from '../utils'; +import { List } from '../utils/types/iqser-types'; import { GenericService } from './generic.service'; -import { APP_BASE_HREF } from '@angular/common'; export type UserAttributes = Record; @@ -14,9 +14,9 @@ export const KEYS = { @Injectable() export abstract class IqserUserPreferenceService extends GenericService { + #userAttributes: UserAttributes = {}; protected abstract readonly _devFeaturesEnabledKey: string; protected readonly _serviceName: string = 'tenant-user-management'; - #userAttributes: UserAttributes = {}; get userAttributes(): UserAttributes { return this.#userAttributes; diff --git a/src/lib/services/stats.service.ts b/src/lib/services/stats.service.ts index 062edf4..28545aa 100644 --- a/src/lib/services/stats.service.ts +++ b/src/lib/services/stats.service.ts @@ -1,19 +1,19 @@ +import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { BehaviorSubject, Observable, switchMap } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; import { tap } from 'rxjs/operators'; -import { HeadersConfiguration, mapEach } from '../utils'; +import { HeadersConfiguration } from '../utils/headers-configuration'; +import { mapEach } from '../utils/operators'; @Injectable() export abstract class StatsService { + readonly #http = inject(HttpClient); + readonly #map = new Map>(); protected abstract readonly _primaryKey: string; protected abstract readonly _entityClass: new (entityInterface: I, ...args: unknown[]) => E; protected abstract readonly _defaultModelPath: string; protected readonly _serviceName: string = 'redaction-gateway-v1'; - readonly #http = inject(HttpClient); - readonly #map = new Map>(); - getFor(ids: string[]): Observable { const request = this.#http.post(`/${this._serviceName}/${encodeURI(this._defaultModelPath)}`, ids, { headers: HeadersConfiguration.getHeaders(), diff --git a/src/lib/sorting/sorting.service.ts b/src/lib/sorting/sorting.service.ts index 0970316..38d52b1 100644 --- a/src/lib/sorting/sorting.service.ts +++ b/src/lib/sorting/sorting.service.ts @@ -1,10 +1,12 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; +import { IListable } from '../listing/models/listable'; +import { Id } from '../listing/models/trackable'; +import { shareDistinctLast } from '../utils/operators'; +import { KeysOf } from '../utils/types/utility-types'; +import { sort } from './functions'; import { SortingOption } from './models/sorting-option.model'; import { SortingOrders } from './models/sorting-order.type'; -import { KeysOf, shareDistinctLast } from '../utils'; -import { Id, IListable } from '../listing'; -import { sort } from './functions'; @Injectable() export class SortingService, PrimaryKey extends Id = T['id']> { diff --git a/src/lib/tenants/index.ts b/src/lib/tenants/index.ts index e3064d8..bbd490a 100644 --- a/src/lib/tenants/index.ts +++ b/src/lib/tenants/index.ts @@ -1,6 +1,6 @@ -export * from './keycloak-initializer'; +export * from './services/keycloak-status.service'; export * from './services'; +export * from './keycloak-initializer'; export * from './tenants.module'; export * from './tenant-select/tenant-select.component'; -export * from './services/keycloak-status.service'; export * from './types'; diff --git a/src/lib/tenants/keycloak-initializer.ts b/src/lib/tenants/keycloak-initializer.ts index 8430f6d..0e69652 100644 --- a/src/lib/tenants/keycloak-initializer.ts +++ b/src/lib/tenants/keycloak-initializer.ts @@ -1,35 +1,11 @@ -import { IqserAppConfig, UI_ROOT } from '../utils'; -import { KeycloakOptions, KeycloakService } from 'keycloak-angular'; -import { KeycloakStatusService } from './services/keycloak-status.service'; import { inject } from '@angular/core'; -import { getConfig } from '../services'; -import { NGXLogger } from 'ngx-logger'; import { Router } from '@angular/router'; - -export function getKeycloakOptions(baseUrl: string, config: IqserAppConfig, tenant: string): KeycloakOptions { - let oauthUrl = config.OAUTH_URL; - if (!oauthUrl.startsWith('http')) { - oauthUrl = oauthUrl.startsWith('/') ? oauthUrl : '/' + oauthUrl; - oauthUrl = window.location.origin + oauthUrl; - } - - return { - config: { - url: oauthUrl, - realm: tenant, - clientId: config.OAUTH_CLIENT_ID, - }, - initOptions: { - checkLoginIframe: false, - onLoad: 'check-sso', - silentCheckSsoRedirectUri: window.location.origin + baseUrl + '/assets/oauth/silent-refresh.html', - flow: 'standard', - enableLogging: true, - }, - enableBearerInterceptor: true, - loadUserProfileAtStartUp: true, - }; -} +import { KeycloakService } from 'keycloak-angular'; +import { NGXLogger } from 'ngx-logger'; +import { getConfig } from '../services/iqser-config.service'; +import { UI_ROOT } from '../utils/tokens'; +import { getKeycloakOptions } from './keycloak-options'; +import { KeycloakStatusService } from './services/keycloak-status.service'; function configureAutomaticRedirectToLoginScreen(keyCloakService: KeycloakService, keycloakStatusService: KeycloakStatusService) { const keycloakInstance = keyCloakService.getKeycloakInstance(); diff --git a/src/lib/tenants/keycloak-options.ts b/src/lib/tenants/keycloak-options.ts new file mode 100644 index 0000000..7f4a8a9 --- /dev/null +++ b/src/lib/tenants/keycloak-options.ts @@ -0,0 +1,27 @@ +import { KeycloakOptions } from 'keycloak-angular'; +import { IqserAppConfig } from '../utils/iqser-app-config'; + +export function getKeycloakOptions(baseUrl: string, config: IqserAppConfig, tenant: string): KeycloakOptions { + let oauthUrl = config.OAUTH_URL; + if (!oauthUrl.startsWith('http')) { + oauthUrl = oauthUrl.startsWith('/') ? oauthUrl : '/' + oauthUrl; + oauthUrl = window.location.origin + oauthUrl; + } + + return { + config: { + url: oauthUrl, + realm: tenant, + clientId: config.OAUTH_CLIENT_ID, + }, + initOptions: { + checkLoginIframe: false, + onLoad: 'check-sso', + silentCheckSsoRedirectUri: window.location.origin + baseUrl + '/assets/oauth/silent-refresh.html', + flow: 'standard', + enableLogging: true, + }, + enableBearerInterceptor: true, + loadUserProfileAtStartUp: true, + }; +} diff --git a/src/lib/tenants/services/keycloak-status.service.ts b/src/lib/tenants/services/keycloak-status.service.ts index 082d6ff..54ece2a 100644 --- a/src/lib/tenants/services/keycloak-status.service.ts +++ b/src/lib/tenants/services/keycloak-status.service.ts @@ -1,9 +1,10 @@ import { inject, Injectable } from '@angular/core'; import { KeycloakService } from 'keycloak-angular'; -import { getConfig } from '../../services'; -import { getKeycloakOptions, TenantsService } from '../index'; import { NGXLogger } from 'ngx-logger'; -import { UI_ROOT } from '../../utils'; +import { getConfig } from '../../services/iqser-config.service'; +import { UI_ROOT } from '../../utils/tokens'; +import { getKeycloakOptions } from '../keycloak-options'; +import { TenantsService } from './tenants.service'; @Injectable({ providedIn: 'root' }) export class KeycloakStatusService { diff --git a/src/lib/tenants/tenant-select/tenant-select.component.ts b/src/lib/tenants/tenant-select/tenant-select.component.ts index 6344a6e..57c74ea 100644 --- a/src/lib/tenants/tenant-select/tenant-select.component.ts +++ b/src/lib/tenants/tenant-select/tenant-select.component.ts @@ -5,11 +5,11 @@ import { KeycloakService } from 'keycloak-angular'; import { NGXLogger } from 'ngx-logger'; import { LoadingService } from '../../loading'; import { getConfig } from '../../services'; -import { getKeycloakOptions } from '../keycloak-initializer'; +import { selectTenantTranslations } from '../../translations/select-tenant-translations'; +import { UI_ROOT } from '../../utils'; +import { getKeycloakOptions } from '../keycloak-options'; import { IStoredTenantId, TenantsService } from '../services'; import { KeycloakStatusService } from '../services/keycloak-status.service'; -import { UI_ROOT } from '../../utils'; -import { selectTenantTranslations } from '../../translations/select-tenant-translations'; @Component({ templateUrl: './tenant-select.component.html', @@ -17,8 +17,7 @@ import { selectTenantTranslations } from '../../translations/select-tenant-trans changeDetection: ChangeDetectionStrategy.OnPush, }) export class TenantSelectComponent { - @Input() isLoggedOut = false; - @Input() noRoleLogOut = false; + readonly #uiRoot = inject(UI_ROOT); protected readonly logger = inject(NGXLogger); protected readonly tenantsService = inject(TenantsService); protected storedTenants: IStoredTenantId[] = []; @@ -32,7 +31,8 @@ export class TenantSelectComponent { tenantId: ['', Validators.required], }); protected readonly translations = selectTenantTranslations; - readonly #uiRoot = inject(UI_ROOT); + @Input() isLoggedOut = false; + @Input() noRoleLogOut = false; constructor() { this.#loadStoredTenants(); diff --git a/src/lib/tenants/tenants.module.ts b/src/lib/tenants/tenants.module.ts index 104c679..dcbbe46 100644 --- a/src/lib/tenants/tenants.module.ts +++ b/src/lib/tenants/tenants.module.ts @@ -10,11 +10,13 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { RouterLink } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; -import { CircleButtonComponent, IconButtonComponent } from '../buttons'; -import { StopPropagationDirective } from '../directives'; -import { LogoComponent } from '../shared'; +import { CircleButtonComponent } from '../buttons/circle-button/circle-button.component'; +import { IconButtonComponent } from '../buttons/icon-button/icon-button.component'; +import { StopPropagationDirective } from '../directives/stop-propagation.directive'; +import { LogoComponent } from '../shared/logo/logo.component'; import { SpacerComponent } from '../shared/spacer/spacer.component'; -import { TenantIdInterceptor, TenantIdResponseInterceptor } from './services'; +import { TenantIdInterceptor } from './services/tenant-id-interceptor'; +import { TenantIdResponseInterceptor } from './services/tenant-id-response-interceptor'; import { TenantSelectComponent } from './tenant-select/tenant-select.component'; @NgModule({ diff --git a/src/lib/utils/auto-unsubscribe.directive.ts b/src/lib/utils/auto-unsubscribe.directive.ts index 1431dc3..cb3fa31 100644 --- a/src/lib/utils/auto-unsubscribe.directive.ts +++ b/src/lib/utils/auto-unsubscribe.directive.ts @@ -3,7 +3,8 @@ import { Subscription } from 'rxjs'; import { OnDetach } from './custom-route-reuse.strategy'; /** - * Inherit this class when you need to subscribe to observables in your components + * @deprecated Use takeUntilDestroyed() + * TODO: remove this asap */ @Directive() export abstract class AutoUnsubscribe implements OnDestroy, OnDetach { diff --git a/src/lib/utils/custom-route-reuse.strategy.ts b/src/lib/utils/custom-route-reuse.strategy.ts index 00d69b7..b993da2 100644 --- a/src/lib/utils/custom-route-reuse.strategy.ts +++ b/src/lib/utils/custom-route-reuse.strategy.ts @@ -1,7 +1,7 @@ -import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'; -import { Debounce } from '../utils'; import { ComponentRef, Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'; import { Subject } from 'rxjs'; +import { Debounce } from './decorators/debounce.decorator'; export interface OnAttach { ngOnAttach(previousRoute?: ActivatedRouteSnapshot): void; @@ -25,9 +25,9 @@ interface RouteStorageObject { @Injectable({ providedIn: 'root' }) export class CustomRouteReuseStrategy implements RouteReuseStrategy { + readonly #storedRoutes = new Map(); readonly attached$ = new Subject(); readonly detached$ = new Subject(); - readonly #storedRoutes = new Map(); private static _removeTooltips(): void { while (document.getElementsByTagName('mat-tooltip-component').length > 0) { diff --git a/src/lib/utils/functions.ts b/src/lib/utils/functions.ts index da8ca29..517d26f 100644 --- a/src/lib/utils/functions.ts +++ b/src/lib/utils/functions.ts @@ -3,7 +3,7 @@ import { UntypedFormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import dayjs, { type Dayjs } from 'dayjs'; import { forOwn, has, isEqual, isPlainObject, transform } from 'lodash-es'; -import type { Id, ITrackable } from '../listing'; +import { Id, ITrackable } from '../listing/models/trackable'; export function capitalize(value: string | string): string { if (!value) { diff --git a/src/lib/utils/headers-configuration.ts b/src/lib/utils/headers-configuration.ts index f3631f7..ef96cc0 100644 --- a/src/lib/utils/headers-configuration.ts +++ b/src/lib/utils/headers-configuration.ts @@ -1,5 +1,10 @@ import { HttpHeaders } from '@angular/common/http'; -import { HeaderOptions } from '../services/generic.service'; + +export interface HeaderOptions { + readonly authorization?: boolean; + readonly accept?: boolean; + readonly contentType?: boolean; +} export class HeadersConfiguration { static getHeaders(options?: HeaderOptions): HttpHeaders { diff --git a/src/lib/utils/types/common-ui-options.ts b/src/lib/utils/types/common-ui-options.ts index 130659a..d5fe61b 100644 --- a/src/lib/utils/types/common-ui-options.ts +++ b/src/lib/utils/types/common-ui-options.ts @@ -1,5 +1,6 @@ -import { IqserConfigService, IqserUserPreferenceService } from '../../services'; import { Type } from '@angular/core'; +import { IqserConfigService } from '../../services/iqser-config.service'; +import { IqserUserPreferenceService } from '../../services/iqser-user-preference.service'; import { IqserAppConfig } from '../iqser-app-config'; export interface CommonUiOptions< From f3faa6a6cc6b36840de6952f15938858130a4ef2 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 20 Jun 2024 11:36:47 +0300 Subject: [PATCH 127/201] migrate inputs to signals & remove inputs/index.ts --- src/index.ts | 1 - src/lib/filtering/filters.module.ts | 12 ++-- .../simple-popup-filter.component.ts | 4 +- .../details-radio.component.html | 14 +++-- .../details-radio/details-radio.component.ts | 21 ++++--- .../dynamic-input.component.html | 31 ++++++---- .../dynamic-input/dynamic-input.component.ts | 40 +++++-------- .../editable-input.component.html | 51 +++++++++------- .../editable-input.component.ts | 60 +++++++++---------- src/lib/inputs/index.ts | 7 --- .../input-with-action.component.html | 29 +++++---- .../input-with-action.component.ts | 58 +++++++----------- .../round-checkbox.component.html | 13 ++-- .../round-checkbox.component.ts | 25 ++++---- src/lib/listing/listing.module.ts | 3 +- .../table-header/table-header.component.ts | 2 +- .../column-header/column-header.component.ts | 12 ++-- src/lib/utils/context.component.ts | 3 + 18 files changed, 193 insertions(+), 193 deletions(-) delete mode 100644 src/lib/inputs/index.ts diff --git a/src/index.ts b/src/index.ts index d6bd5ca..2014a20 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,6 @@ export * from './lib/dialog'; export * from './lib/form'; export * from './lib/listing'; export * from './lib/help-mode'; -export * from './lib/inputs'; export * from './lib/services'; export * from './lib/loading'; export * from './lib/error'; diff --git a/src/lib/filtering/filters.module.ts b/src/lib/filtering/filters.module.ts index 7667ea0..ec8e941 100644 --- a/src/lib/filtering/filters.module.ts +++ b/src/lib/filtering/filters.module.ts @@ -1,14 +1,16 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { TranslateModule } from '@ngx-translate/core'; -import { ChevronButtonComponent, IconButtonComponent } from '../buttons'; +import { ChevronButtonComponent } from '../buttons/chevron-button/chevron-button.component'; +import { IconButtonComponent } from '../buttons/icon-button/icon-button.component'; +import { PreventDefaultDirective } from '../directives/prevent-default.directive'; +import { StopPropagationDirective } from '../directives/stop-propagation.directive'; +import { InputWithActionComponent } from '../inputs/input-with-action/input-with-action.component'; import { QuickFiltersComponent } from './quick-filters/quick-filters.component'; import { SingleFilterComponent } from './single-filter/single-filter.component'; -import { MatIconModule } from '@angular/material/icon'; -import { PreventDefaultDirective, StopPropagationDirective } from '../directives'; -import { InputWithActionComponent } from '../inputs'; const components = [QuickFiltersComponent, SingleFilterComponent]; diff --git a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts index 47fa481..bdef5b2 100644 --- a/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts +++ b/src/lib/filtering/simple-popup-filter/simple-popup-filter.component.ts @@ -1,11 +1,11 @@ import { Component, computed, effect, input, output, signal, untracked } from '@angular/core'; +import { MatCheckbox } from '@angular/material/checkbox'; import { MatMenuModule } from '@angular/material/menu'; import { TranslateModule } from '@ngx-translate/core'; -import { MatCheckbox } from '@angular/material/checkbox'; import { ChevronButtonComponent, CircleButtonComponent, IconButtonComponent } from '../../buttons'; import { StopPropagationDirective } from '../../directives'; -import { InputWithActionComponent } from '../../inputs'; +import { InputWithActionComponent } from '../../inputs/input-with-action/input-with-action.component'; import { SimpleFilterOption } from '../models/simple-filter-option'; @Component({ diff --git a/src/lib/inputs/details-radio/details-radio.component.html b/src/lib/inputs/details-radio/details-radio.component.html index 4a61fbd..f374875 100644 --- a/src/lib/inputs/details-radio/details-radio.component.html +++ b/src/lib/inputs/details-radio/details-radio.component.html @@ -1,13 +1,13 @@ -
- @for (option of options; track option) { +
+ @for (option of options(); track option) {
@if (option.icon) { @@ -15,7 +15,9 @@
+ {{ option.description | translate: option.descriptionParams | replaceNbsp }} + @if (option.extraOption && !option.extraOption.hidden && isSelected(option)) {
{{ option.extraOption.label | translate }} + @if (option.extraOption.description) { }
+ @if (isSelected(option)) { } @@ -43,8 +47,10 @@ } @else {
+
+ {{ option.description | translate }} }
diff --git a/src/lib/inputs/details-radio/details-radio.component.ts b/src/lib/inputs/details-radio/details-radio.component.ts index 8d0f087..371f2c4 100644 --- a/src/lib/inputs/details-radio/details-radio.component.ts +++ b/src/lib/inputs/details-radio/details-radio.component.ts @@ -1,5 +1,5 @@ import { NgClass } from '@angular/common'; -import { booleanAttribute, Component, EventEmitter, Input, Output } from '@angular/core'; +import { booleanAttribute, Component, input, output } from '@angular/core'; import { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; @@ -40,12 +40,12 @@ import { DetailsRadioOption } from './details-radio-option'; ], }) export class DetailsRadioComponent extends FormFieldComponent> { - @Input({ required: true }) options: DetailsRadioOption[] = []; - @Input({ transform: booleanAttribute }) displayInRow = false; + readonly options = input.required[]>(); + readonly displayInRow = input(false, { transform: booleanAttribute }); - @Output() readonly extraOptionChanged: EventEmitter> = new EventEmitter(); + readonly extraOptionChanged = output>(); - toggleOption(option: DetailsRadioOption): void { + toggleOption(option: DetailsRadioOption) { if (option.value !== this._value?.value && !option.disabled) { this.markAsTouched(); const currentlyChecked = this.value?.value === option.value; @@ -54,15 +54,20 @@ export class DetailsRadioComponent extends FormFieldComponent): string { + groupId(option: DetailsRadioOption) { return (option.id ?? option.label.replace('.', '-')) + '-checkbox-details-input'; } - isSelected(option: DetailsRadioOption): boolean { + isSelected(option: DetailsRadioOption) { return option.value === this.value?.value; } - emitExtraOption(): void { + emitExtraOption() { + if (!this.value) { + console.error('Extra option selected but the value is undefined'); + return; + } + this.extraOptionChanged.emit(this.value); } } diff --git a/src/lib/inputs/dynamic-input/dynamic-input.component.html b/src/lib/inputs/dynamic-input/dynamic-input.component.html index f655490..e70597e 100644 --- a/src/lib/inputs/dynamic-input/dynamic-input.component.html +++ b/src/lib/inputs/dynamic-input/dynamic-input.component.html @@ -1,37 +1,46 @@ -
- @if (label) { - +
+ @if (label()) { + } - @if (isDate) { + @if (isDate()) { + + } - @if (isText) { + @if (isText()) { } - @if (isNumber) { - + @if (isNumber()) { + }
diff --git a/src/lib/inputs/dynamic-input/dynamic-input.component.ts b/src/lib/inputs/dynamic-input/dynamic-input.component.ts index 8a8a645..acd0ce8 100644 --- a/src/lib/inputs/dynamic-input/dynamic-input.component.ts +++ b/src/lib/inputs/dynamic-input/dynamic-input.component.ts @@ -1,13 +1,13 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; -import { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { FormFieldComponent } from '../form-field/form-field-component.directive'; import { NgClass } from '@angular/common'; +import { ChangeDetectionStrategy, Component, computed, input, model, output } from '@angular/core'; +import { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { MatDatepickerModule } from '@angular/material/datepicker'; -import { StopPropagationDirective } from '../../directives'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; +import { StopPropagationDirective } from '../../directives'; +import { FormFieldComponent } from '../form-field/form-field-component.directive'; -const InputTypes = { +export const InputTypes = { DATE: 'DATE', NUMBER: 'NUMBER', TEXT: 'TEXT', @@ -39,28 +39,20 @@ type DynamicInput = number | string | Date; imports: [NgClass, FormsModule, MatDatepickerModule, StopPropagationDirective, MatIconModule, MatInputModule], }) export class DynamicInputComponent extends FormFieldComponent { - @Input() label?: string; - @Input({ required: true }) type!: InputType; - @Input() placeholder?: string; - @Input() id?: string; - @Input() classList = ''; - @Input() input!: DynamicInput; - @Output() readonly closedDatepicker = new EventEmitter(); + readonly label = input(); + readonly type = input.required(); + readonly placeholder = input(); + readonly id = input(); + readonly classList = input(''); + readonly input = model(); + readonly closedDatepicker = output(); - get isDate() { - return this.type === InputTypes.DATE; - } - - get isNumber() { - return this.type === InputTypes.NUMBER; - } - - get isText() { - return this.type === InputTypes.TEXT; - } + readonly isDate = computed(() => this.type() === InputTypes.DATE); + readonly isNumber = computed(() => this.type() === InputTypes.NUMBER); + readonly isText = computed(() => this.type() === InputTypes.TEXT); writeValue(input: DynamicInput): void { - this.input = input; + this.input.set(input); } onCloseDatepicker() { diff --git a/src/lib/inputs/editable-input/editable-input.component.html b/src/lib/inputs/editable-input/editable-input.component.html index 581d1d3..0b35ec8 100644 --- a/src/lib/inputs/editable-input/editable-input.component.html +++ b/src/lib/inputs/editable-input/editable-input.component.html @@ -1,48 +1,53 @@ -@if (!editing) { - @if (showPreview) { -
- {{ value }} -
+@if (!_editing()) { + @if (showPreview()) { +
{{ value() }}
} +
- @if (canEdit) { + @if (canEdit()) { } +
-} - -@if (editing) { +} @else { -
- @if (!parentId) { - +
+ @if (!parentId()) { + } @else { }
+
- + +
diff --git a/src/lib/inputs/editable-input/editable-input.component.ts b/src/lib/inputs/editable-input/editable-input.component.ts index 942e2d2..ce4e4da 100644 --- a/src/lib/inputs/editable-input/editable-input.component.ts +++ b/src/lib/inputs/editable-input/editable-input.component.ts @@ -1,43 +1,41 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { booleanAttribute, ChangeDetectionStrategy, Component, input, OnChanges, output, signal, SimpleChanges } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { CircleButtonComponent, CircleButtonType, CircleButtonTypes } from '../../buttons'; +import { CircleButtonComponent } from '../../buttons/circle-button/circle-button.component'; +import { CircleButtonType, CircleButtonTypes } from '../../buttons/types/circle-button.type'; @Component({ - selector: 'iqser-editable-input [value]', + selector: 'iqser-editable-input', templateUrl: './editable-input.component.html', styleUrls: ['./editable-input.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CircleButtonComponent, FormsModule], + host: { + '[class.editing]': '_editing()', + }, }) export class EditableInputComponent implements OnChanges { - @Input() id?: string; - @Input() parentId?: string; - @Input() value!: string; - @Input() editTooltip?: string; - @Input() saveTooltip?: string; - @Input() cancelTooltip?: string; - @Input() placeholder = ''; - @Input() class?: string; - @Input() showPreview = true; - @Input() canEdit = true; - @Input() buttonsType: CircleButtonType = CircleButtonTypes.default; - @Input() helpModeKey: string = ''; - @Input() lastChild = false; - @Output() readonly save = new EventEmitter(); + protected readonly _editing = signal(false); + readonly id = input(); + readonly parentId = input(); + readonly value = input.required(); + readonly editTooltip = input(''); + readonly saveTooltip = input(''); + readonly cancelTooltip = input(''); + readonly placeholder = input(''); + readonly class = input(); + readonly showPreview = input(true, { transform: booleanAttribute }); + readonly canEdit = input(true, { transform: booleanAttribute }); + readonly buttonsType = input(CircleButtonTypes.default); + readonly helpModeKey = input(''); + readonly lastChild = input(false, { transform: booleanAttribute }); + readonly save = output(); textArea?: { width: number; height: number }; newValue = ''; - private _editing = false; - - @HostBinding('class.editing') - get editing(): boolean { - return this._editing; - } - set editing(value: boolean) { - this._editing = value; - this.newValue = this.value; + this._editing.set(value); + this.newValue = this.value(); } ngOnChanges(changes: SimpleChanges): void { @@ -51,12 +49,12 @@ export class EditableInputComponent implements OnChanges { setTextAreaSize() { setTimeout(() => { - const element = document.getElementById(this.id as string) as HTMLElement; - const parentElement = document.getElementById(this.parentId as string) as HTMLElement; + const element = document.getElementById(this.id() as string) as HTMLElement; + const parentElement = document.getElementById(this.parentId() as string) as HTMLElement; const width = parentElement.offsetWidth - 98; - let height = (this.lastChild ? parentElement.offsetHeight : element.offsetHeight) - 16; - if (this.lastChild) { - const lastChildIndex = Number(this.id?.split('-')[2]); + let height = (this.lastChild() ? parentElement.offsetHeight : element.offsetHeight) - 16; + if (this.lastChild()) { + const lastChildIndex = Number(this.id()?.split('-')[2]); height = height - lastChildIndex * element.offsetHeight; } this.textArea = { width, height }; diff --git a/src/lib/inputs/index.ts b/src/lib/inputs/index.ts deleted file mode 100644 index 27194c4..0000000 --- a/src/lib/inputs/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './round-checkbox/round-checkbox.component'; -export * from './editable-input/editable-input.component'; -export * from './input-with-action/input-with-action.component'; -export * from './details-radio/details-radio.component'; -export * from './details-radio/details-radio-option'; -export * from './form-field/form-field-component.directive'; -export * from './dynamic-input/dynamic-input.component'; diff --git a/src/lib/inputs/input-with-action/input-with-action.component.html b/src/lib/inputs/input-with-action/input-with-action.component.html index f574a68..d1ea96a 100644 --- a/src/lib/inputs/input-with-action/input-with-action.component.html +++ b/src/lib/inputs/input-with-action/input-with-action.component.html @@ -1,34 +1,33 @@ -
+ - @if (hint) { - {{ hint }} + @if (hint()) { + {{ hint() }} } - @if (isSearch && !hasContent) { + @if (_isSearch() && !_hasContent()) { } - @if (isSearch && hasContent) { - + @if (_isSearch() && _hasContent()) { + } - @if (!isSearch) { + @if (icon(); as icon) { }
diff --git a/src/lib/inputs/input-with-action/input-with-action.component.ts b/src/lib/inputs/input-with-action/input-with-action.component.ts index 5e8d67f..9753394 100644 --- a/src/lib/inputs/input-with-action/input-with-action.component.ts +++ b/src/lib/inputs/input-with-action/input-with-action.component.ts @@ -1,10 +1,10 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; -import { randomString } from '../../utils'; +import { booleanAttribute, ChangeDetectionStrategy, Component, computed, input, model, output } from '@angular/core'; import { FormsModule } from '@angular/forms'; - -import { CircleButtonComponent } from '../../buttons'; import { MatIconModule } from '@angular/material/icon'; +import { CircleButtonComponent } from '../../buttons/circle-button/circle-button.component'; +import { randomString } from '../../utils/functions'; + @Component({ selector: 'iqser-input-with-action', templateUrl: './input-with-action.component.html', @@ -14,41 +14,27 @@ import { MatIconModule } from '@angular/material/icon'; imports: [FormsModule, MatIconModule, CircleButtonComponent], }) export class InputWithActionComponent { - @Input() inputId = `${randomString() + '-search-input'}`; - @Input() actionButtonId = `${randomString() + '-action-input'}`; - @Input() placeholder = ''; - @Input() hint?: string; - @Input() width: number | 'full' = 250; - @Input() icon?: string; - @Input() autocomplete: 'on' | 'off' = 'on'; - @Input() value = ''; - @Input() disabled = false; - @Output() readonly action = new EventEmitter(); - @Output() readonly valueChange = new EventEmitter(); + readonly inputId = input(`${randomString() + '-search-input'}`); + readonly actionButtonId = input(`${randomString() + '-action-input'}`); + readonly placeholder = input(''); + readonly hint = input(); + readonly width = input(250); + protected readonly _computedWidth = computed(() => (this.width() === 'full' ? '100%' : `${this.width()}px`)); + readonly icon = input(); + protected readonly _isSearch = computed(() => !this.icon()); + readonly autocomplete = input<'on' | 'off'>('on'); + readonly value = model(''); + protected readonly _hasContent = computed(() => !!this.value()?.length); + readonly disabled = input(false, { transform: booleanAttribute }); + readonly action = output(); - get hasContent(): boolean { - return !!this.value?.length; + reset() { + this.value.set(''); } - get computedWidth(): string { - return this.width === 'full' ? '100%' : `${this.width}px`; - } - - get isSearch(): boolean { - return !this.icon; - } - - constructor(private readonly _changeDetectorRef: ChangeDetectorRef) {} - - reset(): void { - this.value = ''; - this.valueChange.emit(this.value); - this._changeDetectorRef.markForCheck(); - } - - executeAction(): void { - if (this.hasContent) { - this.action.emit(this.value); + executeAction() { + if (this._hasContent()) { + this.action.emit(this.value()); } } } diff --git a/src/lib/inputs/round-checkbox/round-checkbox.component.html b/src/lib/inputs/round-checkbox/round-checkbox.component.html index 1d9ff37..189049e 100644 --- a/src/lib/inputs/round-checkbox/round-checkbox.component.html +++ b/src/lib/inputs/round-checkbox/round-checkbox.component.html @@ -1,15 +1,16 @@
- @if (active && !indeterminate) { + @if (active() && !indeterminate()) { } - @if (indeterminate) { + + @if (indeterminate()) { }
diff --git a/src/lib/inputs/round-checkbox/round-checkbox.component.ts b/src/lib/inputs/round-checkbox/round-checkbox.component.ts index c54dab1..270ca90 100644 --- a/src/lib/inputs/round-checkbox/round-checkbox.component.ts +++ b/src/lib/inputs/round-checkbox/round-checkbox.component.ts @@ -1,4 +1,4 @@ -import { booleanAttribute, ChangeDetectionStrategy, Component, ElementRef, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; +import { booleanAttribute, ChangeDetectionStrategy, Component, effect, ElementRef, input, numberAttribute, viewChild } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; @Component({ @@ -9,18 +9,17 @@ import { MatIconModule } from '@angular/material/icon'; standalone: true, imports: [MatIconModule], }) -export class RoundCheckboxComponent implements OnInit { - @Input() size = 20; - @Input({ transform: booleanAttribute }) active = false; - @Input() indeterminate = false; - @Input() type: 'default' | 'with-bg' = 'default'; - @HostBinding('class.disabled') - @Input() - disabled = false; +export class RoundCheckboxComponent { + protected readonly _wrapper = viewChild.required('wrapper', { read: ElementRef }); + readonly size = input(20, { transform: numberAttribute }); + readonly active = input(false, { transform: booleanAttribute }); + readonly indeterminate = input(false, { transform: booleanAttribute }); + readonly type = input<'default' | 'with-bg'>('default'); + readonly disabled = input(false, { transform: booleanAttribute }); - @ViewChild('wrapper', { static: true }) private readonly _wrapper!: ElementRef; - - ngOnInit(): void { - (this._wrapper.nativeElement as HTMLElement).style.setProperty('--size', `${this.size}px`); + constructor() { + effect(() => { + (this._wrapper().nativeElement as HTMLElement).style.setProperty('--size', `${this.size()}px`); + }); } } diff --git a/src/lib/listing/listing.module.ts b/src/lib/listing/listing.module.ts index e844f29..cf3608e 100644 --- a/src/lib/listing/listing.module.ts +++ b/src/lib/listing/listing.module.ts @@ -10,7 +10,8 @@ import { CircleButtonComponent, IconButtonComponent } from '../buttons'; import { HasScrollbarDirective, SyncWidthDirective } from '../directives'; import { EmptyStateComponent } from '../empty-state'; import { IqserFiltersModule, PopupFilterComponent } from '../filtering'; -import { InputWithActionComponent, RoundCheckboxComponent } from '../inputs'; +import { InputWithActionComponent } from '../inputs/input-with-action/input-with-action.component'; +import { RoundCheckboxComponent } from '../inputs/round-checkbox/round-checkbox.component'; import { SnakeCasePipe } from '../pipes/snake-case.pipe'; import { PageHeaderComponent } from './page-header/page-header.component'; import { ScrollButtonComponent } from './scroll-button/scroll-button.component'; diff --git a/src/lib/listing/table-header/table-header.component.ts b/src/lib/listing/table-header/table-header.component.ts index 74cc476..46b4b19 100644 --- a/src/lib/listing/table-header/table-header.component.ts +++ b/src/lib/listing/table-header/table-header.component.ts @@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, Component, inject, Input, TemplateRef } from ' import { TranslateModule } from '@ngx-translate/core'; import { SyncWidthDirective } from '../../directives'; import { FilterService, IqserFiltersModule } from '../../filtering'; -import { RoundCheckboxComponent } from '../../inputs'; +import { RoundCheckboxComponent } from '../../inputs/round-checkbox/round-checkbox.component'; import { Id, IListable, ListingMode, ListingModes, TableColumnConfig } from '../models'; import { EntitiesService, ListingService } from '../services'; import { TableColumnNameComponent } from '../table-column-name/table-column-name.component'; 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 3de5eec..43ce9dc 100644 --- a/src/lib/listing/workflow/column-header/column-header.component.ts +++ b/src/lib/listing/workflow/column-header/column-header.component.ts @@ -14,11 +14,13 @@ import { import { TranslateModule } from '@ngx-translate/core'; import { combineLatest } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; -import { CircleButtonComponent, CircleButtonTypes } from '../../../buttons'; -import { RoundCheckboxComponent } from '../../../inputs'; -import { ContextComponent, Debounce } from '../../../utils'; -import { IListable } from '../../models'; -import { ListingService } from '../../services'; +import { CircleButtonComponent } from '../../../buttons/circle-button/circle-button.component'; +import { CircleButtonTypes } from '../../../buttons/types/circle-button.type'; +import { RoundCheckboxComponent } from '../../../inputs/round-checkbox/round-checkbox.component'; +import { ContextComponent } from '../../../utils/context.component'; +import { Debounce } from '../../../utils/decorators/debounce.decorator'; +import { IListable } from '../../models/listable'; +import { ListingService } from '../../services/listing.service'; import { WorkflowColumn } from '../models/workflow-column.model'; interface ColumnHeaderContext { diff --git a/src/lib/utils/context.component.ts b/src/lib/utils/context.component.ts index cd89a04..9100766 100644 --- a/src/lib/utils/context.component.ts +++ b/src/lib/utils/context.component.ts @@ -2,6 +2,9 @@ import { combineLatest, Observable, of } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; import { ValuesOf } from './types/utility-types'; +/** + * @deprecated Switch to signals instead + */ export class ContextComponent { componentContext$: Observable | null = of({} as T); From 590ebcbae2a8a358a52468511c3b60e366ef7e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Thu, 20 Jun 2024 14:57:38 +0300 Subject: [PATCH 128/201] Moved fullscreen icons to common-ui --- src/assets/icons/exit-fullscreen.svg | 9 +++++++++ src/assets/icons/fullscreen.svg | 9 +++++++++ src/lib/utils/constants.ts | 2 ++ 3 files changed, 20 insertions(+) create mode 100644 src/assets/icons/exit-fullscreen.svg create mode 100644 src/assets/icons/fullscreen.svg diff --git a/src/assets/icons/exit-fullscreen.svg b/src/assets/icons/exit-fullscreen.svg new file mode 100644 index 0000000..48b88b1 --- /dev/null +++ b/src/assets/icons/exit-fullscreen.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/assets/icons/fullscreen.svg b/src/assets/icons/fullscreen.svg new file mode 100644 index 0000000..ea10452 --- /dev/null +++ b/src/assets/icons/fullscreen.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/lib/utils/constants.ts b/src/lib/utils/constants.ts index b1e2bc5..b56febf 100644 --- a/src/lib/utils/constants.ts +++ b/src/lib/utils/constants.ts @@ -13,9 +13,11 @@ export const ICONS = new Set([ 'document', 'download', 'edit', + 'exit-fullscreen', 'expand', 'failure', 'filter-list', + 'fullscreen', 'help-outline', 'lanes', 'list', From f24e5dbb82069a0d4ccc6a9823ef712c3138b28b Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Tue, 25 Jun 2024 00:21:16 +0300 Subject: [PATCH 129/201] RED-8748 - added hidden property to filter model --- .../filter-card/filter-card.component.html | 28 ++++++++++--------- src/lib/filtering/filter.service.ts | 5 ++++ src/lib/filtering/models/filter.model.ts | 1 + src/lib/filtering/models/filter.ts | 2 ++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/lib/filtering/filter-card/filter-card.component.html b/src/lib/filtering/filter-card/filter-card.component.html index 127487e..7655694 100644 --- a/src/lib/filtering/filter-card/filter-card.component.html +++ b/src/lib/filtering/filter-card/filter-card.component.html @@ -110,19 +110,21 @@ @if (filter.children?.length && filter.expanded) {
@for (child of filter.children; track child) { -
- - - - -
+ @if (!child.hidden) { +
+ + + + +
+ } }
} diff --git a/src/lib/filtering/filter.service.ts b/src/lib/filtering/filter.service.ts index 7215aac..079cff4 100644 --- a/src/lib/filtering/filter.service.ts +++ b/src/lib/filtering/filter.service.ts @@ -41,6 +41,11 @@ export class FilterService { this.showResetFilters$ = this._showResetFilters$; } + get noAnnotationsFilterChecked() { + const filterGroup = this.filterGroups.find(g => g.slug === 'primaryFilters'); + return !!filterGroup?.filters[0]?.children.find(f => f.id === 'no-annotations-filter' && f.checked); + } + get filterGroups(): IFilterGroup[] { return Object.values(this.#filterGroups$.getValue()); } diff --git a/src/lib/filtering/models/filter.model.ts b/src/lib/filtering/models/filter.model.ts index b5fd02b..ac065b8 100644 --- a/src/lib/filtering/models/filter.model.ts +++ b/src/lib/filtering/models/filter.model.ts @@ -10,5 +10,6 @@ export interface IFilter { readonly required?: boolean; readonly disabled?: boolean; readonly helpModeKey?: string; + readonly hidden?: boolean; readonly metadata?: Record; } diff --git a/src/lib/filtering/models/filter.ts b/src/lib/filtering/models/filter.ts index 83f7775..9b591f4 100644 --- a/src/lib/filtering/models/filter.ts +++ b/src/lib/filtering/models/filter.ts @@ -10,6 +10,7 @@ export class Filter implements IFilter, IListable { readonly checker?: (obj?: unknown) => boolean; readonly skipTranslation?: boolean; readonly metadata?: Record; + readonly hidden?: boolean; checked: boolean; matches?: number; @@ -25,6 +26,7 @@ export class Filter implements IFilter, IListable { this.required = !!filter.required; this.skipTranslation = !!filter.skipTranslation; this.metadata = filter.metadata; + this.hidden = !!filter.hidden; } get searchKey(): string { From ca0db25484128cc4df2a68e190a612c0c09cd10d Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 27 Jun 2024 13:23:35 +0300 Subject: [PATCH 130/201] RED-9390 - Workload filter does not remember previous ALL or NONE selection --- .../filter-card/filter-card.component.html | 2 +- .../filter-card/filter-card.component.ts | 6 ++++-- src/lib/filtering/filter.service.ts | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/lib/filtering/filter-card/filter-card.component.html b/src/lib/filtering/filter-card/filter-card.component.html index 7655694..ae6e2ed 100644 --- a/src/lib/filtering/filter-card/filter-card.component.html +++ b/src/lib/filtering/filter-card/filter-card.component.html @@ -62,7 +62,7 @@ >
}
>(); @@ -241,7 +244,7 @@ export class FilterService { } filterCheckboxClicked(params: CheckboxClickedParams) { - const { filterGroup, nestedFilter, parent, fileId, primaryFiltersSlug } = params; + const { filterGroup, nestedFilter, parent, primaryFiltersSlug } = params; if (filterGroup.singleSelect) { this.deactivateFilters({ primaryFiltersSlug, exceptedFilterId: nestedFilter.id }); @@ -264,23 +267,22 @@ export class FilterService { } this.refresh(); - this.#updateFiltersInLocalStorage(fileId); } - #updateFiltersInLocalStorage(fileId?: string) { + updateFiltersInLocalStorage(fileId?: string) { if (fileId) { - const primaryFilters = this.getGroup('primaryFilters'); - const secondaryFilters = this.getGroup('secondaryFilters'); + const primaryFilters = this.getGroup(PRIMARY_FILTERS); + const secondaryFilters = this.getGroup(SECONDARY_FILTERS); const filters: LocalStorageFilters = { primaryFilters: extractFilterValues(primaryFilters?.filters), secondaryFilters: extractFilterValues(secondaryFilters?.filters), }; - const workloadFiltersString = localStorage.getItem('workload-filters') ?? '{}'; + const workloadFiltersString = localStorage.getItem(WORKLOAD_FILTERS_KEY) ?? '{}'; const workloadFilters = JSON.parse(workloadFiltersString); workloadFilters[fileId] = filters; - localStorage.setItem('workload-filters', JSON.stringify(workloadFilters)); + localStorage.setItem(WORKLOAD_FILTERS_KEY, JSON.stringify(workloadFilters)); } } } From 1c48cea02e97efb5d40f69e844484c6611985f5e Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 27 Jun 2024 14:53:44 +0300 Subject: [PATCH 131/201] RED-9453 - prevent enter key --- .../icon-button/icon-button.component.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib/buttons/icon-button/icon-button.component.ts b/src/lib/buttons/icon-button/icon-button.component.ts index 940eda0..6047175 100644 --- a/src/lib/buttons/icon-button/icon-button.component.ts +++ b/src/lib/buttons/icon-button/icon-button.component.ts @@ -1,5 +1,15 @@ import { NgClass } from '@angular/common'; -import { booleanAttribute, ChangeDetectionStrategy, Component, computed, EventEmitter, inject, input, Output } from '@angular/core'; +import { + booleanAttribute, + ChangeDetectionStrategy, + Component, + computed, + EventEmitter, + HostListener, + inject, + input, + Output, +} from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { RouterLink } from '@angular/router'; @@ -33,4 +43,10 @@ export class IconButtonComponent { }; }); @Output() readonly action = new EventEmitter(); + + @HostListener('window:keydown.Enter', ['$event']) + preventEnterKey($event: KeyboardEvent) { + $event?.preventDefault(); + $event?.stopPropagation(); + } } From ce334dbdebffef79a92d0a5ab9734710d527b8fe Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 27 Jun 2024 15:32:15 +0300 Subject: [PATCH 132/201] RED-9362 - Help Mode tooltip missing --- .../help-mode/help-button/help-button.component.html | 1 + .../help-mode/help-button/help-button.component.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib/help-mode/help-button/help-button.component.html b/src/lib/help-mode/help-button/help-button.component.html index 9f53692..1e611bb 100644 --- a/src/lib/help-mode/help-button/help-button.component.html +++ b/src/lib/help-mode/help-button/help-button.component.html @@ -3,6 +3,7 @@ class="help-mode-slide-toggle" [class.dialog-toggle]="dialogButton" [class.active]="helpModeService.isHelpModeActive" + [matTooltip]="buttonTooltip" >
diff --git a/src/lib/help-mode/help-button/help-button.component.ts b/src/lib/help-mode/help-button/help-button.component.ts index a331ddf..437669b 100644 --- a/src/lib/help-mode/help-button/help-button.component.ts +++ b/src/lib/help-mode/help-button/help-button.component.ts @@ -2,13 +2,16 @@ import { AfterViewInit, Component, ElementRef, HostListener, Input, OnDestroy, OnInit } from '@angular/core'; import { MatIcon } from '@angular/material/icon'; import { HelpModeService } from '../help-mode.service'; +import { MatTooltip } from '@angular/material/tooltip'; +import { TranslateService } from '@ngx-translate/core'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; @Component({ selector: 'iqser-help-button', templateUrl: './help-button.component.html', styleUrls: ['./help-button.component.scss'], standalone: true, - imports: [MatIcon], + imports: [MatIcon, MatTooltip], }) export class HelpButtonComponent implements OnInit, AfterViewInit, OnDestroy { @Input() dialogButton = true; @@ -16,6 +19,7 @@ export class HelpButtonComponent implements OnInit, AfterViewInit, OnDestroy { constructor( private readonly _elementRef: ElementRef, + private readonly _translateService: TranslateService, readonly helpModeService: HelpModeService, ) {} @@ -68,4 +72,9 @@ export class HelpButtonComponent implements OnInit, AfterViewInit, OnDestroy { this.helpModeButton?.style.setProperty('top', `${currentComponentRect.top}px`); this.helpModeButton?.style.setProperty('left', `${currentComponentRect.left}px`); } + + get buttonTooltip() { + const translation = this.helpModeService.isHelpModeActive ? _('help-button.disable') : _('help-button.enable'); + return this._translateService.instant(translation); + } } From 01c244aa0760de32950630c270fd6a3c3b344784 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 4 Jul 2024 12:52:26 +0300 Subject: [PATCH 133/201] RED-9513 - Enter Key Not Working: Unable to Add Terms to Dictionary --- .../icon-button/icon-button.component.html | 2 +- .../icon-button/icon-button.component.ts | 21 ++++++------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/lib/buttons/icon-button/icon-button.component.html b/src/lib/buttons/icon-button/icon-button.component.html index 703af59..d086836 100644 --- a/src/lib/buttons/icon-button/icon-button.component.html +++ b/src/lib/buttons/icon-button/icon-button.component.html @@ -1,5 +1,5 @@