RED-3800, refactor multitenancy

This commit is contained in:
George 2023-11-29 17:06:48 +02:00
parent a53d139687
commit b7a9b5c77c
40 changed files with 153 additions and 165 deletions

View File

@ -34,7 +34,6 @@
"main": "apps/red-ui/src/main.ts", "main": "apps/red-ui/src/main.ts",
"polyfills": "apps/red-ui/src/polyfills.ts", "polyfills": "apps/red-ui/src/polyfills.ts",
"tsConfig": "apps/red-ui/tsconfig.json", "tsConfig": "apps/red-ui/tsconfig.json",
"baseHref": "/ui/",
"assets": [ "assets": [
"apps/red-ui/src/favicon.ico", "apps/red-ui/src/favicon.ico",
{ {

View File

@ -205,18 +205,13 @@ const routes: IqserRoutes = [
component: TenantSelectComponent, component: TenantSelectComponent,
}, },
{ {
path: ':tenant', path: 'main',
redirectTo: ':tenant/main',
pathMatch: 'full',
},
{
path: ':tenant/main',
canActivate: [orderedAsyncGuards([ifLoggedIn(), hasAnyRole(), mainGuard()])], canActivate: [orderedAsyncGuards([ifLoggedIn(), hasAnyRole(), mainGuard()])],
component: BaseScreenComponent, component: BaseScreenComponent,
children: mainRoutes, children: mainRoutes,
}, },
{ {
path: ':tenant/auth-error', path: 'auth-error',
component: AuthErrorComponent, component: AuthErrorComponent,
canActivate: [doesNotHaveAnyRole()], canActivate: [doesNotHaveAnyRole()],
}, },

View File

@ -1,4 +1,4 @@
import { DatePipe as BaseDatePipe } from '@angular/common'; import { APP_BASE_HREF, DatePipe as BaseDatePipe } from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ENVIRONMENT_INITIALIZER, ErrorHandler, inject, NgModule } from '@angular/core'; import { ENVIRONMENT_INITIALIZER, ErrorHandler, inject, NgModule } from '@angular/core';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
@ -43,7 +43,7 @@ import {
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { CommonUiModule } from '@iqser/common-ui/lib/common-ui.module'; import { CommonUiModule } from '@iqser/common-ui/lib/common-ui.module';
import { LogoComponent, SkeletonComponent, ToastComponent } from '@iqser/common-ui/lib/shared'; import { LogoComponent, SkeletonComponent, ToastComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe, TenantsModule } from '@iqser/common-ui/lib/tenants'; import { TenantsModule } from '@iqser/common-ui/lib/tenants';
import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { MissingTranslationHandler } from '@ngx-translate/core'; import { MissingTranslationHandler } from '@ngx-translate/core';
@ -61,13 +61,14 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { UserService } from '@users/user.service'; import { UserService } from '@users/user.service';
import { UI_CACHES } from '@utils/constants'; import { UI_CACHES } from '@utils/constants';
import { REDMissingTranslationHandler } from '@utils/missing-translations-handler'; import { REDMissingTranslationHandler } from '@utils/missing-translations-handler';
import { LoggerModule, NgxLoggerLevel, TOKEN_LOGGER_CONFIG, TOKEN_LOGGER_RULES_SERVICE } from 'ngx-logger'; import { LoggerModule, NGXLogger, NgxLoggerLevel, TOKEN_LOGGER_CONFIG, TOKEN_LOGGER_RULES_SERVICE } from 'ngx-logger';
import { ToastrModule } from 'ngx-toastr'; import { ToastrModule } from 'ngx-toastr';
import * as helpModeKeys from '../assets/help-mode/help-mode-keys.json'; import * as helpModeKeys from '../assets/help-mode/help-mode-keys.json';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module'; import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens'; import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
import { UI_ROOT } from '@common-ui/utils';
export const appModuleFactory = (config: AppConfig) => { export const appModuleFactory = (config: AppConfig) => {
@NgModule({ @NgModule({
@ -183,11 +184,27 @@ export const appModuleFactory = (config: AppConfig) => {
IqserDenyDirective, IqserDenyDirective,
IqserListingModule, IqserListingModule,
IconButtonComponent, IconButtonComponent,
TenantPipe,
MatDividerModule, MatDividerModule,
ChevronButtonComponent, ChevronButtonComponent,
], ],
providers: [ providers: [
{
provide: UI_ROOT,
useValue: '/ui',
},
{
provide: APP_BASE_HREF,
useFactory: () => {
const uiRoot = inject(UI_ROOT);
const pathParams = location.pathname.split('/').filter(Boolean);
const uiRootPathIndex = pathParams.indexOf(uiRoot.replace('/', ''));
const tenant = pathParams[uiRootPathIndex + 1] ?? '';
const appBaseHref = uiRoot + '/' + tenant;
inject(NGXLogger).info('Provide APP_BASE_HREF:', appBaseHref);
return appBaseHref;
},
},
{ {
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
multi: true, multi: true,

View File

@ -9,7 +9,7 @@
<redaction-breadcrumbs></redaction-breadcrumbs> <redaction-breadcrumbs></redaction-breadcrumbs>
</div> </div>
<a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/'] | tenant" class="logo"> <a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/']" class="logo">
<div [attr.help-mode-key]="'home'" class="actions"> <div [attr.help-mode-key]="'home'" class="actions">
<iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="iqser:logo"></iqser-logo> <iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="iqser:logo"></iqser-logo>
<div class="app-name">{{ titleService.getTitle() }}</div> <div class="app-name">{{ titleService.getTitle() }}</div>

View File

@ -14,7 +14,7 @@
[id]="first ? 'navigateToActiveDossiers' : ''" [id]="first ? 'navigateToActiveDossiers' : ''"
[matTooltip]="breadcrumb.options.clamp && (breadcrumb.name$ | async)" [matTooltip]="breadcrumb.options.clamp && (breadcrumb.name$ | async)"
[routerLinkActiveOptions]="breadcrumb.options.routerLinkActiveOptions || { exact: false }" [routerLinkActiveOptions]="breadcrumb.options.routerLinkActiveOptions || { exact: false }"
[routerLink]="breadcrumb.options.routerLink | tenant" [routerLink]="breadcrumb.options.routerLink"
class="breadcrumb" class="breadcrumb"
routerLinkActive="active" routerLinkActive="active"
> >
@ -32,7 +32,7 @@
<div id="breadcrumbs-menu-items"> <div id="breadcrumbs-menu-items">
<a <a
*ngFor="let option of breadcrumb.options.options" *ngFor="let option of breadcrumb.options.options"
[routerLink]="option.options.routerLink | tenant" [routerLink]="option.options.routerLink"
mat-menu-item mat-menu-item
routerLinkActive="active" routerLinkActive="active"
> >

View File

@ -1,6 +1,6 @@
<div id="user-menu-items"> <div id="user-menu-items">
<ng-container *ngFor="let item of userMenuItems; trackBy: trackBy"> <ng-container *ngFor="let item of userMenuItems; trackBy: trackBy">
<a (click)="item.action?.()" *ngIf="item.show" [id]="item.id" [routerLink]="item.routerLink | tenant" mat-menu-item> <a (click)="item.action?.()" *ngIf="item.show" [id]="item.id" [routerLink]="item.routerLink" mat-menu-item>
{{ item.name | translate }} {{ item.name | translate }}
</a> </a>
</ng-container> </ng-container>

View File

@ -1,7 +1,6 @@
import { Injectable, Injector, ProviderToken } from '@angular/core'; import { Injectable, Injector, ProviderToken } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { getConfig } from '@iqser/common-ui'; import { getConfig } from '@iqser/common-ui';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
import { DOSSIER_ID, DOSSIER_TEMPLATE_ID } from '@red/domain'; import { DOSSIER_ID, DOSSIER_TEMPLATE_ID } from '@red/domain';
import { DossiersService } from '@services/dossiers/dossiers.service'; import { DossiersService } from '@services/dossiers/dossiers.service';
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
@ -16,7 +15,6 @@ export class DossierFilesGuard implements CanActivate {
constructor( constructor(
private readonly _injector: Injector, private readonly _injector: Injector,
private readonly _tenantsService: TenantsService,
private readonly _filesMapService: FilesMapService, private readonly _filesMapService: FilesMapService,
private readonly _filesService: FilesService, private readonly _filesService: FilesService,
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
@ -37,7 +35,7 @@ export class DossierFilesGuard implements CanActivate {
} }
if (!dossiersService.has(dossierId)) { if (!dossiersService.has(dossierId)) {
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main`, dossierTemplateId]); await this._router.navigate(['/main', dossierTemplateId]);
return false; return false;
} }

View File

@ -1,6 +1,5 @@
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
import { DOSSIER_TEMPLATE_ID } from '@red/domain'; import { DOSSIER_TEMPLATE_ID } from '@red/domain';
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service'; import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
@ -15,7 +14,7 @@ export function templateExistsWhenEnteringAdmin(): CanActivateFn {
const dossierTemplate = inject(DossierTemplateStatsService).get(dossierTemplateId); const dossierTemplate = inject(DossierTemplateStatsService).get(dossierTemplateId);
if (!dossierTemplate) { if (!dossierTemplate) {
await inject(Router).navigate([inject(TenantsService).activeTenantId, 'main', 'admin', 'dossier-templates']); await inject(Router).navigate(['main', 'admin', 'dossier-templates']);
return false; return false;
} }
return true; return true;
@ -29,7 +28,6 @@ export function templateExistsWhenEnteringDossierList(): CanActivateFn {
const dossierTemplatesService = inject(DossierTemplatesService); const dossierTemplatesService = inject(DossierTemplatesService);
const logger = inject(NGXLogger); const logger = inject(NGXLogger);
const router = inject(Router); const router = inject(Router);
const tenantsService = inject(TenantsService);
const userPreferencesService = inject(UserPreferenceService); const userPreferencesService = inject(UserPreferenceService);
await firstValueFrom(dashboardStatsService.loadAll()); await firstValueFrom(dashboardStatsService.loadAll());
@ -38,7 +36,7 @@ export function templateExistsWhenEnteringDossierList(): CanActivateFn {
if (!dossierTemplateStats || dossierTemplateStats.isEmpty) { if (!dossierTemplateStats || dossierTemplateStats.isEmpty) {
logger.warn(`[ROUTES] Dossier template ${dossierTemplateId} not found, redirecting to main`); logger.warn(`[ROUTES] Dossier template ${dossierTemplateId} not found, redirecting to main`);
await userPreferencesService.saveLastDossierTemplate(null); await userPreferencesService.saveLastDossierTemplate(null);
await router.navigate([tenantsService.activeTenantId, 'main']); await router.navigate(['main']);
return false; return false;
} }
return true; return true;

View File

@ -25,19 +25,23 @@ export function ifLoggedIn(): AsyncGuard {
const keycloakStatusService = inject(KeycloakStatusService); const keycloakStatusService = inject(KeycloakStatusService);
const keycloakInstance = keycloakService.getKeycloakInstance(); const keycloakInstance = keycloakService.getKeycloakInstance();
const tenant = route.paramMap.get('tenant'); const pathParams = location.pathname.split('/').filter(Boolean);
const uiPathIndex = pathParams.indexOf('ui');
const tenant = pathParams[uiPathIndex + 1];
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
const username = queryParams.get('username'); const username = queryParams.get('username');
const router = inject(Router);
if (!keycloakInstance) { if (!keycloakInstance) {
if (!tenant) { if (!tenant) {
logger.error('[ROUTES] No tenant found, something is wrong...'); logger.error('[ROUTES] No tenant found, something is wrong...');
return inject(Router).navigate(['/']); return router.navigate(['/']);
} }
logger.info('[KEYCLOAK] Keycloak init...'); logger.info('[KEYCLOAK] Keycloak init...');
await keycloakInitializer(tenant); await keycloakInitializer(tenant);
logger.info('[KEYCLOAK] Keycloak init done!'); logger.info('[KEYCLOAK] Keycloak init done!');
console.log({ tenant });
await tenantsService.selectTenant(tenant); await tenantsService.selectTenant(tenant);
await usersService.initialize(); await usersService.initialize();
await licenseService.loadLicenses(); await licenseService.loadLicenses();

View File

@ -1,10 +1,10 @@
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router'; import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { KeycloakService } from 'keycloak-angular'; import { KeycloakService } from 'keycloak-angular';
export function ifNotLoggedIn(): CanActivateFn { export function ifNotLoggedIn(): CanActivateFn {
return async (route: ActivatedRouteSnapshot) => { return async () => {
const logger = inject(NGXLogger); const logger = inject(NGXLogger);
const router = inject(Router); const router = inject(Router);
const keycloakService = inject(KeycloakService); const keycloakService = inject(KeycloakService);
@ -16,14 +16,14 @@ export function ifNotLoggedIn(): CanActivateFn {
return true; return true;
} }
const tenant = route.paramMap.get('tenant') || keycloakService.getKeycloakInstance().realm; const tenant = keycloakService.getKeycloakInstance().realm;
if (!tenant) { if (!tenant) {
logger.error('[ROUTES] Tenant not found in route or keycloak realm'); logger.error('[ROUTES] Tenant not found in route or keycloak realm');
return false; return false;
} }
logger.warn('[ROUTES] Is logged in for ' + tenant + ', redirecting to /' + tenant); logger.warn('[ROUTES] Is logged in for ' + tenant + ', redirecting to /' + tenant);
await router.navigate([tenant]); await router.navigate(['/main']);
return false; return false;
}; };
} }

View File

@ -55,7 +55,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { AuditInfoDialogComponent } from './dialogs/audit-info-dialog/audit-info-dialog.component'; import { AuditInfoDialogComponent } from './dialogs/audit-info-dialog/audit-info-dialog.component';
import { DossierTemplateActionsComponent } from './shared/components/dossier-template-actions/dossier-template-actions.component'; import { DossierTemplateActionsComponent } from './shared/components/dossier-template-actions/dossier-template-actions.component';
import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
import { SelectComponent } from '@shared/components/select/select.component'; import { SelectComponent } from '@shared/components/select/select.component';
import { PaginationComponent } from '@common-ui/pagination/pagination.component'; import { PaginationComponent } from '@common-ui/pagination/pagination.component';
@ -124,7 +123,6 @@ const components = [
DetailsRadioComponent, DetailsRadioComponent,
IqserAllowDirective, IqserAllowDirective,
IqserDenyDirective, IqserDenyDirective,
TenantPipe,
SelectComponent, SelectComponent,
PaginationComponent, PaginationComponent,
], ],

View File

@ -107,7 +107,7 @@
<iqser-circle-button <iqser-circle-button
*ngIf="permissionsService.canEditEntities()" *ngIf="permissionsService.canEditEntities()"
[routerLink]="dict.routerLink | tenant" [routerLink]="dict.routerLink"
[tooltip]="'entities-listing.action.edit' | translate" [tooltip]="'entities-listing.action.edit' | translate"
icon="iqser:edit" icon="iqser:edit"
></iqser-circle-button> ></iqser-circle-button>

View File

@ -30,7 +30,7 @@ import { environment } from '@environments/environment';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { watermarkTranslations } from '@translations/watermark-translations'; import { watermarkTranslations } from '@translations/watermark-translations';
import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { AsControl, BASE_HREF_FN, Debounce, getParam, trackByFactory } from '@iqser/common-ui/lib/utils'; import { AsControl, Debounce, getParam, trackByFactory, UI_ROOT_PATH_FN } from '@iqser/common-ui/lib/utils';
import { TenantsService } from '@iqser/common-ui/lib/tenants'; import { TenantsService } from '@iqser/common-ui/lib/tenants';
export const DEFAULT_WATERMARK: Partial<IWatermark> = { export const DEFAULT_WATERMARK: Partial<IWatermark> = {
@ -62,13 +62,6 @@ interface WatermarkForm {
styleUrls: ['./watermark-screen.component.scss'], styleUrls: ['./watermark-screen.component.scss'],
}) })
export class WatermarkScreenComponent implements OnInit { export class WatermarkScreenComponent implements OnInit {
@ViewChild('viewer', { static: true }) private readonly _viewer: ElementRef<HTMLDivElement>;
private readonly _convertPath = inject(BASE_HREF_FN);
readonly #loaded$ = new BehaviorSubject(false);
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly #watermarkId = Number(getParam(WATERMARK_ID));
readonly #config = getConfig<AppConfig>();
#watermark: Partial<IWatermark> = {};
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly translations = watermarkTranslations; readonly translations = watermarkTranslations;
readonly trackBy = trackByFactory(); readonly trackBy = trackByFactory();
@ -86,6 +79,13 @@ export class WatermarkScreenComponent implements OnInit {
readonly watermarkHorizontalAlignments = Object.values(WATERMARK_HORIZONTAL_ALIGNMENTS); readonly watermarkHorizontalAlignments = Object.values(WATERMARK_HORIZONTAL_ALIGNMENTS);
readonly watermarkVerticalAlignments = Object.values(WATERMARK_VERTICAL_ALIGNMENTS); readonly watermarkVerticalAlignments = Object.values(WATERMARK_VERTICAL_ALIGNMENTS);
currentAlignment: WatermarkAlignment; currentAlignment: WatermarkAlignment;
@ViewChild('viewer', { static: true }) private readonly _viewer: ElementRef<HTMLDivElement>;
readonly #loaded$ = new BehaviorSubject(false);
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly #watermarkId = Number(getParam(WATERMARK_ID));
readonly #config = getConfig<AppConfig>();
#watermark: Partial<IWatermark> = {};
readonly #convertPath = inject(UI_ROOT_PATH_FN);
constructor( constructor(
private readonly _http: HttpClient, private readonly _http: HttpClient,
@ -222,8 +222,8 @@ export class WatermarkScreenComponent implements OnInit {
this.instance = await WebViewer( this.instance = await WebViewer(
{ {
licenseKey: this._licenseService.activeLicenseKey, licenseKey: this._licenseService.activeLicenseKey,
path: this._convertPath('/assets/wv-resources'), path: this.#convertPath('/assets/wv-resources'),
css: this._convertPath('/assets/pdftron/stylesheet.css'), css: this.#convertPath('/assets/pdftron/stylesheet.css'),
fullAPI: true, fullAPI: true,
isReadOnly: true, isReadOnly: true,
backendType: 'ems', backendType: 'ems',
@ -242,7 +242,7 @@ export class WatermarkScreenComponent implements OnInit {
}); });
if (environment.production) { if (environment.production) {
this.instance.Core.setCustomFontURL('https://' + window.location.host + this._convertPath('/assets/pdftron')); this.instance.Core.setCustomFontURL('https://' + window.location.host + this.#convertPath('/assets/pdftron'));
} }
this.#disableElements(); this.#disableElements();

View File

@ -25,7 +25,6 @@ import { ColorPickerModule } from 'ngx-color-picker';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { IqserAuthGuard, IqserUsersModule } from '@iqser/common-ui/lib/users'; import { IqserAuthGuard, IqserUsersModule } from '@iqser/common-ui/lib/users';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
const routes: IqserRoutes = [ const routes: IqserRoutes = [
{ {
@ -72,7 +71,6 @@ const routes: IqserRoutes = [
CircleButtonComponent, CircleButtonComponent,
HasScrollbarDirective, HasScrollbarDirective,
IqserAllowDirective, IqserAllowDirective,
TenantPipe,
MatTooltipModule, MatTooltipModule,
], ],
}) })

View File

@ -15,7 +15,7 @@
*allow="roles.watermarks.write; if: currentUser.isAdmin" *allow="roles.watermarks.write; if: currentUser.isAdmin"
[attr.help-mode-key]="'create_new_watermark'" [attr.help-mode-key]="'create_new_watermark'"
[label]="'watermarks-listing.add-new' | translate" [label]="'watermarks-listing.add-new' | translate"
[routerLink]="getRouterLink() | tenant" [routerLink]="getRouterLink()"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"
icon="iqser:plus" icon="iqser:plus"
></iqser-icon-button> ></iqser-icon-button>
@ -44,13 +44,13 @@
<div class="cell"> <div class="cell">
<div class="small-label"> <div class="small-label">
{{ entity.dateAdded | date : 'd MMM yyyy' }} {{ entity.dateAdded | date: 'd MMM yyyy' }}
</div> </div>
</div> </div>
<div class="cell"> <div class="cell">
<div class="small-label"> <div class="small-label">
{{ entity.dateModified | date : 'd MMM yyyy' }} {{ entity.dateModified | date: 'd MMM yyyy' }}
</div> </div>
</div> </div>
@ -58,7 +58,7 @@
<div class="action-buttons"> <div class="action-buttons">
<div [attr.help-mode-key]="'edit_delete_watermark'"> <div [attr.help-mode-key]="'edit_delete_watermark'">
<iqser-circle-button <iqser-circle-button
[routerLink]="getRouterLink(entity) | tenant" [routerLink]="getRouterLink(entity)"
[tooltip]="'watermarks-listing.action.edit' | translate" [tooltip]="'watermarks-listing.action.edit' | translate"
icon="iqser:edit" icon="iqser:edit"
></iqser-circle-button> ></iqser-circle-button>

View File

@ -2,10 +2,10 @@
<ng-container *ngFor="let item of items[type]"> <ng-container *ngFor="let item of items[type]">
<a <a
*ngIf="item.show" *ngIf="item.show"
[attr.help-mode-key]="item.helpModeKey"
[class.disabled]="isDisabled(item.screen)" [class.disabled]="isDisabled(item.screen)"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
[routerLink]="prefix + item.screen | tenant" [routerLink]="prefix + item.screen"
[attr.help-mode-key]="item.helpModeKey"
class="item" class="item"
routerLinkActive="active" routerLinkActive="active"
> >

View File

@ -4,7 +4,6 @@ import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { getConfig, IqserHelpModeModule, IqserPermissionsService, isIqserDevMode } from '@iqser/common-ui'; import { getConfig, IqserHelpModeModule, IqserPermissionsService, isIqserDevMode } from '@iqser/common-ui';
import { SideNavComponent } from '@iqser/common-ui/lib/shared'; import { SideNavComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { AdminSideNavType, AdminSideNavTypes, AppConfig, DOSSIER_TEMPLATE_ID, ENTITY_TYPE, User, WATERMARK_ID } from '@red/domain'; import { AdminSideNavType, AdminSideNavTypes, AppConfig, DOSSIER_TEMPLATE_ID, ENTITY_TYPE, User, WATERMARK_ID } from '@red/domain';
@ -23,10 +22,9 @@ interface NavItem {
templateUrl: './admin-side-nav.component.html', templateUrl: './admin-side-nav.component.html',
styleUrls: ['./admin-side-nav.component.scss'], styleUrls: ['./admin-side-nav.component.scss'],
standalone: true, standalone: true,
imports: [TranslateModule, NgIf, IqserHelpModeModule, RouterLink, RouterLinkActive, NgForOf, SideNavComponent, TenantPipe], imports: [TranslateModule, NgIf, IqserHelpModeModule, RouterLink, RouterLinkActive, NgForOf, SideNavComponent],
}) })
export class AdminSideNavComponent implements OnInit { export class AdminSideNavComponent implements OnInit {
readonly #config = getConfig<AppConfig>();
readonly isIqserDevMode = isIqserDevMode(); readonly isIqserDevMode = isIqserDevMode();
@Input() type: AdminSideNavType; @Input() type: AdminSideNavType;
@Input() disabledItems: string[] = []; @Input() disabledItems: string[] = [];
@ -34,6 +32,7 @@ export class AdminSideNavComponent implements OnInit {
readonly currentUser = getCurrentUser<User>(); readonly currentUser = getCurrentUser<User>();
readonly roles = Roles; readonly roles = Roles;
prefix: string; prefix: string;
readonly #config = getConfig<AppConfig>();
readonly isDocumine = this.#config.IS_DOCUMINE; readonly isDocumine = this.#config.IS_DOCUMINE;
readonly canAccessRulesInDocumine = this.isDocumine && !this.#config.RULE_EDITOR_DEV_ONLY; readonly canAccessRulesInDocumine = this.isDocumine && !this.#config.RULE_EDITOR_DEV_ONLY;
readonly items: { readonly [key in AdminSideNavType]: NavItem[] } = { readonly items: { readonly [key in AdminSideNavType]: NavItem[] } = {
@ -102,7 +101,10 @@ export class AdminSideNavComponent implements OnInit {
{ {
screen: 'component-rules', screen: 'component-rules',
label: _('admin-side-nav.component-rule-editor'), label: _('admin-side-nav.component-rule-editor'),
show: this.isDocumine && (this.isIqserDevMode || this.canAccessRulesInDocumine) && this._permissionsService.has(Roles.rules.read), show:
this.isDocumine &&
(this.isIqserDevMode || this.canAccessRulesInDocumine) &&
this._permissionsService.has(Roles.rules.read),
}, },
{ {
screen: 'default-colors', screen: 'default-colors',

View File

@ -2,29 +2,25 @@
<ng-container *ngIf="dossierTemplate$ | async as dossierTemplate"> <ng-container *ngIf="dossierTemplate$ | async as dossierTemplate">
<a <a
*ngIf="root || dossierTemplate" *ngIf="root || dossierTemplate"
[routerLink]="'/main/admin/dossier-templates' | tenant" [routerLink]="'/main/admin/dossier-templates'"
class="breadcrumb" class="breadcrumb"
translate="dossier-templates.label" translate="dossier-templates.label"
></a> ></a>
<mat-icon svgIcon="iqser:arrow-right"></mat-icon> <mat-icon svgIcon="iqser:arrow-right"></mat-icon>
<a <a [class.active]="(activeDictionary$ | async) === undefined" [routerLink]="dossierTemplate.routerLink" class="breadcrumb ml-0">
[class.active]="(activeDictionary$ | async) === undefined"
[routerLink]="dossierTemplate.routerLink | tenant"
class="breadcrumb ml-0"
>
{{ dossierTemplate.name }} {{ dossierTemplate.name }}
</a> </a>
<ng-container *ngIf="activeDictionary$ | async as activeDictionary"> <ng-container *ngIf="activeDictionary$ | async as activeDictionary">
<mat-icon svgIcon="iqser:arrow-right"></mat-icon> <mat-icon svgIcon="iqser:arrow-right"></mat-icon>
<a [routerLink]="dossierTemplate.routerLink + '/entities' | tenant" class="breadcrumb ml-0"> <a [routerLink]="dossierTemplate.routerLink + '/entities'" class="breadcrumb ml-0">
{{ 'admin-side-nav.entities' | translate }} {{ 'admin-side-nav.entities' | translate }}
</a> </a>
<mat-icon svgIcon="iqser:arrow-right"></mat-icon> <mat-icon svgIcon="iqser:arrow-right"></mat-icon>
<a [routerLink]="activeDictionary.routerLink | tenant" class="breadcrumb ml-0" routerLinkActive="active"> <a [routerLink]="activeDictionary.routerLink" class="breadcrumb ml-0" routerLinkActive="active">
{{ activeDictionary.label }} {{ activeDictionary.label }}
</a> </a>
</ng-container> </ng-container>

View File

@ -8,14 +8,13 @@ import { DictionariesMapService } from '@services/entity-services/dictionaries-m
import { AsyncPipe, NgIf } from '@angular/common'; import { AsyncPipe, NgIf } from '@angular/common';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
@Component({ @Component({
selector: 'redaction-dossier-template-breadcrumbs', selector: 'redaction-dossier-template-breadcrumbs',
templateUrl: './dossier-template-breadcrumbs.component.html', templateUrl: './dossier-template-breadcrumbs.component.html',
styleUrls: ['./dossier-template-breadcrumbs.component.scss'], styleUrls: ['./dossier-template-breadcrumbs.component.scss'],
standalone: true, standalone: true,
imports: [NgIf, AsyncPipe, RouterLink, MatIconModule, TranslateModule, RouterLinkActive, TenantPipe], imports: [NgIf, AsyncPipe, RouterLink, MatIconModule, TranslateModule, RouterLinkActive],
}) })
export class DossierTemplateBreadcrumbsComponent { export class DossierTemplateBreadcrumbsComponent {
@Input() root = false; @Input() root = false;

View File

@ -1,14 +1,14 @@
<div <div
(mousedown)="fileAttributesService.resetEdit()"
[class.help-mode-active]="helpModeService?.isHelpModeActive$ | async" [class.help-mode-active]="helpModeService?.isHelpModeActive$ | async"
class="workflow-item" class="workflow-item"
(mousedown)="fileAttributesService.resetEdit()"
> >
<div class="details-wrapper"> <div class="details-wrapper">
<div class="details"> <div class="details">
<div <div
[attr.help-mode-key]="'workflow_view'" [attr.help-mode-key]="'workflow_view'"
[matTooltip]="file.filename" [matTooltip]="file.filename"
[routerLink]="file.routerLink | tenant" [routerLink]="file.routerLink"
class="filename pointer" class="filename pointer"
matTooltipPosition="above" matTooltipPosition="above"
> >

View File

@ -28,7 +28,6 @@ import { FileAttributeComponent } from './components/file-attribute/file-attribu
import { SharedModule } from '@shared/shared.module'; import { SharedModule } from '@shared/shared.module';
import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { StatusBarComponent } from '@iqser/common-ui/lib/shared'; import { StatusBarComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
const routes: IqserRoutes = [ const routes: IqserRoutes = [
{ {
@ -69,7 +68,6 @@ const routes: IqserRoutes = [
HasScrollbarDirective, HasScrollbarDirective,
DynamicInputComponent, DynamicInputComponent,
IqserAllowDirective, IqserAllowDirective,
TenantPipe,
DisableStopPropagationDirective, DisableStopPropagationDirective,
FileAttributeComponent, FileAttributeComponent,
], ],

View File

@ -1,5 +1,4 @@
import { Component, computed, Inject } from '@angular/core'; import { Component, computed } from '@angular/core';
import { BASE_HREF } from '@iqser/common-ui/lib/utils';
import { ViewMode, ViewModes } from '@red/domain'; import { ViewMode, ViewModes } from '@red/domain';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { FileDataService } from '../../services/file-data.service'; import { FileDataService } from '../../services/file-data.service';
@ -30,7 +29,6 @@ export class ViewSwitchComponent {
}); });
constructor( constructor(
@Inject(BASE_HREF) private readonly _baseHref: string,
readonly viewModeService: ViewModeService, readonly viewModeService: ViewModeService,
private readonly _state: FilePreviewStateService, private readonly _state: FilePreviewStateService,
private readonly _fileDataService: FileDataService, private readonly _fileDataService: FileDataService,

View File

@ -26,17 +26,17 @@
<iqser-circle-button <iqser-circle-button
(action)="openComponentLogView()" (action)="openComponentLogView()"
*allow="roles.getRss" *allow="roles.getRss"
[attr.help-mode-key]="'editor_scm'"
[tooltip]="'file-preview.open-rss-view' | translate" [tooltip]="'file-preview.open-rss-view' | translate"
class="ml-8" class="ml-8"
icon="red:extract" icon="red:extract"
tooltipPosition="below" tooltipPosition="below"
[attr.help-mode-key]="'editor_scm'"
></iqser-circle-button> ></iqser-circle-button>
<redaction-file-actions <redaction-file-actions
[dossier]="state.dossier()" [dossier]="state.dossier()"
[helpModeKeyPrefix]="'editor'"
[file]="file" [file]="file"
[helpModeKeyPrefix]="'editor'"
[minWidth]="width" [minWidth]="width"
type="file-preview" type="file-preview"
></redaction-file-actions> ></redaction-file-actions>
@ -51,10 +51,10 @@
<iqser-circle-button <iqser-circle-button
(action)="toggleFullScreen()" (action)="toggleFullScreen()"
[attr.help-mode-key]="'editor_full_screen'"
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'" [icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
[tooltip]="'file-preview.fullscreen' | translate" [tooltip]="'file-preview.fullscreen' | translate"
class="ml-2" class="ml-2"
[attr.help-mode-key]="'editor_full_screen'"
></iqser-circle-button> ></iqser-circle-button>
<!-- Dev Mode Features--> <!-- Dev Mode Features-->
@ -71,11 +71,11 @@
<iqser-circle-button <iqser-circle-button
*ngIf="!fullScreen" *ngIf="!fullScreen"
[routerLink]="state.dossier().routerLink | tenant" [attr.help-mode-key]="'editor_close'"
[routerLink]="state.dossier().routerLink"
[tooltip]="'common.close' | translate" [tooltip]="'common.close' | translate"
class="ml-8" class="ml-8"
icon="iqser:close" icon="iqser:close"
[attr.help-mode-key]="'editor_close'"
></iqser-circle-button> ></iqser-circle-button>
</div> </div>
</div> </div>

View File

@ -26,7 +26,6 @@ import {
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { IqserFiltersModule } from '@iqser/common-ui/lib/filtering'; import { IqserFiltersModule } from '@iqser/common-ui/lib/filtering';
import { StatusBarComponent } from '@iqser/common-ui/lib/shared'; import { StatusBarComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '@shared/shared.module'; import { SharedModule } from '@shared/shared.module';
@ -151,7 +150,6 @@ const components = [
RoundCheckboxComponent, RoundCheckboxComponent,
IqserAllowDirective, IqserAllowDirective,
IqserDenyDirective, IqserDenyDirective,
TenantPipe,
LogPipe, LogPipe,
ReplaceNbspPipe, ReplaceNbspPipe,
], ],

View File

@ -1,7 +1,6 @@
import { inject, Injectable, NgZone } from '@angular/core'; import { inject, Injectable, NgZone } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui'; import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import { BASE_HREF_FN } from '@iqser/common-ui/lib/utils';
import { AnnotationPermissions } from '@models/file/annotation.permissions'; import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -17,7 +16,6 @@ export class PdfAnnotationActionsService {
readonly #state = inject(FilePreviewStateService); readonly #state = inject(FilePreviewStateService);
readonly #translateService = inject(TranslateService); readonly #translateService = inject(TranslateService);
readonly #ngZone = inject(NgZone); readonly #ngZone = inject(NgZone);
readonly #convertPath = inject(BASE_HREF_FN);
readonly #annotationActionsService = inject(AnnotationActionsService); readonly #annotationActionsService = inject(AnnotationActionsService);
readonly #iqserPermissionsService = inject(IqserPermissionsService); readonly #iqserPermissionsService = inject(IqserPermissionsService);
readonly #annotationManager = inject(REDAnnotationManager); readonly #annotationManager = inject(REDAnnotationManager);
@ -107,7 +105,7 @@ export class PdfAnnotationActionsService {
#getButton(icon: string, title: string, action: () => void | Promise<void>): IHeaderElement { #getButton(icon: string, title: string, action: () => void | Promise<void>): IHeaderElement {
return { return {
type: 'actionButton', type: 'actionButton',
img: this.#convertPath(`/assets/icons/general/${icon}.svg`), img: `ui/assets/icons/general/${icon}.svg`,
title: this.#translateService.instant(title), title: this.#translateService.instant(title),
onClick: () => this.#ngZone.run(async () => action()), onClick: () => this.#ngZone.run(async () => action()),
}; };
@ -121,7 +119,7 @@ export class PdfAnnotationActionsService {
button.className = 'Button'; button.className = 'Button';
button.style.setProperty('pointer-events', 'none'); button.style.setProperty('pointer-events', 'none');
const img = document.createElement('img'); const img = document.createElement('img');
img.src = this.#convertPath('/assets/icons/general/disabled-check.svg'); img.src = 'ui/assets/icons/general/disabled-check.svg';
button.appendChild(img); button.appendChild(img);
return button; return button;

View File

@ -1,7 +1,7 @@
import { computed, effect, inject, Injectable, NgZone } from '@angular/core'; import { computed, effect, inject, Injectable, NgZone } from '@angular/core';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui'; import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { BASE_HREF_FN, isJustOne, shareDistinctLast } from '@iqser/common-ui/lib/utils'; import { isJustOne, shareDistinctLast, UI_ROOT_PATH_FN } from '@iqser/common-ui/lib/utils';
import { import {
ManualRedactionEntryType, ManualRedactionEntryType,
ManualRedactionEntryTypes, ManualRedactionEntryTypes,
@ -43,15 +43,6 @@ import Quad = Core.Math.Quad;
@Injectable() @Injectable()
export class PdfProxyService { export class PdfProxyService {
readonly #convertPath = inject(BASE_HREF_FN);
readonly #visibilityOffIcon = this.#convertPath('/assets/icons/general/visibility-off.svg');
readonly #visibilityIcon = this.#convertPath('/assets/icons/general/visibility.svg');
readonly #falsePositiveIcon = this.#convertPath('/assets/icons/general/pdftron-action-false-positive.svg');
readonly #addRedactionIcon = this._iqserPermissionsService.has(Roles.getRss)
? this.#convertPath('/assets/icons/general/pdftron-action-add-annotation.svg')
: this.#convertPath('/assets/icons/general/pdftron-action-add-redaction.svg');
readonly #isDocumine = getConfig().IS_DOCUMINE;
readonly #addHintIcon = this.#convertPath('/assets/icons/general/pdftron-action-add-hint.svg');
readonly annotationSelected$ = this.#annotationSelected$; readonly annotationSelected$ = this.#annotationSelected$;
readonly manualAnnotationRequested$ = new Subject<ManualRedactionEntryWrapper>(); readonly manualAnnotationRequested$ = new Subject<ManualRedactionEntryWrapper>();
readonly redactTextRequested$ = new Subject<ManualRedactionEntryWrapper>(); readonly redactTextRequested$ = new Subject<ManualRedactionEntryWrapper>();
@ -70,6 +61,15 @@ export class PdfProxyService {
const isAllowed = this._permissionsService.canPerformAnnotationActions(this._state.file(), this._state.dossier()); const isAllowed = this._permissionsService.canPerformAnnotationActions(this._state.file(), this._state.dossier());
return isAllowed && isStandard; return isAllowed && isStandard;
}); });
readonly #convertPath = inject(UI_ROOT_PATH_FN);
readonly #visibilityOffIcon = this.#convertPath('/assets/icons/general/visibility-off.svg');
readonly #visibilityIcon = this.#convertPath('/assets/icons/general/visibility.svg');
readonly #falsePositiveIcon = this.#convertPath('/assets/icons/general/pdftron-action-false-positive.svg');
readonly #addRedactionIcon = this._iqserPermissionsService.has(Roles.getRss)
? this.#convertPath('/assets/icons/general/pdftron-action-add-annotation.svg')
: this.#convertPath('/assets/icons/general/pdftron-action-add-redaction.svg');
readonly #isDocumine = getConfig().IS_DOCUMINE;
readonly #addHintIcon = this.#convertPath('/assets/icons/general/pdftron-action-add-hint.svg');
constructor( constructor(
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,

View File

@ -5,13 +5,13 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { PdfViewer } from './pdf-viewer.service'; import { PdfViewer } from './pdf-viewer.service';
import { REDDocumentViewer } from './document-viewer.service'; import { REDDocumentViewer } from './document-viewer.service';
import { BASE_HREF_FN } from '@iqser/common-ui/lib/utils'; import { UI_ROOT_PATH_FN } from '@common-ui/utils';
@Injectable() @Injectable()
export class LayersService { export class LayersService {
private readonly _convertPath = inject(BASE_HREF_FN); readonly #convertPath = inject(UI_ROOT_PATH_FN);
readonly #enableIcon = this._convertPath('/assets/icons/general/pdftron-action-disable-layers.svg'); readonly #enableIcon = this.#convertPath('/assets/icons/general/pdftron-action-disable-layers.svg');
readonly #disableIcon = this._convertPath('/assets/icons/general/pdftron-action-enable-layers.svg'); readonly #disableIcon = this.#convertPath('/assets/icons/general/pdftron-action-enable-layers.svg');
private _active = false; private _active = false;

View File

@ -4,7 +4,7 @@ import { ActivatedRoute } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { environment } from '@environments/environment'; import { environment } from '@environments/environment';
import { ErrorService, getConfig } from '@iqser/common-ui'; import { ErrorService, getConfig } from '@iqser/common-ui';
import { BASE_HREF_FN, shareDistinctLast } from '@iqser/common-ui/lib/utils'; import { shareDistinctLast, UI_ROOT_PATH_FN } from '@iqser/common-ui/lib/utils';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer'; import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
import { AppConfig, File, IHeaderElement } from '@red/domain'; import { AppConfig, File, IHeaderElement } from '@red/domain';
@ -16,17 +16,37 @@ import { map, startWith } from 'rxjs/operators';
import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, USELESS_ELEMENTS } from '../utils/constants'; import { DISABLED_HOTKEYS, DOCUMENT_LOADING_ERROR, USELESS_ELEMENTS } from '../utils/constants';
import { asList } from '../utils/functions'; import { asList } from '../utils/functions';
import { Rgb } from '../utils/types'; import { Rgb } from '../utils/types';
import { REDAnnotationManager } from './annotation-manager.service';
import Annotation = Core.Annotations.Annotation; import Annotation = Core.Annotations.Annotation;
import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation; import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation;
import DocumentViewer = Core.DocumentViewer; import DocumentViewer = Core.DocumentViewer;
import Quad = Core.Math.Quad; import Quad = Core.Math.Quad;
import TextTool = Core.Tools.TextTool; import TextTool = Core.Tools.TextTool;
import { REDAnnotationManager } from './annotation-manager.service';
@Injectable() @Injectable()
export class PdfViewer { export class PdfViewer {
readonly #convertPath = inject(BASE_HREF_FN); readonly currentPage$ = inject(ActivatedRoute).queryParamMap.pipe(
map(params => Number(params.get('page') ?? '1')),
shareDistinctLast(),
);
readonly currentPage = toSignal(this.currentPage$);
documentViewer: DocumentViewer;
fileId: string;
dossierId: string;
pageChanged$: Observable<number>;
readonly isCompareMode: Signal<boolean>;
readonly totalPages: Signal<number>;
searchOptions = {
caseSensitive: false, // match case
wholeWord: false, // match whole words only
wildcard: false, // allow using '*' as a wildcard value
regex: false, // string is treated as a regular expression
searchUp: false, // search from the end of the document upwards
ambientString: true, // return ambient string as part of the result
};
selectedText = '';
#instance: WebViewerInstance; #instance: WebViewerInstance;
readonly #convertPath = inject(UI_ROOT_PATH_FN);
readonly #licenseKey = inject(LicenseService).activeLicenseKey; readonly #licenseKey = inject(LicenseService).activeLicenseKey;
readonly #config = getConfig<AppConfig>(); readonly #config = getConfig<AppConfig>();
readonly #isCompareMode = signal(false); readonly #isCompareMode = signal(false);
@ -44,27 +64,6 @@ export class PdfViewer {
}; };
readonly #destroyRef = inject(DestroyRef); readonly #destroyRef = inject(DestroyRef);
readonly #totalPages = signal<number>(0); readonly #totalPages = signal<number>(0);
readonly currentPage$ = inject(ActivatedRoute).queryParamMap.pipe(
map(params => Number(params.get('page') ?? '1')),
shareDistinctLast(),
);
readonly currentPage = toSignal(this.currentPage$);
documentViewer: DocumentViewer;
fileId: string;
dossierId: string;
pageChanged$: Observable<number>;
readonly isCompareMode: Signal<boolean>;
readonly totalPages: Signal<number>;
searchOptions = {
caseSensitive: false, // match case
wholeWord: false, // match whole words only
wildcard: false, // allow using '*' as a wildcard value
regex: false, // string is treated as a regular expression
searchUp: false, // search from the end of the document upwards
ambientString: true, // return ambient string as part of the result
};
selectedText = '';
constructor( constructor(
private readonly _logger: NGXLogger, private readonly _logger: NGXLogger,

View File

@ -1,21 +1,21 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { BASE_HREF_FN } from '@iqser/common-ui/lib/utils';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Core } from '@pdftron/webviewer'; import { Core } from '@pdftron/webviewer';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { HeaderElements } from '../../file-preview/utils/constants'; import { HeaderElements } from '../../file-preview/utils/constants';
import { AnnotationDrawService } from './annotation-draw.service'; import { AnnotationDrawService } from './annotation-draw.service';
import { PdfViewer } from './pdf-viewer.service'; import { PdfViewer } from './pdf-viewer.service';
import { UI_ROOT_PATH_FN } from '@common-ui/utils';
import Annotation = Core.Annotations.Annotation; import Annotation = Core.Annotations.Annotation;
@Injectable() @Injectable()
export class ReadableRedactionsService { export class ReadableRedactionsService {
private readonly _convertPath = inject(BASE_HREF_FN);
readonly #enableIcon = this._convertPath('/assets/icons/general/redaction-preview.svg');
readonly #disableIcon = this._convertPath('/assets/icons/general/redaction-final.svg');
readonly #active$ = new BehaviorSubject<boolean>(true);
readonly active$: Observable<boolean>; readonly active$: Observable<boolean>;
readonly #convertPath = inject(UI_ROOT_PATH_FN);
readonly #enableIcon = this.#convertPath('/assets/icons/general/redaction-preview.svg');
readonly #disableIcon = this.#convertPath('/assets/icons/general/redaction-final.svg');
readonly #active$ = new BehaviorSubject<boolean>(true);
constructor( constructor(
private readonly _pdf: PdfViewer, private readonly _pdf: PdfViewer,

View File

@ -5,13 +5,13 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { PdfViewer } from './pdf-viewer.service'; import { PdfViewer } from './pdf-viewer.service';
import { REDDocumentViewer } from './document-viewer.service'; import { REDDocumentViewer } from './document-viewer.service';
import { BASE_HREF_FN } from '@iqser/common-ui/lib/utils'; import { UI_ROOT_PATH_FN } from '@common-ui/utils';
@Injectable() @Injectable()
export class TooltipsService { export class TooltipsService {
private readonly _convertPath = inject(BASE_HREF_FN); readonly #convertPath = inject(UI_ROOT_PATH_FN);
readonly #enableIcon = this._convertPath('/assets/icons/general/pdftron-action-enable-tooltips.svg'); readonly #enableIcon = this.#convertPath('/assets/icons/general/pdftron-action-enable-tooltips.svg');
readonly #disableIcon = this._convertPath('/assets/icons/general/pdftron-action-disable-tooltips.svg'); readonly #disableIcon = this.#convertPath('/assets/icons/general/pdftron-action-disable-tooltips.svg');
constructor( constructor(
private readonly _pdf: PdfViewer, private readonly _pdf: PdfViewer,

View File

@ -1,6 +1,5 @@
import { inject, Injectable, NgZone } from '@angular/core'; import { inject, Injectable, NgZone } from '@angular/core';
import { getConfig, HelpModeService, IqserPermissionsService, isIqserDevMode } from '@iqser/common-ui'; import { getConfig, HelpModeService, IqserPermissionsService, isIqserDevMode } from '@iqser/common-ui';
import { BASE_HREF_FN } from '@iqser/common-ui/lib/utils';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { IHeaderElement, RotationTypes } from '@red/domain'; import { IHeaderElement, RotationTypes } from '@red/domain';
import { FilesMapService } from '@services/files/files-map.service'; import { FilesMapService } from '@services/files/files-map.service';
@ -16,6 +15,7 @@ import { PageRotationService } from './page-rotation.service';
import { PdfViewer } from './pdf-viewer.service'; import { PdfViewer } from './pdf-viewer.service';
import { ReadableRedactionsService } from './readable-redactions.service'; import { ReadableRedactionsService } from './readable-redactions.service';
import { TooltipsService } from './tooltips.service'; import { TooltipsService } from './tooltips.service';
import { UI_ROOT_PATH_FN } from '@common-ui/utils';
const divider: IHeaderElement = { const divider: IHeaderElement = {
type: 'divider', type: 'divider',
@ -23,7 +23,9 @@ const divider: IHeaderElement = {
@Injectable() @Injectable()
export class ViewerHeaderService { export class ViewerHeaderService {
readonly #convertPath = inject(BASE_HREF_FN); readonly events$: Observable<ViewerEvent>;
toggleLoadAnnotations$: Observable<boolean>;
#convertPath = inject(UI_ROOT_PATH_FN);
readonly #iqserPermissionService = inject(IqserPermissionsService); readonly #iqserPermissionService = inject(IqserPermissionsService);
readonly #isDocumine = getConfig().IS_DOCUMINE; readonly #isDocumine = getConfig().IS_DOCUMINE;
#buttons: Map<HeaderElementType, IHeaderElement>; #buttons: Map<HeaderElementType, IHeaderElement>;
@ -43,8 +45,6 @@ export class ViewerHeaderService {
[HeaderElements.APPLY_ROTATION, false], [HeaderElements.APPLY_ROTATION, false],
[HeaderElements.DISCARD_ROTATION, false], [HeaderElements.DISCARD_ROTATION, false],
]); ]);
readonly events$: Observable<ViewerEvent>;
toggleLoadAnnotations$: Observable<boolean>;
constructor( constructor(
private readonly _filesMapService: FilesMapService, private readonly _filesMapService: FilesMapService,
@ -329,6 +329,10 @@ export class ViewerHeaderService {
this.enable([HeaderElements.COMPARE_BUTTON]); this.enable([HeaderElements.COMPARE_BUTTON]);
} }
resetLayers() {
this._layersService.resetLayers();
}
#closeCompareMode() { #closeCompareMode() {
this._pdf.closeCompareMode(); this._pdf.closeCompareMode();
const { dossierId, fileId } = this._pdf; const { dossierId, fileId } = this._pdf;
@ -377,8 +381,4 @@ export class ViewerHeaderService {
this.disable(ROTATION_ACTION_BUTTONS); this.disable(ROTATION_ACTION_BUTTONS);
} }
} }
resetLayers() {
this._layersService.resetLayers();
}
} }

View File

@ -50,7 +50,7 @@
<div class="cell small-label full-opacity stats-subtitle"> <div class="cell small-label full-opacity stats-subtitle">
<div> <div>
<mat-icon *ngIf="item.archived" svgIcon="red:archive"></mat-icon> <mat-icon *ngIf="item.archived" svgIcon="red:archive"></mat-icon>
<a [routerLink]="routerLink | tenant" iqserStopPropagation> {{ item.dossierName }}</a> <a [routerLink]="routerLink" iqserStopPropagation> {{ item.dossierName }}</a>
</div> </div>
</div> </div>

View File

@ -8,7 +8,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { SearchItemTemplateComponent } from './search-item-template/search-item-template.component'; import { SearchItemTemplateComponent } from './search-item-template/search-item-template.component';
import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { StatusBarComponent } from '@iqser/common-ui/lib/shared'; import { StatusBarComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
const routes = [{ path: '', component: SearchScreenComponent }]; const routes = [{ path: '', component: SearchScreenComponent }];
@ -23,7 +22,6 @@ const routes = [{ path: '', component: SearchScreenComponent }];
IqserListingModule, IqserListingModule,
StatusBarComponent, StatusBarComponent,
StopPropagationDirective, StopPropagationDirective,
TenantPipe,
], ],
}) })
export class SearchModule {} export class SearchModule {}

View File

@ -20,11 +20,7 @@
</div> </div>
<div class="cell"> <div class="cell">
<a <a *ngIf="item.isFile && fileDossier$ | async as fileDossier" [routerLink]="fileDossier.routerLink" class="small-label link-action">
*ngIf="item.isFile && fileDossier$ | async as fileDossier"
[routerLink]="fileDossier.routerLink | tenant"
class="small-label link-action"
>
{{ fileDossier.dossierName }} {{ fileDossier.dossierName }}
</a> </a>
@ -33,13 +29,13 @@
<div class="cell"> <div class="cell">
<span class="small-label"> <span class="small-label">
{{ item.softDeletedTime | date : 'exactDate' }} {{ item.softDeletedTime | date: 'exactDate' }}
</span> </span>
</div> </div>
<div class="cell"> <div class="cell">
<div class="small-label"> <div class="small-label">
{{ item.restoreDate | date : 'timeFromNow' }} {{ item.restoreDate | date: 'timeFromNow' }}
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<iqser-circle-button <iqser-circle-button

View File

@ -8,7 +8,6 @@ import { SharedModule } from '@shared/shared.module';
import { TrashDialogService } from './services/trash-dialog.service'; import { TrashDialogService } from './services/trash-dialog.service';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
const routes = [{ path: '', component: TrashScreenComponent }]; const routes = [{ path: '', component: TrashScreenComponent }];
@ -22,7 +21,6 @@ const routes = [{ path: '', component: TrashScreenComponent }];
TranslateModule, TranslateModule,
IqserListingModule, IqserListingModule,
CircleButtonComponent, CircleButtonComponent,
TenantPipe,
], ],
providers: [TrashDialogService], providers: [TrashDialogService],
}) })

View File

@ -10,8 +10,9 @@ import { UserService } from '@users/user.service';
import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
import { DossiersCacheService } from './dossiers/dossiers-cache.service'; import { DossiersCacheService } from './dossiers/dossiers-cache.service';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { BASE_HREF, List, mapEach } from '@iqser/common-ui/lib/utils'; import { List, mapEach } from '@iqser/common-ui/lib/utils';
import { TenantsService } from '@iqser/common-ui/lib/tenants'; import { TenantsService } from '@iqser/common-ui/lib/tenants';
import { APP_BASE_HREF } from '@angular/common';
const INCLUDE_SEEN = false; const INCLUDE_SEEN = false;
@ -23,13 +24,13 @@ const NOTIFICATIONS_THRESHOLD = 1000;
providedIn: 'root', providedIn: 'root',
}) })
export class NotificationsService extends EntitiesService<INotification, Notification> implements OnDestroy { export class NotificationsService extends EntitiesService<INotification, Notification> implements OnDestroy {
readonly #config = getConfig<AppConfig>();
readonly #subscription = new Subscription();
protected readonly _defaultModelPath = 'notification'; protected readonly _defaultModelPath = 'notification';
protected readonly _entityClass = Notification; protected readonly _entityClass = Notification;
readonly #config = getConfig<AppConfig>();
readonly #subscription = new Subscription();
constructor( constructor(
@Inject(BASE_HREF) private readonly _baseHref: string, @Inject(APP_BASE_HREF) private readonly _baseHref: string,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _tenantsService: TenantsService, private readonly _tenantsService: TenantsService,
private readonly _userService: UserService, private readonly _userService: UserService,
@ -96,7 +97,7 @@ export class NotificationsService extends EntitiesService<INotification, Notific
} }
private _getDossierHref(dossier: Dossier): string { private _getDossierHref(dossier: Dossier): string {
return dossier ? `${this._baseHref}/${this._tenantsService.activeTenantId}${dossier.routerLink}` : null; return dossier ? `${this._baseHref}/${dossier.routerLink}` : null;
} }
private _getUsername(userId: string | undefined) { private _getUsername(userId: string | undefined) {

View File

@ -18,7 +18,7 @@ export class RedRoleGuard extends IqserRoleGuard {
if (!currentUser?.hasAnyRole) { if (!currentUser?.hasAnyRole) {
this._logger.warn('[GUARD] User has no roles, redirect to auth-error'); this._logger.warn('[GUARD] User has no roles, redirect to auth-error');
await this._router.navigate([`/${this._tenantsService.activeTenantId}/auth-error`]); await this._router.navigate(['/auth-error']);
this._loadingService.stop(); this._loadingService.stop();
return false; return false;
} }
@ -26,7 +26,7 @@ export class RedRoleGuard extends IqserRoleGuard {
// we have at least 1 RED Role -> if it's not user he must be an admin // we have at least 1 RED Role -> if it's not user he must be an admin
if (currentUser.isUserAdmin && !currentUser.isAdmin && state.url.includes('admin') && !state.url.includes('users')) { if (currentUser.isUserAdmin && !currentUser.isAdmin && state.url.includes('admin') && !state.url.includes('users')) {
this._logger.warn('[GUARD] Redirect to users page'); this._logger.warn('[GUARD] Redirect to users page');
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main/admin/users`]); await this._router.navigate(['/main/admin/users']);
return false; return false;
} }
@ -36,7 +36,7 @@ export class RedRoleGuard extends IqserRoleGuard {
!currentUser.isUser && !currentUser.isUser &&
!(state.url.includes('/main/admin/users') || state.url.includes('/main/account')) !(state.url.includes('/main/admin/users') || state.url.includes('/main/account'))
) { ) {
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main/admin/users`]); await this._router.navigate(['/main/admin/users']);
return false; return false;
} }
@ -45,10 +45,10 @@ export class RedRoleGuard extends IqserRoleGuard {
return true; return true;
} }
if (!currentUser.isUser) { if (!currentUser.isUser) {
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main/admin`]); await this._router.navigate(['/main/admin']);
} else { } else {
this._logger.warn('[GUARD] Redirect to tenant route'); this._logger.warn('[GUARD] Redirect to tenant route');
await this._router.navigate([`/${this._tenantsService.activeTenantId}`]); await this._router.navigate(['']);
} }
return false; return false;
} }

View File

@ -2,7 +2,6 @@ import { inject } from '@angular/core';
import { Router, RouterStateSnapshot } from '@angular/router'; import { Router, RouterStateSnapshot } from '@angular/router';
import { AsyncGuard, IqserPermissionsService, LoadingService } from '@iqser/common-ui'; import { AsyncGuard, IqserPermissionsService, LoadingService } from '@iqser/common-ui';
import { TenantsService } from '@iqser/common-ui/lib/tenants'; import { TenantsService } from '@iqser/common-ui/lib/tenants';
import { BASE_HREF } from '@iqser/common-ui/lib/utils';
import { ConfigService } from '@services/config.service'; import { ConfigService } from '@services/config.service';
import { DossiersChangesService } from '@services/dossiers/dossier-changes.service'; import { DossiersChangesService } from '@services/dossiers/dossier-changes.service';
import { FeaturesService } from '@services/features.service'; import { FeaturesService } from '@services/features.service';
@ -13,6 +12,7 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { APP_BASE_HREF } from '@angular/common';
async function redirectToLastDossierTemplate( async function redirectToLastDossierTemplate(
router: Router, router: Router,
@ -45,7 +45,7 @@ export function mainGuard(): AsyncGuard {
const tenantsService = inject(TenantsService); const tenantsService = inject(TenantsService);
const loadingService = inject(LoadingService); const loadingService = inject(LoadingService);
const configService = inject(ConfigService); const configService = inject(ConfigService);
const baseHref = inject(BASE_HREF); const baseHref = inject(APP_BASE_HREF);
const generalConfig$ = inject(GeneralSettingsService).getGeneralConfigurations(); const generalConfig$ = inject(GeneralSettingsService).getGeneralConfigurations();
const updatedDisplayName$ = generalConfig$.pipe(tap(config => configService.updateDisplayName(config.displayName))); const updatedDisplayName$ = generalConfig$.pipe(tap(config => configService.updateDisplayName(config.displayName)));

View File

@ -1,7 +1,7 @@
{ {
"ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null, "ADMIN_CONTACT_URL": null,
"API_URL": "https://dan1.iqser.cloud", "API_URL": "https://dan.iqser.cloud",
"APP_NAME": "RedactManager", "APP_NAME": "RedactManager",
"IS_DOCUMINE": false, "IS_DOCUMINE": false,
"RULE_EDITOR_DEV_ONLY": false, "RULE_EDITOR_DEV_ONLY": false,
@ -13,7 +13,7 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3, "MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction", "OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null, "OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dan1.iqser.cloud/auth", "OAUTH_URL": "https://dan.iqser.cloud/auth",
"RECENT_PERIOD_IN_HOURS": 24, "RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural", "SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",

@ -1 +1 @@
Subproject commit 2bb459961af80944c5cd56bac8bff1fc786dbebc Subproject commit d1c0559099be64cb608b87afde6b4ee57de2604f