Merge branch 'master' into VM/RED-4247

This commit is contained in:
Valentin Mihai 2022-06-30 00:42:25 +03:00
commit 4c14a86fbe
82 changed files with 968 additions and 702 deletions

View File

@ -1,8 +1,10 @@
import { Component, ViewContainerRef } from '@angular/core';
import { Component, Inject, Renderer2, ViewContainerRef } from '@angular/core';
import { RouterHistoryService } from '@services/router-history.service';
import { UserService } from '@services/user.service';
import { REDDocumentViewer } from './modules/pdf-viewer/services/document-viewer.service';
import { DossiersChangesService } from '@services/dossiers/dossier-changes.service';
import { DOCUMENT } from '@angular/common';
import { UserPreferenceService } from '@services/user-preference.service';
@Component({
selector: 'redaction-root',
@ -16,9 +18,13 @@ export class AppComponent {
readonly viewContainerRef: ViewContainerRef,
private readonly _routerHistoryService: RouterHistoryService,
private readonly _userService: UserService,
private readonly _userPreferenceService: UserPreferenceService,
readonly documentViewer: REDDocumentViewer,
private readonly _dossierChangesService: DossiersChangesService,
@Inject(DOCUMENT) private document: Document,
private renderer: Renderer2,
) {
this.renderer.addClass(this.document.body, _userPreferenceService.getTheme());
// TODO: Find a better place to initialize dossiers refresh
if (_userService.currentUser?.isUser) {
_dossierChangesService.initializeRefresh();

View File

@ -22,7 +22,7 @@
[placeholder]="'search.placeholder' | translate"
iqserHelpMode="search_in_entire_application"
></redaction-spotlight-search>
<iqser-help-button [dialogButton]="false"></iqser-help-button>
<iqser-help-button></iqser-help-button>
<redaction-notifications iqserHelpMode="open_notifications"></redaction-notifications>
</div>

View File

@ -41,7 +41,7 @@
white-space: normal;
a {
color: var(--iqser-accent);
color: var(--iqser-text);
font-weight: bold;
}
}
@ -55,7 +55,7 @@
}
&:hover {
background-color: var(--iqser-grey-6);
background-color: var(--iqser-tab-hover);
.dot {
background-color: var(--iqser-grey-5);

View File

@ -7,13 +7,13 @@
font-size: 13px;
border: none;
outline: none;
color: var(--iqser-accent);
background-color: var(--iqser-white);
color: var(--iqser-text);
background-color: transparent;
}
.highlight {
border-radius: 4px;
background-color: var(--iqser-grey-2);
background-color: var(--iqser-side-nav);
}
.wrapper {

View File

@ -88,7 +88,7 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
}
get isSuperTypeBasedColor() {
return this.isSkipped || this.isSuggestion || this.isDeclinedSuggestion || this.isIgnoredHint;
return this.isSuggestion || this.isDeclinedSuggestion || this.isIgnoredHint;
}
get isSkipped() {
@ -359,8 +359,16 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
content += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
}
if (annotationWrapper.hasBeenRemovedByManualOverride) {
content += 'Removed by manual override';
}
if (entry.section) {
content += 'In section: "' + entry.section + '"';
let prefix = 'In section: ';
if (content.length) {
prefix = ` ${prefix.toLowerCase()}`;
}
content += `${prefix} "${entry.section}"`;
}
annotationWrapper.shortContent = this._getShortContent(annotationWrapper, entry) || content;
@ -429,7 +437,7 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
case LogEntryStatus.APPROVED:
return SuperTypes.Redaction;
case LogEntryStatus.DECLINED:
return SuperTypes.Skipped;
return isHintDictionary ? SuperTypes.Hint : SuperTypes.Skipped;
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionRemoveDictionary;
}

View File

@ -1,7 +1,7 @@
@use 'common-mixins';
.content-container {
background-color: var(--iqser-grey-2);
background-color: var(--iqser-alt-background);
justify-content: center;
@include common-mixins.scroll-bar;
overflow: auto;

View File

@ -16,7 +16,7 @@
.statement {
opacity: 0.7;
color: var(--iqser-grey-1);
color: var(--iqser-text);
font-weight: 500;
padding: 10px 0;
}
@ -40,7 +40,7 @@
padding: 10px 0;
.group-title {
color: var(--iqser-grey-1);
color: var(--iqser-text);
font-weight: 600;
}

View File

@ -15,7 +15,8 @@
<label translate="user-profile-screen.form.last-name"></label>
<input formControlName="lastName" name="lastName" type="text" />
</div>
<div class="iqser-input-group">
<div *ngIf="userPreferences.areDevFeaturesEnabled" class="iqser-input-group">
<label translate="top-bar.navigation-items.my-account.children.language.label"></label>
<mat-select formControlName="language">
<mat-option *ngFor="let language of languages" [value]="language">
@ -23,14 +24,26 @@
</mat-option>
</mat-select>
</div>
<div class="iqser-input-group">
<a [href]="changePasswordUrl" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
</div>
<div *ngIf="devMode" class="iqser-input-group">
<mat-slide-toggle color="primary" formControlName="darkTheme">
{{ 'user-profile-screen.form.dark-theme' | translate }}
</mat-slide-toggle>
</div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="form.invalid || !(profileChanged || languageChanged)" color="primary" mat-flat-button type="submit">
<button
[disabled]="form.invalid || !(profileChanged || languageChanged || themeChanged)"
color="primary"
mat-flat-button
type="submit"
>
{{ 'user-profile-screen.actions.save' | translate }}
</button>
</div>

View File

@ -1,3 +1,3 @@
a {
color: black;
color: var(--iqser-text);
}

View File

@ -10,6 +10,7 @@ import { UserService } from '@services/user.service';
import { ConfigService } from '@services/config.service';
import { LanguageService } from '../../../../../i18n/language.service';
import { firstValueFrom } from 'rxjs';
import { UserPreferenceService } from '@services/user-preference.service';
@Component({
selector: 'redaction-user-profile-screen',
@ -18,38 +19,44 @@ import { firstValueFrom } from 'rxjs';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserProfileScreenComponent extends BaseFormComponent implements OnInit {
changePasswordUrl: SafeResourceUrl;
translations = languagesTranslations;
readonly translations = languagesTranslations;
readonly devMode = this._userPreferenceService.areDevFeaturesEnabled;
readonly changePasswordUrl: SafeResourceUrl;
private _profileModel: IProfile;
#profileModel: IProfile;
constructor(
readonly permissionsService: PermissionsService,
private readonly _formBuilder: UntypedFormBuilder,
domSanitizer: DomSanitizer,
configService: ConfigService,
private readonly _userService: UserService,
private readonly _configService: ConfigService,
private readonly _languageService: LanguageService,
private readonly _domSanitizer: DomSanitizer,
readonly permissionsService: PermissionsService,
readonly userPreferences: UserPreferenceService,
private readonly _loadingService: LoadingService,
private readonly _formBuilder: UntypedFormBuilder,
private readonly _languageService: LanguageService,
protected readonly _translateService: TranslateService,
protected readonly _userPreferenceService: UserPreferenceService,
) {
super();
this._loadingService.start();
this.changePasswordUrl = this._domSanitizer.bypassSecurityTrustResourceUrl(
`${this._configService.values.OAUTH_URL}/account/password`,
);
this.changePasswordUrl = domSanitizer.bypassSecurityTrustResourceUrl(`${configService.values.OAUTH_URL}/account/password`);
}
get languageChanged(): boolean {
return this._profileModel['language'] !== this.form.get('language').value;
return this.#profileModel['language'] !== this.form.get('language').value;
}
get themeChanged(): boolean {
return this.#profileModel['darkTheme'] !== this.form.get('darkTheme').value;
}
get profileChanged(): boolean {
const keys = Object.keys(this.form.getRawValue());
keys.splice(keys.indexOf('language'), 1);
keys.splice(keys.indexOf('darkTheme'), 1);
for (const key of keys) {
if (this._profileModel[key] !== this.form.get(key).value) {
if (this.#profileModel[key] !== this.form.get(key).value) {
return true;
}
}
@ -86,6 +93,10 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
await firstValueFrom(this._userService.loadAll());
}
if (this.themeChanged) {
await this._userPreferenceService.saveTheme(this.form.get('darkTheme').value ? 'dark' : 'light');
}
this._initializeForm();
}
@ -95,23 +106,25 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
firstName: [undefined],
lastName: [undefined],
language: [undefined],
darkTheme: [false],
});
}
private _initializeForm(): void {
try {
this.form = this._getForm();
this._profileModel = {
this.#profileModel = {
email: this._userService.currentUser.email,
firstName: this._userService.currentUser.firstName,
lastName: this._userService.currentUser.lastName,
language: this._languageService.currentLanguage,
darkTheme: this._userPreferenceService.getTheme() === 'dark',
};
if (this._userService.currentUser.email) {
// disable email if it's already set
this.form.get('email').disable();
}
this.form.patchValue(this._profileModel, { emitEvent: false });
this.form.patchValue(this.#profileModel, { emitEvent: false });
this.initialFormValue = this.form.getRawValue();
} catch (e) {
} finally {

View File

@ -54,7 +54,7 @@
.csv-part-header {
min-height: 50px;
box-sizing: border-box;
background: var(--iqser-white);
background: var(--iqser-background);
border-top: 1px solid var(--iqser-separator);
border-bottom: 1px solid var(--iqser-separator);
display: flex;
@ -82,7 +82,7 @@
}
.search-input-container {
background-color: var(--iqser-white);
background-color: var(--iqser-background);
border-bottom: 1px solid var(--iqser-separator);
padding: 8px 16px;
}
@ -100,7 +100,7 @@
align-items: center;
justify-content: center;
text-align: center;
color: var(--iqser-grey-7);
color: var(--iqser-btn-bg);
line-height: 15px;
font-weight: 500;
}
@ -116,14 +116,14 @@
> .left {
width: 375px;
min-width: 375px;
background: var(--iqser-grey-2);
background: var(--iqser-alt-background);
display: flex;
flex-direction: column;
.csv-header-pill-content {
overflow: auto;
padding: 7px 0;
background: var(--iqser-grey-2);
background: var(--iqser-alt-background);
@include common-mixins.no-scroll-bar;
.csv-header-pill-wrapper {
@ -133,7 +133,7 @@
min-height: 32px;
border-radius: 8px;
padding: 10px;
background: var(--iqser-white);
background: var(--iqser-background);
cursor: pointer;
display: flex;
flex-direction: column;
@ -169,7 +169,7 @@
min-width: 150px;
display: flex;
flex-direction: column;
background: var(--iqser-grey-2);
background: var(--iqser-alt-background);
border-right: 1px solid var(--iqser-separator);
&:not(.collapsed) iqser-circle-button {

View File

@ -102,8 +102,8 @@
{{ log.message }}
</div>
<div class="small-label cell">
{{ log.recordDate | date: 'd MMM yyyy, hh:mm a' }}
<div class="cell">
<span class="small-label">{{ log.recordDate | date: 'd MMM yyyy, hh:mm a' }}</span>
</div>
<div class="user-column cell">

View File

@ -77,12 +77,12 @@
{{ attribute.label }}
</div>
<div class="cell small-label">
{{ attribute.placeholder }}
<div class="cell">
<span class="small-label">{{ attribute.placeholder }}</span>
</div>
<div class="cell small-label">
{{ translations[attribute.type] | translate }}
<div class="cell">
<span class="small-label">{{ translations[attribute.type] | translate }}</span>
</div>
<div class="cell">

View File

@ -70,12 +70,12 @@
</div>
</div>
<div class="cell small-label">
<span>{{ state.rank }}</span>
<div class="cell">
<span class="small-label">{{ state.rank }}</span>
</div>
<div class="cell small-label">
<span>{{ state.dossierCount }}</span>
<div class="cell">
<span class="small-label">{{ state.dossierCount }}</span>
</div>
<div class="cell">

View File

@ -9,7 +9,7 @@
font-size: 16px;
font-weight: 600;
line-height: 20px;
color: var(--iqser-grey-1);
color: var(--iqser-text);
}
.right-container {

View File

@ -78,8 +78,8 @@
</div>
</div>
<div class="cell center small-label">
{{ dict.rank }}
<div class="cell center">
<span class="small-label">{{ dict.rank }}</span>
</div>
<div class="cell center">

View File

@ -18,7 +18,7 @@
padding: 30px;
overflow: auto;
@include common-mixins.scroll-bar;
background-color: var(--iqser-grey-2);
background-color: var(--iqser-alt-background);
justify-content: center;
.dialog {

View File

@ -96,7 +96,9 @@
<span>{{ attribute.label }}</span>
</div>
<div [translate]="translations[attribute.type]" class="small-label cell"></div>
<div class="cell">
<span [translate]="translations[attribute.type]" class="small-label"></span>
</div>
<div class="center read-only cell">
<mat-icon
@ -107,8 +109,8 @@
></mat-icon>
</div>
<div class="small-label cell">
{{ attribute.csvColumnHeader }}
<div class="cell">
<span class="small-label">{{ attribute.csvColumnHeader }}</span>
</div>
<div class="center cell">

View File

@ -1,7 +1,7 @@
@use 'common-mixins';
.content-container {
background-color: var(--iqser-grey-2);
background-color: var(--iqser-alt-background);
display: flex;
flex-direction: column;
align-items: center;

View File

@ -66,7 +66,7 @@
}
.tooltip-anchor {
fill: rgb(0, 0, 0);
fill: var(--iqser-text);
}
.gridline-path {
@ -86,4 +86,18 @@
}
}
}
fill: var(--iqser-text);
}
.chart-legend .legend-labels {
background: var(--iqser-alt-background) !important;
.legend-label .legend-label-text {
color: var(--iqser-text) !important;
&:hover {
color: rgba(var(--iqser-text-rgb), 0.8) !important;
}
}
}

View File

@ -27,7 +27,7 @@
&:hover {
> div {
background-color: var(--iqser-grey-2);
background-color: var(--iqser-alt-background);
}
}
}

View File

@ -24,7 +24,7 @@
.template {
padding: 8px 10px;
background-color: var(--iqser-grey-6);
background-color: var(--iqser-btn-bg);
border-radius: 4px;
transition: background-color 0.2s;
position: relative;
@ -51,7 +51,7 @@
}
&:hover {
background-color: var(--iqser-grey-4);
background-color: var(--iqser-btn-bg-hover);
.actions {
display: flex;

View File

@ -7,6 +7,7 @@ import { RulesService } from '../../../services/rules.service';
import { firstValueFrom } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
import { EditorThemeService } from '@services/editor-theme.service';
import ICodeEditor = monaco.editor.ICodeEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
@ -44,6 +45,7 @@ export class RulesScreenComponent implements OnInit {
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _toaster: Toaster,
private readonly _loadingService: LoadingService,
private readonly _editorThemeService: EditorThemeService,
route: ActivatedRoute,
) {
this.#dossierTemplateId = route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
@ -73,15 +75,10 @@ export class RulesScreenComponent implements OnInit {
onCodeEditorInit(editor: ICodeEditor) {
this._codeEditor = editor;
(window as any).monaco.editor.defineTheme('redaction', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.lineHighlightBackground': '#f4f5f7',
},
});
(window as any).monaco.editor.setTheme('redaction');
for (const theme of this._editorThemeService.themes) {
(window as any).monaco.editor.defineTheme(theme, this._editorThemeService.configurations[theme]);
}
(window as any).monaco.editor.setTheme(this._editorThemeService.getTheme(true));
this._changeDetectorRef.detectChanges();
}

View File

@ -74,7 +74,9 @@
<redaction-initials-avatar [showYou]="true" [user]="user" [withName]="true"></redaction-initials-avatar>
</div>
<div class="small-label cell">{{ user.email || '-' }}</div>
<div class="cell">
<span class="small-label">{{ user.email || '-' }}</span>
</div>
<div class="center cell">
<mat-slide-toggle
@ -85,7 +87,9 @@
></mat-slide-toggle>
</div>
<div class="small-label cell">{{ getDisplayRoles(user) }}</div>
<div class="cell">
<span class="small-label">{{ getDisplayRoles(user) }}</span>
</div>
<div class="cell">
<div class="action-buttons">

View File

@ -39,7 +39,7 @@
width: 60px;
height: 60px;
border-radius: 8px;
background-color: var(--iqser-grey-6);
background-color: var(--iqser-btn-bg);
color: var(--iqser-grey-7);
align-items: center;
justify-content: center;
@ -53,7 +53,7 @@
}
&:not(.disabled):not(.active):hover {
background-color: darken(variables.$grey-6, 2);
background-color: var(--iqser-btn-bg-hover);
}
&:not(:last-child) {

View File

@ -11,8 +11,9 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { WatermarkService } from '@services/entity-services/watermark.service';
import { firstValueFrom, Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { LicenseService } from '@services/license.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { ActivatedRoute, Router } from '@angular/router';
import { LicenseService } from '../../../../../services/license.service';
export const DEFAULT_WATERMARK: IWatermark = {
id: null,
@ -56,6 +57,7 @@ export class WatermarkScreenComponent implements OnInit {
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
private readonly _watermarkService: WatermarkService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _router: Router,
) {
this._loadingService.start();
@ -154,6 +156,8 @@ export class WatermarkScreenComponent implements OnInit {
).then(instance => {
this._instance = instance;
instance.UI.setTheme(this._userPreferenceService.getTheme());
instance.Core.documentViewer.on('documentLoaded', async () => {
this._loadingService.stop();
await this._drawWatermark();

View File

@ -1,4 +1,4 @@
<form [formGroup]="form" *ngIf="form">
<form *ngIf="form" [formGroup]="form">
<div class="row">
<div class="iqser-input-group required">
<label translate="add-edit-entity.form.name"></label>
@ -12,45 +12,24 @@
</div>
<div class="row mt-14">
<div class="iqser-input-group required">
<label translate="add-edit-entity.form.color"></label>
<div *ngFor="let color of colors" class="iqser-input-group required">
<label [translate]="color.label"></label>
<input
[placeholder]="'add-edit-entity.form.color-placeholder' | translate"
[formControlName]="color.controlName"
[name]="color.controlName"
[placeholder]="color.placeholder | translate"
class="hex-color-input"
formControlName="hexColor"
name="hexColor"
type="text"
/>
<div
(colorPickerChange)="form.get('hexColor').setValue($event)"
[colorPicker]="form.get('hexColor').value"
[cpDisabled]="form.get('hexColor').disabled"
(colorPickerChange)="form.get(color.controlName).setValue($event)"
[colorPicker]="form.get(color.controlName).value"
[cpDisabled]="form.get(color.controlName).disabled"
[cpOutputFormat]="'hex'"
[style.background]="form.get('hexColor').value"
[style.background]="form.get(color.controlName).value"
class="input-icon"
>
<mat-icon *ngIf="hasHexColor$ | async" svgIcon="red:color-picker"></mat-icon>
</div>
</div>
<div class="iqser-input-group required">
<label translate="add-edit-entity.form.recommendation-color"></label>
<input
[placeholder]="'add-edit-entity.form.recommendation-color-placeholder' | translate"
class="hex-color-input"
formControlName="recommendationHexColor"
name="recommendationHexColor"
type="text"
/>
<div
(colorPickerChange)="form.get('recommendationHexColor').setValue($event)"
[colorPicker]="form.get('recommendationHexColor').value"
[cpDisabled]="form.get('recommendationHexColor').disabled"
[cpOutputFormat]="'hex'"
[style.background]="form.get('recommendationHexColor').value"
class="input-icon"
>
<mat-icon *ngIf="hasRecommendationHexColor$ | async" svgIcon="red:color-picker"></mat-icon>
<mat-icon *ngIf="color.hasColor$ | async" svgIcon="red:color-picker"></mat-icon>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { Dictionary, IDictionary } from '@red/domain';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
import { FormGroup, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
import { map, startWith } from 'rxjs/operators';
import { firstValueFrom, Observable } from 'rxjs';
import { toSnakeCase } from '@utils/functions';
@ -10,7 +10,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { BaseFormComponent, LoadingService, Toaster } from '@iqser/common-ui';
const REDACTION_FIELDS = ['defaultReason', 'addToDictionaryAction'];
const REDACTION_FIELDS = ['defaultReason'];
@Component({
selector: 'redaction-add-edit-entity [entity] [dossierTemplateId]',
@ -22,10 +22,10 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
@Input() dossierTemplateId: string;
@Input() entity: Dictionary;
hasHexColor$: Observable<boolean>;
hasRecommendationHexColor$: Observable<boolean>;
technicalName$: Observable<string>;
colors: { label: string; placeholder: string; controlName: string; hasColor$: Observable<boolean> }[];
constructor(
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _permissionsService: PermissionsService,
@ -77,6 +77,29 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
}
}
#initializeColors(form: FormGroup): void {
this.colors = [
{
label: _('add-edit-entity.form.color'),
placeholder: _('add-edit-entity.form.color-placeholder'),
controlName: 'hexColor',
hasColor$: this._colorEmpty$(form, 'hexColor'),
},
{
label: _('add-edit-entity.form.recommendation-color'),
placeholder: _('add-edit-entity.form.recommendation-color-placeholder'),
controlName: 'recommendationHexColor',
hasColor$: this._colorEmpty$(form, 'recommendationHexColor'),
},
{
label: _('add-edit-entity.form.skipped-color'),
placeholder: _('add-edit-entity.form.skipped-color-placeholder'),
controlName: 'skippedHexColor',
hasColor$: this._colorEmpty$(form, 'skippedHexColor'),
},
];
}
private _initializeForm(): void {
const controlsConfig = {
type: [this.entity?.type],
@ -85,6 +108,7 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
rank: [{ value: this.entity?.rank, disabled: this.#isSystemManaged }, Validators.required],
hexColor: [this.entity?.hexColor, [Validators.required, Validators.minLength(7)]],
recommendationHexColor: [this.entity?.recommendationHexColor, [Validators.required, Validators.minLength(7)]],
skippedHexColor: [this.entity?.skippedHexColor, [Validators.required, Validators.minLength(7)]],
hint: [{ value: !!this.entity?.hint, disabled: this.#isSystemManaged }],
hasDictionary: [{ value: !!this.entity?.hasDictionary, disabled: this.#isSystemManaged }],
caseSensitive: [{ value: this.entity ? !this.entity.caseInsensitive : false, disabled: this.#isSystemManaged }],
@ -94,17 +118,17 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
Object.assign(controlsConfig, {
defaultReason: [{ value: null, disabled: true }],
});
if (this.entity?.hasDictionary) {
Object.assign(controlsConfig, {
addToDictionaryAction: [this.#addToDictionaryActionControl],
});
}
}
if (this.entity?.hasDictionary) {
Object.assign(controlsConfig, {
addToDictionaryAction: [this.#addToDictionaryActionControl],
});
}
const form = this._formBuilder.group(controlsConfig);
this.hasHexColor$ = this._colorEmpty$(form, 'hexColor');
this.hasRecommendationHexColor$ = this._colorEmpty$(form, 'recommendationHexColor');
this.#initializeColors(form);
this.technicalName$ = form.get('label').valueChanges.pipe(map((value: string) => this._toTechnicalName(value)));
form.get('hint').valueChanges.subscribe(isHint => {
@ -112,15 +136,11 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
REDACTION_FIELDS.forEach(field => form.removeControl(field));
} else {
form.addControl('defaultReason', new UntypedFormControl({ value: null, disabled: true }));
if (form.get('hasDictionary').value) {
form.addControl('addToDictionaryAction', new UntypedFormControl(this.#addToDictionaryActionControl));
}
}
});
form.get('hasDictionary').valueChanges.subscribe(hasDictionary => {
if (hasDictionary && !form.get('hint').value) {
if (hasDictionary) {
form.addControl('addToDictionaryAction', new UntypedFormControl(this.#addToDictionaryActionControl));
} else {
form.removeControl('addToDictionaryAction');
@ -171,6 +191,7 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
description: this.form.get('description').value,
hexColor: this.form.get('hexColor').value,
recommendationHexColor: this.form.get('recommendationHexColor').value,
skippedHexColor: this.form.get('skippedHexColor').value,
hint: this.form.get('hint').value,
rank: this.form.get('rank').value,
dossierTemplateId: this.dossierTemplateId,

View File

@ -2,7 +2,9 @@
<redaction-dossier-name-column [dossierStats]="stats$ | async" [dossier]="dossier"></redaction-dossier-name-column>
</div>
<div class="cell small-label">{{ dossier.archivedTime | date: 'd MMM yyyy' }}</div>
<div class="cell">
<span class="small-label">{{ dossier.archivedTime | date: 'd MMM yyyy' }}</span>
</div>
<div class="cell user-column">
<redaction-initials-avatar [user]="dossier.ownerId" [withName]="true"></redaction-initials-avatar>

View File

@ -17,7 +17,7 @@
&:not(.empty) {
&:hover {
background-color: var(--iqser-grey-2);
background-color: var(--iqser-side-nav);
.heading {
text-decoration: underline;

View File

@ -1,6 +1,6 @@
:host {
align-items: center;
background-color: var(--iqser-grey-2);
background-color: var(--iqser-alt-background);
.container {
padding: 32px;

View File

@ -21,7 +21,7 @@
transition: background-color 0.2s;
&:hover {
background-color: var(--iqser-grey-6);
background-color: var(--iqser-btn-bg);
}
}
}

View File

@ -29,7 +29,7 @@
padding: 3px 8px;
&:hover {
background-color: var(--iqser-grey-6);
background-color: var(--iqser-tab-hover);
}
&.active {

View File

@ -31,5 +31,5 @@
}
.right-chart {
border-left: 1px solid rgba(226, 228, 233, 0.9);
border-left: 1px solid var(--iqser-separator);
}

View File

@ -28,7 +28,7 @@
height: 24px;
&:hover {
background-color: var(--iqser-grey-6);
background-color: var(--iqser-tab-hover);
border-radius: 12px;
mat-icon {

View File

@ -52,7 +52,7 @@
}
&:hover {
background-color: var(--iqser-grey-4);
background-color: var(--iqser-annotation-comments-hover);
}
}
}
@ -60,7 +60,7 @@
&:hover,
&.help-mode {
background-color: var(--iqser-grey-8);
background-color: var(--iqser-annotation-hover);
::ng-deep .annotation-actions {
display: flex;

View File

@ -78,7 +78,7 @@
outline: none;
&.active-panel {
background-color: #fafafa;
background-color: var(--iqser-workload-pages-bg);
}
}
@ -98,7 +98,7 @@
transition: background-color 0.25s;
&:not(.disabled):hover {
background-color: var(--iqser-grey-6);
background-color: var(--iqser-tab-hover);
}
mat-icon {

View File

@ -1,12 +1,12 @@
.page-wrapper {
color: var(--iqser-accent);
color: var(--iqser-text);
position: relative;
padding: 12px 14px 12px 8px;
cursor: pointer;
border-left: 4px solid transparent;
&:hover {
background-color: var(--iqser-grey-2);
background-color: var(--iqser-alt-background);
}
&.active {
@ -33,10 +33,10 @@
}
&.read {
color: var(--iqser-grey-5);
color: var(--iqser-inputs-outline);
.text {
color: var(--iqser-accent);
color: var(--iqser-text);
}
}

View File

@ -32,7 +32,12 @@ export class TypeAnnotationIconComponent implements OnChanges {
this.color = this.annotation.color;
} else {
const type = this.annotation.isSuperTypeBasedColor ? this.annotation.superType : this.annotation.type;
this.color = this._dictionariesMapService.getDictionaryColor(type, this._dossierTemplateId, this.annotation.isRecommendation);
this.color = this._dictionariesMapService.getDictionaryColor(
type,
this._dossierTemplateId,
this.annotation.isRecommendation,
this.annotation.isSkipped,
);
}
this.type =

View File

@ -1,7 +1,7 @@
.vertical-line {
width: 1px;
height: 30px;
background-color: var(--iqser-grey-4);
background-color: var(--iqser-separator);
margin: 0 16px;
}

View File

@ -6,7 +6,6 @@ import {
bool,
CircleButtonTypes,
CustomError,
Debounce,
ErrorService,
FilterService,
List,
@ -92,7 +91,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _router: Router,
private readonly _ngZone: NgZone,
private readonly _logger: NGXLogger,
private readonly _filesService: FilesService,
private readonly _annotationManager: REDAnnotationManager,
private readonly _errorService: ErrorService,
private readonly _filterService: FilterService,
@ -307,14 +305,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
}
viewerPageChanged(page: number) {
// this.multiSelectService.deactivate();
return this.#updateQueryParamsPage(page);
}
@Debounce(100)
async viewerReady() {
// Go to initial page from query params
const pageNumber: string = this._activatedRoute.snapshot.queryParams.page;
if (pageNumber) {
const file = this.state.file;
@ -327,7 +318,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
page = file.numberOfPages;
}
this.pdf.navigateTo(page);
setTimeout(() => {
this.pdf.navigateTo(page);
}, 300);
}
this._loadingService.stop();
@ -347,7 +340,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) {
const fileManagementService = this._injector.get(FileManagementService);
const originalFile = fileManagementService.downloadOriginalFile(dossierId, fileId, 'response', cacheIdentifier);
const originalFile = fileManagementService.downloadOriginal(dossierId, fileId, 'response', cacheIdentifier);
download(await firstValueFrom(originalFile), filename);
}
@ -368,21 +361,24 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
pairwise(),
tap(annotations => this.deleteAnnotations(...annotations)),
);
const currentPageAnnotations$ = combineLatest([this.pdf.currentPage$, annotations$]).pipe(
const currentPageIfNotHighlightsView$ = combineLatest([this.pdf.currentPage$, this._viewModeService.viewMode$]).pipe(
filter(([, viewMode]) => viewMode !== ViewModes.TEXT_HIGHLIGHTS),
map(([page]) => page),
);
const currentPageAnnotations$ = combineLatest([currentPageIfNotHighlightsView$, annotations$]).pipe(
map(
([page, [oldAnnotations, newAnnotations]]) =>
[oldAnnotations.filter(byPage(page)), newAnnotations.filter(byPage(page))] as const,
),
);
let start;
return combineLatest([currentPageAnnotations$, documentLoaded$]).pipe(
filter(([, loaded]) => loaded),
tap(() => (start = new Date().getTime())),
map(([annotations]) => annotations),
switchMap(annotations => this.drawChangedAnnotations(...annotations)),
tap(([, newAnnotations]) => this.#highlightSelectedAnnotations(newAnnotations)),
tap(() => this._logger.info(`[ANNOTATIONS] Processing time: ${new Date().getTime() - start}`)),
tap(() => this.updateViewMode()),
);
}
@ -430,7 +426,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
#rebuildFilters() {
const startTime = new Date().getTime();
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this._fileDataService.all);
const annotationFilters = this._annotationProcessingService.getAnnotationFilter();
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
this._filterService.addFilterGroup({
slug: 'primaryFilters',
@ -451,6 +447,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
async #updateQueryParamsPage(page: number): Promise<void> {
console.log('updateQueryParamsPage: ', page);
const extras: NavigationExtras = {
queryParams: { page },
queryParamsHandling: 'merge',
@ -549,7 +546,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
.pipe(
switchMap(blob => from(this._documentViewer.lock()).pipe(map(() => blob))),
tap(() => this._errorService.clear()),
tap(blob => this.pdf.loadDocument(blob, this.state.file)),
tap(blob => this.pdf.loadDocument(blob, this.state.file, () => this.state.reloadBlob())),
)
.subscribe();
@ -558,7 +555,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
});
this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page =>
this._ngZone.run(() => this.viewerPageChanged(page)),
this._ngZone.run(() => {
console.log('viewerPageChanged', page);
return this.#updateQueryParamsPage(page);
}),
);
this.addActiveScreenSubscription = this.pdfProxyService.annotationSelected$.subscribe();
}
@ -587,30 +587,28 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
if (!newAnnotations.length) {
return;
}
const currentFilters = this._filterService.getGroup('primaryFilters')?.filters || [];
this.#rebuildFilters();
const startTime = new Date().getTime();
if (currentFilters) {
this._handleDeltaAnnotationFilters(currentFilters, this._fileDataService.all);
this._handleDeltaAnnotationFilters(currentFilters);
}
await this._annotationDrawService.draw(newAnnotations, this.state.dossierTemplateId, this._skippedService.hideSkipped);
this._logger.info(`[ANNOTATIONS] Redraw time: ${new Date().getTime() - startTime} ms for ${newAnnotations.length} annotations`);
}
private _handleDeltaAnnotationFilters(currentFilters: NestedFilter[], newAnnotations: AnnotationWrapper[]) {
private _handleDeltaAnnotationFilters(currentFilters: NestedFilter[]) {
const primaryFilterGroup = this._filterService.getGroup('primaryFilters');
const primaryFilters = primaryFilterGroup.filters;
const secondaryFilters = this._filterService.getGroup('secondaryFilters').filters;
const hasAnyFilterSet = [...primaryFilters, ...secondaryFilters].find(f => f.checked || f.indeterminate);
const hasAnyFilterSet = [...primaryFilters, ...secondaryFilters].some(f => f.checked || f.indeterminate);
if (!hasAnyFilterSet) {
return;
}
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newAnnotations);
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter();
handleFilterDelta(currentFilters, newPageSpecificFilters, primaryFilters);
this._filterService.addFilterGroup({
@ -626,7 +624,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
}
private _setAnnotationsOpacity(annotations: Annotation[], restoreToOriginal: boolean = false) {
private _setAnnotationsOpacity(annotations: Annotation[], restoreToOriginal = false) {
annotations.forEach(annotation => {
annotation['Opacity'] = restoreToOriginal ? parseFloat(annotation.getCustomData('opacity')) : 1;
});

View File

@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { SuperTypeSorter } from '../../../utils';
import { Filter, handleCheckedValue, IFilter, INestedFilter, NestedFilter } from '@iqser/common-ui';
@ -7,10 +7,13 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { IViewedPage } from '@red/domain';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { FilePreviewStateService } from './file-preview-state.service';
import { FileDataService } from './file-data.service';
@Injectable()
export class AnnotationProcessingService {
constructor(private readonly _state: FilePreviewStateService, private readonly _dictionariesMapService: DictionariesMapService) {}
readonly #fileDataService = inject(FileDataService);
readonly #state = inject(FilePreviewStateService);
readonly #dictionariesMapService = inject(DictionariesMapService);
static secondaryAnnotationFilters(viewedPages?: IViewedPage[]): INestedFilter[] {
const _viewedPages = viewedPages ? viewedPages.map(page => page.page) : [];
@ -50,11 +53,11 @@ export class AnnotationProcessingService {
].map(item => new NestedFilter(item));
}
getAnnotationFilter(annotations: AnnotationWrapper[]): INestedFilter[] {
getAnnotationFilter(): INestedFilter[] {
const filterMap = new Map<string, INestedFilter>();
const filters: INestedFilter[] = [];
annotations?.forEach(a => {
this.#fileDataService.all?.forEach(a => {
const topLevelFilter = a.topLevelFilter;
const filter = filterMap.get(a.filterKey);
if (filter) {
@ -76,8 +79,8 @@ export class AnnotationProcessingService {
}
const dictionary =
a.type === 'dossier_redaction'
? this._state.dossierDictionary
: this._dictionariesMapService.getDictionary(a.type, this._state.dossierTemplateId);
? this.#state.dossierDictionary
: this.#dictionariesMapService.getDictionary(a.type, this.#state.dossierTemplateId);
const childFilter: IFilter = {
id: a.filterKey,
label: dictionary.label,
@ -212,11 +215,13 @@ export class AnnotationProcessingService {
if (first.pageNumber === second.pageNumber) {
if (first.y > second.y) {
return -1;
} else if (first.y < second.y) {
return 1;
} else {
return first.x < second.x ? -1 : 1;
}
if (first.y < second.y) {
return 1;
}
return first.x < second.x ? -1 : 1;
}
return first.pageNumber < second.pageNumber ? -1 : 1;
});

View File

@ -4,7 +4,7 @@ import { Dictionary, Dossier, DOSSIER_ID, File, FILE_ID } from '@red/domain';
import { ActivatedRoute, Router } from '@angular/router';
import { FilesMapService } from '@services/files/files-map.service';
import { PermissionsService } from '@services/permissions.service';
import { boolFactory } from '@iqser/common-ui';
import { boolFactory, LoadingService } from '@iqser/common-ui';
import { filter, map, startWith, tap, withLatestFrom } from 'rxjs/operators';
import { FileManagementService } from '@services/files/file-management.service';
import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider';
@ -12,6 +12,21 @@ import { wipeFilesCache } from '@red/cache';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { FilesService } from '@services/files/files.service';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
const ONE_MEGABYTE = 1024 * 1024;
function getRemainingTime(event: HttpProgressEvent, startTime: number) {
const currTime = new Date().getTime();
const remaining = event.total - event.loaded;
const speed = event.loaded / ((currTime - startTime) / 1000);
return Math.round(remaining / speed);
}
function isDownload(event: HttpEvent<Blob>): event is HttpProgressEvent {
return event.type === HttpEventType.DownloadProgress && event.total > ONE_MEGABYTE;
}
@Injectable()
export class FilePreviewStateService {
@ -40,6 +55,8 @@ export class FilePreviewStateService {
private readonly _dossiersService: DossiersService,
private readonly _fileManagementService: FileManagementService,
private readonly _dictionaryService: DictionaryService,
private readonly _translateService: TranslateService,
private readonly _loadingService: LoadingService,
) {
const dossiersService = dossiersServiceResolver(_injector, router);
@ -89,6 +106,16 @@ export class FilePreviewStateService {
this.#reloadBlob$.next(true);
}
#getRemainingTimeVerbose(event: HttpProgressEvent, startTime: number) {
const remainingTime = getRemainingTime(event, startTime);
if (remainingTime > 60) {
const minutes: string = this._translateService.instant('minutes');
return `${Math.round(remainingTime / 60)} ${minutes}`;
}
const seconds: string = this._translateService.instant('seconds');
return `${remainingTime} ${seconds}`;
}
#dossierFilesChange$() {
return this._dossiersService.dossierFileChanges$.pipe(
filter(dossierId => dossierId === this.dossierId),
@ -97,8 +124,36 @@ export class FilePreviewStateService {
}
#downloadOriginalFile(cacheIdentifier: string, wipeCaches = true): Observable<Blob> {
const downloadFile = this._fileManagementService.downloadOriginalFile(this.dossierId, this.fileId, 'body', cacheIdentifier);
const downloadFile$ = this.#getFileToDownload(cacheIdentifier);
const obs = wipeCaches ? from(wipeFilesCache()) : of({});
return obs.pipe(switchMap(() => downloadFile));
return obs.pipe(switchMap(() => downloadFile$));
}
#getFileToDownload(cacheIdentifier: string): Observable<Blob> {
const downloadFile$ = this._fileManagementService.downloadOriginal(this.dossierId, this.fileId, 'events', cacheIdentifier);
let start;
return downloadFile$.pipe(
tap(() => (start ? undefined : (start = new Date().getTime()))),
tap(event => this.#showLoadingIfIsDownloadEvent(event, start)),
filter(event => event.type === HttpEventType.Response),
map((event: HttpResponse<Blob>) => event.body),
);
}
#showLoadingIfIsDownloadEvent(event: HttpEvent<Blob>, start) {
if (isDownload(event)) {
this.#updateDownloadProgress(event, start);
}
}
#updateDownloadProgress(event: HttpProgressEvent, startTime: number) {
const progress = Math.round((event.loaded / event.total) * 100);
const loading: string = this._translateService.instant('loading');
this._loadingService.update({
title: loading + ' ' + this.file.filename,
type: 'progress-bar',
value: progress,
remainingTime: this.#getRemainingTimeVerbose(event, startTime),
});
}
}

View File

@ -4,7 +4,7 @@
bottom: 20px;
left: calc(50% - (var(--workload-width) / 2));
transform: translate(-50%);
background: var(--iqser-white);
background: var(--iqser-background);
color: var(--iqser-grey-7);
border: 1px solid var(--iqser-grey-7);
border-radius: 8px;
@ -32,6 +32,7 @@
margin: 0;
}
background-color: var(--iqser-background);
color: var(--iqser-grey-7);
text-decoration: none;
outline: none;
@ -55,7 +56,7 @@
padding-right: 4px;
&:hover {
color: var(--iqser-accent);
color: var(--iqser-text);
}
}
}

View File

@ -75,7 +75,7 @@ export class AnnotationDrawService {
color = this._dictionariesMapService.getDictionaryColor(dictionary, dossierTemplateId, true);
break;
case SuperTypes.Skipped:
color = this._dictionariesMapService.getDictionaryColor(superType, dossierTemplateId);
color = this._dictionariesMapService.getDictionaryColor(dictionary, dossierTemplateId, false, true);
break;
default:
color = this._dictionariesMapService.getDictionaryColor(superType, dossierTemplateId);
@ -85,9 +85,14 @@ export class AnnotationDrawService {
}
private async _draw(annotationWrappers: List<AnnotationWrapper>, dossierTemplateId: string, hideSkipped: boolean) {
const totalPages = await firstValueFrom(this._pdf.totalPages$);
const annotations = annotationWrappers
.map(annotation => this._computeAnnotation(annotation, dossierTemplateId, hideSkipped))
.filter(a => !!a);
.map(annotation => this._computeAnnotation(annotation, dossierTemplateId, hideSkipped, totalPages))
.filterTruthy();
const documentLoaded = await firstValueFrom(this._documentViewer.loaded$);
if (!documentLoaded) {
return;
}
await this._annotationManager.add(annotations);
if (this._userPreferenceService.areDevFeaturesEnabled) {
@ -130,9 +135,9 @@ export class AnnotationDrawService {
return rectangleAnnot;
}
private _computeAnnotation(annotationWrapper: AnnotationWrapper, dossierTemplateId: string, hideSkipped: boolean) {
private _computeAnnotation(annotationWrapper: AnnotationWrapper, dossierTemplateId: string, hideSkipped: boolean, totalPages: number) {
const pageNumber = this._pdf.isCompare ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
if (pageNumber > this._pdf.pageCount) {
if (pageNumber > totalPages) {
// skip imported annotations from files that have more pages than the current one
return;
}

View File

@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { Core } from '@pdftron/webviewer';
import { NGXLogger } from 'ngx-logger';
import { fromEvent, merge, Observable } from 'rxjs';
@ -22,12 +22,10 @@ export class REDDocumentViewer {
selectedText = '';
#document: DocumentViewer;
constructor(
private readonly _logger: NGXLogger,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _pdf: PdfViewer,
private readonly _activatedRoute: ActivatedRoute,
) {}
readonly #logger = inject(NGXLogger);
readonly #userPreferenceService = inject(UserPreferenceService);
readonly #pdf = inject(PdfViewer);
readonly #activatedRoute = inject(ActivatedRoute);
get PDFDoc() {
return this.document?.getPDFDoc();
@ -41,7 +39,7 @@ export class REDDocumentViewer {
const event$ = fromEvent(this.#document, 'documentUnloaded');
const toBool$ = event$.pipe(map(() => false));
return toBool$.pipe(tap(() => this._logger.info('[PDF] Document unloaded')));
return toBool$.pipe(tap(() => this.#logger.info('[PDF] Document unloaded')));
}
get #documentLoaded$() {
@ -52,7 +50,7 @@ export class REDDocumentViewer {
tap(() => this.#setCurrentPage()),
tap(() => this.#setInitialDisplayMode()),
tap(() => this.updateTooltipsVisibility()),
tap(() => this._logger.info('[PDF] Document loaded')),
tap(() => this.#logger.info('[PDF] Document loaded')),
);
}
@ -73,13 +71,7 @@ export class REDDocumentViewer {
get #textSelected$(): Observable<string> {
return fromEvent<[Quad, string, number]>(this.#document, 'textSelected').pipe(
tap(([, selectedText]) => (this.selectedText = selectedText)),
tap(([, , pageNumber]) => {
if (this._pdf.isCompare && pageNumber % 2 === 0) {
this._pdf.disable('textPopup');
} else {
this._pdf.enable('textPopup');
}
}),
tap(([, , pageNumber]) => this.#disableTextPopupIfCompareMode(pageNumber)),
map(([, selectedText]) => selectedText),
);
}
@ -89,14 +81,14 @@ export class REDDocumentViewer {
}
close() {
this._logger.info('[PDF] Closing document');
this.#logger.info('[PDF] Closing document');
this.#document.closeDocument();
this._pdf.closeCompareMode();
this.#pdf.closeCompareMode();
}
updateTooltipsVisibility(): void {
const current = this._userPreferenceService.getFilePreviewTooltipsPreference();
this._pdf.instance.UI.setAnnotationContentOverlayHandler(() => (current ? undefined : false));
const current = this.#userPreferenceService.getFilePreviewTooltipsPreference();
this.#pdf.instance.UI.setAnnotationContentOverlayHandler(() => (current ? undefined : false));
}
init(document: DocumentViewer) {
@ -114,7 +106,7 @@ export class REDDocumentViewer {
}
await document.lock();
this._logger.info('[PDF] Locked');
this.#logger.info('[PDF] Locked');
return true;
}
@ -156,7 +148,7 @@ export class REDDocumentViewer {
pages.forEach(page => this.#document.setRotation(0, Number(page)));
}
rotate(rotation: RotationType, page = this._pdf.currentPage) {
rotate(rotation: RotationType, page = this.#pdf.currentPage) {
if (rotation === RotationTypes.LEFT) {
this.#document.rotateCounterClockwise(page);
} else {
@ -164,16 +156,24 @@ export class REDDocumentViewer {
}
}
#disableTextPopupIfCompareMode(pageNumber) {
if (this.#pdf.isCompare && pageNumber % 2 === 0) {
return this.#pdf.disable('textPopup');
}
this.#pdf.enable('textPopup');
}
#setCurrentPage() {
const currentDocPage = this._activatedRoute.snapshot.queryParamMap.get('page');
this.#document.setCurrentPage(Number(currentDocPage ?? '1'), false);
const currentDocPage = this.#activatedRoute.snapshot.queryParamMap.get('page');
this.#pdf.navigateTo(currentDocPage ?? 1);
}
#setInitialDisplayMode() {
this._pdf.instance.UI.setFitMode('FitPage');
this.#pdf.instance.UI.setFitMode('FitPage');
const displayModeManager = this.#document.getDisplayModeManager();
const instanceDisplayMode = displayModeManager.getDisplayMode();
instanceDisplayMode.mode = this._pdf.isCompare ? 'Facing' : 'Single';
instanceDisplayMode.mode = this.#pdf.isCompare ? 'Facing' : 'Single';
displayModeManager.setDisplayMode(instanceDisplayMode);
}
}

View File

@ -13,6 +13,7 @@ import { Rgb } from '../utils/types';
import { asList } from '../utils/functions';
import { TranslateService } from '@ngx-translate/core';
import { LicenseService } from '@services/license.service';
import { UserPreferenceService } from '@services/user-preference.service';
import TextTool = Core.Tools.TextTool;
import Annotation = Core.Annotations.Annotation;
import TextHighlightAnnotation = Core.Annotations.TextHighlightAnnotation;
@ -53,6 +54,7 @@ export class PdfViewer {
private readonly _activatedRoute: ActivatedRoute,
private readonly _licenseService: LicenseService,
private readonly _translateService: TranslateService,
private readonly _userPreferenceService: UserPreferenceService,
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
) {}
@ -133,6 +135,7 @@ export class PdfViewer {
this.#instance = await this.#getInstance(htmlElement);
await this.PDFNet.initialize(this._licenseService.activeLicenseKey);
this.#instance.UI.setTheme(this._userPreferenceService.getTheme());
this._logger.info('[PDF] Initialized');
this.documentViewer = this.#instance.Core.documentViewer;
@ -166,11 +169,11 @@ export class PdfViewer {
this.#compareMode$.next(false);
}
async loadDocument(blob: Blob, file: File) {
async loadDocument(blob: Blob, file: File, actionOnError: () => void = () => {}) {
const onError = () => {
this._injector.get(ErrorService).set(DOCUMENT_LOADING_ERROR);
this._logger.error('[PDF] Error while loading document');
// this.stateService.reloadBlob();
actionOnError();
};
const document = await this.PDFNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());

View File

@ -1,8 +1,8 @@
@use 'libs/common-ui/src/assets/styles/common-mixins';
@use 'common-mixins';
.file-actions {
display: flex;
color: var(--iqser-grey-1);
color: var(--iqser-text);
> *:not(:last-child) {
margin-right: 2px;

View File

@ -10,7 +10,7 @@ import {
ViewChild,
} from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { Action, ActionTypes, Dossier, File } from '@red/domain';
import { Action, ActionTypes, Dossier, File, User } from '@red/domain';
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import {
CircleButtonType,
@ -46,7 +46,7 @@ import { ROTATION_ACTION_BUTTONS } from '../../../pdf-viewer/utils/constants';
})
export class FileActionsComponent implements OnChanges {
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser;
readonly currentUser: User;
@Input() file: File;
@Input() dossier: Dossier;

View File

@ -24,19 +24,18 @@
></redaction-team-members>
</ng-container>
<ng-container *ngIf="(selectedReviewers$ | async)?.length">
<div class="all-caps-label mt-16" id="reviewersLabel" translate="assign-dossier-owner.dialog.reviewers"></div>
<redaction-team-members
(remove)="toggleSelected($event)"
[canAdd]="false"
[canRemove]="hasOwner && !disabled"
[dossierId]="dossier.id"
[largeSpacing]="true"
[memberIds]="selectedReviewers$ | async"
[perLine]="13"
[unremovableMembers]="[selectedOwnerId]"
></redaction-team-members>
</ng-container>
<div class="all-caps-label mt-16" id="reviewersLabel" translate="assign-dossier-owner.dialog.reviewers"></div>
<redaction-team-members
(remove)="toggleSelected($event)"
[canAdd]="false"
[canRemove]="hasOwner && !disabled"
[dossierId]="dossier.id"
[largeSpacing]="true"
[memberIds]="selectedReviewers$ | async"
[perLine]="13"
[unremovableMembers]="[selectedOwnerId]"
></redaction-team-members>
<ng-container *ngIf="!(selectedReviewers$ | async)?.length">
<div class="info mt-4">{{ 'assign-dossier-owner.dialog.no-reviewers' | translate }}</div>

View File

@ -54,7 +54,7 @@ redaction-team-members {
&.selected,
&:hover {
background-color: var(--iqser-grey-2);
background-color: var(--iqser-alt-background);
.actions {
display: flex;

View File

@ -62,7 +62,7 @@
}
&:hover:not(.active):not(.filter-disabled) {
background-color: var(--iqser-grey-6);
background-color: var(--iqser-btn-bg);
}
&.active {

View File

@ -10,5 +10,5 @@ redaction-small-chip {
.dossier-state-text {
font-size: 13px;
line-height: 16px;
color: var(--iqser-grey-1);
color: var(--iqser-text);
}

View File

@ -6,7 +6,7 @@ ngx-monaco-editor {
}
%arrow {
border: solid var(--iqser-grey-1);
border: solid var(--iqser-text);
border-width: 2px 0 0 2px;
height: 4px !important;
width: 4px !important;

View File

@ -1,5 +1,7 @@
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { Debounce, List, OnChange } from '@iqser/common-ui';
import { UserPreferenceService } from '@services/user-preference.service';
import { EditorThemeService } from '@services/editor-theme.service';
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
import ICodeEditor = monaco.editor.ICodeEditor;
import IDiffEditor = monaco.editor.IDiffEditor;
@ -41,6 +43,8 @@ export class EditorComponent implements OnInit, OnChanges {
private _diffEditor: IDiffEditor;
private _decorations: string[] = [];
constructor(private readonly _userPreferenceService: UserPreferenceService, private readonly _editorThemeService: EditorThemeService) {}
get currentEntries(): string[] {
return this.value.split('\n');
}
@ -102,31 +106,14 @@ export class EditorComponent implements OnInit, OnChanges {
}
private _defineThemes(): void {
(window as any).monaco.editor.defineTheme('redaction', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.lineHighlightBackground': '#f4f5f7',
},
});
(window as any).monaco.editor.defineTheme('redaction-disabled', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.background': '#f4f5f7',
'editor.foreground': '#9398a0',
'editor.lineHighlightBackground': '#f4f5f7',
'editorLineNumber.foreground': '#9398a0',
'editorActiveLineNumber.foreground': '#9398a0',
},
});
for (const theme of this._editorThemeService.themes) {
(window as any).monaco.editor.defineTheme(theme, this._editorThemeService.configurations[theme]);
}
}
private _setTheme(): void {
this._defineThemes();
(window as any).monaco.editor.setTheme(this.canEdit ? 'redaction' : 'redaction-disabled');
(window as any).monaco.editor.setTheme(this._editorThemeService.getTheme(this.canEdit));
}
private _handleMarginButtonClick(event: IEditorMouseEvent) {

View File

@ -18,6 +18,6 @@ mat-slide-toggle {
}
&[disabled] {
color: rgba(var(--iqser-accent-rgb), 0.3);
color: rgba(var(--iqser-text-rgb), 0.3);
}
}

View File

@ -12,7 +12,6 @@
}
}
// https://stackoverflow.com/questions/34641281/how-to-add-class-to-host-element
:host(.fixed-height) {
height: var(--height);
overflow: hidden;
@ -65,23 +64,23 @@ mat-chip {
}
.mat-chip.mat-standard-chip.mat-chip-selected.mat-primary {
background-color: var(--iqser-grey-6);
color: var(--iqser-accent);
background-color: var(--iqser-btn-bg);
color: var(--iqser-text);
}
.mat-chip.mat-standard-chip {
background-color: var(--iqser-white);
color: var(--iqser-accent);
background-color: var(--iqser-background);
color: var(--iqser-text);
margin: 0 0 2px 0;
transition: background-color 0.2s;
&:hover {
background-color: var(--iqser-grey-8);
background-color: var(--iqser-not-disabled-table-item);
}
}
.mat-chip.mat-standard-chip::after {
background: var(--iqser-grey-8);
background: transparent;
}
.mat-standard-chip:focus::after {

View File

@ -215,8 +215,8 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
private _createSubscription(uploadFile: FileUploadModel) {
this.activeUploads++;
const obs = this.uploadFileForm(uploadFile.dossierId, uploadFile.keepManualRedactions, uploadFile.file);
return obs.subscribe(
event => {
return obs.subscribe({
next: event => {
if (event.type === HttpEventType.UploadProgress) {
uploadFile.progress = Math.round((event.loaded / (event.total || event.loaded)) * 100);
this._applicationRef.tick();
@ -234,7 +234,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
this._removeUpload(uploadFile);
}
},
(err: HttpErrorResponse) => {
error: (err: HttpErrorResponse) => {
uploadFile.completed = true;
uploadFile.error = {
// Extract error message
@ -246,7 +246,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
this.scheduleUpload(uploadFile);
}
},
);
});
}
private _removeUpload(fileUploadModel: FileUploadModel) {

View File

@ -1,15 +1,14 @@
@use 'common-mixins';
@use 'variables';
.red-upload-download-overlay {
background: var(--iqser-white);
background: var(--iqser-background);
position: fixed;
bottom: 20px;
right: 20px;
box-shadow: 0 3px 12px 5px rgba(40, 50, 65, 0.14);
border-radius: 8px;
overflow: hidden;
width: 400px;
@include common-mixins.drop-shadow;
.red-upload-download-header {
display: flex;
@ -19,14 +18,14 @@
padding: 16px 14px 16px 16px;
cursor: pointer;
color: var(--iqser-accent);
color: var(--iqser-text);
font-size: 13px;
font-weight: 600;
line-height: 16px;
mat-icon {
height: 13px;
color: var(--iqser-accent);
color: var(--iqser-text);
&.collapse-icon {
height: 15px;
@ -52,7 +51,7 @@
.dossier-name-wrapper {
display: flex;
padding: 4px 16px;
background-color: var(--iqser-grey-2);
background-color: var(--iqser-alt-background);
> span {
@include common-mixins.line-clamp(1);
@ -90,7 +89,7 @@
}
&.error {
background-color: rgba(variables.$primary, 0.1);
background-color: rgba(var(--iqser-primary-rgb), 0.1);
padding-right: 100px;
.error-message {
@ -113,13 +112,13 @@
}
&:not(.error) {
background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, variables.$grey-2 35%);
background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, var(--iqser-alt-background) 35%);
padding-left: 60px;
}
}
&:not(.error):hover {
background-color: var(--iqser-grey-2);
background-color: var(--iqser-alt-background);
.actions {
display: flex;

View File

@ -0,0 +1,62 @@
import { Injectable } from '@angular/core';
import { UserPreferenceService } from './user-preference.service';
import { editor } from 'monaco-editor';
import IStandaloneThemeData = editor.IStandaloneThemeData;
@Injectable({
providedIn: 'root',
})
export class EditorThemeService {
readonly themes = ['redaction', 'redaction-disabled', 'redaction-dark', 'redaction-disabled-dark'];
readonly configurations: Record<string, IStandaloneThemeData> = {
redaction: {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.lineHighlightBackground': '#f4f5f7',
},
},
'redaction-disabled': {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.background': '#f4f5f7',
'editor.foreground': '#9398a0',
'editor.lineHighlightBackground': '#f4f5f7',
'editorLineNumber.foreground': '#9398a0',
'editorActiveLineNumber.foreground': '#9398a0',
},
},
'redaction-dark': {
base: 'vs-dark',
inherit: true,
rules: [],
colors: {
'editor.background': '#151a21',
'editor.lineHighlightBackground': '#283241',
},
},
'redaction-disabled-dark': {
base: 'vs-dark',
inherit: true,
rules: [],
colors: {
'editor.background': '#151a21',
'editor.foreground': '#9398a0',
'editor.lineHighlightBackground': '#283241',
'editorLineNumber.foreground': '#9398a0',
'editorActiveLineNumber.foreground': '#9398a0',
},
},
};
constructor(private readonly _userPreferenceService: UserPreferenceService) {}
getTheme(canEdit: boolean): string {
const isDarkTheme = this._userPreferenceService.getTheme() === 'dark';
const editorTheme = canEdit ? 'redaction' : 'redaction-disabled';
return `${editorTheme}${isDarkTheme ? '-dark' : ''}`;
}
}

View File

@ -13,14 +13,14 @@ export class DictionariesMapService extends EntitiesMapService<Dictionary, IDict
return this.get(dossierTemplateId, type) || this.get(dossierTemplateId, 'default');
}
getDictionaryColor(type: string, dossierTemplateId: string, isRecommendation = false) {
getDictionaryColor(type: string, dossierTemplateId: string, isRecommendation = false, isSkipped = false) {
const defaultColor = '#CCCCCC';
if (!this.get(dossierTemplateId)) {
return defaultColor;
}
const dictionary = this.getDictionary(type, dossierTemplateId);
const colorKey = isRecommendation ? 'recommendationHexColor' : 'hexColor';
const colorKey = isRecommendation ? 'recommendationHexColor' : isSkipped ? 'skippedHexColor' : 'hexColor';
if (dictionary && dictionary[colorKey]) {
return dictionary[colorKey];
}

View File

@ -1,21 +1,16 @@
import { GenericService, HeadersConfiguration, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { Injectable, Injector } from '@angular/core';
import { HttpHeaders, HttpResponse } from '@angular/common/http';
import { HttpEvent, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { FilesService } from './files.service';
import { DossierStatsService } from '../dossiers/dossier-stats.service';
import { File, IPageRotationRequest } from '@red/domain';
@Injectable({
providedIn: 'root',
})
export class FileManagementService extends GenericService<unknown> {
constructor(
protected readonly _injector: Injector,
private readonly _filesService: FilesService,
private readonly _dossierStatsService: DossierStatsService,
) {
constructor(protected readonly _injector: Injector, private readonly _filesService: FilesService) {
super(_injector, '');
}
@ -30,13 +25,13 @@ export class FileManagementService extends GenericService<unknown> {
return this._post(body, `rotate/${dossierId}/${fileId}`);
}
downloadOriginalFile(dossierId: string, fileId: string, observe?: 'body', indicator?: string): Observable<Blob>;
downloadOriginalFile(dossierId: string, fileId: string, observe?: 'response', indicator?: string): Observable<HttpResponse<Blob>>;
downloadOriginal(dossierId: string, fileId: string, observe?: 'events', indicator?: string): Observable<HttpEvent<Blob>>;
downloadOriginal(dossierId: string, fileId: string, observe?: 'response', indicator?: string): Observable<HttpResponse<Blob>>;
@Validate()
downloadOriginalFile(
downloadOriginal(
@RequiredParam() dossierId: string,
@RequiredParam() fileId: string,
observe: 'body' | 'response' = 'body',
observe: 'events' | 'response' = 'events',
indicator?: string,
) {
const queryParams: QueryParam[] = [{ key: 'inline', value: true }];
@ -56,6 +51,7 @@ export class FileManagementService extends GenericService<unknown> {
params: this._queryParams(queryParams),
headers: headers,
observe: observe,
reportProgress: observe === 'events',
});
}
}

View File

@ -9,6 +9,7 @@ const KEYS = {
dossierRecent: 'Dossier-Recent',
filePreviewTooltips: 'File-Preview-Tooltips',
lastDossierTemplate: 'Last-Dossier-Template',
theme: 'Theme',
} as const;
@Injectable({
@ -46,6 +47,15 @@ export class UserPreferenceService extends GenericService<UserAttributes> {
await this._save(KEYS.lastDossierTemplate, dossierTemplateId);
}
getTheme(): string {
return this._getAttribute(KEYS.theme, 'light');
}
async saveTheme(theme: 'light' | 'dark'): Promise<void> {
await this._save(KEYS.theme, theme);
window.location.reload();
}
getLanguage(): string {
return this._getAttribute(KEYS.language);
}
@ -64,7 +74,7 @@ export class UserPreferenceService extends GenericService<UserAttributes> {
}
toggleDevFeatures(): void {
sessionStorage.setItem('redaction.enable-dev-features', `${!this.areDevFeaturesEnabled}`);
sessionStorage.setItem('redaction.enable-dev-features', String(!this.areDevFeaturesEnabled));
window.location.reload();
}

View File

@ -100,6 +100,8 @@
"recommendation-color": "",
"recommendation-color-placeholder": "",
"redaction": "",
"skipped-color": "",
"skipped-color-placeholder": "",
"technical-name": "",
"technical-name-hint": ""
},
@ -1595,6 +1597,7 @@
"usage-details": "Nutzungsdetails"
},
"license-information": "Lizenzinformationen",
"loading": "",
"manual-annotation": {
"dialog": {
"actions": {
@ -1628,6 +1631,7 @@
}
}
},
"minutes": "",
"notification": {
"assign-approver": "Sie wurden dem Dokument <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}<b> als Genehmiger zugewiesen!",
"assign-reviewer": "Sie wurden dem Dokument <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> im Dossier <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}<b> als Reviewer zugewiesen!",
@ -1922,6 +1926,7 @@
"placeholder": "Nach Dokumenten oder Dokumenteninhalt suchen",
"this-dossier": "in diesem Dossier"
},
"seconds": "",
"size": "",
"smtp-auth-config": {
"actions": {
@ -2050,6 +2055,7 @@
"save": "Änderungen speichern"
},
"form": {
"dark-theme": "",
"email": "Email",
"first-name": "Vorname",
"last-name": "Nachname"

View File

@ -100,6 +100,8 @@
"recommendation-color": "Recommendation Hex Color",
"recommendation-color-placeholder": "#",
"redaction": "Redaction",
"skipped-color": "Skipped Hex Color",
"skipped-color-placeholder": "#",
"technical-name": "Technical Name",
"technical-name-hint": "{type, select, edit{Autogenerated based on the initial display name.} create{Autogenerates based on the display name and cannot be edited after saving.} other{}}"
},
@ -1595,6 +1597,7 @@
"usage-details": "Usage Details"
},
"license-information": "License Information",
"loading": "Loading",
"manual-annotation": {
"dialog": {
"actions": {
@ -1628,6 +1631,7 @@
}
}
},
"minutes": "minutes",
"notification": {
"assign-approver": "You have been assigned as approver for <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> in dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
"assign-reviewer": "You have been assigned as reviewer for <b>{fileHref, select, null{{fileName}} other{<a href=\"{fileHref}\" target=\"_blank\">{fileName}</a>}}</b> in dossier: <b>{dossierHref, select, null{{dossierName}} other{<a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a>}}</b>!",
@ -1922,6 +1926,7 @@
"placeholder": "Search documents...",
"this-dossier": "in this dossier"
},
"seconds": "seconds",
"size": "Size",
"smtp-auth-config": {
"actions": {
@ -2050,6 +2055,7 @@
"save": "Save Changes"
},
"form": {
"dark-theme": "Dark Theme",
"email": "Email",
"first-name": "First name",
"last-name": "Last name"

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Styleguide" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Styleguide-Actions" transform="translate(-979.000000, -588.000000)" fill="#283241" fill-rule="nonzero">
<svg height="14px" version="1.1" viewBox="0 0 14 14" width="14px" xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fill-rule="evenodd" id="Styleguide" stroke="none" stroke-width="1">
<g fill="currentColor" fill-rule="nonzero" id="Styleguide-Actions" transform="translate(-979.000000, -588.000000)">
<g id="reference" transform="translate(969.000000, 578.000000)">
<g id="status" transform="translate(10.000000, 10.000000)">
<polygon id="Path" points="14 6.3 8.68 6.3 12.46 2.52 11.48 1.54 7.7 5.32 7.7 0 6.3 0 6.3 5.32 2.52 1.54 1.54 2.52 5.32 6.3 0 6.3 0 7.7 5.32 7.7 1.54 11.48 2.52 12.46 6.3 8.68 6.3 14 7.7 14 7.7 8.68 11.48 12.46 12.46 11.48 8.68 7.7 14 7.7"></polygon>
<polygon id="Path"
points="14 6.3 8.68 6.3 12.46 2.52 11.48 1.54 7.7 5.32 7.7 0 6.3 0 6.3 5.32 2.52 1.54 1.54 2.52 5.32 6.3 0 6.3 0 7.7 5.32 7.7 1.54 11.48 2.52 12.46 6.3 8.68 6.3 14 7.7 14 7.7 8.68 11.48 12.46 12.46 11.48 8.68 7.7 14 7.7"></polygon>
</g>
</g>
</g>

Before

Width:  |  Height:  |  Size: 881 B

After

Width:  |  Height:  |  Size: 873 B

View File

@ -37,3 +37,8 @@
.excluded-page {
border: 2px solid #dd4d5080;
}
.AnnotationPopup {
--component-background: white;
--popup-button-hover: #e7edf3;
}

View File

@ -9,9 +9,7 @@ $grey-5: #d3d5da;
$grey-6: #f0f1f4;
$grey-7: #9398a0;
$grey-8: #f9fafb;
$grey-9: #f5f5f7;
$grey-10: #313d4e;
$grey-11: #ecedf0;
$blue-1: #4875f7;
$blue-2: #48c9f7;
@ -33,9 +31,3 @@ $accent: $grey-1;
$warn: $yellow-2;
$light: $white;
$dark: $black;
$btn-bg-hover: $grey-4;
$btn-bg: $grey-6;
$quick-filter-border: $grey-5;
$separator: rgba(226, 228, 233, 0.9);

View File

@ -88,7 +88,7 @@
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--iqser-grey-6);
background-color: var(--iqser-btn-bg);
> div {
display: flex;

View File

@ -1,4 +1,5 @@
@use 'variables';
@use 'common-mixins';
.monaco-diff-editor {
.editor.original {
@ -60,9 +61,9 @@
right: 40px;
border-radius: 8px;
padding: 16px 32px 16px 16px;
background-color: var(--iqser-white);
box-shadow: 0 2px 6px 0 rgba(40, 50, 65, 0.3);
background-color: var(--iqser-background);
z-index: 5000;
@include common-mixins.drop-shadow;
> *:not(:last-child) {
margin-right: 24px;

View File

@ -4,22 +4,23 @@
@use 'common-variables';
@use 'assets/styles/red-theme';
@include common-variables.configure(
@include common-variables.configureLight(
$iqser-primary: vars.$primary,
$iqser-primary-rgb: common-functions.hexToRgb(vars.$primary),
$iqser-primary-2: vars.$primary-2,
$iqser-accent: vars.$accent,
$iqser-accent-rgb: common-functions.hexToRgb(vars.$accent),
$iqser-background: vars.$white,
$iqser-alt-background: vars.$grey-2,
$iqser-text: vars.$accent,
$iqser-text-rgb: common-functions.hexToRgb(vars.$accent),
$iqser-disabled: vars.$grey-7,
$iqser-not-disabled-table-item: vars.$grey-8,
$iqser-btn-bg-hover: vars.$btn-bg-hover,
$iqser-btn-bg: vars.$btn-bg,
$iqser-btn-bg-hover: vars.$grey-4,
$iqser-btn-bg: vars.$grey-6,
$iqser-side-nav: vars.$grey-2,
$iqser-warn: vars.$warn,
$iqser-white: vars.$white,
$iqser-black: vars.$black,
$iqser-light: vars.$light,
$iqser-separator: vars.$separator,
$iqser-quick-filter-border: vars.$quick-filter-border,
$iqser-separator: rgba(vars.$grey-4, 0.9),
$iqser-grey-2: vars.$grey-2,
$iqser-grey-3: vars.$grey-3,
$iqser-grey-4: vars.$grey-4,
@ -30,5 +31,66 @@
$iqser-green-2: vars.$green-2,
$iqser-yellow-1: vars.$yellow-1,
$iqser-yellow-2: vars.$yellow-2,
$iqser-helpmode-primary: vars.$green-2
$iqser-helpmode-primary: vars.$green-2,
$iqser-inputs-outline: vars.$grey-5,
$iqser-popup-background: vars.$white,
$iqser-shadow: vars.$grey-4,
$iqser-toggle-bg: vars.$grey-4,
$iqser-file-drop-drag-over: #e2eefd,
$iqser-user-avatar-1: vars.$grey-6,
$iqser-user-avatar-2: vars.$grey-4,
$iqser-annotation-hover: vars.$grey-8,
$iqser-annotation-comments-hover: vars.$grey-4,
$iqser-workload-pages-bg: vars.$grey-8,
$iqser-tab-hover: vars.$grey-6,
$iqser-loading-progress: vars.$grey-7
);
$light-accent-5: lighten(vars.$accent, 5%);
$light-accent-10: lighten(vars.$accent, 10%);
$dark-accent-5: darken(vars.$accent, 5%);
$dark-accent-8: darken(vars.$accent, 8%);
$dark-accent-10: darken(vars.$accent, 10%);
@include common-variables.configureDark(
$iqser-primary: vars.$primary,
$iqser-primary-rgb: common-functions.hexToRgb(vars.$primary),
$iqser-primary-2: vars.$primary-2,
$iqser-accent: vars.$accent,
$iqser-accent-rgb: common-functions.hexToRgb(vars.$accent),
$iqser-background: $dark-accent-10,
$iqser-alt-background: $dark-accent-5,
$iqser-text: vars.$white,
$iqser-text-rgb: common-functions.hexToRgb(vars.$white),
$iqser-disabled: vars.$grey-7,
$iqser-not-disabled-table-item: $dark-accent-5,
$iqser-btn-bg-hover: vars.$accent,
$iqser-btn-bg: $dark-accent-5,
$iqser-side-nav: $dark-accent-8,
$iqser-warn: vars.$warn,
$iqser-separator: vars.$accent,
$iqser-grey-2: vars.$grey-2,
$iqser-grey-3: vars.$grey-3,
$iqser-grey-4: vars.$grey-4,
$iqser-grey-5: vars.$grey-5,
$iqser-grey-6: vars.$grey-6,
$iqser-grey-7: vars.$grey-7,
$iqser-green-1: vars.$green-1,
$iqser-green-2: vars.$green-2,
$iqser-yellow-1: vars.$yellow-1,
$iqser-yellow-2: vars.$yellow-2,
$iqser-helpmode-primary: vars.$green-2,
$iqser-inputs-outline: $light-accent-10,
$iqser-popup-background: $dark-accent-5,
$iqser-shadow: rgba(0, 0, 0, 0.4),
$iqser-toggle-bg: $light-accent-5,
$iqser-file-drop-drag-over: $light-accent-10,
$iqser-user-avatar-1: $light-accent-5,
$iqser-user-avatar-2: $light-accent-10,
$iqser-annotation-hover: $dark-accent-5,
$iqser-annotation-comments-hover: $light-accent-5,
$iqser-workload-pages-bg: $dark-accent-8,
$iqser-tab-hover: vars.$accent,
$iqser-loading-progress: $light-accent-10
);

@ -1 +1 @@
Subproject commit d892f174f534fa794f30204ff9e855fc36dcfa8e
Subproject commit f12e9a20943a8c6cffd1b5c1c2e1ed38e0ab022a

View File

@ -9,6 +9,7 @@ export class Dictionary extends Entity<IDictionary> implements IDictionary {
readonly dossierTemplateId?: string;
readonly hexColor?: string;
readonly recommendationHexColor?: string;
readonly skippedHexColor?: string;
readonly hint: boolean;
readonly label: string;
readonly rank?: number;
@ -33,6 +34,7 @@ export class Dictionary extends Entity<IDictionary> implements IDictionary {
this.falseRecommendationEntries = entity.falseRecommendationEntries ?? [];
this.hexColor = entity.hexColor;
this.recommendationHexColor = entity.recommendationHexColor;
this.skippedHexColor = entity.skippedHexColor;
this.hint = !!entity.hint;
this.label = entity.label ?? entity.type;
this.rank = entity.rank;

View File

@ -54,6 +54,8 @@ export interface IDictionary {
readonly recommendationHexColor?: string;
readonly skippedHexColor?: string;
readonly hasDictionary?: boolean;
readonly systemManaged?: boolean;

View File

@ -31,6 +31,7 @@ export class File extends Entity<IFile> implements IFile {
readonly fileAttributes: FileAttributes;
readonly fileId: string;
readonly filename: string;
readonly fileSize: number;
readonly hasAnnotationComments: boolean;
readonly hasHints: boolean;
readonly hasImages: boolean;
@ -94,6 +95,7 @@ export class File extends Entity<IFile> implements IFile {
this.excludedFromAutomaticAnalysis = !!file.excludedFromAutomaticAnalysis;
this.fileId = file.fileId;
this.filename = file.filename;
this.fileSize = file.fileSize;
this.hasAnnotationComments = !!file.hasAnnotationComments;
this.hasHints = !!file.hasHints;
this.hasImages = !!file.hasImages;

View File

@ -13,17 +13,8 @@ export interface IFile {
* Shows if all manual changes have been applied by a reanalysis.
*/
readonly allManualRedactionsApplied?: boolean;
/**
* Shows how long the last analysis took
*/
readonly analysisDuration?: number;
/**
* Shows if the file requires reanalysis.
*/
readonly analysisRequired?: boolean;
/**
* Shows the date of approval, if approved.
*/
readonly approvalDate?: string;
/**
* The current reviewer's (if any) user id.
@ -37,34 +28,17 @@ export interface IFile {
* Shows which dossier dictionary versions was used during the analysis.
*/
readonly dossierDictionaryVersion?: number;
/**
* The ID of the dossier the file belongs to.
*/
readonly dossierId: string;
/**
* Shows if the file was excluded from analysis.
*/
readonly excluded?: boolean;
/**
* Shows if the file was excluded from automatic analysis.
*/
readonly excludedFromAutomaticAnalysis?: boolean;
/**
* Set of excluded pages for this file.
*/
readonly excludedPages?: Array<number>;
readonly excludedPages?: number[];
fileAttributes?: FileAttributes;
/**
* The ID of the file.
*/
readonly fileId: string;
/**
* The file's name.
*/
readonly filename: string;
/**
* Shows if this file has comments on annotations.
*/
readonly fileSize: number;
readonly hasAnnotationComments?: boolean;
/**
* Shows if any hints were found during the analysis.
@ -82,9 +56,6 @@ export interface IFile {
* Shows if any requests were found during the analysis.
*/
readonly hasRequests?: boolean;
/**
* Shows if there are any Suggestions in this file.
*/
readonly hasSuggestions?: boolean;
/**
* Shows if there is any change between the previous and current analysis.

View File

@ -4,4 +4,5 @@ export interface IProfile {
firstName: string;
lastName: string;
language: string;
darkTheme: boolean;
}

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "3.570.0",
"version": "3.581.0",
"private": true,
"license": "MIT",
"scripts": {

View File

@ -2,7 +2,7 @@
@use 'mixin';
.portal-search-result {
background-color: variables.$grey-9;
background-color: variables.$grey-2;
}
.search-container {

View File

@ -11,15 +11,21 @@
body {
color: variables.$accent;
background-color: variables.$grey-9;
background-color: variables.$grey-2;
font-family: 'Open Sans', sans-serif;
h1, .h1,
h2, .h2,
h3, .h3,
h4, .h4,
h5, .h5,
h6, .h6,
h1,
.h1,
h2,
.h2,
h3,
.h3,
h4,
.h4,
h5,
.h5,
h6,
.h6,
p,
pre {
margin: 0;
@ -28,23 +34,28 @@ body {
@include common-mixins.scroll-bar;
h1, .h1 {
h1,
.h1 {
@include mixin.heading-1;
}
h2, .h2 {
h2,
.h2 {
@include mixin.heading-2;
}
h3, .h3 {
h3,
.h3 {
@include mixin.heading-3;
}
h4, .h4 {
h4,
.h4 {
@include mixin.heading-4;
}
h5, .h5 {
h5,
.h5 {
@include mixin.heading-5;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff