[AAE-35649] Viewer renderer wait until subsequent renderers will finish (#10924)

* [AAE-35649] Viewer renderer wait until particular renderers are done

* [AAE-35649] Add docs and unit test coverage

* [AAE-35649] Add missing space
This commit is contained in:
MichalKinas 2025-06-11 09:28:07 +02:00 committed by GitHub
parent 0c7bf62097
commit 5d9acaec0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 87 additions and 51 deletions

View File

@ -245,16 +245,17 @@ The Viewer supports dynamically-loaded viewer preview extensions, to know more a
You can define your own custom handler to override supported file formats or handle other file formats that are not yet supported by
the component. Below is an example that shows how to use the `adf-viewer-extension`
to handle 3D data files:
to handle 3D data files. `contentLoaded` should be an `EventEmitter` that will emit as soon as the component responsible for rendering finishes.
```html
<adf-alfresco-viewer [nodeId]="nodeId">
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['obj','3ds']" #extension>
<ng-template let-urlFileContent="urlFileContent" let-extension="extension">
<ng-template let-urlFileContent="urlFileContent" let-extension="extension" let-markAsLoaded="markAsLoaded">
<threed-viewer
[urlFile]="urlFileContent"
[extension]="extension">
[extension]="extension"
(contentLoaded)="markAsLoaded()">
</threed-viewer>
</ng-template>
</adf-viewer-extension>
@ -270,17 +271,19 @@ You need to keep all instances of `adf-viewer-extension` inside `viewerExtension
<adf-alfresco-viewer [nodeId]="nodeId">
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['xls','xlsx']" #extension>
<ng-template let-urlFileContent="urlFileContent">
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-xls-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-xls-component>
</ng-template>
</adf-viewer-extension>
<adf-viewer-extension [supportedExtensions]="['txt']" #extension>
<ng-template let-urlFileContent="urlFileContent" >
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-txt-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-txt-component>
</ng-template>
</adf-viewer-extension>

View File

@ -206,22 +206,24 @@ The Viewer supports dynamically-loaded viewer preview extensions, to know more a
#### Code extension mechanism
You can define your own custom handler to override supported file formats or handle other file formats that are not yet supported by
the [Viewer render component](viewer.component.md). In order to do that first you need to define a template containing at least one `adf-viewer-extension`:
the [Viewer render component](viewer.component.md). In order to do that first you need to define a template containing at least one `adf-viewer-extension`. `contentLoaded` should be an `EventEmitter` that will emit as soon as the component responsible for rendering finishes.
```html
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['xls','xlsx']" #extension>
<ng-template let-urlFileContent="urlFileContent">
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-xls-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-xls-component>
</ng-template>
</adf-viewer-extension>
<adf-viewer-render-extension [supportedExtensions]="['txt']" #extension>
<ng-template let-urlFileContent="urlFileContent" >
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-txt-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-txt-component>
</ng-template>
</adf-viewer-render-extension>

View File

@ -215,16 +215,17 @@ The Viewer supports dynamically-loaded viewer preview extensions, to know more a
You can define your own custom handler to override supported file formats or handle other file formats that are not yet supported by
the [Viewer component](viewer.component.md). Below is an example that shows how to use the `adf-viewer-extension`
to handle 3D data files:
to handle 3D data files. `contentLoaded` should be an `EventEmitter` that will emit as soon as the component responsible for rendering finishes.
```html
<adf-viewer [urlFile]="urlFile">
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['obj','3ds']" #extension>
<ng-template let-urlFileContent="urlFileContent" let-extension="extension">
<ng-template let-urlFileContent="urlFileContent" let-extension="extension" let-markAsLoaded="markAsLoaded">
<threed-viewer
[urlFile]="urlFileContent"
[extension]="extension">
[extension]="extension"
(contentLoaded)="markAsLoaded()">
</threed-viewer>
</ng-template>
</adf-viewer-extension>
@ -239,17 +240,19 @@ You need to keep all instances of `adf-viewer-extension` inside `viewerExtension
<adf-viewer [urlFile]="urlFile">
<ng-template #viewerExtensions>
<adf-viewer-extension [supportedExtensions]="['xls','xlsx']" #extension>
<ng-template let-urlFileContent="urlFileContent">
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded">
<my-custom-xls-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-xls-component>
</ng-template>
</adf-viewer-extension>
<adf-viewer-extension [supportedExtensions]="['txt']" #extension>
<ng-template let-urlFileContent="urlFileContent" >
<ng-template let-urlFileContent="urlFileContent" let-markAsLoaded="markAsLoaded" >
<my-custom-txt-component
urlFileContent="urlFileContent">
urlFileContent="urlFileContent"
(contentLoaded)="markAsLoaded()">
</my-custom-txt-component>
</ng-template>
</adf-viewer-extension>

View File

@ -42,6 +42,7 @@ describe('Text View component', () => {
describe('View', () => {
it('Should text container be present with urlFile', async () => {
spyOn(component.contentLoaded, 'emit');
fixture.detectChanges();
const urlFile = './fake-test-file.txt';
const change = new SimpleChange(null, urlFile, true);
@ -52,9 +53,11 @@ describe('Text View component', () => {
await fixture.whenStable();
expect(testingUtils.getByCSS('.adf-txt-viewer-content').nativeElement.textContent).toContain('example');
expect(component.contentLoaded.emit).toHaveBeenCalled();
});
it('Should text container be present with Blob file', async () => {
spyOn(component.contentLoaded, 'emit');
const blobFile = new Blob(['text example'], { type: 'text/txt' });
const change = new SimpleChange(null, blobFile, true);
@ -65,6 +68,7 @@ describe('Text View component', () => {
await fixture.whenStable();
expect(testingUtils.getByCSS('.adf-txt-viewer-content').nativeElement.textContent).toContain('example');
expect(component.contentLoaded.emit).toHaveBeenCalled();
});
});
});

View File

@ -16,7 +16,7 @@
*/
import { HttpClient } from '@angular/common/http';
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { AppConfigService } from '../../../app-config';
@Component({
@ -34,6 +34,9 @@ export class TxtViewerComponent implements OnChanges {
@Input()
blobFile: Blob;
@Output()
contentLoaded = new EventEmitter<void>();
content: string | ArrayBuffer;
constructor(private http: HttpClient, private appConfigService: AppConfigService) {}
@ -67,6 +70,9 @@ export class TxtViewerComponent implements OnChanges {
},
(event) => {
reject(event);
},
() => {
this.contentLoaded.emit();
}
);
});
@ -78,13 +84,17 @@ export class TxtViewerComponent implements OnChanges {
reader.onload = () => {
this.content = reader.result;
resolve();
};
reader.onerror = (error: any) => {
reject(error);
};
reader.onloadend = () => {
this.contentLoaded.emit();
resolve();
};
reader.readAsText(blob);
});
}

View File

@ -23,6 +23,7 @@
[extension]="externalViewer.fileExtension"
[nodeId]="nodeId"
[attr.data-automation-id]="externalViewer.component"
(contentLoaded)="markAsLoaded()"
/>
</ng-container>
@ -68,7 +69,7 @@
</ng-container>
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFile" [blobFile]="blobFile" />
<adf-txt-viewer [urlFile]="urlFile" [blobFile]="blobFile" (contentLoaded)="markAsLoaded()" />
</ng-container>
<ng-container *ngSwitchCase="'custom'">
@ -80,6 +81,7 @@
[extension]="extension"
[nodeId]="nodeId"
[attr.data-automation-id]="ext.component"
(contentLoaded)="markAsLoaded()"
/>
</ng-container>
@ -87,7 +89,7 @@
<span *ngIf="extensionTemplate.isVisible" class="adf-viewer-render-custom-content">
<ng-template
[ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension }"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension, markAsLoaded: markAsLoaded.bind(this) }"
/>
</span>
</ng-container>
@ -101,5 +103,5 @@
</div>
}
<ng-container *ngIf="viewerTemplateExtensions">
<ng-template [ngTemplateOutlet]="viewerTemplateExtensions" [ngTemplateOutletInjector]="injector" />
<ng-template [ngTemplateOutlet]="viewerTemplateExtensions" [ngTemplateOutletContext]="{ urlFile: urlFile, extension: extension, markAsLoaded: markAsLoaded.bind(this) }" [ngTemplateOutletInjector]="injector" />
</ng-container>

View File

@ -520,7 +520,8 @@ describe('ViewerComponent', () => {
expect(component.viewerType).toBe('pdf');
});
it('should show spinner until content is ready when viewerType is image', () => {
it('should show spinner until renderer calls markAsLoaded', () => {
spyOn(component, 'markAsLoaded').and.callThrough();
component.isLoading = false;
component.urlFile = 'some-url.png';
@ -534,16 +535,7 @@ describe('ViewerComponent', () => {
expect(getMainLoader()).toBeNull();
expect(component.viewerType).toBe('image');
});
it('should not show spinner when isLoading = false and isContentReady = false for other viewer types', () => {
component.isLoading = false;
component.urlFile = 'some-url.txt';
component.ngOnChanges();
fixture.detectChanges();
expect(getMainLoader()).toBeNull();
expect(component.markAsLoaded).toHaveBeenCalled();
});
});
});

View File

@ -178,10 +178,11 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
ngOnInit() {
this.cacheTypeForContent = 'no-cache';
this.setDefaultLoadingState();
this.isLoading = true;
}
ngOnChanges() {
this.isLoading = true;
if (this.blobFile) {
this.setUpBlobData();
} else if (this.urlFile) {
@ -196,6 +197,9 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
private setUpBlobData() {
this.internalFileName = this.fileName;
this.viewerType = this.viewUtilService.getViewerTypeByMimeType(this.blobFile.type);
if (this.viewerType === 'unknown') {
this.isLoading = false;
}
this.extensionChange.emit(this.blobFile.type);
this.scrollTop();
@ -205,6 +209,9 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
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.extensionsSupportedByTemplates);
if (this.viewerType === 'unknown') {
this.isLoading = false;
}
this.extensionChange.emit(this.extension);
this.scrollTop();
@ -233,14 +240,4 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
onClose() {
this.close.next(true);
}
private canBePreviewed(): boolean {
return this.viewerType === 'media' || this.viewerType === 'pdf' || this.viewerType === 'image';
}
private setDefaultLoadingState() {
if (this.canBePreviewed()) {
this.isLoading = true;
}
}
}

View File

@ -15,8 +15,22 @@
* limitations under the License.
*/
import { Component, Input, ComponentRef, OnInit, ViewChild, ViewContainerRef, OnDestroy, OnChanges } from '@angular/core';
import {
Component,
Input,
ComponentRef,
OnInit,
ViewChild,
ViewContainerRef,
OnDestroy,
OnChanges,
EventEmitter,
Output,
DestroyRef,
inject
} from '@angular/core';
import { ExtensionService } from '../../services/extension.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'adf-preview-extension',
@ -43,6 +57,11 @@ export class PreviewExtensionComponent implements OnInit, OnChanges, OnDestroy {
@Input()
extension: string;
@Output()
contentLoaded = new EventEmitter<void>();
private readonly destroyRef = inject(DestroyRef);
private componentRef: ComponentRef<any>;
constructor(private extensionService: ExtensionService) {}
@ -73,11 +92,15 @@ export class PreviewExtensionComponent implements OnInit, OnChanges, OnDestroy {
private updateInstance() {
if (this.componentRef?.instance) {
const instance = this.componentRef.instance;
this.componentRef.setInput('url', this.url);
this.componentRef.setInput('extension', this.extension);
this.componentRef.setInput('nodeId', this.nodeId);
instance.url = this.url;
instance.extension = this.extension;
instance.nodeId = this.nodeId;
if (this.componentRef.instance?.contentLoaded) {
this.componentRef.instance.contentLoaded.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.contentLoaded.emit();
});
}
}
}
}