[ACS-6620] Proper viewer extension template projection to viewer renderer (#9273)

* [ACS-6620] Proper viewer extension template projection to viewer renderer

* [ACS-6620] Use extensions instead of content for viewer config

* [ACS-6620] Lint fix
This commit is contained in:
MichalKinas
2024-01-25 19:36:59 +01:00
committed by GitHub
parent 5ec8228504
commit 2c627f1166
17 changed files with 265 additions and 111 deletions

View File

@@ -24,6 +24,7 @@
[urlFile]="urlFileContent"
[tracks]="tracks"
[readOnly]="readOnly"
[viewerExtensions]="viewerExtensions"
(downloadFile)="onDownloadFile()"
(navigateBefore)="onNavigateBeforeClick($event)"
(navigateNext)="onNavigateNextClick($event)"

View File

@@ -80,6 +80,9 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit, OnDestroy {
@ContentChild(ViewerOpenWithComponent)
openWith: ViewerOpenWithComponent;
@ContentChild('viewerExtensions', { static: false })
viewerExtensions: TemplateRef<any>;
/** Node Id of the file to load. */
@Input()
nodeId: string = null;

View File

@@ -79,13 +79,13 @@
</adf-preview-extension>
</ng-container>
<span class="adf-viewer-render-custom-content"
*ngFor="let extensionTemplate of extensionTemplates">
<ng-template *ngIf="extensionTemplate.isVisible"
[ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension:extension }">
</ng-template>
</span>
<ng-container *ngFor="let extensionTemplate of extensionTemplates">
<span *ngIf="extensionTemplate.isVisible" class="adf-viewer-render-custom-content">
<ng-template [ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension }">
</ng-template>
</span>
</ng-container>
</ng-container>
<ng-container *ngSwitchDefault>
@@ -94,3 +94,6 @@
</div>
</div>
</div>
<ng-container *ngIf="viewerTemplateExtensions">
<ng-template [ngTemplateOutlet]="viewerTemplateExtensions" [ngTemplateOutletInjector]="injector"></ng-template>
</ng-container>

View File

@@ -17,7 +17,7 @@
import { Location } from '@angular/common';
import { SpyLocation } from '@angular/common/testing';
import { Component, ViewChild } from '@angular/core';
import { Component, TemplateRef, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RenderingQueueServices } from '../services/rendering-queue.services';
import { ViewerRenderComponent } from './viewer-render.component';
@@ -33,8 +33,20 @@ import { By } from '@angular/platform-browser';
@Component({
selector: 'adf-double-viewer',
template: `
<adf-viewer-render [urlFile]="urlFileViewer1" #viewer1></adf-viewer-render>
<adf-viewer-render [urlFile]="urlFileViewer1" [viewerTemplateExtensions]="viewerTemplateExtensions" #viewer1></adf-viewer-render>
<adf-viewer-render [urlFile]="urlFileViewer2" #viewer2></adf-viewer-render>
<ng-template #viewerExtension>
<adf-viewer-extension [supportedExtensions]="['json']">
<ng-template>
<h1>JSON Viewer</h1>
</ng-template>
</adf-viewer-extension>
<adf-viewer-extension [supportedExtensions]="['test']">
<ng-template>
<h1>Test Viewer</h1>
</ng-template>
</adf-viewer-extension>
</ng-template>
`
})
class DoubleViewerComponent {
@@ -44,6 +56,9 @@ class DoubleViewerComponent {
@ViewChild('viewer2')
viewer2: ViewerRenderComponent;
@ViewChild('viewerExtension', { static: true })
viewerTemplateExtensions: TemplateRef<any>;
urlFileViewer1: string;
urlFileViewer2: string;
@@ -261,6 +276,42 @@ describe('ViewerComponent', () => {
});
});
describe('Custom viewer extension template', () => {
const getCustomViewerContent = (customFixture: ComponentFixture<DoubleViewerComponent>): HTMLHeadingElement =>
customFixture.debugElement.query(By.css('.adf-viewer-render-custom-content h1')).nativeElement;
it('should render provided custom template when file type matches supported extensions', async () => {
const fixtureCustom = TestBed.createComponent(DoubleViewerComponent);
fixtureCustom.detectChanges();
await fixtureCustom.whenStable();
const customComponent = fixtureCustom.componentInstance.viewer1;
fixtureCustom.componentInstance.urlFileViewer1 = 'fake-url-file.json';
customComponent.ngOnChanges();
fixtureCustom.detectChanges();
await fixtureCustom.whenStable();
let customContent = getCustomViewerContent(fixtureCustom);
expect(customComponent.extensionsSupportedByTemplates).toEqual(['json', 'test']);
expect(customComponent.extensionTemplates.length).toBe(2);
expect(customComponent.extensionTemplates[0].isVisible).toBeTrue();
expect(customComponent.extensionTemplates[1].isVisible).toBeFalse();
expect(customContent.innerText).toBe('JSON Viewer');
fixtureCustom.componentInstance.urlFileViewer1 = 'fake-url-file.test';
customComponent.ngOnChanges();
fixtureCustom.detectChanges();
await fixtureCustom.whenStable();
customContent = getCustomViewerContent(fixtureCustom);
expect(customComponent.extensionTemplates[0].isVisible).toBeFalse();
expect(customComponent.extensionTemplates[1].isVisible).toBeTrue();
expect(customContent.innerText).toBe('Test Viewer');
});
});
describe('MimeType handling', () => {
it('should display an image file identified by mimetype when the filename has no extension', (done) => {
component.urlFile = 'fake-content-img';
@@ -338,7 +389,6 @@ describe('ViewerComponent', () => {
expect(element.querySelector('adf-pdf-viewer')).not.toBeNull();
done();
});
}, 25000);
it('should display a PDF file identified by mimetype when the file extension is wrong', (done) => {

View File

@@ -18,7 +18,7 @@
import {
Component, EventEmitter,
Input, OnChanges, Output, TemplateRef,
ViewEncapsulation, OnInit, OnDestroy
ViewEncapsulation, OnInit, OnDestroy, Injector
} from '@angular/core';
import { Subject } from 'rxjs';
import { ViewUtilService } from '../services/view-util.service';
@@ -79,6 +79,10 @@ export class ViewerRenderComponent implements OnChanges, OnInit, OnDestroy {
@Input()
tracks: Track[] = [];
/** Template containing ViewerExtensionDirective instances providing different viewer extensions based on supported file extension. */
@Input()
viewerTemplateExtensions: TemplateRef<any>;
/** Emitted when the filename extension changes. */
@Output()
extensionChange = new EventEmitter<string>();
@@ -96,6 +100,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit, OnDestroy {
isSaving = new EventEmitter<boolean>();
extensionTemplates: { template: TemplateRef<any>; isVisible: boolean }[] = [];
extensionsSupportedByTemplates: string[] = [];
extension: string;
internalFileName: string;
viewerType: string = 'unknown';
@@ -133,7 +138,8 @@ export class ViewerRenderComponent implements OnChanges, OnInit, OnDestroy {
constructor(private viewUtilService: ViewUtilService,
private extensionService: AppExtensionService,
public dialog: MatDialog) {
public dialog: MatDialog,
public readonly injector: Injector) {
}
ngOnInit() {
@@ -166,7 +172,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit, OnDestroy {
private setUpUrlFile() {
this.internalFileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile);
this.extension = this.viewUtilService.getFileExtension(this.internalFileName);
this.viewerType = this.viewUtilService.getViewerType(this.extension, this.mimeType);
this.viewerType = this.viewUtilService.getViewerType(this.extension, this.mimeType, this.extensionsSupportedByTemplates);
this.extensionChange.emit(this.extension);
this.scrollTop();

View File

@@ -171,7 +171,8 @@
(submitFile)="onSubmitFile($event)"
[urlFile]="urlFile"
(isSaving)="allowNavigate = !$event"
[tracks]="tracks">
[tracks]="tracks"
[viewerTemplateExtensions]="viewerExtensions ?? viewerTemplateExtensions">
</adf-viewer-render>
</div>

View File

@@ -71,6 +71,9 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
@ContentChild(ViewerMoreActionsComponent)
mnuMoreActions: ViewerMoreActionsComponent;
@ContentChild('viewerExtensions', { static: false })
viewerTemplateExtensions: TemplateRef<any>;
get CloseButtonPosition() {
return CloseButtonPosition;
}
@@ -190,6 +193,10 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
@Input()
hideInfoButton = false;
/** Template containing ViewerExtensionDirective instances providing different viewer extensions based on supported file extension. */
@Input()
viewerExtensions: TemplateRef<any>;
/**
* Enable dialog box to allow user to download the previewed file, in case the preview is not responding for a set period of time.
*/

View File

@@ -26,6 +26,7 @@ import { TranslateModule } from '@ngx-translate/core';
describe('ExtensionViewerDirective', () => {
let extensionViewerDirective: ViewerExtensionDirective;
let viewerRenderer: ViewerRenderComponent;
class MockElementRef extends ElementRef {
constructor() {
@@ -42,12 +43,13 @@ describe('ExtensionViewerDirective', () => {
providers: [
{ provide: Location, useClass: SpyLocation },
ViewerExtensionDirective,
{provide: ElementRef, useClass: MockElementRef},
{ provide: ElementRef, useClass: MockElementRef },
ViewerRenderComponent,
{ provide: ChangeDetectorRef, useValue: { detectChanges: () => {} } }
]
});
extensionViewerDirective = TestBed.inject(ViewerExtensionDirective);
viewerRenderer = TestBed.inject(ViewerRenderComponent);
extensionViewerDirective.templateModel = {template: '', isVisible: false};
});
@@ -64,4 +66,12 @@ describe('ExtensionViewerDirective', () => {
extensionViewerDirective.supportedExtensions = ['xls', 'sts'];
expect(extensionViewerDirective.isVisible('png')).not.toBeTruthy();
});
it('should set correct template and supported extensions in viewer renderer component', () => {
extensionViewerDirective.supportedExtensions = ['png', 'txt'];
extensionViewerDirective.ngAfterContentInit();
expect(viewerRenderer.extensionTemplates.length).toBe(1);
expect(viewerRenderer.extensionTemplates[0]).toEqual(extensionViewerDirective.templateModel);
expect(viewerRenderer.extensionsSupportedByTemplates).toEqual(extensionViewerDirective.supportedExtensions);
});
});

View File

@@ -46,7 +46,7 @@ export class ViewerExtensionDirective implements AfterContentInit, OnDestroy {
ngAfterContentInit() {
this.templateModel = { template: this.template, isVisible: false };
this.viewerComponent.extensionsSupportedByTemplates.push(...this.supportedExtensions);
this.viewerComponent.extensionTemplates.push(this.templateModel);
this.viewerComponent.extensionChange
@@ -54,12 +54,6 @@ export class ViewerExtensionDirective implements AfterContentInit, OnDestroy {
.subscribe(fileExtension => {
this.templateModel.isVisible = this.isVisible(fileExtension);
});
if (this.supportedExtensions instanceof Array) {
this.supportedExtensions.forEach((extension) => {
this.viewerComponent.externalExtensions.push(extension);
});
}
}
ngOnDestroy() {

View File

@@ -0,0 +1,78 @@
/*!
* @license
* Copyright © 2005-2023 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 { AppExtensionService } from '@alfresco/adf-extensions';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { ViewUtilService } from './view-util.service';
describe('ViewUtilService', () => {
let viewUtilService: ViewUtilService;
let appExtensionService: AppExtensionService;
const extensionsSupportedByTemplates = ['dmn', 'txt'];
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AppExtensionService]
});
viewUtilService = TestBed.inject(ViewUtilService);
appExtensionService = TestBed.inject(AppExtensionService);
});
it('should extract file name from url', () => {
expect(viewUtilService.getFilenameFromUrl('http://localhost/test.jpg?cache=1000')).toBe('test.jpg');
expect(viewUtilService.getFilenameFromUrl('http://localhost:4200/test-path/test2.json#cache=1000')).toBe('test2.json');
});
it('should extract file extension from url', () => {
expect(viewUtilService.getFileExtension('http://localhost/test.jpg?cache=1000')).toBe('jpg');
expect(viewUtilService.getFileExtension('http://localhost:4200/test-path/test2.json#cache=1000')).toBe('json');
});
it('should return correct viewer type based on file mime type', () => {
expect(viewUtilService.getViewerTypeByMimeType('text/plain')).toBe('text');
expect(viewUtilService.getViewerTypeByMimeType('application/pdf')).toBe('pdf');
expect(viewUtilService.getViewerTypeByMimeType('image/gif')).toBe('image');
expect(viewUtilService.getViewerTypeByMimeType('video/webm')).toBe('media');
expect(viewUtilService.getViewerTypeByMimeType('image/test')).toBe('unknown');
});
it('should check if extension is custom one added either by extension service or by a template in viewer renderer', () => {
spyOn(appExtensionService, 'getViewerExtensions').and.returnValue([{ fileExtension: 'json', component: 'test', id: 'test' }]);
expect(viewUtilService.isCustomViewerExtension('pdf')).toBeFalse();
expect(viewUtilService.isCustomViewerExtension('txt')).toBeFalse();
expect(viewUtilService.isCustomViewerExtension('json')).toBeTrue();
expect(viewUtilService.isCustomViewerExtension('docx', extensionsSupportedByTemplates)).toBeFalse();
expect(viewUtilService.isCustomViewerExtension('dmn', extensionsSupportedByTemplates)).toBeTrue();
expect(viewUtilService.isCustomViewerExtension('txt', extensionsSupportedByTemplates)).toBeTrue();
});
it('should return correct viewer type based on extension and mime type', () => {
spyOn(appExtensionService, 'getViewerExtensions').and.returnValue([{ fileExtension: '*', component: 'test', id: 'test' }]);
expect(viewUtilService.getViewerType('pdf', 'application/pdf')).toBe('external');
appExtensionService.getViewerExtensions = jasmine.createSpy().and.returnValue([{ fileExtension: 'json', component: 'test', id: 'test' }]);
expect(viewUtilService.getViewerType('json', '')).toBe('custom');
expect(viewUtilService.getViewerType('dmn', '')).toBe('unknown');
expect(viewUtilService.getViewerType('dmn', '', extensionsSupportedByTemplates)).toBe('custom');
expect(viewUtilService.getViewerType('pdf', '')).toBe('pdf');
expect(viewUtilService.getViewerType('', 'application/pdf')).toBe('pdf');
});
});

View File

@@ -88,8 +88,8 @@ export class ViewUtilService {
return null;
}
getViewerType(extension: string, mimeType: string): string {
let viewerType = this.getViewerTypeByExtension(extension);
getViewerType(extension: string, mimeType: string, extensionsSupportedByTemplates?: string[]): string {
let viewerType = this.getViewerTypeByExtension(extension, extensionsSupportedByTemplates);
if (viewerType === 'unknown') {
viewerType = this.getViewerTypeByMimeType(mimeType);
@@ -112,7 +112,7 @@ export class ViewUtilService {
return 'unknown';
}
private getViewerTypeByExtension(extension: string): string {
private getViewerTypeByExtension(extension: string, extensionsSupportedByTemplates?: string[]): string {
if (extension) {
extension = extension.toLowerCase();
}
@@ -121,7 +121,7 @@ export class ViewUtilService {
return 'external';
}
if (this.isCustomViewerExtension(extension)) {
if (this.isCustomViewerExtension(extension, extensionsSupportedByTemplates)) {
return 'custom';
}
@@ -148,8 +148,11 @@ export class ViewUtilService {
return !!this.viewerExtensions.find((ext) => ext.fileExtension === '*');
}
isCustomViewerExtension(extension: string): boolean {
isCustomViewerExtension(extension: string, extensionsSupportedByTemplates?: string[]): boolean {
const extensions = this.externalExtensions || [];
if (extensionsSupportedByTemplates) {
extensions.push(...extensionsSupportedByTemplates);
}
if (extension && extensions.length > 0) {
extension = extension.toLowerCase();

View File

@@ -72,7 +72,7 @@ export class AppExtensionService {
*/
getViewerExtensions(): ViewerExtensionRef[] {
return this.extensionService
.getElements<ViewerExtensionRef>('features.viewer.content')
.getElements<ViewerExtensionRef>('features.viewer.extensions')
.filter((extension) => !this.isViewerExtensionDisabled(extension));
}

View File

@@ -96,7 +96,7 @@ export class ExtensionLoaderService {
* Retrieves configuration elements.
* Filters element by **enabled** and **order** attributes.
* Example:
* `getElements<ViewerExtensionRef>(config, 'features.viewer.content')`
* `getElements<ViewerExtensionRef>(config, 'features.viewer.extensions')`
*
* @param config configuration settings
* @param key element key