Merge branch 'master' into VM/RED-4247
This commit is contained in:
commit
4c14a86fbe
@ -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();
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
a {
|
||||
color: black;
|
||||
color: var(--iqser-text);
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: var(--iqser-grey-1);
|
||||
color: var(--iqser-text);
|
||||
}
|
||||
|
||||
.right-container {
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
|
||||
&:hover {
|
||||
> div {
|
||||
background-color: var(--iqser-grey-2);
|
||||
background-color: var(--iqser-alt-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
&:not(.empty) {
|
||||
&:hover {
|
||||
background-color: var(--iqser-grey-2);
|
||||
background-color: var(--iqser-side-nav);
|
||||
|
||||
.heading {
|
||||
text-decoration: underline;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
:host {
|
||||
align-items: center;
|
||||
background-color: var(--iqser-grey-2);
|
||||
background-color: var(--iqser-alt-background);
|
||||
|
||||
.container {
|
||||
padding: 32px;
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--iqser-grey-6);
|
||||
background-color: var(--iqser-btn-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
padding: 3px 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--iqser-grey-6);
|
||||
background-color: var(--iqser-tab-hover);
|
||||
}
|
||||
|
||||
&.active {
|
||||
|
||||
@ -31,5 +31,5 @@
|
||||
}
|
||||
|
||||
.right-chart {
|
||||
border-left: 1px solid rgba(226, 228, 233, 0.9);
|
||||
border-left: 1px solid var(--iqser-separator);
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
height: 24px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--iqser-grey-6);
|
||||
background-color: var(--iqser-tab-hover);
|
||||
border-radius: 12px;
|
||||
|
||||
mat-icon {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.vertical-line {
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
background-color: var(--iqser-grey-4);
|
||||
background-color: var(--iqser-separator);
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -54,7 +54,7 @@ redaction-team-members {
|
||||
|
||||
&.selected,
|
||||
&:hover {
|
||||
background-color: var(--iqser-grey-2);
|
||||
background-color: var(--iqser-alt-background);
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
}
|
||||
|
||||
&:hover:not(.active):not(.filter-disabled) {
|
||||
background-color: var(--iqser-grey-6);
|
||||
background-color: var(--iqser-btn-bg);
|
||||
}
|
||||
|
||||
&.active {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -18,6 +18,6 @@ mat-slide-toggle {
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
color: rgba(var(--iqser-accent-rgb), 0.3);
|
||||
color: rgba(var(--iqser-text-rgb), 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
62
apps/red-ui/src/app/services/editor-theme.service.ts
Normal file
62
apps/red-ui/src/app/services/editor-theme.service.ts
Normal 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' : ''}`;
|
||||
}
|
||||
}
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 |
@ -37,3 +37,8 @@
|
||||
.excluded-page {
|
||||
border: 2px solid #dd4d5080;
|
||||
}
|
||||
|
||||
.AnnotationPopup {
|
||||
--component-background: white;
|
||||
--popup-button-hover: #e7edf3;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
@ -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;
|
||||
|
||||
@ -54,6 +54,8 @@ export interface IDictionary {
|
||||
|
||||
readonly recommendationHexColor?: string;
|
||||
|
||||
readonly skippedHexColor?: string;
|
||||
|
||||
readonly hasDictionary?: boolean;
|
||||
|
||||
readonly systemManaged?: boolean;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -4,4 +4,5 @@ export interface IProfile {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
language: string;
|
||||
darkTheme: boolean;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "redaction",
|
||||
"version": "3.570.0",
|
||||
"version": "3.581.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
@use 'mixin';
|
||||
|
||||
.portal-search-result {
|
||||
background-color: variables.$grey-9;
|
||||
background-color: variables.$grey-2;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user