cherrypick compare view
This commit is contained in:
parent
2652ab9cab
commit
32723d2fc0
@ -10,8 +10,8 @@ import { WatermarkControllerService, WatermarkModelRes } from '@redaction/red-ui
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { hexToRgb } from '@utils/functions';
|
||||
import { BASE_HREF } from '../../../../tokens';
|
||||
import { stampPDFPage } from '../../../../utils/page-stamper';
|
||||
|
||||
export const DEFAULT_WATERMARK: WatermarkModelRes = {
|
||||
text: null,
|
||||
@ -169,7 +169,6 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
responseType: 'blob'
|
||||
})
|
||||
.subscribe(blobData => {
|
||||
console.log('load blank');
|
||||
this._instance.loadDocument(blobData, { filename: 'blank.pdf' });
|
||||
});
|
||||
});
|
||||
@ -184,57 +183,28 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
const pdfNet = this._instance.PDFNet;
|
||||
const document = await this._instance.docViewer.getDocument().getPDFDoc();
|
||||
|
||||
await pdfNet.runWithCleanup(
|
||||
async () => {
|
||||
await document.lock();
|
||||
const text = this.configForm.get('text').value || '';
|
||||
const fontSize = this.configForm.get('fontSize').value;
|
||||
const fontType = this.configForm.get('fontType').value;
|
||||
const orientation: WatermarkModelRes.OrientationEnum =
|
||||
this.configForm.get('orientation').value;
|
||||
const opacity = this.configForm.get('opacity').value;
|
||||
const color = this.configForm.get('hexColor').value;
|
||||
|
||||
const pageSet = await pdfNet.PageSet.createSinglePage(1);
|
||||
|
||||
await pdfNet.Stamper.deleteStamps(document, pageSet);
|
||||
|
||||
const text = this.configForm.get('text').value || '';
|
||||
const fontSize = this.configForm.get('fontSize').value;
|
||||
const fontType = this.configForm.get('fontType').value;
|
||||
const orientation: WatermarkModelRes.OrientationEnum =
|
||||
this.configForm.get('orientation').value;
|
||||
const opacity = this.configForm.get('opacity').value;
|
||||
const color = this.configForm.get('hexColor').value;
|
||||
|
||||
const rgbColor = hexToRgb(color);
|
||||
|
||||
const stamper = await pdfNet.Stamper.create(3, fontSize, 0);
|
||||
await stamper.setFontColor(
|
||||
await pdfNet.ColorPt.init(rgbColor.r / 255, rgbColor.g / 255, rgbColor.b / 255)
|
||||
);
|
||||
await stamper.setOpacity(opacity / 100);
|
||||
|
||||
switch (orientation) {
|
||||
case WatermarkModelRes.OrientationEnum.VERTICAL:
|
||||
await stamper.setAlignment(0, 0);
|
||||
await stamper.setRotation(-90);
|
||||
break;
|
||||
case WatermarkModelRes.OrientationEnum.HORIZONTAL:
|
||||
break;
|
||||
case WatermarkModelRes.OrientationEnum.DIAGONAL:
|
||||
default:
|
||||
await stamper.setAlignment(0, 0);
|
||||
await stamper.setRotation(-45);
|
||||
}
|
||||
|
||||
const font = await pdfNet.Font.createAndEmbed(
|
||||
document,
|
||||
this._convertFont(fontType)
|
||||
);
|
||||
await stamper.setFont(font);
|
||||
await stamper.setTextAlignment(0);
|
||||
await stamper.stampText(document, text, pageSet);
|
||||
|
||||
this._instance.docViewer.refreshAll();
|
||||
this._instance.docViewer.updateView([0], 0);
|
||||
this._changeDetectorRef.detectChanges();
|
||||
},
|
||||
environment.licenseKey ? atob(environment.licenseKey) : null
|
||||
await stampPDFPage(
|
||||
document,
|
||||
pdfNet,
|
||||
text,
|
||||
fontSize,
|
||||
fontType,
|
||||
orientation,
|
||||
opacity,
|
||||
color,
|
||||
1
|
||||
);
|
||||
this._instance.docViewer.refreshAll();
|
||||
this._instance.docViewer.updateView([0], 0);
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
private _initForm() {
|
||||
@ -262,16 +232,4 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
private _convertFont(fontType: any): number {
|
||||
switch (fontType) {
|
||||
case 'times-new-roman':
|
||||
return 0;
|
||||
case 'helvetica':
|
||||
return 4;
|
||||
case 'courier':
|
||||
return 8;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,34 @@
|
||||
<div class="page">
|
||||
<div #viewer [id]="fileStatus.fileId" class="viewer"></div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
#compareFileInput
|
||||
(change)="uploadFile($event.target['files'])"
|
||||
class="file-upload-input"
|
||||
type="file"
|
||||
/>
|
||||
|
||||
<div class="pagination noselect" *ngIf="totalPages && currentPage">
|
||||
<div (click)="previousPage()">
|
||||
<mat-icon class="chevron-icon" svgIcon="red:nav-prev"></mat-icon>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
#pageInput
|
||||
[value]="currentPage"
|
||||
min="1"
|
||||
[max]="totalPages"
|
||||
type="number"
|
||||
(change)="navigateToPage(pageInput.value)"
|
||||
class="page-number-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="separator">/</div>
|
||||
<div>
|
||||
{{ totalPages }}
|
||||
</div>
|
||||
<div (click)="nextPage()">
|
||||
<mat-icon class="chevron-icon" svgIcon="red:nav-next"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
@import '../../../../../assets/styles/red-variables';
|
||||
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -20,3 +22,62 @@
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
background: $white;
|
||||
color: $grey-7;
|
||||
border: 1px solid $grey-7;
|
||||
border-radius: 8px;
|
||||
padding: 6px 2px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> div {
|
||||
height: 16px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.separator {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.page-number-input {
|
||||
-moz-appearance: textfield;
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
color: $grey-7;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid $grey-7;
|
||||
}
|
||||
}
|
||||
|
||||
.chevron-icon {
|
||||
height: 16px;
|
||||
transform: rotate(-90deg);
|
||||
cursor: pointer;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
|
||||
&:hover {
|
||||
color: $grey-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,10 @@ import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { translateQuads } from '@utils/pdf-coordinates';
|
||||
import { BASE_HREF } from '../../../../tokens';
|
||||
import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service';
|
||||
import { stampPDFPage } from '../../../../utils/page-stamper';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
import TextTool = Tools.TextTool;
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-pdf-viewer',
|
||||
@ -48,6 +51,10 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
@Output() viewerReady = new EventEmitter<WebViewerInstance>();
|
||||
@Output() annotationsChanged = new EventEmitter<AnnotationWrapper>();
|
||||
@ViewChild('viewer', { static: true }) viewer: ElementRef;
|
||||
@ViewChild('compareFileInput', { static: true }) compareFileInput: ElementRef;
|
||||
|
||||
viewMode: 'STANDARD' | 'COMPARE' = 'STANDARD';
|
||||
|
||||
instance: WebViewerInstance;
|
||||
private _selectedText = '';
|
||||
private readonly _allowedKeyboardShortcuts = ['+', '-', 'p', 'r', 'Escape'];
|
||||
@ -56,11 +63,13 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
@Inject(BASE_HREF) private readonly _baseHref: string,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||
private readonly _projectsDialogService: DossiersDialogService,
|
||||
private readonly _ngZone: NgZone,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _annotationDrawService: AnnotationDrawService,
|
||||
private readonly _annotationActionsService: AnnotationActionsService,
|
||||
private readonly _appConfigService: AppConfigService
|
||||
private readonly _appConfigService: AppConfigService,
|
||||
private readonly _appLoadStateService: LoadingService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -114,7 +123,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
this.instance.annotManager.deselectAnnotations(ann);
|
||||
}
|
||||
|
||||
navigateToPage(pageNumber: number) {
|
||||
navigateToPage(pageNumber) {
|
||||
const activePage = this.instance.docViewer.getCurrentPage();
|
||||
if (activePage !== pageNumber) {
|
||||
this.instance.docViewer.displayPageLocation(pageNumber, 0, 0);
|
||||
@ -124,10 +133,11 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
setInitialViewerState() {
|
||||
// viewer init
|
||||
this.instance.setFitMode('FitPage');
|
||||
|
||||
const instanceDisplayMode = this.instance.docViewer
|
||||
.getDisplayModeManager()
|
||||
.getDisplayMode();
|
||||
instanceDisplayMode.mode = 'Single';
|
||||
instanceDisplayMode.mode = this.viewMode === 'STANDARD' ? 'Single' : 'Facing';
|
||||
this.instance.docViewer.getDisplayModeManager().setDisplayMode(instanceDisplayMode);
|
||||
}
|
||||
|
||||
@ -139,6 +149,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
this.instance = await WebViewer(
|
||||
{
|
||||
licenseKey: environment.licenseKey ? atob(environment.licenseKey) : null,
|
||||
fullAPI: true,
|
||||
path: this._convertPath('/assets/wv-resources'),
|
||||
css: this._convertPath('/assets/pdftron/stylesheet.css'),
|
||||
backendType: 'ems'
|
||||
@ -245,6 +256,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
|
||||
private _disableElements() {
|
||||
this.instance.disableElements([
|
||||
'pageNavOverlay',
|
||||
'menuButton',
|
||||
'selectToolButton',
|
||||
'textHighlightToolButton',
|
||||
@ -279,8 +291,36 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
img: this._convertPath('/assets/icons/general/rectangle.svg'),
|
||||
title: 'annotation.rectangle'
|
||||
});
|
||||
originalHeaderItems.splice(11, 0, {
|
||||
type: 'actionButton',
|
||||
element: 'compare',
|
||||
dataElement: 'compareButton',
|
||||
img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'),
|
||||
title: 'Compare',
|
||||
onClick: () => {
|
||||
this.compareFileInput.nativeElement.click();
|
||||
}
|
||||
});
|
||||
originalHeaderItems.splice(11, 0, {
|
||||
type: 'actionButton',
|
||||
element: 'closeCompare',
|
||||
dataElement: 'closeCompareButton',
|
||||
img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'),
|
||||
title: 'Leave Compare Mode',
|
||||
onClick: () => {
|
||||
this.closeCompareMode();
|
||||
}
|
||||
});
|
||||
originalHeaderItems.splice(13, 0, {
|
||||
type: 'divider',
|
||||
dataElement: 'compareToolDivider'
|
||||
});
|
||||
|
||||
console.log(originalHeaderItems);
|
||||
});
|
||||
|
||||
this.instance.disableElements(['closeCompareButton']);
|
||||
|
||||
this.instance.docViewer.getTool('AnnotationCreateRectangle').setStyles(() => ({
|
||||
StrokeThickness: 2,
|
||||
StrokeColor: this._annotationDrawService.getColor(this.instance, 'manual'),
|
||||
@ -365,8 +405,8 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
const mre = this._getManualRedactionEntry(quadsObject, 'Rectangle');
|
||||
// cleanup selection and button state
|
||||
this.deselectAllAnnotations();
|
||||
this.instance.disableElements(['shapeToolGroupButton']);
|
||||
this.instance.enableElements(['shapeToolGroupButton']);
|
||||
this.instance.disableElements(['shapeToolGroupButton', 'rectangleToolDivider']);
|
||||
this.instance.enableElements(['shapeToolGroupButton', 'rectangleToolDivider']);
|
||||
// dispatch event
|
||||
this.manualAnnotationRequested.emit(
|
||||
new ManualRedactionEntryWrapper(
|
||||
@ -480,6 +520,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
'add-rectangle',
|
||||
'add-false-positive',
|
||||
'shapeToolGroupButton',
|
||||
'rectangleToolDivider',
|
||||
'annotationPopup'
|
||||
]);
|
||||
if (this._selectedText.length > 2) {
|
||||
@ -493,6 +534,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
'add-false-positive',
|
||||
'add-rectangle',
|
||||
'shapeToolGroupButton',
|
||||
'rectangleToolDivider',
|
||||
'annotationPopup'
|
||||
]);
|
||||
}
|
||||
@ -591,4 +633,202 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
|
||||
private _convertPath(path: string): string {
|
||||
return this._baseHref + path;
|
||||
}
|
||||
|
||||
async uploadFile(files: any) {
|
||||
const fileToCompare = files[0];
|
||||
|
||||
const fileReader = new FileReader();
|
||||
|
||||
if (fileToCompare) {
|
||||
fileReader.onload = async () => {
|
||||
const pdfData = fileReader.result;
|
||||
|
||||
const PDFNet = this.instance.PDFNet;
|
||||
await PDFNet.initialize(
|
||||
environment.licenseKey ? atob(environment.licenseKey) : null
|
||||
);
|
||||
|
||||
const mergedDocument = await PDFNet.PDFDoc.create();
|
||||
const compareDocument = await PDFNet.PDFDoc.createFromBuffer(<any>pdfData);
|
||||
const currentDocument = await PDFNet.PDFDoc.createFromBuffer(
|
||||
await this.fileData.arrayBuffer()
|
||||
);
|
||||
|
||||
const currentDocumentPageCount = await currentDocument.getPageCount();
|
||||
const compareDocumentPageCount = await compareDocument.getPageCount();
|
||||
|
||||
const loadCompareDocument = async () => {
|
||||
this._appLoadStateService.start();
|
||||
|
||||
try {
|
||||
const maxPageCount = Math.max(
|
||||
currentDocumentPageCount,
|
||||
compareDocumentPageCount
|
||||
);
|
||||
|
||||
for (let i = 1; i <= maxPageCount; i++) {
|
||||
if (currentDocumentPageCount >= i) {
|
||||
await mergedDocument.insertPages(
|
||||
i * 2,
|
||||
currentDocument,
|
||||
i,
|
||||
i,
|
||||
PDFNet.PDFDoc.InsertFlag.e_none
|
||||
);
|
||||
} else {
|
||||
const pageToCopy = await compareDocument.getPage(i);
|
||||
const blankPage = await mergedDocument.pageCreate(
|
||||
await pageToCopy.getCropBox()
|
||||
);
|
||||
await blankPage.setRotation(await pageToCopy.getRotation());
|
||||
await mergedDocument.pagePushBack(blankPage);
|
||||
await stampPDFPage(
|
||||
mergedDocument,
|
||||
PDFNet,
|
||||
'<< Compare Placeholder Page >>',
|
||||
20,
|
||||
'courier',
|
||||
'DIAGONAL',
|
||||
33,
|
||||
'#ffb83b',
|
||||
await mergedDocument.getPageCount()
|
||||
);
|
||||
}
|
||||
if (compareDocumentPageCount >= i) {
|
||||
await mergedDocument.insertPages(
|
||||
i * 2,
|
||||
compareDocument,
|
||||
i,
|
||||
i,
|
||||
PDFNet.PDFDoc.InsertFlag.e_none
|
||||
);
|
||||
} else {
|
||||
const pageToCopy = await currentDocument.getPage(i);
|
||||
const blankPage = await mergedDocument.pageCreate(
|
||||
await pageToCopy.getCropBox()
|
||||
);
|
||||
await blankPage.setRotation(await pageToCopy.getRotation());
|
||||
await mergedDocument.pagePushBack(blankPage);
|
||||
|
||||
await stampPDFPage(
|
||||
mergedDocument,
|
||||
PDFNet,
|
||||
'<< Compare Placeholder Page >>',
|
||||
20,
|
||||
'courier',
|
||||
'DIAGONAL',
|
||||
33,
|
||||
'#ffb83b',
|
||||
await mergedDocument.getPageCount()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const buffer = await mergedDocument.saveMemoryBuffer(
|
||||
PDFNet.SDFDoc.SaveOptions.e_linearized
|
||||
);
|
||||
|
||||
const mergedDocumentBuffer = new Blob([buffer], {
|
||||
type: 'application/pdf'
|
||||
});
|
||||
|
||||
this.viewMode = 'COMPARE';
|
||||
|
||||
this.instance.loadDocument(mergedDocumentBuffer, {
|
||||
filename: this.fileStatus ? this.fileStatus.filename : 'document.pdf'
|
||||
});
|
||||
this.instance.disableElements(['compareButton']);
|
||||
this.instance.enableElements(['closeCompareButton']);
|
||||
|
||||
this.compareFileInput.nativeElement.value = null;
|
||||
|
||||
this.navigateToPage(1);
|
||||
} catch (e) {}
|
||||
this._appLoadStateService.stop();
|
||||
};
|
||||
|
||||
if (currentDocumentPageCount !== compareDocumentPageCount) {
|
||||
this._projectsDialogService.openCompareFileConfirmationDialog(
|
||||
null,
|
||||
fileToCompare.name,
|
||||
currentDocumentPageCount,
|
||||
compareDocumentPageCount,
|
||||
loadCompareDocument
|
||||
);
|
||||
} else {
|
||||
loadCompareDocument();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fileReader.readAsArrayBuffer(fileToCompare);
|
||||
}
|
||||
|
||||
get isCompareMode() {
|
||||
return this.viewMode === 'COMPARE';
|
||||
}
|
||||
|
||||
async closeCompareMode() {
|
||||
this.viewMode = 'STANDARD';
|
||||
const PDFNet = this.instance.PDFNet;
|
||||
await PDFNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
|
||||
const currentDocument = await PDFNet.PDFDoc.createFromBuffer(
|
||||
await this.fileData.arrayBuffer()
|
||||
);
|
||||
this.instance.loadDocument(currentDocument, {
|
||||
filename: this.fileStatus ? this.fileStatus.filename : 'document.pdf'
|
||||
});
|
||||
this.instance.disableElements(['closeCompareButton']);
|
||||
this.instance.enableElements(['compareButton']);
|
||||
this.navigateToPage(1);
|
||||
}
|
||||
|
||||
get currentPage() {
|
||||
try {
|
||||
return this.isCompareMode
|
||||
? Math.ceil(this.instance?.docViewer?.getCurrentPage() / 2)
|
||||
: this.instance?.docViewer?.getCurrentPage();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
get totalPages() {
|
||||
try {
|
||||
return this.isCompareMode
|
||||
? Math.ceil(this.instance?.docViewer?.getPageCount() / 2)
|
||||
: this.instance?.docViewer?.getPageCount();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private get _currentInternalPage() {
|
||||
return this.instance?.docViewer?.getCurrentPage();
|
||||
}
|
||||
|
||||
private get _totalInternalPages() {
|
||||
return this.instance?.docViewer?.getPageCount();
|
||||
}
|
||||
|
||||
previousPage() {
|
||||
if (this._currentInternalPage > 1) {
|
||||
this.navigateToPage(Math.max(this._currentInternalPage - this.paginationOffset, 1));
|
||||
}
|
||||
}
|
||||
|
||||
nextPage() {
|
||||
if (this._currentInternalPage < this._totalInternalPages) {
|
||||
this.navigateToPage(
|
||||
Math.min(
|
||||
this._currentInternalPage + this.paginationOffset,
|
||||
this._totalInternalPages
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get paginationOffset() {
|
||||
return this.isCompareMode ? 2 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
private _instance: WebViewerInstance;
|
||||
private _lastPage: string;
|
||||
@ViewChild('fileWorkloadComponent') private _workloadComponent: FileWorkloadComponent;
|
||||
@ViewChild(PdfViewerComponent) private _viewerComponent: PdfViewerComponent;
|
||||
@ViewChild(PdfViewerComponent) viewerComponent: PdfViewerComponent;
|
||||
|
||||
constructor(
|
||||
readonly appStateService: AppStateService,
|
||||
@ -127,7 +127,11 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
}
|
||||
|
||||
get activeViewerPage() {
|
||||
return this._instance?.docViewer?.getCurrentPage();
|
||||
return this.viewerComponent?.viewMode === 'STANDARD'
|
||||
? this._instance?.docViewer?.getCurrentPage()
|
||||
: this._instance?.docViewer?.getCurrentPage() % 2 === 0
|
||||
? this._instance?.docViewer?.getCurrentPage() / 2
|
||||
: (this._instance?.docViewer?.getCurrentPage() + 1) / 2;
|
||||
}
|
||||
|
||||
get canSwitchToRedactedView() {
|
||||
@ -343,18 +347,20 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
| { annotations: AnnotationWrapper[]; multiSelect: boolean }
|
||||
) {
|
||||
if (annotations) {
|
||||
this._viewerComponent.selectAnnotations(annotations);
|
||||
this.viewerComponent.selectAnnotations(annotations);
|
||||
} else {
|
||||
this._viewerComponent.deselectAllAnnotations();
|
||||
this.viewerComponent.deselectAllAnnotations();
|
||||
}
|
||||
}
|
||||
|
||||
deselectAnnotations(annotations: AnnotationWrapper[]) {
|
||||
this._viewerComponent.deselectAnnotations(annotations);
|
||||
this.viewerComponent.deselectAnnotations(annotations);
|
||||
}
|
||||
|
||||
selectPage(pageNumber: number) {
|
||||
this._viewerComponent.navigateToPage(pageNumber);
|
||||
this.viewerComponent.navigateToPage(
|
||||
this.viewerComponent.isCompareMode ? pageNumber * 2 - 1 : pageNumber
|
||||
);
|
||||
this._workloadComponent?.scrollAnnotationsToPage(pageNumber, 'always');
|
||||
}
|
||||
|
||||
@ -443,6 +449,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
viewerReady($event: WebViewerInstance) {
|
||||
this._instance = $event;
|
||||
this._cleanupAndRedrawManualAnnotations();
|
||||
this._updateCanPerformActions();
|
||||
this.viewReady = true;
|
||||
// Go to initial page from query params
|
||||
const pageNumber = this._lastPage || this._activatedRoute.snapshot.queryParams.page;
|
||||
@ -586,7 +593,9 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
|
||||
private _updateCanPerformActions() {
|
||||
this.canPerformAnnotationActions =
|
||||
this.permissionsService.canPerformAnnotationActions() && this.viewMode === 'STANDARD';
|
||||
this.permissionsService.canPerformAnnotationActions() &&
|
||||
this.viewMode === 'STANDARD' &&
|
||||
!this.viewerComponent?.isCompareMode;
|
||||
}
|
||||
|
||||
private async _loadFileData(performUpdate: boolean = false): Promise<void> {
|
||||
@ -626,7 +635,8 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
this._annotationDrawService.drawAnnotations(
|
||||
this._instance,
|
||||
this.annotationData.allAnnotations,
|
||||
this.hideSkipped
|
||||
this.hideSkipped,
|
||||
this.viewerComponent.isCompareMode
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -650,7 +660,8 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
this._annotationDrawService.drawAnnotations(
|
||||
this._instance,
|
||||
newPageAnnotations,
|
||||
this.hideSkipped
|
||||
this.hideSkipped,
|
||||
this.viewerComponent.isCompareMode
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -22,11 +22,14 @@ export class AnnotationDrawService {
|
||||
drawAnnotations(
|
||||
activeViewer: WebViewerInstance,
|
||||
annotationWrappers: AnnotationWrapper[],
|
||||
hideSkipped: boolean = false
|
||||
hideSkipped: boolean = false,
|
||||
compareMode: boolean = false
|
||||
) {
|
||||
const annotations = [];
|
||||
annotationWrappers.forEach(annotation => {
|
||||
annotations.push(this.computeAnnotation(activeViewer, annotation, hideSkipped));
|
||||
annotations.push(
|
||||
this.computeAnnotation(activeViewer, annotation, hideSkipped, compareMode)
|
||||
);
|
||||
});
|
||||
|
||||
const annotationManager = activeViewer.annotManager;
|
||||
@ -91,9 +94,12 @@ export class AnnotationDrawService {
|
||||
computeAnnotation(
|
||||
activeViewer: WebViewerInstance,
|
||||
annotationWrapper: AnnotationWrapper,
|
||||
hideSkipped: boolean = false
|
||||
hideSkipped: boolean = false,
|
||||
compareMode: boolean = false
|
||||
) {
|
||||
const pageNumber = annotationWrapper.pageNumber;
|
||||
const pageNumber = compareMode
|
||||
? annotationWrapper.pageNumber * 2 - 1
|
||||
: annotationWrapper.pageNumber;
|
||||
const highlight = new activeViewer.Annotations.TextHighlightAnnotation();
|
||||
highlight.PageNumber = pageNumber;
|
||||
highlight.StrokeColor = this.getColor(
|
||||
|
||||
@ -363,4 +363,37 @@ export class DossiersDialogService {
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
openCompareFileConfirmationDialog(
|
||||
$event: MouseEvent,
|
||||
fileName: string,
|
||||
currentDocumentPageCount: number,
|
||||
compareDocumentPageCount: number,
|
||||
callback?: Function
|
||||
): MatDialogRef<ConfirmationDialogComponent> {
|
||||
$event?.stopPropagation();
|
||||
|
||||
const ref = this._dialog.open(ConfirmationDialogComponent, {
|
||||
...dialogConfig,
|
||||
data: new ConfirmationDialogInput({
|
||||
title: 'confirmation-dialog.compare-file.title',
|
||||
question: 'confirmation-dialog.compare-file.question',
|
||||
translateParams: {
|
||||
fileName: fileName,
|
||||
currentDocumentPageCount,
|
||||
compareDocumentPageCount
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
ref.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
|
||||
63
apps/red-ui/src/app/utils/page-stamper.ts
Normal file
63
apps/red-ui/src/app/utils/page-stamper.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { hexToRgb } from './functions';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
export async function stampPDFPage(
|
||||
document: any,
|
||||
PDFNet: any,
|
||||
text: string,
|
||||
fontSize: number,
|
||||
fontType: string,
|
||||
orientation: string,
|
||||
opacity: number,
|
||||
color: string,
|
||||
page: number
|
||||
) {
|
||||
await PDFNet.runWithCleanup(
|
||||
async () => {
|
||||
await document.lock();
|
||||
|
||||
const pageSet = await PDFNet.PageSet.createSinglePage(page);
|
||||
|
||||
await PDFNet.Stamper.deleteStamps(document, pageSet);
|
||||
|
||||
const rgbColor = hexToRgb(color);
|
||||
|
||||
const stamper = await PDFNet.Stamper.create(3, fontSize, 0);
|
||||
await stamper.setFontColor(
|
||||
await PDFNet.ColorPt.init(rgbColor.r / 255, rgbColor.g / 255, rgbColor.b / 255)
|
||||
);
|
||||
await stamper.setOpacity(opacity / 100);
|
||||
|
||||
switch (orientation) {
|
||||
case 'VERTICAL':
|
||||
await stamper.setAlignment(0, 0);
|
||||
await stamper.setRotation(-90);
|
||||
break;
|
||||
case 'HORIZONTAL':
|
||||
break;
|
||||
case 'DIAGONAL':
|
||||
default:
|
||||
await stamper.setAlignment(0, 0);
|
||||
await stamper.setRotation(-45);
|
||||
}
|
||||
|
||||
const font = await PDFNet.Font.createAndEmbed(document, convertFont(fontType));
|
||||
await stamper.setFont(font);
|
||||
await stamper.setTextAlignment(0);
|
||||
await stamper.stampText(document, text, pageSet);
|
||||
},
|
||||
environment.licenseKey ? atob(environment.licenseKey) : null
|
||||
);
|
||||
}
|
||||
|
||||
function convertFont(type: string): number {
|
||||
switch (type) {
|
||||
case 'times-new-roman':
|
||||
return 0;
|
||||
case 'helvetica':
|
||||
return 4;
|
||||
case 'courier':
|
||||
return 8;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
@ -678,6 +678,10 @@
|
||||
"deny": "Cancel"
|
||||
},
|
||||
"confirmation-dialog": {
|
||||
"compare-file": {
|
||||
"title": "Compare with file: {{fileName}}",
|
||||
"question": "<strong>Warning!</strong> <br><br> Number of pages does not match, current document has <strong>{{currentDocumentPageCount}} page(s)</strong>. Uploaded document has <strong>{{compareDocumentPageCount}} page(s)</strong>. <br><br> Do you wish to proceed?"
|
||||
},
|
||||
"assign-file-to-me": {
|
||||
"title": "Re-assign reviewer",
|
||||
"question": "This document is currently reviewed by someone else. Do you want to become the reviewer and assign yourself to this document?"
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
<?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 id="Styleguide-Actions" transform="translate(-62.000000, -609.000000)">
|
||||
<rect height="906" width="904" x="0" y="0"></rect>
|
||||
<g id="Group-6" transform="translate(52.000000, 599.000000)">
|
||||
<rect height="34" id="Rectangle" rx="17" width="34" x="0" y="0"></rect>
|
||||
<g fill="#868E96" fill-rule="nonzero" id="status" transform="translate(10.000000, 10.000000)">
|
||||
<path
|
||||
d="M7.7,2.38 L8.75,3.29 L6.86,5.18 L7.7,5.18 C11.2,5.18 14,8.05 14,11.48 L14,11.48 L12.6,11.48 C12.6,8.75 10.43,6.58 7.7,6.58 L7.7,6.58 L6.86,6.58 L8.75,8.47 L7.77,9.45 L4.2,5.88 L7.7,2.38 Z M3.5,2.38 L4.48,3.36 L1.96,5.88 L4.48,8.47 L3.5,9.45 L0,5.88 L3.5,2.38 Z"
|
||||
id="Back-to-review"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 18.08 18.08" style="enable-background:new 0 0 18.08 18.08;" xml:space="preserve">
|
||||
<g fill="#868E96">
|
||||
<path d="M6.504,10.283c-0.101,0-0.197,0.039-0.269,0.111c-0.071,0.07-0.111,0.167-0.111,0.269l0.008,2.55
|
||||
H1.026V2.245H8.54v1.36c0.151-0.102,0.334-0.162,0.53-0.162h0.547V1.729c0-0.188-0.153-0.342-0.342-0.342H0.342
|
||||
C0.154,1.387,0,1.54,0,1.729v12.046c0,0.189,0.153,0.343,0.342,0.343h6.664c0.097,0,0.19-0.042,0.255-0.114l0.853-0.956v-2.764
|
||||
L6.504,10.283L6.504,10.283z" />
|
||||
<path d="M5.48,9.182l2.208-1.827c0,0,0.175-0.15,0.018-0.307c-0.216-0.216-2.279-1.77-2.279-1.77
|
||||
s-0.274-0.276-0.274,0.09c0,0.367,0,0.951,0,0.951s-0.159,0-0.403,0c-0.75,0-2.212,0-2.771,0c0,0-0.131-0.004-0.131,0.166
|
||||
c0,0.169,0,1.211,0,1.448S2.005,8.13,2.005,8.13c0.575,0,1.988,0,2.765,0c0.272,0,0.448,0,0.448,0s0,0.729,0,1.029
|
||||
C5.217,9.459,5.48,9.182,5.48,9.182z" />
|
||||
<path d="M17.738,3.962H8.803c-0.188,0-0.341,0.153-0.341,0.342V16.35c0,0.188,0.153,0.342,0.341,0.342
|
||||
h6.664c0.098,0,0.19-0.042,0.255-0.114l2.271-2.542c0.057-0.063,0.087-0.144,0.087-0.227V4.305
|
||||
C18.08,4.116,17.926,3.962,17.738,3.962z M17.003,12.859l-2.037-0.001c-0.001,0-0.001,0-0.001,0c-0.101,0-0.196,0.04-0.268,0.11
|
||||
s-0.111,0.168-0.11,0.269l0.007,2.55H9.49V4.819h7.514L17.003,12.859L17.003,12.859z" />
|
||||
<path d="M12.654,12.288c0,0,0.274,0.275,0.274-0.09c0-0.367,0-0.951,0-0.951s0.158,0,0.402,0
|
||||
c0.751,0,2.212,0,2.771,0c0,0,0.132,0.005,0.132-0.165s0-1.211,0-1.449c0-0.237-0.156-0.196-0.156-0.196c-0.575,0-1.989,0-2.766,0
|
||||
c-0.271,0-0.448,0-0.448,0s0-0.728,0-1.029c0-0.301-0.262-0.023-0.262-0.023l-2.209,1.826c0,0-0.175,0.15-0.018,0.308
|
||||
C10.59,10.733,12.654,12.288,12.654,12.288z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@ -36,3 +36,13 @@
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.noselect {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Old versions of Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently
|
||||
supported by Chrome, Edge, Opera and Firefox */
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user