diff --git a/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.html
index 44f74eff2..4f4b27476 100644
--- a/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.html
+++ b/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.html
@@ -49,15 +49,15 @@
tabindex="0"
>
-
+
();
@Output() toggleSkipped = new EventEmitter
();
@Output() annotationsChanged = new EventEmitter();
- quickScrollFirstEnabled = false;
- quickScrollLastEnabled = false;
displayedPages: number[] = [];
pagesPanelActive = true;
@ViewChild('annotationsElement') private _annotationsElement: ElementRef;
@@ -129,20 +127,9 @@ export class FileWorkloadComponent {
filters.secondary
);
this.displayedPages = Object.keys(this.displayedAnnotations).map((key) => Number(key));
- this.computeQuickNavButtonsState();
this._changeDetectorRef.markForCheck();
}
- computeQuickNavButtonsState() {
- setTimeout(() => {
- const element: HTMLElement =
- this._quickNavigationElement.nativeElement.querySelector(`#pages`);
- const { scrollTop, scrollHeight, clientHeight } = element;
- this.quickScrollFirstEnabled = scrollTop !== 0;
- this.quickScrollLastEnabled = scrollHeight !== scrollTop + clientHeight;
- }, 0);
- }
-
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent) {
this.pagesPanelActive = false;
if (this.annotationIsSelected(annotation)) {
@@ -234,15 +221,11 @@ export class FileWorkloadComponent {
}
scrollQuickNavFirst() {
- if (this.displayedPages.length > 0) {
- this._scrollQuickNavigationToPage(this.displayedPages[0]);
- }
+ this.selectPage.emit(1);
}
scrollQuickNavLast() {
- if (this.displayedPages.length > 0) {
- this._scrollQuickNavigationToPage(this.displayedPages[this.displayedPages.length - 1]);
- }
+ this.selectPage.emit(this.fileData.fileStatus.numberOfPages);
}
pageSelectedByClick($event: number) {
diff --git a/apps/red-ui/src/app/modules/projects/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html b/apps/red-ui/src/app/modules/projects/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html
index 56d67ca60..9b9f80f93 100644
--- a/apps/red-ui/src/app/modules/projects/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html
+++ b/apps/red-ui/src/app/modules/projects/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html
@@ -35,7 +35,7 @@
-
- {{ annotation.value }}
+ {{ printable(annotation) }}
diff --git a/apps/red-ui/src/app/modules/projects/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts b/apps/red-ui/src/app/modules/projects/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts
index 8917354f3..f4cdc488b 100644
--- a/apps/red-ui/src/app/modules/projects/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts
+++ b/apps/red-ui/src/app/modules/projects/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts
@@ -2,6 +2,7 @@ import { Component, Inject } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { TranslateService } from '@ngx-translate/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { humanize } from '../../../../utils/functions';
export interface RemoveAnnotationsDialogInput {
annotationsToRemove: AnnotationWrapper[];
@@ -27,4 +28,14 @@ export class RemoveAnnotationsDialogComponent {
confirm() {
this.dialogRef.close(true);
}
+
+ printable(annotation: AnnotationWrapper) {
+ if (annotation.isImage) {
+ return this._translateService.instant('remove-annotations-dialog.image-type', {
+ typeLabel: humanize(annotation.dictionary)
+ });
+ } else {
+ return annotation.value;
+ }
+ }
}
diff --git a/apps/red-ui/src/app/modules/projects/projects.module.ts b/apps/red-ui/src/app/modules/projects/projects.module.ts
index a268ba0a2..f615d9c1a 100644
--- a/apps/red-ui/src/app/modules/projects/projects.module.ts
+++ b/apps/red-ui/src/app/modules/projects/projects.module.ts
@@ -37,6 +37,7 @@ import { AnnotationDrawService } from './services/annotation-draw.service';
import { AnnotationProcessingService } from './services/annotation-processing.service';
import { AnnotationRemoveActionsComponent } from './components/annotation-remove-actions/annotation-remove-actions.component';
import { DossierDictionaryDialogComponent } from './dialogs/dossier-dictionary-dialog/dossier-dictionary-dialog.component';
+import { UserPreferenceControllerService } from '@redaction/red-ui-http';
const screens = [
ProjectListingScreenComponent,
@@ -83,7 +84,8 @@ const services = [
ManualAnnotationService,
PdfViewerDataService,
AnnotationDrawService,
- AnnotationProcessingService
+ AnnotationProcessingService,
+ UserPreferenceControllerService
];
@NgModule({
diff --git a/apps/red-ui/src/app/modules/projects/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/projects/screens/file-preview-screen/file-preview-screen.component.ts
index 15119b540..2b606320f 100644
--- a/apps/red-ui/src/app/modules/projects/screens/file-preview-screen/file-preview-screen.component.ts
+++ b/apps/red-ui/src/app/modules/projects/screens/file-preview-screen/file-preview-screen.component.ts
@@ -30,7 +30,11 @@ import { handleFilterDelta, processFilters } from '@shared/components/filter/uti
import { UserPreferenceService } from '@services/user-preference.service';
import { UserService } from '@services/user.service';
import { FormBuilder, FormGroup } from '@angular/forms';
-import { FileManagementControllerService, StatusControllerService } from '@redaction/red-ui-http';
+import {
+ FileManagementControllerService,
+ StatusControllerService,
+ UserPreferenceControllerService
+} from '@redaction/red-ui-http';
import { PdfViewerDataService } from '../../services/pdf-viewer-data.service';
import { download } from '@utils/file-download-utils';
import { ViewMode } from '@models/file/view-mode';
@@ -79,6 +83,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
readonly permissionsService: PermissionsService,
readonly userPreferenceService: UserPreferenceService,
readonly userService: UserService,
+ private readonly _userPreferenceControllerService: UserPreferenceControllerService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _activatedRoute: ActivatedRoute,
private readonly _dialogService: ProjectsDialogService,
@@ -96,6 +101,16 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
this.reviewerForm = this._formBuilder.group({
reviewer: [this.appStateService.activeFile.currentReviewer]
});
+
+ this._loadFileData().subscribe(() => {
+ this._updateCanPerformActions();
+ });
+
+ document.documentElement.addEventListener('fullscreenchange', () => {
+ if (!document.fullscreenElement) {
+ this.fullScreen = false;
+ }
+ });
}
get singleUsersSelectOptions() {
@@ -208,23 +223,18 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
}
ngOnAttach(previousRoute: ActivatedRouteSnapshot) {
- this.displayPDFViewer = true;
+ this.ngOnInit();
this._lastPage = previousRoute.queryParams.page;
- this._subscribeToFileUpdates();
}
ngOnInit(): void {
this.displayPDFViewer = true;
- document.documentElement.addEventListener('fullscreenchange', () => {
- if (!document.fullscreenElement) {
- this.fullScreen = false;
- }
- });
-
- this._loadFileData().subscribe(() => {
- this._updateCanPerformActions();
- });
+ const key = 'Project-Recent-' + this.projectId;
+ this._userPreferenceControllerService
+ .savePreferences([this.fileId], key)
+ .toPromise()
+ .then();
this._subscribeToFileUpdates();
}
@@ -348,6 +358,10 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent) {
+ if (this._router.url.indexOf('/file/') < 0) {
+ return;
+ }
+
if (
!ALL_HOTKEY_ARRAY.includes($event.key) ||
this.dialogRef?.getState() === MatDialogState.OPEN
diff --git a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html
index 1e6ba2781..1cf231e76 100644
--- a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html
+++ b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html
@@ -200,6 +200,7 @@
"
[class.disabled]="fileStatus.isExcluded"
[class.pointer]="permissionsService.canOpenFile(fileStatus)"
+ [class.last-opened]="isLastOpenedFile(fileStatus)"
[routerLink]="fileLink(fileStatus)"
class="table-item"
>
diff --git a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.scss b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.scss
index c305c4f98..4f91ae3fe 100644
--- a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.scss
+++ b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.scss
@@ -122,3 +122,23 @@ cdk-virtual-scroll-viewport {
.primary-attribute {
padding-top: 6px;
}
+
+.last-opened {
+ > .selection-column {
+ padding-left: 6px !important;
+ border-left: 4px solid $red-1;
+ }
+
+ > div {
+ animation: red-fading-background 3s 1;
+ }
+}
+
+@keyframes red-fading-background {
+ 0% {
+ background-color: rgba($red-1, 0.1);
+ }
+ 100% {
+ background-color: inherit;
+ }
+}
diff --git a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.ts b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.ts
index 90aa23fe2..c82fdc20a 100644
--- a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.ts
+++ b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.ts
@@ -18,7 +18,7 @@ import {
} from '@shared/components/filter/utils/filter-utils';
import { PermissionsService } from '@services/permissions.service';
import { UserService } from '@services/user.service';
-import { FileStatus } from '@redaction/red-ui-http';
+import { FileStatus, UserPreferenceControllerService } from '@redaction/red-ui-http';
import { Subscription, timer } from 'rxjs';
import { filter, tap } from 'rxjs/operators';
import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter';
@@ -57,6 +57,7 @@ export class ProjectOverviewScreenComponent
private _routerEventsScrollPositionSub: Subscription;
private _fileChangedSub: Subscription;
private _lastScrollPosition: number;
+ private _lastOpenedFileId = '';
@ViewChild(CdkVirtualScrollViewport) private _scrollBar: CdkVirtualScrollViewport;
@@ -75,6 +76,7 @@ export class ProjectOverviewScreenComponent
private readonly _translateService: TranslateService,
private readonly _fileDropOverlayService: FileDropOverlayService,
private readonly _appStateService: AppStateService,
+ private readonly _userPreferenceControllerService: UserPreferenceControllerService,
protected readonly _injector: Injector
) {
super(_injector);
@@ -85,6 +87,10 @@ export class ProjectOverviewScreenComponent
return this._appStateService.activeProject;
}
+ isLastOpenedFile(fileStatus: FileStatusWrapper): boolean {
+ return this._lastOpenedFileId === fileStatus.fileId;
+ }
+
protected get _filterComponents(): FilterComponent[] {
return [
this._statusFilterComponent,
@@ -112,6 +118,12 @@ export class ProjectOverviewScreenComponent
}
ngOnInit(): void {
+ this._userPreferenceControllerService.getAllUserAttributes().subscribe((attributes) => {
+ if (attributes === null || attributes === undefined) return;
+ const key = 'Project-Recent-' + this.activeProject.projectId;
+ this._lastOpenedFileId = attributes[key][0];
+ });
+
this._fileDropOverlayService.initFileDropHandling();
this.calculateData();
diff --git a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts
index 79d298f7d..d932d650e 100644
--- a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts
+++ b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts
@@ -1,10 +1,11 @@
-import { Component, Input } from '@angular/core';
+import { Component, Inject, Input } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { ProjectWrapper } from '@state/model/project.wrapper';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { FileDownloadService } from '@upload-download/services/file-download.service';
import { NotificationService } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core';
+import { BASE_HREF } from '../../../../../tokens';
export type MenuState = 'OPEN' | 'CLOSED';
@@ -21,6 +22,7 @@ export class FileDownloadBtnComponent {
@Input() tooltipClass: string;
constructor(
+ @Inject(BASE_HREF) private readonly _baseHref: string,
private readonly _permissionsService: PermissionsService,
private readonly _fileDownloadService: FileDownloadService,
private readonly _translateService: TranslateService,
@@ -47,7 +49,9 @@ export class FileDownloadBtnComponent {
.downloadFiles(Array.isArray(this.file) ? this.file : [this.file], this.project)
.subscribe(() => {
this._notificationService.showToastNotification(
- this._translateService.instant('download-status.queued')
+ this._translateService.instant('download-status.queued', {
+ baseUrl: this._baseHref
+ })
);
});
}
diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json
index 5a3e94e6c..cc2704e91 100644
--- a/apps/red-ui/src/assets/i18n/de.json
+++ b/apps/red-ui/src/assets/i18n/de.json
@@ -275,7 +275,6 @@
"description": "Beschreibung"
},
"header": "Dossierübersicht",
- "no-project": "Angefordertes Dossier: {{projectId}} existiert nicht! Zurück zur Dossierliste.",
"bulk": {
"delete": "Dokumente löschen",
"assign": "Prüfer zuweisen",
diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json
index c0dc90b4e..a6442e658 100644
--- a/apps/red-ui/src/assets/i18n/en.json
+++ b/apps/red-ui/src/assets/i18n/en.json
@@ -22,7 +22,7 @@
}
},
"download-status": {
- "queued": "Your download has been queued, you can see all your requested downloads here: My Downloads .",
+ "queued": "Your download has been queued, you can see all your requested downloads here: My Downloads .",
"error": {
"generic": "Download failed"
},
@@ -231,7 +231,8 @@
"report": {
"action": "Download redaction report"
},
- "assign": "Assign Reviewer",
+ "assign-reviewer": "Assign Reviewer",
+ "assign-approver": "Assign Approver",
"assign-me": "Assign To Me",
"table-header": {
"title": "{{length}} documents",
@@ -281,7 +282,6 @@
"description": "Description"
},
"header": "Dossier Overview",
- "no-project": "Requested dossier: {{projectId}} does not exist! Back to Dossier Listing. ",
"bulk": {
"delete": "Delete Documents",
"assign": "Assign Reviewer",
@@ -349,8 +349,8 @@
"download-original-file": "Download Original File",
"exit-fullscreen": "Exit Full Screen (F)",
"quick-nav": {
- "jump-first": "Jump to first annotation",
- "jump-last": "Jump to last annotation"
+ "jump-first": "Jump to first page",
+ "jump-last": "Jump to last page"
}
},
"annotation-actions": {
@@ -376,6 +376,10 @@
"success": "Redaction suggestion approved!",
"error": "Failed to approved redaction: {{error}}"
},
+ "request-remove": {
+ "success": "Requested to remove redaction!",
+ "error": "Failed to request removal of redaction: {{error}}"
+ },
"assign-reviewer": "Assign Reviewer",
"assign-approver": "Assign Approver",
"assign-me": "Assign To Me",
@@ -431,35 +435,167 @@
"accept-recommendation": {
"label": "Accept Recommendation"
},
- "assign-reviewer-owner": {
- "dialog": {
- "single-user": "Reviewer",
- "title": "Manage File Reviewer",
- "save": "Save",
- "cancel": "Cancel"
- }
+ "suggest-remove-annotation": "Remove or Suggest to remove this entry",
+ "suggest-remove-annotations": "Remove or Suggest to remove selected entries",
+ "reject-suggestion": "Reject Suggestion",
+ "remove-annotation": {
+ "suggest-remove-from-dict": "Suggest to remove from dictionary",
+ "suggest-only-here": "Suggest to remove only here",
+ "remove-from-dict": "Remove from dictionary",
+ "only-here": "Remove only here",
+ "false-positive": "False Positive"
},
- "assign-approver-owner": {
- "dialog": {
- "single-user": "Approver",
- "title": "Manage File Approver",
- "save": "Save",
- "cancel": "Cancel"
- }
- },
- "assign-project-owner": {
- "dialog": {
- "single-user": "Owner",
- "multi-user": "Review Team",
- "title": "Manage Dossier Team",
- "approvers": "Approvers",
- "reviewers": "Reviewers",
- "save": "Save Changes",
- "cancel": "Cancel",
- "search": "Search...",
- "no-approvers": "No approvers yet.\nSelect from the list below.",
- "no-reviewers": "No reviewers yet.\nSelect from the list below.",
- "make-approver": "Make Approver"
+ "remove": "Remove",
+ "undo": "Undo",
+ "reject": "Reject",
+ "hide": "Hide",
+ "show": "Show"
+ },
+ "initials-avatar": {
+ "unassigned": "Unassigned",
+ "you": "You"
+ },
+ "assign-reviewer-owner": {
+ "dialog": {
+ "single-user": "Reviewer",
+ "title": "Manage File Reviewer",
+ "save": "Save",
+ "cancel": "Cancel"
+ }
+ },
+ "assign-approver-owner": {
+ "dialog": {
+ "single-user": "Approver",
+ "title": "Manage File Approver",
+ "save": "Save",
+ "cancel": "Cancel"
+ }
+ },
+ "assign-project-owner": {
+ "dialog": {
+ "single-user": "Owner",
+ "multi-user": "Review Team",
+ "title": "Manage Dossier Team",
+ "approvers": "Approvers",
+ "reviewers": "Reviewers",
+ "save": "Save Changes",
+ "cancel": "Cancel",
+ "search": "Search...",
+ "no-approvers": "No approvers yet.\nSelect from the list below.",
+ "no-reviewers": "No reviewers yet.\nSelect from the list below.",
+ "make-approver": "Make Approver"
+ }
+ },
+ "project-member-guard": {
+ "access-denied": "You are not allowed to access that page."
+ },
+ "comments": {
+ "comment": "{{count}} comment",
+ "comments": "{{count}} comments",
+ "add-comment": "Add a comment",
+ "hide-comments": "Hide",
+ "cancel": "Cancel"
+ },
+ "UNPROCESSED": "Unprocessed",
+ "REPROCESS": "Processing",
+ "FULLREPROCESS": "Processing",
+ "PROCESSING": "Processing",
+ "OCR_PROCESSING": "OCR Processing",
+ "ERROR": "Re-processing required",
+ "UNASSIGNED": "Unassigned",
+ "UNDER_REVIEW": "Under Review",
+ "UNDER_APPROVAL": "Under Approval",
+ "APPROVED": "Approved",
+ "EXCLUDED": "Excluded",
+ "by": "by",
+ "efsa": "EFSA Approval",
+ "finished": "Finished",
+ "submitted": "Submitted",
+ "active": "Active",
+ "archived": "Archived",
+ "hint": "Hint",
+ "skipped": "Skipped",
+ "redaction": "Redaction",
+ "comment": "Comment",
+ "pending-analysis": "Pending Re-Analysis",
+ "suggestion": "Suggestion for redaction",
+ "dictionary": "Dictionary",
+ "type": "Type",
+ "content": "Reason",
+ "page": "Page",
+ "annotation": "Annotation",
+ "annotations": "Annotations",
+ "filter": {
+ "hint": "Hints only",
+ "redaction": "Redacted",
+ "suggestion": "Suggested Redaction",
+ "analysis": "Analysis required",
+ "none": "No Annotations",
+ "updated": "Updated",
+ "image": "Images"
+ },
+ "filter-menu": {
+ "label": "Filter",
+ "filter-types": "Filter types",
+ "filter-options": "Filter options",
+ "with-comments": "Show only annotations with comments"
+ },
+ "sorting": {
+ "recent": "Recent",
+ "oldest": "Oldest",
+ "alphabetically": "Alphabetically",
+ "number-of-pages": "Number of pages",
+ "number-of-analyses": "Number of analyses",
+ "custom": "Custom"
+ },
+ "readonly-pill": "Read-only",
+ "group": {
+ "redactions": "Redaction Dictionaries",
+ "hints": "Hint Dictionaries"
+ },
+ "annotation-type": {
+ "recommendation": "Recommendation",
+ "remove-only-here": "Pending removal ( only here )",
+ "add-dictionary": "Pending add to dictionary",
+ "remove-dictionary": "Pending remove from dictionary",
+ "suggestion-add-dictionary": "Suggested dictionary add",
+ "suggestion-force-redaction": "Suggestion force redaction",
+ "suggestion-remove-dictionary": "Suggested dictionary removal",
+ "suggestion-add": "Suggested redaction",
+ "suggestion-remove": "Suggested redaction removal",
+ "skipped": "Skipped",
+ "pending-analysis": "Pending Re-Analysis",
+ "hint": "Hint",
+ "redaction": "Redaction",
+ "manual-redaction": "Manual Redaction",
+ "declined-suggestion": "Declined Suggestion"
+ },
+ "manual-annotation": {
+ "dialog": {
+ "header": {
+ "dictionary": "Add to dictionary",
+ "redaction": "Redaction",
+ "force": "Force Redaction",
+ "request-dictionary": "Request add to dictionary",
+ "request-redaction": "Request Redaction",
+ "false-positive": "Set false positive",
+ "request-false-positive": "Request false positive"
+ },
+ "add-redaction": {
+ "success": "Redaction suggestion added!",
+ "failed": "Failed to add redaction: {{message}}"
+ },
+ "actions": {
+ "save": "Save"
+ },
+ "content": {
+ "text": "Selected text:",
+ "rectangle": "Custom Rectangle",
+ "dictionary": "Dictionary",
+ "reason": "Reason",
+ "reason-placeholder": "Select a reason ...",
+ "legalBasis": "Legal Basis",
+ "comment": "Comment"
}
},
"approve-request": {
@@ -492,6 +628,7 @@
"title": "Remove Redaction",
"question": "Following redactions will be removed only here:"
},
+ "image-type": "Image: {{typeLabel}}",
"dictionary": "Dictionary",
"value": "Value",
"confirm": "Yes, proceed and remove!",
diff --git a/package.json b/package.json
index 58890f334..9a9e9d47a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "redaction",
- "version": "2.38.0",
+ "version": "2.40.0",
"private": true,
"license": "MIT",
"scripts": {