unfuck circular imports

This commit is contained in:
Dan Percic 2024-06-19 13:05:12 +03:00
parent 04ae68891c
commit d595a22db1
57 changed files with 471 additions and 303 deletions

View File

@ -1,4 +1,4 @@
import { List } from '../utils';
import { List } from '../utils/types/iqser-types';
export interface DynamicCache {
readonly urls: List;

View File

@ -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<string, string>;
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<string, string>;
protected constructor(
protected readonly _dialogRef: MatDialogRef<BaseDialogComponent>,

View File

@ -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',

View File

@ -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',

View File

@ -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<T extends string> {
return ref;
}
open(
type: Type<unknown>,
data?: unknown,
config?: object,
cb?: (...params: unknown[]) => Promise<unknown> | void,
finallyCb?: (...params: unknown[]) => void | Promise<unknown>,
): MatDialogRef<unknown> {
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;
}
}

View File

@ -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');

View File

@ -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',

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,10 @@
export interface LocalStorageFilter {
id: string;
checked: boolean;
children?: LocalStorageFilter[] | null;
}
export interface LocalStorageFilters {
primaryFilters: LocalStorageFilter[] | null;
secondaryFilters: LocalStorageFilter[] | null;
}

View File

@ -1,4 +1,4 @@
import { IListable } from '../../listing';
import { IListable } from '../../listing/models/listable';
import { Filter } from './filter';
import { INestedFilter } from './nested-filter.model';

View File

@ -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]',

View File

@ -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',

View File

@ -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<string, Helper> = {};
#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;

View File

@ -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<HelpModeKey>('Help mode keys');
export const MANUAL_BASE_URL = new InjectionToken<string>('Base manual URL', {

View File

@ -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;
}

View File

@ -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];

View File

@ -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<Class extends IListable<PrimaryKey>, PrimaryKey extends Id = Class['id']>

View File

@ -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,

View File

@ -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<T extends IListable> {
readonly searchPositions = SearchPositions;
@ -30,15 +49,11 @@ export class PageHeaderComponent<T extends IListable> {
@Input() helpModeKey?: 'dossier' | 'document';
@Input() searchPosition: SearchPosition = SearchPositions.afterFilters;
@Output() readonly closeAction = new EventEmitter();
readonly filterService = inject(FilterService, { optional: true });
readonly searchService = inject<SearchService<T>>(SearchService<T>, { 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<T>,
) {}
get filterHelpModeKey() {
return this.helpModeKey ? (this.helpModeKey === 'dossier' ? 'filter_dossier_list' : 'filter_documents') : '';
}
@ -48,15 +63,15 @@ export class PageHeaderComponent<T extends IListable> {
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<K extends { label?: string }>(_index: number, item: K): string | undefined {

View File

@ -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;

View File

@ -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>,
PrimaryKey extends Id = Class['id'],
> extends GenericService<Interface> {
readonly noData$: Observable<boolean>;
readonly all$: Observable<Class[]>;
readonly allLength$: Observable<number>;
protected readonly _defaultModelPath: string = '';
protected readonly _entityClass?: new (entityInterface: Interface, ...args: unknown[]) => Class;
protected readonly _entityChanged$ = new Subject<Class>();
protected readonly _entityDeleted$ = new Subject<Class>();
protected readonly _all$ = new BehaviorSubject<Class[]>([]);
readonly noData$: Observable<boolean>;
readonly all$: Observable<Class[]>;
readonly allLength$: Observable<number>;
constructor() {
super();

View File

@ -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()

View File

@ -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<Interface> {
data: Interface[];

View File

@ -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<T extends IListable<PrimaryKey>, PrimaryKey extends Id = T['id']> {
readonly sortingOrders = SortingOrders;

View File

@ -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<Class extends IListable<PrimaryKey>, 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<Class extends IListable<PrimaryKey>, PrimaryK
@Input() rowIdPrefix: string = 'item';
@Input() namePropertyKey?: string;
readonly trackBy = trackByFactory<Class>();
@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<Class>,
readonly listingService: ListingService<Class>,

View File

@ -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<T extends IListable> implements OnChanges {
@Input() entity!: T;
@Input() selectionEnabled = false;
readonly isSelected$: Observable<boolean>;
readonly #entityChanged$ = new BehaviorSubject<T>(this.entity);
@Input() selectionEnabled = false;
readonly isSelected$: Observable<boolean>;
constructor(
@Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent<T>,

View File

@ -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<T extends IListable<PrimaryKey>, PrimaryKey extends Id = T['id']> {
readonly listingModes = ListingModes;
@ -21,11 +36,10 @@ export class TableHeaderComponent<T extends IListable<PrimaryKey>, PrimaryKey ex
@Input() bulkActions?: TemplateRef<unknown>;
@Input() helpModeKey?: string;
readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters');
readonly quickFilters$ = inject(FilterService).getFilterModels$('quickFilters');
constructor(
readonly entitiesService: EntitiesService<T, T>,
readonly listingService: ListingService<T>,
readonly filterService: FilterService,
) {}
}

View File

@ -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<Class extends IListable<PrimaryKey>, PrimaryKey extends Id = Class['id']> implements OnChanges {
@ViewChild(TableContentComponent, { static: true }) private readonly _tableContent!: TableContentComponent<Class>;
readonly listingModes = ListingModes;
@Input() tableColumnConfigs!: readonly TableColumnConfig<Class>[];
@Input() bulkActions?: TemplateRef<unknown>;
@Input() headerTemplate?: TemplateRef<unknown>;
@ -51,7 +57,6 @@ export class TableComponent<Class extends IListable<PrimaryKey>, PrimaryKey exte
@Input() itemMouseEnterFn?: (entity: Class) => void;
@Input() itemMouseLeaveFn?: (entity: Class) => void;
@Output() readonly noDataAction = new EventEmitter<void>();
@ViewChild(TableContentComponent, { static: true }) private readonly _tableContent!: TableContentComponent<Class>;
constructor(
@Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent<Class>,

View File

@ -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<T extends IListable, K extends string> extends ContextComponent<ColumnHeaderContext> implements OnInit {
readonly circleButtonTypes = CircleButtonTypes;

View File

@ -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<T> {
noData: boolean;
@ -37,8 +50,27 @@ interface WorkflowContext<T> {
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<T extends IListable, K extends string> extends ContextComponent<WorkflowContext<T>> implements OnInit {
@ViewChildren(CdkDropList) private readonly _dropLists!: QueryList<CdkDropList>;
private readonly _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
this._updateItemSize(entries[0]);
});
@Input() config!: WorkflowConfig<T, K>;
@Input() itemClasses!: Record<string, (e: T) => boolean>;
@Input() addElementIcon!: string;
@ -51,9 +83,7 @@ export class WorkflowComponent<T extends IListable, K extends string> extends Co
@Input() bulkActions?: TemplateRef<unknown>;
@Output() readonly noDataAction = new EventEmitter<void>();
@Output() readonly addElement = new EventEmitter<void>();
@ContentChild('workflowItemTemplate') readonly itemTemplate!: TemplateRef<{ entity: T }>;
readonly trackBy = trackByFactory<T>();
itemHeight?: number;
itemWidth?: number;
@ -62,10 +92,6 @@ export class WorkflowComponent<T extends IListable, K extends string> extends Co
selectionColumn?: WorkflowColumn<T, K>;
readonly draggingEntities$ = new BehaviorSubject<T[]>([]);
all: { [key: string]: EntityWrapper<T> } = {};
@ViewChildren(CdkDropList) private readonly _dropLists!: QueryList<CdkDropList>;
private readonly _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
this._updateItemSize(entries[0]);
});
constructor(
@Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent<T>,

View File

@ -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',

View File

@ -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<IqserPermissions>;
readonly #permissions$ = new BehaviorSubject<IqserPermissions>({});
readonly permissions$: Observable<IqserPermissions>;
constructor() {
this.permissions$ = this.#permissions$.asObservable();

View File

@ -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<IqserRoles>;
readonly #roles$ = new BehaviorSubject<IqserRoles>({});
readonly roles$: Observable<IqserRoles>;
constructor(private readonly _permissionsService: IqserPermissionsService) {
this.roles$ = this.#roles$.asObservable();

View File

@ -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<string, PermissionValidationFn>;
export type IqserRoles = Record<string, RoleValidationFn | List>;
@ -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;

View File

@ -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<T>(value: unknown): value is T {
return typeof value === 'function';

View File

@ -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<T extends IListable<PrimaryKey>, PrimaryKey extends Id = T['id']> {
skip = false;
private readonly _query$ = new BehaviorSubject('');
skip = false;
readonly valueChanges$ = this._query$.asObservable().pipe(shareDistinctLast());
get searchValue(): string {

View File

@ -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 {

View File

@ -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({

View File

@ -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<Interface, Class extends Entity<Interface, PrimaryKey>, PrimaryKey extends Id = Class['id']> {
protected readonly _map = new Map<Id, BehaviorSubject<Class[]>>();
readonly #entityChanged$ = new Subject<Class>();
readonly #entitiesChanged$ = new BehaviorSubject<boolean>(false);
readonly #entityDeleted$ = new Subject<Class>();
protected readonly _map = new Map<Id, BehaviorSubject<Class[]>>();
get empty(): boolean {
return this._map.size === 0;

View File

@ -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;

View File

@ -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<T extends IqserAppConfig = IqserAppConfig> {

View File

@ -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<string, List>;
@ -14,9 +14,9 @@ export const KEYS = {
@Injectable()
export abstract class IqserUserPreferenceService extends GenericService<UserAttributes> {
#userAttributes: UserAttributes = {};
protected abstract readonly _devFeaturesEnabledKey: string;
protected readonly _serviceName: string = 'tenant-user-management';
#userAttributes: UserAttributes = {};
get userAttributes(): UserAttributes {
return this.#userAttributes;

View File

@ -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<E, I = E> {
readonly #http = inject(HttpClient);
readonly #map = new Map<string, BehaviorSubject<E>>();
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<string, BehaviorSubject<E>>();
getFor(ids: string[]): Observable<E[]> {
const request = this.#http.post<I[]>(`/${this._serviceName}/${encodeURI(this._defaultModelPath)}`, ids, {
headers: HeadersConfiguration.getHeaders(),

View File

@ -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<T extends IListable<PrimaryKey>, PrimaryKey extends Id = T['id']> {

View File

@ -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';

View File

@ -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();

View File

@ -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,
};
}

View File

@ -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 {

View File

@ -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();

View File

@ -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({

View File

@ -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 {

View File

@ -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<string, RouteStorageObject>();
readonly attached$ = new Subject<ActivatedRouteSnapshot>();
readonly detached$ = new Subject<ActivatedRouteSnapshot>();
readonly #storedRoutes = new Map<string, RouteStorageObject>();
private static _removeTooltips(): void {
while (document.getElementsByTagName('mat-tooltip-component').length > 0) {

View File

@ -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) {

View File

@ -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 {

View File

@ -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<