Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f13fa62d3 |
101
src/lib/common-ui.module.ts
Normal file
101
src/lib/common-ui.module.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { inject, ModuleWithProviders, NgModule, Optional, Provider, SkipSelf } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CommonUiOptions, IqserAppConfig, ModuleOptions } from './utils';
|
||||||
|
import { ConnectionStatusComponent, FullPageErrorComponent } from './error';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
import { ApiPathInterceptor, DefaultUserPreferenceService, IqserConfigService, IqserUserPreferenceService } from './services';
|
||||||
|
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { CircleButtonComponent, IconButtonComponent } from './buttons';
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
import { ICONS } from './utils/constants';
|
||||||
|
import { StopPropagationDirective } from './directives';
|
||||||
|
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||||
|
|
||||||
|
const matModules = [MatIconModule, MatButtonModule, MatDialogModule, MatCheckboxModule, MatTooltipModule, MatProgressBarModule];
|
||||||
|
const components = [ConnectionStatusComponent, FullPageErrorComponent];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [...components],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
...matModules,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslateModule,
|
||||||
|
IconButtonComponent,
|
||||||
|
CircleButtonComponent,
|
||||||
|
StopPropagationDirective,
|
||||||
|
],
|
||||||
|
exports: [...components],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
multi: true,
|
||||||
|
useClass: ApiPathInterceptor,
|
||||||
|
},
|
||||||
|
{ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CommonUiModule {
|
||||||
|
constructor(@Optional() @SkipSelf() parentModule?: CommonUiModule) {
|
||||||
|
if (parentModule) {
|
||||||
|
throw new Error('CommonUiModule is already loaded. Import it in the AppModule only!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconRegistry = inject(MatIconRegistry);
|
||||||
|
const sanitizer = inject(DomSanitizer);
|
||||||
|
|
||||||
|
ICONS.forEach(icon => {
|
||||||
|
const url = sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/${icon}.svg`);
|
||||||
|
iconRegistry.addSvgIconInNamespace('iqser', icon, url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static forRoot<
|
||||||
|
UserPreference extends IqserUserPreferenceService,
|
||||||
|
Config extends IqserConfigService<AppConfig>,
|
||||||
|
AppConfig extends IqserAppConfig = IqserAppConfig,
|
||||||
|
>(options: CommonUiOptions<UserPreference, Config, AppConfig>): ModuleWithProviders<CommonUiModule> {
|
||||||
|
const userPreferenceService = ModuleOptions.getService(
|
||||||
|
IqserUserPreferenceService,
|
||||||
|
DefaultUserPreferenceService,
|
||||||
|
options.existingUserPreferenceService,
|
||||||
|
);
|
||||||
|
|
||||||
|
const configServiceProviders = this._getConfigServiceProviders(options.configServiceFactory, options.configService);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ngModule: CommonUiModule,
|
||||||
|
providers: [userPreferenceService, ...configServiceProviders],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _getConfigServiceProviders(configServiceFactory: () => unknown, configService?: unknown): Provider[] {
|
||||||
|
if (configService) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: configService,
|
||||||
|
useFactory: configServiceFactory,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: IqserConfigService,
|
||||||
|
useExisting: configService,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: IqserConfigService,
|
||||||
|
useFactory: configServiceFactory,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
import { NgClass } from '@angular/common';
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
import { Component, inject } from '@angular/core';
|
|
||||||
import { toSignal } from '@angular/core/rxjs-interop';
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
import { connectionStatusTranslations } from '../../translations';
|
import { connectionStatusTranslations } from '../../translations';
|
||||||
import { ErrorService } from '../error.service';
|
import { ErrorService } from '../error.service';
|
||||||
@ -17,7 +16,8 @@ import { ErrorService } from '../error.service';
|
|||||||
transition('* => online', animate('3s ease-in')),
|
transition('* => online', animate('3s ease-in')),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
imports: [NgClass],
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class ConnectionStatusComponent {
|
export class ConnectionStatusComponent {
|
||||||
protected readonly connectionStatusTranslations = connectionStatusTranslations;
|
protected readonly connectionStatusTranslations = connectionStatusTranslations;
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
||||||
import { NavigationStart, Router } from '@angular/router';
|
|
||||||
import { fromEvent, merge, Observable, Subject } from 'rxjs';
|
import { fromEvent, merge, Observable, Subject } from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, map, tap } from 'rxjs/operators';
|
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||||
import { LoadingService } from '../loading';
|
import { LoadingService } from '../loading';
|
||||||
|
import { distinctUntilChanged, filter, map, tap } from 'rxjs/operators';
|
||||||
|
import { NavigationStart, Router } from '@angular/router';
|
||||||
import { shareLast } from '../utils';
|
import { shareLast } from '../utils';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
export class CustomError {
|
export class CustomError {
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
@ -39,17 +39,17 @@ const isSameEventType = (previous: Event | string | undefined, current: Event |
|
|||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ErrorService {
|
export class ErrorService {
|
||||||
readonly #error$ = new Subject<ErrorType | undefined>();
|
readonly offline$: Observable<Event>;
|
||||||
|
readonly online$: Observable<Event>;
|
||||||
|
readonly connectionStatus$: Observable<string | undefined>;
|
||||||
|
readonly #error$ = new Subject<ErrorType>();
|
||||||
|
readonly error$ = this.#error$.pipe(filter(error => !error || !isOffline(error)));
|
||||||
readonly #online$ = new Subject();
|
readonly #online$ = new Subject();
|
||||||
readonly #loadingService = inject(LoadingService);
|
readonly #loadingService = inject(LoadingService);
|
||||||
readonly #router = inject(Router);
|
readonly #router = inject(Router);
|
||||||
readonly #displayNotification$ = new Subject<string | undefined>();
|
readonly #displayNotification$ = new Subject<string | undefined>();
|
||||||
#notificationTimeout: Record<string, NodeJS.Timeout | undefined> = {};
|
#notificationTimeout: Record<string, NodeJS.Timeout | undefined> = {};
|
||||||
#displayedNotificationType: string | undefined;
|
#displayedNotificationType: string | undefined;
|
||||||
readonly offline$: Observable<Event>;
|
|
||||||
readonly online$: Observable<Event>;
|
|
||||||
readonly connectionStatus$: Observable<string | undefined>;
|
|
||||||
readonly error$ = this.#error$.pipe(filter(error => !error || !isOffline(error)));
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.#router.events
|
this.#router.events
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
@if (normalizedError(); as normalizedError) {
|
@if (errorService.error$ | async; as error) {
|
||||||
<section class="full-page-section"></section>
|
<section class="full-page-section"></section>
|
||||||
<section class="full-page-content flex-align-items-center">
|
<section class="full-page-content flex-align-items-center">
|
||||||
<mat-icon svgIcon="iqser:failure"></mat-icon>
|
<mat-icon svgIcon="iqser:failure"></mat-icon>
|
||||||
|
<div [translate]="errorTitle(error)" class="heading-l mt-24"></div>
|
||||||
<div [translate]="normalizedError.title" class="heading-l mt-24"></div>
|
@if (error.message) {
|
||||||
|
<div class="mt-16 error">{{ error.message }}</div>
|
||||||
@if (normalizedError.error.message) {
|
|
||||||
<div class="mt-16 error">{{ normalizedError.error.message }}</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="action(normalizedError.error)"
|
(action)="action(error)"
|
||||||
[icon]="normalizedError.actionIcon"
|
[icon]="actionIcon(error)"
|
||||||
[label]="normalizedError.actionLabel | translate"
|
[label]="actionLabel(error) | translate"
|
||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
class="mt-20"
|
class="mt-20"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
|
|||||||
@ -1,33 +1,30 @@
|
|||||||
import { Component, computed, inject } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
import { toSignal } from '@angular/core/rxjs-interop';
|
|
||||||
import { MatIcon } from '@angular/material/icon';
|
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { TranslatePipe } from '@ngx-translate/core';
|
import { IconButtonTypes } from '../../buttons';
|
||||||
import { IconButtonComponent, IconButtonTypes } from '../../buttons';
|
|
||||||
import { CustomError, ErrorService, ErrorType } from '../error.service';
|
import { CustomError, ErrorService, ErrorType } from '../error.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-full-page-error',
|
selector: 'iqser-full-page-error',
|
||||||
templateUrl: './full-page-error.component.html',
|
templateUrl: './full-page-error.component.html',
|
||||||
styleUrls: ['./full-page-error.component.scss'],
|
styleUrls: ['./full-page-error.component.scss'],
|
||||||
imports: [MatIcon, IconButtonComponent, TranslatePipe],
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class FullPageErrorComponent {
|
export class FullPageErrorComponent {
|
||||||
readonly #error = toSignal(inject(ErrorService).error$);
|
|
||||||
protected readonly normalizedError = computed(() => {
|
|
||||||
const error = this.#error();
|
|
||||||
if (!error) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
error: error,
|
|
||||||
title: error instanceof CustomError ? error.label : _('error.title'),
|
|
||||||
actionLabel: error instanceof CustomError ? error.actionLabel : _('error.reload'),
|
|
||||||
actionIcon: error instanceof CustomError ? error.actionIcon : 'iqser:refresh',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
protected readonly iconButtonTypes = IconButtonTypes;
|
protected readonly iconButtonTypes = IconButtonTypes;
|
||||||
|
protected readonly errorService = inject(ErrorService);
|
||||||
|
|
||||||
|
errorTitle(error: ErrorType): string {
|
||||||
|
return error instanceof CustomError ? error.label : _('error.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
actionLabel(error: ErrorType): string {
|
||||||
|
return error instanceof CustomError ? error.actionLabel : _('error.reload');
|
||||||
|
}
|
||||||
|
|
||||||
|
actionIcon(error: ErrorType): string {
|
||||||
|
return error instanceof CustomError ? error.actionIcon : 'iqser:refresh';
|
||||||
|
}
|
||||||
|
|
||||||
action(error: ErrorType): void {
|
action(error: ErrorType): void {
|
||||||
if (error instanceof CustomError && error.action) {
|
if (error instanceof CustomError && error.action) {
|
||||||
|
|||||||
@ -10,11 +10,10 @@ import {
|
|||||||
import { Inject, Injectable, Optional } from '@angular/core';
|
import { Inject, Injectable, Optional } from '@angular/core';
|
||||||
import { finalize, MonoTypeOperatorFunction, Observable, retry, throwError, timer } from 'rxjs';
|
import { finalize, MonoTypeOperatorFunction, Observable, retry, throwError, timer } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
import { LoadingService } from '../loading';
|
import { MAX_RETRIES_ON_SERVER_ERROR, SERVER_ERROR_SKIP_PATHS } from './tokens';
|
||||||
import { getConfig } from '../services';
|
|
||||||
import { KeycloakStatusService } from '../tenants';
|
|
||||||
import { ErrorService } from './error.service';
|
import { ErrorService } from './error.service';
|
||||||
import { SERVER_ERROR_SKIP_PATHS } from './tokens';
|
import { KeycloakStatusService } from '../tenants';
|
||||||
|
import { LoadingService } from '../loading';
|
||||||
|
|
||||||
function updateSeconds(seconds: number) {
|
function updateSeconds(seconds: number) {
|
||||||
if (seconds === 0 || seconds === 1) {
|
if (seconds === 0 || seconds === 1) {
|
||||||
@ -53,12 +52,12 @@ function backoffOnServerError(maxRetries = 3, skippedPaths: string[] = []): Mono
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerErrorInterceptor implements HttpInterceptor {
|
export class ServerErrorInterceptor implements HttpInterceptor {
|
||||||
private readonly _urlsWithError = new Set();
|
private readonly _urlsWithError = new Set();
|
||||||
readonly #config = getConfig();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _errorService: ErrorService,
|
private readonly _errorService: ErrorService,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _keycloakStatusService: KeycloakStatusService,
|
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[],
|
@Optional() @Inject(SERVER_ERROR_SKIP_PATHS) private readonly _skippedPaths: string[],
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -89,7 +88,7 @@ export class ServerErrorInterceptor implements HttpInterceptor {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
backoffOnServerError(this.#config.MAX_RETRIES_ON_SERVER_ERROR, this._skippedPaths),
|
backoffOnServerError(this._maxRetries, this._skippedPaths),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const MAX_RETRIES_ON_SERVER_ERROR = new InjectionToken<number>('Number of retries before giving up');
|
||||||
export const SERVER_ERROR_SKIP_PATHS = new InjectionToken<string[]>('A list of paths to skip when handling server errors');
|
export const SERVER_ERROR_SKIP_PATHS = new InjectionToken<string[]>('A list of paths to skip when handling server errors');
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
|
import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
|
||||||
import { toSignal } from '@angular/core/rxjs-interop';
|
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
||||||
import { getConfig, IqserUserPreferenceService } from '../services';
|
import { getConfig } from '../services';
|
||||||
|
import { IqserUserPreferenceService } from '../services';
|
||||||
import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component';
|
import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component';
|
||||||
import { HELP_MODE_KEYS } from './tokens';
|
import { HELP_MODE_KEYS, MANUAL_BASE_URL } from './tokens';
|
||||||
import { HelpModeKey } from './types';
|
import { HelpModeKey } from './types';
|
||||||
import {
|
import {
|
||||||
DOCUMINE_THEME_CLASS,
|
DOCUMINE_THEME_CLASS,
|
||||||
@ -20,6 +20,7 @@ import {
|
|||||||
ScrollableParentViews,
|
ScrollableParentViews,
|
||||||
WEB_VIEWER_ELEMENTS,
|
WEB_VIEWER_ELEMENTS,
|
||||||
} from './utils/constants';
|
} from './utils/constants';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
export interface Helper {
|
export interface Helper {
|
||||||
readonly element: HTMLElement;
|
readonly element: HTMLElement;
|
||||||
@ -38,13 +39,13 @@ export class HelpModeService {
|
|||||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||||
#helpers: Record<string, Helper> = {};
|
#helpers: Record<string, Helper> = {};
|
||||||
#dialogMode = false;
|
#dialogMode = false;
|
||||||
readonly #config = getConfig();
|
|
||||||
readonly isHelpModeActive$ = this.#isHelpModeActive$.asObservable();
|
readonly isHelpModeActive$ = this.#isHelpModeActive$.asObservable();
|
||||||
readonly isHelpModeActive = toSignal(this.isHelpModeActive$, { initialValue: false });
|
readonly isHelpModeActive = toSignal(this.isHelpModeActive$, { initialValue: false });
|
||||||
readonly helpModeDialogIsOpened$ = this.#helpModeDialogIsOpened$.asObservable();
|
readonly helpModeDialogIsOpened$ = this.#helpModeDialogIsOpened$.asObservable();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(HELP_MODE_KEYS) private readonly _keys: HelpModeKey[],
|
@Inject(HELP_MODE_KEYS) private readonly _keys: HelpModeKey[],
|
||||||
|
@Inject(MANUAL_BASE_URL) private readonly _manualBaseURL: string,
|
||||||
private readonly _dialog: MatDialog,
|
private readonly _dialog: MatDialog,
|
||||||
private readonly _rendererFactory: RendererFactory2,
|
private readonly _rendererFactory: RendererFactory2,
|
||||||
private readonly _translateService: TranslateService,
|
private readonly _translateService: TranslateService,
|
||||||
@ -160,7 +161,7 @@ export class HelpModeService {
|
|||||||
|
|
||||||
#generateDocsLink(key: string) {
|
#generateDocsLink(key: string) {
|
||||||
const currentLang = this._translateService.currentLang;
|
const currentLang = this._translateService.currentLang;
|
||||||
return `${this.#config.MANUAL_BASE_URL}/${currentLang}/index-${currentLang}.html?contextId=${key}`;
|
return `${this._manualBaseURL}/${currentLang}/index-${currentLang}.html?contextId=${key}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
#isElementVisible(helper: Helper): boolean {
|
#isElementVisible(helper: Helper): boolean {
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { InjectionToken } from '@angular/core';
|
import { inject, InjectionToken } from '@angular/core';
|
||||||
|
import { IqserConfigService } from '../services/iqser-config.service';
|
||||||
import { HelpModeKey } from './types';
|
import { HelpModeKey } from './types';
|
||||||
|
|
||||||
export const HELP_MODE_KEYS = new InjectionToken<HelpModeKey>('Help mode keys');
|
export const HELP_MODE_KEYS = new InjectionToken<HelpModeKey>('Help mode keys');
|
||||||
|
export const MANUAL_BASE_URL = new InjectionToken<string>('Base manual URL', {
|
||||||
|
factory: () => inject(IqserConfigService).values.MANUAL_BASE_URL,
|
||||||
|
});
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { makeEnvironmentProviders } from '@angular/core';
|
|
||||||
import { HelpModeService } from '../help-mode.service';
|
import { HelpModeService } from '../help-mode.service';
|
||||||
import { HELP_MODE_KEYS } from '../tokens';
|
import { HELP_MODE_KEYS } from '../tokens';
|
||||||
import { HelpModeKey } from '../types';
|
import { HelpModeKey } from '../types';
|
||||||
|
|
||||||
export function provideHelpMode(helpModeKeys: HelpModeKey[]) {
|
export function provideHelpMode(helpModeKeys: HelpModeKey[]) {
|
||||||
return makeEnvironmentProviders([{ provide: HELP_MODE_KEYS, useValue: helpModeKeys }, HelpModeService]);
|
return [{ provide: HELP_MODE_KEYS, useValue: helpModeKeys }, HelpModeService];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';
|
|
||||||
import { inject } from '@angular/core';
|
|
||||||
import { getConfig } from '../services/iqser-config.service';
|
|
||||||
import { UI_ROOT_PATH_FN } from '../utils/tokens';
|
|
||||||
|
|
||||||
export const apiPathInterceptorFn: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {
|
|
||||||
const config = getConfig();
|
|
||||||
const convertPath = inject(UI_ROOT_PATH_FN);
|
|
||||||
|
|
||||||
if (!req.url.startsWith('/assets')) {
|
|
||||||
const apiUrl = `${config.API_URL}${req.url}`;
|
|
||||||
return next(req.clone({ url: apiUrl }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = convertPath(req.url);
|
|
||||||
return next(req.clone({ url }));
|
|
||||||
};
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
||||||
import { EnvironmentProviders, inject, makeEnvironmentProviders, provideAppInitializer, Provider, Type } from '@angular/core';
|
|
||||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
|
||||||
import { MatIconRegistry } from '@angular/material/icon';
|
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
|
||||||
import { apiPathInterceptorFn } from './interceptors/api-path.interceptor';
|
|
||||||
import { CONFIG_SERVICE, DefaultUserPreferenceService, IqserConfigService, IqserUserPreferenceService } from './services';
|
|
||||||
import { ICONS } from './utils/constants';
|
|
||||||
|
|
||||||
type ProvideCommonUiOptions = {
|
|
||||||
configService: Type<IqserConfigService>;
|
|
||||||
existingUserPreferenceService: Type<IqserUserPreferenceService>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function provideCommonUi(options: ProvideCommonUiOptions): EnvironmentProviders {
|
|
||||||
return makeEnvironmentProviders([
|
|
||||||
provideCommonIcons(),
|
|
||||||
{
|
|
||||||
provide: CONFIG_SERVICE,
|
|
||||||
useExisting: options.configService,
|
|
||||||
},
|
|
||||||
provideHttpClient(withInterceptors([apiPathInterceptorFn])),
|
|
||||||
{ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } },
|
|
||||||
getService(IqserUserPreferenceService, DefaultUserPreferenceService, options.existingUserPreferenceService),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getService<B, D, E>(base: B, _default: Type<D>, existing?: E): Provider {
|
|
||||||
if (existing) {
|
|
||||||
return {
|
|
||||||
provide: base,
|
|
||||||
useExisting: existing,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { provide: base, useClass: _default };
|
|
||||||
}
|
|
||||||
|
|
||||||
function provideCommonIcons() {
|
|
||||||
return provideAppInitializer(() => {
|
|
||||||
const iconRegistry = inject(MatIconRegistry);
|
|
||||||
const sanitizer = inject(DomSanitizer);
|
|
||||||
|
|
||||||
ICONS.forEach(icon => {
|
|
||||||
const url = sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/${icon}.svg`);
|
|
||||||
iconRegistry.addSvgIconInNamespace('iqser', icon, url);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
21
src/lib/services/api-path.interceptor.ts
Normal file
21
src/lib/services/api-path.interceptor.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ApiPathInterceptor implements HttpInterceptor {
|
||||||
|
readonly #config = getConfig();
|
||||||
|
readonly #convertPath = inject(UI_ROOT_PATH_FN);
|
||||||
|
|
||||||
|
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
|
if (!req.url.startsWith('/assets')) {
|
||||||
|
const apiUrl = `${this.#config.API_URL}${req.url}`;
|
||||||
|
return next.handle(req.clone({ url: apiUrl }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = this.#convertPath(req.url);
|
||||||
|
return next.handle(req.clone({ url }));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import exp from 'constants';
|
||||||
|
|
||||||
export * from './toaster.service';
|
export * from './toaster.service';
|
||||||
export * from './error-message.service';
|
export * from './error-message.service';
|
||||||
export * from './generic.service';
|
export * from './generic.service';
|
||||||
@ -7,4 +9,5 @@ export * from './entities-map.service';
|
|||||||
export * from './iqser-user-preference.service';
|
export * from './iqser-user-preference.service';
|
||||||
export * from './language.service';
|
export * from './language.service';
|
||||||
export * from './iqser-config.service';
|
export * from './iqser-config.service';
|
||||||
|
export * from './api-path.interceptor';
|
||||||
export * from './skeleton.service';
|
export * from './skeleton.service';
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
import { inject, InjectionToken } from '@angular/core';
|
import { Inject, inject, Injectable } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { CacheApiService } from '../caching/cache-api.service';
|
import { CacheApiService } from '../caching/cache-api.service';
|
||||||
import { wipeAllCaches } from '../caching/cache-utils';
|
import { wipeAllCaches } from '../caching/cache-utils';
|
||||||
import { IStoredTenantId, TenantsService } from '../tenants';
|
|
||||||
import { LANDING_PAGE_THEMES, MANUAL_BASE_URL, THEME_DIRECTORIES } from '../utils/constants';
|
|
||||||
import { IqserAppConfig } from '../utils/iqser-app-config';
|
import { IqserAppConfig } from '../utils/iqser-app-config';
|
||||||
|
import { LANDING_PAGE_THEMES, MANUAL_BASE_URL, THEME_DIRECTORIES } from '../utils/constants';
|
||||||
|
import { IStoredTenantId, TenantsService } from '../tenants';
|
||||||
|
|
||||||
export const CONFIG_SERVICE = new InjectionToken<IqserConfigService>('IqserConfigService');
|
@Injectable()
|
||||||
|
export class IqserConfigService<T extends IqserAppConfig = IqserAppConfig> {
|
||||||
export abstract class IqserConfigService<T extends IqserAppConfig = IqserAppConfig> {
|
|
||||||
protected readonly _cacheApiService = inject(CacheApiService);
|
protected readonly _cacheApiService = inject(CacheApiService);
|
||||||
protected readonly _titleService = inject(Title);
|
protected readonly _titleService = inject(Title);
|
||||||
protected readonly _tenantsService = inject(TenantsService);
|
protected readonly _tenantsService = inject(TenantsService);
|
||||||
|
|
||||||
protected constructor(protected _values: T) {
|
constructor(@Inject('Doesnt matter') protected _values: T) {
|
||||||
this._checkFrontendVersion();
|
this._checkFrontendVersion();
|
||||||
this.#updateAppType();
|
this.#updateAppType();
|
||||||
this._titleService.setTitle(this._values.APP_NAME);
|
this._titleService.setTitle(this._values.APP_NAME);
|
||||||
@ -74,5 +73,5 @@ export abstract class IqserConfigService<T extends IqserAppConfig = IqserAppConf
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getConfig<T extends IqserAppConfig = IqserAppConfig>() {
|
export function getConfig<T extends IqserAppConfig = IqserAppConfig>() {
|
||||||
return inject<IqserConfigService<T>>(CONFIG_SERVICE).values;
|
return inject<IqserConfigService<T>>(IqserConfigService).values;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
export interface IqserTranslateModuleOptions {
|
export interface IqserTranslateModuleOptions {
|
||||||
readonly pathPrefix?: string;
|
readonly pathPrefix?: string;
|
||||||
readonly pathPrefixFactory?: () => string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,10 +45,7 @@ export class IqserTranslateModule {
|
|||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: translateLoaderToken,
|
provide: translateLoaderToken,
|
||||||
useFactory: () => {
|
useFactory: () => pruningTranslationLoaderFactory(pathPrefix),
|
||||||
const prefix = options?.pathPrefixFactory !== undefined ? options.pathPrefixFactory() : pathPrefix;
|
|
||||||
return pruningTranslationLoaderFactory(prefix);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: MissingTranslationHandler,
|
provide: MissingTranslationHandler,
|
||||||
|
|||||||
@ -17,3 +17,4 @@ export * from './context.component';
|
|||||||
export * from './tokens';
|
export * from './tokens';
|
||||||
export * from './module-options';
|
export * from './module-options';
|
||||||
export * from './iqser-app-config';
|
export * from './iqser-app-config';
|
||||||
|
export * from './types/common-ui-options';
|
||||||
|
|||||||
@ -9,5 +9,4 @@ export interface IqserAppConfig {
|
|||||||
readonly MANUAL_BASE_URL: string;
|
readonly MANUAL_BASE_URL: string;
|
||||||
readonly BASE_TRANSLATIONS_DIRECTORY?: string;
|
readonly BASE_TRANSLATIONS_DIRECTORY?: string;
|
||||||
readonly LANDING_PAGE_THEME: 'redactmanager' | 'documine' | 'mixed';
|
readonly LANDING_PAGE_THEME: 'redactmanager' | 'documine' | 'mixed';
|
||||||
readonly MAX_RETRIES_ON_SERVER_ERROR?: number;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export const UI_ROOT_PATH_FN = new InjectionToken<(path: string) => string>('App
|
|||||||
const root = inject(UI_ROOT);
|
const root = inject(UI_ROOT);
|
||||||
|
|
||||||
return (path: string) => {
|
return (path: string) => {
|
||||||
if (path.startsWith('/')) {
|
if (path[0] === '/') {
|
||||||
return root + path;
|
return root + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
src/lib/utils/types/common-ui-options.ts
Normal file
14
src/lib/utils/types/common-ui-options.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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<
|
||||||
|
UserPreference extends IqserUserPreferenceService,
|
||||||
|
Config extends IqserConfigService<AppConfig>,
|
||||||
|
AppConfig extends IqserAppConfig,
|
||||||
|
> {
|
||||||
|
existingUserPreferenceService?: Type<UserPreference>;
|
||||||
|
configService?: Type<Config>;
|
||||||
|
configServiceFactory: () => Config;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user