-
- {{ activeViewerPage }} -
- {{ activeAnnotationsLength || 0 }}
-
-
+
+
+
+ {{ activeViewerPage }} -
+ {{ activeAnnotationsLength || 0 }}
+
+
-
-
-
-
-
-
-
- = displayedPages[displayedPages.length - 1]
- "
- class="mt-8"
- icon="red:nav-next"
- text="file-preview.tabs.annotations.jump-to-next"
- type="show-bg"
- >
-
-
-
-
-
-
+
+
+
+ = displayedPages[displayedPages.length - 1]
+ "
+ class="mt-8"
+ icon="red:nav-next"
+ text="file-preview.tabs.annotations.jump-to-next"
+ type="show-bg"
+ >
+
+
+
+
+
+
+
-
-
-
- {{ annotation.typeLabel | translate }}
-
-
-
- {{ annotation.descriptor | translate }}: {{ annotation.dictionary | humanize: false }}
-
-
- : {{ annotation.shortContent }}
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.scss b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.scss
index 8e225c5d7..dd8d3aa68 100644
--- a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.scss
+++ b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.scss
@@ -233,7 +233,7 @@
}
&:hover {
- background-color: #f9fafb;
+ background-color: $grey-8;
::ng-deep .annotation-actions {
display: flex;
diff --git a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts
index dbaa11354..9b72900e1 100644
--- a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts
+++ b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts
@@ -40,6 +40,7 @@ export class FileWorkloadComponent {
@Input() secondaryFilters: FilterModel[];
@Input() fileData: FileDataModel;
@Input() hideSkipped: boolean;
+ @Input() excludePages: boolean;
@Input() annotationActionsTemplate: TemplateRef;
@Output() shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter();
@Output() selectAnnotations = new EventEmitter<
@@ -49,6 +50,8 @@ export class FileWorkloadComponent {
@Output() selectPage = new EventEmitter();
@Output() toggleSkipped = new EventEmitter();
@Output() annotationsChanged = new EventEmitter();
+ @Output() closeExcludePagesView = new EventEmitter();
+ @Output() actionPerformed = new EventEmitter();
displayedPages: number[] = [];
pagesPanelActive = true;
@ViewChildren(CommentsComponent) annotationCommentsComponents: QueryList;
@@ -56,9 +59,9 @@ export class FileWorkloadComponent {
@ViewChild('quickNavigation') private _quickNavigationElement: ElementRef;
constructor(
+ private readonly _permissionsService: PermissionsService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _annotationProcessingService: AnnotationProcessingService,
- private readonly _permissionsService: PermissionsService,
private readonly _translateService: TranslateService
) {}
@@ -71,18 +74,6 @@ export class FileWorkloadComponent {
private _multiSelectActive = false;
- get isProcessing(): boolean {
- return this.fileData.fileStatus?.isProcessing;
- }
-
- get activeAnnotationsLength(): number | undefined {
- return this.displayedAnnotations[this.activeViewerPage]?.annotations?.length;
- }
-
- get isReadOnly(): boolean {
- return !this._permissionsService.canPerformAnnotationActions();
- }
-
get multiSelectActive(): boolean {
return this._multiSelectActive;
}
@@ -97,6 +88,18 @@ export class FileWorkloadComponent {
}
}
+ get isProcessing(): boolean {
+ return this.fileData?.fileStatus?.isProcessing;
+ }
+
+ get activeAnnotationsLength(): number | undefined {
+ return this.displayedAnnotations[this.activeViewerPage]?.annotations?.length;
+ }
+
+ get isReadOnly(): boolean {
+ return !this._permissionsService.canPerformAnnotationActions();
+ }
+
private get _firstSelectedAnnotation() {
return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null;
}
@@ -224,15 +227,21 @@ export class FileWorkloadComponent {
}
scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed') {
- const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(
- `div[anotation-page-header="${page}"]`
- );
- FileWorkloadComponent._scrollToFirstElement(elements, mode);
+ if (this._annotationsElement) {
+ const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(
+ `div[anotation-page-header="${page}"]`
+ );
+ FileWorkloadComponent._scrollToFirstElement(elements, mode);
+ }
}
@debounce()
scrollToSelectedAnnotation() {
- if (!this.selectedAnnotations || this.selectedAnnotations.length === 0) {
+ if (
+ !this.selectedAnnotations ||
+ this.selectedAnnotations.length === 0 ||
+ !this._annotationsElement
+ ) {
return;
}
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(
@@ -420,9 +429,11 @@ export class FileWorkloadComponent {
}
private _scrollQuickNavigationToPage(page: number) {
- const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(
- `#quick-nav-page-${page}`
- );
- FileWorkloadComponent._scrollToFirstElement(elements);
+ if (this._quickNavigationElement) {
+ const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(
+ `#quick-nav-page-${page}`
+ );
+ FileWorkloadComponent._scrollToFirstElement(elements);
+ }
}
}
diff --git a/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.html b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.html
new file mode 100644
index 000000000..7ff791ae4
--- /dev/null
+++ b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+ {{ range.startPage }}
+
+
+ {{ range.startPage }}-{{ range.endPage }}
+
+
+
+
+
+
diff --git a/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.scss b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.scss
new file mode 100644
index 000000000..c5d6bf1eb
--- /dev/null
+++ b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.scss
@@ -0,0 +1,60 @@
+@import '../../../../../assets/styles/red-variables';
+@import '../../../../../assets/styles/red-mixins';
+
+:host {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .exclude-pages-input-container {
+ background-color: $grey-6;
+ padding: 15px 15px 16px 14px;
+ }
+
+ .all-caps-label-container {
+ padding: 8px 16px;
+ border-bottom: 1px solid $separator;
+ }
+
+ .ranges {
+ overflow: hidden;
+
+ .range {
+ padding-left: 17px;
+ padding-right: 16px;
+ border-bottom: 1px solid $separator;
+ transition: background-color 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 50px;
+
+ redaction-circle-button {
+ display: none;
+ }
+
+ &:hover {
+ background-color: $grey-8;
+
+ redaction-circle-button {
+ display: initial;
+ }
+ }
+ }
+
+ &.has-scrollbar:hover {
+ @include scroll-bar;
+ overflow: auto;
+
+ .range {
+ padding-right: 5px;
+ }
+ }
+ }
+
+ .no-excluded {
+ padding: 50px 16px;
+ text-align: center;
+ opacity: 0.7;
+ }
+}
diff --git a/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts
new file mode 100644
index 000000000..10f6a34d3
--- /dev/null
+++ b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts
@@ -0,0 +1,108 @@
+import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
+import { PermissionsService } from '../../../../services/permissions.service';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { PageRange, ReanalysisControllerService } from '@redaction/red-ui-http';
+import { FileDataModel } from '../../../../models/file/file-data.model';
+import { NotificationService, NotificationType } from '../../../../services/notification.service';
+import { LoadingService } from '../../../../services/loading.service';
+import { TranslateService } from '@ngx-translate/core';
+
+@Component({
+ selector: 'redaction-page-exclusion',
+ templateUrl: './page-exclusion.component.html',
+ styleUrls: ['./page-exclusion.component.scss']
+})
+export class PageExclusionComponent implements OnChanges {
+ @Input() fileData: FileDataModel;
+ @Output() actionPerformed = new EventEmitter();
+
+ excludePagesForm: FormGroup;
+ excludedPagesRanges: PageRange[] = [];
+
+ constructor(
+ readonly permissionsService: PermissionsService,
+ private readonly _formBuilder: FormBuilder,
+ private readonly _reanalysisControllerService: ReanalysisControllerService,
+ private readonly _notificationService: NotificationService,
+ private readonly _loadingService: LoadingService,
+ private readonly _translateService: TranslateService
+ ) {
+ this.excludePagesForm = this._formBuilder.group({
+ value: ['']
+ });
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes.fileData) {
+ const excludedPages = (this.fileData?.fileStatus?.excludedPages || []).sort(
+ (p1, p2) => p1 - p2
+ );
+ this.excludedPagesRanges = excludedPages.reduce((ranges, page) => {
+ if (!ranges.length) {
+ return [{ startPage: page, endPage: page }];
+ }
+
+ if (page === ranges[ranges.length - 1].endPage + 1) {
+ ranges[ranges.length - 1].endPage = page;
+ } else {
+ ranges.push({ startPage: page, endPage: page });
+ }
+ return ranges;
+ }, []);
+ }
+ }
+
+ async excludePagesRange() {
+ this._loadingService.start();
+ try {
+ const pageRanges = this.excludePagesForm
+ .get('value')
+ .value.split(',')
+ .map(range => {
+ const splitted = range.split('-');
+ const startPage = parseInt(splitted[0], 10);
+ const endPage = splitted.length > 1 ? parseInt(splitted[1], 10) : startPage;
+ if (!startPage || !endPage) {
+ throw new Error();
+ }
+ return {
+ startPage,
+ endPage
+ };
+ });
+ await this._reanalysisControllerService
+ .excludePages(
+ {
+ pageRanges: pageRanges
+ },
+ this.fileData.fileStatus.dossierId,
+ this.fileData.fileStatus.fileId
+ )
+ .toPromise();
+ this.excludePagesForm.reset();
+ this.actionPerformed.emit('exclude-pages');
+ } catch (e) {
+ this._notificationService.showToastNotification(
+ this._translateService.instant('file-preview.tabs.exclude-pages.error'),
+ null,
+ NotificationType.ERROR
+ );
+ this._loadingService.stop();
+ }
+ }
+
+ async includePagesRange(range: PageRange) {
+ this._loadingService.start();
+ await this._reanalysisControllerService
+ .includePages(
+ {
+ pageRanges: [range]
+ },
+ this.fileData.fileStatus.dossierId,
+ this.fileData.fileStatus.fileId
+ )
+ .toPromise();
+ this.excludePagesForm.reset();
+ this.actionPerformed.emit('exclude-pages');
+ }
+}
diff --git a/apps/red-ui/src/app/modules/dossier/dossiers.module.ts b/apps/red-ui/src/app/modules/dossier/dossiers.module.ts
index 70406daef..bd319a76c 100644
--- a/apps/red-ui/src/app/modules/dossier/dossiers.module.ts
+++ b/apps/red-ui/src/app/modules/dossier/dossiers.module.ts
@@ -45,6 +45,7 @@ import { EditDossierTeamMembersComponent } from './dialogs/edit-dossier-dialog/t
import { TeamMembersManagerComponent } from './components/team-members-manager/team-members-manager.component';
import { TeamMembersDialogComponent } from './dialogs/team-members-dialog/team-members-dialog.component';
import { ScrollButtonComponent } from './components/scroll-button/scroll-button.component';
+import { PageExclusionComponent } from './components/page-exclusion/page-exclusion.component';
const screens = [
DossierListingScreenComponent,
@@ -87,6 +88,7 @@ const components = [
EditDossierTeamMembersComponent,
TeamMembersManagerComponent,
ScrollButtonComponent,
+ PageExclusionComponent,
...screens,
...dialogs
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html
index d9c1be49d..fa07401f6 100644
--- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html
+++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html
@@ -56,11 +56,11 @@
@@ -95,6 +95,7 @@
(actionPerformed)="fileActionPerformed($event)"
*ngIf="viewReady"
[activeDocumentInfo]="viewDocumentInfo"
+ [activeExcludePages]="excludePages"
>
{
if (!document.fullscreenElement) {
@@ -169,6 +172,36 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
return this.appStateService.activeFile.fileStatus.lastReviewer;
}
+ get assignOrChangeReviewerTooltip() {
+ return this.currentReviewer
+ ? 'file-preview.change-reviewer'
+ : 'file-preview.assign-reviewer';
+ }
+
+ get currentReviewer(): string {
+ return this.appStateService.activeFile.currentReviewer;
+ }
+
+ get status(): FileStatus.StatusEnum {
+ return this.appStateService.activeFile.status;
+ }
+
+ get statusBarConfig(): [{ length: number; color: FileStatus.StatusEnum }] {
+ return [{ length: 1, color: this.status }];
+ }
+
+ get isUnderReviewOrApproval(): boolean {
+ return this.status === 'UNDER_REVIEW' || this.status === 'UNDER_APPROVAL';
+ }
+
+ get canAssignReviewer(): boolean {
+ return (
+ !this.currentReviewer &&
+ this.permissionsService.canAssignUser() &&
+ this.appStateService.activeDossier.hasMoreThanOneReviewer
+ );
+ }
+
updateViewMode() {
const annotations = this._getAnnotations(a => a.getCustomData('redacto-manager'));
const redactions = annotations.filter(a => a.getCustomData('redaction'));
@@ -441,10 +474,22 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
await this.appStateService.reloadActiveDossierFiles();
return;
+ case 'exclude-pages':
+ await this.appStateService.reloadActiveDossierFiles();
+ await this._loadFileData();
+ this._loadingService.stop();
+ return;
+
case 'view-document-info':
this.viewDocumentInfo = !this.viewDocumentInfo;
return;
+ case 'view-exclude-pages':
+ this.excludePages = !this.excludePages;
+ this._workloadComponent.multiSelectActive = false;
+ this.viewDocumentInfo = false;
+ return;
+
default:
this._updateCanPerformActions();
}
@@ -509,36 +554,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
return false;
}
- get assignOrChangeReviewerTooltip() {
- return this.currentReviewer
- ? 'file-preview.change-reviewer'
- : 'file-preview.assign-reviewer';
- }
-
- get currentReviewer(): string {
- return this.appStateService.activeFile.currentReviewer;
- }
-
- get status(): FileStatus.StatusEnum {
- return this.appStateService.activeFile.status;
- }
-
- get statusBarConfig(): [{ length: number; color: FileStatus.StatusEnum }] {
- return [{ length: 1, color: this.status }];
- }
-
- get isUnderReviewOrApproval(): boolean {
- return this.status === 'UNDER_REVIEW' || this.status === 'UNDER_APPROVAL';
- }
-
- get canAssignReviewer(): boolean {
- return (
- !this.currentReviewer &&
- this.permissionsService.canAssignUser() &&
- this.appStateService.activeDossier.hasMoreThanOneReviewer
- );
- }
-
//
async openSSRFilePreview() {
window.open(`/pdf-preview/${this.dossierId}/${this.fileId}`, '_blank');
@@ -576,7 +591,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
private async _loadFileData(performUpdate: boolean = false): Promise {
const fileData = await this._fileDownloadService.loadActiveFileData().toPromise();
- console.log(fileData);
if (!fileData.fileStatus.isPending && !fileData.fileStatus.isError) {
if (performUpdate) {
this.fileData.redactionLog = fileData.redactionLog;
diff --git a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts
index db8ee150b..ac87e06b2 100644
--- a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts
+++ b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts
@@ -30,19 +30,19 @@ export class ManualAnnotationService {
addComment(comment: string, annotationId: string) {
return this._manualRedactionControllerService.addComment(
{ text: comment },
+ annotationId,
this._appStateService.activeDossierId,
- this._appStateService.activeFileId,
- annotationId
+ this._appStateService.activeFileId
);
}
// this wraps /manualRedaction/comment/undo
deleteComment(commentId: string, annotationId: string) {
return this._manualRedactionControllerService.undoComment(
- this._appStateService.activeDossierId,
- this._appStateService.activeFileId,
annotationId,
- commentId
+ commentId,
+ this._appStateService.activeDossierId,
+ this._appStateService.activeFileId
);
}
diff --git a/apps/red-ui/src/app/modules/icons/icons.module.ts b/apps/red-ui/src/app/modules/icons/icons.module.ts
index b91031400..0fed86bb4 100644
--- a/apps/red-ui/src/app/modules/icons/icons.module.ts
+++ b/apps/red-ui/src/app/modules/icons/icons.module.ts
@@ -39,6 +39,7 @@ export class IconsModule {
'edit',
'entries',
'error',
+ 'exclude-pages',
'exit-fullscreen',
'expand',
'folder',
diff --git a/apps/red-ui/src/app/modules/shared/components/input-with-action/input-with-action.component.html b/apps/red-ui/src/app/modules/shared/components/input-with-action/input-with-action.component.html
index e4885dff3..179797f41 100644
--- a/apps/red-ui/src/app/modules/shared/components/input-with-action/input-with-action.component.html
+++ b/apps/red-ui/src/app/modules/shared/components/input-with-action/input-with-action.component.html
@@ -1,4 +1,4 @@
-