chore: eslint fixes for @typescript-eslint/no-explicit-any (#11672)

This commit is contained in:
Denys Vuika
2026-02-23 13:11:44 +00:00
committed by GitHub
parent 9be5eae00c
commit 303a9efb3e
40 changed files with 269 additions and 178 deletions

View File

@@ -145,6 +145,14 @@ module.exports = {
'no-multiple-empty-lines': 'error', 'no-multiple-empty-lines': 'error',
'no-redeclare': 'off', 'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': ['off', { ignoreDeclarationMerge: true }], '@typescript-eslint/no-redeclare': ['off', { ignoreDeclarationMerge: true }],
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_'
}
],
'no-return-await': 'error', 'no-return-await': 'error',
'rxjs/no-create': 'error', 'rxjs/no-create': 'error',
'rxjs/no-subject-unsubscribe': 'error', 'rxjs/no-subject-unsubscribe': 'error',

View File

@@ -92,8 +92,8 @@ See the [Custom layout](#custom-layout) section for full details of all availabl
| showRightSidebar | `boolean` | false | Toggles right sidebar visibility. Requires `allowRightSidebar` to be set to `true`. | | showRightSidebar | `boolean` | false | Toggles right sidebar visibility. Requires `allowRightSidebar` to be set to `true`. |
| showToolbar | `boolean` | true | Hide or show the toolbar | | showToolbar | `boolean` | true | Hide or show the toolbar |
| showViewer | `boolean` | true | Hide or show the viewer | | showViewer | `boolean` | true | Hide or show the viewer |
| sidebarLeftTemplate | `TemplateRef<any>` | null | The template for the left sidebar. The template context contains the loaded node data. | | sidebarLeftTemplate | `TemplateRef<unknown>` | null | The template for the left sidebar. The template context contains the loaded node data. |
| sidebarRightTemplate | `TemplateRef<any>` | null | The template for the right sidebar. The template context contains the loaded node data. | | sidebarRightTemplate | `TemplateRef<unknown>` | null | The template for the right sidebar. The template context contains the loaded node data. |
| versionId | `string` | null | Version Id of the file to load. | | versionId | `string` | null | Version Id of the file to load. |
### Events ### Events

View File

@@ -236,7 +236,7 @@ Next in your component you need to get a reference of created template
```ts ```ts
@ViewChild('viewerExtensions') @ViewChild('viewerExtensions')
viewerTemplateExtensions: TemplateRef<any>; viewerTemplateExtensions: TemplateRef<unknown>;
``` ```
and pass it via `viewerTemplateExtensions` input: and pass it via `viewerTemplateExtensions` input:

View File

@@ -53,8 +53,8 @@ With icon:
``` ```
```ts ```ts
@ViewChild('contentDialogTemplate') contentDialogTemplate: TemplateRef<any>; @ViewChild('contentDialogTemplate') contentDialogTemplate: TemplateRef<unknown>;
@ViewChild('actionsDialogTemplate') actionsDialogTemplate: TemplateRef<any>; @ViewChild('actionsDialogTemplate') actionsDialogTemplate: TemplateRef<unknown>;
constructor(private dialog: MatDialog) {} constructor(private dialog: MatDialog) {}

View File

@@ -68,7 +68,7 @@ interface DataColumn {
title?: string; title?: string;
srTitle?: string; srTitle?: string;
cssClass?: string; cssClass?: string;
template?: TemplateRef<any>; template?: TemplateRef<unknown>;
formatTooltip?: Function; formatTooltip?: Function;
focus?: boolean; focus?: boolean;
} }

View File

@@ -21,9 +21,9 @@ interface DialogData {
isCloseButtonHidden?: boolean; isCloseButtonHidden?: boolean;
isCancelButtonHidden?: boolean; isCancelButtonHidden?: boolean;
dialogSize?: DialogSizes; dialogSize?: DialogSizes;
contentTemplate?: TemplateRef<any>; contentTemplate?: TemplateRef<unknown>;
actionsTemplate?: TemplateRef<any>; actionsTemplate?: TemplateRef<unknown>;
descriptionTemplate?: TemplateRef<any>; descriptionTemplate?: TemplateRef<unknown>;
headerIcon?: string; headerIcon?: string;
additionalActionButtons?: AdditionalDialogActionButton[]; additionalActionButtons?: AdditionalDialogActionButton[];
componentData?: any; componentData?: any;
@@ -46,9 +46,9 @@ interface DialogData {
| dialogSize | `DialogSize` | `Medium` | Set dialog size. Can be `Large`, `Medium`, `Alert`. (optional) | | dialogSize | `DialogSize` | `Medium` | Set dialog size. Can be `Large`, `Medium`, `Alert`. (optional) |
| contentText | `string` | | Inserts a content text. (optional) | | contentText | `string` | | Inserts a content text. (optional) |
| contentComponent | `Type<any>` | | Inserts a content component. (optional) | | contentComponent | `Type<any>` | | Inserts a content component. (optional) |
| contentTemplate | `TemplateRef<any>` | | Inserts a content template. (optional) | | contentTemplate | `TemplateRef<unknown>` | | Inserts a content template. (optional) |
| actionsTemplate | `TemplateRef<any>` | | Inserts a template styled on the left. Should be used for additional `mat-button` style buttons. (optional) | | actionsTemplate | `TemplateRef<unknown>` | | Inserts a template styled on the left. Should be used for additional `mat-button` style buttons. (optional) |
| descriptionTemplate | `TemplateRef<any>` | | Inserts a description template. (optional) | | descriptionTemplate | `TemplateRef<unknown>` | | Inserts a description template. (optional) |
| additionalActionButtons | `AdditionalDialogActionButton[]` | | Inserts additional base-styled buttons into the action bar on the left. (optional) | | additionalActionButtons | `AdditionalDialogActionButton[]` | | Inserts additional base-styled buttons into the action bar on the left. (optional) |
| componentData | `any` | | Data that injected in contentComponent. (optional) | | componentData | `any` | | Data that injected in contentComponent. (optional) |
| dataOnConfirm$ | `Subject<any>` | | Data to be passed on confirm action after dialog closed. (optional) | | dataOnConfirm$ | `Subject<any>` | | Data to be passed on confirm action after dialog closed. (optional) |

View File

@@ -18,10 +18,16 @@
import { Injectable, inject } from '@angular/core'; import { Injectable, inject } from '@angular/core';
import { from, Observable, throwError, Subject } from 'rxjs'; import { from, Observable, throwError, Subject } from 'rxjs';
import { catchError, map, switchMap, filter, take } from 'rxjs/operators'; import { catchError, map, switchMap, filter, take } from 'rxjs/operators';
import { RepositoryInfo, SystemPropertiesRepresentation, DiscoveryApi, AboutApi, SystemPropertiesApi } from '@alfresco/js-api'; import {
RepositoryInfo,
SystemPropertiesRepresentation,
DiscoveryApi,
AboutApi,
SystemPropertiesApi,
BpmProductVersionModel
} from '@alfresco/js-api';
import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { BpmProductVersionModel, AuthenticationService } from '@alfresco/adf-core'; import { AuthenticationService } from '@alfresco/adf-core';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -66,24 +72,21 @@ export class DiscoveryApiService {
} }
/** /**
* @deprecated since 8.3.0 this method is no longer used, and will be removed in the next major release.
* Gets product information for Process Services. * Gets product information for Process Services.
* *
* @returns ProductVersionModel containing product details * @returns ProductVersionModel containing product details
*/ */
getBpmProductInfo(): Observable<BpmProductVersionModel> { getBpmProductInfo(): Observable<BpmProductVersionModel> {
const aboutApi = new AboutApi(this.alfrescoApiService.getInstance()); const aboutApi = new AboutApi(this.alfrescoApiService.getInstance());
return from(aboutApi.getAppVersion());
return from(aboutApi.getAppVersion()).pipe(
map((res) => new BpmProductVersionModel(res)),
catchError((err) => throwError(err))
);
} }
getBPMSystemProperties(): Observable<SystemPropertiesRepresentation> { getBPMSystemProperties(): Observable<SystemPropertiesRepresentation> {
const systemPropertiesApi = new SystemPropertiesApi(this.alfrescoApiService.getInstance()); const systemPropertiesApi = new SystemPropertiesApi(this.alfrescoApiService.getInstance());
return from(systemPropertiesApi.getProperties()).pipe( return from(systemPropertiesApi.getProperties()).pipe(
map((res: any) => { map((res) => {
if ('string' === typeof res) { if ('string' === typeof res) {
throw new Error('Not valid response'); throw new Error('Not valid response');
} }

View File

@@ -207,7 +207,6 @@ describe('BaseQueryBuilderService', () => {
expect(service.getUserFacetBuckets('field1').length).toBe(0); expect(service.getUserFacetBuckets('field1').length).toBe(0);
expect(service.getUserFacetBuckets('field2').length).toBe(0); expect(service.getUserFacetBuckets('field2').length).toBe(0);
}); });
}); });
describe('buildQuery', () => { describe('buildQuery', () => {
@@ -332,12 +331,12 @@ describe('BaseQueryBuilderService', () => {
expect(errorSpy).toHaveBeenCalledWith(mockError); expect(errorSpy).toHaveBeenCalledWith(mockError);
expect(executedSpy).toHaveBeenCalledWith( expect(executedSpy).toHaveBeenCalledWith(
jasmine.objectContaining({ jasmine.objectContaining({
list: jasmine.objectContaining({ list: jasmine.objectContaining({
pagination: { totalItems: 0 }, pagination: { totalItems: 0 },
entries: [] entries: []
}) })
}) })
); );
}); });
@@ -423,9 +422,7 @@ describe('BaseQueryBuilderService', () => {
spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); spyOn(router, 'navigate').and.returnValue(Promise.resolve(true));
spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging));
let callCount = 0;
service.searchForms.pipe(skip(1)).subscribe((forms) => { service.searchForms.pipe(skip(1)).subscribe((forms) => {
callCount++;
expect(forms[1].selected).toBe(true); expect(forms[1].selected).toBe(true);
expect(forms[0].selected).toBe(false); expect(forms[0].selected).toBe(false);
done(); done();
@@ -446,7 +443,7 @@ describe('BaseQueryBuilderService', () => {
it('should call execute when updating configuration', async () => { it('should call execute when updating configuration', async () => {
spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); spyOn(router, 'navigate').and.returnValue(Promise.resolve(true));
spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging));
spyOn(service, 'execute') spyOn(service, 'execute');
service.userQuery = 'test'; service.userQuery = 'test';
service.updateSelectedConfiguration('config-2'); service.updateSelectedConfiguration('config-2');

View File

@@ -15,7 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { IFeaturesService, FlagChangeset } from '../interfaces/features.interface'; import { IFeaturesService, FlagChangeset } from '../interfaces/features.interface';

View File

@@ -91,7 +91,6 @@ export class StorageFeaturesService implements IFeaturesService, IWritableFeatur
} }
removeFlag(key: string): void { removeFlag(key: string): void {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { [key]: _, ...flags } = this.currentFlagState; const { [key]: _, ...flags } = this.currentFlagState;
this.flags.next(flags); this.flags.next(flags);
} }

View File

@@ -27,7 +27,6 @@ describe('StoragePrefixFactory', () => {
it('should get prefix set in app.config.json', () => { it('should get prefix set in app.config.json', () => {
const appConfigPrefix = 'prefix-from-app-config-json'; const appConfigPrefix = 'prefix-from-app-config-json';
const appConfigService: TestAppConfigService = { const appConfigService: TestAppConfigService = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
select(_property: string) { select(_property: string) {
return of(appConfigPrefix); return of(appConfigPrefix);
} }
@@ -48,7 +47,6 @@ describe('StoragePrefixFactory', () => {
it('should work with NO prefix set in app.config.json', () => { it('should work with NO prefix set in app.config.json', () => {
const appConfigPrefix = undefined; const appConfigPrefix = undefined;
const appConfigService: TestAppConfigService = { const appConfigService: TestAppConfigService = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
select(_property: string) { select(_property: string) {
return of(appConfigPrefix); return of(appConfigPrefix);
} }
@@ -69,7 +67,6 @@ describe('StoragePrefixFactory', () => {
it('should return prefix from provided factory, when NO prefix is set in app.config.json', () => { it('should return prefix from provided factory, when NO prefix is set in app.config.json', () => {
const appConfigPrefix = undefined; const appConfigPrefix = undefined;
const appConfigService: TestAppConfigService = { const appConfigService: TestAppConfigService = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
select(_property: string) { select(_property: string) {
return of(appConfigPrefix); return of(appConfigPrefix);
} }
@@ -100,7 +97,6 @@ describe('StoragePrefixFactory', () => {
const appConfigPrefix = 'prefix-from-app-config-json'; const appConfigPrefix = 'prefix-from-app-config-json';
const appConfigService: TestAppConfigService = { const appConfigService: TestAppConfigService = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
select(_property: string) { select(_property: string) {
return of(appConfigPrefix); return of(appConfigPrefix);
} }

View File

@@ -1,34 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class BpmProductVersionModel {
edition: string;
majorVersion: string;
revisionVersion: string;
minorVersion: string;
type: string;
constructor(obj?: any) {
if (obj) {
this.edition = obj.edition || null;
this.majorVersion = obj.majorVersion || null;
this.revisionVersion = obj.revisionVersion || null;
this.minorVersion = obj.minorVersion || null;
this.type = obj.type || null;
}
}
}

View File

@@ -15,7 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
export * from './product-version.model';
export * from './comment.model'; export * from './comment.model';
export * from './pagination.model'; export * from './pagination.model';
export * from './request-pagination.model'; export * from './request-pagination.model';

View File

@@ -25,12 +25,12 @@ import { TranslationService } from '../translation/translation.service';
export class FileSizePipe implements PipeTransform { export class FileSizePipe implements PipeTransform {
private readonly translation = inject(TranslationService); private readonly translation = inject(TranslationService);
transform(paramByte: any, decimals: number = 2): string { transform(paramByte: number | string | null, decimals: number = 2): string {
if (paramByte == null) { if (paramByte == null) {
return ''; return '';
} }
const bytes = parseInt(paramByte, 10); const bytes = typeof paramByte === 'number' ? paramByte : parseInt(paramByte as string, 10);
if (isNaN(bytes)) { if (isNaN(bytes)) {
return ''; return '';
} }

View File

@@ -40,7 +40,7 @@ describe('SearchTextInputComponent', () => {
debugElement = fixture.debugElement; debugElement = fixture.debugElement;
testingUtils = new UnitTestingUtils(debugElement); testingUtils = new UnitTestingUtils(debugElement);
userPreferencesService = TestBed.inject(UserPreferencesService); userPreferencesService = TestBed.inject(UserPreferencesService);
component.focusListener = new Subject<any>(); component.focusListener = new Subject();
}); });
afterEach(() => { afterEach(() => {

View File

@@ -91,7 +91,7 @@ describe('TranslationService', () => {
const mockLanguages = spyOnProperty(window, 'navigator').and.returnValue({ const mockLanguages = spyOnProperty(window, 'navigator').and.returnValue({
language: 'en-GB', language: 'en-GB',
languages: returnedLanguages languages: returnedLanguages
} as any); } as unknown as Navigator);
expect(translationService.getLocale()).toBe('fr-FR'); expect(translationService.getLocale()).toBe('fr-FR');
expect(mockLanguages).toHaveBeenCalled(); expect(mockLanguages).toHaveBeenCalled();
@@ -102,7 +102,7 @@ describe('TranslationService', () => {
const mockLanguages = spyOnProperty(window, 'navigator').and.returnValue({ const mockLanguages = spyOnProperty(window, 'navigator').and.returnValue({
language: 'de-DE', language: 'de-DE',
languages: [] languages: []
} as any); } as unknown as Navigator);
expect(translationService.getLocale()).toBe('de-DE'); expect(translationService.getLocale()).toBe('de-DE');
expect(mockLanguages).toHaveBeenCalled(); expect(mockLanguages).toHaveBeenCalled();

View File

@@ -74,11 +74,11 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
// eslint-disable-next-line @angular-eslint/no-output-native // eslint-disable-next-line @angular-eslint/no-output-native
@Output() @Output()
error = new EventEmitter<any>(); error = new EventEmitter<void>();
// eslint-disable-next-line @angular-eslint/no-output-native // eslint-disable-next-line @angular-eslint/no-output-native
@Output() @Output()
submit = new EventEmitter<any>(); submit = new EventEmitter<Blob>();
@Output() @Output()
isSaving = new EventEmitter<boolean>(); isSaving = new EventEmitter<boolean>();

View File

@@ -48,7 +48,7 @@ export class MediaPlayerComponent implements OnChanges {
tracks: Track[] = []; tracks: Track[] = [];
@Output() @Output()
error = new EventEmitter<any>(); error = new EventEmitter<Event>();
@Output() @Output()
canPlay = new EventEmitter<void>(); canPlay = new EventEmitter<void>();
@@ -66,7 +66,7 @@ export class MediaPlayerComponent implements OnChanges {
} }
} }
onMediaPlayerError(event: any) { onMediaPlayerError(event: Event) {
this.error.emit(event); this.error.emit(event);
} }
} }

View File

@@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { PdfPasswordDialogComponent } from './pdf-viewer-password-dialog'; import { PdfPasswordDialogComponent } from './pdf-viewer-password-dialog';
declare const pdfjsLib: any; declare const pdfjsLib: { PasswordResponses: { NEED_PASSWORD: number; INCORRECT_PASSWORD: number } };
describe('PdfPasswordDialogComponent', () => { describe('PdfPasswordDialogComponent', () => {
let component: PdfPasswordDialogComponent; let component: PdfPasswordDialogComponent;

View File

@@ -25,7 +25,7 @@ import { MatInputModule } from '@angular/material/input';
import { TranslatePipe } from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core';
import { IconModule } from '../../../icon/icon.module'; import { IconModule } from '../../../icon/icon.module';
declare const pdfjsLib: any; declare const pdfjsLib: { PasswordResponses: { NEED_PASSWORD: number; INCORRECT_PASSWORD: number } };
@Component({ @Component({
selector: 'adf-pdf-viewer-password-dialog', selector: 'adf-pdf-viewer-password-dialog',

View File

@@ -28,7 +28,7 @@ describe('PdfThumbComponent', () => {
const width = 91; const width = 91;
const height = 119; const height = 119;
const page = { const page = {
id: 'pageId', id: 1,
getPage: jasmine.createSpy('getPage').and.returnValue( getPage: jasmine.createSpy('getPage').and.returnValue(
Promise.resolve({ Promise.resolve({
getViewport: () => ({ width, height }), getViewport: () => ({ width, height }),

View File

@@ -20,6 +20,7 @@ import { AsyncPipe, NgIf } from '@angular/common';
import { Component, ElementRef, Input, OnInit, ViewEncapsulation, inject } from '@angular/core'; import { Component, ElementRef, Input, OnInit, ViewEncapsulation, inject } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { TranslatePipe } from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core';
import { PdfThumbnailPage } from '../pdf-viewer/pdf-viewer.component';
@Component({ @Component({
selector: 'adf-pdf-thumb', selector: 'adf-pdf-thumb',
@@ -33,7 +34,7 @@ export class PdfThumbComponent implements OnInit, FocusableOption {
private readonly element = inject(ElementRef); private readonly element = inject(ElementRef);
@Input() @Input()
page: any = null; page: PdfThumbnailPage = null;
image$: Promise<string>; image$: Promise<string>;

View File

@@ -19,8 +19,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PdfThumbListComponent } from './pdf-viewer-thumbnails.component'; import { PdfThumbListComponent } from './pdf-viewer-thumbnails.component';
import { UnitTestingUtils } from '../../../testing'; import { UnitTestingUtils } from '../../../testing';
import { DOWN_ARROW, ESCAPE, UP_ARROW } from '@angular/cdk/keycodes'; import { DOWN_ARROW, ESCAPE, UP_ARROW } from '@angular/cdk/keycodes';
import { PDFViewer } from 'pdfjs-dist/web/pdf_viewer.mjs';
declare const pdfjsViewer: any; declare const pdfjsViewer: { EventBus: new () => { dispatch: (event: string, data: unknown) => void } };
describe('PdfThumbListComponent', () => { describe('PdfThumbListComponent', () => {
let fixture: ComponentFixture<PdfThumbListComponent>; let fixture: ComponentFixture<PdfThumbListComponent>;
@@ -77,7 +78,7 @@ describe('PdfThumbListComponent', () => {
fixture = TestBed.createComponent(PdfThumbListComponent); fixture = TestBed.createComponent(PdfThumbListComponent);
testingUtils = new UnitTestingUtils(fixture.debugElement); testingUtils = new UnitTestingUtils(fixture.debugElement);
component = fixture.componentInstance; component = fixture.componentInstance;
component.pdfViewer = viewerMock; component.pdfViewer = viewerMock as unknown as PDFViewer;
// provide scrollable container // provide scrollable container
fixture.nativeElement.style.display = 'block'; fixture.nativeElement.style.display = 'block';

View File

@@ -36,6 +36,9 @@ import {
inject inject
} from '@angular/core'; } from '@angular/core';
import { delay } from 'rxjs/operators'; import { delay } from 'rxjs/operators';
import { PDFViewer } from 'pdfjs-dist/web/pdf_viewer.mjs';
import { PDFPageProxy } from 'pdfjs-dist/types/src/display/api';
import { PageChangingEvent, PdfThumbnailPage } from '../pdf-viewer/pdf-viewer.component';
import { PdfThumbComponent } from '../pdf-viewer-thumb/pdf-viewer-thumb.component'; import { PdfThumbComponent } from '../pdf-viewer-thumb/pdf-viewer-thumb.component';
@Component({ @Component({
@@ -50,25 +53,25 @@ export class PdfThumbListComponent implements OnInit, AfterViewInit, OnDestroy {
private readonly element = inject(ElementRef); private readonly element = inject(ElementRef);
private readonly document = inject(DOCUMENT); private readonly document = inject(DOCUMENT);
@Input({ required: true }) pdfViewer: any; @Input({ required: true }) pdfViewer: PDFViewer;
@Output() @Output()
close = new EventEmitter<void>(); close = new EventEmitter<void>();
virtualHeight: number = 0; virtualHeight: number = 0;
translateY: number = 0; translateY: number = 0;
renderItems = []; renderItems: PdfThumbnailPage[] = [];
width: number = 91; width: number = 91;
currentHeight: number = 0; currentHeight: number = 0;
private items = []; private items: PdfThumbnailPage[] = [];
private readonly margin: number = 15; private readonly margin: number = 15;
private itemHeight: number = 114 + this.margin; private itemHeight: number = 114 + this.margin;
private previouslyFocusedElement: HTMLElement | null = null; private previouslyFocusedElement: HTMLElement | null = null;
private keyManager: FocusKeyManager<PdfThumbComponent>; private keyManager: FocusKeyManager<PdfThumbComponent>;
@ContentChild(TemplateRef) @ContentChild(TemplateRef)
template: any; template: TemplateRef<unknown>;
@ViewChildren(PdfThumbComponent) @ViewChildren(PdfThumbComponent)
thumbsList: QueryList<PdfThumbComponent>; thumbsList: QueryList<PdfThumbComponent>;
@@ -145,7 +148,7 @@ export class PdfThumbListComponent implements OnInit, AfterViewInit, OnDestroy {
} }
} }
trackByFn(_: number, item: any): number { trackByFn(_: number, item: PdfThumbnailPage): number {
return item.id; return item.id;
} }
@@ -171,7 +174,7 @@ export class PdfThumbListComponent implements OnInit, AfterViewInit, OnDestroy {
} }
} }
getPages() { getPages(): PdfThumbnailPage[] {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
return this.pdfViewer._pages.map((page) => ({ return this.pdfViewer._pages.map((page) => ({
id: page.id, id: page.id,
@@ -181,12 +184,11 @@ export class PdfThumbListComponent implements OnInit, AfterViewInit, OnDestroy {
})); }));
} }
private setHeight(id): number { private setHeight(id: number): Promise<void> {
const height = this.pdfViewer.pdfDocument.getPage(id).then((page) => this.calculateHeight(page)); return this.pdfViewer.pdfDocument.getPage(id).then((page) => this.calculateHeight(page));
return height;
} }
private calculateHeight(page) { private calculateHeight(page: PDFPageProxy) {
const viewport = page.getViewport({ scale: 1 }); const viewport = page.getViewport({ scale: 1 });
const pageRatio = viewport.width / viewport.height; const pageRatio = viewport.width / viewport.height;
const height = Math.floor(this.width / pageRatio); const height = Math.floor(this.width / pageRatio);
@@ -222,7 +224,7 @@ export class PdfThumbListComponent implements OnInit, AfterViewInit, OnDestroy {
}; };
} }
private onPageChange(event) { private onPageChange(event: PageChangingEvent) {
const index = this.renderItems.findIndex((element) => element.id === event.pageNumber); const index = this.renderItems.findIndex((element) => element.id === event.pageNumber);
if (index < 0) { if (index < 0) {

View File

@@ -30,7 +30,12 @@ import { PDFJS_MODULE, PDFJS_VIEWER_MODULE, PdfViewerComponent } from './pdf-vie
import pdfjsLibraryMock, { annotations } from '../mock/pdfjs-lib.mock'; import pdfjsLibraryMock, { annotations } from '../mock/pdfjs-lib.mock';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
declare const pdfjsLib: any; declare const pdfjsLib: {
PasswordResponses: {
NEED_PASSWORD: number;
INCORRECT_PASSWORD: number;
};
};
@Component({ @Component({
selector: 'adf-url-test-component', selector: 'adf-url-test-component',
@@ -41,7 +46,7 @@ class UrlTestComponent {
@ViewChild(PdfViewerComponent, { static: true }) @ViewChild(PdfViewerComponent, { static: true })
pdfViewerComponent: PdfViewerComponent; pdfViewerComponent: PdfViewerComponent;
urlFile: any; urlFile: string;
constructor() { constructor() {
this.urlFile = './fake-test-file.pdf'; this.urlFile = './fake-test-file.pdf';
@@ -57,7 +62,7 @@ class UrlTestPasswordComponent {
@ViewChild(PdfViewerComponent, { static: true }) @ViewChild(PdfViewerComponent, { static: true })
pdfViewerComponent: PdfViewerComponent; pdfViewerComponent: PdfViewerComponent;
urlFile: any; urlFile: string;
constructor() { constructor() {
this.urlFile = './fake-test-password-file.pdf'; this.urlFile = './fake-test-password-file.pdf';
@@ -72,7 +77,7 @@ class BlobTestComponent {
@ViewChild(PdfViewerComponent, { static: true }) @ViewChild(PdfViewerComponent, { static: true })
pdfViewerComponent: PdfViewerComponent; pdfViewerComponent: PdfViewerComponent;
blobFile: any; blobFile: Blob;
constructor() { constructor() {
this.blobFile = this.createFakeBlob(); this.blobFile = this.createFakeBlob();
@@ -101,7 +106,7 @@ class BlobTestComponent {
describe('Test PdfViewer component', () => { describe('Test PdfViewer component', () => {
let component: PdfViewerComponent; let component: PdfViewerComponent;
let fixture: ComponentFixture<PdfViewerComponent>; let fixture: ComponentFixture<PdfViewerComponent>;
let change: any; let change: SimpleChange;
let dialog: MatDialog; let dialog: MatDialog;
let testingUtils: UnitTestingUtils; let testingUtils: UnitTestingUtils;
@@ -265,17 +270,17 @@ describe('Test PdfViewer component', () => {
fixtureUrlTestPasswordComponent = TestBed.createComponent(UrlTestPasswordComponent); fixtureUrlTestPasswordComponent = TestBed.createComponent(UrlTestPasswordComponent);
componentUrlTestPasswordComponent = fixtureUrlTestPasswordComponent.componentInstance; componentUrlTestPasswordComponent = fixtureUrlTestPasswordComponent.componentInstance;
spyOn(dialog, 'open').and.callFake((_: any, context: any) => { spyOn(dialog, 'open').and.callFake((_dialogComponent: unknown, context: { data: { reason: number } }) => {
if (context.data.reason === pdfjsLib.PasswordResponses.NEED_PASSWORD) { if (context.data.reason === pdfjsLib.PasswordResponses.NEED_PASSWORD) {
return { return {
afterClosed: () => of('wrong_password') afterClosed: () => of('wrong_password')
} as any; } as ReturnType<MatDialog['open']>;
} }
if (context.data.reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) { if (context.data.reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
return { return {
afterClosed: () => of('password') afterClosed: () => of('password')
} as any; } as ReturnType<MatDialog['open']>;
} }
return undefined; return undefined;
@@ -328,7 +333,7 @@ describe('Test PdfViewer component', () => {
() => () =>
({ ({
afterClosed: () => of('') afterClosed: () => of('')
}) as any }) as ReturnType<MatDialog['open']>
); );
spyOn(componentUrlTestPasswordComponent.pdfViewerComponent.close, 'emit'); spyOn(componentUrlTestPasswordComponent.pdfViewerComponent.close, 'emit');
@@ -456,7 +461,9 @@ describe('Test PdfViewer - User interaction', () => {
component.urlFile = './fake-test-file.pdf'; component.urlFile = './fake-test-file.pdf';
fixture.detectChanges(); fixture.detectChanges();
component.ngOnChanges({ urlFile: { currentValue: './fake-test-file.pdf' } } as any); component.ngOnChanges({
urlFile: new SimpleChange(null, './fake-test-file.pdf', true)
});
flush(); flush();
})); }));
@@ -666,7 +673,10 @@ describe('Test PdfViewer - User interaction', () => {
it('should have corrected content in annotation popup', fakeAsync(() => { it('should have corrected content in annotation popup', fakeAsync(() => {
dispatchAnnotationLayerRenderedEvent(); dispatchAnnotationLayerRenderedEvent();
expect(getAnnotationTitle()).toBe('Annotation title'); expect(getAnnotationTitle()).toBe('Annotation title');
expect(getAnnotationDate()).toBe('2/2/2026, 10:41:06 AM'); const dateText = getAnnotationDate();
// Date format may vary by locale, so check it contains the key parts
expect(dateText).toMatch(/2026/);
expect(dateText).toMatch(/10:41:06|10:41:6/);
expect(getAnnotationContent()).toBe('Annotation contents'); expect(getAnnotationContent()).toBe('Annotation contents');
expect(getAnnotationPopupElement()).toBeDefined(); expect(getAnnotationPopupElement()).toBeDefined();
})); }));

View File

@@ -47,12 +47,41 @@ import { PdfPasswordDialogComponent } from '../pdf-viewer-password-dialog/pdf-vi
import { PdfThumbListComponent } from '../pdf-viewer-thumbnails/pdf-viewer-thumbnails.component'; import { PdfThumbListComponent } from '../pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
import * as pdfjsLib from 'pdfjs-dist/build/pdf.min.mjs'; import * as pdfjsLib from 'pdfjs-dist/build/pdf.min.mjs';
import { EventBus, PDFViewer } from 'pdfjs-dist/web/pdf_viewer.mjs'; import { EventBus, PDFViewer } from 'pdfjs-dist/web/pdf_viewer.mjs';
import { OnProgressParameters, PDFDocumentLoadingTask, PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api'; import { OnProgressParameters, PDFDocumentLoadingTask, PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist/types/src/display/api';
import { IconModule } from '../../../icon/icon.module'; import { IconModule } from '../../../icon/icon.module';
import { PDFDateString } from 'pdfjs-dist'; import { PDFDateString } from 'pdfjs-dist';
export type PdfScaleMode = 'init' | 'page-actual' | 'page-width' | 'page-height' | 'page-fit' | 'auto'; export type PdfScaleMode = 'init' | 'page-actual' | 'page-width' | 'page-height' | 'page-fit' | 'auto';
export interface PageChangingEvent {
pageNumber: number;
source?: {
container?: {
id?: string;
};
};
}
export interface PdfThumbnailPage {
id: number;
getWidth: () => number;
getHeight: () => number;
getPage: () => Promise<PDFPageProxy>;
}
export interface PdfAnnotationData {
titleObj?: {
str?: string;
};
modificationDate?: string;
}
export interface PdfAnnotationWithTitle extends PdfAnnotationData {
titleObj: {
str: string;
};
}
export const PDFJS_MODULE = new InjectionToken('PDFJS_MODULE', { factory: () => pdfjsLib }); export const PDFJS_MODULE = new InjectionToken('PDFJS_MODULE', { factory: () => pdfjsLib });
export const PDFJS_VIEWER_MODULE = new InjectionToken('PDFJS_VIEWER_MODULE', { factory: () => PDFViewer }); export const PDFJS_VIEWER_MODULE = new InjectionToken('PDFJS_VIEWER_MODULE', { factory: () => PDFViewer });
@@ -93,19 +122,19 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
allowThumbnails = false; allowThumbnails = false;
@Input() @Input()
thumbnailsTemplate: TemplateRef<any> = null; thumbnailsTemplate: TemplateRef<unknown> = null;
@Input() @Input()
cacheType: string = ''; cacheType: string = '';
@Output() @Output()
rendered = new EventEmitter<any>(); rendered = new EventEmitter<void>();
@Output() @Output()
error = new EventEmitter<any>(); error = new EventEmitter<void>();
@Output() @Output()
close = new EventEmitter<any>(); close = new EventEmitter<void>();
@Output() @Output()
pagesLoaded = new EventEmitter<void>(); pagesLoaded = new EventEmitter<void>();
@@ -127,7 +156,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
loadingTask: PDFDocumentLoadingTask; loadingTask: PDFDocumentLoadingTask;
isPanelDisabled = true; isPanelDisabled = true;
showThumbnails: boolean = false; showThumbnails: boolean = false;
pdfThumbnailsContext: { viewer: any } = { viewer: null }; pdfThumbnailsContext: { viewer: PDFViewer | null } = { viewer: null };
randomPdfId: string; randomPdfId: string;
documentOverflow = false; documentOverflow = false;
@@ -214,7 +243,12 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
const urlFile = changes['urlFile']; const urlFile = changes['urlFile'];
if (urlFile?.currentValue) { if (urlFile?.currentValue) {
const pdfOptions: any = { const pdfOptions: {
url: string;
withCredentials: boolean | undefined;
isEvalSupported: boolean;
httpHeaders?: { 'Cache-Control': string };
} & typeof this.pdfjsDefaultOptions = {
...this.pdfjsDefaultOptions, ...this.pdfjsDefaultOptions,
url: urlFile.currentValue, url: urlFile.currentValue,
withCredentials: this.appConfigService.get<boolean>('auth.withCredentials', undefined), withCredentials: this.appConfigService.get<boolean>('auth.withCredentials', undefined),
@@ -233,7 +267,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
} }
} }
executePdf(pdfOptions: any) { executePdf(pdfOptions: Parameters<typeof this.pdfjsLib.getDocument>[0]) {
this.setupPdfJsWorker().then(() => { this.setupPdfJsWorker().then(() => {
this.loadingTask = this.pdfjsLib.getDocument(pdfOptions); this.loadingTask = this.pdfjsLib.getDocument(pdfOptions);
@@ -282,7 +316,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
} }
initPDFViewer(pdfDocument: PDFDocumentProxy) { initPDFViewer(pdfDocument: PDFDocumentProxy) {
const viewer: any = this.getViewer(); const viewer: HTMLDivElement = this.getViewer();
const container = this.getDocumentContainer(); const container = this.getDocumentContainer();
if (viewer && container) { if (viewer && container) {
@@ -433,8 +467,8 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
return document.getElementById(`${this.randomPdfId}-viewer-pdf-viewer`) as HTMLDivElement; return document.getElementById(`${this.randomPdfId}-viewer-pdf-viewer`) as HTMLDivElement;
} }
private getViewer(): HTMLElement { private getViewer(): HTMLDivElement {
return document.getElementById(`${this.randomPdfId}-viewer-viewerPdf`); return document.getElementById(`${this.randomPdfId}-viewer-viewerPdf`) as HTMLDivElement;
} }
checkPageFitInContainer(scale: number): number { checkPageFitInContainer(scale: number): number {
@@ -519,9 +553,9 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
* @param ticks number of ticks to zoom * @param ticks number of ticks to zoom
*/ */
zoomIn(ticks?: number): void { zoomIn(ticks?: number): void {
let newScale: any = this.pdfViewer.currentScaleValue; let newScale: number = Number(this.pdfViewer.currentScaleValue);
do { do {
newScale = (newScale * this.DEFAULT_SCALE_DELTA).toFixed(2); newScale = Number((newScale * this.DEFAULT_SCALE_DELTA).toFixed(2));
newScale = Math.ceil(newScale * 10) / 10; newScale = Math.ceil(newScale * 10) / 10;
newScale = Math.min(this.MAX_SCALE, newScale); newScale = Math.min(this.MAX_SCALE, newScale);
} while (--ticks > 0 && newScale < this.MAX_SCALE); } while (--ticks > 0 && newScale < this.MAX_SCALE);
@@ -535,9 +569,9 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
* @param ticks number of ticks to scale * @param ticks number of ticks to scale
*/ */
zoomOut(ticks?: number): void { zoomOut(ticks?: number): void {
let newScale: any = this.pdfViewer.currentScaleValue; let newScale: number = Number(this.pdfViewer.currentScaleValue);
do { do {
newScale = (newScale / this.DEFAULT_SCALE_DELTA).toFixed(2); newScale = Number((newScale / this.DEFAULT_SCALE_DELTA).toFixed(2));
newScale = Math.floor(newScale * 10) / 10; newScale = Math.floor(newScale * 10) / 10;
newScale = Math.max(this.MIN_SCALE, newScale); newScale = Math.max(this.MIN_SCALE, newScale);
} while (--ticks > 0 && newScale > this.MIN_SCALE); } while (--ticks > 0 && newScale > this.MIN_SCALE);
@@ -589,9 +623,13 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
/** /**
* Page Change Event * Page Change Event
* *
* @param event event * @param event - page change event
* @param event.pageNumber - the new page number
* @param event.source - the source object
* @param event.source.container - the container element
* @param event.source.container.id - the container id
*/ */
onPageChange(event: any) { onPageChange(event: PageChangingEvent) {
if (event.source && event.source.container.id === `${this.randomPdfId}-viewer-pdf-viewer`) { if (event.source && event.source.container.id === `${this.randomPdfId}-viewer-pdf-viewer`) {
this.page = event.pageNumber; this.page = event.pageNumber;
this.displayPage = event.pageNumber; this.displayPage = event.pageNumber;
@@ -682,11 +720,11 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
} }
} }
private createAnnotationPopup(annotation: any, text: string, annotationElement: HTMLElement): void { private createAnnotationPopup(annotation: PdfAnnotationData, text: string, annotationElement: HTMLElement): void {
const popupElement = document.createElement('div'); const popupElement = document.createElement('div');
let headerElement: HTMLSpanElement; let headerElement: HTMLSpanElement;
if (annotation.titleObj?.str) { if (annotation.titleObj?.str) {
headerElement = this.createAnnotationPopupHeader(annotation); headerElement = this.createAnnotationPopupHeader(annotation as PdfAnnotationWithTitle);
} }
const contentElement = this.createAnnotationPopupContent(text); const contentElement = this.createAnnotationPopupContent(text);
popupElement.classList.add('popup', 'adf-pdf-viewer-annotation-tooltip'); popupElement.classList.add('popup', 'adf-pdf-viewer-annotation-tooltip');
@@ -694,7 +732,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
annotationElement.appendChild(popupElement); annotationElement.appendChild(popupElement);
} }
private createAnnotationPopupHeader(annotation: any): HTMLSpanElement { private createAnnotationPopupHeader(annotation: PdfAnnotationWithTitle): HTMLSpanElement {
const headerElement = document.createElement('span'); const headerElement = document.createElement('span');
const titleElement = document.createElement('span'); const titleElement = document.createElement('span');
let dateElement: HTMLTimeElement; let dateElement: HTMLTimeElement;

View File

@@ -31,7 +31,7 @@ export class TxtViewerComponent implements OnChanges {
private readonly appConfigService = inject(AppConfigService); private readonly appConfigService = inject(AppConfigService);
@Input() @Input()
urlFile: any; urlFile: string;
@Input() @Input()
blobFile: Blob; blobFile: Blob;
@@ -86,7 +86,7 @@ export class TxtViewerComponent implements OnChanges {
this.content = reader.result; this.content = reader.result;
}; };
reader.onerror = (error: any) => { reader.onerror = (error: ProgressEvent<FileReader>) => {
reject(error); reject(error);
}; };

View File

@@ -54,7 +54,7 @@ class DoubleViewerComponent {
viewer2: ViewerRenderComponent; viewer2: ViewerRenderComponent;
@ViewChild('viewerExtension', { static: true }) @ViewChild('viewerExtension', { static: true })
viewerTemplateExtensions: TemplateRef<any>; viewerTemplateExtensions: TemplateRef<unknown>;
urlFileViewer1: string; urlFileViewer1: string;
urlFileViewer2: string; urlFileViewer2: string;

View File

@@ -89,7 +89,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
/** The template for the pdf thumbnails. */ /** The template for the pdf thumbnails. */
@Input() @Input()
thumbnailsTemplate: TemplateRef<any> = null; thumbnailsTemplate: TemplateRef<unknown> = null;
/** MIME type of the file content (when not determined by the filename extension). */ /** MIME type of the file content (when not determined by the filename extension). */
@Input() @Input()
@@ -124,7 +124,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
/** Template containing ViewerExtensionDirective instances providing different viewer extensions based on supported file extension. */ /** Template containing ViewerExtensionDirective instances providing different viewer extensions based on supported file extension. */
@Input() @Input()
viewerTemplateExtensions: TemplateRef<any>; viewerTemplateExtensions: TemplateRef<unknown>;
/** Custom error message to be displayed in the viewer. */ /** Custom error message to be displayed in the viewer. */
@Input() @Input()
@@ -149,7 +149,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
@ViewChild(ImgViewerComponent) @ViewChild(ImgViewerComponent)
imgViewer: ImgViewerComponent; imgViewer: ImgViewerComponent;
extensionTemplates: { template: TemplateRef<any>; isVisible: boolean }[] = []; extensionTemplates: { template: TemplateRef<unknown>; isVisible: boolean }[] = [];
extensionsSupportedByTemplates: string[] = []; extensionsSupportedByTemplates: string[] = [];
extension: string; extension: string;
internalFileName: string; internalFileName: string;

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, DebugElement, SimpleChanges } from '@angular/core'; import { Component, DebugElement, SimpleChange, SimpleChanges } from '@angular/core';
import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { of } from 'rxjs'; import { of } from 'rxjs';
@@ -42,8 +42,8 @@ import { ThumbnailService } from '../../common/services/thumbnail.service';
class DummyDialogComponent {} class DummyDialogComponent {}
describe('ViewerComponent', () => { describe('ViewerComponent', () => {
let component: ViewerComponent<any>; let component: ViewerComponent<unknown>;
let fixture: ComponentFixture<ViewerComponent<any>>; let fixture: ComponentFixture<ViewerComponent<unknown>>;
let dialog: MatDialog; let dialog: MatDialog;
let viewUtilService: ViewUtilService; let viewUtilService: ViewUtilService;
let appConfigService: AppConfigService; let appConfigService: AppConfigService;
@@ -106,7 +106,9 @@ describe('ViewerComponent', () => {
describe('Mime Type Test', () => { describe('Mime Type Test', () => {
it('should mimeType change when blobFile changes', () => { it('should mimeType change when blobFile changes', () => {
const mockSimpleChanges: any = { blobFile: { currentValue: { type: 'image/png' } } }; const mockSimpleChanges: SimpleChanges = {
blobFile: new SimpleChange(null, { type: 'image/png' }, true)
};
component.ngOnChanges(mockSimpleChanges); component.ngOnChanges(mockSimpleChanges);
@@ -115,7 +117,10 @@ describe('ViewerComponent', () => {
it('should set mimeTypeIconUrl when mimeType changes and no nodeMimeType is provided', () => { it('should set mimeTypeIconUrl when mimeType changes and no nodeMimeType is provided', () => {
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue('image/png'); spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue('image/png');
const mockSimpleChanges: any = { mimeType: { currentValue: 'image/png' }, nodeMimeType: undefined }; const mockSimpleChanges: SimpleChanges = {
mimeType: new SimpleChange(null, 'image/png', true),
nodeMimeType: undefined
};
component.ngOnChanges(mockSimpleChanges); component.ngOnChanges(mockSimpleChanges);
@@ -125,7 +130,10 @@ describe('ViewerComponent', () => {
it('should set mimeTypeIconUrl when nodeMimeType changes', () => { it('should set mimeTypeIconUrl when nodeMimeType changes', () => {
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue('application/pdf'); spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue('application/pdf');
const mockSimpleChanges: any = { mimeType: { currentValue: 'image/png' }, nodeMimeType: { currentValue: 'application/pdf' } }; const mockSimpleChanges: SimpleChanges = {
mimeType: new SimpleChange(null, 'image/png', true),
nodeMimeType: new SimpleChange(null, 'application/pdf', true)
};
component.ngOnChanges(mockSimpleChanges); component.ngOnChanges(mockSimpleChanges);
fixture.detectChanges(); fixture.detectChanges();
@@ -523,7 +531,9 @@ describe('ViewerComponent', () => {
}); });
it('should file name be present if is overlay mode ', async () => { it('should file name be present if is overlay mode ', async () => {
const mockSimpleChanges: any = { blobFile: { currentValue: { type: 'image/png' } } }; const mockSimpleChanges: SimpleChanges = {
blobFile: new SimpleChange(null, { type: 'image/png' }, true)
};
component.ngOnChanges(mockSimpleChanges); component.ngOnChanges(mockSimpleChanges);
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
@@ -705,7 +715,7 @@ describe('ViewerComponent', () => {
downloadPromptReminderDelay: 2 downloadPromptReminderDelay: 2
} }
}; };
dialogOpenSpy = spyOn(dialog, 'open').and.returnValue({ afterClosed: () => of(null) } as any); dialogOpenSpy = spyOn(dialog, 'open').and.returnValue({ afterClosed: () => of(null) } as ReturnType<MatDialog['open']>);
component.urlFile = undefined; component.urlFile = undefined;
component.clearDownloadPromptTimeouts(); component.clearDownloadPromptTimeouts();
}); });
@@ -716,11 +726,11 @@ describe('ViewerComponent', () => {
}); });
it('should configure reminder timeout to display non responsive dialog after initial dialog', fakeAsync(() => { it('should configure reminder timeout to display non responsive dialog after initial dialog', fakeAsync(() => {
dialogOpenSpy.and.returnValue({ afterClosed: () => of(DownloadPromptActions.WAIT) } as any); dialogOpenSpy.and.returnValue({ afterClosed: () => of(DownloadPromptActions.WAIT) } as ReturnType<MatDialog['open']>);
fixture.detectChanges(); fixture.detectChanges();
tick(3000); tick(3000);
expect(component.downloadPromptReminderTimer).toBeDefined(); expect(component.downloadPromptReminderTimer).toBeDefined();
dialogOpenSpy.and.returnValue({ afterClosed: () => of(null) } as any); dialogOpenSpy.and.returnValue({ afterClosed: () => of(null) } as ReturnType<MatDialog['open']>);
flush(); flush();
discardPeriodicTasks(); discardPeriodicTasks();
})); }));
@@ -741,12 +751,12 @@ describe('ViewerComponent', () => {
})); }));
it('should show reminder non responsive dialog after initial dialog', fakeAsync(() => { it('should show reminder non responsive dialog after initial dialog', fakeAsync(() => {
dialogOpenSpy.and.returnValue({ afterClosed: () => of(DownloadPromptActions.WAIT) } as any); dialogOpenSpy.and.returnValue({ afterClosed: () => of(DownloadPromptActions.WAIT) } as ReturnType<MatDialog['open']>);
fixture.detectChanges(); fixture.detectChanges();
tick(3000); tick(3000);
expect(dialogOpenSpy).toHaveBeenCalled(); expect(dialogOpenSpy).toHaveBeenCalled();
dialogOpenSpy.and.returnValue({ afterClosed: () => of(null) } as any); dialogOpenSpy.and.returnValue({ afterClosed: () => of(null) } as ReturnType<MatDialog['open']>);
tick(2000); tick(2000);
expect(dialogOpenSpy).toHaveBeenCalledTimes(2); expect(dialogOpenSpy).toHaveBeenCalledTimes(2);
@@ -755,7 +765,7 @@ describe('ViewerComponent', () => {
})); }));
it('should emit downloadFileEvent when DownloadPromptDialog return DownloadPromptActions.DOWNLOAD on close', fakeAsync(() => { it('should emit downloadFileEvent when DownloadPromptDialog return DownloadPromptActions.DOWNLOAD on close', fakeAsync(() => {
dialogOpenSpy.and.returnValue({ afterClosed: () => of(DownloadPromptActions.DOWNLOAD) } as any); dialogOpenSpy.and.returnValue({ afterClosed: () => of(DownloadPromptActions.DOWNLOAD) } as ReturnType<MatDialog['open']>);
spyOn(component.downloadFile, 'emit'); spyOn(component.downloadFile, 'emit');
fixture.detectChanges(); fixture.detectChanges();
tick(3000); tick(3000);

View File

@@ -117,7 +117,7 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
mnuMoreActions: ViewerMoreActionsComponent; mnuMoreActions: ViewerMoreActionsComponent;
@ContentChild('viewerExtensions', { static: false }) @ContentChild('viewerExtensions', { static: false })
viewerTemplateExtensions: TemplateRef<any>; viewerTemplateExtensions: TemplateRef<unknown>;
get CloseButtonPosition() { get CloseButtonPosition() {
return CloseButtonPosition; return CloseButtonPosition;
@@ -190,11 +190,11 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
/** The template for the right sidebar. The template context contains the loaded node data. */ /** The template for the right sidebar. The template context contains the loaded node data. */
@Input() @Input()
sidebarRightTemplate: TemplateRef<any> = null; sidebarRightTemplate: TemplateRef<unknown> = null;
/** The template for the left sidebar. The template context contains the loaded node data. */ /** The template for the left sidebar. The template context contains the loaded node data. */
@Input() @Input()
sidebarLeftTemplate: TemplateRef<any> = null; sidebarLeftTemplate: TemplateRef<unknown> = null;
/** Enable when where is possible the editing functionalities */ /** Enable when where is possible the editing functionalities */
@Input() @Input()
@@ -243,7 +243,7 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
/** Template containing ViewerExtensionDirective instances providing different viewer extensions based on supported file extension. */ /** Template containing ViewerExtensionDirective instances providing different viewer extensions based on supported file extension. */
@Input() @Input()
viewerExtensions: TemplateRef<any>; viewerExtensions: TemplateRef<unknown>;
/** Identifier of a node that is opened by the viewer. */ /** Identifier of a node that is opened by the viewer. */
@Input() @Input()

View File

@@ -17,7 +17,7 @@
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { SpyLocation } from '@angular/common/testing'; import { SpyLocation } from '@angular/common/testing';
import { ChangeDetectorRef, ElementRef } from '@angular/core'; import { ChangeDetectorRef, ElementRef, TemplateRef } from '@angular/core';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { ViewerRenderComponent } from '../components/viewer-render/viewer-render.component'; import { ViewerRenderComponent } from '../components/viewer-render/viewer-render.component';
import { ViewerExtensionDirective } from './viewer-extension.directive'; import { ViewerExtensionDirective } from './viewer-extension.directive';
@@ -50,7 +50,7 @@ describe('ExtensionViewerDirective', () => {
}); });
extensionViewerDirective = TestBed.inject(ViewerExtensionDirective); extensionViewerDirective = TestBed.inject(ViewerExtensionDirective);
viewerRenderer = TestBed.inject(ViewerRenderComponent); viewerRenderer = TestBed.inject(ViewerRenderComponent);
extensionViewerDirective.templateModel = { template: '', isVisible: false }; extensionViewerDirective.templateModel = { template: {} as TemplateRef<unknown>, isVisible: false };
}); });
it('is defined', () => { it('is defined', () => {

View File

@@ -26,7 +26,7 @@ export class ViewerExtensionDirective implements AfterContentInit {
private readonly viewerComponent = inject(ViewerRenderComponent); private readonly viewerComponent = inject(ViewerRenderComponent);
@ContentChild(TemplateRef) @ContentChild(TemplateRef)
template: any; template: TemplateRef<unknown>;
@Input() @Input()
urlFileContent: string; urlFileContent: string;
@@ -37,7 +37,7 @@ export class ViewerExtensionDirective implements AfterContentInit {
@Input() @Input()
supportedExtensions: string[]; supportedExtensions: string[];
templateModel: any; templateModel: { template: TemplateRef<unknown>; isVisible: boolean };
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);

View File

@@ -16,6 +16,29 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import type { PDFViewer } from 'pdfjs-dist/types/web/pdf_viewer';
import type { PDFThumbnailViewer } from 'pdfjs-dist/types/web/pdf_thumbnail_viewer';
import type { PDFPageView } from 'pdfjs-dist/types/web/pdf_page_view';
interface VisiblePage {
id: number;
}
interface VisiblePages {
first?: VisiblePage;
last?: VisiblePage;
views?: Array<{ view: PDFPageView }>;
}
/**
* Type helper for accessing the resume method on PDFPageView.
* PDFPageView implements IRenderableView which includes a resume method,
* but the TypeScript definitions don't properly expose it.
*/
// cspell:ignore Renderable
interface ResumableView {
resume?: () => void;
}
/** /**
* *
@@ -33,13 +56,13 @@ export class RenderingQueueServices {
CLEANUP_TIMEOUT: number = 30_000; CLEANUP_TIMEOUT: number = 30_000;
pdfViewer: any = null; pdfViewer: PDFViewer | null = null;
pdfThumbnailViewer: any = null; pdfThumbnailViewer: PDFThumbnailViewer | null = null;
onIdle: any = null; onIdle: (() => void) | null = null;
highestPriorityPage: string | null = null; highestPriorityPage: string | null = null;
idleTimeout: any = null; idleTimeout: number | null = null;
printing: any = false; printing = false;
isThumbnailViewEnabled = false; isThumbnailViewEnabled = false;
/** /**
@@ -47,7 +70,7 @@ export class RenderingQueueServices {
* *
* @param pdfViewer viewer instance * @param pdfViewer viewer instance
*/ */
setViewer(pdfViewer): void { setViewer(pdfViewer: PDFViewer): void {
this.pdfViewer = pdfViewer; this.pdfViewer = pdfViewer;
} }
@@ -56,7 +79,7 @@ export class RenderingQueueServices {
* *
* @param pdfThumbnailViewer viewer instance * @param pdfThumbnailViewer viewer instance
*/ */
setThumbnailViewer(pdfThumbnailViewer): void { setThumbnailViewer(pdfThumbnailViewer: PDFThumbnailViewer): void {
this.pdfThumbnailViewer = pdfThumbnailViewer; this.pdfThumbnailViewer = pdfThumbnailViewer;
} }
@@ -66,18 +89,18 @@ export class RenderingQueueServices {
* @param view view to render * @param view view to render
* @returns `true` if the view has higher priority, otherwise `false` * @returns `true` if the view has higher priority, otherwise `false`
*/ */
isHighestPriority(view: any): boolean { isHighestPriority(view: PDFPageView): boolean {
return this.highestPriorityPage === view.renderingId; return this.highestPriorityPage === view.renderingId;
} }
renderHighestPriority(currentlyVisiblePages) { renderHighestPriority(currentlyVisiblePages?: unknown): void {
if (this.idleTimeout) { if (this.idleTimeout) {
clearTimeout(this.idleTimeout); clearTimeout(this.idleTimeout);
this.idleTimeout = null; this.idleTimeout = null;
} }
// Pages have a higher priority than thumbnails, so check them first. // Pages have a higher priority than thumbnails, so check them first.
if (this.pdfViewer.forceRendering(currentlyVisiblePages)) { if (this.pdfViewer?.forceRendering(currentlyVisiblePages)) {
return; return;
} }
// No pages needed rendering so check thumbnails. // No pages needed rendering so check thumbnails.
@@ -91,11 +114,23 @@ export class RenderingQueueServices {
} }
if (this.onIdle) { if (this.onIdle) {
this.idleTimeout = setTimeout(this.onIdle.bind(this), this.CLEANUP_TIMEOUT); // Type assertion needed: setTimeout returns NodeJS.Timeout in Node types,
// but returns number at runtime in browser (where this code executes).
// PDFRenderingQueue interface requires idleTimeout to be number.
this.idleTimeout = setTimeout(this.onIdle.bind(this), this.CLEANUP_TIMEOUT) as unknown as number;
} }
} }
getHighestPriority(visible, views, scrolledDown) { /**
* Gets the highest priority page to render from the visible pages
* This method is part of the PDFRenderingQueue interface compatibility
*
* @param visible visible pages information
* @param views array of page views
* @param scrolledDown whether the user scrolled down
* @returns the highest priority page view to render, null if all done, or false if no visible pages
*/
getHighestPriority(visible: VisiblePages, views: PDFPageView[], scrolledDown: boolean): PDFPageView | null | false {
// The state has changed figure out which page has the highest priority to // The state has changed figure out which page has the highest priority to
// render next (if any). // render next (if any).
// Priority: // Priority:
@@ -104,6 +139,10 @@ export class RenderingQueueServices {
// 2 if last scrolled up page before the visible pages // 2 if last scrolled up page before the visible pages
const visibleViews = visible.views; const visibleViews = visible.views;
if (!visibleViews) {
return false;
}
const numberVisible = visibleViews.length; const numberVisible = visibleViews.length;
if (numberVisible === 0) { if (numberVisible === 0) {
return false; return false;
@@ -116,13 +155,13 @@ export class RenderingQueueServices {
} }
// All the visible views have rendered, try to render next/previous pages. // All the visible views have rendered, try to render next/previous pages.
if (scrolledDown) { if (scrolledDown && visible.last) {
const nextPageIndex = visible.last.id; const nextPageIndex = visible.last.id;
// ID's start at 1 so no need to add 1. // ID's start at 1 so no need to add 1.
if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) { if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) {
return views[nextPageIndex]; return views[nextPageIndex];
} }
} else { } else if (visible.first) {
const previousPageIndex = visible.first.id - 2; const previousPageIndex = visible.first.id - 2;
if (views[previousPageIndex] && !this.isViewFinished(views[previousPageIndex])) { if (views[previousPageIndex] && !this.isViewFinished(views[previousPageIndex])) {
return views[previousPageIndex]; return views[previousPageIndex];
@@ -142,7 +181,7 @@ export class RenderingQueueServices {
* @param view the View instance to check * @param view the View instance to check
* @returns `true` if rendering is finished, otherwise `false` * @returns `true` if rendering is finished, otherwise `false`
*/ */
isViewFinished(view): boolean { isViewFinished(view: PDFPageView): boolean {
return view.renderingState === this.renderingStates.FINISHED; return view.renderingState === this.renderingStates.FINISHED;
} }
@@ -154,7 +193,7 @@ export class RenderingQueueServices {
* @param view View instance to render * @param view View instance to render
* @returns the rendered state of the view * @returns the rendered state of the view
*/ */
renderView(view: any): boolean { renderView(view: PDFPageView): boolean {
const state = view.renderingState; const state = view.renderingState;
switch (state) { switch (state) {
case this.renderingStates.FINISHED: { case this.renderingStates.FINISHED: {
@@ -162,7 +201,10 @@ export class RenderingQueueServices {
} }
case this.renderingStates.PAUSED: { case this.renderingStates.PAUSED: {
this.highestPriorityPage = view.renderingId; this.highestPriorityPage = view.renderingId;
view.resume(); const resumableView = view as unknown as ResumableView;
if (resumableView.resume) {
resumableView.resume();
}
break; break;
} }
case this.renderingStates.RUNNING: { case this.renderingStates.RUNNING: {
@@ -171,10 +213,9 @@ export class RenderingQueueServices {
} }
case this.renderingStates.INITIAL: { case this.renderingStates.INITIAL: {
this.highestPriorityPage = view.renderingId; this.highestPriorityPage = view.renderingId;
// eslint-disable-next-line space-before-function-paren const continueRendering = () => {
const continueRendering = function () {
this.renderHighestPriority(); this.renderHighestPriority();
}.bind(this); };
view.draw().then(continueRendering, continueRendering); view.draw().then(continueRendering, continueRendering);
break; break;
} }

View File

@@ -26,4 +26,4 @@ TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserDynamicTesting()
teardown: { destroyAfterEach: true } teardown: { destroyAfterEach: true }
}); });
(window as any).pdfjsLib = pdfjsLibraryMock; (window as unknown as { pdfjsLib: typeof pdfjsLibraryMock }).pdfjsLib = pdfjsLibraryMock;

View File

@@ -7,6 +7,10 @@ const { constants } = require('karma');
module.exports = function (config) { module.exports = function (config) {
config.set({ config.set({
basePath: '', basePath: '',
files: [
{ pattern: '../../node_modules/pdfjs-dist/build/pdf.min.mjs', type: 'module', included: true, watched: false },
{ pattern: '../../node_modules/pdfjs-dist/build/pdf.worker.min.mjs', type: 'module', included: true, watched: false }
],
frameworks: ['jasmine', '@angular-devkit/build-angular'], frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [ plugins: [
require('karma-jasmine'), require('karma-jasmine'),

View File

@@ -9,6 +9,8 @@ module.exports = function (config) {
basePath: '../../', basePath: '../../',
files: [ files: [
{ pattern: 'node_modules/pdfjs-dist/build/pdf.min.mjs', type: 'module', included: true, watched: false },
{ pattern: 'node_modules/pdfjs-dist/build/pdf.worker.min.mjs', type: 'module', included: true, watched: false },
{ pattern: 'node_modules/chart.js/dist/Chart.js', included: true, watched: false }, { pattern: 'node_modules/chart.js/dist/Chart.js', included: true, watched: false },
{ pattern: 'node_modules/raphael/raphael.min.js', included: true, watched: false }, { pattern: 'node_modules/raphael/raphael.min.js', included: true, watched: false },
{ {

View File

@@ -17,6 +17,17 @@
import { BaseApi } from './base.api'; import { BaseApi } from './base.api';
/**
* Product version model for Process Services.
*/
export interface BpmProductVersionModel {
edition: string;
majorVersion: string;
revisionVersion: string;
minorVersion: string;
type: string;
}
/** /**
* About service. * About service.
*/ */
@@ -26,7 +37,7 @@ export class AboutApi extends BaseApi {
* Provides information about the running Alfresco Process Services Suite. The response payload object has the properties type, majorVersion, minorVersion, revisionVersion and edition. * Provides information about the running Alfresco Process Services Suite. The response payload object has the properties type, majorVersion, minorVersion, revisionVersion and edition.
* @return Promise<{ [key: string]: string; }> * @return Promise<{ [key: string]: string; }>
*/ */
getAppVersion(): Promise<{ [key: string]: string }> { getAppVersion(): Promise<BpmProductVersionModel> {
return this.get({ return this.get({
path: '/api/enterprise/app-version' path: '/api/enterprise/app-version'
}); });

View File

@@ -8,6 +8,8 @@ module.exports = function (config) {
config.set({ config.set({
basePath: '../../', basePath: '../../',
files: [ files: [
{ pattern: 'node_modules/pdfjs-dist/build/pdf.min.mjs', type: 'module', included: true, watched: false },
{ pattern: 'node_modules/pdfjs-dist/build/pdf.worker.min.mjs', type: 'module', included: true, watched: false },
{ {
pattern: 'node_modules/@angular/material/prebuilt-themes/indigo-pink.css', pattern: 'node_modules/@angular/material/prebuilt-themes/indigo-pink.css',
included: true, included: true,

View File

@@ -8,6 +8,8 @@ module.exports = function (config) {
config.set({ config.set({
basePath: '../../', basePath: '../../',
files: [ files: [
{ pattern: 'node_modules/pdfjs-dist/build/pdf.min.mjs', type: 'module', included: true, watched: false },
{ pattern: 'node_modules/pdfjs-dist/build/pdf.worker.min.mjs', type: 'module', included: true, watched: false },
{ {
pattern: 'node_modules/@angular/material/prebuilt-themes/indigo-pink.css', pattern: 'node_modules/@angular/material/prebuilt-themes/indigo-pink.css',
included: true, included: true,