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",
"polyfills": "apps/red-ui/src/polyfills.ts",
"tsConfig": "apps/red-ui/tsconfig.json",
"baseHref": "/ui/",
"assets": [
"apps/red-ui/src/favicon.ico",
{

View File

@ -205,18 +205,13 @@ const routes: IqserRoutes = [
component: TenantSelectComponent,
},
{
path: ':tenant',
redirectTo: ':tenant/main',
pathMatch: 'full',
},
{
path: ':tenant/main',
path: 'main',
canActivate: [orderedAsyncGuards([ifLoggedIn(), hasAnyRole(), mainGuard()])],
component: BaseScreenComponent,
children: mainRoutes,
},
{
path: ':tenant/auth-error',
path: 'auth-error',
component: AuthErrorComponent,
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 { ENVIRONMENT_INITIALIZER, ErrorHandler, inject, NgModule } from '@angular/core';
import { MatDividerModule } from '@angular/material/divider';
@ -43,7 +43,7 @@ import {
} from '@iqser/common-ui';
import { CommonUiModule } from '@iqser/common-ui/lib/common-ui.module';
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 { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { MissingTranslationHandler } from '@ngx-translate/core';
@ -61,13 +61,14 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { UserService } from '@users/user.service';
import { UI_CACHES } from '@utils/constants';
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 * as helpModeKeys from '../assets/help-mode/help-mode-keys.json';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
import { UI_ROOT } from '@common-ui/utils';
export const appModuleFactory = (config: AppConfig) => {
@NgModule({
@ -183,11 +184,27 @@ export const appModuleFactory = (config: AppConfig) => {
IqserDenyDirective,
IqserListingModule,
IconButtonComponent,
TenantPipe,
MatDividerModule,
ChevronButtonComponent,
],
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,
multi: true,

View File

@ -9,7 +9,7 @@
<redaction-breadcrumbs></redaction-breadcrumbs>
</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">
<iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="iqser:logo"></iqser-logo>
<div class="app-name">{{ titleService.getTitle() }}</div>

View File

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

View File

@ -1,6 +1,6 @@
<div id="user-menu-items">
<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 }}
</a>
</ng-container>

View File

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

View File

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

View File

@ -25,19 +25,23 @@ export function ifLoggedIn(): AsyncGuard {
const keycloakStatusService = inject(KeycloakStatusService);
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 username = queryParams.get('username');
const router = inject(Router);
if (!keycloakInstance) {
if (!tenant) {
logger.error('[ROUTES] No tenant found, something is wrong...');
return inject(Router).navigate(['/']);
return router.navigate(['/']);
}
logger.info('[KEYCLOAK] Keycloak init...');
await keycloakInitializer(tenant);
logger.info('[KEYCLOAK] Keycloak init done!');
console.log({ tenant });
await tenantsService.selectTenant(tenant);
await usersService.initialize();
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 { NGXLogger } from 'ngx-logger';
import { KeycloakService } from 'keycloak-angular';
export function ifNotLoggedIn(): CanActivateFn {
return async (route: ActivatedRouteSnapshot) => {
return async () => {
const logger = inject(NGXLogger);
const router = inject(Router);
const keycloakService = inject(KeycloakService);
@ -16,14 +16,14 @@ export function ifNotLoggedIn(): CanActivateFn {
return true;
}
const tenant = route.paramMap.get('tenant') || keycloakService.getKeycloakInstance().realm;
const tenant = keycloakService.getKeycloakInstance().realm;
if (!tenant) {
logger.error('[ROUTES] Tenant not found in route or keycloak realm');
return false;
}
logger.warn('[ROUTES] Is logged in for ' + tenant + ', redirecting to /' + tenant);
await router.navigate([tenant]);
await router.navigate(['/main']);
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 { DossierTemplateActionsComponent } from './shared/components/dossier-template-actions/dossier-template-actions.component';
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 { PaginationComponent } from '@common-ui/pagination/pagination.component';
@ -124,7 +123,6 @@ const components = [
DetailsRadioComponent,
IqserAllowDirective,
IqserDenyDirective,
TenantPipe,
SelectComponent,
PaginationComponent,
],

View File

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

View File

@ -30,7 +30,7 @@ import { environment } from '@environments/environment';
import { tap } from 'rxjs/operators';
import { watermarkTranslations } from '@translations/watermark-translations';
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';
export const DEFAULT_WATERMARK: Partial<IWatermark> = {
@ -62,13 +62,6 @@ interface WatermarkForm {
styleUrls: ['./watermark-screen.component.scss'],
})
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 translations = watermarkTranslations;
readonly trackBy = trackByFactory();
@ -86,6 +79,13 @@ export class WatermarkScreenComponent implements OnInit {
readonly watermarkHorizontalAlignments = Object.values(WATERMARK_HORIZONTAL_ALIGNMENTS);
readonly watermarkVerticalAlignments = Object.values(WATERMARK_VERTICAL_ALIGNMENTS);
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(
private readonly _http: HttpClient,
@ -222,8 +222,8 @@ export class WatermarkScreenComponent implements OnInit {
this.instance = await WebViewer(
{
licenseKey: this._licenseService.activeLicenseKey,
path: this._convertPath('/assets/wv-resources'),
css: this._convertPath('/assets/pdftron/stylesheet.css'),
path: this.#convertPath('/assets/wv-resources'),
css: this.#convertPath('/assets/pdftron/stylesheet.css'),
fullAPI: true,
isReadOnly: true,
backendType: 'ems',
@ -242,7 +242,7 @@ export class WatermarkScreenComponent implements OnInit {
});
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();

View File

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

View File

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

View File

@ -2,10 +2,10 @@
<ng-container *ngFor="let item of items[type]">
<a
*ngIf="item.show"
[attr.help-mode-key]="item.helpModeKey"
[class.disabled]="isDisabled(item.screen)"
[routerLinkActiveOptions]="{ exact: false }"
[routerLink]="prefix + item.screen | tenant"
[attr.help-mode-key]="item.helpModeKey"
[routerLink]="prefix + item.screen"
class="item"
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 { getConfig, IqserHelpModeModule, IqserPermissionsService, isIqserDevMode } from '@iqser/common-ui';
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 { TranslateModule } from '@ngx-translate/core';
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',
styleUrls: ['./admin-side-nav.component.scss'],
standalone: true,
imports: [TranslateModule, NgIf, IqserHelpModeModule, RouterLink, RouterLinkActive, NgForOf, SideNavComponent, TenantPipe],
imports: [TranslateModule, NgIf, IqserHelpModeModule, RouterLink, RouterLinkActive, NgForOf, SideNavComponent],
})
export class AdminSideNavComponent implements OnInit {
readonly #config = getConfig<AppConfig>();
readonly isIqserDevMode = isIqserDevMode();
@Input() type: AdminSideNavType;
@Input() disabledItems: string[] = [];
@ -34,6 +32,7 @@ export class AdminSideNavComponent implements OnInit {
readonly currentUser = getCurrentUser<User>();
readonly roles = Roles;
prefix: string;
readonly #config = getConfig<AppConfig>();
readonly isDocumine = this.#config.IS_DOCUMINE;
readonly canAccessRulesInDocumine = this.isDocumine && !this.#config.RULE_EDITOR_DEV_ONLY;
readonly items: { readonly [key in AdminSideNavType]: NavItem[] } = {
@ -102,7 +101,10 @@ export class AdminSideNavComponent implements OnInit {
{
screen: 'component-rules',
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',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { computed, effect, inject, Injectable, NgZone } from '@angular/core';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
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 {
ManualRedactionEntryType,
ManualRedactionEntryTypes,
@ -43,15 +43,6 @@ import Quad = Core.Math.Quad;
@Injectable()
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 manualAnnotationRequested$ = 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());
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(
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 { PdfViewer } from './pdf-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()
export class LayersService {
private readonly _convertPath = inject(BASE_HREF_FN);
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 #convertPath = inject(UI_ROOT_PATH_FN);
readonly #enableIcon = this.#convertPath('/assets/icons/general/pdftron-action-disable-layers.svg');
readonly #disableIcon = this.#convertPath('/assets/icons/general/pdftron-action-enable-layers.svg');
private _active = false;

View File

@ -4,7 +4,7 @@ import { ActivatedRoute } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { environment } from '@environments/environment';
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 WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
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 { asList } from '../utils/functions';
import { Rgb } from '../utils/types';
import { REDAnnotationManager } from './annotation-manager.service';
import Annotation = Core.Annotations.Annotation;
import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation;
import DocumentViewer = Core.DocumentViewer;
import Quad = Core.Math.Quad;
import TextTool = Core.Tools.TextTool;
import { REDAnnotationManager } from './annotation-manager.service';
@Injectable()
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;
readonly #convertPath = inject(UI_ROOT_PATH_FN);
readonly #licenseKey = inject(LicenseService).activeLicenseKey;
readonly #config = getConfig<AppConfig>();
readonly #isCompareMode = signal(false);
@ -44,27 +64,6 @@ export class PdfViewer {
};
readonly #destroyRef = inject(DestroyRef);
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(
private readonly _logger: NGXLogger,

View File

@ -1,21 +1,21 @@
import { inject, Injectable } from '@angular/core';
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 { Core } from '@pdftron/webviewer';
import { BehaviorSubject, Observable } from 'rxjs';
import { HeaderElements } from '../../file-preview/utils/constants';
import { AnnotationDrawService } from './annotation-draw.service';
import { PdfViewer } from './pdf-viewer.service';
import { UI_ROOT_PATH_FN } from '@common-ui/utils';
import Annotation = Core.Annotations.Annotation;
@Injectable()
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 #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(
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 { PdfViewer } from './pdf-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()
export class TooltipsService {
private readonly _convertPath = inject(BASE_HREF_FN);
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 #convertPath = inject(UI_ROOT_PATH_FN);
readonly #enableIcon = this.#convertPath('/assets/icons/general/pdftron-action-enable-tooltips.svg');
readonly #disableIcon = this.#convertPath('/assets/icons/general/pdftron-action-disable-tooltips.svg');
constructor(
private readonly _pdf: PdfViewer,

View File

@ -1,6 +1,5 @@
import { inject, Injectable, NgZone } from '@angular/core';
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 { IHeaderElement, RotationTypes } from '@red/domain';
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 { ReadableRedactionsService } from './readable-redactions.service';
import { TooltipsService } from './tooltips.service';
import { UI_ROOT_PATH_FN } from '@common-ui/utils';
const divider: IHeaderElement = {
type: 'divider',
@ -23,7 +23,9 @@ const divider: IHeaderElement = {
@Injectable()
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 #isDocumine = getConfig().IS_DOCUMINE;
#buttons: Map<HeaderElementType, IHeaderElement>;
@ -43,8 +45,6 @@ export class ViewerHeaderService {
[HeaderElements.APPLY_ROTATION, false],
[HeaderElements.DISCARD_ROTATION, false],
]);
readonly events$: Observable<ViewerEvent>;
toggleLoadAnnotations$: Observable<boolean>;
constructor(
private readonly _filesMapService: FilesMapService,
@ -329,6 +329,10 @@ export class ViewerHeaderService {
this.enable([HeaderElements.COMPARE_BUTTON]);
}
resetLayers() {
this._layersService.resetLayers();
}
#closeCompareMode() {
this._pdf.closeCompareMode();
const { dossierId, fileId } = this._pdf;
@ -377,8 +381,4 @@ export class ViewerHeaderService {
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>
<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>

View File

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

View File

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

View File

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

View File

@ -10,8 +10,9 @@ import { UserService } from '@users/user.service';
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
import { DossiersCacheService } from './dossiers/dossiers-cache.service';
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 { APP_BASE_HREF } from '@angular/common';
const INCLUDE_SEEN = false;
@ -23,13 +24,13 @@ const NOTIFICATIONS_THRESHOLD = 1000;
providedIn: 'root',
})
export class NotificationsService extends EntitiesService<INotification, Notification> implements OnDestroy {
readonly #config = getConfig<AppConfig>();
readonly #subscription = new Subscription();
protected readonly _defaultModelPath = 'notification';
protected readonly _entityClass = Notification;
readonly #config = getConfig<AppConfig>();
readonly #subscription = new Subscription();
constructor(
@Inject(BASE_HREF) private readonly _baseHref: string,
@Inject(APP_BASE_HREF) private readonly _baseHref: string,
private readonly _translateService: TranslateService,
private readonly _tenantsService: TenantsService,
private readonly _userService: UserService,
@ -96,7 +97,7 @@ export class NotificationsService extends EntitiesService<INotification, Notific
}
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) {

View File

@ -18,7 +18,7 @@ export class RedRoleGuard extends IqserRoleGuard {
if (!currentUser?.hasAnyRole) {
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();
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
if (currentUser.isUserAdmin && !currentUser.isAdmin && state.url.includes('admin') && !state.url.includes('users')) {
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;
}
@ -36,7 +36,7 @@ export class RedRoleGuard extends IqserRoleGuard {
!currentUser.isUser &&
!(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;
}
@ -45,10 +45,10 @@ export class RedRoleGuard extends IqserRoleGuard {
return true;
}
if (!currentUser.isUser) {
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main/admin`]);
await this._router.navigate(['/main/admin']);
} else {
this._logger.warn('[GUARD] Redirect to tenant route');
await this._router.navigate([`/${this._tenantsService.activeTenantId}`]);
await this._router.navigate(['']);
}
return false;
}

View File

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

View File

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

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