Merge branch 'release/4.839.x' into 'master'
Add websockets to handle annotations & rules See merge request redactmanager/red-ui!753
This commit is contained in:
commit
427a81cff6
@ -4,41 +4,40 @@ variables:
|
||||
PROJECT: red-ui
|
||||
DOCKERFILELOCATION: 'docker/$PROJECT/Dockerfile'
|
||||
|
||||
|
||||
include:
|
||||
- project: 'gitlab/gitlab'
|
||||
ref: 'main'
|
||||
file: 'ci-templates/docker_build_nexus_v2.yml'
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE != "schedule"
|
||||
- if: $CI_PIPELINE_SOURCE != "schedule"
|
||||
|
||||
localazy update:
|
||||
image: node:20.5
|
||||
cache:
|
||||
- key:
|
||||
files:
|
||||
- yarn.lock
|
||||
paths:
|
||||
- .yarn-cache/
|
||||
script:
|
||||
# - git config user.email "${CI_EMAIL}"
|
||||
# - git config user.name "${CI_USERNAME}"
|
||||
# - git remote add gitlab_origin https://${CI_USERNAME}:${CI_ACCESS_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
|
||||
- git push https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
|
||||
- cd tools/localazy
|
||||
- yarn install --cache-folder .yarn-cache
|
||||
- yarn start
|
||||
- cd ../..
|
||||
- git add .
|
||||
- |-
|
||||
CHANGES=$(git status --porcelain | wc -l)
|
||||
if [ "$CHANGES" -gt "0" ]
|
||||
then
|
||||
git status
|
||||
git commit -m "push back localazy update"
|
||||
git push gitlab_origin HEAD:${CI_COMMIT_REF_NAME}
|
||||
# git push https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
|
||||
# git push
|
||||
fi
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
image: node:20.5
|
||||
cache:
|
||||
- key:
|
||||
files:
|
||||
- yarn.lock
|
||||
paths:
|
||||
- .yarn-cache/
|
||||
script:
|
||||
# - git config user.email "${CI_EMAIL}"
|
||||
# - git config user.name "${CI_USERNAME}"
|
||||
# - git remote add gitlab_origin https://${CI_USERNAME}:${CI_ACCESS_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
|
||||
- git push https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
|
||||
- cd tools/localazy
|
||||
- yarn install --cache-folder .yarn-cache
|
||||
- yarn start
|
||||
- cd ../..
|
||||
- git add .
|
||||
- |-
|
||||
CHANGES=$(git status --porcelain | wc -l)
|
||||
if [ "$CHANGES" -gt "0" ]
|
||||
then
|
||||
git status
|
||||
git commit -m "push back localazy update"
|
||||
git push gitlab_origin HEAD:${CI_COMMIT_REF_NAME}
|
||||
# git push https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
|
||||
# git push
|
||||
fi
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
|
||||
@ -135,20 +135,20 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
features: {
|
||||
ANNOTATIONS: {
|
||||
color: 'aqua',
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
level: NgxLoggerLevel.DEBUG,
|
||||
},
|
||||
FILTERS: {
|
||||
enabled: false,
|
||||
},
|
||||
TENANTS: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
},
|
||||
ROUTES: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
},
|
||||
PDF: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
},
|
||||
FILE: {
|
||||
enabled: false,
|
||||
@ -171,6 +171,9 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
DOSSIERS_CHANGES: {
|
||||
enabled: false,
|
||||
},
|
||||
GUARDS: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
} as ILoggerConfig,
|
||||
},
|
||||
|
||||
@ -40,8 +40,7 @@ export function ifLoggedIn(): AsyncGuard {
|
||||
|
||||
logger.info('[KEYCLOAK] Keycloak init...');
|
||||
await keycloakInitializer(tenant);
|
||||
logger.info('[KEYCLOAK] Keycloak init done!');
|
||||
console.log({ tenant });
|
||||
logger.info('[KEYCLOAK] Keycloak init done for tenant: ', { tenant });
|
||||
await tenantsService.selectTenant(tenant);
|
||||
await usersService.initialize();
|
||||
await licenseService.loadLicenses();
|
||||
@ -51,6 +50,7 @@ export function ifLoggedIn(): AsyncGuard {
|
||||
const jwtToken = jwtDecode(token) as JwtToken;
|
||||
const authTime = (jwtToken.auth_time || jwtToken.iat).toString();
|
||||
localStorage.setItem('authTime', authTime);
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
|
||||
import { RedRoleGuard } from '@users/red-role.guard';
|
||||
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
|
||||
import { inject, provideEnvironmentInitializer } from '@angular/core';
|
||||
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
||||
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
|
||||
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
|
||||
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
|
||||
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
|
||||
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
|
||||
import { templateExistsWhenEnteringAdmin } from '@guards/dossier-template-exists.guard';
|
||||
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
||||
import { entityExistsGuard } from '@guards/entity-exists-guard.service';
|
||||
import { PermissionsGuard } from '@guards/permissions-guard';
|
||||
import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
|
||||
import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
|
||||
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
||||
import { CopilotService } from '@services/copilot.service';
|
||||
import { RedRoleGuard } from '@users/red-role.guard';
|
||||
import { Roles } from '@users/roles';
|
||||
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
|
||||
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
|
||||
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
||||
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
||||
import { templateExistsWhenEnteringAdmin } from '@guards/dossier-template-exists.guard';
|
||||
import { entityExistsGuard } from '@guards/entity-exists-guard.service';
|
||||
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
|
||||
import { PermissionsGuard } from '@guards/permissions-guard';
|
||||
import { Roles } from '@users/roles';
|
||||
import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
|
||||
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
|
||||
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
|
||||
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
|
||||
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
|
||||
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
|
||||
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
|
||||
import { AdminDialogService } from './services/admin-dialog.service';
|
||||
import { AuditService } from './services/audit.service';
|
||||
import { DigitalSignatureService } from './services/digital-signature.service';
|
||||
@ -78,7 +80,12 @@ const dossierTemplateIdRoutes: IqserRoutes = [
|
||||
},
|
||||
type: 'ENTITY',
|
||||
},
|
||||
providers: [RulesService],
|
||||
providers: [
|
||||
RulesService,
|
||||
provideEnvironmentInitializer(() => {
|
||||
return inject(CopilotService).connectAsync('/api/llm/llm-websocket');
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'component-rules',
|
||||
|
||||
@ -1,8 +1,57 @@
|
||||
<div class="header-container">
|
||||
<div [translate]="translations[this.type]['title']" class="heading-l"></div>
|
||||
<div [translate]="translations[type()]['title']" class="heading-l"></div>
|
||||
</div>
|
||||
|
||||
<ngx-monaco-editor (init)="onCodeEditorInit($event)" [(ngModel)]="codeEditorText" [options]="editorOptions"></ngx-monaco-editor>
|
||||
<div class="flex" style="height: 100%">
|
||||
<ngx-monaco-editor (init)="onCodeEditorInit($event)" [(ngModel)]="codeEditorText" [options]="editorOptions"></ngx-monaco-editor>
|
||||
|
||||
<div [class.collapsed]="collapsed()" class="right-container flex-column">
|
||||
<div class="collapsed-wrapper">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="collapsible; context: { action: 'expand', tooltip: ('copilot.label' | translate) }"
|
||||
></ng-container>
|
||||
<div class="all-caps-label" translate="copilot.label"></div>
|
||||
</div>
|
||||
|
||||
<div class="header-wrapper flex mt-8">
|
||||
<div class="heading-xl flex-1">{{ 'copilot.label' | translate | titlecase }}</div>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="collapsible; context: { action: 'collapse', tooltip: ('copilot.label' | translate) }"
|
||||
></ng-container>
|
||||
</div>
|
||||
|
||||
<div class="mt-24">
|
||||
@for (comment of conversation(); track comment) {
|
||||
<div class="comment">
|
||||
<div class="comment-details-wrapper">
|
||||
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
|
||||
{{ comment.date | date: 'sophisticatedDate' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre class="text-auto">{{ comment.text }}</pre>
|
||||
</div>
|
||||
}
|
||||
|
||||
<iqser-input-with-action
|
||||
(action)="add($event)"
|
||||
[placeholder]="'comments.add-comment' | translate"
|
||||
autocomplete="off"
|
||||
icon="iqser:collapse"
|
||||
width="full"
|
||||
></iqser-input-with-action>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #collapsible let-action="action" let-tooltip="tooltip">
|
||||
<iqser-circle-button
|
||||
(action)="toggleCollapse()"
|
||||
[icon]="'iqser:' + action"
|
||||
[tooltipPosition]="IqserTooltipPositions.before"
|
||||
[tooltip]="tooltip"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="changed && permissionsService.canEditRules() && !isLeaving" class="changes-box">
|
||||
<div (click)="goToErrors()" *ngIf="numberOfErrors() || numberOfWarnings()" class="errors">
|
||||
@ -12,15 +61,15 @@
|
||||
<span
|
||||
*ngIf="numberOfErrors()"
|
||||
[translateParams]="{ errors: numberOfErrors() }"
|
||||
[translate]="translations[this.type]['errors-found']"
|
||||
[translate]="translations[type()]['errors-found']"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
*ngIf="numberOfWarnings()"
|
||||
[translateParams]="{ warnings: numberOfWarnings() }"
|
||||
[translate]="translations[this.type]['warnings-found']"
|
||||
class="warning"
|
||||
[class.only-warning]="!numberOfErrors()"
|
||||
[translateParams]="{ warnings: numberOfWarnings() }"
|
||||
[translate]="translations[type()]['warnings-found']"
|
||||
class="warning"
|
||||
></span>
|
||||
</div>
|
||||
</span>
|
||||
@ -29,11 +78,11 @@
|
||||
<div class="actions">
|
||||
<iqser-icon-button
|
||||
(action)="save()"
|
||||
[label]="translations[this.type]['save-changes'] | translate"
|
||||
[label]="translations[type()]['save-changes'] | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="iqser:check"
|
||||
></iqser-icon-button>
|
||||
|
||||
<div (click)="revert()" [translate]="translations[this.type]['revert-changes']" class="all-caps-label cancel"></div>
|
||||
<div (click)="revert()" [translate]="translations[type()]['revert-changes']" class="all-caps-label cancel"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -81,3 +81,22 @@ ngx-monaco-editor {
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.right-container {
|
||||
display: flex;
|
||||
width: 750px;
|
||||
min-width: 375px;
|
||||
padding: 16px 24px 16px 24px;
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
padding-right: 13px;
|
||||
}
|
||||
|
||||
redaction-dossier-details {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.text-auto {
|
||||
text-wrap: auto;
|
||||
}
|
||||
|
||||
@ -1,22 +1,40 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, OnInit, signal } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { IconButtonComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { RulesService } from '../../../services/rules.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DOSSIER_TEMPLATE_ID, DroolsKeywords, IRules } from '@red/domain';
|
||||
import { EditorThemeService } from '@services/editor-theme.service';
|
||||
import { NgIf, NgTemplateOutlet, TitleCasePipe } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
computed,
|
||||
DestroyRef,
|
||||
inject,
|
||||
input,
|
||||
OnInit,
|
||||
signal,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { InputWithActionComponent } from '@common-ui/inputs/input-with-action/input-with-action.component';
|
||||
import { TenantsService } from '@common-ui/tenants';
|
||||
import { getCurrentUser } from '@common-ui/users';
|
||||
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
|
||||
import { Debounce, getParam } from '@iqser/common-ui/lib/utils';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { Debounce, IqserTooltipPositions } from '@iqser/common-ui/lib/utils';
|
||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DroolsKeywords, IRules } from '@red/domain';
|
||||
import { CopilotService } from '@services/copilot.service';
|
||||
import { EditorThemeService } from '@services/editor-theme.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DatePipe } from '@shared/pipes/date.pipe';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { RulesService } from '../../../services/rules.service';
|
||||
import { rulesScreenTranslations } from '../../../translations/rules-screen-translations';
|
||||
import ICodeEditor = monaco.editor.ICodeEditor;
|
||||
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
|
||||
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
|
||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
interface SyntaxError {
|
||||
line: number;
|
||||
@ -31,18 +49,57 @@ interface UploadResponse {
|
||||
deprecatedWarnings: SyntaxError[];
|
||||
}
|
||||
|
||||
export const SentenceTypes = {
|
||||
question: 'question',
|
||||
answer: 'answer',
|
||||
} as const;
|
||||
|
||||
export type SentenceType = keyof typeof SentenceTypes;
|
||||
|
||||
interface Sentence {
|
||||
text: string | null;
|
||||
date: string;
|
||||
type: SentenceType;
|
||||
}
|
||||
|
||||
const endingSentence: Sentence = { text: null, date: new Date().toISOString(), type: SentenceTypes.answer };
|
||||
|
||||
const RULE_VALIDATION_TIMEOUT = 2000;
|
||||
|
||||
@Component({
|
||||
templateUrl: './rules-screen.component.html',
|
||||
styleUrls: ['./rules-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [MonacoEditorModule, MatIcon, FormsModule, IconButtonComponent, NgIf, TranslateModule],
|
||||
imports: [
|
||||
MonacoEditorModule,
|
||||
MatIcon,
|
||||
FormsModule,
|
||||
IconButtonComponent,
|
||||
NgIf,
|
||||
TranslateModule,
|
||||
CircleButtonComponent,
|
||||
InputWithActionComponent,
|
||||
MatTooltip,
|
||||
NgTemplateOutlet,
|
||||
DatePipe,
|
||||
TitleCasePipe,
|
||||
],
|
||||
})
|
||||
export default class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
|
||||
readonly #errorGlyphs = signal<string[]>([]);
|
||||
#codeEditor: ICodeEditor;
|
||||
#decorations: string[] = [];
|
||||
readonly #errors = signal<SyntaxError[]>([]);
|
||||
#ruleValidationTimeout: number = null;
|
||||
readonly #copilotService = inject(CopilotService);
|
||||
readonly #currentUser = getCurrentUser();
|
||||
readonly #conversation = signal<Sentence[]>([endingSentence]);
|
||||
protected readonly collapsed = signal(true);
|
||||
protected readonly IqserTooltipPositions = IqserTooltipPositions;
|
||||
readonly dossierTemplateId = input.required<string>();
|
||||
readonly translations = rulesScreenTranslations;
|
||||
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly inputWithAction = viewChild(InputWithActionComponent);
|
||||
readonly editorOptions: IStandaloneEditorConstructionOptions = {
|
||||
theme: 'vs',
|
||||
language: 'java',
|
||||
@ -54,19 +111,43 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
|
||||
initialLines: string[] = [];
|
||||
currentLines: string[] = [];
|
||||
isLeaving = false;
|
||||
readonly type: IRules['ruleFileType'];
|
||||
readonly #errorGlyphs = signal<string[]>([]);
|
||||
readonly type = input.required<IRules['ruleFileType']>();
|
||||
readonly numberOfErrors = computed(() => this.#errors().filter(e => !e.warning).length);
|
||||
readonly numberOfWarnings = computed(() => this.#errors().filter(e => e.warning).length);
|
||||
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
||||
#codeEditor: ICodeEditor;
|
||||
#decorations: string[] = [];
|
||||
#errors = signal<SyntaxError[]>([]);
|
||||
#ruleValidationTimeout: number = null;
|
||||
readonly conversation = computed(() => this.#conversation().filter(r => !!r.text));
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _rulesService: RulesService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _editorThemeService: EditorThemeService,
|
||||
) {
|
||||
const username = this.#currentUser.id;
|
||||
const tenant = inject(TenantsService).activeTenantId;
|
||||
inject(DestroyRef).onDestroy(() => this.#copilotService.deactivate());
|
||||
this.#copilotService
|
||||
.listen<{ token?: string }>('/user/' + username + '/queue/' + tenant + '/rules-copilot')
|
||||
.pipe(
|
||||
takeUntilDestroyed(),
|
||||
map(res => res?.token),
|
||||
)
|
||||
.subscribe(token => {
|
||||
if (token === null) {
|
||||
this.#conversation.update(responses => [...responses, { ...endingSentence }]);
|
||||
return;
|
||||
}
|
||||
this.#conversation.update(responses => {
|
||||
const last = responses.pop();
|
||||
return [...responses, { ...last, text: (last.text ?? '') + token }];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
set isLeavingPage(isLeaving: boolean) {
|
||||
this.isLeaving = isLeaving;
|
||||
this._changeDetectorRef.detectChanges();
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
@ -84,16 +165,23 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
|
||||
this.#closeProblemsView();
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _rulesService: RulesService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _editorThemeService: EditorThemeService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
this.type = this._route.snapshot.data.type;
|
||||
toggleCollapse() {
|
||||
this.collapsed.update(collapsed => !collapsed);
|
||||
if (this.#conversation().length === 1) {
|
||||
this.#copilotService.send('Hello!');
|
||||
}
|
||||
}
|
||||
|
||||
add(question: string) {
|
||||
this.#conversation.update(responses => {
|
||||
const last = responses.pop();
|
||||
last.text = question;
|
||||
last.type = SentenceTypes.question;
|
||||
last.date = new Date().toISOString();
|
||||
return [...responses, last, { ...endingSentence }];
|
||||
});
|
||||
this.inputWithAction().reset();
|
||||
this.#copilotService.send(question);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -121,7 +209,7 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
|
||||
}
|
||||
(window as any).monaco.editor.setTheme(this._editorThemeService.getTheme(true));
|
||||
await this.#configureSyntaxHighlighting();
|
||||
this._changeDetectorRef.detectChanges();
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
@Debounce()
|
||||
@ -141,12 +229,20 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
|
||||
await this.#uploadRules();
|
||||
}
|
||||
|
||||
revert(): void {
|
||||
this.currentLines = this.initialLines;
|
||||
this.#decorations = this.#codeEditor?.deltaDecorations(this.#decorations, []) || [];
|
||||
this.#removeErrorMarkers();
|
||||
this._changeDetectorRef.markForCheck();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
async #uploadRules(dryRun = false) {
|
||||
return firstValueFrom(
|
||||
this._rulesService.uploadRules({
|
||||
rules: this.#getValue(),
|
||||
dossierTemplateId: this.#dossierTemplateId,
|
||||
ruleFileType: this.type,
|
||||
dossierTemplateId: this.dossierTemplateId(),
|
||||
ruleFileType: this.type(),
|
||||
dryRun,
|
||||
}),
|
||||
).then(
|
||||
@ -155,7 +251,7 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
|
||||
this.#drawErrorMarkers(errors);
|
||||
if (!dryRun) {
|
||||
await this.#initialize();
|
||||
this._toaster.success(rulesScreenTranslations[this.type]['success.generic']);
|
||||
this._toaster.success(rulesScreenTranslations[this.type()]['success.generic']);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
@ -172,20 +268,12 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
|
||||
this.#drawErrorMarkers(errors);
|
||||
this._loadingService.stop();
|
||||
if (!dryRun) {
|
||||
this._toaster.error(rulesScreenTranslations[this.type]['error.generic']);
|
||||
this._toaster.error(rulesScreenTranslations[this.type()]['error.generic']);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
revert(): void {
|
||||
this.currentLines = this.initialLines;
|
||||
this.#decorations = this.#codeEditor?.deltaDecorations(this.#decorations, []) || [];
|
||||
this.#removeErrorMarkers();
|
||||
this._changeDetectorRef.detectChanges();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
#mapErrors(response: UploadResponse, dryRun = false) {
|
||||
const warnings = response.deprecatedWarnings.map(w => ({ ...w, warning: true }));
|
||||
if (dryRun) {
|
||||
@ -295,7 +383,7 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
|
||||
|
||||
async #initialize() {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._rulesService.download(this.#dossierTemplateId, this.type)).then(
|
||||
await firstValueFrom(this._rulesService.download(this.dossierTemplateId(), this.type())).then(
|
||||
rules => {
|
||||
this.currentLines = this.initialLines = rules.rules.split('\n');
|
||||
this.revert();
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { Component, ElementRef, HostListener, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import {
|
||||
@ -16,6 +17,7 @@ import {
|
||||
} from '@iqser/common-ui';
|
||||
import { NestedFilter } from '@iqser/common-ui/lib/filtering';
|
||||
import { getParam, OnAttach, OnDetach, shareLast } from '@iqser/common-ui/lib/utils';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import {
|
||||
Dossier,
|
||||
DOSSIER_ID,
|
||||
@ -26,6 +28,7 @@ import {
|
||||
WorkflowFileStatus,
|
||||
} from '@red/domain';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
import { DossierAttributesService } from '@services/entity-services/dossier-attributes.service';
|
||||
import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider';
|
||||
@ -33,6 +36,7 @@ import { FileAttributesService } from '@services/entity-services/file-attributes
|
||||
import { FilesMapService } from '@services/files/files-map.service';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { TypeFilterComponent } from '@shared/components/type-filter/type-filter.component';
|
||||
import { FileUploadModel } from '@upload-download/model/file-upload.model';
|
||||
import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service';
|
||||
import { FileUploadService } from '@upload-download/services/file-upload.service';
|
||||
@ -42,17 +46,13 @@ import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { convertFiles, Files, handleFileDrop } from '@utils/index';
|
||||
import { merge, Observable } from 'rxjs';
|
||||
import { filter, map, skip, switchMap, tap } from 'rxjs/operators';
|
||||
import { DossierOverviewBulkActionsComponent } from '../components/bulk-actions/dossier-overview-bulk-actions.component';
|
||||
import { DossierDetailsComponent } from '../components/dossier-details/dossier-details.component';
|
||||
import { DossierOverviewScreenHeaderComponent } from '../components/screen-header/dossier-overview-screen-header.component';
|
||||
import { TableItemComponent } from '../components/table-item/table-item.component';
|
||||
import { WorkflowItemComponent } from '../components/workflow-item/workflow-item.component';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { BulkActionsService } from '../services/bulk-actions.service';
|
||||
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { DossierOverviewScreenHeaderComponent } from '../components/screen-header/dossier-overview-screen-header.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { WorkflowItemComponent } from '../components/workflow-item/workflow-item.component';
|
||||
import { DossierDetailsComponent } from '../components/dossier-details/dossier-details.component';
|
||||
import { DossierOverviewBulkActionsComponent } from '../components/bulk-actions/dossier-overview-bulk-actions.component';
|
||||
import { TableItemComponent } from '../components/table-item/table-item.component';
|
||||
import { TypeFilterComponent } from '@shared/components/type-filter/type-filter.component';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-overview-screen.component.html',
|
||||
|
||||
@ -83,14 +83,15 @@
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.undoDirectAction(annotations())"
|
||||
*allow="roles.redactions.deleteManual; if: annotationPermissions().canUndo"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.undo' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:undo"
|
||||
></iqser-circle-button>
|
||||
<ng-template [allow]="roles.redactions.deleteManual" [allowIf]="annotationPermissions().canUndo">
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.undoDirectAction(annotations())"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.undo' | translate"
|
||||
[type]="buttonType"
|
||||
icon="red:undo"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationReferencesService.show(annotations()[0])"
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { Component, computed, input, Input, untracked } from '@angular/core';
|
||||
import { CircleButtonComponent, getConfig, HelpModeService, IqserAllowDirective, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Roles } from '@users/roles';
|
||||
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
|
||||
@ -9,10 +11,8 @@ import { AnnotationActionsService } from '../../services/annotation-actions.serv
|
||||
import { AnnotationReferencesService } from '../../services/annotation-references.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { ViewModeService } from '../../services/view-mode.service';
|
||||
import { SkippedService } from '../../services/skipped.service';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ViewModeService } from '../../services/view-mode.service';
|
||||
|
||||
export const AnnotationButtonTypes = {
|
||||
default: 'default',
|
||||
@ -28,6 +28,7 @@ export type AnnotationButtonType = keyof typeof AnnotationButtonTypes;
|
||||
imports: [CircleButtonComponent, NgIf, TranslateModule, AsyncPipe, IqserAllowDirective],
|
||||
})
|
||||
export class AnnotationActionsComponent {
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
@Input() buttonType: AnnotationButtonType = AnnotationButtonTypes.default;
|
||||
@Input() tooltipPosition: 'before' | 'above' = 'before';
|
||||
@Input() canPerformAnnotationActions: boolean;
|
||||
@ -67,6 +68,9 @@ export class AnnotationActionsComponent {
|
||||
readonly hideSkipped = computed(() => this.skippedService.hideSkipped() && this.annotations().some(a => a.isSkipped));
|
||||
readonly isImageHint = computed(() => this.annotations().every(a => a.IMAGE_HINT));
|
||||
readonly isImage = computed(() => this.annotations().reduce((acc, a) => acc && a.isImage, true));
|
||||
readonly annotationChangesAllowed = computed(
|
||||
() => (!this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis) && !this.somePending(),
|
||||
);
|
||||
readonly canRemoveRedaction = computed(
|
||||
() => this.annotationChangesAllowed() && this.annotationPermissions().canRemoveRedaction && this.sameType(),
|
||||
);
|
||||
@ -75,10 +79,6 @@ export class AnnotationActionsComponent {
|
||||
readonly canAcceptRecommendation = computed(
|
||||
() => this.annotationChangesAllowed() && this.annotationPermissions().canAcceptRecommendation,
|
||||
);
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly annotationChangesAllowed = computed(
|
||||
() => (!this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis) && !this.somePending(),
|
||||
);
|
||||
readonly canResize = computed(
|
||||
() => this.annotationChangesAllowed() && this.annotationPermissions().canResizeAnnotation && this.annotations().length === 1,
|
||||
);
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { Component, computed, ElementRef, input, output } from '@angular/core';
|
||||
import { getConfig, HasScrollbarDirective } from '@iqser/common-ui';
|
||||
import { FilterService } from '@iqser/common-ui/lib/filtering';
|
||||
@ -5,25 +7,24 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { ListItem } from '@models/file/list-item';
|
||||
import { EarmarkGroup } from '@red/domain';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { isTargetInput } from '@utils/functions';
|
||||
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
|
||||
import { AnnotationReferencesService } from '../../services/annotation-references.service';
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { ViewModeService } from '../../services/view-mode.service';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { HighlightsSeparatorComponent } from '../highlights-separator/highlights-separator.component';
|
||||
import { AnnotationWrapperComponent } from '../annotation-wrapper/annotation-wrapper.component';
|
||||
import { AnnotationReferencesListComponent } from '../annotation-references-list/annotation-references-list.component';
|
||||
import { Clipboard } from '@angular/cdk/clipboard';
|
||||
import { isTargetInput } from '@utils/functions';
|
||||
import { AnnotationWrapperComponent } from '../annotation-wrapper/annotation-wrapper.component';
|
||||
import { HighlightsSeparatorComponent } from '../highlights-separator/highlights-separator.component';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotations-list',
|
||||
templateUrl: './annotations-list.component.html',
|
||||
styleUrls: ['./annotations-list.component.scss'],
|
||||
imports: [NgIf, HighlightsSeparatorComponent, AnnotationWrapperComponent, AnnotationReferencesListComponent],
|
||||
imports: [NgIf, AnnotationReferencesListComponent, HighlightsSeparatorComponent, AnnotationWrapperComponent],
|
||||
})
|
||||
export class AnnotationsListComponent extends HasScrollbarDirective {
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly annotations = input.required<ListItem<AnnotationWrapper>[]>();
|
||||
readonly pagesPanelActive = output<boolean>();
|
||||
readonly displayedHighlightGroups = computed(() => {
|
||||
@ -43,7 +44,6 @@ export class AnnotationsListComponent extends HasScrollbarDirective {
|
||||
|
||||
return result;
|
||||
});
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
|
||||
constructor(
|
||||
protected readonly _elementRef: ElementRef,
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="fileAssignService.assignToMe([file])"
|
||||
(action)="assignToMe(file)"
|
||||
*ngIf="_canAssignToSelf()"
|
||||
[icon]="'red:assign-me'"
|
||||
[tooltip]="'file-preview.assign-me' | translate"
|
||||
|
||||
@ -1,21 +1,22 @@
|
||||
import { Component, computed, HostListener, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { Component, computed, HostListener, inject, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { StatusBarComponent } from '@common-ui/shared';
|
||||
import { Bind } from '@common-ui/utils';
|
||||
import { CircleButtonComponent, LoadingService, StopPropagationDirective, Toaster } from '@iqser/common-ui';
|
||||
import { getCurrentUser, InitialsAvatarComponent } from '@iqser/common-ui/lib/users';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { File, User } from '@red/domain';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { AssignUserDropdownComponent } from '@shared/components/assign-user-dropdown/assign-user-dropdown.component';
|
||||
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
|
||||
import { UserService } from '@users/user.service';
|
||||
import { moveElementInArray } from '@utils/functions';
|
||||
import { FileAssignService } from '../../../shared-dossiers/services/file-assign.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
|
||||
import { Bind } from '@common-ui/utils';
|
||||
import { AssignUserDropdownComponent } from '@shared/components/assign-user-dropdown/assign-user-dropdown.component';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { StatusBarComponent } from '@common-ui/shared';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FileAssignService } from '../../../shared-dossiers/services/file-assign.service';
|
||||
import { FileDataService } from '../../services/file-data.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-user-management',
|
||||
@ -48,6 +49,7 @@ export class UserManagementComponent implements OnInit, OnDestroy {
|
||||
: this.#customSort([...dossier.memberIds, ...unassignUser]);
|
||||
});
|
||||
protected readonly _currentUserId = getCurrentUser().id;
|
||||
protected readonly fileDataService = inject(FileDataService);
|
||||
readonly translations = workflowFileStatusTranslations;
|
||||
readonly statusBarConfig = computed(() => [{ length: 1, color: this.state.file().workflowStatus }]);
|
||||
readonly assignTooltip = computed(() => {
|
||||
@ -71,6 +73,13 @@ export class UserManagementComponent implements OnInit, OnDestroy {
|
||||
readonly ngZone: NgZone,
|
||||
) {}
|
||||
|
||||
async assignToMe(file: File) {
|
||||
await this.fileAssignService.assignToMe([file]);
|
||||
//TODO: check which one to call
|
||||
// await firstValueFrom(this.fileDataService.updateAnnotations(file, file.numberOfAnalyses));
|
||||
await this.fileDataService.loadEntityLog();
|
||||
}
|
||||
|
||||
async assignReviewer(file: File, user: User | string) {
|
||||
const assigneeId = typeof user === 'string' ? user : user?.id;
|
||||
|
||||
@ -84,6 +93,11 @@ export class UserManagementComponent implements OnInit, OnDestroy {
|
||||
await this.filesService.setUnderApproval(file, assigneeId);
|
||||
}
|
||||
|
||||
if (assigneeId === this._currentUserId) {
|
||||
// await firstValueFrom(this.fileDataService.updateAnnotations(file, file.numberOfAnalyses));
|
||||
await this.fileDataService.loadEntityLog();
|
||||
}
|
||||
|
||||
this.loadingService.stop();
|
||||
|
||||
const translateParams = { reviewerName: this.userService.getName(assigneeId), filename: file.filename };
|
||||
|
||||
@ -6,6 +6,7 @@ import { AnnotationActionsService } from './services/annotation-actions.service'
|
||||
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
||||
import { AnnotationReferencesService } from './services/annotation-references.service';
|
||||
import { AnnotationsListingService } from './services/annotations-listing.service';
|
||||
import { ComponentLogFilterService } from './services/component-log-filter.service';
|
||||
import { DocumentInfoService } from './services/document-info.service';
|
||||
import { ExcludedPagesService } from './services/excluded-pages.service';
|
||||
import { FileDataService } from './services/file-data.service';
|
||||
@ -16,7 +17,6 @@ import { PdfProxyService } from './services/pdf-proxy.service';
|
||||
import { SkippedService } from './services/skipped.service';
|
||||
import { StampService } from './services/stamp.service';
|
||||
import { ViewModeService } from './services/view-mode.service';
|
||||
import { ComponentLogFilterService } from './services/component-log-filter.service';
|
||||
|
||||
export const filePreviewScreenProviders = [
|
||||
FilterService,
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
|
||||
import { CdkDrag, CdkDragEnd, CdkDragMove, CdkDragStart } from '@angular/cdk/drag-drop';
|
||||
import { NgIf } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
effect,
|
||||
ElementRef,
|
||||
inject,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
@ -13,6 +15,7 @@ import {
|
||||
viewChild,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
|
||||
import {
|
||||
@ -29,21 +32,25 @@ import {
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { copyLocalStorageFiltersValues, FilterService, INestedFilter, NestedFilter, processFilters } from '@iqser/common-ui/lib/filtering';
|
||||
import { AutoUnsubscribe, Bind, bool, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
|
||||
import { AutoUnsubscribe, Bind, bool, List, log, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { File, ViewModes } from '@red/domain';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AnalyseStatuses, AnalysisEvent, File, ViewModes, WsTopics } from '@red/domain';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
import { ComponentLogService } from '@services/entity-services/component-log.service';
|
||||
import { FilesMapService } from '@services/files/files-map.service';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { ReanalysisService } from '@services/reanalysis.service';
|
||||
import { WebSocketService } from '@services/web-socket.service';
|
||||
import { TypeFilterComponent } from '@shared/components/type-filter/type-filter.component';
|
||||
import { Roles } from '@users/roles';
|
||||
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { combineLatest, first, firstValueFrom, Observable, of, pairwise } from 'rxjs';
|
||||
import { combineLatest, first, firstValueFrom, Observable, of, pairwise, Subscription } from 'rxjs';
|
||||
import { catchError, filter, map, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { byId, byPage, handleFilterDelta, hasChanges } from '../../utils';
|
||||
import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.service';
|
||||
@ -54,33 +61,28 @@ import { PdfViewer } from '../pdf-viewer/services/pdf-viewer.service';
|
||||
import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service';
|
||||
import { ViewerHeaderService } from '../pdf-viewer/services/viewer-header.service';
|
||||
import { ROTATION_ACTION_BUTTONS, ViewerEvents } from '../pdf-viewer/utils/constants';
|
||||
import { FileHeaderComponent } from './components/file-header/file-header.component';
|
||||
import { FilePreviewRightContainerComponent } from './components/right-container/file-preview-right-container.component';
|
||||
import { StructuredComponentManagementComponent } from './components/structured-component-management/structured-component-management.component';
|
||||
import { AddHintDialogComponent } from './dialogs/add-hint-dialog/add-hint-dialog.component';
|
||||
import { AddAnnotationDialogComponent } from './dialogs/docu-mine/add-annotation-dialog/add-annotation-dialog.component';
|
||||
import { RectangleAnnotationDialog } from './dialogs/rectangle-annotation-dialog/rectangle-annotation-dialog.component';
|
||||
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
|
||||
import { filePreviewScreenProviders } from './file-preview-providers';
|
||||
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
||||
import { AnnotationsListingService } from './services/annotations-listing.service';
|
||||
import { DocumentInfoService } from './services/document-info.service';
|
||||
import { FileDataService } from './services/file-data.service';
|
||||
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
|
||||
import { FilePreviewStateService } from './services/file-preview-state.service';
|
||||
import { ManualRedactionService } from './services/manual-redaction.service';
|
||||
import { MultiSelectService } from './services/multi-select.service';
|
||||
import { PdfProxyService } from './services/pdf-proxy.service';
|
||||
import { SkippedService } from './services/skipped.service';
|
||||
import { StampService } from './services/stamp.service';
|
||||
import { ViewModeService } from './services/view-mode.service';
|
||||
import { RedactTextData } from './utils/dialog-types';
|
||||
import { MultiSelectService } from './services/multi-select.service';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FilePreviewRightContainerComponent } from './components/right-container/file-preview-right-container.component';
|
||||
import { TypeFilterComponent } from '@shared/components/type-filter/type-filter.component';
|
||||
import { FileHeaderComponent } from './components/file-header/file-header.component';
|
||||
import { StructuredComponentManagementComponent } from './components/structured-component-management/structured-component-management.component';
|
||||
import { DocumentInfoService } from './services/document-info.service';
|
||||
import { RectangleAnnotationDialog } from './dialogs/rectangle-annotation-dialog/rectangle-annotation-dialog.component';
|
||||
import { ANNOTATION_ACTION_ICONS, ANNOTATION_ACTIONS } from './utils/constants';
|
||||
import { ComponentLogService } from '@services/entity-services/component-log.service';
|
||||
import { CdkDrag, CdkDragEnd, CdkDragMove, CdkDragStart } from '@angular/cdk/drag-drop';
|
||||
import { RedactTextData } from './utils/dialog-types';
|
||||
|
||||
@Component({
|
||||
templateUrl: './file-preview-screen.component.html',
|
||||
@ -99,18 +101,27 @@ import { CdkDrag, CdkDragEnd, CdkDragMove, CdkDragStart } from '@angular/cdk/dra
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly roles = Roles;
|
||||
readonly fileId = this.state.fileId;
|
||||
readonly dossierId = this.state.dossierId;
|
||||
readonly resizeHandle = viewChild<ElementRef>('resize');
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
@ViewChild('annotationFilterTemplate', {
|
||||
read: TemplateRef,
|
||||
static: false,
|
||||
})
|
||||
private readonly _filterTemplate: TemplateRef<unknown>;
|
||||
#loadAllAnnotationsEnabled = false;
|
||||
readonly #wsConnection$ = inject(WebSocketService)
|
||||
.listen<AnalysisEvent>(WsTopics.ANALYSIS)
|
||||
.pipe(
|
||||
log('[WS] Analysis events'),
|
||||
filter(event => event.analyseStatus === AnalyseStatuses.FINISHED),
|
||||
switchMap(event => this._fileDataService.updateAnnotations(this.state.file(), event.analysisNumber)),
|
||||
log('[WS] Annotations updated'),
|
||||
);
|
||||
#wsConnectionSub: Subscription;
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly roles = Roles;
|
||||
readonly fileId = this.state.fileId;
|
||||
readonly dossierId = this.state.dossierId;
|
||||
readonly resizeHandle = viewChild<ElementRef>('resize');
|
||||
|
||||
constructor(
|
||||
readonly pdf: PdfViewer,
|
||||
@ -152,11 +163,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
private readonly _componentLogService: ComponentLogService,
|
||||
) {
|
||||
super();
|
||||
effect(() => {
|
||||
const file = this.state.file();
|
||||
this._fileDataService.loadAnnotations(file).then();
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
const file = this.state.file();
|
||||
if (file.analysisRequired && !file.excludedFromAutomaticAnalysis) {
|
||||
@ -164,6 +170,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
});
|
||||
|
||||
const file = this.state.file();
|
||||
if (this._fileDataService.annotations().length) {
|
||||
firstValueFrom(this._fileDataService.updateAnnotations(file, file.numberOfAnalyses)).then();
|
||||
} else {
|
||||
this._fileDataService.loadAnnotations(file).then();
|
||||
}
|
||||
|
||||
effect(
|
||||
() => {
|
||||
if (this._documentViewer.loaded()) {
|
||||
@ -286,12 +299,14 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
this.pdf.instance.UI.hotkeys.off('esc');
|
||||
this.pdf.instance.UI.iframeWindow.document.removeEventListener('click', this.handleViewerClick);
|
||||
this._changeRef.markForCheck();
|
||||
this.#wsConnectionSub.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.pdf.instance.UI.hotkeys.off('esc');
|
||||
this.pdf.instance.UI.iframeWindow.document.removeEventListener('click', this.handleViewerClick);
|
||||
super.ngOnDestroy();
|
||||
this.#wsConnectionSub.unsubscribe();
|
||||
}
|
||||
|
||||
handleEscInsideViewer($event: KeyboardEvent) {
|
||||
@ -354,6 +369,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.#wsConnectionSub = this.#wsConnection$.subscribe();
|
||||
this.#updateViewerPosition();
|
||||
const file = this.state.file();
|
||||
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { IqserRoutes } from '@iqser/common-ui';
|
||||
import { FilePreviewScreenComponent } from './file-preview-screen.component';
|
||||
import { inject, provideEnvironmentInitializer } from '@angular/core';
|
||||
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
||||
import { IqserRoutes } from '@iqser/common-ui';
|
||||
import { WebSocketService } from '@services/web-socket.service';
|
||||
import { FileAssignService } from '../shared-dossiers/services/file-assign.service';
|
||||
import { FilePreviewScreenComponent } from './file-preview-screen.component';
|
||||
import { DocumentUnloadedGuard } from './services/document-unloaded.guard';
|
||||
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
|
||||
import { ManualRedactionService } from './services/manual-redaction.service';
|
||||
import { TablesService } from './services/tables.service';
|
||||
import { FileAssignService } from '../shared-dossiers/services/file-assign.service';
|
||||
|
||||
export default [
|
||||
{
|
||||
@ -13,6 +15,15 @@ export default [
|
||||
component: FilePreviewScreenComponent,
|
||||
pathMatch: 'full',
|
||||
canDeactivate: [PendingChangesGuard, DocumentUnloadedGuard],
|
||||
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, TablesService, FileAssignService],
|
||||
providers: [
|
||||
FilePreviewDialogService,
|
||||
ManualRedactionService,
|
||||
DocumentUnloadedGuard,
|
||||
TablesService,
|
||||
FileAssignService,
|
||||
provideEnvironmentInitializer(async () => {
|
||||
return inject(WebSocketService).connectAsync('/redaction-gateway-v1/websocket');
|
||||
}),
|
||||
],
|
||||
},
|
||||
] satisfies IqserRoutes;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { IqserDialog } from '@common-ui/dialog/iqser-dialog.service';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
import { List } from '@iqser/common-ui/lib/utils';
|
||||
import { List, log } from '@iqser/common-ui/lib/utils';
|
||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
@ -27,6 +27,7 @@ import { EditAnnotationDialogComponent } from '../dialogs/docu-mine/edit-annotat
|
||||
import { RemoveAnnotationDialogComponent } from '../dialogs/docu-mine/remove-annotation-dialog/remove-annotation-dialog.component';
|
||||
import { ResizeAnnotationDialogComponent } from '../dialogs/docu-mine/resize-annotation-dialog/resize-annotation-dialog.component';
|
||||
import { EditRedactionDialogComponent } from '../dialogs/edit-redaction-dialog/edit-redaction-dialog.component';
|
||||
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
|
||||
import { RedactRecommendationDialogComponent } from '../dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component';
|
||||
import { RemoveRedactionDialogComponent } from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
|
||||
import { ResizeRedactionDialogComponent } from '../dialogs/resize-redaction-dialog/resize-redaction-dialog.component';
|
||||
@ -49,7 +50,6 @@ import { FilePreviewDialogService } from './file-preview-dialog.service';
|
||||
import { FilePreviewStateService } from './file-preview-state.service';
|
||||
import { ManualRedactionService } from './manual-redaction.service';
|
||||
import { SkippedService } from './skipped.service';
|
||||
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
|
||||
|
||||
@Injectable()
|
||||
export class AnnotationActionsService {
|
||||
@ -362,7 +362,7 @@ export class AnnotationActionsService {
|
||||
}
|
||||
|
||||
async #processObsAndEmit(obs: Observable<unknown>) {
|
||||
await firstValueFrom(obs).finally(() => this._fileDataService.annotationsChanged());
|
||||
await firstValueFrom(obs.pipe(log('==>>[[[CHANGES]]]'))).finally(() => this._fileDataService.annotationsChanged());
|
||||
}
|
||||
|
||||
#getFalsePositiveText(annotation: AnnotationWrapper) {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { effect, inject, Injectable, Signal, signal } from '@angular/core';
|
||||
import { effect, inject, Injectable, Signal, signal, WritableSignal } from '@angular/core';
|
||||
import { toObservable } from '@angular/core/rxjs-interop';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { TenantsService } from '@common-ui/tenants';
|
||||
import { log } from '@common-ui/utils';
|
||||
import { EntitiesService, getConfig, Toaster } from '@iqser/common-ui';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import {
|
||||
@ -27,6 +29,7 @@ import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import dayjs from 'dayjs';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { FilePreviewStateService } from './file-preview-state.service';
|
||||
import { MultiSelectService } from './multi-select.service';
|
||||
import { ViewModeService } from './view-mode.service';
|
||||
@ -43,13 +46,14 @@ export function chronologicallyBy<T>(property: (x: T) => string) {
|
||||
|
||||
@Injectable()
|
||||
export class FileDataService extends EntitiesService<AnnotationWrapper, AnnotationWrapper> {
|
||||
readonly #annotations = signal<AnnotationWrapper[]>([]);
|
||||
readonly #annotations: WritableSignal<AnnotationWrapper[]>;
|
||||
readonly #earmarks = signal<Map<number, AnnotationWrapper[]>>(new Map());
|
||||
#originalViewedPages: ViewedPage[] = [];
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly #logger = inject(NGXLogger);
|
||||
readonly #toaster = inject(Toaster);
|
||||
readonly #isIqserDevMode = inject(UserPreferenceService).isIqserDevMode;
|
||||
readonly #tenantsService = inject(TenantsService);
|
||||
protected readonly _entityClass = AnnotationWrapper;
|
||||
missingTypes = new Set<string>();
|
||||
readonly earmarks: Signal<Map<number, AnnotationWrapper[]>>;
|
||||
@ -70,9 +74,24 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
||||
private readonly _defaultColorsService: DefaultColorsService,
|
||||
) {
|
||||
super();
|
||||
const localStorageKey = this.#tenantsService.activeTenantId + '-annotations-' + this._state.fileId;
|
||||
const storedAnnotations = JSON.parse(localStorage.getItem(localStorageKey) || '[]') as [];
|
||||
this.#annotations = signal<AnnotationWrapper[]>(
|
||||
storedAnnotations.map(a => {
|
||||
const newAnn = new AnnotationWrapper();
|
||||
Object.assign(newAnn, a);
|
||||
return newAnn;
|
||||
}),
|
||||
);
|
||||
this.annotations$ = toObservable(this.#annotations);
|
||||
this.annotations = this.#annotations.asReadonly();
|
||||
this.earmarks = this.#earmarks.asReadonly();
|
||||
|
||||
effect(() => {
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(this.#annotations()));
|
||||
console.log('FileDataService#annotations', this.#annotations());
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
const viewMode = this._viewModeService.viewMode();
|
||||
const earmarks = ([] as AnnotationWrapper[]).concat(...this.#earmarks().values());
|
||||
@ -119,7 +138,8 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
||||
const file = this._state.file();
|
||||
const fileReloaded = await this._filesService.reload(file.dossierId, file);
|
||||
if (!fileReloaded) {
|
||||
await this.loadAnnotations(file);
|
||||
await this.#loadViewedPages(file);
|
||||
// await this.loadAnnotations(file);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,6 +162,31 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
||||
this.#annotations.set(annotations);
|
||||
}
|
||||
|
||||
async processEntityLog(entityLog: IEntityLog) {
|
||||
let annotations = await this.#convertData(entityLog);
|
||||
this.#checkMissingTypes();
|
||||
|
||||
return this.#isIqserDevMode ? annotations : annotations.filter(a => !a.isFalsePositive);
|
||||
}
|
||||
|
||||
updateAnnotations(file: File, analysisNumber: number) {
|
||||
const delta$ = this._entityLogService.getDelta(file.dossierId, file.fileId, analysisNumber);
|
||||
return delta$.pipe(
|
||||
log('[REDACTION_LOG] Delta loaded'),
|
||||
switchMap(delta => this.processEntityLog(delta)),
|
||||
tap(annotations => {
|
||||
const notDeleted = annotations.filter(annotation => !annotation.isRemoved);
|
||||
this.#annotations.update(old => {
|
||||
const notUpdated = old.filter(oldAnnotation => {
|
||||
return !oldAnnotation.pending && !annotations.some(newAnnotation => newAnnotation.id === oldAnnotation.id);
|
||||
});
|
||||
return [...notUpdated, ...notDeleted].sort((a, b) => a.positions[0].page - b.positions[0].page);
|
||||
});
|
||||
}),
|
||||
tap(() => this.#logger.info('[REDACTION_LOG] Annotations updated', this.#annotations())),
|
||||
);
|
||||
}
|
||||
|
||||
#checkMissingTypes() {
|
||||
if (this.missingTypes.size > 0) {
|
||||
this.#toaster.error(_('error.missing-types'), {
|
||||
@ -190,7 +235,8 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
||||
continue;
|
||||
}
|
||||
|
||||
const canBeMappedToASuperType = !!SuperTypeMapper[entry.entryType][entry.state](entry);
|
||||
const isRemoved = entry.state === EntryStates.REMOVED;
|
||||
const canBeMappedToASuperType = !!SuperTypeMapper[entry.entryType][entry.state](entry) || isRemoved;
|
||||
if (!canBeMappedToASuperType) {
|
||||
if (this.#isIqserDevMode) {
|
||||
this.#logger.warn(
|
||||
|
||||
@ -210,6 +210,7 @@ export class REDAnnotationManager {
|
||||
}
|
||||
|
||||
#getByIds(annotations: List | List<AnnotationWrapper>) {
|
||||
annotations = annotations ?? [];
|
||||
return annotations.map((item: string | AnnotationWrapper) => this.#getById(item)).filter(a => !!a);
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { Component, computed, HostBinding, Injector, input, Optional, signal, ViewChild } from '@angular/core';
|
||||
import { NgIf, NgTemplateOutlet } from '@angular/common';
|
||||
import { Component, computed, HostBinding, inject, Injector, input, Optional, signal, ViewChild } from '@angular/core';
|
||||
import { toObservable } from '@angular/core/rxjs-interop';
|
||||
import { Router } from '@angular/router';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { StatusBarComponent } from '@common-ui/shared';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
getConfig,
|
||||
@ -20,24 +22,23 @@ import { FileManagementService } from '@services/files/file-management.service';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { ReanalysisService, ReanalyzeQueryParams } from '@services/reanalysis.service';
|
||||
import { ApproveWarningDetailsComponent } from '@shared/components/approve-warning-details/approve-warning-details.component';
|
||||
import { ExpandableFileActionsComponent } from '@shared/components/expandable-file-actions/expandable-file-actions.component';
|
||||
import { ProcessingIndicatorComponent } from '@shared/components/processing-indicator/processing-indicator.component';
|
||||
import { LongPressDirective, LongPressEvent } from '@shared/directives/long-press.directive';
|
||||
import { Roles } from '@users/roles';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { setLocalStorageDataByFileId } from '@utils/local-storage';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { RulesService } from '../../../admin/services/rules.service';
|
||||
import { DocumentInfoService } from '../../../file-preview/services/document-info.service';
|
||||
import { ExcludedPagesService } from '../../../file-preview/services/excluded-pages.service';
|
||||
import { FileDataService } from '../../../file-preview/services/file-data.service';
|
||||
import { PageRotationService } from '../../../pdf-viewer/services/page-rotation.service';
|
||||
import { ViewerHeaderService } from '../../../pdf-viewer/services/viewer-header.service';
|
||||
import { AssignReviewerApproverDialogComponent } from '../../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import { FileAssignService } from '../../services/file-assign.service';
|
||||
import { ProcessingIndicatorComponent } from '@shared/components/processing-indicator/processing-indicator.component';
|
||||
import { StatusBarComponent } from '@common-ui/shared';
|
||||
import { NgIf, NgTemplateOutlet } from '@angular/common';
|
||||
import { ApproveWarningDetailsComponent } from '@shared/components/approve-warning-details/approve-warning-details.component';
|
||||
import { RulesService } from '../../../admin/services/rules.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-actions',
|
||||
@ -48,8 +49,49 @@ import { RulesService } from '../../../admin/services/rules.service';
|
||||
export class FileActionsComponent {
|
||||
@ViewChild(ExpandableFileActionsComponent)
|
||||
private readonly _expandableActionsComponent: ExpandableFileActionsComponent;
|
||||
readonly #fileDataService = inject(FileDataService, { optional: true });
|
||||
readonly #analysisForced = signal(false);
|
||||
readonly #ariaExpanded$ = toObservable(this._documentInfoService?.shown);
|
||||
readonly #areRulesLocked = computed(() => this._rulesService.currentTemplateRules().timeoutDetected);
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly file = input.required<File>();
|
||||
readonly #assignTooltip? = computed(() =>
|
||||
this.file().isUnderApproval ? _('dossier-overview.assign-approver') : _('dossier-overview.assign-reviewer'),
|
||||
);
|
||||
readonly #showReanalyse = computed(
|
||||
() => (this.#canReanalyse() || this.file().excludedFromAutomaticAnalysis || this.#analysisForced()) && !this.file().dossierArchived,
|
||||
);
|
||||
readonly #toggleTooltip? = computed(() => {
|
||||
if (!this.#canToggleAnalysis()) {
|
||||
return _('file-preview.toggle-analysis.only-managers');
|
||||
}
|
||||
return this.file()?.excluded ? _('file-preview.toggle-analysis.enable') : _('file-preview.toggle-analysis.disable');
|
||||
});
|
||||
readonly #showDownload = computed(
|
||||
() =>
|
||||
this._permissionsService.canDownloadRedactedFile() &&
|
||||
!!this.file().lastProcessed &&
|
||||
!this.file().isError &&
|
||||
this.#isDossierMember(),
|
||||
);
|
||||
readonly dossier = input.required<Dossier>();
|
||||
readonly #showImportRedactions = computed(
|
||||
() => this._permissionsService.canImportRedactions(this.file(), this.dossier()) && !this.file().isError,
|
||||
);
|
||||
readonly #showDelete = computed(() => this._permissionsService.canSoftDeleteFile(this.file(), this.dossier()));
|
||||
readonly #showOCR = computed(() => this._permissionsService.canOcrFile(this.file(), this.dossier()) && !this.file().isError);
|
||||
readonly #canReanalyse = computed(() => this._permissionsService.canReanalyseFile(this.file(), this.dossier()));
|
||||
readonly #canEnableAutoAnalysis = computed(
|
||||
() => this._permissionsService.canEnableAutoAnalysis([this.file()], this.dossier()) && !this.file().isError,
|
||||
);
|
||||
readonly #canToggleAnalysis = computed(() => this._permissionsService.canToggleAnalysis(this.file(), this.dossier()));
|
||||
readonly #showToggleAnalysis = computed(
|
||||
() => !!this.file().lastProcessed && this._permissionsService.showToggleAnalysis(this.dossier()),
|
||||
);
|
||||
readonly #isDossierMember = computed(() => this._permissionsService.isDossierMember(this.dossier()));
|
||||
readonly #canDisableAutoAnalysis = computed(
|
||||
() => !this.#isDocumine && this._permissionsService.canDisableAutoAnalysis([this.file()], this.dossier()) && !this.file().isError,
|
||||
);
|
||||
readonly type = input.required<'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow'>();
|
||||
readonly maxWidth = input<number>();
|
||||
readonly minWidth = input<number>();
|
||||
@ -57,16 +99,21 @@ export class FileActionsComponent {
|
||||
readonly singleEntityAction = input(false);
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
readonly tooltipPosition = IqserTooltipPositions.above;
|
||||
|
||||
readonly isDossierOverview = computed(() => this.type().startsWith('dossier-overview'));
|
||||
readonly #showAssignToSelf = computed(
|
||||
() => this._permissionsService.canAssignToSelf(this.file(), this.dossier()) && this.isDossierOverview(),
|
||||
);
|
||||
readonly #showAssign = computed(
|
||||
() =>
|
||||
(this._permissionsService.canAssignUser(this.file(), this.dossier()) ||
|
||||
this._permissionsService.canUnassignUser(this.file(), this.dossier())) &&
|
||||
this.isDossierOverview(),
|
||||
);
|
||||
readonly #showReanalyseDossierOverview = computed(
|
||||
() => this.#showReanalyse() && this.isDossierOverview() && !this.file().isApproved && this.#isDossierMember(),
|
||||
);
|
||||
readonly isDossierOverviewList = computed(() => this.type() === 'dossier-overview-list');
|
||||
readonly isDossierOverviewWorkflow = computed(() => this.type() === 'dossier-overview-workflow');
|
||||
readonly isFilePreview = computed(() => this.type() === 'file-preview');
|
||||
readonly buttons = computed(() => this.#buttons);
|
||||
readonly showStatusBar = computed(() => !this.file().isError && !this.file().isUnprocessed && this.isDossierOverviewList());
|
||||
readonly #assignTooltip? = computed(() =>
|
||||
this.file().isUnderApproval ? _('dossier-overview.assign-approver') : _('dossier-overview.assign-reviewer'),
|
||||
);
|
||||
readonly #showSetToNew = computed(
|
||||
() =>
|
||||
this._permissionsService.canSetToNew(this.file(), this.dossier()) && !this.isDossierOverviewWorkflow() && !this.file().isError,
|
||||
@ -77,24 +124,6 @@ export class FileActionsComponent {
|
||||
!this.isDossierOverviewWorkflow() &&
|
||||
!this.file().isError,
|
||||
);
|
||||
readonly #showAssignToSelf = computed(
|
||||
() => this._permissionsService.canAssignToSelf(this.file(), this.dossier()) && this.isDossierOverview(),
|
||||
);
|
||||
readonly #showImportRedactions = computed(
|
||||
() => this._permissionsService.canImportRedactions(this.file(), this.dossier()) && !this.file().isError,
|
||||
);
|
||||
readonly #showAssign = computed(
|
||||
() =>
|
||||
(this._permissionsService.canAssignUser(this.file(), this.dossier()) ||
|
||||
this._permissionsService.canUnassignUser(this.file(), this.dossier())) &&
|
||||
this.isDossierOverview(),
|
||||
);
|
||||
readonly #showDelete = computed(() => this._permissionsService.canSoftDeleteFile(this.file(), this.dossier()));
|
||||
readonly #showOCR = computed(() => this._permissionsService.canOcrFile(this.file(), this.dossier()) && !this.file().isError);
|
||||
readonly #canReanalyse = computed(() => this._permissionsService.canReanalyseFile(this.file(), this.dossier()));
|
||||
readonly #canEnableAutoAnalysis = computed(
|
||||
() => this._permissionsService.canEnableAutoAnalysis([this.file()], this.dossier()) && !this.file().isError,
|
||||
);
|
||||
readonly #showUnderReview = computed(
|
||||
() =>
|
||||
this._permissionsService.canSetUnderReview(this.file(), this.dossier()) &&
|
||||
@ -113,41 +142,12 @@ export class FileActionsComponent {
|
||||
!this.isDossierOverviewWorkflow() &&
|
||||
!this.file().isError,
|
||||
);
|
||||
readonly #canToggleAnalysis = computed(() => this._permissionsService.canToggleAnalysis(this.file(), this.dossier()));
|
||||
readonly #toggleTooltip? = computed(() => {
|
||||
if (!this.#canToggleAnalysis()) {
|
||||
return _('file-preview.toggle-analysis.only-managers');
|
||||
}
|
||||
return this.file()?.excluded ? _('file-preview.toggle-analysis.enable') : _('file-preview.toggle-analysis.disable');
|
||||
});
|
||||
readonly #showToggleAnalysis = computed(
|
||||
() => !!this.file().lastProcessed && this._permissionsService.showToggleAnalysis(this.dossier()),
|
||||
);
|
||||
readonly #analysisForced = signal(false);
|
||||
readonly #showReanalyse = computed(
|
||||
() => (this.#canReanalyse() || this.file().excludedFromAutomaticAnalysis || this.#analysisForced()) && !this.file().dossierArchived,
|
||||
);
|
||||
readonly #isDossierMember = computed(() => this._permissionsService.isDossierMember(this.dossier()));
|
||||
readonly #showDownload = computed(
|
||||
() =>
|
||||
this._permissionsService.canDownloadRedactedFile() &&
|
||||
!!this.file().lastProcessed &&
|
||||
!this.file().isError &&
|
||||
this.#isDossierMember(),
|
||||
);
|
||||
readonly isFilePreview = computed(() => this.type() === 'file-preview');
|
||||
readonly #showReanalyseFilePreview = computed(
|
||||
() => this.#showReanalyse() && this.isFilePreview() && !this.file().isApproved && this.#isDossierMember(),
|
||||
);
|
||||
readonly #showReanalyseDossierOverview = computed(
|
||||
() => this.#showReanalyse() && this.isDossierOverview() && !this.file().isApproved && this.#isDossierMember(),
|
||||
);
|
||||
readonly #ariaExpanded$ = toObservable(this._documentInfoService?.shown);
|
||||
readonly #areRulesLocked = computed(() => this._rulesService.currentTemplateRules().timeoutDetected);
|
||||
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly #canDisableAutoAnalysis = computed(
|
||||
() => !this.#isDocumine && this._permissionsService.canDisableAutoAnalysis([this.file()], this.dossier()) && !this.file().isError,
|
||||
);
|
||||
readonly buttons = computed(() => this.#buttons);
|
||||
readonly showStatusBar = computed(() => !this.file().isError && !this.file().isUnprocessed && this.isDossierOverviewList());
|
||||
|
||||
constructor(
|
||||
private readonly _injector: Injector,
|
||||
@ -433,6 +433,9 @@ export class FileActionsComponent {
|
||||
|
||||
async #assignToMe() {
|
||||
await this._fileAssignService.assignToMe([this.file()]);
|
||||
// TODO: check which one to call
|
||||
// await firstValueFrom(this.#fileDataService?.updateAnnotations(this.file, this.file.numberOfAnalyses));
|
||||
await this.#fileDataService?.loadEntityLog();
|
||||
}
|
||||
|
||||
async #reanalyseFile() {
|
||||
|
||||
@ -1,21 +1,22 @@
|
||||
import { ApplicationRef, Injectable, OnDestroy } from '@angular/core';
|
||||
import { FileUploadModel } from '../model/file-upload.model';
|
||||
import { HttpErrorResponse, HttpEventType, HttpStatusCode } from '@angular/common/http';
|
||||
import { interval, Subject, Subscription } from 'rxjs';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { ApplicationRef, Injectable, OnDestroy } from '@angular/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { TenantsService } from '@common-ui/tenants';
|
||||
import { ErrorMessageService, GenericService, Toaster } from '@iqser/common-ui';
|
||||
import { HeadersConfiguration } from '@iqser/common-ui/lib/utils';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { IFileUploadResult, OverwriteFileOption, OverwriteFileOptions } from '@red/domain';
|
||||
import { isAcceptedFileType, isCsv, isDocument, isZip } from '@utils/file-drop-utils';
|
||||
import { ErrorMessageService, GenericService, Toaster } from '@iqser/common-ui';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { FilesMapService } from '@services/files/files-map.service';
|
||||
import { switchMap, tap, throttleTime } from 'rxjs/operators';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
import { UploadDownloadDialogService } from './upload-download-dialog.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { HeadersConfiguration } from '@iqser/common-ui/lib/utils';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { LicenseFeatures } from '../../admin/screens/license/utils/constants';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { isAcceptedFileType, isCsv, isZip } from '@utils/file-drop-utils';
|
||||
import { interval, Subject, Subscription } from 'rxjs';
|
||||
import { switchMap, tap, throttleTime } from 'rxjs/operators';
|
||||
import { LicenseFeatures } from '../../admin/screens/license/utils/constants';
|
||||
import { FileUploadModel } from '../model/file-upload.model';
|
||||
import { UploadDownloadDialogService } from './upload-download-dialog.service';
|
||||
|
||||
export interface ActiveUpload {
|
||||
subscription: Subscription;
|
||||
@ -45,6 +46,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
||||
private readonly _licenseService: LicenseService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
) {
|
||||
super();
|
||||
const fileFetch$ = this.#fetchFiles$.pipe(
|
||||
@ -111,6 +113,11 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
||||
option = res.applyToAllFiles ? currentOption : undefined;
|
||||
}
|
||||
|
||||
const existingFile = dossierFiles.find(pf => pf.filename === file.file.name);
|
||||
if (OverwriteFileOptions.FULL_OVERWRITE === currentOption || OverwriteFileOptions.PARTIAL_OVERWRITE === currentOption) {
|
||||
localStorage.removeItem(`${this._tenantsService.activeTenantId}-annotations-${existingFile.id}`);
|
||||
}
|
||||
|
||||
if (currentOption === OverwriteFileOptions.PARTIAL_OVERWRITE) {
|
||||
file.keepManualRedactions = true;
|
||||
}
|
||||
|
||||
18
apps/red-ui/src/app/services/copilot.service.ts
Normal file
18
apps/red-ui/src/app/services/copilot.service.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { StompService } from '@services/stomp.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CopilotService extends StompService {
|
||||
override get topicPrefix(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
override send(message: string) {
|
||||
this.publish({
|
||||
destination: '/app/rules-copilot',
|
||||
body: JSON.stringify({ prompts: [message] }),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,34 +1,49 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { GenericService, Toaster } from '@iqser/common-ui';
|
||||
import { EntryStates, IEntityLog, IEntityLogEntry } from '@red/domain';
|
||||
import { EntryState, EntryStates, IEntityLog, IEntityLogEntry, ISectionGrid } from '@red/domain';
|
||||
import { firstValueFrom, of } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class EntityLogService extends GenericService<unknown> {
|
||||
protected readonly _defaultModelPath = '';
|
||||
readonly #toaster = inject(Toaster);
|
||||
protected readonly _defaultModelPath = '';
|
||||
|
||||
async getEntityLog(dossierId: string, fileId: string) {
|
||||
const queryParams = [{ key: 'includeUnprocessed', value: true }];
|
||||
const entityLog$ = this._getOne<IEntityLog>([dossierId, fileId], 'entityLog', queryParams);
|
||||
const entityLog = await firstValueFrom(entityLog$.pipe(catchError(() => of({} as IEntityLog))));
|
||||
entityLog.entityLogEntry = this.#filterInvalidEntries(entityLog.entityLogEntry);
|
||||
entityLog.entityLogEntry = this.#filterInvalidEntries(entityLog.entityLogEntry, [EntryStates.REMOVED]);
|
||||
entityLog.entityLogEntry.sort((a, b) => a.positions[0].pageNumber - b.positions[0].pageNumber);
|
||||
return entityLog;
|
||||
}
|
||||
|
||||
#filterInvalidEntries(entityLogEntry: IEntityLogEntry[]) {
|
||||
getDelta(dossierId: string, fileId: string, analysisNumber: number) {
|
||||
const req$ = this._getOne<IEntityLog>([dossierId, fileId, analysisNumber.toString()], 'entityLog');
|
||||
return req$.pipe(
|
||||
map(entityLog => {
|
||||
entityLog.entityLogEntry = this.#filterInvalidEntries(entityLog.entityLogEntry);
|
||||
return entityLog;
|
||||
}),
|
||||
catchError(() => of({} as IEntityLog)),
|
||||
);
|
||||
}
|
||||
|
||||
getSectionGrid(dossierId: string, fileId: string) {
|
||||
return this._getOne<ISectionGrid>([dossierId, fileId], 'sectionGrid');
|
||||
}
|
||||
|
||||
#filterInvalidEntries(entityLogEntry: IEntityLogEntry[], invalidStates: EntryState[] = []) {
|
||||
return entityLogEntry.filter(entry => {
|
||||
entry.positions = entry.positions?.filter(p => !!p.rectangle?.length);
|
||||
const hasPositions = !!entry.positions?.length;
|
||||
const isRemoved = entry.state === EntryStates.REMOVED;
|
||||
const hasInvalidState = invalidStates.includes(entry.state);
|
||||
if (!hasPositions) {
|
||||
this.#toaster.devInfo(`Entry ${entry.id} was skipped because it has no position`);
|
||||
}
|
||||
return hasPositions && !isRemoved;
|
||||
return hasPositions && !hasInvalidState;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
73
apps/red-ui/src/app/services/stomp.service.ts
Normal file
73
apps/red-ui/src/app/services/stomp.service.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { KeycloakStatusService } from '@common-ui/tenants';
|
||||
import { log } from '@common-ui/utils';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
import { IMessage, IWatchParams, RxStomp } from '@stomp/rx-stomp';
|
||||
import { StompHeaders } from '@stomp/stompjs';
|
||||
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
|
||||
export abstract class StompService extends RxStomp {
|
||||
readonly #logger = inject(NGXLogger);
|
||||
readonly #config = getConfig();
|
||||
readonly #keycloakStatusService = inject(KeycloakStatusService);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.connectionState$.pipe(log('[WS] Connection state'), takeUntilDestroyed()).subscribe();
|
||||
this.webSocketErrors$.pipe(log('[WS] Errors'), takeUntilDestroyed()).subscribe();
|
||||
this.stompErrors$.pipe(takeUntilDestroyed()).subscribe(frame => {
|
||||
console.error(frame);
|
||||
console.error('Broker reported error: ' + frame.headers['message']);
|
||||
console.error('Additional details: ' + frame.body);
|
||||
});
|
||||
this.#keycloakStatusService.token$.pipe(takeUntilDestroyed()).subscribe(token => {
|
||||
this.#logger.info('[WS] Update connectHeaders');
|
||||
this.configure({
|
||||
connectHeaders: { Authorization: 'Bearer ' + token },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get topicPrefix() {
|
||||
return '';
|
||||
}
|
||||
|
||||
send(value: unknown) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
override watch(opts: IWatchParams): Observable<IMessage>;
|
||||
override watch(destination: string, headers?: StompHeaders): Observable<IMessage>;
|
||||
override watch(opts: string | IWatchParams, headers?: StompHeaders): Observable<IMessage> {
|
||||
if (typeof opts === 'string') {
|
||||
return super.watch(this.topicPrefix + opts, headers);
|
||||
}
|
||||
|
||||
return super.watch(opts);
|
||||
}
|
||||
|
||||
listen<T>(topic: string): Observable<T> {
|
||||
return this.watch(topic).pipe(
|
||||
tap(msg => this.#logger.info('[WS] Response on topic ' + topic, msg.body)),
|
||||
map(msg => JSON.parse(msg.body)),
|
||||
);
|
||||
}
|
||||
|
||||
connect(url: string) {
|
||||
this.configure({
|
||||
debug: (msg: string) => this.#logger.debug('[WS] ' + msg),
|
||||
brokerURL: this.#config.API_URL + url,
|
||||
reconnectDelay: 0,
|
||||
});
|
||||
|
||||
this.activate();
|
||||
}
|
||||
|
||||
async connectAsync(url: string) {
|
||||
return await firstValueFrom(this.#keycloakStatusService.token$.pipe(map(() => this.connect(url))));
|
||||
}
|
||||
}
|
||||
14
apps/red-ui/src/app/services/web-socket.service.ts
Normal file
14
apps/red-ui/src/app/services/web-socket.service.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { TenantsService } from '@common-ui/tenants';
|
||||
import { StompService } from '@services/stomp.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class WebSocketService extends StompService {
|
||||
readonly #tenantService = inject(TenantsService);
|
||||
|
||||
override get topicPrefix() {
|
||||
return '/topic/' + this.#tenantService.activeTenantId + '/';
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,6 @@ import { inject, Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
import { IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { IqserRoleGuard } from '@iqser/common-ui/lib/users';
|
||||
import { UserService } from '@users/user.service';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
|
||||
@Injectable({
|
||||
@ -10,10 +9,9 @@ import { NGXLogger } from 'ngx-logger';
|
||||
})
|
||||
export class RedRoleGuard extends IqserRoleGuard {
|
||||
protected readonly _permissionsService = inject(IqserPermissionsService);
|
||||
protected readonly _userService = inject(UserService);
|
||||
protected readonly _logger = inject(NGXLogger);
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||
override async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||
const currentUser = this._userService.currentUser;
|
||||
|
||||
if (!currentUser?.hasAnyRole) {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { User } from '@red/domain';
|
||||
import { QueryParam } from '@iqser/common-ui';
|
||||
import { Roles } from '@users/roles';
|
||||
import { of } from 'rxjs';
|
||||
import { IIqserUser, IqserUserService } from '@iqser/common-ui/lib/users';
|
||||
import { List } from '@iqser/common-ui/lib/utils';
|
||||
import { User } from '@red/domain';
|
||||
import { Roles } from '@users/roles';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -13,7 +13,7 @@ export class UserService extends IqserUserService<IIqserUser, User> {
|
||||
protected readonly _defaultModelPath = 'user';
|
||||
protected readonly _entityClass = User;
|
||||
|
||||
async loadCurrentUser(): Promise<User | undefined> {
|
||||
override async loadCurrentUser(): Promise<User | undefined> {
|
||||
const currentUser = await super.loadCurrentUser();
|
||||
|
||||
this._permissionsService.add({
|
||||
@ -23,12 +23,12 @@ export class UserService extends IqserUserService<IIqserUser, User> {
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
loadAll() {
|
||||
override loadAll() {
|
||||
const canReadUsers = this._permissionsService.has(Roles.users.read);
|
||||
return canReadUsers ? super.loadAll() : of([]);
|
||||
}
|
||||
|
||||
getAll() {
|
||||
override getAll() {
|
||||
const canReadAllUsers = this._permissionsService.has(Roles.users.readAll);
|
||||
const url = canReadAllUsers ? this._defaultModelPath : `${this._defaultModelPath}/red`;
|
||||
return super.getAll(url);
|
||||
@ -39,8 +39,8 @@ export class UserService extends IqserUserService<IIqserUser, User> {
|
||||
return this._post(null, `${this._defaultModelPath}/profile/activate/${user.userId}`, queryParams);
|
||||
}
|
||||
|
||||
protected readonly _rolesFilter = (role: string) => role.startsWith('RED_');
|
||||
protected readonly _permissionsFilter = (role: string) => role.startsWith('red-') || role.startsWith('fforesight-');
|
||||
protected override readonly _rolesFilter = (role: string) => role.startsWith('RED_');
|
||||
protected override readonly _permissionsFilter = (role: string) => role.startsWith('red-') || role.startsWith('fforesight-');
|
||||
}
|
||||
|
||||
export function getCurrentUser() {
|
||||
|
||||
@ -275,9 +275,6 @@
|
||||
"watermarks": "Wasserzeichen"
|
||||
},
|
||||
"analysis-disabled": "",
|
||||
"annotation": {
|
||||
"pending": "(Analyse steht aus)"
|
||||
},
|
||||
"annotation-actions": {
|
||||
"accept-recommendation": {
|
||||
"label": "Empfehlung annehmen"
|
||||
@ -333,14 +330,14 @@
|
||||
"error": "Rekategorisierung des Bilds fehlgeschlagen: {error}",
|
||||
"success": "Bild wurde einer neuen Kategorie zugeordnet."
|
||||
},
|
||||
"remove": {
|
||||
"error": "Entfernen der Schwärzung fehlgeschlagen: {error}",
|
||||
"success": "Schwärzung wurde entfernt"
|
||||
},
|
||||
"remove-hint": {
|
||||
"error": "Entfernen des Hinweises fehlgeschlagen: {error}",
|
||||
"success": "Hinweis wurde entfernt"
|
||||
},
|
||||
"remove": {
|
||||
"error": "Entfernen der Schwärzung fehlgeschlagen: {error}",
|
||||
"success": "Schwärzung wurde entfernt"
|
||||
},
|
||||
"undo": {
|
||||
"error": "Die Aktion konnte nicht rückgängig gemacht werden. Fehler: {error}",
|
||||
"success": "Rücksetzung erfolgreich"
|
||||
@ -353,15 +350,15 @@
|
||||
"remove-highlights": {
|
||||
"label": "Ausgewählte Markierungen entfernen"
|
||||
},
|
||||
"resize": {
|
||||
"label": "Größe ändern"
|
||||
},
|
||||
"resize-accept": {
|
||||
"label": "Neue Größe speichern"
|
||||
},
|
||||
"resize-cancel": {
|
||||
"label": "Größenänderung abbrechen"
|
||||
},
|
||||
"resize": {
|
||||
"label": "Größe ändern"
|
||||
},
|
||||
"see-references": {
|
||||
"label": "Referenzen anzeigen"
|
||||
},
|
||||
@ -396,6 +393,9 @@
|
||||
"skipped": "Ignorierte Schwärzung",
|
||||
"text-highlight": "Markierung"
|
||||
},
|
||||
"annotation": {
|
||||
"pending": "(Analyse steht aus)"
|
||||
},
|
||||
"annotations": "Annotationen",
|
||||
"archived-dossiers-listing": {
|
||||
"no-data": {
|
||||
@ -702,6 +702,9 @@
|
||||
}
|
||||
},
|
||||
"content": "Grund",
|
||||
"copilot": {
|
||||
"label": ""
|
||||
},
|
||||
"dashboard": {
|
||||
"empty-template": {
|
||||
"description": "Diese Vorlage enthält keine Dossiers. Erstellen Sie ein Dossier, um nach diesen Regeln zu schwärzen.",
|
||||
@ -1023,13 +1026,13 @@
|
||||
"recent": "Neu ({hours} h)",
|
||||
"unassigned": "Keinem Bearbeiter zugewiesen"
|
||||
},
|
||||
"reanalyse": {
|
||||
"action": "Datei analysieren"
|
||||
},
|
||||
"reanalyse-dossier": {
|
||||
"error": "Einplanung der Dateien für die Reanalyse fehlgeschlagen. Bitte versuchen Sie es noch einmal.",
|
||||
"success": "Dateien für Reanalyse vorgesehen."
|
||||
},
|
||||
"reanalyse": {
|
||||
"action": "Datei analysieren"
|
||||
},
|
||||
"report-download": "",
|
||||
"start-auto-analysis": "Auto-Analyse aktivieren",
|
||||
"stop-auto-analysis": "Auto-Analyse anhalten",
|
||||
@ -1105,14 +1108,6 @@
|
||||
"total-documents": "Dokumente",
|
||||
"total-people": "<strong>{count}</strong> {count, plural, one{Benutzer} other {Benutzer}}"
|
||||
},
|
||||
"dossier-templates": {
|
||||
"label": "Dossier-Vorlagen",
|
||||
"status": {
|
||||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"incomplete": "Unvollständig"
|
||||
}
|
||||
},
|
||||
"dossier-templates-listing": {
|
||||
"action": {
|
||||
"clone": "Vorlage klonen",
|
||||
@ -1147,6 +1142,14 @@
|
||||
"title": "{length} {length, plural, one{Dossier-Vorlage} other{Dossier-Vorlagen}}"
|
||||
}
|
||||
},
|
||||
"dossier-templates": {
|
||||
"label": "Dossier-Vorlagen",
|
||||
"status": {
|
||||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"incomplete": "Unvollständig"
|
||||
}
|
||||
},
|
||||
"dossier-watermark-selector": {
|
||||
"heading": "Wasserzeichen auf Dokumenten",
|
||||
"no-watermark": "Kein Wasserzeichen in der Dossier-Vorlage verfügbar:<br>Bitten Sie Ihren Admin, eines zu konfigurieren.",
|
||||
@ -1357,15 +1360,6 @@
|
||||
"title": "{length} {length, plural, one{Wörterbuch} other{Wörterbücher}}"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"info": {
|
||||
"actions": {
|
||||
"revert": "Zurücksetzen",
|
||||
"save": "Änderungen speichern"
|
||||
},
|
||||
"heading": "Entität bearbeiten"
|
||||
}
|
||||
},
|
||||
"entity-rules-screen": {
|
||||
"error": {
|
||||
"generic": "Fehler: Aktualisierung der Entitätsregeln fehlgeschlagen."
|
||||
@ -1379,19 +1373,28 @@
|
||||
"title": "Entitätsregeln-Editor",
|
||||
"warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} in Regeln gefunden"
|
||||
},
|
||||
"entity": {
|
||||
"info": {
|
||||
"actions": {
|
||||
"revert": "Zurücksetzen",
|
||||
"save": "Änderungen speichern"
|
||||
},
|
||||
"heading": "Entität bearbeiten"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"deleted-entity": {
|
||||
"dossier": {
|
||||
"action": "Zurück zur Übersicht",
|
||||
"label": "Dieses Dossier wurde gelöscht!"
|
||||
},
|
||||
"file": {
|
||||
"action": "Zurück zum Dossier",
|
||||
"label": "Diese Datei wurde gelöscht!"
|
||||
},
|
||||
"file-dossier": {
|
||||
"action": "Zurück zur Übersicht",
|
||||
"label": "Das Dossier dieser Datei wurde gelöscht!"
|
||||
},
|
||||
"file": {
|
||||
"action": "Zurück zum Dossier",
|
||||
"label": "Diese Datei wurde gelöscht!"
|
||||
}
|
||||
},
|
||||
"file-preview": {
|
||||
@ -1409,12 +1412,6 @@
|
||||
},
|
||||
"exact-date": "{day}. {month} {year} um {hour}:{minute} Uhr",
|
||||
"file": "Datei",
|
||||
"file-attribute": {
|
||||
"update": {
|
||||
"error": "Aktualisierung des Werts für das Datei-Attribut fehlgeschlagen. Bitte versuchen Sie es noch einmal.",
|
||||
"success": "Der Wert für das Dateiattribut wurde erfolgreich aktualisiert."
|
||||
}
|
||||
},
|
||||
"file-attribute-encoding-types": {
|
||||
"ascii": "ASCII",
|
||||
"iso": "ISO-8859-1",
|
||||
@ -1425,6 +1422,12 @@
|
||||
"number": "Nummer",
|
||||
"text": "Freier Text"
|
||||
},
|
||||
"file-attribute": {
|
||||
"update": {
|
||||
"error": "Aktualisierung des Werts für das Datei-Attribut fehlgeschlagen. Bitte versuchen Sie es noch einmal.",
|
||||
"success": "Der Wert für das Dateiattribut wurde erfolgreich aktualisiert."
|
||||
}
|
||||
},
|
||||
"file-attributes-configurations": {
|
||||
"cancel": "Abbrechen",
|
||||
"form": {
|
||||
@ -1642,15 +1645,6 @@
|
||||
"zip": "Die Zip-Datei wurde erfolgreich hochgeladen!"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"analysis": "Analyse erforderlich",
|
||||
"comment": "Kommentare",
|
||||
"hint": "Nur Hinweise",
|
||||
"image": "Bilder",
|
||||
"none": "Keine Annotationen",
|
||||
"redaction": "Schwärzung",
|
||||
"updated": "Aktualisiert"
|
||||
},
|
||||
"filter-menu": {
|
||||
"filter-options": "Filteroptionen",
|
||||
"filter-types": "Filter",
|
||||
@ -1660,6 +1654,15 @@
|
||||
"unseen-pages": "Nur Annotationen auf ungesehenen Seiten",
|
||||
"with-comments": "Nur Annotationen mit Kommentaren"
|
||||
},
|
||||
"filter": {
|
||||
"analysis": "Analyse erforderlich",
|
||||
"comment": "Kommentare",
|
||||
"hint": "Nur Hinweise",
|
||||
"image": "Bilder",
|
||||
"none": "Keine Annotationen",
|
||||
"redaction": "Schwärzung",
|
||||
"updated": "Aktualisiert"
|
||||
},
|
||||
"filters": {
|
||||
"assigned-people": "Bearbeiter",
|
||||
"documents-status": "Dokumentenstatus",
|
||||
@ -1949,13 +1952,6 @@
|
||||
"user-promoted-to-approver": "Sie wurden zum Genehmiger in einem Dossier ernannt: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>",
|
||||
"user-removed-as-dossier-member": "Sie wurden als Dossier-Mitglied entfernt: \n<b>{dossierHref, select, null{{dossierName}} other\n{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>\n"
|
||||
},
|
||||
"notifications": {
|
||||
"button-text": "Benachrichtigungen",
|
||||
"deleted-dossier": "Gelöschtes Dossier",
|
||||
"label": "Benachrichtigungen",
|
||||
"mark-all-as-read": "Alle als gelesen markieren",
|
||||
"mark-as": "Als {type, select, read{gelesen} unread{ungelesen} other{}} markieren"
|
||||
},
|
||||
"notifications-screen": {
|
||||
"category": {
|
||||
"email-notifications": "E-Mail-Benachrichtigungen",
|
||||
@ -1969,6 +1965,7 @@
|
||||
"dossier": "Benachrichtigungen zu Dossiers",
|
||||
"other": "Andere Benachrichtigungen"
|
||||
},
|
||||
"options-title": "Wählen Sie aus, bei welchen Aktivitäten Sie benachrichtigt werden möchten",
|
||||
"options": {
|
||||
"ASSIGN_APPROVER": "Wenn ich einem Dokument als Genehmiger zugewiesen werde",
|
||||
"ASSIGN_REVIEWER": "Wenn ich einem Dokument als Prüfer zugewiesen werde",
|
||||
@ -1986,7 +1983,6 @@
|
||||
"USER_PROMOTED_TO_APPROVER": "Wenn ich Genehmiger in einem Dossier werde",
|
||||
"USER_REMOVED_AS_DOSSIER_MEMBER": "Wenn ich die Dossier-Mitgliedschaft verliere"
|
||||
},
|
||||
"options-title": "Wählen Sie aus, bei welchen Aktivitäten Sie benachrichtigt werden möchten",
|
||||
"schedule": {
|
||||
"daily": "Tägliche Zusammenfassung",
|
||||
"instant": "Sofort",
|
||||
@ -1994,6 +1990,13 @@
|
||||
},
|
||||
"title": "Benachrichtigungseinstellungen"
|
||||
},
|
||||
"notifications": {
|
||||
"button-text": "Benachrichtigungen",
|
||||
"deleted-dossier": "Gelöschtes Dossier",
|
||||
"label": "Benachrichtigungen",
|
||||
"mark-all-as-read": "Alle als gelesen markieren",
|
||||
"mark-as": "Als {type, select, read{gelesen} unread{ungelesen} other{}} markieren"
|
||||
},
|
||||
"ocr": {
|
||||
"confirmation-dialog": {
|
||||
"cancel": "Abbrechen",
|
||||
@ -2105,10 +2108,6 @@
|
||||
"warnings-label": "Dialoge und Meldungen",
|
||||
"warnings-subtitle": "„Nicht mehr anzeigen“-Optionen"
|
||||
},
|
||||
"processing": {
|
||||
"basic": "Verarbeitung läuft",
|
||||
"ocr": "OCR"
|
||||
},
|
||||
"processing-status": {
|
||||
"ocr": "OCR",
|
||||
"pending": "Ausstehend",
|
||||
@ -2116,6 +2115,10 @@
|
||||
"processed": "Verarbeitet",
|
||||
"processing": "Verarbeitung läuft"
|
||||
},
|
||||
"processing": {
|
||||
"basic": "Verarbeitung läuft",
|
||||
"ocr": "OCR"
|
||||
},
|
||||
"readonly": "Lesemodus",
|
||||
"readonly-archived": "Lesemodus (archiviert)",
|
||||
"redact-text": {
|
||||
@ -2385,12 +2388,6 @@
|
||||
"red-user-admin": "{count, plural, one{Benutzeradmin} other{Benutzeradmins}}",
|
||||
"regular": "{count, plural, one{regulärer Benutzer} other{reguläre Benutzer}}"
|
||||
},
|
||||
"search": {
|
||||
"active-dossiers": "Dokumente in aktiven Dossiers",
|
||||
"all-dossiers": "Alle Dokumente",
|
||||
"placeholder": "Dokumente durchsuchen...",
|
||||
"this-dossier": "In diesem Dossier"
|
||||
},
|
||||
"search-screen": {
|
||||
"cols": {
|
||||
"assignee": "Bearbeiter",
|
||||
@ -2414,6 +2411,12 @@
|
||||
"no-match": "Der Suchbegriff wurde in keinem der Dokumente gefunden.",
|
||||
"table-header": "{length} {length, plural, one{Suchergebnis} other{Suchergebnisse}}"
|
||||
},
|
||||
"search": {
|
||||
"active-dossiers": "Dokumente in aktiven Dossiers",
|
||||
"all-dossiers": "Alle Dokumente",
|
||||
"placeholder": "Dokumente durchsuchen...",
|
||||
"this-dossier": "In diesem Dossier"
|
||||
},
|
||||
"seconds": "Sekunden",
|
||||
"size": "Größe",
|
||||
"smtp-auth-config": {
|
||||
|
||||
@ -702,6 +702,9 @@
|
||||
}
|
||||
},
|
||||
"content": "Reason",
|
||||
"copilot": {
|
||||
"label": "Copilot"
|
||||
},
|
||||
"dashboard": {
|
||||
"empty-template": {
|
||||
"description": "This template does not contain any dossiers. Create a dossier that applies this ruleset.",
|
||||
|
||||
@ -702,6 +702,9 @@
|
||||
}
|
||||
},
|
||||
"content": "Grund",
|
||||
"copilot": {
|
||||
"label": ""
|
||||
},
|
||||
"dashboard": {
|
||||
"empty-template": {
|
||||
"description": "Diese Vorlage enthält keine Dossiers. Erstellen Sie ein Dossier, um nach diesen Regeln zu schwärzen.",
|
||||
|
||||
@ -702,6 +702,9 @@
|
||||
}
|
||||
},
|
||||
"content": "Reason",
|
||||
"copilot": {
|
||||
"label": "Copilot"
|
||||
},
|
||||
"dashboard": {
|
||||
"empty-template": {
|
||||
"description": "This template does not contain any dossiers. Create a dossier that applies this ruleset.",
|
||||
|
||||
@ -31,3 +31,4 @@ export * from './lib/colors';
|
||||
export * from './lib/component-log';
|
||||
export * from './lib/component-mappings';
|
||||
export * from './lib/component-definitions';
|
||||
export * from './lib/web-socket';
|
||||
|
||||
@ -19,9 +19,9 @@ export class TrashDossier extends TrashItem implements Partial<IDossier> {
|
||||
|
||||
constructor(
|
||||
dossier: IDossier,
|
||||
protected readonly _retentionHours: number,
|
||||
readonly hasRestoreRights: boolean,
|
||||
readonly hasHardDeleteRights: boolean,
|
||||
protected override readonly _retentionHours: number,
|
||||
override readonly hasRestoreRights: boolean,
|
||||
override readonly hasHardDeleteRights: boolean,
|
||||
readonly ownerName: string,
|
||||
) {
|
||||
super(_retentionHours, dossier.softDeletedTime || '-', hasRestoreRights, hasHardDeleteRights);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { TrashItem } from './trash.item';
|
||||
import { File, IFile } from '../files';
|
||||
import { FileAttributes } from '../file-attributes';
|
||||
import { File, IFile } from '../files';
|
||||
import { TrashItem } from './trash.item';
|
||||
|
||||
export class TrashFile extends TrashItem implements Partial<IFile> {
|
||||
readonly type = 'file';
|
||||
@ -22,9 +22,9 @@ export class TrashFile extends TrashItem implements Partial<IFile> {
|
||||
constructor(
|
||||
file: File,
|
||||
readonly dossierTemplateId: string,
|
||||
protected readonly _retentionHours: number,
|
||||
readonly hasRestoreRights: boolean,
|
||||
readonly hasHardDeleteRights: boolean,
|
||||
protected override readonly _retentionHours: number,
|
||||
override readonly hasRestoreRights: boolean,
|
||||
override readonly hasHardDeleteRights: boolean,
|
||||
readonly ownerName: string,
|
||||
readonly fileDossierName: string,
|
||||
) {
|
||||
|
||||
16
libs/red-domain/src/lib/web-socket/analysis-event.ts
Normal file
16
libs/red-domain/src/lib/web-socket/analysis-event.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export const AnalyseStatuses = {
|
||||
PROCESSING: 'PROCESSING',
|
||||
FINISHED: 'FINISHED',
|
||||
} as const;
|
||||
|
||||
export type AnalyseStatus = keyof typeof AnalyseStatuses;
|
||||
|
||||
export interface AnalysisEvent {
|
||||
analyseStatus: AnalyseStatus;
|
||||
analysisNumber: number;
|
||||
dossierId: string;
|
||||
fileId: string;
|
||||
numberOfOCRedPages: number;
|
||||
numberOfPagesToOCR: number;
|
||||
timestamp: string;
|
||||
}
|
||||
2
libs/red-domain/src/lib/web-socket/index.ts
Normal file
2
libs/red-domain/src/lib/web-socket/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './analysis-event';
|
||||
export * from './topics';
|
||||
3
libs/red-domain/src/lib/web-socket/topics.ts
Normal file
3
libs/red-domain/src/lib/web-socket/topics.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const WsTopics = {
|
||||
ANALYSIS: 'analysis-events',
|
||||
} as const;
|
||||
@ -35,6 +35,8 @@
|
||||
"@ngx-translate/core": "16.0.3",
|
||||
"@ngx-translate/http-loader": "16.0.0",
|
||||
"@pdftron/webviewer": "11.1.0",
|
||||
"@stomp/rx-stomp": "^2.0.0",
|
||||
"@stomp/stompjs": "^7.0.0",
|
||||
"chart.js": "4.4.7",
|
||||
"dayjs": "1.11.13",
|
||||
"file-saver": "^2.0.5",
|
||||
@ -56,6 +58,7 @@
|
||||
"scroll-into-view-if-needed": "3.1.0",
|
||||
"streamsaver": "^2.0.5",
|
||||
"tslib": "2.8.1",
|
||||
"uuid": "^11.0.3",
|
||||
"zone.js": "0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
"useDefineForClassFields": false,
|
||||
"strictPropertyInitialization": false,
|
||||
"importHelpers": true,
|
||||
// "noImplicitOverride": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user