Pull request #224: Exclude pages from redaction
Merge in RED/ui from RED-1618 to master * commit '751ccb5ef4c844f61bcb777af5f5783b297da9bc': Exclude pages icon Exclude pages from redaction Fixed comments params order
This commit is contained in:
commit
2767809cd2
@ -196,4 +196,8 @@ export class FileStatusWrapper {
|
||||
get cacheIdentifier() {
|
||||
return btoa(this.fileStatus.lastUploaded + this.fileStatus.lastOCRTime);
|
||||
}
|
||||
|
||||
get excludedPages(): number[] {
|
||||
return this.fileStatus.excludedPages;
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,8 @@
|
||||
*ngIf="permissionsService.canAddComment()"
|
||||
[form]="commentForm"
|
||||
[placeholder]="translateService.instant('comments.add-comment')"
|
||||
type="submit"
|
||||
icon="red:collapse"
|
||||
type="action"
|
||||
width="full"
|
||||
></redaction-input-with-action>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ChangeDetectorRef, Component, HostBinding, Input } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { Comment } from '@redaction/red-ui-http';
|
||||
import { ManualAnnotationService } from '../../services/manual-annotation.service';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
@ -28,7 +28,7 @@ export class CommentsComponent {
|
||||
private readonly _manualAnnotationService: ManualAnnotationService
|
||||
) {
|
||||
this.commentForm = this._formBuilder.group({
|
||||
value: ['', Validators.required]
|
||||
value: ['']
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -58,6 +58,15 @@
|
||||
tooltipPosition="below"
|
||||
></redaction-circle-button>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="toggleExcludePages()"
|
||||
*ngIf="screen === 'file-preview'"
|
||||
[attr.aria-expanded]="activeExcludePages"
|
||||
icon="red:exclude-pages"
|
||||
tooltip="file-preview.exclude-pages"
|
||||
tooltipPosition="below"
|
||||
></redaction-circle-button>
|
||||
|
||||
<!-- Ready for approval-->
|
||||
<redaction-circle-button
|
||||
(action)="setFileUnderApproval($event)"
|
||||
@ -136,8 +145,8 @@
|
||||
<!-- exclude from redaction -->
|
||||
<div class="red-input-group">
|
||||
<mat-slide-toggle
|
||||
(click)="$event.stopPropagation()"
|
||||
(change)="toggleAnalysis()"
|
||||
(click)="$event.stopPropagation()"
|
||||
[checked]="!fileStatus?.isExcluded"
|
||||
[class.mr-24]="screen === 'dossier-overview'"
|
||||
[disabled]="!permissionsService.canToggleAnalysis(fileStatus)"
|
||||
|
||||
@ -13,6 +13,7 @@ import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
export class FileActionsComponent implements OnInit {
|
||||
@Input() fileStatus: FileStatusWrapper;
|
||||
@Input() activeDocumentInfo: boolean;
|
||||
@Input() activeExcludePages: boolean;
|
||||
@Output() actionPerformed = new EventEmitter<string>();
|
||||
actionMenuOpen: boolean;
|
||||
|
||||
@ -104,6 +105,10 @@ export class FileActionsComponent implements OnInit {
|
||||
this.actionPerformed.emit('view-document-info');
|
||||
}
|
||||
|
||||
toggleExcludePages() {
|
||||
this.actionPerformed.emit('view-exclude-pages');
|
||||
}
|
||||
|
||||
openDeleteFileDialog($event: MouseEvent) {
|
||||
this._dialogService.openDeleteFilesDialog(
|
||||
$event,
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
<div class="right-title heading" translate="file-preview.tabs.annotations.label">
|
||||
<div
|
||||
*ngIf="!excludePages"
|
||||
class="right-title heading"
|
||||
translate="file-preview.tabs.annotations.label"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
(click)="multiSelectActive = true"
|
||||
@ -16,14 +20,28 @@
|
||||
></redaction-popup-filter>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="excludePages"
|
||||
class="right-title heading"
|
||||
translate="file-preview.tabs.exclude-pages.label"
|
||||
>
|
||||
<div>
|
||||
<redaction-circle-button
|
||||
(action)="closeExcludePagesView.emit()"
|
||||
icon="red:close"
|
||||
tooltip="file-preview.tabs.exclude-pages.close"
|
||||
tooltipPosition="before"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div class="read-only d-flex" *ngIf="isReadOnly" [class.justify-center]="!isProcessing">
|
||||
<div class="flex-align-items-center" *ngIf="isProcessing">
|
||||
<span class="read-only-text" [translate]="fileData.fileStatus.status"></span>
|
||||
<mat-progress-bar class="w-100" [mode]="'indeterminate'"></mat-progress-bar>
|
||||
<div *ngIf="isReadOnly" [class.justify-center]="!isProcessing" class="read-only d-flex">
|
||||
<div *ngIf="isProcessing" class="flex-align-items-center">
|
||||
<span [translate]="fileData.fileStatus.status" class="read-only-text"></span>
|
||||
<mat-progress-bar [mode]="'indeterminate'" class="w-100"></mat-progress-bar>
|
||||
</div>
|
||||
<div class="flex-center">
|
||||
<mat-icon svgIcon="red:read-only" class="red-white"></mat-icon>
|
||||
<mat-icon class="red-white" svgIcon="red:read-only"></mat-icon>
|
||||
<span class="read-only-text" translate="readonly"></span>
|
||||
</div>
|
||||
</div>
|
||||
@ -89,133 +107,150 @@
|
||||
</div>
|
||||
|
||||
<div style="overflow: hidden; width: 100%">
|
||||
<div attr.anotation-page-header="{{ activeViewerPage }}" class="page-separator">
|
||||
<span *ngIf="!!activeViewerPage" class="all-caps-label">
|
||||
<span translate="page"></span> {{ activeViewerPage }} -
|
||||
{{ activeAnnotationsLength || 0 }}
|
||||
<span
|
||||
[translate]="activeAnnotationsLength === 1 ? 'annotation' : 'annotations'"
|
||||
></span>
|
||||
</span>
|
||||
<ng-container *ngIf="!excludePages">
|
||||
<div attr.anotation-page-header="{{ activeViewerPage }}" class="page-separator">
|
||||
<span *ngIf="!!activeViewerPage" class="all-caps-label">
|
||||
<span translate="page"></span> {{ activeViewerPage }} -
|
||||
{{ activeAnnotationsLength || 0 }}
|
||||
<span
|
||||
[translate]="
|
||||
activeAnnotationsLength === 1 ? 'annotation' : 'annotations'
|
||||
"
|
||||
></span>
|
||||
</span>
|
||||
|
||||
<div *ngIf="multiSelectActive">
|
||||
<div (click)="selectAllOnActivePage()" class="all-caps-label primary pointer">
|
||||
All
|
||||
</div>
|
||||
<div (click)="deselectAllOnActivePage()" class="all-caps-label primary pointer">
|
||||
None
|
||||
<div *ngIf="multiSelectActive">
|
||||
<div
|
||||
(click)="selectAllOnActivePage()"
|
||||
class="all-caps-label primary pointer"
|
||||
translate="file-preview.tabs.annotations.select-all"
|
||||
></div>
|
||||
<div
|
||||
(click)="deselectAllOnActivePage()"
|
||||
class="all-caps-label primary pointer"
|
||||
translate="file-preview.tabs.annotations.select-none"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
#annotationsElement
|
||||
(keydown)="preventKeyDefault($event)"
|
||||
(keyup)="preventKeyDefault($event)"
|
||||
[class.active-panel]="!pagesPanelActive"
|
||||
class="annotations"
|
||||
redactionHasScrollbar
|
||||
tabindex="1"
|
||||
>
|
||||
<ng-container *ngIf="!displayedAnnotations[activeViewerPage]">
|
||||
<redaction-empty-state
|
||||
[horizontalPadding]="24"
|
||||
[verticalPadding]="40"
|
||||
icon="red:document"
|
||||
screen="file-preview"
|
||||
></redaction-empty-state>
|
||||
<div class="no-annotations-buttons-container mt-32">
|
||||
<redaction-icon-button
|
||||
(action)="jumpToPreviousWithAnnotations()"
|
||||
[disabled]="activeViewerPage <= displayedPages[0]"
|
||||
icon="red:nav-prev"
|
||||
text="file-preview.tabs.annotations.jump-to-previous"
|
||||
type="show-bg"
|
||||
></redaction-icon-button>
|
||||
<redaction-icon-button
|
||||
(action)="jumpToNextWithAnnotations()"
|
||||
[disabled]="
|
||||
activeViewerPage >= displayedPages[displayedPages.length - 1]
|
||||
"
|
||||
class="mt-8"
|
||||
icon="red:nav-next"
|
||||
text="file-preview.tabs.annotations.jump-to-next"
|
||||
type="show-bg"
|
||||
></redaction-icon-button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div
|
||||
(click)="annotationClicked(annotation, $event)"
|
||||
*ngFor="let annotation of displayedAnnotations[activeViewerPage]?.annotations"
|
||||
[class.active]="annotationIsSelected(annotation)"
|
||||
[class.multi-select-active]="multiSelectActive"
|
||||
attr.annotation-id="{{ annotation.id }}"
|
||||
attr.annotation-page="{{ activeViewerPage }}"
|
||||
class="annotation-wrapper"
|
||||
#annotationsElement
|
||||
(keydown)="preventKeyDefault($event)"
|
||||
(keyup)="preventKeyDefault($event)"
|
||||
[class.active-panel]="!pagesPanelActive"
|
||||
class="annotations"
|
||||
redactionHasScrollbar
|
||||
tabindex="1"
|
||||
>
|
||||
<div class="active-bar-marker"></div>
|
||||
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
|
||||
<redaction-hidden-action
|
||||
(action)="logAnnotation(annotation)"
|
||||
[requiredClicks]="2"
|
||||
>
|
||||
<div
|
||||
[matTooltip]="annotation.content"
|
||||
class="details"
|
||||
matTooltipPosition="above"
|
||||
<ng-container *ngIf="!displayedAnnotations[activeViewerPage]">
|
||||
<redaction-empty-state
|
||||
[horizontalPadding]="24"
|
||||
[verticalPadding]="40"
|
||||
icon="red:document"
|
||||
screen="file-preview"
|
||||
></redaction-empty-state>
|
||||
<div class="no-annotations-buttons-container mt-32">
|
||||
<redaction-icon-button
|
||||
(action)="jumpToPreviousWithAnnotations()"
|
||||
[disabled]="activeViewerPage <= displayedPages[0]"
|
||||
icon="red:nav-prev"
|
||||
text="file-preview.tabs.annotations.jump-to-previous"
|
||||
type="show-bg"
|
||||
></redaction-icon-button>
|
||||
<redaction-icon-button
|
||||
(action)="jumpToNextWithAnnotations()"
|
||||
[disabled]="
|
||||
activeViewerPage >= displayedPages[displayedPages.length - 1]
|
||||
"
|
||||
class="mt-8"
|
||||
icon="red:nav-next"
|
||||
text="file-preview.tabs.annotations.jump-to-next"
|
||||
type="show-bg"
|
||||
></redaction-icon-button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div
|
||||
(click)="annotationClicked(annotation, $event)"
|
||||
*ngFor="
|
||||
let annotation of displayedAnnotations[activeViewerPage]?.annotations
|
||||
"
|
||||
[class.active]="annotationIsSelected(annotation)"
|
||||
[class.multi-select-active]="multiSelectActive"
|
||||
attr.annotation-id="{{ annotation.id }}"
|
||||
attr.annotation-page="{{ activeViewerPage }}"
|
||||
class="annotation-wrapper"
|
||||
>
|
||||
<div class="active-bar-marker"></div>
|
||||
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
|
||||
<redaction-hidden-action
|
||||
(action)="logAnnotation(annotation)"
|
||||
[requiredClicks]="2"
|
||||
>
|
||||
<redaction-type-annotation-icon
|
||||
[annotation]="annotation"
|
||||
></redaction-type-annotation-icon>
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<strong>{{ annotation.typeLabel | translate }}</strong>
|
||||
</div>
|
||||
<div *ngIf="annotation?.dictionary !== 'manual'">
|
||||
<strong>
|
||||
<span>{{ annotation.descriptor | translate }}</span
|
||||
>: </strong
|
||||
>{{ annotation.dictionary | humanize: false }}
|
||||
</div>
|
||||
<div *ngIf="annotation.shortContent && !annotation.isHint">
|
||||
<strong><span translate="content"></span>: </strong
|
||||
>{{ annotation.shortContent }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="active-icon-marker-container">
|
||||
<redaction-round-checkbox
|
||||
*ngIf="
|
||||
multiSelectActive && annotationIsSelected(annotation)
|
||||
"
|
||||
[active]="true"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-wrapper">
|
||||
<div
|
||||
(click)="toggleExpandComments(annotation, $event)"
|
||||
[matTooltip]="commentsTooltip(annotation)"
|
||||
class="comments-counter"
|
||||
[matTooltip]="annotation.content"
|
||||
class="details"
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
{{ annotation.comments.length }}
|
||||
<redaction-type-annotation-icon
|
||||
[annotation]="annotation"
|
||||
></redaction-type-annotation-icon>
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<strong>{{ annotation.typeLabel | translate }}</strong>
|
||||
</div>
|
||||
<div *ngIf="annotation?.dictionary !== 'manual'">
|
||||
<strong>
|
||||
<span>{{ annotation.descriptor | translate }}</span
|
||||
>: </strong
|
||||
>{{ annotation.dictionary | humanize: false }}
|
||||
</div>
|
||||
<div *ngIf="annotation.shortContent && !annotation.isHint">
|
||||
<strong><span translate="content"></span>: </strong
|
||||
>{{ annotation.shortContent }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="active-icon-marker-container">
|
||||
<redaction-round-checkbox
|
||||
*ngIf="
|
||||
multiSelectActive &&
|
||||
annotationIsSelected(annotation)
|
||||
"
|
||||
[active]="true"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions" *ngIf="!multiSelectActive">
|
||||
<ng-container
|
||||
[ngTemplateOutletContext]="{ annotation: annotation }"
|
||||
[ngTemplateOutlet]="annotationActionsTemplate"
|
||||
></ng-container>
|
||||
|
||||
<div class="actions-wrapper">
|
||||
<div
|
||||
(click)="toggleExpandComments(annotation, $event)"
|
||||
[matTooltip]="commentsTooltip(annotation)"
|
||||
class="comments-counter"
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
{{ annotation.comments.length }}
|
||||
</div>
|
||||
<div *ngIf="!multiSelectActive" class="actions">
|
||||
<ng-container
|
||||
[ngTemplateOutletContext]="{ annotation: annotation }"
|
||||
[ngTemplateOutlet]="annotationActionsTemplate"
|
||||
></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<redaction-comments [annotation]="annotation"></redaction-comments>
|
||||
</redaction-hidden-action>
|
||||
<redaction-comments [annotation]="annotation"></redaction-comments>
|
||||
</redaction-hidden-action>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<redaction-page-exclusion
|
||||
(actionPerformed)="actionPerformed.emit($event)"
|
||||
*ngIf="excludePages"
|
||||
[fileData]="fileData"
|
||||
></redaction-page-exclusion>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -233,7 +233,7 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f9fafb;
|
||||
background-color: $grey-8;
|
||||
|
||||
::ng-deep .annotation-actions {
|
||||
display: flex;
|
||||
|
||||
@ -40,6 +40,7 @@ export class FileWorkloadComponent {
|
||||
@Input() secondaryFilters: FilterModel[];
|
||||
@Input() fileData: FileDataModel;
|
||||
@Input() hideSkipped: boolean;
|
||||
@Input() excludePages: boolean;
|
||||
@Input() annotationActionsTemplate: TemplateRef<any>;
|
||||
@Output() shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter<boolean>();
|
||||
@Output() selectAnnotations = new EventEmitter<
|
||||
@ -49,6 +50,8 @@ export class FileWorkloadComponent {
|
||||
@Output() selectPage = new EventEmitter<number>();
|
||||
@Output() toggleSkipped = new EventEmitter<any>();
|
||||
@Output() annotationsChanged = new EventEmitter<AnnotationWrapper>();
|
||||
@Output() closeExcludePagesView = new EventEmitter();
|
||||
@Output() actionPerformed = new EventEmitter<string>();
|
||||
displayedPages: number[] = [];
|
||||
pagesPanelActive = true;
|
||||
@ViewChildren(CommentsComponent) annotationCommentsComponents: QueryList<CommentsComponent>;
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
<div *ngIf="permissionsService.canExcludePages()" class="exclude-pages-input-container">
|
||||
<redaction-input-with-action
|
||||
(action)="excludePagesRange()"
|
||||
[form]="excludePagesForm"
|
||||
hint="file-preview.tabs.exclude-pages.hint"
|
||||
icon="red:check"
|
||||
placeholder="file-preview.tabs.exclude-pages.input-placeholder"
|
||||
type="action"
|
||||
width="full"
|
||||
></redaction-input-with-action>
|
||||
</div>
|
||||
|
||||
<div *ngIf="excludedPagesRanges.length" class="all-caps-label-container">
|
||||
<div
|
||||
class="all-caps-label"
|
||||
translate="file-preview.tabs.exclude-pages.removed-from-redaction"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="ranges" redactionHasScrollbar>
|
||||
<div *ngFor="let range of excludedPagesRanges" class="range">
|
||||
<ng-container *ngIf="range.startPage === range.endPage">
|
||||
{{ range.startPage }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="range.startPage !== range.endPage">
|
||||
{{ range.startPage }}-{{ range.endPage }}
|
||||
</ng-container>
|
||||
<redaction-circle-button
|
||||
(action)="includePagesRange(range)"
|
||||
*ngIf="permissionsService.canExcludePages()"
|
||||
icon="red:undo"
|
||||
tooltip="file-preview.tabs.exclude-pages.put-back"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="!permissionsService.canExcludePages() && !excludedPagesRanges.length"
|
||||
class="no-excluded heading-l"
|
||||
translate="file-preview.tabs.exclude-pages.no-excluded"
|
||||
></div>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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<string>();
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -56,11 +56,11 @@
|
||||
</ng-container>
|
||||
|
||||
<redaction-assign-user-dropdown
|
||||
*ngIf="editingReviewer"
|
||||
[value]="currentReviewer"
|
||||
[options]="singleUsersSelectOptions"
|
||||
(save)="assignReviewer($event)"
|
||||
(cancel)="editingReviewer = false"
|
||||
(save)="assignReviewer($event)"
|
||||
*ngIf="editingReviewer"
|
||||
[options]="singleUsersSelectOptions"
|
||||
[value]="currentReviewer"
|
||||
></redaction-assign-user-dropdown>
|
||||
|
||||
<div *ngIf="canAssign" class="assign-actions-wrapper">
|
||||
@ -95,6 +95,7 @@
|
||||
(actionPerformed)="fileActionPerformed($event)"
|
||||
*ngIf="viewReady"
|
||||
[activeDocumentInfo]="viewDocumentInfo"
|
||||
[activeExcludePages]="excludePages"
|
||||
></redaction-file-actions>
|
||||
|
||||
<redaction-circle-button
|
||||
@ -152,7 +153,12 @@
|
||||
|
||||
<div class="right-container">
|
||||
<redaction-empty-state
|
||||
*ngIf="viewReady && appStateService.activeFile.isExcluded && !viewDocumentInfo"
|
||||
*ngIf="
|
||||
viewReady &&
|
||||
appStateService.activeFile.isExcluded &&
|
||||
!viewDocumentInfo &&
|
||||
!excludePages
|
||||
"
|
||||
[horizontalPadding]="40"
|
||||
icon="red:needs-work"
|
||||
text="file-preview.tabs.is-excluded"
|
||||
@ -166,7 +172,9 @@
|
||||
|
||||
<redaction-file-workload
|
||||
#fileWorkloadComponent
|
||||
(actionPerformed)="fileActionPerformed($event)"
|
||||
(annotationsChanged)="annotationsChangedByReviewAction($event)"
|
||||
(closeExcludePagesView)="excludePages = false"
|
||||
(deselectAnnotations)="deselectAnnotations($event)"
|
||||
(selectAnnotations)="selectAnnotations($event)"
|
||||
(selectPage)="selectPage($event)"
|
||||
@ -177,6 +185,7 @@
|
||||
[annotationActionsTemplate]="annotationActionsTemplate"
|
||||
[annotations]="annotations"
|
||||
[dialogRef]="dialogRef"
|
||||
[excludePages]="excludePages"
|
||||
[fileData]="fileData"
|
||||
[hideSkipped]="hideSkipped"
|
||||
[primaryFilters]="primaryFilters"
|
||||
|
||||
@ -45,6 +45,7 @@ import {
|
||||
handleFilterDelta,
|
||||
processFilters
|
||||
} from '@shared/components/filters/popup-filter/utils/filter-utils';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
|
||||
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
|
||||
|
||||
@ -71,6 +72,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
hideSkipped = false;
|
||||
displayPDFViewer = false;
|
||||
viewDocumentInfo = false;
|
||||
excludePages = false;
|
||||
|
||||
private _instance: WebViewerInstance;
|
||||
private _lastPage: string;
|
||||
@ -94,7 +96,8 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
private readonly _fileDownloadService: PdfViewerDataService,
|
||||
private readonly _statusControllerService: StatusControllerService,
|
||||
private readonly _ngZone: NgZone,
|
||||
private readonly _fileManagementControllerService: FileManagementControllerService
|
||||
private readonly _fileManagementControllerService: FileManagementControllerService,
|
||||
private readonly _loadingService: LoadingService
|
||||
) {
|
||||
document.documentElement.addEventListener('fullscreenchange', () => {
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
// <!-- Dev Mode Features-->
|
||||
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<void> {
|
||||
const fileData = await this._fileDownloadService.loadActiveFileData().toPromise();
|
||||
console.log(fileData);
|
||||
if (!fileData.fileStatus.isPending && !fileData.fileStatus.isError) {
|
||||
if (performUpdate) {
|
||||
this.fileData.redactionLog = fileData.redactionLog;
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ export class IconsModule {
|
||||
'edit',
|
||||
'entries',
|
||||
'error',
|
||||
'exclude-pages',
|
||||
'exit-fullscreen',
|
||||
'expand',
|
||||
'folder',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<form (submit)="type === 'submit' && submit()" [formGroup]="form">
|
||||
<form [formGroup]="form">
|
||||
<div [style.max-width]="computedWidth" [style.width]="computedWidth" class="red-input-group">
|
||||
<input
|
||||
[formControlName]="formControlName"
|
||||
@ -8,6 +8,8 @@
|
||||
type="text"
|
||||
/>
|
||||
|
||||
<span *ngIf="hint" [translate]="hint" class="hint"></span>
|
||||
|
||||
<!-- Search-->
|
||||
<mat-icon
|
||||
*ngIf="type === 'search' && !hasContent"
|
||||
@ -26,11 +28,11 @@
|
||||
|
||||
<!-- Submit-->
|
||||
<redaction-circle-button
|
||||
(action)="submit($event)"
|
||||
*ngIf="type === 'submit'"
|
||||
[disabled]="form.invalid"
|
||||
(action)="executeAction($event)"
|
||||
*ngIf="type === 'action'"
|
||||
[disabled]="!hasContent"
|
||||
[icon]="icon"
|
||||
[size]="25"
|
||||
icon="red:collapse"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
</div>
|
||||
|
||||
@ -9,6 +9,6 @@ mat-icon.disabled {
|
||||
|
||||
redaction-circle-button {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
top: 4px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
@ -9,8 +9,10 @@ import { FormGroup } from '@angular/forms';
|
||||
export class InputWithActionComponent {
|
||||
@Input() form: FormGroup;
|
||||
@Input() placeholder: string;
|
||||
@Input() hint: string;
|
||||
@Input() width: number | 'full' = 250;
|
||||
@Input() type: 'search' | 'submit';
|
||||
@Input() type: 'search' | 'action';
|
||||
@Input() icon: string;
|
||||
@Output() action = new EventEmitter<any>();
|
||||
|
||||
get formControlName(): 'query' | 'value' {
|
||||
@ -18,7 +20,7 @@ export class InputWithActionComponent {
|
||||
}
|
||||
|
||||
get hasContent() {
|
||||
return !!this.form.get(this.formControlName).value.length;
|
||||
return !!this.form.get(this.formControlName).value?.length;
|
||||
}
|
||||
|
||||
get computedWidth() {
|
||||
@ -29,7 +31,7 @@ export class InputWithActionComponent {
|
||||
this.form.patchValue({ query: '' });
|
||||
}
|
||||
|
||||
submit($event?: MouseEvent) {
|
||||
executeAction($event?: MouseEvent) {
|
||||
$event?.stopPropagation();
|
||||
if (this.hasContent) {
|
||||
this.action.emit();
|
||||
|
||||
@ -216,4 +216,11 @@ export class PermissionsService {
|
||||
canAddComment(fileStatus = this._activeFile): boolean {
|
||||
return this.isFileReviewer(fileStatus) || this.isApprover();
|
||||
}
|
||||
|
||||
canExcludePages(fileStatus = this._activeFile): boolean {
|
||||
return (
|
||||
['UNDER_REVIEW', 'UNDER_APPROVAL'].includes(fileStatus.status) &&
|
||||
(this.isFileReviewer(fileStatus) || this.isApprover())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,7 +375,19 @@
|
||||
"label": "Workload",
|
||||
"select": "Select",
|
||||
"jump-to-previous": "Jump to Previous",
|
||||
"jump-to-next": "Jump to Next"
|
||||
"jump-to-next": "Jump to Next",
|
||||
"select-all": "All",
|
||||
"select-none": "None"
|
||||
},
|
||||
"exclude-pages": {
|
||||
"label": "Exclude Pages",
|
||||
"close": "Close",
|
||||
"error": "Error! Invalid page selection.",
|
||||
"input-placeholder": "e.g. 1-20,22,32",
|
||||
"hint": "Minus (-) for range and comma (,) for enumeration.",
|
||||
"removed-from-redaction": "Removed from redaction",
|
||||
"put-back": "Put back",
|
||||
"no-excluded": "No excluded pages."
|
||||
},
|
||||
"is-excluded": "Redaction is disabled for this document."
|
||||
},
|
||||
@ -392,6 +404,7 @@
|
||||
"last-reviewer": "Last Reviewed by:",
|
||||
"fullscreen": "Full Screen (F)",
|
||||
"document-info": "Your Document Info lives here. This includes metadata required on each document.",
|
||||
"exclude-pages": "Exclude pages from redaction",
|
||||
"new-tab-ssr": "Open Document in Server Side Rendering Mode",
|
||||
"html-debug": "Open Document HTML Debug",
|
||||
"download-original-file": "Download Original File",
|
||||
|
||||
14
apps/red-ui/src/assets/icons/general/exclude-pages.svg
Normal file
14
apps/red-ui/src/assets/icons/general/exclude-pages.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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="Export-just-icons" transform="translate(-346.000000, -902.000000)">
|
||||
<g id="status" transform="translate(346.000000, 902.000000)">
|
||||
<path
|
||||
d="M14,0 L14,11.2 L11.2,11.2 L11.2,14 L0,14 L0,2.8 L2.8,2.8 L2.8,0 L14,0 Z M2.8,4.2 L1.4,4.2 L1.4,12.6 L9.8,12.6 L9.8,11.2 L2.8,11.2 L2.8,4.2 Z M12.6,1.4 L4.2,1.4 L4.2,9.8 L12.6,9.8 L12.6,1.4 Z M9.88492424,3.12512627 L10.8748737,4.11507576 L9.39,5.6 L10.8748737,7.08492424 L9.88492424,8.07487373 L8.4,6.59 L6.91507576,8.07487373 L5.92512627,7.08492424 L7.41,5.6 L5.92512627,4.11507576 L6.91507576,3.12512627 L8.4,4.61 L9.88492424,3.12512627 Z"
|
||||
id="pages"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1012 B |
@ -106,7 +106,7 @@ cdk-virtual-scroll-viewport {
|
||||
|
||||
&:hover {
|
||||
> div {
|
||||
background-color: #f9fafb;
|
||||
background-color: $grey-8;
|
||||
|
||||
&.selection-column redaction-round-checkbox {
|
||||
opacity: 1;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user