mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-45391 Lazy loading and improvements for pdfJS (#11869)
* [AAE-45391] - Lazy loading some of the big external libs * [AAE-45391] - Added some extra units to pdfjs viewer * [AAE-45391] - Some improement on viewer component * [AAE-45391] - Fixed the way it was redered to a newer angular system * [AAE-45391] - Fixed the way it was redered to a newer angular system * [AAE-45391] - fixed schematic * [AAE-45391] - Added some docs for the schematics as well * [AAE-37328] - Fixed comment on missing provider
This commit is contained in:
@@ -2,11 +2,6 @@
|
||||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../dist/libs/core",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "pdf.worker.mjs",
|
||||
"input": "./assets/pdfjs",
|
||||
"output": "assets/pdfjs"
|
||||
},
|
||||
{
|
||||
"glob": "assets/**/*",
|
||||
"input": "./src/lib",
|
||||
@@ -38,7 +33,6 @@
|
||||
"allowedNonPeerDependencies": [
|
||||
"cropperjs",
|
||||
"angular-oauth2-oidc",
|
||||
"date-fns",
|
||||
"rxjs"
|
||||
"date-fns"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
"dependencies": {
|
||||
"cropperjs": "^1.6.2",
|
||||
"angular-oauth2-oidc": "19.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"rxjs": "7.8.2"
|
||||
"date-fns": "^2.30.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": ">=16.0.0",
|
||||
@@ -37,7 +36,13 @@
|
||||
"@alfresco/js-api": ">=9.4.1",
|
||||
"@alfresco/adf-extensions": ">=8.4.1",
|
||||
"minimatch": ">=10.0.0",
|
||||
"pdfjs-dist": ">=3.3.122"
|
||||
"pdfjs-dist": ">=3.3.122",
|
||||
"rxjs": ">=7.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"pdfjs-dist": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"core",
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# Migration Schematic: migrate-pdf-viewer-imports (v9.0.0)
|
||||
|
||||
Automatically migrates consumer projects from the monolithic `@alfresco/adf-core` PDF viewer to the new `@alfresco/adf-core/viewer/pdf` secondary entrypoint.
|
||||
|
||||
## What it does
|
||||
|
||||
### 1. Rewrites imports
|
||||
|
||||
Moves PDF-related symbols from `@alfresco/adf-core` to `@alfresco/adf-core/viewer/pdf`:
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
import { AppConfigService, PdfViewerComponent, RenderingQueueServices } from '@alfresco/adf-core';
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
import { PdfViewerComponent, RenderingQueueServices } from '@alfresco/adf-core/viewer/pdf';
|
||||
```
|
||||
|
||||
Affected symbols:
|
||||
- `PdfViewerComponent`
|
||||
- `PdfPasswordDialogComponent`
|
||||
- `PdfThumbListComponent`
|
||||
- `PdfThumbComponent`
|
||||
- `PDFJS_MODULE`
|
||||
- `PDFJS_VIEWER_MODULE`
|
||||
- `RenderingQueueServices`
|
||||
|
||||
### 2. Adds `providePdfViewer()` to app providers
|
||||
|
||||
The PDF viewer now requires explicit registration via the `providePdfViewer()` provider function. The schematic automatically adds it to the consumer's application configuration.
|
||||
|
||||
**Standalone app (`app.config.ts`):**
|
||||
```typescript
|
||||
import { providePdfViewer } from '@alfresco/adf-core/viewer/pdf';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [providePdfViewer(), /* ...existing providers */]
|
||||
};
|
||||
```
|
||||
|
||||
**NgModule app (`app.module.ts`):**
|
||||
```typescript
|
||||
import { providePdfViewer } from '@alfresco/adf-core/viewer/pdf';
|
||||
|
||||
@NgModule({
|
||||
providers: [providePdfViewer()]
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
## How it runs
|
||||
|
||||
Triggered automatically via `ng update @alfresco/adf-core` when upgrading to v9.0.0+.
|
||||
|
||||
Can also be run manually:
|
||||
```bash
|
||||
ng generate @alfresco/adf-core:migrate-pdf-viewer-imports
|
||||
```
|
||||
|
||||
## File discovery
|
||||
|
||||
The schematic scans all `.ts` files in the project (excluding `node_modules`, `.git`, `.angular`, `.nxcache`) and processes any file containing one of the PDF symbols listed above.
|
||||
|
||||
For provider injection, it searches for the app config file in this order:
|
||||
1. `src/app/app.config.ts`
|
||||
2. `src/app/app.module.ts`
|
||||
3. `src/main.ts`
|
||||
4. Falls back to scanning for any file containing `bootstrapApplication` or `ApplicationConfig`
|
||||
|
||||
## Edge cases handled
|
||||
|
||||
| Scenario | Behavior |
|
||||
|----------|----------|
|
||||
| Existing `@alfresco/adf-core/viewer/pdf` import | Merges moved symbols into it (deduplicates) |
|
||||
| All symbols in the core import are PDF symbols | Removes the entire `@alfresco/adf-core` import |
|
||||
| `providePdfViewer` already present in the project | Skips provider injection |
|
||||
| No app config file found | Skips provider injection (consumer must add manually) |
|
||||
| File has no PDF symbols | Skipped entirely (no modifications) |
|
||||
| Empty providers array | Inserts `providePdfViewer()` as the first element |
|
||||
|
||||
## Why this migration exists
|
||||
|
||||
In v9.0.0, the PDF viewer was extracted into a secondary entrypoint (`@alfresco/adf-core/viewer/pdf`) so that:
|
||||
- `pdfjs-dist` becomes an optional peer dependency
|
||||
- Applications that don't use PDF viewing avoid bundling ~500KB of PDF processing code
|
||||
- The primary `@alfresco/adf-core` bundle stays lean
|
||||
|
||||
Without this schematic, consumers upgrading to v9 would see PDF files rendered as "unknown format" because the `PDF_VIEWER_COMPONENT` injection token defaults to `null` when `providePdfViewer()` is not called.
|
||||
@@ -0,0 +1,274 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import { Rule, Tree } from '@angular-devkit/schematics';
|
||||
|
||||
const PDF_SYMBOLS = [
|
||||
'PdfViewerComponent',
|
||||
'PdfPasswordDialogComponent',
|
||||
'PdfThumbListComponent',
|
||||
'PdfThumbComponent',
|
||||
'PDFJS_MODULE',
|
||||
'PDFJS_VIEWER_MODULE',
|
||||
'RenderingQueueServices'
|
||||
];
|
||||
|
||||
const OLD_SOURCE = '@alfresco/adf-core';
|
||||
const NEW_SOURCE = '@alfresco/adf-core/viewer/pdf';
|
||||
|
||||
/**
|
||||
* @returns Schematic rule for migrating PDF viewer imports to the secondary entrypoint
|
||||
*/
|
||||
export function migratePdfViewerImports(): Rule {
|
||||
return (tree: Tree) => {
|
||||
let providerAdded = false;
|
||||
|
||||
tree.visit((filePath: string) => {
|
||||
if (
|
||||
!filePath.includes('/.git/') &&
|
||||
!filePath.includes('/node_modules/') &&
|
||||
!filePath.includes('/.angular/') &&
|
||||
!filePath.includes('/.nxcache/') &&
|
||||
/\.ts$/.test(filePath)
|
||||
) {
|
||||
const bufferContent = tree.read(filePath);
|
||||
if (!bufferContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileContent = bufferContent.toString();
|
||||
|
||||
if (PDF_SYMBOLS.some((sym) => fileContent.includes(sym))) {
|
||||
const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true);
|
||||
const updatedContent = movePdfImports(sourceFile, fileContent);
|
||||
|
||||
if (updatedContent !== fileContent) {
|
||||
tree.overwrite(filePath, updatedContent);
|
||||
}
|
||||
}
|
||||
|
||||
if (!providerAdded && fileContent.includes('providePdfViewer')) {
|
||||
providerAdded = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!providerAdded) {
|
||||
addProvidePdfViewerToAppConfig(tree);
|
||||
}
|
||||
|
||||
return tree;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sourceFile - the parsed TypeScript source file
|
||||
* @param fileContent - the raw file content string
|
||||
* @returns updated file content with PDF imports moved to the secondary entrypoint
|
||||
*/
|
||||
function movePdfImports(sourceFile: ts.SourceFile, fileContent: string): string {
|
||||
const importDeclarations = sourceFile.statements.filter(ts.isImportDeclaration);
|
||||
const coreImport = importDeclarations.find((decl) => {
|
||||
const moduleSpecifier = decl.moduleSpecifier.getText().replace(/['"]/g, '');
|
||||
return moduleSpecifier === OLD_SOURCE;
|
||||
});
|
||||
|
||||
if (!coreImport?.importClause?.namedBindings || !ts.isNamedImports(coreImport.importClause.namedBindings)) {
|
||||
return fileContent;
|
||||
}
|
||||
|
||||
const namedImports = coreImport.importClause.namedBindings.elements;
|
||||
const pdfImports = namedImports.filter((el) => PDF_SYMBOLS.includes(el.name.text));
|
||||
const remainingImports = namedImports.filter((el) => !PDF_SYMBOLS.includes(el.name.text));
|
||||
|
||||
if (pdfImports.length === 0) {
|
||||
return fileContent;
|
||||
}
|
||||
|
||||
const pdfImportNames = pdfImports.map((el) => el.name.text);
|
||||
|
||||
const existingNewImport = importDeclarations.find((decl) => decl.moduleSpecifier.getText().replace(/['"]/g, '') === NEW_SOURCE);
|
||||
|
||||
const existingNewImportNames: string[] = [];
|
||||
if (existingNewImport?.importClause?.namedBindings && ts.isNamedImports(existingNewImport.importClause.namedBindings)) {
|
||||
existingNewImportNames.push(...existingNewImport.importClause.namedBindings.elements.map((el) => el.name.text));
|
||||
}
|
||||
|
||||
const mergedNames = [...new Set([...existingNewImportNames, ...pdfImportNames])];
|
||||
const mergedImportStatement = `import { ${mergedNames.join(', ')} } from '${NEW_SOURCE}';`;
|
||||
|
||||
const edits: { start: number; end: number; replacement: string }[] = [];
|
||||
|
||||
if (remainingImports.length === 0) {
|
||||
edits.push({ start: coreImport.getFullStart(), end: coreImport.getEnd(), replacement: '' });
|
||||
} else {
|
||||
const remainingNames = remainingImports.map((el) => el.getText()).join(', ');
|
||||
const updatedImport = `import { ${remainingNames} } from '${OLD_SOURCE}';`;
|
||||
edits.push({ start: coreImport.getStart(), end: coreImport.getEnd(), replacement: updatedImport });
|
||||
}
|
||||
|
||||
if (existingNewImport) {
|
||||
edits.push({ start: existingNewImport.getStart(), end: existingNewImport.getEnd(), replacement: mergedImportStatement });
|
||||
}
|
||||
|
||||
edits.sort((a, b) => b.start - a.start);
|
||||
let updatedContent = fileContent;
|
||||
for (const edit of edits) {
|
||||
updatedContent = updatedContent.slice(0, edit.start) + edit.replacement + updatedContent.slice(edit.end);
|
||||
}
|
||||
|
||||
if (!existingNewImport) {
|
||||
const updatedSource = ts.createSourceFile('temp.ts', updatedContent, ts.ScriptTarget.Latest, true);
|
||||
const firstNonImport = updatedSource.statements.find((stmt) => !ts.isImportDeclaration(stmt));
|
||||
const insertPos = firstNonImport ? firstNonImport.getFullStart() : updatedContent.length;
|
||||
updatedContent = updatedContent.slice(0, insertPos).trimEnd() + '\n' + mergedImportStatement + '\n' + updatedContent.slice(insertPos);
|
||||
}
|
||||
|
||||
return updatedContent.replace(/\n{3,}/g, '\n\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tree - the schematic file tree
|
||||
*/
|
||||
function addProvidePdfViewerToAppConfig(tree: Tree): void {
|
||||
const candidates = ['src/app/app.config.ts', 'src/app/app.module.ts', 'src/main.ts'];
|
||||
let targetPath: string | null = null;
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (tree.exists(`/${candidate}`)) {
|
||||
targetPath = `/${candidate}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetPath) {
|
||||
tree.visit((filePath: string) => {
|
||||
if (targetPath) {
|
||||
return;
|
||||
}
|
||||
if (!filePath.includes('/node_modules/') && !filePath.includes('/.git/') && /\.ts$/.test(filePath)) {
|
||||
const content = tree.read(filePath)?.toString() ?? '';
|
||||
if (content.includes('bootstrapApplication') || content.includes('ApplicationConfig')) {
|
||||
targetPath = filePath;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!targetPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = tree.read(targetPath);
|
||||
if (!buffer) {
|
||||
return;
|
||||
}
|
||||
const content = buffer.toString();
|
||||
|
||||
if (content.includes('providePdfViewer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceFile = ts.createSourceFile(targetPath, content, ts.ScriptTarget.Latest, true);
|
||||
const result = insertProvidePdfViewer(sourceFile, content);
|
||||
|
||||
if (result !== content) {
|
||||
tree.overwrite(targetPath, result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sourceFile - the parsed TypeScript source file
|
||||
* @param content - the raw file content
|
||||
* @returns updated content with providePdfViewer() added to providers
|
||||
*/
|
||||
function insertProvidePdfViewer(sourceFile: ts.SourceFile, content: string): string {
|
||||
const providersArray = findProvidersArray(sourceFile);
|
||||
if (!providersArray) {
|
||||
return content;
|
||||
}
|
||||
|
||||
const lastElement = providersArray.elements[providersArray.elements.length - 1];
|
||||
let insertPos: number;
|
||||
let prefix: string;
|
||||
|
||||
if (lastElement) {
|
||||
insertPos = lastElement.getEnd();
|
||||
prefix = ', providePdfViewer()';
|
||||
} else {
|
||||
insertPos = providersArray.getStart() + 1;
|
||||
prefix = 'providePdfViewer()';
|
||||
}
|
||||
|
||||
let updatedContent = content.slice(0, insertPos) + prefix + content.slice(insertPos);
|
||||
|
||||
const importStatement = `import { providePdfViewer } from '${NEW_SOURCE}';\n`;
|
||||
const existingPdfImport = sourceFile.statements
|
||||
.filter(ts.isImportDeclaration)
|
||||
.find((decl) => decl.moduleSpecifier.getText().replace(/['"]/g, '') === NEW_SOURCE);
|
||||
|
||||
if (existingPdfImport) {
|
||||
if (!content.includes('providePdfViewer')) {
|
||||
const updatedSource = ts.createSourceFile('temp.ts', updatedContent, ts.ScriptTarget.Latest, true);
|
||||
const existingDecl = updatedSource.statements
|
||||
.filter(ts.isImportDeclaration)
|
||||
.find((decl) => decl.moduleSpecifier.getText().replace(/['"]/g, '') === NEW_SOURCE);
|
||||
|
||||
if (existingDecl?.importClause?.namedBindings && ts.isNamedImports(existingDecl.importClause.namedBindings)) {
|
||||
const names = existingDecl.importClause.namedBindings.elements.map((el) => el.name.text);
|
||||
if (!names.includes('providePdfViewer')) {
|
||||
names.push('providePdfViewer');
|
||||
const newImport = `import { ${names.join(', ')} } from '${NEW_SOURCE}';`;
|
||||
updatedContent = updatedContent.slice(0, existingDecl.getStart()) + newImport + updatedContent.slice(existingDecl.getEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const firstImport = sourceFile.statements.find(ts.isImportDeclaration);
|
||||
const importInsertPos = firstImport ? firstImport.getFullStart() : 0;
|
||||
updatedContent = updatedContent.slice(0, importInsertPos) + importStatement + updatedContent.slice(importInsertPos);
|
||||
}
|
||||
|
||||
return updatedContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sourceFile - the TypeScript source file to search
|
||||
* @returns the providers ArrayLiteralExpression if found
|
||||
*/
|
||||
function findProvidersArray(sourceFile: ts.SourceFile): ts.ArrayLiteralExpression | null {
|
||||
let result: ts.ArrayLiteralExpression | null = null;
|
||||
|
||||
const visit = (node: ts.Node): void => {
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ts.isPropertyAssignment(node) && node.name.getText() === 'providers' && ts.isArrayLiteralExpression(node.initializer)) {
|
||||
result = node.initializer;
|
||||
return;
|
||||
}
|
||||
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
visit(sourceFile);
|
||||
return result;
|
||||
}
|
||||
|
||||
export default migratePdfViewerImports;
|
||||
@@ -5,6 +5,11 @@
|
||||
"description": "Update alfresco-api imports",
|
||||
"version": "7.0.0",
|
||||
"factory": "./7_0_0/index#updateAlfrescoApiImports"
|
||||
},
|
||||
"migrate-pdf-viewer-imports": {
|
||||
"description": "Move PDF viewer imports to @alfresco/adf-core/viewer/pdf secondary entrypoint",
|
||||
"version": "9.0.0",
|
||||
"factory": "./9_0_0/index#migratePdfViewerImports"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@@ -32,17 +32,13 @@
|
||||
}
|
||||
|
||||
@case ('pdf') {
|
||||
<adf-pdf-viewer
|
||||
[thumbnailsTemplate]="thumbnailsTemplate"
|
||||
[allowThumbnails]="allowThumbnails"
|
||||
[blobFile]="blobFile"
|
||||
[urlFile]="urlFile"
|
||||
[fileName]="internalFileName"
|
||||
[cacheType]="cacheTypeForContent"
|
||||
(pagesLoaded)="markAsLoaded()"
|
||||
(close)="onClose()"
|
||||
(error)="onUnsupportedFile()"
|
||||
/>
|
||||
@if (pdfViewerComponent) {
|
||||
<ng-container
|
||||
[ngComponentOutlet]="pdfViewerComponent"
|
||||
[ngComponentOutletInputs]="pdfViewerInputs" />
|
||||
} @else {
|
||||
<adf-viewer-unknown-format />
|
||||
}
|
||||
}
|
||||
|
||||
@case ('image') {
|
||||
|
||||
@@ -18,13 +18,30 @@
|
||||
import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions';
|
||||
import { Location } from '@angular/common';
|
||||
import { SpyLocation } from '@angular/common/testing';
|
||||
import { Component, DebugElement, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { Component, DebugElement, EventEmitter, Input, Output, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { ComponentFixture, DeferBlockBehavior, TestBed } from '@angular/core/testing';
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { UnitTestingUtils } from '../../../testing';
|
||||
import { RenderingQueueServices } from '../../services/rendering-queue.services';
|
||||
import { ViewerRenderComponent } from './viewer-render.component';
|
||||
import { ImgViewerComponent, MediaPlayerComponent, PdfViewerComponent, ViewerExtensionDirective } from '@alfresco/adf-core';
|
||||
import { PDF_VIEWER_COMPONENT } from '../../tokens/pdf-viewer.token';
|
||||
import { PdfViewerRef } from '../../tokens/pdf-viewer-ref';
|
||||
import { ImgViewerComponent, MediaPlayerComponent, ViewerExtensionDirective } from '@alfresco/adf-core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-pdf-viewer',
|
||||
template: '<div class="adf-pdf-viewer-mock"></div>'
|
||||
})
|
||||
class MockPdfViewerComponent implements PdfViewerRef {
|
||||
@Input() urlFile = '';
|
||||
@Input() blobFile: Blob;
|
||||
@Input() fileName = '';
|
||||
@Input() allowThumbnails = false;
|
||||
@Input() thumbnailsTemplate: TemplateRef<unknown> = null;
|
||||
@Input() cacheType = '';
|
||||
@Output() pagesLoaded = new EventEmitter<void>();
|
||||
@Output() error = new EventEmitter<void>();
|
||||
@Output() close = new EventEmitter<void>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-double-viewer',
|
||||
@@ -69,7 +86,7 @@ describe('ViewerComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatDialogModule, ViewerRenderComponent, DoubleViewerComponent],
|
||||
providers: [RenderingQueueServices, { provide: Location, useClass: SpyLocation }, MatDialog],
|
||||
providers: [{ provide: Location, useClass: SpyLocation }, MatDialog, { provide: PDF_VIEWER_COMPONENT, useValue: MockPdfViewerComponent }],
|
||||
deferBlockBehavior: DeferBlockBehavior.Playthrough
|
||||
});
|
||||
fixture = TestBed.createComponent(ViewerRenderComponent);
|
||||
@@ -532,7 +549,7 @@ describe('ViewerComponent', () => {
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
|
||||
const imgViewer = testingUtils.getByDirective(PdfViewerComponent);
|
||||
const imgViewer = testingUtils.getByDirective(MockPdfViewerComponent);
|
||||
imgViewer.triggerEventHandler('pagesLoaded', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
*/
|
||||
|
||||
import { AppExtensionService, ExtensionsModule, ViewerExtensionRef, PreviewExtensionComponent } from '@alfresco/adf-extensions';
|
||||
import { NgForOf, NgTemplateOutlet } from '@angular/common';
|
||||
import { NgComponentOutlet, NgForOf, NgTemplateOutlet } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
effect,
|
||||
EventEmitter,
|
||||
Injector,
|
||||
Input,
|
||||
@@ -26,18 +27,22 @@ import {
|
||||
OnInit,
|
||||
Output,
|
||||
TemplateRef,
|
||||
Type,
|
||||
ViewChild,
|
||||
ViewEncapsulation,
|
||||
inject
|
||||
inject,
|
||||
viewChild
|
||||
} from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Track } from '../../models/viewer.model';
|
||||
import { ViewUtilService } from '../../services/view-util.service';
|
||||
import { PDF_VIEWER_COMPONENT } from '../../tokens/pdf-viewer.token';
|
||||
import { PdfViewerRef } from '../../tokens/pdf-viewer-ref';
|
||||
import { ImgViewerComponent } from '../img-viewer/img-viewer.component';
|
||||
import { MediaPlayerComponent } from '../media-player/media-player.component';
|
||||
import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component';
|
||||
import { TxtViewerComponent } from '../txt-viewer/txt-viewer.component';
|
||||
import { UnknownFormatComponent } from '../unknown-format/unknown-format.component';
|
||||
|
||||
@@ -50,7 +55,7 @@ import { UnknownFormatComponent } from '../unknown-format/unknown-format.compone
|
||||
imports: [
|
||||
TranslatePipe,
|
||||
MatProgressSpinnerModule,
|
||||
PdfViewerComponent,
|
||||
NgComponentOutlet,
|
||||
ImgViewerComponent,
|
||||
MediaPlayerComponent,
|
||||
TxtViewerComponent,
|
||||
@@ -68,6 +73,30 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
|
||||
dialog = inject(MatDialog);
|
||||
readonly injector = inject(Injector);
|
||||
|
||||
readonly pdfViewerComponent: Type<PdfViewerRef> | null = inject(PDF_VIEWER_COMPONENT, { optional: true });
|
||||
|
||||
pdfViewerInputs: Record<string, unknown> = {};
|
||||
|
||||
private readonly pdfOutlet = viewChild(NgComponentOutlet);
|
||||
|
||||
constructor() {
|
||||
effect((onCleanup) => {
|
||||
const outlet = this.pdfOutlet();
|
||||
const instance = outlet?.componentInstance as PdfViewerRef | null;
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subs: Subscription[] = [
|
||||
instance.pagesLoaded.subscribe(() => this.markAsLoaded()),
|
||||
instance.close.subscribe(() => this.onClose()),
|
||||
instance.error.subscribe(() => this.onUnsupportedFile())
|
||||
];
|
||||
|
||||
onCleanup(() => subs.forEach((s) => s.unsubscribe()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If you want to load an external file that does not come from ACS you
|
||||
* can use this URL to specify where to load the file from.
|
||||
@@ -197,6 +226,13 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
|
||||
} else if (this.urlFile) {
|
||||
this.setUpUrlFile();
|
||||
}
|
||||
if (this.viewerType === 'pdf' && !this.pdfViewerComponent) {
|
||||
console.error(
|
||||
'@alfresco/adf-core: PDF viewer is not configured. ' +
|
||||
'Add providePdfViewer() from @alfresco/adf-core/viewer/pdf to your application providers.'
|
||||
);
|
||||
}
|
||||
this.updatePdfViewerInputs();
|
||||
}
|
||||
|
||||
markAsLoaded() {
|
||||
@@ -250,4 +286,15 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
|
||||
onClose() {
|
||||
this.close.next(true);
|
||||
}
|
||||
|
||||
private updatePdfViewerInputs(): void {
|
||||
this.pdfViewerInputs = {
|
||||
urlFile: this.urlFile,
|
||||
blobFile: this.blobFile,
|
||||
fileName: this.internalFileName,
|
||||
allowThumbnails: this.allowThumbnails,
|
||||
thumbnailsTemplate: this.thumbnailsTemplate,
|
||||
cacheType: this.cacheTypeForContent
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
*/
|
||||
|
||||
export * from './services/view-util.service';
|
||||
export * from './tokens/pdf-viewer.token';
|
||||
export * from './tokens/pdf-viewer-ref';
|
||||
export * from './components/img-viewer/img-viewer.component';
|
||||
export * from './components/media-player/media-player.component';
|
||||
export * from './components/pdf-viewer-password-dialog/pdf-viewer-password-dialog';
|
||||
export * from './components/pdf-viewer/pdf-viewer.component';
|
||||
export * from './components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
|
||||
export * from './components/pdf-viewer-thumb/pdf-viewer-thumb.component';
|
||||
export * from './components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
|
||||
export * from './components/txt-viewer/txt-viewer.component';
|
||||
export * from './components/unknown-format/unknown-format.component';
|
||||
export * from './components/viewer-more-actions.component';
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
import { EventEmitter, TemplateRef } from '@angular/core';
|
||||
|
||||
export interface PdfViewerRef {
|
||||
urlFile: string;
|
||||
blobFile: Blob;
|
||||
fileName: string;
|
||||
allowThumbnails: boolean;
|
||||
thumbnailsTemplate: TemplateRef<unknown>;
|
||||
cacheType: string;
|
||||
pagesLoaded: EventEmitter<void>;
|
||||
error: EventEmitter<void>;
|
||||
close: EventEmitter<void>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
import { InjectionToken, Type } from '@angular/core';
|
||||
import { PdfViewerRef } from './pdf-viewer-ref';
|
||||
|
||||
export const PDF_VIEWER_COMPONENT = new InjectionToken<Type<PdfViewerRef> | null>('PDF_VIEWER_COMPONENT', {
|
||||
providedIn: 'root',
|
||||
factory: () => null
|
||||
});
|
||||
@@ -19,10 +19,6 @@ import { NgModule } from '@angular/core';
|
||||
import { DownloadPromptDialogComponent } from './components/download-prompt-dialog/download-prompt-dialog.component';
|
||||
import { ImgViewerComponent } from './components/img-viewer/img-viewer.component';
|
||||
import { MediaPlayerComponent } from './components/media-player/media-player.component';
|
||||
import { PdfPasswordDialogComponent } from './components/pdf-viewer-password-dialog/pdf-viewer-password-dialog';
|
||||
import { PdfThumbComponent } from './components/pdf-viewer-thumb/pdf-viewer-thumb.component';
|
||||
import { PdfThumbListComponent } from './components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
|
||||
import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component';
|
||||
import { TxtViewerComponent } from './components/txt-viewer/txt-viewer.component';
|
||||
import { UnknownFormatComponent } from './components/unknown-format/unknown-format.component';
|
||||
import { ViewerMoreActionsComponent } from './components/viewer-more-actions.component';
|
||||
@@ -36,14 +32,10 @@ import { ViewerComponent } from './components/viewer.component';
|
||||
import { ViewerExtensionDirective } from './directives/viewer-extension.directive';
|
||||
|
||||
export const VIEWER_DIRECTIVES = [
|
||||
PdfPasswordDialogComponent,
|
||||
ViewerRenderComponent,
|
||||
ImgViewerComponent,
|
||||
TxtViewerComponent,
|
||||
MediaPlayerComponent,
|
||||
PdfViewerComponent,
|
||||
PdfThumbComponent,
|
||||
PdfThumbListComponent,
|
||||
ViewerExtensionDirective,
|
||||
UnknownFormatComponent,
|
||||
ViewerToolbarComponent,
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# @alfresco/adf-core/viewer/pdf
|
||||
|
||||
Secondary entrypoint that provides the PDF viewer component, extracted from `@alfresco/adf-core` to make `pdfjs-dist` an optional dependency.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Install `pdfjs-dist` (if not already present):
|
||||
|
||||
```bash
|
||||
npm install pdfjs-dist
|
||||
```
|
||||
|
||||
2. Add `providePdfViewer()` to your app providers:
|
||||
|
||||
```typescript
|
||||
import { providePdfViewer } from '@alfresco/adf-core/viewer/pdf';
|
||||
|
||||
// Standalone app (app.config.ts)
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
providePdfViewer(),
|
||||
// ...other providers
|
||||
]
|
||||
};
|
||||
|
||||
// NgModule-based app (app.module.ts)
|
||||
@NgModule({
|
||||
providers: [providePdfViewer()]
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
That's it. The viewer will render PDFs using the real `PdfViewerComponent`.
|
||||
|
||||
## Migration from v8.x
|
||||
|
||||
If upgrading from a version where `PdfViewerComponent` was part of `@alfresco/adf-core` directly, run:
|
||||
|
||||
```bash
|
||||
ng update @alfresco/adf-core
|
||||
```
|
||||
|
||||
The migration schematic will:
|
||||
- Rewrite imports of PDF symbols (`PdfViewerComponent`, `PdfPasswordDialogComponent`, `PdfThumbListComponent`, `PdfThumbComponent`, `PDFJS_MODULE`, `PDFJS_VIEWER_MODULE`, `RenderingQueueServices`) from `@alfresco/adf-core` to `@alfresco/adf-core/viewer/pdf`
|
||||
- Add `providePdfViewer()` to your app's providers array
|
||||
|
||||
## Opting out of PDF support
|
||||
|
||||
If your application does not need to render PDFs, you can skip installing `pdfjs-dist` and omit `providePdfViewer()`. The viewer will display an "unknown format" placeholder for PDF files.
|
||||
|
||||
## Exports
|
||||
|
||||
| Symbol | Description |
|
||||
|--------|-------------|
|
||||
| `providePdfViewer()` | Provider function that registers the PDF viewer |
|
||||
| `PdfViewerComponent` | The PDF viewer component |
|
||||
| `PdfPasswordDialogComponent` | Password dialog for protected PDFs |
|
||||
| `PdfThumbListComponent` | PDF thumbnail list |
|
||||
| `PdfThumbComponent` | Individual PDF thumbnail |
|
||||
| `PDFJS_MODULE` | Injection token for pdfjs-dist library |
|
||||
| `PDFJS_VIEWER_MODULE` | Injection token for pdfjs viewer module |
|
||||
| `RenderingQueueServices` | Service managing PDF page render queue |
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
@alfresco/adf-core (primary entrypoint)
|
||||
- Exports: PDF_VIEWER_COMPONENT token, PdfViewerRef interface
|
||||
- ViewerRenderComponent injects the token via NgComponentOutlet
|
||||
- Does NOT depend on pdfjs-dist
|
||||
|
||||
@alfresco/adf-core/viewer/pdf (this entrypoint)
|
||||
- Imports from: @alfresco/adf-core, pdfjs-dist
|
||||
- Exports: PdfViewerComponent, providePdfViewer(), etc.
|
||||
```
|
||||
|
||||
The dependency is one-way (secondary -> primary). The primary entrypoint never imports from this package, avoiding circular dependencies and keeping `pdfjs-dist` out of the main bundle.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"lib": {
|
||||
"entryFile": "src/index.ts",
|
||||
"styleIncludePaths": [
|
||||
"../../src/lib"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* @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 * from './lib/components/pdf-viewer/pdf-viewer.component';
|
||||
export * from './lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
|
||||
export * from './lib/components/pdf-viewer-thumb/pdf-viewer-thumb.component';
|
||||
export * from './lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog';
|
||||
export * from './lib/services/rendering-queue.services';
|
||||
export * from './lib/provide-pdf-viewer';
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,53 @@
|
||||
/*!
|
||||
* @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 const annotations = [
|
||||
{
|
||||
subtype: 'Text',
|
||||
name: 'NoIcon',
|
||||
id: 'R13',
|
||||
titleObj: {
|
||||
str: 'Annotation title'
|
||||
},
|
||||
contentsObj: {
|
||||
str: 'Annotation contents'
|
||||
},
|
||||
modificationDate: "D:20260202104106Z00'00",
|
||||
popupRef: 'R1'
|
||||
}
|
||||
];
|
||||
export default {
|
||||
GlobalWorkerOptions: {},
|
||||
getDocument: jasmine.createSpy('getDocument').and.callFake(() => ({
|
||||
loadingTask: () => ({
|
||||
destroy: () => Promise.resolve()
|
||||
}),
|
||||
promise: new Promise((resolve) => {
|
||||
resolve({
|
||||
numPages: 6,
|
||||
getPage: () =>
|
||||
Promise.resolve({
|
||||
getAnnotations: () => annotations
|
||||
})
|
||||
});
|
||||
})
|
||||
})),
|
||||
PasswordResponses: {
|
||||
NEED_PASSWORD: 1,
|
||||
INCORRECT_PASSWORD: 2
|
||||
}
|
||||
};
|
||||
+1
-1
@@ -23,7 +23,7 @@ import { ReactiveFormsModule, UntypedFormControl, Validators } from '@angular/fo
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { IconModule } from '../../../icon/icon.module';
|
||||
import { IconModule } from '@alfresco/adf-core';
|
||||
|
||||
declare const pdfjsLib: { PasswordResponses: { NEED_PASSWORD: number; INCORRECT_PASSWORD: number } };
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { PdfThumbListComponent } from './pdf-viewer-thumbnails.component';
|
||||
import { UnitTestingUtils } from '../../../testing';
|
||||
import { UnitTestingUtils } from '@alfresco/adf-core';
|
||||
import { DOWN_ARROW, ESCAPE, UP_ARROW } from '@angular/cdk/keycodes';
|
||||
|
||||
declare const pdfjsViewer: any;
|
||||
@@ -0,0 +1,99 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { PDF_VIEWER_COMPONENT, ViewerRenderComponent, UnitTestingUtils } from '@alfresco/adf-core';
|
||||
import { PdfViewerComponent, PDFJS_MODULE, PDFJS_VIEWER_MODULE } from './pdf-viewer.component';
|
||||
import { providePdfViewer } from '../../provide-pdf-viewer';
|
||||
import pdfjsLibraryMock from '../mock/pdfjs-lib.mock';
|
||||
|
||||
describe('PdfViewer Integration with ViewerRenderComponent', () => {
|
||||
let fixture: ComponentFixture<ViewerRenderComponent>;
|
||||
let component: ViewerRenderComponent;
|
||||
let testingUtils: UnitTestingUtils;
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ViewerRenderComponent],
|
||||
providers: [
|
||||
providePdfViewer(),
|
||||
{ provide: PDFJS_MODULE, useValue: pdfjsLibraryMock },
|
||||
{ provide: PDFJS_VIEWER_MODULE, useValue: class {} }
|
||||
]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(ViewerRenderComponent);
|
||||
component = fixture.componentInstance;
|
||||
testingUtils = new UnitTestingUtils(fixture.debugElement);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should provide PdfViewerComponent via token', () => {
|
||||
const token = TestBed.inject(PDF_VIEWER_COMPONENT);
|
||||
expect(token).toBe(PdfViewerComponent);
|
||||
});
|
||||
|
||||
it('should render real PdfViewerComponent for PDF files', async () => {
|
||||
component.urlFile = 'fake-test-file.pdf';
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.viewerType).toBe('pdf');
|
||||
expect(testingUtils.getByCSS('adf-pdf-viewer')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should pass inputs to real PdfViewerComponent', async () => {
|
||||
component.urlFile = 'fake-test-file.pdf';
|
||||
component.allowThumbnails = true;
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
const pdfViewer = testingUtils.getByCSS('adf-pdf-viewer');
|
||||
expect(pdfViewer).not.toBeNull();
|
||||
expect(pdfViewer.componentInstance.urlFile).toBe('fake-test-file.pdf');
|
||||
expect(pdfViewer.componentInstance.allowThumbnails).toBe(true);
|
||||
});
|
||||
|
||||
it('should show unknown format when token is not provided', async () => {
|
||||
TestBed.resetTestingModule();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ViewerRenderComponent],
|
||||
providers: [{ provide: PDF_VIEWER_COMPONENT, useValue: null }]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(ViewerRenderComponent);
|
||||
component = fixture.componentInstance;
|
||||
testingUtils = new UnitTestingUtils(fixture.debugElement);
|
||||
|
||||
component.urlFile = 'fake-test-file.pdf';
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.viewerType).toBe('pdf');
|
||||
expect(testingUtils.getByCSS('adf-pdf-viewer')).toBeNull();
|
||||
expect(testingUtils.getByCSS('adf-viewer-unknown-format')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { provideCoreAuthTesting } from '@alfresco/adf-core';
|
||||
import { PdfViewerComponent } from './pdf-viewer.component';
|
||||
import { RenderingQueueServices } from '../../services/rendering-queue.services';
|
||||
import * as pdfjsLib from 'pdfjs-dist/build/pdf.min.mjs';
|
||||
|
||||
const MINIMAL_PDF_BASE64 =
|
||||
'JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog' +
|
||||
'IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv' +
|
||||
'TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K' +
|
||||
'Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg' +
|
||||
'L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+' +
|
||||
'PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u' +
|
||||
'dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq' +
|
||||
'Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU' +
|
||||
'CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu' +
|
||||
'ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g' +
|
||||
'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' +
|
||||
'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' +
|
||||
'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G';
|
||||
|
||||
/** @returns a Blob containing a minimal valid 1-page PDF */
|
||||
function createRealPdfBlob(): Blob {
|
||||
const binaryString = atob(MINIMAL_PDF_BASE64);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return new Blob([bytes], { type: 'application/pdf' });
|
||||
}
|
||||
|
||||
@Component({
|
||||
imports: [PdfViewerComponent],
|
||||
template: `<adf-pdf-viewer [blobFile]="blobFile" />`
|
||||
})
|
||||
class RealPdfTestHostComponent {
|
||||
@ViewChild(PdfViewerComponent, { static: true })
|
||||
pdfViewerComponent: PdfViewerComponent;
|
||||
|
||||
blobFile = createRealPdfBlob();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param predicate - condition to wait for
|
||||
* @param timeout - max wait time in ms
|
||||
* @returns a promise that resolves when the predicate returns true
|
||||
*/
|
||||
function waitFor(predicate: () => boolean, timeout = 5000): Promise<void> {
|
||||
const start = Date.now();
|
||||
return new Promise((resolve, reject) => {
|
||||
const check = () => {
|
||||
if (predicate()) {
|
||||
resolve();
|
||||
} else if (Date.now() - start > timeout) {
|
||||
reject(new Error('waitFor timed out'));
|
||||
} else {
|
||||
setTimeout(check, 50);
|
||||
}
|
||||
};
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
describe('PdfViewerComponent with real pdfjs-dist', () => {
|
||||
let fixture: ComponentFixture<RealPdfTestHostComponent>;
|
||||
let component: PdfViewerComponent;
|
||||
|
||||
beforeAll(async () => {
|
||||
const workerModule = await import('pdfjs-dist/build/pdf.worker.min.mjs');
|
||||
(globalThis as any).pdfjsWorker = { WorkerMessageHandler: workerModule.WorkerMessageHandler };
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
delete (globalThis as any).pdfjsWorker;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = '';
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RealPdfTestHostComponent],
|
||||
providers: [provideCoreAuthTesting(), { provide: MatDialog, useValue: { open: () => {} } }, RenderingQueueServices]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(RealPdfTestHostComponent);
|
||||
component = fixture.componentInstance.pdfViewerComponent;
|
||||
|
||||
spyOn(component as any, 'setupPdfJsWorker').and.resolveTo();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should load a real PDF blob and detect page count', async () => {
|
||||
fixture.detectChanges();
|
||||
await waitFor(() => component.totalPages > 0);
|
||||
expect(component.totalPages).toBe(1);
|
||||
});
|
||||
|
||||
it('should emit pagesLoaded when a real PDF is rendered', async () => {
|
||||
const pagesLoadedSpy = spyOn(component.pagesLoaded, 'emit');
|
||||
fixture.detectChanges();
|
||||
await waitFor(() => pagesLoadedSpy.calls.count() > 0);
|
||||
expect(pagesLoadedSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/mat-selectors' as ms;
|
||||
@use 'styles/mat-selectors' as ms;
|
||||
|
||||
.adf-pdf-viewer {
|
||||
width: 100%;
|
||||
+2
-4
@@ -21,9 +21,7 @@ import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { of } from 'rxjs';
|
||||
import { AppConfigService } from '../../../app-config';
|
||||
import { EventMock } from '../../../mock';
|
||||
import { UnitTestingUtils, provideCoreAuthTesting } from '../../../testing';
|
||||
import { AppConfigService, EventMock, UnitTestingUtils, provideCoreAuthTesting } from '@alfresco/adf-core';
|
||||
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';
|
||||
@@ -461,7 +459,7 @@ describe('Test PdfViewer - User interaction', () => {
|
||||
|
||||
component.urlFile = './fake-test-file.pdf';
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({
|
||||
component.ngOnChanges({
|
||||
urlFile: new SimpleChange(null, './fake-test-file.pdf', true)
|
||||
});
|
||||
|
||||
+2
-4
@@ -40,8 +40,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import { from, Subject, switchMap } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { AppConfigService } from '../../../app-config';
|
||||
import { ToolbarComponent, ToolbarDividerComponent } from '../../../toolbar';
|
||||
import { AppConfigService, IconModule, ToolbarComponent, ToolbarDividerComponent } from '@alfresco/adf-core';
|
||||
import { RenderingQueueServices } from '../../services/rendering-queue.services';
|
||||
import { PdfPasswordDialogComponent } from '../pdf-viewer-password-dialog/pdf-viewer-password-dialog';
|
||||
import { PdfThumbListComponent } from '../pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
|
||||
@@ -49,7 +48,6 @@ import * as pdfjsLib from 'pdfjs-dist/build/pdf.min.mjs';
|
||||
import { PDFDateString } from 'pdfjs-dist/build/pdf.min.mjs';
|
||||
import { EventBus, PDFViewer } from 'pdfjs-dist/web/pdf_viewer.mjs';
|
||||
import { OnProgressParameters, PDFDocumentLoadingTask, PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist/types/src/display/api';
|
||||
import { IconModule } from '../../../icon/icon.module';
|
||||
|
||||
export type PdfScaleMode = 'init' | 'page-actual' | 'page-width' | 'page-height' | 'page-fit' | 'auto';
|
||||
|
||||
@@ -630,7 +628,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
|
||||
* @param event.source.container.id - the container id
|
||||
*/
|
||||
onPageChange(event: PageChangingEvent) {
|
||||
if (event.source && event.source.container.id === `${this.randomPdfId}-viewer-pdf-viewer`) {
|
||||
if (event.source?.container.id === `${this.randomPdfId}-viewer-pdf-viewer`) {
|
||||
this.page = event.pageNumber;
|
||||
this.displayPage = event.pageNumber;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
import { Provider } from '@angular/core';
|
||||
import { PDF_VIEWER_COMPONENT } from '@alfresco/adf-core';
|
||||
import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component';
|
||||
|
||||
/** @returns Provider that registers the PdfViewerComponent for the viewer */
|
||||
export function providePdfViewer(): Provider {
|
||||
return { provide: PDF_VIEWER_COMPONENT, useValue: PdfViewerComponent };
|
||||
}
|
||||
@@ -21,9 +21,6 @@
|
||||
]
|
||||
},
|
||||
"allowedNonPeerDependencies": [
|
||||
"@alfresco/adf-core",
|
||||
"@alfresco/adf-content-services",
|
||||
"@ngx-translate/core",
|
||||
"chart.js",
|
||||
"ng2-charts",
|
||||
"raphael"
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
"url": "https://github.com/Alfresco/alfresco-ng2-components/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alfresco/adf-core": ">=8.4.1",
|
||||
"@alfresco/adf-content-services": ">=8.4.1",
|
||||
"@ngx-translate/core": ">=17.0.0",
|
||||
"chart.js": "^4.3.0",
|
||||
"ng2-charts": "^4.1.1",
|
||||
"raphael": ">=2.3.0"
|
||||
@@ -23,7 +20,10 @@
|
||||
"@angular/compiler": ">=14.1.3",
|
||||
"@angular/core": ">=14.1.3",
|
||||
"@angular/forms": ">=14.1.3",
|
||||
"@angular/material": ">=14.1.2"
|
||||
"@angular/material": ">=14.1.2",
|
||||
"@alfresco/adf-core": ">=8.4.1",
|
||||
"@alfresco/adf-content-services": ">=8.4.1",
|
||||
"@ngx-translate/core": ">=17.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"analytics",
|
||||
|
||||
@@ -26,8 +26,5 @@
|
||||
"../core/src/lib"
|
||||
]
|
||||
},
|
||||
"allowedNonPeerDependencies": [
|
||||
"@apollo/client",
|
||||
"apollo-angular"
|
||||
]
|
||||
"allowedNonPeerDependencies": []
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"@alfresco/adf-core/breadcrumbs": ["lib/core/breadcrumbs/src/index.ts"],
|
||||
"@alfresco/adf-core/feature-flags": ["lib/core/feature-flags/public-api.ts"],
|
||||
"@alfresco/adf-core/shell": ["lib/core/shell/src/index.ts"],
|
||||
"@alfresco/adf-core/viewer/pdf": ["lib/core/viewer/pdf/src/index.ts"],
|
||||
"@alfresco/adf-extensions": ["lib/extensions/src/public-api.ts"],
|
||||
"@alfresco/adf-insights": ["lib/insights/src/public-api.ts"],
|
||||
"@alfresco/adf-process-services": ["lib/process-services/src/public-api.ts"],
|
||||
|
||||
Reference in New Issue
Block a user