Merge branch 'VM/RED-8748' into 'master'
RED-8748 - Integrated component view in DocuMine Closes RED-8748 See merge request redactmanager/red-ui!449
This commit is contained in:
commit
463fddefd3
@ -48,7 +48,6 @@ export class AddEditComponentMappingDialogComponent
|
||||
{
|
||||
protected readonly encodingTypeOptions = Object.keys(FileAttributeEncodingTypes);
|
||||
protected readonly translations = fileAttributeEncodingTypesTranslations;
|
||||
protected readonly iconButtonTypes = IconButtonTypes;
|
||||
activeFile: File;
|
||||
form!: UntypedFormGroup;
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
class="mt-6 mr-10"
|
||||
></redaction-annotation-icon>
|
||||
|
||||
<div class="flex-1">
|
||||
<div [class.flex-1]="!isDocumine">
|
||||
<div>
|
||||
<strong>{{ annotation.superTypeLabel | translate }}</strong>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<div *ngIf="annotation.typeLabel">
|
||||
<div *ngIf="annotation.typeLabel" [class.type-label]="isDocumine">
|
||||
<strong>
|
||||
<span>{{ annotation.descriptor | translate }}</span
|
||||
>:
|
||||
|
||||
@ -3,6 +3,13 @@
|
||||
position: relative;
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
|
||||
.type-label {
|
||||
width: 130px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.active-icon-marker-container {
|
||||
|
||||
@ -3,8 +3,7 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
|
||||
import { Roles } from '@users/roles';
|
||||
import { ImageCategory } from '../../utils/constants';
|
||||
import { ManualRedactionTypes } from '@red/domain';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotation-card',
|
||||
@ -12,8 +11,9 @@ import { ManualRedactionTypes } from '@red/domain';
|
||||
styleUrls: ['./annotation-card.component.scss'],
|
||||
})
|
||||
export class AnnotationCardComponent {
|
||||
readonly roles = Roles;
|
||||
readonly annotationTypesTranslations = annotationTypesTranslations;
|
||||
protected readonly roles = Roles;
|
||||
protected readonly annotationTypesTranslations = annotationTypesTranslations;
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
@Input() annotation: AnnotationWrapper;
|
||||
@Input() isSelected = false;
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
(click)="annotationClicked(annotation.item, $event)"
|
||||
[annotation]="annotation"
|
||||
[id]="'annotation-' + annotation.item.id"
|
||||
[class.documine-wrapper]="isDocumine"
|
||||
></redaction-annotation-wrapper>
|
||||
</ng-container>
|
||||
|
||||
|
||||
@ -10,7 +10,8 @@
|
||||
@include common-mixins.scroll-bar;
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover redaction-annotation-wrapper::ng-deep {
|
||||
&.has-scrollbar:hover redaction-annotation-wrapper::ng-deep,
|
||||
&::ng-deep.documine-wrapper {
|
||||
.annotation {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ChangeDetectorRef, Component, computed, ElementRef, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { HasScrollbarDirective } from '@iqser/common-ui';
|
||||
import { getConfig, HasScrollbarDirective } from '@iqser/common-ui';
|
||||
import { FilterService } from '@iqser/common-ui/lib/filtering';
|
||||
import { IqserEventTarget } from '@iqser/common-ui/lib/utils';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
@ -26,6 +26,7 @@ export class AnnotationsListComponent extends HasScrollbarDirective {
|
||||
}
|
||||
return [] as EarmarkGroup[];
|
||||
});
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
|
||||
constructor(
|
||||
protected readonly _elementRef: ElementRef,
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
<button [class.active]="true" [matTooltipPosition]="'above'" [matTooltip]="'documine-export.document-tooltip' | translate" class="red-tab">
|
||||
{{ 'documine-export.document' | translate }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
[matMenuTriggerFor]="bulkComponentDownloadMenu"
|
||||
[matTooltipPosition]="'above'"
|
||||
[matTooltip]="'documine-export.export-tooltip' | translate"
|
||||
class="red-tab"
|
||||
>
|
||||
{{ 'documine-export.export' | translate }}
|
||||
</button>
|
||||
|
||||
<mat-menu #bulkComponentDownloadMenu="matMenu">
|
||||
<button (click)="downloadComponentAsJSON()" [innerHTML]="'component-download.json' | translate" mat-menu-item></button>
|
||||
<button (click)="downloadComponentAsXML()" [innerHTML]="'component-download.xml' | translate" mat-menu-item></button>
|
||||
</mat-menu>
|
||||
@ -0,0 +1,22 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { Dossier } from '@red/domain';
|
||||
import { ComponentLogService } from '@services/files/component-log.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-documine-export',
|
||||
templateUrl: './documine-export.component.html',
|
||||
})
|
||||
export class DocumineExportComponent {
|
||||
@Input() dossier: Dossier;
|
||||
|
||||
constructor(private readonly _componentLogService: ComponentLogService) {}
|
||||
|
||||
downloadComponentAsJSON() {
|
||||
return firstValueFrom(this._componentLogService.exportJSON(this.dossier.dossierTemplateId, this.dossier.dossierId));
|
||||
}
|
||||
|
||||
async downloadComponentAsXML() {
|
||||
return firstValueFrom(this._componentLogService.exportXML(this.dossier.dossierTemplateId, this.dossier.dossierId));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
<div class="component-value" [ngClass]="{ selected: selected, editing: editing }" (click)="select()">
|
||||
<div class="component">{{ entryLabel }}</div>
|
||||
<div class="value" *ngIf="!editing; else editValue">
|
||||
<div class="text">
|
||||
<span
|
||||
*ngFor="let componentValue of entry.componentValues"
|
||||
[innerHTML]="transformNewLines(componentValue.value ?? componentValue.originalValue)"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<iqser-circle-button
|
||||
(action)="edit()"
|
||||
*ngIf="canEdit"
|
||||
[tooltip]="'component-management.actions.edit' | translate"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
<div class="changes-dot" *ngIf="hasUpdatedValues && canEdit"></div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-icon *ngIf="!editing" class="arrow-right" svgIcon="red:arrow-right"></mat-icon>
|
||||
</div>
|
||||
|
||||
<ng-template #editValue>
|
||||
<div cdkDropList (cdkDropListDropped)="drop($event)">
|
||||
<div *ngFor="let value of entry.componentValues; let index = index" class="editing-value" cdkDrag>
|
||||
<mat-icon class="draggable" svgIcon="red:draggable-dots" cdkDragHandle></mat-icon>
|
||||
<div class="iqser-input-group w-full">
|
||||
<textarea [(ngModel)]="value.value" rows="1" type="text"></textarea>
|
||||
</div>
|
||||
<iqser-circle-button
|
||||
(action)="removeValue(index)"
|
||||
[tooltip]="'component-management.actions.delete' | translate"
|
||||
class="remove-value"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editing-actions">
|
||||
<iqser-icon-button
|
||||
(action)="save()"
|
||||
[disabled]="disabled"
|
||||
[label]="'component-management.actions.save' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
></iqser-icon-button>
|
||||
<div (click)="deselect($event)" class="all-caps-label cancel" translate="component-management.actions.cancel"></div>
|
||||
<div class="flex right">
|
||||
<iqser-circle-button
|
||||
*ngIf="hasUpdatedValues && canEdit"
|
||||
(action)="undo()"
|
||||
[showDot]="true"
|
||||
[tooltip]="'component-management.actions.undo' | translate"
|
||||
class="undo-value"
|
||||
icon="red:undo"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="add()"
|
||||
[tooltip]="'component-management.actions.add' | translate"
|
||||
class="add-value"
|
||||
icon="iqser:plus"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
@ -0,0 +1,158 @@
|
||||
.component-value {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 10px 0 10px 0;
|
||||
margin-left: 26px;
|
||||
margin-right: 26px;
|
||||
position: relative;
|
||||
|
||||
.component {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.value {
|
||||
width: 60%;
|
||||
display: flex;
|
||||
|
||||
.text {
|
||||
width: 80%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
iqser-circle-button {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.changes-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: var(--iqser-primary);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.header {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) scale(0.7);
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
&:not(.header):hover,
|
||||
&.selected {
|
||||
background-color: var(--iqser-grey-8);
|
||||
border-left: 4px solid var(--iqser-primary);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
||||
&:not(.editing) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.component {
|
||||
margin-left: 22px;
|
||||
}
|
||||
.value {
|
||||
margin-right: 26px;
|
||||
|
||||
.actions {
|
||||
iqser-circle-button {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&.editing {
|
||||
flex-direction: column;
|
||||
|
||||
.editing-value {
|
||||
display: flex;
|
||||
margin: 10px 0 10px 22px;
|
||||
|
||||
.iqser-input-group {
|
||||
margin-top: 0;
|
||||
|
||||
textarea::-webkit-resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
textarea::-moz-resizer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
iqser-circle-button {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
margin-top: 7px;
|
||||
cursor: grab;
|
||||
}
|
||||
}
|
||||
|
||||
.editing-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 30px 10px 10px 22px;
|
||||
|
||||
.right {
|
||||
margin-left: auto;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .add-value {
|
||||
mat-icon {
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .undo-value {
|
||||
mat-icon {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .remove-value {
|
||||
mat-icon {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
.cdk-drag-preview {
|
||||
display: flex;
|
||||
|
||||
.draggable {
|
||||
margin-top: 7px;
|
||||
cursor: grab;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,167 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { IComponentLogEntry } from '@red/domain';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, IqserDialog } from '@iqser/common-ui';
|
||||
import { FilterService } from '@common-ui/filtering';
|
||||
import { RevertValueDialogComponent } from '../../dialogs/docu-mine/revert-value-dialog/revert-value-dialog.component';
|
||||
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { KeyValuePipe, NgClass, NgForOf, NgIf } from '@angular/common';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-editable-structured-component-value [entry] [canEdit]',
|
||||
templateUrl: './editable-structured-component-value.component.html',
|
||||
styleUrls: ['/editable-structured-component-value.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
CircleButtonComponent,
|
||||
NgClass,
|
||||
TranslateModule,
|
||||
KeyValuePipe,
|
||||
CdkDropList,
|
||||
MatIcon,
|
||||
IconButtonComponent,
|
||||
CdkDrag,
|
||||
NgIf,
|
||||
NgForOf,
|
||||
CdkDragHandle,
|
||||
FormsModule,
|
||||
],
|
||||
})
|
||||
export class EditableStructuredComponentValueComponent implements OnInit {
|
||||
@Input() entry: IComponentLogEntry;
|
||||
@Input() canEdit: boolean;
|
||||
@Output() readonly deselectLast = new EventEmitter();
|
||||
@Output() readonly overrideValue = new EventEmitter<IComponentLogEntry>();
|
||||
@Output() readonly revertOverride = new EventEmitter<string>();
|
||||
|
||||
selected = false;
|
||||
|
||||
protected entryLabel: string;
|
||||
protected editing = false;
|
||||
protected hasUpdatedValues = false;
|
||||
protected initialEntry: IComponentLogEntry;
|
||||
|
||||
protected readonly iconButtonTypes = IconButtonTypes;
|
||||
|
||||
constructor(
|
||||
private readonly _filtersService: FilterService,
|
||||
private readonly _iqserDialog: IqserDialog,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.initialEntry = this.#initialEntry;
|
||||
this.hasUpdatedValues = this.#hasUpdatedValues;
|
||||
this.entryLabel = this.parseName(this.entry.name);
|
||||
this.deselect();
|
||||
}
|
||||
|
||||
select() {
|
||||
if (!this.editing) {
|
||||
if (this.selected) {
|
||||
this.deselect();
|
||||
return;
|
||||
}
|
||||
this.deselectLast.emit();
|
||||
this.selected = true;
|
||||
this.#setWorkloadFilters();
|
||||
}
|
||||
}
|
||||
|
||||
edit() {
|
||||
this.deselectLast.emit();
|
||||
this.selected = true;
|
||||
this.editing = true;
|
||||
}
|
||||
|
||||
deselect($event?: MouseEvent) {
|
||||
$event?.stopImmediatePropagation();
|
||||
this.selected = false;
|
||||
this.editing = false;
|
||||
this._filtersService.deactivateFilters({ primaryFiltersSlug: 'primaryFilters' });
|
||||
}
|
||||
|
||||
removeValue(index: number) {
|
||||
this.entry.componentValues.splice(index, 1);
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
for (let i = 0; i < this.entry.componentValues.length; i++) {
|
||||
if (this.entry.componentValues[i].value !== this.initialEntry.componentValues[i]?.value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.entry.componentValues.length === this.initialEntry.componentValues.length;
|
||||
}
|
||||
|
||||
save() {
|
||||
this.entry.overridden = true;
|
||||
this.overrideValue.emit(this.entry);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
async undo() {
|
||||
const dialog = this._iqserDialog.openDefault(RevertValueDialogComponent, { data: { entry: this.entry }, width: '800px' });
|
||||
const result = await dialog.result();
|
||||
if (result) {
|
||||
this.revertOverride.emit(this.entry.name);
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
add() {
|
||||
this.entry.componentValues.push({
|
||||
componentRuleId: null,
|
||||
entityReferences: [],
|
||||
originalValue: null,
|
||||
value: '',
|
||||
valueDescription: '',
|
||||
});
|
||||
}
|
||||
|
||||
drop(event: CdkDragDrop<string>) {
|
||||
moveItemInArray(this.entry.componentValues, event.previousIndex, event.currentIndex);
|
||||
}
|
||||
|
||||
parseName(name: string) {
|
||||
return name.replaceAll('_', ' ');
|
||||
}
|
||||
|
||||
transformNewLines(value: string) {
|
||||
return value.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
get #hasUpdatedValues() {
|
||||
for (const value of this.entry.componentValues) {
|
||||
if (value.originalValue === null && value.value === '') {
|
||||
continue;
|
||||
}
|
||||
if (value.originalValue !== value.value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get #initialEntry() {
|
||||
return JSON.parse(JSON.stringify(this.entry));
|
||||
}
|
||||
|
||||
#setWorkloadFilters() {
|
||||
this._filtersService.deactivateFilters({ primaryFiltersSlug: 'primaryFilters' });
|
||||
|
||||
const filterGroup = this._filtersService.getGroup('primaryFilters');
|
||||
for (const filter of filterGroup.filters) {
|
||||
const nestedFilter = filter.children.find(f => f.label === this.entryLabel);
|
||||
if (nestedFilter) {
|
||||
this._filtersService.filterCheckboxClicked({ nestedFilter, filterGroup, primaryFiltersSlug: 'primaryFilters' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
<div class="page-header">
|
||||
<div class="flex">
|
||||
<redaction-view-switch *ngIf="!isDocumine"></redaction-view-switch>
|
||||
<redaction-documine-export *ngIf="isDocumine" [dossier]="state.dossier()"></redaction-documine-export>
|
||||
</div>
|
||||
|
||||
<!-- TODO: mode this file preview header to a separate component-->
|
||||
<div #actionsWrapper class="flex-2 actions-container">
|
||||
<div class="assignee" [class.documine]="isDocumine">
|
||||
<div class="vertical-line" *ngIf="isDocumine"></div>
|
||||
|
||||
<redaction-processing-indicator [file]="file" class="mr-16"></redaction-processing-indicator>
|
||||
|
||||
<redaction-user-management></redaction-user-management>
|
||||
|
||||
<ng-container *ngIf="permissionsService.isApprover(state.dossier()) && !!file.lastReviewer">
|
||||
<div class="vertical-line"></div>
|
||||
|
||||
<div class="all-caps-label mr-16 ml-8 label">
|
||||
{{ 'file-preview.last-assignee' | translate }}
|
||||
</div>
|
||||
|
||||
<iqser-initials-avatar [user]="lastAssignee()" [withName]="true"></iqser-initials-avatar>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="vertical-line" *ngIf="!isDocumine"></div>
|
||||
|
||||
<!-- TODO: mode these actions to a separate component -->
|
||||
|
||||
<redaction-file-actions
|
||||
[dossier]="state.dossier()"
|
||||
[file]="file"
|
||||
[helpModeKeyPrefix]="'editor'"
|
||||
[minWidth]="width"
|
||||
type="file-preview"
|
||||
iqserDisableStopPropagation
|
||||
></redaction-file-actions>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="getTables()"
|
||||
*allow="roles.getTables"
|
||||
[icon]="'red:csv'"
|
||||
[tooltip]="'file-preview.get-tables' | translate"
|
||||
class="ml-2"
|
||||
iqserDisableStopPropagation
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="toggleFullScreen()"
|
||||
[attr.help-mode-key]="'editor_full_screen'"
|
||||
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
|
||||
[tooltip]="'file-preview.fullscreen' | translate"
|
||||
class="ml-2"
|
||||
iqserDisableStopPropagation
|
||||
></iqser-circle-button>
|
||||
|
||||
<!-- Dev Mode Features-->
|
||||
<iqser-circle-button
|
||||
(action)="downloadOriginalFile(file)"
|
||||
*ngIf="isIqserDevMode"
|
||||
[tooltip]="'file-preview.download-original-file' | translate"
|
||||
[type]="circleButtonTypes.primary"
|
||||
class="ml-8"
|
||||
icon="iqser:download"
|
||||
iqserDisableStopPropagation
|
||||
></iqser-circle-button>
|
||||
|
||||
<!-- End Dev Mode Features-->
|
||||
|
||||
<iqser-circle-button
|
||||
*ngIf="!fullScreen"
|
||||
[attr.help-mode-key]="'editor_close'"
|
||||
[routerLink]="state.dossier().routerLink"
|
||||
[tooltip]="'common.close' | translate"
|
||||
class="ml-8"
|
||||
icon="iqser:close"
|
||||
iqserDisableStopPropagation
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,25 @@
|
||||
.page-header {
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
.assignee {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.documine {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-line {
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
background-color: var(--iqser-separator);
|
||||
margin: 0 16px;
|
||||
}
|
||||
@ -0,0 +1,221 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
computed,
|
||||
ElementRef,
|
||||
HostListener,
|
||||
Input,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { Roles } from '@users/roles';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
getConfig,
|
||||
HelpModeService,
|
||||
IqserDialog,
|
||||
IqserPermissionsService,
|
||||
isIqserDevMode,
|
||||
LoadingService,
|
||||
} from '@iqser/common-ui';
|
||||
import { Bind, Debounce, OnDetach } from '@iqser/common-ui/lib/utils';
|
||||
import { File } from '@red/domain';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import JSZip from 'jszip';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
|
||||
import { AnnotationDrawService } from '../../../pdf-viewer/services/annotation-draw.service';
|
||||
import { TablesService } from '../../services/tables.service';
|
||||
import { ALL_HOTKEYS } from '../../utils/constants';
|
||||
import { Router } from '@angular/router';
|
||||
import { AnnotationActionsService } from '../../services/annotation-actions.service';
|
||||
import { FileDataService } from '../../services/file-data.service';
|
||||
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { download } from '@utils/file-download-utils';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { FileManagementService } from '@services/files/file-management.service';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-header',
|
||||
templateUrl: './file-header.component.html',
|
||||
styleUrls: ['/file-header.component.scss'],
|
||||
})
|
||||
export class FileHeaderComponent implements OnInit, AfterViewInit, OnDetach, OnDestroy {
|
||||
@ViewChild('actionsWrapper', { static: false }) private readonly _actionsWrapper: ElementRef;
|
||||
@Input() file: File;
|
||||
|
||||
protected readonly roles = Roles;
|
||||
protected readonly circleButtonTypes = CircleButtonTypes;
|
||||
|
||||
readonly lastAssignee = computed(() => this.getLastAssignee());
|
||||
readonly isIqserDevMode = isIqserDevMode();
|
||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
width: number;
|
||||
fullScreen = false;
|
||||
|
||||
constructor(
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _iqserPermissionsService: IqserPermissionsService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _annotationDrawService: AnnotationDrawService,
|
||||
private readonly _tablesService: TablesService,
|
||||
private readonly _router: Router,
|
||||
private readonly _ngZone: NgZone,
|
||||
private readonly _helpModeService: HelpModeService,
|
||||
private readonly _annotationActionsService: AnnotationActionsService,
|
||||
private readonly _fileDataService: FileDataService,
|
||||
private readonly _annotationManager: REDAnnotationManager,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _fileManagementService: FileManagementService,
|
||||
private readonly _multiSelectService: MultiSelectService,
|
||||
readonly state: FilePreviewStateService,
|
||||
readonly permissionsService: PermissionsService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||
this._updateItemWidth(entries[0]);
|
||||
});
|
||||
_observer.observe(this._actionsWrapper.nativeElement);
|
||||
}
|
||||
|
||||
ngOnDetach() {
|
||||
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
|
||||
}
|
||||
|
||||
async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) {
|
||||
const originalFile = this._fileManagementService.downloadOriginal(dossierId, fileId, 'response', cacheIdentifier);
|
||||
download(await firstValueFrom(originalFile), filename);
|
||||
}
|
||||
|
||||
getLastAssignee() {
|
||||
const { isApproved, lastReviewer, lastApprover } = this.state.file();
|
||||
const isRss = this._iqserPermissionsService.has(this.roles.getRss);
|
||||
return isApproved ? (isRss ? lastReviewer : lastApprover) : lastReviewer;
|
||||
}
|
||||
|
||||
async getTables() {
|
||||
this._loadingService.start();
|
||||
|
||||
const currentPage = this._pdf.currentPage();
|
||||
const tables = await this._tablesService.get(this.state.dossierId, this.state.fileId, this._pdf.currentPage());
|
||||
await this._annotationDrawService.drawTables(tables, currentPage, this.state.dossierTemplateId);
|
||||
|
||||
const filename = this.state.file().filename;
|
||||
const zip = new JSZip();
|
||||
|
||||
tables.forEach((t, index) => {
|
||||
const blob = new Blob([atob(t.csvAsBytes)], {
|
||||
type: 'text/csv;charset=utf-8',
|
||||
});
|
||||
zip.file(filename + '_page' + currentPage + '_table' + (index + 1) + '.csv', blob);
|
||||
});
|
||||
|
||||
saveAs(await zip.generateAsync({ type: 'blob' }), filename + '_tables.zip');
|
||||
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
toggleFullScreen() {
|
||||
this.fullScreen = !this.fullScreen;
|
||||
if (this.fullScreen) {
|
||||
this.#openFullScreen();
|
||||
} else {
|
||||
this.closeFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
closeFullScreen() {
|
||||
if (!!document.fullscreenElement && document.exitFullscreen) {
|
||||
document.exitFullscreen().then();
|
||||
}
|
||||
}
|
||||
|
||||
@Bind()
|
||||
fullscreenListener() {
|
||||
if (!document.fullscreenElement) {
|
||||
this.fullScreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keyup', ['$event'])
|
||||
handleKeyEvent($event: KeyboardEvent) {
|
||||
if (this._router.url.indexOf('/file/') < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ALL_HOTKEYS.includes($event.key) || this._dialog.openDialogs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (['Escape'].includes($event.key)) {
|
||||
$event.preventDefault();
|
||||
if (this._annotationManager.resizingAnnotationId) {
|
||||
const resizedAnnotation = this._fileDataService
|
||||
.annotations()
|
||||
.find(annotation => annotation.id === this._annotationManager.resizingAnnotationId);
|
||||
this._annotationActionsService.cancelResize(resizedAnnotation).then();
|
||||
}
|
||||
|
||||
if (this._annotationManager.selected.length) {
|
||||
this._annotationManager.deselectAll();
|
||||
}
|
||||
|
||||
if (this._multiSelectService.active()) {
|
||||
this._multiSelectService.deactivate();
|
||||
}
|
||||
this.fullScreen = false;
|
||||
this.closeFullScreen();
|
||||
this._changeRef.markForCheck();
|
||||
}
|
||||
|
||||
if (!$event.ctrlKey && !$event.metaKey && ['f', 'F'].includes($event.key)) {
|
||||
// if you type in an input, don't toggle full-screen
|
||||
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
|
||||
return;
|
||||
}
|
||||
this.toggleFullScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
if (['h', 'H'].includes($event.key)) {
|
||||
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
|
||||
return;
|
||||
}
|
||||
this._ngZone.run(() => {
|
||||
window.focus();
|
||||
this._helpModeService.activateHelpMode(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#openFullScreen() {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement.requestFullscreen) {
|
||||
documentElement.requestFullscreen().then();
|
||||
}
|
||||
}
|
||||
|
||||
@Debounce(30)
|
||||
private _updateItemWidth(entry: ResizeObserverEntry): void {
|
||||
this.width = entry.contentRect.width;
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
}
|
||||
@ -1,50 +1,47 @@
|
||||
<div
|
||||
*ngIf="excludedPagesService.shown(); else selectAndFilter"
|
||||
class="right-title heading"
|
||||
translate="file-preview.tabs.exclude-pages.label"
|
||||
>
|
||||
<div>
|
||||
<iqser-circle-button
|
||||
(action)="excludedPagesService.toggle()"
|
||||
[tooltip]="'file-preview.tabs.exclude-pages.close' | translate"
|
||||
icon="iqser:close"
|
||||
tooltipPosition="before"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #selectAndFilter>
|
||||
<div class="right-title heading">
|
||||
{{ title() | translate }}
|
||||
<ng-container *ngIf="!isDocumine">
|
||||
<div
|
||||
*ngIf="excludedPagesService.shown(); else selectAndFilter"
|
||||
class="right-title heading"
|
||||
translate="file-preview.tabs.exclude-pages.label"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
(click)="multiSelectService.activate()"
|
||||
*ngIf="multiSelectService.enabled() && multiSelectService.inactive()"
|
||||
[attr.help-mode-key]="'workload_bulk_selection'"
|
||||
class="all-caps-label primary pointer"
|
||||
translate="file-preview.tabs.annotations.select"
|
||||
></div>
|
||||
|
||||
<iqser-popup-filter
|
||||
*ngIf="documentInfoService.hidden()"
|
||||
[actionsTemplate]="annotationFilterActionTemplate"
|
||||
[attr.help-mode-key]="'workload_filter'"
|
||||
[fileId]="state.file()?.id"
|
||||
[primaryFiltersSlug]="'primaryFilters'"
|
||||
[secondaryFiltersSlug]="'secondaryFilters'"
|
||||
></iqser-popup-filter>
|
||||
<iqser-circle-button
|
||||
(action)="excludedPagesService.toggle()"
|
||||
[tooltip]="'file-preview.tabs.exclude-pages.close' | translate"
|
||||
icon="iqser:close"
|
||||
tooltipPosition="before"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #selectAndFilter>
|
||||
<div class="right-title heading">
|
||||
{{ title() | translate }}
|
||||
<div>
|
||||
<div
|
||||
(click)="multiSelectService.activate()"
|
||||
*ngIf="multiSelectService.enabled() && multiSelectService.inactive()"
|
||||
[attr.help-mode-key]="'workload_bulk_selection'"
|
||||
class="all-caps-label primary pointer"
|
||||
translate="file-preview.tabs.annotations.select"
|
||||
></div>
|
||||
|
||||
<ng-container *ngTemplateOutlet="annotationsFilter"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<div class="right-content">
|
||||
<redaction-readonly-banner
|
||||
*ngIf="showAnalysisDisabledBanner; else readOnlyBanner"
|
||||
[customTranslation]="translations.analysisDisabled"
|
||||
></redaction-readonly-banner>
|
||||
<ng-template #readOnlyBanner>
|
||||
<redaction-readonly-banner *ngIf="state.isReadonly()"></redaction-readonly-banner>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!isDocumine">
|
||||
<redaction-readonly-banner
|
||||
*ngIf="showAnalysisDisabledBanner; else readOnlyBanner"
|
||||
[customTranslation]="translations.analysisDisabled"
|
||||
></redaction-readonly-banner>
|
||||
<ng-template #readOnlyBanner>
|
||||
<redaction-readonly-banner *ngIf="state.isReadonly()"></redaction-readonly-banner>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="multiSelectService.active()" class="multi-select">
|
||||
<div class="selected-wrapper">
|
||||
@ -88,22 +85,20 @@
|
||||
(click)="scrollQuickNavFirst()"
|
||||
[class.disabled]="pdf.currentPage() === 1"
|
||||
[matTooltip]="'file-preview.quick-nav.jump-first' | translate"
|
||||
[class.documine-height]="isDocumine"
|
||||
class="jump"
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="iqser:nav-first"></mat-icon>
|
||||
</div>
|
||||
|
||||
<redaction-pages
|
||||
(click)="pagesPanelActive = true"
|
||||
[displayedAnnotations]="displayedAnnotations"
|
||||
[pages]="displayedPages"
|
||||
></redaction-pages>
|
||||
<redaction-pages (click)="pagesPanelActive = true" [pages]="displayedPages"></redaction-pages>
|
||||
|
||||
<div
|
||||
(click)="scrollQuickNavLast()"
|
||||
[class.disabled]="pdf.currentPage() === state.file()?.numberOfPages"
|
||||
[matTooltip]="'file-preview.quick-nav.jump-last' | translate"
|
||||
[class.documine-height]="isDocumine"
|
||||
class="jump"
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
@ -111,7 +106,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="content" [class.documine-width]="isDocumine">
|
||||
<div
|
||||
*ngIf="!viewModeService.isEarmarks()"
|
||||
[attr.anotation-page-header]="pdf.currentPage()"
|
||||
@ -119,21 +114,23 @@
|
||||
class="workload-separator"
|
||||
>
|
||||
<span *ngIf="!!pdf.currentPage()" class="flex-align-items-center">
|
||||
<iqser-circle-button
|
||||
(action)="excludedPagesService.toggle()"
|
||||
*ngIf="currentPageIsExcluded()"
|
||||
[size]="14"
|
||||
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
|
||||
class="mr-10 primary"
|
||||
icon="red:exclude-pages"
|
||||
tooltipPosition="above"
|
||||
></iqser-circle-button>
|
||||
<ng-container *ngIf="!isDocumine; else documineHeader">
|
||||
<iqser-circle-button
|
||||
(action)="excludedPagesService.toggle()"
|
||||
*ngIf="currentPageIsExcluded()"
|
||||
[size]="14"
|
||||
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
|
||||
class="mr-10 primary"
|
||||
icon="red:exclude-pages"
|
||||
tooltipPosition="above"
|
||||
></iqser-circle-button>
|
||||
|
||||
<span
|
||||
[translateParams]="{ page: pdf.currentPage(), count: activeAnnotations.length }"
|
||||
[translate]="'page'"
|
||||
class="all-caps-label"
|
||||
></span>
|
||||
<span
|
||||
[translateParams]="{ page: pdf.currentPage(), count: activeAnnotations.length }"
|
||||
[translate]="'page'"
|
||||
class="all-caps-label"
|
||||
></span>
|
||||
</ng-container>
|
||||
</span>
|
||||
|
||||
<div *ngIf="multiSelectService.active()">
|
||||
@ -231,3 +228,19 @@
|
||||
iqserPreventDefault
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #documineHeader>
|
||||
<span [translate]="'annotations'"></span>
|
||||
<ng-container *ngTemplateOutlet="annotationsFilter"></ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #annotationsFilter>
|
||||
<iqser-popup-filter
|
||||
*ngIf="documentInfoService.hidden()"
|
||||
[actionsTemplate]="annotationFilterActionTemplate"
|
||||
[attr.help-mode-key]="'workload_filter'"
|
||||
[fileId]="state.file()?.id"
|
||||
[primaryFiltersSlug]="'primaryFilters'"
|
||||
[secondaryFiltersSlug]="'secondaryFilters'"
|
||||
></iqser-popup-filter>
|
||||
</ng-template>
|
||||
|
||||
@ -32,7 +32,33 @@
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.documine-width {
|
||||
width: calc(var(--documine-workload-content-width));
|
||||
border-right: 1px solid var(--iqser-separator);
|
||||
z-index: 1;
|
||||
|
||||
.workload-separator {
|
||||
min-height: 37px;
|
||||
background: var(--iqser-grey-8);
|
||||
|
||||
.flex-align-items-center {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
|
||||
::ng-deep span {
|
||||
color: var(--iqser-text);
|
||||
font-size: var(--iqser-font-size);
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.quick-navigation,
|
||||
@ -46,7 +72,8 @@
|
||||
|
||||
.quick-navigation {
|
||||
border-right: 1px solid var(--iqser-separator);
|
||||
min-width: 61px;
|
||||
border-left: 1px solid var(--iqser-separator);
|
||||
min-width: var(--qiuck-navigation-width);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -59,6 +86,10 @@
|
||||
cursor: pointer;
|
||||
transition: background-color 0.25s;
|
||||
|
||||
&.documine-height {
|
||||
min-height: 37px;
|
||||
}
|
||||
|
||||
&:not(.disabled):hover {
|
||||
background-color: var(--iqser-tab-hover);
|
||||
}
|
||||
|
||||
@ -38,7 +38,6 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||
export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
|
||||
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
|
||||
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly #isIqserDevMode = this._userPreferenceService.isIqserDevMode;
|
||||
displayedAnnotations = new Map<number, AnnotationWrapper[]>();
|
||||
displayedPages: number[] = [];
|
||||
@ -51,6 +50,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
||||
);
|
||||
protected readonly currentPageIsExcluded = computed(() => this.state.file().excludedPages.includes(this.pdf.currentPage()));
|
||||
protected readonly translations = workloadTranslations;
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
|
||||
constructor(
|
||||
readonly filterService: FilterService,
|
||||
@ -362,7 +362,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
||||
annotations = annotations.filter(a => !a.isRemoved);
|
||||
}
|
||||
|
||||
if (this.#isDocumine && !this.#isIqserDevMode) {
|
||||
if (this.isDocumine && !this.#isIqserDevMode) {
|
||||
annotations = annotations.filter(a => !a.isOCR);
|
||||
}
|
||||
|
||||
@ -476,6 +476,6 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
|
||||
|
||||
get showAnalysisDisabledBanner() {
|
||||
const file = this.state.file();
|
||||
return this.#isDocumine && file.excludedFromAutomaticAnalysis && file.workflowStatus !== WorkflowFileStatuses.APPROVED;
|
||||
return this.isDocumine && file.excludedFromAutomaticAnalysis && file.workflowStatus !== WorkflowFileStatuses.APPROVED;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@ export class PagesComponent implements AfterViewInit {
|
||||
readonly #listingService = inject(AnnotationsListingService);
|
||||
protected readonly _pdf = inject(PdfViewer);
|
||||
@Input({ required: true }) pages: List<number>;
|
||||
@Input({ required: true }) displayedAnnotations: Map<number, AnnotationWrapper[]>;
|
||||
readonly viewedPages$ = inject(ViewedPagesMapService).get$(this.#state.fileId);
|
||||
|
||||
ngAfterViewInit() {
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
<div class="components-header">
|
||||
<span [translate]="'component-management.components'"></span>
|
||||
<iqser-popup-filter [primaryFiltersSlug]="'componentLogFilters'"></iqser-popup-filter>
|
||||
</div>
|
||||
|
||||
<div *ngIf="displayedComponents$ | async as displayedComponents" class="components-container">
|
||||
<div class="component-row">
|
||||
<div class="header">
|
||||
<div class="component">{{ 'component-management.table-header.component' | translate }}</div>
|
||||
<div class="value">{{ 'component-management.table-header.value' | translate }}</div>
|
||||
</div>
|
||||
<div class="row-separator"></div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let entry of displayedComponents" class="component-row">
|
||||
<redaction-editable-structured-component-value
|
||||
#editableComponent
|
||||
[entry]="entry"
|
||||
[canEdit]="canEdit"
|
||||
(deselectLast)="deselectLast()"
|
||||
(revertOverride)="revertOverride($event)"
|
||||
(overrideValue)="overrideValue($event)"
|
||||
></redaction-editable-structured-component-value>
|
||||
<div class="row-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,55 @@
|
||||
.components-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-height: 36px;
|
||||
background: var(--iqser-grey-8);
|
||||
border-bottom: 1px solid var(--iqser-separator);
|
||||
padding: 0 10px 0 26px;
|
||||
|
||||
::ng-deep span {
|
||||
color: var(--iqser-text);
|
||||
font-size: var(--iqser-font-size);
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.components-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 12px;
|
||||
overflow: scroll;
|
||||
height: calc(100% - 40px);
|
||||
|
||||
.component-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 13px;
|
||||
margin-right: 13px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
padding: 10px 26px;
|
||||
font-weight: 600;
|
||||
|
||||
:first-child {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
|
||||
.row-separator {
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid var(--iqser-separator);
|
||||
}
|
||||
border-bottom: 1px solid var(--iqser-separator);
|
||||
margin-left: 26px;
|
||||
margin-right: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
import { Component, Input, OnInit, signal, ViewChildren } from '@angular/core';
|
||||
import { ComponentLogEntry, Dictionary, File, IComponentLogEntry, WorkflowFileStatuses } from '@red/domain';
|
||||
import { IconButtonTypes, LoadingService } from '@iqser/common-ui';
|
||||
import { ComponentLogService } from '@services/files/component-log.service';
|
||||
import { FilesMapService } from '@services/files/files-map.service';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { combineLatest, firstValueFrom, Observable } from 'rxjs';
|
||||
import { List } from '@common-ui/utils';
|
||||
import { EditableStructuredComponentValueComponent } from '../editable-structured-component-value/editable-structured-component-value.component';
|
||||
import { FilterService } from '@common-ui/filtering';
|
||||
import { ComponentLogFilterService } from '../../services/component-log-filter.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { toObservable } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-structured-component-management',
|
||||
templateUrl: './structured-component-management.component.html',
|
||||
styleUrls: ['/structured-component-management.component.scss'],
|
||||
})
|
||||
export class StructuredComponentManagementComponent implements OnInit {
|
||||
@Input() file: File;
|
||||
@Input() dictionaries: Dictionary[];
|
||||
|
||||
@ViewChildren('editableComponent') editableComponents: List<EditableStructuredComponentValueComponent>;
|
||||
|
||||
protected readonly componentLogData = signal<ComponentLogEntry[] | undefined>(undefined);
|
||||
protected readonly componentLogData$ = toObservable(this.componentLogData);
|
||||
protected readonly openScmDialogByDefault = signal(this.userPreferences.getOpenScmDialogByDefault());
|
||||
protected readonly iconButtonTypes = IconButtonTypes;
|
||||
protected displayedComponents$: Observable<ComponentLogEntry[]>;
|
||||
|
||||
constructor(
|
||||
private readonly _componentLogService: ComponentLogService,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _componentLogFilterService: ComponentLogFilterService,
|
||||
private readonly _filterService: FilterService,
|
||||
readonly userPreferences: UserPreferenceService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.#loadData();
|
||||
this.displayedComponents$ = this.#displayedComponents$();
|
||||
}
|
||||
|
||||
#displayedComponents$() {
|
||||
const componentLogFilters$ = this._filterService.getFilterModels$('componentLogFilters');
|
||||
return combineLatest([this.componentLogData$, componentLogFilters$]).pipe(
|
||||
map(([components, filters]) => this._componentLogFilterService.filterComponents(components, filters)),
|
||||
);
|
||||
}
|
||||
|
||||
deselectLast() {
|
||||
const lastSelected = this.editableComponents.find(c => c.selected);
|
||||
if (lastSelected) {
|
||||
lastSelected.deselect();
|
||||
}
|
||||
}
|
||||
|
||||
get canEdit() {
|
||||
return this.file.workflowStatus !== WorkflowFileStatuses.APPROVED;
|
||||
}
|
||||
|
||||
async toggleOpenScmDialogByDefault() {
|
||||
await this.userPreferences.toggleOpenScmDialogByDefault();
|
||||
await this.userPreferences.reload();
|
||||
this.openScmDialogByDefault.set(this.userPreferences.getOpenScmDialogByDefault());
|
||||
}
|
||||
|
||||
async revertOverride(originalKey: string) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(
|
||||
this._componentLogService.revertOverride(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, [originalKey]),
|
||||
);
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
async overrideValue(componentLogEntry: IComponentLogEntry) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(
|
||||
this._componentLogService.override(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, componentLogEntry),
|
||||
);
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
async #loadData(): Promise<void> {
|
||||
this._loadingService.start();
|
||||
const componentLogData = await firstValueFrom(
|
||||
this._componentLogService.getComponentLogData(
|
||||
this.file.dossierTemplateId,
|
||||
this.file.dossierId,
|
||||
this.file.fileId,
|
||||
this.dictionaries,
|
||||
),
|
||||
);
|
||||
this.#computeFilters(componentLogData);
|
||||
this.componentLogData.set(componentLogData);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
#computeFilters(componentLogs: ComponentLogEntry[]) {
|
||||
const filterGroups = this._componentLogFilterService.filterGroups(componentLogs);
|
||||
this._filterService.addFilterGroups(filterGroups);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<section class="dialog">
|
||||
<div [translate]="'revert-value-dialog.title'" class="dialog-header heading-l"></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<span *ngFor="let value of entry.componentValues">
|
||||
{{ value.valueDescription }}
|
||||
</span>
|
||||
<div class="compare-values-container">
|
||||
<div>
|
||||
<span class="header">
|
||||
{{ 'revert-value-dialog.original-values' | translate }}
|
||||
</span>
|
||||
<div class="values">
|
||||
<p *ngFor="let value of entry.componentValues; let index = index">
|
||||
{{ index + 1 + '. ' + (value.originalValue ?? '') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="header">
|
||||
{{ 'revert-value-dialog.current-values' | translate }}
|
||||
</span>
|
||||
<div class="values">
|
||||
<p *ngFor="let value of entry.componentValues; let index = index">
|
||||
{{ index + 1 + '. ' + value.value }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<iqser-icon-button (click)="save()" [label]="'revert-value-dialog.actions.revert' | translate" [type]="iconButtonTypes.primary" />
|
||||
|
||||
<div [translate]="'revert-value-dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
||||
</div>
|
||||
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close" />
|
||||
</section>
|
||||
@ -0,0 +1,27 @@
|
||||
.dialog-content {
|
||||
padding-top: 12px;
|
||||
|
||||
.compare-values-container {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
|
||||
div {
|
||||
flex-grow: 1;
|
||||
|
||||
.header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.values {
|
||||
margin-top: 10px;
|
||||
padding: 10px 0 10px 15px;
|
||||
width: 90%;
|
||||
background: var(--iqser-grey-8);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CircleButtonComponent, ConfirmOptions, IconButtonComponent, IqserDialogComponent } from '@iqser/common-ui';
|
||||
import { MatDialogClose } from '@angular/material/dialog';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { IComponentLogEntry } from '@red/domain';
|
||||
import { NgFor } from '@angular/common';
|
||||
|
||||
interface RevertValueData {
|
||||
entry: IComponentLogEntry;
|
||||
}
|
||||
interface RevertValueResult {}
|
||||
|
||||
@Component({
|
||||
templateUrl: 'revert-value-dialog.component.html',
|
||||
styleUrls: ['./revert-value-dialog.component.scss'],
|
||||
standalone: true,
|
||||
imports: [CircleButtonComponent, IconButtonComponent, MatDialogClose, TranslateModule, NgFor],
|
||||
})
|
||||
export class RevertValueDialogComponent extends IqserDialogComponent<RevertValueDialogComponent, RevertValueData, RevertValueResult> {
|
||||
protected readonly entry = this.data.entry;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
save() {
|
||||
this.close(ConfirmOptions.CONFIRM);
|
||||
}
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
<section class="dialog">
|
||||
<div [translate]="'component-log-dialog.title'" class="dialog-header heading-l"></div>
|
||||
|
||||
<hr />
|
||||
<div class="dialog-content" id="scm-edit">
|
||||
<div *ngIf="componentLogData() as componentLogEntries" class="table output-data">
|
||||
<div class="table-header">{{ 'component-log-dialog.table-header.component' | translate }}</div>
|
||||
<div class="table-header">{{ 'component-log-dialog.table-header.value' | translate }}</div>
|
||||
<div class="table-header">{{ 'component-log-dialog.table-header.transformation-rule' | translate }}</div>
|
||||
<div class="table-header">{{ 'component-log-dialog.table-header.annotation-references' | translate }}</div>
|
||||
|
||||
<ng-container *ngFor="let entry of componentLogEntries; let index = index">
|
||||
<div class="bold">{{ entry.name }}</div>
|
||||
<div [id]="getValueCellId(index)">
|
||||
<iqser-editable-input
|
||||
(save)="saveEdit($event, entry.originalKey)"
|
||||
[canEdit]="canEdit"
|
||||
[cancelTooltip]="'component-log-dialog.actions.cancel-edit' | translate"
|
||||
[editTooltip]="'component-log-dialog.actions.edit' | translate"
|
||||
[id]="'value-' + index"
|
||||
[parentId]="getValueCellId(index)"
|
||||
[saveTooltip]="'component-log-dialog.actions.save' | translate"
|
||||
[value]="entry.componentValues[0].value ?? entry.componentValues[0].originalValue"
|
||||
[attr.helpModeKey]="'scm_edit_DIALOG'"
|
||||
>
|
||||
<ng-container slot="editing">
|
||||
<iqser-circle-button
|
||||
(action)="undo(entry.originalKey)"
|
||||
*ngIf="entry.componentValues[0].value !== entry.componentValues[0].originalValue && canEdit"
|
||||
[showDot]="true"
|
||||
[tooltip]="
|
||||
'component-log-dialog.actions.undo'
|
||||
| translate: { value: entry.componentValues[0].originalValue }
|
||||
| replaceNbsp
|
||||
"
|
||||
[attr.help-mode-key]="'scm_undo_DIALOG'"
|
||||
class="ml-2"
|
||||
icon="red:undo"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
</iqser-editable-input>
|
||||
</div>
|
||||
<div>{{ entry.componentValues[0].valueDescription }}</div>
|
||||
<div>
|
||||
<ul *ngIf="entry.componentValues[0].entityReferences; else noReferences" class="pl-0">
|
||||
<li
|
||||
*ngFor="let reference of entry.componentValues[0].entityReferences"
|
||||
[innerHTML]="
|
||||
'component-log-dialog.annotations'
|
||||
| translate
|
||||
: {
|
||||
type: parseType(reference.displayValue),
|
||||
page: reference.page,
|
||||
ruleNumber: reference.entityRuleId
|
||||
}
|
||||
"
|
||||
class="mb-8"
|
||||
></li>
|
||||
</ul>
|
||||
|
||||
<ng-template #noReferences>-</ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<iqser-icon-button
|
||||
(action)="exportJSON()"
|
||||
[label]="'component-log-dialog.actions.export-json' | translate"
|
||||
[submit]="true"
|
||||
[type]="iconButtonTypes.primary"
|
||||
[attr.help-mode-key]="'scm_export_DIALOG'"
|
||||
></iqser-icon-button>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="exportXML()"
|
||||
[label]="'component-log-dialog.actions.export-xml' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
[attr.help-mode-key]="'scm_export_DIALOG'"
|
||||
></iqser-icon-button>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="exportAllInDossier()"
|
||||
*ngIf="userPreferences.isIqserDevMode"
|
||||
[type]="iconButtonTypes.primary"
|
||||
label="Export All"
|
||||
></iqser-icon-button>
|
||||
|
||||
<div [translate]="'component-log-dialog.actions.close'" class="all-caps-label cancel" mat-dialog-close></div>
|
||||
<mat-checkbox (change)="toggleOpenScmDialogByDefault()" [checked]="openScmDialogByDefault()" class="ml-auto" color="primary"
|
||||
>{{ 'component-log-dialog.actions.display-by-default' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
@ -1,61 +0,0 @@
|
||||
.rss-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid var(--iqser-separator);
|
||||
|
||||
.rss-key {
|
||||
font-weight: bold;
|
||||
flex: 30;
|
||||
text-align: right;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.rss-value {
|
||||
padding: 4px;
|
||||
flex: 70;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
|
||||
> div {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
margin: 10px 0;
|
||||
border-bottom: 1px solid var(--iqser-separator);
|
||||
background-color: var(--iqser-grey-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.annotation-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr 1fr 5fr;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.output-data > div:nth-child(8n + 9),
|
||||
.output-data > div:nth-child(8n + 10),
|
||||
.output-data > div:nth-child(8n + 11),
|
||||
.output-data > div:nth-child(8n + 12) {
|
||||
background: var(--iqser-grey-8);
|
||||
}
|
||||
|
||||
.ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
import { KeyValuePipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, Inject, OnInit, signal } from '@angular/core';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
import { ReplaceNbspPipe } from '@common-ui/pipes/replace-nbsp.pipe';
|
||||
import { BaseDialogComponent, CircleButtonComponent, EditableInputComponent, IconButtonComponent } from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ComponentLogEntry, Dictionary, IFile, WorkflowFileStatuses } from '@red/domain';
|
||||
import { FilesMapService } from '@services/files/files-map.service';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ComponentLogService } from '@services/files/component-log.service';
|
||||
|
||||
interface ScmData {
|
||||
file: IFile;
|
||||
dictionaries: Dictionary[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './structured-component-management-dialog.component.html',
|
||||
styleUrls: ['./structured-component-management-dialog.component.scss'],
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
NgIf,
|
||||
EditableInputComponent,
|
||||
NgForOf,
|
||||
KeyValuePipe,
|
||||
TranslateModule,
|
||||
CircleButtonComponent,
|
||||
IconButtonComponent,
|
||||
MatCheckboxModule,
|
||||
MatDialogModule,
|
||||
ReplaceNbspPipe,
|
||||
],
|
||||
})
|
||||
export class StructuredComponentManagementDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
readonly componentLogData = signal<ComponentLogEntry[] | undefined>(undefined);
|
||||
readonly openScmDialogByDefault = signal(this.userPreferences.getOpenScmDialogByDefault());
|
||||
|
||||
constructor(
|
||||
protected readonly _dialogRef: MatDialogRef<StructuredComponentManagementDialogComponent>,
|
||||
private readonly _componentLogService: ComponentLogService,
|
||||
readonly userPreferences: UserPreferenceService,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: ScmData,
|
||||
) {
|
||||
super(_dialogRef);
|
||||
}
|
||||
|
||||
get canEdit() {
|
||||
return this.data.file.workflowStatus !== WorkflowFileStatuses.APPROVED;
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
getValueCellId(index: number) {
|
||||
return `value-cell-${index}`;
|
||||
}
|
||||
|
||||
originalOrder = (): number => 0;
|
||||
|
||||
exportJSON() {
|
||||
return firstValueFrom(
|
||||
this._componentLogService.exportJSON(this.data.file.dossierTemplateId, this.data.file.dossierId, this.data.file),
|
||||
);
|
||||
}
|
||||
|
||||
exportXML() {
|
||||
return firstValueFrom(
|
||||
this._componentLogService.exportXML(this.data.file.dossierTemplateId, this.data.file.dossierId, this.data.file),
|
||||
);
|
||||
}
|
||||
|
||||
async exportAllInDossier() {
|
||||
const allFilesInDossier = this._filesMapService.get(this.data.file.dossierId);
|
||||
for (const file of allFilesInDossier) {
|
||||
await firstValueFrom(this._componentLogService.exportJSON(this.data.file.dossierTemplateId, file.dossierId, file));
|
||||
await firstValueFrom(this._componentLogService.exportXML(this.data.file.dossierTemplateId, file.dossierId, file));
|
||||
}
|
||||
}
|
||||
|
||||
save() {
|
||||
return this.exportJSON();
|
||||
}
|
||||
|
||||
parseType(type: string) {
|
||||
return type.replaceAll('_', ' ').replace(/(^\w{1})|(\s+\w{1})/g, letter => letter.toUpperCase());
|
||||
}
|
||||
|
||||
async toggleOpenScmDialogByDefault() {
|
||||
await this.userPreferences.toggleOpenScmDialogByDefault();
|
||||
await this.userPreferences.reload();
|
||||
this.openScmDialogByDefault.set(this.userPreferences.getOpenScmDialogByDefault());
|
||||
}
|
||||
|
||||
async undo(originalKey: string) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._componentLogService.revertOverride(this.data.file.dossierId, this.data.file.fileId, [originalKey]));
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
async saveEdit(event: string, originalKey: string) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._componentLogService.override(this.data.file.dossierId, this.data.file.fileId, { [originalKey]: event }));
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
async #loadData(): Promise<void> {
|
||||
this._loadingService.start();
|
||||
const componentLogData = await firstValueFrom(
|
||||
this._componentLogService.getComponentLogData(
|
||||
this.data.file.dossierTemplateId,
|
||||
this.data.file.dossierId,
|
||||
this.data.file.fileId,
|
||||
),
|
||||
);
|
||||
this.#updateDisplayValue(componentLogData);
|
||||
this.componentLogData.set(componentLogData);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
#updateDisplayValue(componentLogs: ComponentLogEntry[]) {
|
||||
const dictionaries = this.data.dictionaries;
|
||||
for (const componentLog of componentLogs) {
|
||||
let foundDictionary: Dictionary;
|
||||
for (const reference of componentLog.componentValues[0].entityReferences) {
|
||||
if (foundDictionary) {
|
||||
reference.displayValue = foundDictionary.label;
|
||||
continue;
|
||||
}
|
||||
foundDictionary = dictionaries.find(dict => dict.type === reference.type);
|
||||
foundDictionary = foundDictionary ?? ({ label: reference.type } as Dictionary);
|
||||
reference.displayValue = foundDictionary.label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@ 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,
|
||||
@ -38,4 +39,5 @@ export const filePreviewScreenProviders = [
|
||||
SearchService,
|
||||
StampService,
|
||||
PdfProxyService,
|
||||
ComponentLogFilterService,
|
||||
];
|
||||
|
||||
@ -1,100 +1,21 @@
|
||||
<section *ngIf="state.file() as file">
|
||||
<div class="page-header">
|
||||
<div class="flex flex-1">
|
||||
<redaction-view-switch></redaction-view-switch>
|
||||
</div>
|
||||
|
||||
<!-- TODO: mode this file preview header to a separate component-->
|
||||
<div #actionsWrapper class="flex-2 actions-container">
|
||||
<redaction-processing-indicator [file]="file" class="mr-16"></redaction-processing-indicator>
|
||||
|
||||
<redaction-user-management></redaction-user-management>
|
||||
|
||||
<ng-container *ngIf="permissionsService.isApprover(state.dossier()) && !!file.lastReviewer">
|
||||
<div class="vertical-line"></div>
|
||||
|
||||
<div class="all-caps-label mr-16 ml-8 label">
|
||||
{{ 'file-preview.last-assignee' | translate }}
|
||||
</div>
|
||||
|
||||
<iqser-initials-avatar [user]="lastAssignee()" [withName]="true"></iqser-initials-avatar>
|
||||
</ng-container>
|
||||
|
||||
<div class="vertical-line"></div>
|
||||
|
||||
<!-- TODO: mode these actions to a separate component -->
|
||||
<iqser-circle-button
|
||||
(action)="openComponentLogView()"
|
||||
*allow="roles.getRss"
|
||||
[attr.help-mode-key]="'editor_scm'"
|
||||
[tooltip]="'file-preview.open-rss-view' | translate"
|
||||
class="ml-8"
|
||||
icon="red:extract"
|
||||
tooltipPosition="below"
|
||||
iqserDisableStopPropagation
|
||||
></iqser-circle-button>
|
||||
|
||||
<redaction-file-actions
|
||||
[dossier]="state.dossier()"
|
||||
[file]="file"
|
||||
[helpModeKeyPrefix]="'editor'"
|
||||
[minWidth]="width"
|
||||
type="file-preview"
|
||||
iqserDisableStopPropagation
|
||||
></redaction-file-actions>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="getTables()"
|
||||
*allow="roles.getTables"
|
||||
[icon]="'red:csv'"
|
||||
[tooltip]="'file-preview.get-tables' | translate"
|
||||
class="ml-2"
|
||||
iqserDisableStopPropagation
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="toggleFullScreen()"
|
||||
[attr.help-mode-key]="'editor_full_screen'"
|
||||
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
|
||||
[tooltip]="'file-preview.fullscreen' | translate"
|
||||
class="ml-2"
|
||||
iqserDisableStopPropagation
|
||||
></iqser-circle-button>
|
||||
|
||||
<!-- Dev Mode Features-->
|
||||
<iqser-circle-button
|
||||
(action)="downloadOriginalFile(file)"
|
||||
*ngIf="isIqserDevMode"
|
||||
[tooltip]="'file-preview.download-original-file' | translate"
|
||||
[type]="circleButtonTypes.primary"
|
||||
class="ml-8"
|
||||
icon="iqser:download"
|
||||
iqserDisableStopPropagation
|
||||
></iqser-circle-button>
|
||||
|
||||
<!-- End Dev Mode Features-->
|
||||
|
||||
<iqser-circle-button
|
||||
*ngIf="!fullScreen"
|
||||
[attr.help-mode-key]="'editor_close'"
|
||||
[routerLink]="state.dossier().routerLink"
|
||||
[tooltip]="'common.close' | translate"
|
||||
class="ml-8"
|
||||
icon="iqser:close"
|
||||
iqserDisableStopPropagation
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<redaction-file-header [file]="file"></redaction-file-header>
|
||||
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<div class="content-inner">
|
||||
<div class="content-container">
|
||||
<!-- Here comes PDF Viewer-->
|
||||
<redaction-structured-component-management
|
||||
*ngIf="isDocumine"
|
||||
[file]="file"
|
||||
[dictionaries]="state.dictionaries"
|
||||
></redaction-structured-component-management>
|
||||
</div>
|
||||
|
||||
<div class="right-container">
|
||||
<redaction-file-preview-right-container [iqserDisableStopPropagation]="state.isEditingReviewer()"></redaction-file-preview-right-container>
|
||||
<div class="right-container" [class.documine-container]="isDocumine">
|
||||
<redaction-file-preview-right-container
|
||||
[iqserDisableStopPropagation]="state.isEditingReviewer()"
|
||||
></redaction-file-preview-right-container>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -1,20 +1,3 @@
|
||||
.vertical-line {
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
background-color: var(--iqser-separator);
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
position: absolute;
|
||||
}
|
||||
@ -25,8 +8,8 @@
|
||||
|
||||
.right-container {
|
||||
padding: 0;
|
||||
width: 350px;
|
||||
min-width: 350px;
|
||||
width: var(--workload-width);
|
||||
min-width: var(--workload-width);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -54,6 +37,10 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&.documine-container {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-progress {
|
||||
|
||||
@ -1,18 +1,4 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
computed,
|
||||
effect,
|
||||
ElementRef,
|
||||
HostListener,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ChangeDetectorRef, Component, effect, NgZone, OnDestroy, OnInit, TemplateRef, 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';
|
||||
@ -23,16 +9,13 @@ import {
|
||||
CustomError,
|
||||
ErrorService,
|
||||
getConfig,
|
||||
HelpModeService,
|
||||
IConfirmationDialogData,
|
||||
IqserDialog,
|
||||
IqserPermissionsService,
|
||||
isIqserDevMode,
|
||||
LoadingService,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { copyLocalStorageFiltersValues, FilterService, NestedFilter, processFilters } from '@iqser/common-ui/lib/filtering';
|
||||
import { AutoUnsubscribe, Bind, bool, Debounce, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
|
||||
import { AutoUnsubscribe, Bind, bool, List, 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 { Dictionary, File, ViewModes } from '@red/domain';
|
||||
@ -46,8 +29,6 @@ import { PermissionsService } from '@services/permissions.service';
|
||||
import { ReanalysisService } from '@services/reanalysis.service';
|
||||
import { Roles } from '@users/roles';
|
||||
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
||||
import { saveAs } from 'file-saver';
|
||||
import JSZip from 'jszip';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { combineLatest, first, firstValueFrom, Observable, of, pairwise } from 'rxjs';
|
||||
import { catchError, filter, map, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
@ -73,11 +54,8 @@ import { ManualRedactionService } from './services/manual-redaction.service';
|
||||
import { PdfProxyService } from './services/pdf-proxy.service';
|
||||
import { SkippedService } from './services/skipped.service';
|
||||
import { StampService } from './services/stamp.service';
|
||||
import { TablesService } from './services/tables.service';
|
||||
import { ViewModeService } from './services/view-mode.service';
|
||||
import { ALL_HOTKEYS } from './utils/constants';
|
||||
import { RedactTextData } from './utils/dialog-types';
|
||||
import { AnnotationActionsService } from './services/annotation-actions.service';
|
||||
import { MultiSelectService } from './services/multi-select.service';
|
||||
|
||||
@Component({
|
||||
@ -85,26 +63,18 @@ import { MultiSelectService } from './services/multi-select.service';
|
||||
styleUrls: ['./file-preview-screen.component.scss'],
|
||||
providers: filePreviewScreenProviders,
|
||||
})
|
||||
export class FilePreviewScreenComponent
|
||||
extends AutoUnsubscribe
|
||||
implements AfterViewInit, OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate
|
||||
{
|
||||
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly roles = Roles;
|
||||
fullScreen = false;
|
||||
readonly fileId = this.state.fileId;
|
||||
readonly dossierId = this.state.dossierId;
|
||||
readonly lastAssignee = computed(() => this.getLastAssignee());
|
||||
width: number;
|
||||
readonly isIqserDevMode = isIqserDevMode();
|
||||
@ViewChild('annotationFilterTemplate', {
|
||||
read: TemplateRef,
|
||||
static: false,
|
||||
})
|
||||
private readonly _filterTemplate: TemplateRef<unknown>;
|
||||
#loadAllAnnotationsEnabled = false;
|
||||
@ViewChild('actionsWrapper', { static: false }) private readonly _actionsWrapper: ElementRef;
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
|
||||
constructor(
|
||||
readonly pdf: PdfViewer,
|
||||
@ -113,7 +83,6 @@ export class FilePreviewScreenComponent
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly pdfProxyService: PdfProxyService,
|
||||
readonly configService: ConfigService,
|
||||
private readonly _iqserPermissionsService: IqserPermissionsService,
|
||||
private readonly _listingService: AnnotationsListingService,
|
||||
private readonly _router: Router,
|
||||
private readonly _ngZone: NgZone,
|
||||
@ -142,11 +111,7 @@ export class FilePreviewScreenComponent
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _fileManagementService: FileManagementService,
|
||||
private readonly _readableRedactionsService: ReadableRedactionsService,
|
||||
private readonly _helpModeService: HelpModeService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _tablesService: TablesService,
|
||||
private readonly _annotationActionsService: AnnotationActionsService,
|
||||
private readonly _multiSelectService: MultiSelectService,
|
||||
) {
|
||||
super();
|
||||
@ -216,12 +181,6 @@ export class FilePreviewScreenComponent
|
||||
);
|
||||
}
|
||||
|
||||
getLastAssignee() {
|
||||
const { isApproved, lastReviewer, lastApprover } = this.state.file();
|
||||
const isRss = this._iqserPermissionsService.has(this.roles.getRss);
|
||||
return isApproved ? (isRss ? lastReviewer : lastApprover) : lastReviewer;
|
||||
}
|
||||
|
||||
deleteEarmarksOnViewChange$() {
|
||||
const isChangingFromEarmarksViewMode$ = this._viewModeService.viewMode$.pipe(
|
||||
pairwise(),
|
||||
@ -298,24 +257,15 @@ export class FilePreviewScreenComponent
|
||||
this._viewerHeaderService.resetCompareButtons();
|
||||
this._viewerHeaderService.enableLoadAllAnnotations(); // Reset the button state (since the viewer is reused between files)
|
||||
super.ngOnDetach();
|
||||
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
|
||||
this.pdf.instance.UI.hotkeys.off('esc');
|
||||
this._changeRef.markForCheck();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
document.documentElement.removeEventListener('fullscreenchange', this.fullscreenListener);
|
||||
this.pdf.instance.UI.hotkeys.off('esc');
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
@Bind()
|
||||
fullscreenListener() {
|
||||
if (!document.fullscreenElement) {
|
||||
this.fullScreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Bind()
|
||||
handleEscInsideViewer($event: KeyboardEvent) {
|
||||
$event.preventDefault();
|
||||
@ -352,6 +302,7 @@ export class FilePreviewScreenComponent
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
document.getElementById('viewer').classList.add(this.isDocumine ? 'documine-viewer' : 'redaction-viewer');
|
||||
const file = this.state.file();
|
||||
|
||||
if (!file) {
|
||||
@ -368,19 +319,10 @@ export class FilePreviewScreenComponent
|
||||
|
||||
this.pdfProxyService.configureElements();
|
||||
this.#restoreOldFilters();
|
||||
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
|
||||
this.pdf.instance.UI.hotkeys.on('esc', this.handleEscInsideViewer);
|
||||
this.#openComponentLogDialogIfDefault();
|
||||
this._viewerHeaderService.resetLayers();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||
this._updateItemWidth(entries[0]);
|
||||
});
|
||||
_observer.observe(this._actionsWrapper.nativeElement);
|
||||
}
|
||||
|
||||
openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
|
||||
const file = this.state.file();
|
||||
|
||||
@ -406,68 +348,6 @@ export class FilePreviewScreenComponent
|
||||
);
|
||||
}
|
||||
|
||||
toggleFullScreen() {
|
||||
this.fullScreen = !this.fullScreen;
|
||||
if (this.fullScreen) {
|
||||
this.#openFullScreen();
|
||||
} else {
|
||||
this.closeFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keyup', ['$event'])
|
||||
handleKeyEvent($event: KeyboardEvent) {
|
||||
if (this._router.url.indexOf('/file/') < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ALL_HOTKEYS.includes($event.key) || this._dialog.openDialogs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (['Escape'].includes($event.key)) {
|
||||
$event.preventDefault();
|
||||
if (this._annotationManager.resizingAnnotationId) {
|
||||
const resizedAnnotation = this._fileDataService
|
||||
.annotations()
|
||||
.find(annotation => annotation.id === this._annotationManager.resizingAnnotationId);
|
||||
this._annotationActionsService.cancelResize(resizedAnnotation).then();
|
||||
}
|
||||
|
||||
if (this._annotationManager.selected.length) {
|
||||
this._annotationManager.deselectAll();
|
||||
}
|
||||
|
||||
if (this._multiSelectService.active()) {
|
||||
this._multiSelectService.deactivate();
|
||||
}
|
||||
|
||||
this.fullScreen = false;
|
||||
this.closeFullScreen();
|
||||
this._changeRef.markForCheck();
|
||||
}
|
||||
|
||||
if (!$event.ctrlKey && !$event.metaKey && ['f', 'F'].includes($event.key)) {
|
||||
// if you type in an input, don't toggle full-screen
|
||||
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
|
||||
return;
|
||||
}
|
||||
this.toggleFullScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
if (['h', 'H'].includes($event.key)) {
|
||||
if ($event.target instanceof HTMLInputElement || $event.target instanceof HTMLTextAreaElement) {
|
||||
return;
|
||||
}
|
||||
this._ngZone.run(() => {
|
||||
window.focus();
|
||||
this._helpModeService.activateHelpMode(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async viewerReady(pageNumber?: string) {
|
||||
if (pageNumber) {
|
||||
const file = this.state.file();
|
||||
@ -489,21 +369,6 @@ export class FilePreviewScreenComponent
|
||||
this._changeRef.markForCheck();
|
||||
}
|
||||
|
||||
closeFullScreen() {
|
||||
if (!!document.fullscreenElement && document.exitFullscreen) {
|
||||
document.exitFullscreen().then();
|
||||
}
|
||||
}
|
||||
|
||||
async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) {
|
||||
const originalFile = this._fileManagementService.downloadOriginal(dossierId, fileId, 'response', cacheIdentifier);
|
||||
download(await firstValueFrom(originalFile), filename);
|
||||
}
|
||||
|
||||
openComponentLogView() {
|
||||
this._dialogService.openDialog('componentLog', { file: this.state.file(), dictionaries: this.state.dictionaries });
|
||||
}
|
||||
|
||||
loadAnnotations$() {
|
||||
const annotations$ = this._fileDataService.annotations$.pipe(
|
||||
startWith([] as AnnotationWrapper[]),
|
||||
@ -549,28 +414,6 @@ export class FilePreviewScreenComponent
|
||||
return this.#cleanupAndRedrawAnnotations(annotationsToDraw);
|
||||
}
|
||||
|
||||
async getTables() {
|
||||
this._loadingService.start();
|
||||
|
||||
const currentPage = this.pdf.currentPage();
|
||||
const tables = await this._tablesService.get(this.state.dossierId, this.state.fileId, this.pdf.currentPage());
|
||||
await this._annotationDrawService.drawTables(tables, currentPage, this.state.dossierTemplateId);
|
||||
|
||||
const filename = this.state.file().filename;
|
||||
const zip = new JSZip();
|
||||
|
||||
tables.forEach((t, index) => {
|
||||
const blob = new Blob([atob(t.csvAsBytes)], {
|
||||
type: 'text/csv;charset=utf-8',
|
||||
});
|
||||
zip.file(filename + '_page' + currentPage + '_table' + (index + 1) + '.csv', blob);
|
||||
});
|
||||
|
||||
saveAs(await zip.generateAsync({ type: 'blob' }), filename + '_tables.zip');
|
||||
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
async #openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
|
||||
const file = this.state.file();
|
||||
|
||||
@ -594,12 +437,6 @@ export class FilePreviewScreenComponent
|
||||
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
|
||||
}
|
||||
|
||||
@Debounce(30)
|
||||
private _updateItemWidth(entry: ResizeObserverEntry): void {
|
||||
this.width = entry.contentRect.width;
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
#getAnnotationsToDraw(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
|
||||
const currentPage = this.pdf.currentPage();
|
||||
const currentPageAnnotations = this._annotationManager.get(a => a.getPageNumber() === currentPage);
|
||||
@ -733,10 +570,6 @@ export class FilePreviewScreenComponent
|
||||
.pipe(tap(() => this.#handleDeletedFile()))
|
||||
.subscribe();
|
||||
|
||||
this.addActiveScreenSubscription = this._documentViewer.keyUp$.subscribe($event => {
|
||||
this.handleKeyEvent($event);
|
||||
});
|
||||
|
||||
this.addActiveScreenSubscription = this.#earmarks$.subscribe();
|
||||
this.addActiveScreenSubscription = this.deleteEarmarksOnViewChange$().subscribe();
|
||||
|
||||
@ -867,13 +700,6 @@ export class FilePreviewScreenComponent
|
||||
});
|
||||
}
|
||||
|
||||
#openFullScreen() {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement.requestFullscreen) {
|
||||
documentElement.requestFullscreen().then();
|
||||
}
|
||||
}
|
||||
|
||||
#navigateToDossier() {
|
||||
this._logger.info('Navigating to ', this.state.dossier().dossierName);
|
||||
return this._router.navigate([this.state.dossier().routerLink]);
|
||||
@ -900,14 +726,8 @@ export class FilePreviewScreenComponent
|
||||
});
|
||||
}
|
||||
|
||||
#openComponentLogDialogIfDefault() {
|
||||
if (this.permissionsService.canViewRssDialog() && this.userPreferenceService.getOpenScmDialogByDefault()) {
|
||||
this.openComponentLogView();
|
||||
}
|
||||
}
|
||||
|
||||
#getRedactTextDialog(data: RedactTextData) {
|
||||
if (this.#isDocumine) {
|
||||
if (this.isDocumine) {
|
||||
return this._iqserDialog.openDefault(AddAnnotationDialogComponent, { data });
|
||||
}
|
||||
|
||||
|
||||
@ -72,6 +72,10 @@ import { ManualRedactionService } from './services/manual-redaction.service';
|
||||
import { TablesService } from './services/tables.service';
|
||||
import { SelectedAnnotationsTableComponent } from './components/selected-annotations-table/selected-annotations-table.component';
|
||||
import { SelectedAnnotationsListComponent } from './components/selected-annotations-list/selected-annotations-list.component';
|
||||
import { FileHeaderComponent } from './components/file-header/file-header.component';
|
||||
import { DocumineExportComponent } from './components/documine-export/documine-export.component';
|
||||
import { StructuredComponentManagementComponent } from './components/structured-component-management/structured-component-management.component';
|
||||
import { EditableStructuredComponentValueComponent } from './components/editable-structured-component-value/editable-structured-component-value.component';
|
||||
|
||||
const routes: IqserRoutes = [
|
||||
{
|
||||
@ -121,6 +125,9 @@ const components = [
|
||||
FilePreviewScreenComponent,
|
||||
FilePreviewRightContainerComponent,
|
||||
ReadonlyBannerComponent,
|
||||
FileHeaderComponent,
|
||||
DocumineExportComponent,
|
||||
StructuredComponentManagementComponent,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@ -156,6 +163,7 @@ const components = [
|
||||
DisableStopPropagationDirective,
|
||||
SelectedAnnotationsTableComponent,
|
||||
SelectedAnnotationsListComponent,
|
||||
EditableStructuredComponentValueComponent,
|
||||
],
|
||||
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, TablesService],
|
||||
})
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ComponentLogEntry } from '@red/domain';
|
||||
import { INestedFilter, NestedFilter } from '@common-ui/filtering';
|
||||
|
||||
@Injectable()
|
||||
export class ComponentLogFilterService {
|
||||
filterGroups(entities: ComponentLogEntry[]) {
|
||||
const allDistinctComponentLogs = new Set<string>();
|
||||
|
||||
entities?.forEach(entry => allDistinctComponentLogs.add(entry.name));
|
||||
|
||||
const componentLogFilters = [...allDistinctComponentLogs].map(
|
||||
id =>
|
||||
new NestedFilter({
|
||||
id: id,
|
||||
label: id.replaceAll('_', ' '),
|
||||
}),
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
slug: 'componentLogFilters',
|
||||
filters: componentLogFilters,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
filterComponents(components: ComponentLogEntry[], filters: INestedFilter[]) {
|
||||
const someFiltersChecked = !!filters.find(f => f.checked);
|
||||
if (!someFiltersChecked) {
|
||||
return components;
|
||||
}
|
||||
|
||||
const checkedFiltersIds = filters.reduce((ids, f) => {
|
||||
if (f.checked) {
|
||||
ids.push(f.id);
|
||||
}
|
||||
return ids;
|
||||
}, []);
|
||||
|
||||
return components.filter(c => checkedFiltersIds.includes(c.name));
|
||||
}
|
||||
}
|
||||
@ -6,16 +6,8 @@ import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/doc
|
||||
import { ForceAnnotationDialogComponent } from '../dialogs/force-redaction-dialog/force-annotation-dialog.component';
|
||||
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
|
||||
import { ManualAnnotationDialogComponent } from '../dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
|
||||
import { StructuredComponentManagementDialogComponent } from '../dialogs/structured-component-management-dialog/structured-component-management-dialog.component';
|
||||
|
||||
type DialogType =
|
||||
| 'confirm'
|
||||
| 'documentInfo'
|
||||
| 'componentLog'
|
||||
| 'changeLegalBasis'
|
||||
| 'forceAnnotation'
|
||||
| 'manualAnnotation'
|
||||
| 'highlightAction';
|
||||
type DialogType = 'confirm' | 'documentInfo' | 'changeLegalBasis' | 'forceAnnotation' | 'manualAnnotation' | 'highlightAction';
|
||||
|
||||
@Injectable()
|
||||
export class FilePreviewDialogService extends DialogService<DialogType> {
|
||||
@ -41,10 +33,6 @@ export class FilePreviewDialogService extends DialogService<DialogType> {
|
||||
highlightAction: {
|
||||
component: HighlightActionDialogComponent,
|
||||
},
|
||||
componentLog: {
|
||||
component: StructuredComponentManagementDialogComponent,
|
||||
dialogConfig: { width: '90vw' },
|
||||
},
|
||||
};
|
||||
|
||||
constructor(protected readonly _dialog: MatDialog) {
|
||||
|
||||
@ -135,9 +135,11 @@ export class PdfProxyService {
|
||||
}
|
||||
|
||||
configureElements() {
|
||||
const hexColor = this._dictionariesMapService.get(this._state.dossierTemplateId, 'manual').hexColor;
|
||||
const color = this._annotationDrawService.convertColor(hexColor);
|
||||
this._documentViewer.setRectangleToolStyles(color);
|
||||
const hexColor = this._dictionariesMapService.get(this._state.dossierTemplateId, 'manual')?.hexColor;
|
||||
if (hexColor) {
|
||||
const color = this._annotationDrawService.convertColor(hexColor);
|
||||
this._documentViewer.setRectangleToolStyles(color);
|
||||
}
|
||||
}
|
||||
|
||||
#configureRectangleAnnotationPopup(annotation: Annotation) {
|
||||
|
||||
@ -29,6 +29,7 @@ export class IconsModule {
|
||||
'archive',
|
||||
'arrow-up',
|
||||
'arrow-down',
|
||||
'arrow-right',
|
||||
'assign',
|
||||
'assign-me',
|
||||
'attribute',
|
||||
@ -41,6 +42,7 @@ export class IconsModule {
|
||||
'denied',
|
||||
'disable-analysis',
|
||||
'double-chevron-right',
|
||||
'draggable-dots',
|
||||
'enable-analysis',
|
||||
'enter',
|
||||
'entries',
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
<redaction-compare-file-input></redaction-compare-file-input>
|
||||
|
||||
<redaction-paginator></redaction-paginator>
|
||||
<redaction-paginator *ngIf="!isDocumine"></redaction-paginator>
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-pdf-viewer',
|
||||
templateUrl: './pdf-viewer.component.html',
|
||||
styleUrls: ['./pdf-viewer.component.scss'],
|
||||
})
|
||||
export class PdfViewerComponent {}
|
||||
export class PdfViewerComponent {
|
||||
protected readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
}
|
||||
|
||||
@ -275,6 +275,15 @@ export class ViewerHeaderService {
|
||||
|
||||
updateElements(): void {
|
||||
this._pdf.instance?.UI.setHeaderItems(header => {
|
||||
let deletedDividers = 0;
|
||||
if (this.#isDocumine) {
|
||||
const secondHeaderElement = header.getItems()[1] as IHeaderElement;
|
||||
if (secondHeaderElement.type === 'divider') {
|
||||
header.getItems().splice(1, 1);
|
||||
deletedDividers = 1;
|
||||
}
|
||||
}
|
||||
|
||||
const enabledItems: IHeaderElement[] = [];
|
||||
const groups: HeaderElementType[][] = [
|
||||
[HeaderElements.COMPARE_BUTTON, HeaderElements.CLOSE_COMPARE_BUTTON],
|
||||
@ -293,18 +302,19 @@ export class ViewerHeaderService {
|
||||
groups.forEach(group => this.#pushGroup(enabledItems, group));
|
||||
|
||||
const loadAllAnnotationsButton = this.#buttons.get(HeaderElements.LOAD_ALL_ANNOTATIONS);
|
||||
let startButtons = 11;
|
||||
let deleteCount = 15;
|
||||
let startButtons = 10 - deletedDividers;
|
||||
let deleteCount = 14 - deletedDividers;
|
||||
|
||||
if (this.#isEnabled(HeaderElements.LOAD_ALL_ANNOTATIONS)) {
|
||||
if (!header.getItems().includes(loadAllAnnotationsButton)) {
|
||||
header.get('leftPanelButton').insertAfter(loadAllAnnotationsButton);
|
||||
}
|
||||
startButtons = 12;
|
||||
deleteCount = 16;
|
||||
startButtons = 11 - deletedDividers;
|
||||
deleteCount = 15 - deletedDividers;
|
||||
} else {
|
||||
header.delete(HeaderElements.LOAD_ALL_ANNOTATIONS);
|
||||
}
|
||||
|
||||
header.getItems().splice(startButtons, header.getItems().length - deleteCount, ...enabledItems);
|
||||
});
|
||||
|
||||
|
||||
@ -82,6 +82,4 @@ export class EditDictionaryDialogComponent extends IqserDialogComponent<EditDict
|
||||
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
protected readonly iconButtonTypes = IconButtonTypes;
|
||||
}
|
||||
|
||||
@ -4,8 +4,9 @@ import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { ComponentDetails, ComponentLogEntry, IComponentLogData, IComponentLogEntry, IFile } from '@red/domain';
|
||||
import { ComponentDetails, ComponentLogEntry, Dictionary, IComponentLogData, IComponentLogEntry, IFile } from '@red/domain';
|
||||
import { mapEach } from '@common-ui/utils';
|
||||
import { FilePreviewStateService } from '../../modules/file-preview/services/file-preview-state.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ComponentLogService extends GenericService<void> {
|
||||
@ -34,21 +35,32 @@ export class ComponentLogService extends GenericService<void> {
|
||||
);
|
||||
}
|
||||
|
||||
getComponentLogData(dossierTemplateId: string, dossierId: string, fileId: string): Observable<ComponentLogEntry[]> {
|
||||
getComponentLogData(
|
||||
dossierTemplateId: string,
|
||||
dossierId: string,
|
||||
fileId: string,
|
||||
dictionaries: Dictionary[],
|
||||
): Observable<ComponentLogEntry[]> {
|
||||
return this.#componentLogRequest(dossierTemplateId, dossierId, fileId).pipe(
|
||||
map(data => data.componentDetails),
|
||||
catchError(() => of({} as ComponentDetails)),
|
||||
map(componentDetails => this.#mapComponentDetails(componentDetails)),
|
||||
mapEach(log => new ComponentLogEntry(log)),
|
||||
map(log => this.#updateDisplayValue(log, dictionaries)),
|
||||
);
|
||||
}
|
||||
|
||||
override(dossierId: string, fileId: string, componentOverrides: Record<string, string>): Observable<void> {
|
||||
return this._post({ componentOverrides }, `componentLog/override/${dossierId}/${fileId}`);
|
||||
override(dossierTemplateId: string, dossierId: string, fileId: string, componentLogEntry: IComponentLogEntry) {
|
||||
return this._http.post(
|
||||
`/api/dossier-templates/${dossierTemplateId}/dossiers/${dossierId}/files/${fileId}/overrides`,
|
||||
componentLogEntry,
|
||||
);
|
||||
}
|
||||
|
||||
revertOverride(dossierId: string, fileId: string, components: string[]): Observable<void> {
|
||||
return this._post({ components }, `componentLog/override/revert/${dossierId}/${fileId}`);
|
||||
revertOverride(dossierTemplateId: string, dossierId: string, fileId: string, components: string[]) {
|
||||
return this._http.post(`/api/dossier-templates/${dossierTemplateId}/dossiers/${dossierId}/files/${fileId}/overrides/revert`, {
|
||||
components,
|
||||
});
|
||||
}
|
||||
|
||||
exportJSON(dossierTemplateId: string, dossierId: string, file?: IFile): Observable<IComponentLogData> {
|
||||
@ -91,4 +103,17 @@ export class ComponentLogService extends GenericService<void> {
|
||||
#mapComponentDetails(componentDetails: ComponentDetails): IComponentLogEntry[] {
|
||||
return Object.keys(componentDetails).reduce((res, key) => (res.push(componentDetails[key]), res), []);
|
||||
}
|
||||
|
||||
#updateDisplayValue(componentLogs: ComponentLogEntry[], dictionaries: Dictionary[]): ComponentLogEntry[] {
|
||||
for (const componentLog of componentLogs) {
|
||||
for (const componentValue of componentLog.componentValues) {
|
||||
for (const reference of componentValue.entityReferences) {
|
||||
const foundDictionary =
|
||||
dictionaries.find(dict => dict.type === reference.type) ?? ({ label: reference.type } as Dictionary);
|
||||
reference.displayValue = foundDictionary.label;
|
||||
}
|
||||
}
|
||||
}
|
||||
return componentLogs;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { handleCheckedValue, INestedFilter } from '@iqser/common-ui/lib/filtering';
|
||||
import { Dossier, File, User, UserType } from '@red/domain';
|
||||
import { ComponentLogEntry, Dossier, File, User, UserType } from '@red/domain';
|
||||
|
||||
export function handleFilterDelta(oldFilters: INestedFilter[], newFilters: INestedFilter[], allFilters: INestedFilter[]) {
|
||||
const newFiltersDelta = {};
|
||||
|
||||
@ -515,7 +515,7 @@
|
||||
"tooltip": "",
|
||||
"xml": ""
|
||||
},
|
||||
"component-log-dialog": {
|
||||
"component-management": {
|
||||
"actions": {
|
||||
"cancel-edit": "Abbrechen",
|
||||
"close": "Close",
|
||||
@ -526,14 +526,11 @@
|
||||
"save": "Save",
|
||||
"undo": "Undo to: {value}"
|
||||
},
|
||||
"annotations": "<strong>{type}</strong> found on page {page} by rule #{ruleNumber}",
|
||||
"components": "",
|
||||
"table-header": {
|
||||
"annotation-references": "Annotation references",
|
||||
"component": "Component",
|
||||
"transformation-rule": "Transformation rule",
|
||||
"value": "Value"
|
||||
},
|
||||
"title": "Component view"
|
||||
"component": "",
|
||||
"value": ""
|
||||
}
|
||||
},
|
||||
"component-mappings-screen": {
|
||||
"action": {
|
||||
@ -826,6 +823,12 @@
|
||||
"save": "Dokumenteninformation speichern",
|
||||
"title": "Datei-Attribute anlegen"
|
||||
},
|
||||
"documine-export": {
|
||||
"document": "",
|
||||
"document-tooltip": "",
|
||||
"export": "",
|
||||
"export-tooltip": ""
|
||||
},
|
||||
"dossier-attribute-types": {
|
||||
"date": "Datum",
|
||||
"image": "Bild",
|
||||
@ -1506,7 +1509,6 @@
|
||||
"no-data": {
|
||||
"title": "Auf dieser Seite gibt es keine Anmerkungen."
|
||||
},
|
||||
"open-rss-view": "Open Structured Component Management View",
|
||||
"quick-nav": {
|
||||
"jump-first": "Zur ersten Seite springen",
|
||||
"jump-last": "Zur letzten Seite springen"
|
||||
@ -2249,6 +2251,15 @@
|
||||
"header": "Resize {type}"
|
||||
}
|
||||
},
|
||||
"revert-value-dialog": {
|
||||
"actions": {
|
||||
"cancel": "",
|
||||
"revert": ""
|
||||
},
|
||||
"current-values": "",
|
||||
"original-values": "",
|
||||
"title": ""
|
||||
},
|
||||
"roles": {
|
||||
"inactive": "Inaktiv",
|
||||
"manager-admin": "Manager & Admin",
|
||||
@ -2542,4 +2553,4 @@
|
||||
}
|
||||
},
|
||||
"yesterday": "Gestern"
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,6 +390,7 @@
|
||||
"annotation": {
|
||||
"pending": "(Pending analysis)"
|
||||
},
|
||||
"annotations": "Annotations",
|
||||
"archived-dossiers-listing": {
|
||||
"no-data": {
|
||||
"title": "No archived dossiers."
|
||||
@ -515,25 +516,20 @@
|
||||
"tooltip": "",
|
||||
"xml": ""
|
||||
},
|
||||
"component-log-dialog": {
|
||||
"component-management": {
|
||||
"actions": {
|
||||
"cancel-edit": "Cancel",
|
||||
"close": "Close",
|
||||
"display-by-default": "Display by default when opening documents",
|
||||
"add": "Add",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Remove value",
|
||||
"edit": "Edit",
|
||||
"export-json": "Export JSON",
|
||||
"export-xml": "Export XML",
|
||||
"save": "Save",
|
||||
"undo": "Undo to: {value}"
|
||||
"undo": "Undo"
|
||||
},
|
||||
"annotations": "<strong>{type}</strong> found on page {page} by rule #{ruleNumber}",
|
||||
"components": "Components",
|
||||
"table-header": {
|
||||
"annotation-references": "Annotation references",
|
||||
"component": "Component",
|
||||
"transformation-rule": "Transformation rule",
|
||||
"value": "Value"
|
||||
},
|
||||
"title": "Component view"
|
||||
}
|
||||
},
|
||||
"component-mappings-screen": {
|
||||
"action": {
|
||||
@ -826,6 +822,12 @@
|
||||
"save": "Save document info",
|
||||
"title": "Enter file attributes"
|
||||
},
|
||||
"documine-export": {
|
||||
"document": "Document",
|
||||
"document-tooltip": "Document",
|
||||
"export": "Export",
|
||||
"export-tooltip": "Export"
|
||||
},
|
||||
"dossier-attribute-types": {
|
||||
"date": "Date",
|
||||
"image": "Image",
|
||||
@ -1506,7 +1508,6 @@
|
||||
"no-data": {
|
||||
"title": "There have been no changes to this page."
|
||||
},
|
||||
"open-rss-view": "Open Structured Component Management View",
|
||||
"quick-nav": {
|
||||
"jump-first": "Jump to first page",
|
||||
"jump-last": "Jump to last page"
|
||||
@ -2249,6 +2250,15 @@
|
||||
"header": "Resize {type}"
|
||||
}
|
||||
},
|
||||
"revert-value-dialog": {
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"revert": "Revert to original values"
|
||||
},
|
||||
"current-values": "Current values",
|
||||
"original-values": "Original values",
|
||||
"title": "Revert to the original values?"
|
||||
},
|
||||
"roles": {
|
||||
"inactive": "Inactive",
|
||||
"manager-admin": "Manager & admin",
|
||||
@ -2542,4 +2552,4 @@
|
||||
}
|
||||
},
|
||||
"yesterday": "Yesterday"
|
||||
}
|
||||
}
|
||||
|
||||
@ -515,25 +515,20 @@
|
||||
"tooltip": "Component download",
|
||||
"xml": "Download as XML"
|
||||
},
|
||||
"component-log-dialog": {
|
||||
"component-management": {
|
||||
"actions": {
|
||||
"cancel-edit": "Cancel",
|
||||
"close": "Close",
|
||||
"display-by-default": "Display by default when opening documents",
|
||||
"edit": "Edit",
|
||||
"export-json": "Export JSON",
|
||||
"export-xml": "Export XML",
|
||||
"save": "Save",
|
||||
"undo": "Undo"
|
||||
"add": "",
|
||||
"cancel": "",
|
||||
"delete": "",
|
||||
"edit": "",
|
||||
"save": "",
|
||||
"undo": ""
|
||||
},
|
||||
"annotations": "<strong>{type}</strong> found on page {page} by rule #{ruleNumber}",
|
||||
"components": "",
|
||||
"table-header": {
|
||||
"annotation-references": "Annotation references",
|
||||
"component": "Component",
|
||||
"transformation-rule": "Transformation rule",
|
||||
"value": "Value"
|
||||
},
|
||||
"title": "Structured Component Management"
|
||||
"component": "",
|
||||
"value": ""
|
||||
}
|
||||
},
|
||||
"component-mappings-screen": {
|
||||
"action": {
|
||||
@ -826,6 +821,12 @@
|
||||
"save": "Dokumenteninformation speichern",
|
||||
"title": "Datei-Attribute anlegen"
|
||||
},
|
||||
"documine-export": {
|
||||
"document": "",
|
||||
"document-tooltip": "",
|
||||
"export": "",
|
||||
"export-tooltip": ""
|
||||
},
|
||||
"dossier-attribute-types": {
|
||||
"date": "Datum",
|
||||
"image": "Bild",
|
||||
@ -1506,7 +1507,6 @@
|
||||
"no-data": {
|
||||
"title": "Auf dieser Seite gibt es keine Anmerkungen."
|
||||
},
|
||||
"open-rss-view": "Open component view",
|
||||
"quick-nav": {
|
||||
"jump-first": "Zur ersten Seite springen",
|
||||
"jump-last": "Zur letzten Seite springen"
|
||||
@ -2249,6 +2249,15 @@
|
||||
"header": "Resize {type}"
|
||||
}
|
||||
},
|
||||
"revert-value-dialog": {
|
||||
"actions": {
|
||||
"cancel": "",
|
||||
"revert": ""
|
||||
},
|
||||
"current-values": "",
|
||||
"original-values": "",
|
||||
"title": ""
|
||||
},
|
||||
"roles": {
|
||||
"inactive": "Inaktiv",
|
||||
"manager-admin": "Manager & admin",
|
||||
@ -2542,4 +2551,4 @@
|
||||
}
|
||||
},
|
||||
"yesterday": "Gestern"
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,6 +390,7 @@
|
||||
"annotation": {
|
||||
"pending": "(Pending analysis)"
|
||||
},
|
||||
"annotations": "Annotations",
|
||||
"archived-dossiers-listing": {
|
||||
"no-data": {
|
||||
"title": "No archived dossiers."
|
||||
@ -515,25 +516,20 @@
|
||||
"tooltip": "Component download",
|
||||
"xml": "Download as XML"
|
||||
},
|
||||
"component-log-dialog": {
|
||||
"component-management": {
|
||||
"actions": {
|
||||
"cancel-edit": "Cancel",
|
||||
"close": "Close",
|
||||
"display-by-default": "Display by default when opening documents",
|
||||
"add": "Add",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Remove value",
|
||||
"edit": "Edit",
|
||||
"export-json": "Export JSON",
|
||||
"export-xml": "Export XML",
|
||||
"save": "Save",
|
||||
"undo": "Undo to: {value}"
|
||||
"undo": "Undo"
|
||||
},
|
||||
"annotations": "<strong>{type}</strong> found on page {page} by rule #{ruleNumber}",
|
||||
"components": "Components",
|
||||
"table-header": {
|
||||
"annotation-references": "Annotation references",
|
||||
"component": "Component",
|
||||
"transformation-rule": "Transformation rule",
|
||||
"value": "Value"
|
||||
},
|
||||
"title": "Component view"
|
||||
}
|
||||
},
|
||||
"component-mappings-screen": {
|
||||
"action": {
|
||||
@ -826,6 +822,12 @@
|
||||
"save": "Save document info",
|
||||
"title": "Enter file attributes"
|
||||
},
|
||||
"documine-export": {
|
||||
"document": "Document",
|
||||
"document-tooltip": "Document",
|
||||
"export": "Export",
|
||||
"export-tooltip": "Export"
|
||||
},
|
||||
"dossier-attribute-types": {
|
||||
"date": "Date",
|
||||
"image": "Image",
|
||||
@ -1506,7 +1508,6 @@
|
||||
"no-data": {
|
||||
"title": "There have been no changes to this page."
|
||||
},
|
||||
"open-rss-view": "Open component view",
|
||||
"quick-nav": {
|
||||
"jump-first": "Jump to first page",
|
||||
"jump-last": "Jump to last page"
|
||||
@ -2249,6 +2250,15 @@
|
||||
"header": "Resize {type}"
|
||||
}
|
||||
},
|
||||
"revert-value-dialog": {
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"revert": "Revert to original values"
|
||||
},
|
||||
"current-values": "Current values",
|
||||
"original-values": "Original values",
|
||||
"title": "Revert to the original values?"
|
||||
},
|
||||
"roles": {
|
||||
"inactive": "Inactive",
|
||||
"manager-admin": "Manager & admin",
|
||||
@ -2542,4 +2552,4 @@
|
||||
}
|
||||
},
|
||||
"yesterday": "Yesterday"
|
||||
}
|
||||
}
|
||||
|
||||
5
apps/red-ui/src/assets/icons/general/arrow-right.svg
Normal file
5
apps/red-ui/src/assets/icons/general/arrow-right.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M31.1,85c0.9,0.9,2,1.3,3.2,1.3s2.3-0.4,3.2-1.3l31.8-31.8c0.8-0.8,1.3-2,1.3-3.2s-0.5-2.3-1.3-3.2L37.4,15.5 c-1.8-1.7-4.6-1.7-6.4,0.1c-1.7,1.8-1.7,4.6,0.1,6.4L59.6,50L31.1,78.6C29.3,80.4,29.3,83.2,31.1,85z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
23
apps/red-ui/src/assets/icons/general/draggable-dots.svg
Normal file
23
apps/red-ui/src/assets/icons/general/draggable-dots.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="600px" height="800px" viewBox="0 0 276.167 276.167"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M33.144,2.471C15.336,2.471,0.85,16.958,0.85,34.765s14.48,32.293,32.294,32.293s32.294-14.486,32.294-32.293
|
||||
S50.951,2.471,33.144,2.471z"/>
|
||||
<path d="M137.663,2.471c-17.807,0-32.294,14.487-32.294,32.294s14.487,32.293,32.294,32.293c17.808,0,32.297-14.486,32.297-32.293
|
||||
S155.477,2.471,137.663,2.471z"/>
|
||||
<path d="M32.3,170.539c17.807,0,32.297-14.483,32.297-32.293c0-17.811-14.49-32.297-32.297-32.297S0,120.436,0,138.246
|
||||
C0,156.056,14.493,170.539,32.3,170.539z"/>
|
||||
<path d="M136.819,170.539c17.804,0,32.294-14.483,32.294-32.293c0-17.811-14.478-32.297-32.294-32.297
|
||||
c-17.813,0-32.294,14.486-32.294,32.297C104.525,156.056,119.012,170.539,136.819,170.539z"/>
|
||||
<path d="M33.039,209.108c-17.807,0-32.3,14.483-32.3,32.294c0,17.804,14.493,32.293,32.3,32.293s32.293-14.482,32.293-32.293
|
||||
S50.846,209.108,33.039,209.108z"/>
|
||||
<path d="M137.564,209.108c-17.808,0-32.3,14.483-32.3,32.294c0,17.804,14.487,32.293,32.3,32.293
|
||||
c17.804,0,32.293-14.482,32.293-32.293S155.368,209.108,137.564,209.108z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -55,3 +55,11 @@ button.Button[data-element='LOAD_ALL_ANNOTATIONS'] > img[src='/ui/assets/icons/g
|
||||
.HeaderItems .Button.active > img {
|
||||
filter: invert(19%) sepia(100%) saturate(791%) hue-rotate(175deg) brightness(89%) contrast(85%);
|
||||
}
|
||||
|
||||
.MainHeader {
|
||||
height: 36px !important;
|
||||
}
|
||||
|
||||
.view-header-border {
|
||||
background: rgba(226, 228, 233, 0.9);
|
||||
}
|
||||
|
||||
@ -163,12 +163,15 @@ $dark-accent-10: darken(vars.$accent, 10%);
|
||||
|
||||
body {
|
||||
--workload-width: 350px;
|
||||
--documine-workload-content-width: 200px;
|
||||
--structured-component-management-width: 30%;
|
||||
--qiuck-navigation-width: 61px;
|
||||
--iqser-app-name-font-family: OpenSans Extrabold, sans-serif;
|
||||
--iqser-app-name-font-size: 13px;
|
||||
--iqser-logo-size: 28px;
|
||||
}
|
||||
|
||||
#viewer {
|
||||
.redaction-viewer {
|
||||
visibility: hidden;
|
||||
width: calc(100% - var(--workload-width));
|
||||
height: calc(100% - calc(var(--iqser-top-bar-height) + 50px));
|
||||
@ -176,3 +179,14 @@ body {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.documine-viewer {
|
||||
visibility: hidden;
|
||||
width: calc(
|
||||
100% - var(--structured-component-management-width) - var(--documine-workload-content-width) - var(--qiuck-navigation-width) - 3px
|
||||
);
|
||||
height: calc(100% - calc(var(--iqser-top-bar-height) + 50px));
|
||||
bottom: 0;
|
||||
right: calc(var(--qiuck-navigation-width) + 2px);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 04eaca1600149e3f4e803fd2334c25465197dbf6
|
||||
Subproject commit 748cce403285c97e14cd3115a828c94c9d5d4520
|
||||
@ -6,16 +6,19 @@ export interface IComponentLogEntry {
|
||||
name: string;
|
||||
originalKey: string;
|
||||
componentValues: IComponentValue[];
|
||||
overridden?: boolean;
|
||||
}
|
||||
|
||||
export class ComponentLogEntry implements IComponentLogEntry {
|
||||
readonly name: string;
|
||||
readonly originalKey: string;
|
||||
readonly componentValues: ComponentValue[];
|
||||
readonly overridden: boolean;
|
||||
|
||||
constructor(entry: IComponentLogEntry) {
|
||||
this.name = entry.name.replaceAll('_', ' ');
|
||||
this.name = entry.name;
|
||||
this.originalKey = entry.name;
|
||||
this.componentValues = entry.componentValues;
|
||||
this.overridden = !!entry.overridden;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
export interface EntityReference {
|
||||
readonly id: string;
|
||||
readonly type: string;
|
||||
readonly entityRuleId: string;
|
||||
readonly page: number;
|
||||
id: string;
|
||||
type: string;
|
||||
entityRuleId: string;
|
||||
page: number;
|
||||
displayValue?: string;
|
||||
}
|
||||
|
||||
export interface IComponentValue {
|
||||
readonly value: string;
|
||||
readonly originalValue: string;
|
||||
readonly valueDescription: string;
|
||||
readonly componentRuleId: string;
|
||||
readonly entityReferences: EntityReference[];
|
||||
value: string;
|
||||
originalValue: string;
|
||||
valueDescription: string;
|
||||
componentRuleId: string;
|
||||
entityReferences: EntityReference[];
|
||||
}
|
||||
|
||||
export class ComponentValue implements IComponentValue {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user