mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[MNT-25123] pdf annotations incorrect and or not visible (#11621)
* [MNT-25123] Fixed pdf annotations displaying * [MNT-25123] Unit tests, used variables for colors * [MNT-25123] Corrected sonar issues
This commit is contained in:
@@ -479,7 +479,8 @@
|
||||
"RESET": "Reset",
|
||||
"THUMBNAILS": "Document thumbnails",
|
||||
"THUMBNAILS_PANLEL_CLOSE": "Close thumbnails panel",
|
||||
"LOADING": "Document is loading"
|
||||
"LOADING": "Document is loading",
|
||||
"NOTE_ANNOTATION_IMG": "Note Annotation"
|
||||
},
|
||||
"PAGE_LABEL": {
|
||||
"SHOWING": "Showing",
|
||||
|
||||
@@ -25,7 +25,24 @@ export default {
|
||||
promise: new Promise((resolve) => {
|
||||
resolve({
|
||||
numPages: 6,
|
||||
getPage: () => 'fakePage'
|
||||
getPage: () =>
|
||||
Promise.resolve({
|
||||
getAnnotations: () => [
|
||||
{
|
||||
subtype: 'Text',
|
||||
name: 'NoIcon',
|
||||
id: 'R13',
|
||||
titleObj: {
|
||||
str: 'Annotation title'
|
||||
},
|
||||
contentsObj: {
|
||||
str: 'Annotation contents'
|
||||
},
|
||||
modificationDate: "D:20260202104106Z00'00",
|
||||
popupRef: 'R1'
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.adf-pdf-viewer {
|
||||
.textLayer {
|
||||
position: absolute;
|
||||
@@ -183,6 +184,30 @@
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-pdf-viewer-annotation-tooltip.popup {
|
||||
outline-color: var(--adf-pdf-viewer-annotation-tooltip-color);
|
||||
background-color: color-mix(in srgb, var(--adf-pdf-viewer-annotation-tooltip-color) 30%, var(--theme-primary-color-default-contrast));
|
||||
margin-left: 40px;
|
||||
width: fit-content;
|
||||
max-width: 300px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
.popupContent {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.textAnnotation {
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
.adf-pdf-viewer-annotation-tooltip.popup {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-hidden,
|
||||
|
||||
@@ -28,6 +28,7 @@ import { RenderingQueueServices } from '../../services/rendering-queue.services'
|
||||
import { PdfThumbListComponent } from '../pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
|
||||
import { PDFJS_MODULE, PDFJS_VIEWER_MODULE, PdfViewerComponent } from './pdf-viewer.component';
|
||||
import pdfjsLibraryMock from '../mock/pdfjs-lib.mock';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
declare const pdfjsLib: any;
|
||||
|
||||
@@ -464,8 +465,8 @@ describe('Test PdfViewer - User interaction', () => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should init the viewer with annotation mode disabled', () => {
|
||||
expect(pdfViewerSpy).toHaveBeenCalledWith(jasmine.objectContaining({ annotationMode: 0 }));
|
||||
it('should init the viewer with annotation mode enabled', () => {
|
||||
expect(pdfViewerSpy).toHaveBeenCalledWith(jasmine.objectContaining({ annotationMode: 1 }));
|
||||
});
|
||||
|
||||
it('should Total number of pages be loaded', () => {
|
||||
@@ -600,4 +601,62 @@ describe('Test PdfViewer - User interaction', () => {
|
||||
expect(component.isPanelDisabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Annotations', () => {
|
||||
const annotationImageAlt = 'Note Annotation';
|
||||
const annotationAttribute = 'data-annotation-id';
|
||||
|
||||
let annotationElement: HTMLElement;
|
||||
let annotationImageElement: HTMLImageElement;
|
||||
let documentContainer: HTMLDivElement;
|
||||
|
||||
const dispatchAnnotationLayerRenderedEvent = (): void => {
|
||||
pdfViewerSpy.calls.mostRecent().args[0].eventBus.dispatch('annotationlayerrendered', {
|
||||
pageNumber: 1,
|
||||
source: {
|
||||
div: documentContainer
|
||||
}
|
||||
});
|
||||
tick();
|
||||
};
|
||||
|
||||
const getAnnotationPopupElement = (): HTMLElement => annotationElement.querySelector('.adf-pdf-viewer-annotation-tooltip');
|
||||
|
||||
beforeEach(() => {
|
||||
documentContainer = document.createElement('div');
|
||||
annotationImageElement = document.createElement('img');
|
||||
annotationElement = document.createElement('section');
|
||||
annotationElement.setAttribute(annotationAttribute, 'R13');
|
||||
annotationElement.append(annotationImageElement);
|
||||
documentContainer.append(annotationElement);
|
||||
spyOn(TestBed.inject(TranslateService), 'instant').withArgs('ADF_VIEWER.ARIA.NOTE_ANNOTATION_IMG').and.returnValue(annotationImageAlt);
|
||||
});
|
||||
|
||||
it('should have corrected image in annotation popup', fakeAsync(() => {
|
||||
dispatchAnnotationLayerRenderedEvent();
|
||||
expect(annotationImageElement.src).toBe(
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGV' +
|
||||
'pZ2h0PSIyNCI+PHBhdGggZD0iTTIgMmgxNHYxNEgyeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0zIDNoMTJ2MTJIM3oiIGZpbGw9Ii' +
|
||||
'NmZmRiMDAiLz48cGF0aCBkPSJNNSA1aDh2OGgtOHoiIGZpbGw9IiNmZmJiMDAiLz48L3N2Zz4='
|
||||
);
|
||||
expect(annotationImageElement.alt).toBe(annotationImageAlt);
|
||||
}));
|
||||
|
||||
it('should have corrected content in annotation popup', fakeAsync(() => {
|
||||
dispatchAnnotationLayerRenderedEvent();
|
||||
expect(annotationElement.querySelector('.title').textContent).toBe('Annotation title');
|
||||
expect(annotationElement.querySelector('.popupDate').textContent).toBe('2/2/2026, 10:41:06 AM');
|
||||
expect(annotationElement.querySelector('.popupContent').textContent).toBe('Annotation contents');
|
||||
expect(getAnnotationPopupElement()).toBeDefined();
|
||||
}));
|
||||
|
||||
it('should not have corrected content', fakeAsync(() => {
|
||||
const annotationPopupElement = document.createElement('section');
|
||||
annotationPopupElement.setAttribute(annotationAttribute, 'R1');
|
||||
documentContainer.append(annotationPopupElement);
|
||||
|
||||
dispatchAnnotationLayerRenderedEvent();
|
||||
expect(getAnnotationPopupElement()).toBeNull();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import { from, Subject, switchMap } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { AppConfigService } from '../../../app-config';
|
||||
@@ -49,6 +49,7 @@ import * as pdfjsLib from 'pdfjs-dist/build/pdf.min.mjs';
|
||||
import { EventBus, PDFViewer } from 'pdfjs-dist/web/pdf_viewer.mjs';
|
||||
import { OnProgressParameters, PDFDocumentLoadingTask, PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api';
|
||||
import { IconModule } from '../../../icon/icon.module';
|
||||
import { PDFDateString } from 'pdfjs-dist';
|
||||
|
||||
export type PdfScaleMode = 'init' | 'page-actual' | 'page-width' | 'page-height' | 'page-fit' | 'auto';
|
||||
|
||||
@@ -155,6 +156,8 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
|
||||
private renderingQueueServices = inject(RenderingQueueServices);
|
||||
private appConfigService = inject(AppConfigService);
|
||||
|
||||
private readonly translateService = inject(TranslateService);
|
||||
|
||||
constructor() {
|
||||
// needed to preserve "this" context
|
||||
this.onPageChange = this.onPageChange.bind(this);
|
||||
@@ -287,7 +290,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
|
||||
viewer,
|
||||
renderingQueue: this.renderingQueueServices,
|
||||
eventBus: this.eventBus,
|
||||
annotationMode: 0
|
||||
annotationMode: 1
|
||||
});
|
||||
|
||||
// cspell: disable-next
|
||||
@@ -301,6 +304,9 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
|
||||
this.eventBus.on('pagerendered', () => {
|
||||
this.onPageRendered();
|
||||
});
|
||||
this.eventBus.on('annotationlayerrendered', (event) =>
|
||||
this.handleNotRecognizedAnnotations(pdfDocument, event.source.div, event.pageNumber)
|
||||
);
|
||||
|
||||
this.renderingQueueServices.setViewer(this.pdfViewer);
|
||||
this.pdfViewer.setDocument(pdfDocument);
|
||||
@@ -639,4 +645,71 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
|
||||
this.previousPage();
|
||||
}
|
||||
}
|
||||
|
||||
private async handleNotRecognizedAnnotations(
|
||||
pdfDocument: PDFDocumentProxy,
|
||||
documentContainer: HTMLDivElement,
|
||||
pageNumber: number
|
||||
): Promise<void> {
|
||||
const page = await pdfDocument.getPage(pageNumber);
|
||||
const annotations = await page.getAnnotations();
|
||||
annotations.forEach((annotation) => {
|
||||
if (annotation.subtype !== 'Text' || annotation.name !== 'NoIcon') {
|
||||
return;
|
||||
}
|
||||
const annotationElement = documentContainer.querySelector<HTMLElement>(`[data-annotation-id="${annotation.id}"]`);
|
||||
if (!annotationElement) {
|
||||
return;
|
||||
}
|
||||
this.correctAnnotationImage(annotationElement);
|
||||
const text: string = annotation.contentsObj?.str?.trim();
|
||||
if (!text || (annotation.popupRef && documentContainer.querySelector(`[data-annotation-id="${annotation.popupRef}"]`))) {
|
||||
return;
|
||||
}
|
||||
this.createAnnotationPopup(annotation, text, annotationElement);
|
||||
});
|
||||
}
|
||||
|
||||
private correctAnnotationImage(annotationElement: HTMLElement): void {
|
||||
const annotationImageElement = annotationElement.querySelector('img');
|
||||
if (annotationImageElement) {
|
||||
annotationImageElement.src =
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGV' +
|
||||
'pZ2h0PSIyNCI+PHBhdGggZD0iTTIgMmgxNHYxNEgyeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0zIDNoMTJ2MTJIM3oiIGZpbGw9Ii' +
|
||||
'NmZmRiMDAiLz48cGF0aCBkPSJNNSA1aDh2OGgtOHoiIGZpbGw9IiNmZmJiMDAiLz48L3N2Zz4=';
|
||||
annotationImageElement.alt = this.translateService.instant('ADF_VIEWER.ARIA.NOTE_ANNOTATION_IMG');
|
||||
}
|
||||
}
|
||||
|
||||
private createAnnotationPopup(annotation: any, text: string, annotationElement: HTMLElement): void {
|
||||
const popupElement = document.createElement('div');
|
||||
let headerElement: HTMLSpanElement;
|
||||
if (annotation.titleObj?.str) {
|
||||
headerElement = this.createAnnotationPopupHeader(annotation);
|
||||
}
|
||||
const contentElement = this.createAnnotationPopupContent(text);
|
||||
popupElement.classList.add('popup', 'adf-pdf-viewer-annotation-tooltip');
|
||||
headerElement ? popupElement.append(headerElement, contentElement) : popupElement.append(contentElement);
|
||||
annotationElement.appendChild(popupElement);
|
||||
}
|
||||
|
||||
private createAnnotationPopupHeader(annotation: any): HTMLSpanElement {
|
||||
const headerElement = document.createElement('span');
|
||||
const titleElement = document.createElement('span');
|
||||
const dateElement = document.createElement('time');
|
||||
titleElement.innerText = annotation.titleObj.str;
|
||||
titleElement.classList.add('title');
|
||||
dateElement.innerText = PDFDateString.toDateObject(annotation.modificationDate).toLocaleString();
|
||||
dateElement.classList.add('popupDate');
|
||||
headerElement.classList.add('header');
|
||||
headerElement.append(titleElement, dateElement);
|
||||
return headerElement;
|
||||
}
|
||||
|
||||
private createAnnotationPopupContent(text: string): HTMLSpanElement {
|
||||
const contentElement = document.createElement('span');
|
||||
contentElement.innerText = text;
|
||||
contentElement.classList.add('popupContent');
|
||||
return contentElement;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user