diff --git a/docs/extensions/components/preview-extension.component.md b/docs/extensions/components/preview-extension.component.md index 369af0bc06..3ba64711a4 100644 --- a/docs/extensions/components/preview-extension.component.md +++ b/docs/extensions/components/preview-extension.component.md @@ -96,6 +96,29 @@ You also need to provide a [viewer component](../../core/components/viewer.compo } ``` +You can also use `*` wildcard to register a single component that opens all files: + +```json +{ + "$version": "1.0.0", + "$name": "my viewer extension", + "$description": "my viewer plugin", + "features": { + "viewer": { + "content": [ + { + "id": "dev.tools.viewer.viewer", + "fileExtension": ["*"], + "component": "your-extension.main.component" + } + ] + } + } +} +``` + +> It is recommended to use wildcard replacement only when introducing your own Viewer implementation. + See the [App extensions](../../user-guide/app-extensions.md) page for further details of how to develop extensions. diff --git a/lib/core/viewer/components/viewer.component.html b/lib/core/viewer/components/viewer.component.html index dcacaa3142..347b9356d8 100644 --- a/lib/core/viewer/components/viewer.component.html +++ b/lib/core/viewer/components/viewer.component.html @@ -199,8 +199,17 @@ fxFlexOrder="1" fxFlex="1 1 auto">
-
+
+ + + + { let element: HTMLElement; let dialog: MatDialog; let uploadService: UploadService; + let extensionService: AppExtensionService; setupTestBed({ imports: [ @@ -173,12 +175,65 @@ describe('ViewerComponent', () => { uploadService = TestBed.inject(UploadService); alfrescoApiService = TestBed.inject(AlfrescoApiService); dialog = TestBed.inject(MatDialog); + extensionService = TestBed.inject(AppExtensionService); + }); + + afterEach(() => { + fixture.destroy(); }); describe('Extension Type Test', () => { + it('should use external viewer via wildcard notation', async () => { + const extension: ViewerExtensionRef = { + component: 'custom.component', + id: 'custom.component.id', + fileExtension: '*' + }; + spyOn(extensionService, 'getViewerExtensions').and.returnValue([extension]); - afterEach(() => { - fixture.destroy(); + fixture = TestBed.createComponent(ViewerComponent); + element = fixture.nativeElement; + component = fixture.componentInstance; + + component.urlFile = 'fake-test-file.pdf'; + component.ngOnChanges(); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.externalExtensions.includes('*')).toBe(true); + expect(component.externalViewer).toBe(extension); + expect(component.viewerType).toBe('external'); + expect(element.querySelector('[data-automation-id="custom.component"]')).not.toBeNull(); + }); + + it('should use first external viewer provided', async () => { + const extensions: ViewerExtensionRef[] = [ + { + component: 'custom.component.1', + id: 'custom.component.id', + fileExtension: '*' + }, + { + component: 'custom.component.2', + id: 'custom.component.id', + fileExtension: '*' + } + ]; + spyOn(extensionService, 'getViewerExtensions').and.returnValue(extensions); + + fixture = TestBed.createComponent(ViewerComponent); + element = fixture.nativeElement; + component = fixture.componentInstance; + + component.urlFile = 'fake-test-file.pdf'; + component.ngOnChanges(); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(element.querySelector('[data-automation-id="custom.component.1"]')).not.toBeNull(); + expect(element.querySelector('[data-automation-id="custom.component.2"]')).toBeNull(); }); it('should extension file pdf be loaded', (done) => { @@ -244,11 +299,6 @@ describe('ViewerComponent', () => { }); describe('MimeType handling', () => { - - afterEach(() => { - fixture.destroy(); - }); - it('should display an image file identified by mimetype when the filename has no extension', (done) => { component.urlFile = 'fake-content-img'; component.mimeType = 'image/png'; diff --git a/lib/core/viewer/components/viewer.component.ts b/lib/core/viewer/components/viewer.component.ts index 0e21fcbeb5..7a660dfeb7 100644 --- a/lib/core/viewer/components/viewer.component.ts +++ b/lib/core/viewer/components/viewer.component.ts @@ -225,17 +225,39 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy { versionEntry: VersionEntry; extensionTemplates: { template: TemplateRef, isVisible: boolean }[] = []; - externalExtensions: string[] = []; urlFileContent: string; otherMenu: any; extension: string; sidebarRightTemplateContext: { node: Node } = { node: null }; sidebarLeftTemplateContext: { node: Node } = { node: null }; fileTitle: string; - viewerExtensions: Array = []; + + /** + * Returns a list of the active Viewer content extensions. + */ + get viewerExtensions(): ViewerExtensionRef[] { + return this.extensionService.getViewerExtensions(); + } + + /** + * Provides a list of file extensions supported by external plugins. + */ + get externalExtensions(): string[] { + return this.viewerExtensions.map(ext => ext.fileExtension); + } + + private _externalViewer: ViewerExtensionRef; + get externalViewer(): ViewerExtensionRef { + if (!this._externalViewer) { + this._externalViewer = this.viewerExtensions.find(ext => ext.fileExtension === '*'); + } + + return this._externalViewer; + } + readOnly = true; - private cacheBusterNumber; + private cacheBusterNumber: number; cacheTypeForContent = ''; // Extensions that are supported by the Viewer without conversion @@ -316,18 +338,9 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy { }); this.closeOverlayManager(); - this.loadExtensions(); this.cacheTypeForContent = ''; } - private loadExtensions() { - this.viewerExtensions = this.extensionService.getViewerExtensions(); - this.viewerExtensions - .forEach((extension: ViewerExtensionRef) => { - this.externalExtensions.push(extension.fileExtension); - }); - } - private getNodeVersionProperty(node: Node): string { return node?.properties['cm:versionLabel'] ?? ''; } @@ -427,13 +440,8 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy { this.fileTitle = this.getDisplayName(filenameFromUrl); this.extension = this.getFileExtension(filenameFromUrl); this.urlFileContent = this.urlFile; - this.fileName = this.displayName; - - this.viewerType = this.urlFileViewer || this.getViewerTypeByExtension(this.extension); - if (this.viewerType === 'unknown') { - this.viewerType = this.getViewerTypeByMimeType(this.mimeType); - } + this.viewerType = this.urlFileViewer || this.getViewerType(this.extension, this.mimeType); this.extensionChange.emit(this.extension); this.scrollTop(); @@ -442,8 +450,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy { private async setUpNodeFile(nodeData: Node, versionData?: Version) { this.readOnly = !this.contentService.hasAllowableOperations(nodeData, 'update'); - let setupNode; - if (versionData && versionData.content) { this.mimeType = versionData.content.mimeType; } else if (nodeData.content) { @@ -461,13 +467,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy { this.urlFileContent + '&' + currentFileVersion; this.extension = this.getFileExtension(versionData ? versionData.name : nodeData.name); - this.fileName = versionData ? versionData.name : nodeData.name; + this.viewerType = this.getViewerType(this.extension, this.mimeType); - this.viewerType = this.getViewerTypeByExtension(this.extension); - if (this.viewerType === 'unknown') { - this.viewerType = this.getViewerTypeByMimeType(this.mimeType); - } + let setupNode: Promise; if (this.viewerType === 'unknown') { if (versionData) { @@ -485,18 +488,23 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy { return setupNode; } + private getViewerType(extension: string, mimeType: string): string { + let viewerType = this.getViewerTypeByExtension(extension); + + if (viewerType === 'unknown') { + viewerType = this.getViewerTypeByMimeType(mimeType); + } + + return viewerType; + } + private setUpSharedLinkFile(details: any) { this.mimeType = details.entry.content.mimeType; this.fileTitle = this.getDisplayName(details.entry.name); this.extension = this.getFileExtension(details.entry.name); this.fileName = details.entry.name; - this.urlFileContent = this.contentApi.getSharedLinkContentUrl(this.sharedLinkId, false); - - this.viewerType = this.getViewerTypeByMimeType(this.mimeType); - if (this.viewerType === 'unknown') { - this.viewerType = this.getViewerTypeByExtension(this.extension); - } + this.viewerType = this.getViewerType(this.extension, this.mimeType); if (this.viewerType === 'unknown') { this.displaySharedLinkRendition(this.sharedLinkId); @@ -552,6 +560,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy { extension = extension.toLowerCase(); } + if (this.isExternalViewer()) { + return 'external'; + } + if (this.isCustomViewerExtension(extension)) { return 'custom'; } @@ -628,8 +640,12 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy { return null; } + private isExternalViewer(): boolean { + return !!this.viewerExtensions.find(ext => ext.fileExtension === '*'); + } + isCustomViewerExtension(extension: string): boolean { - const extensions: any = this.externalExtensions || []; + const extensions = this.externalExtensions || []; if (extension && extensions.length > 0) { extension = extension.toLowerCase();