[ACS-5184] Show progress spinner on file navigation (#10596)

* [ACS-5184] show progress spinner on file navigation

* [ACS-5184] show spinner unitl content is ready

* [ACS-5184] unit test fix

* [ACS-5184] cr fix

* [ACS-5184] spelling error fix
This commit is contained in:
Mykyta Maliarchuk 2025-04-07 14:13:34 +02:00 committed by GitHub
parent 2d21340947
commit cd63f67e0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 140 additions and 22 deletions

View File

@ -8,6 +8,7 @@
<img #image id="viewer-image"
[src]="urlFile"
[alt]="fileName"
(load)="imageLoaded.emit()"
(error)="onImageError()" />
</div>

View File

@ -80,6 +80,9 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
@Output()
isSaving = new EventEmitter<boolean>();
@Output()
imageLoaded = new EventEmitter<void>();
@ViewChild('image', { static: false })
imageElement: ElementRef;

View File

@ -1,4 +1,4 @@
<video controls class="adf-video-player"
<video controls class="adf-video-player" (canplay)="canPlay.emit()"
[ngClass]="{ 'adf-audio-file': mimeType && mimeType.startsWith('audio') }">
<source [src]="urlFile"
[type]="mimeType"

View File

@ -49,6 +49,9 @@ export class MediaPlayerComponent implements OnChanges {
@Output()
error = new EventEmitter<any>();
@Output()
canPlay = new EventEmitter<void>();
constructor(private urlService: UrlService) {}
ngOnChanges(changes: SimpleChanges) {

View File

@ -103,6 +103,9 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
@Output()
close = new EventEmitter<any>();
@Output()
pagesLoaded = new EventEmitter<void>();
page: number;
displayPage: number;
totalPages: number;
@ -565,6 +568,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
*
*/
onPagesLoaded() {
this.pagesLoaded.emit();
this.isPanelDisabled = false;
}

View File

@ -1,16 +1,13 @@
<div *ngIf="isLoading"
class="adf-viewer-render-main">
<div *ngIf="(viewerType === 'media' || viewerType === 'pdf' || viewerType === 'image') ? isLoading || !isContentReady : isLoading"
class="adf-viewer-render-main-loader">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container">
<ng-container *ngIf="isLoading">
<div class="adf-viewer-render__loading-screen">
<div class="adf-viewer-render__loading-screen ">
<h2>{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div>
<mat-spinner class="adf-viewer-render__loading-screen__spinner" />
<mat-spinner class="adf-viewer-render__loading-screen__spinner"/>
</div>
</div>
</ng-container>
</div>
</div>
</div>
@ -35,6 +32,7 @@
[urlFile]="urlFile"
[fileName]="internalFileName"
[cacheType]="cacheTypeForContent"
(pagesLoaded)="isContentReady = true"
(close)="onClose()"
(error)="onUnsupportedFile()" />
</ng-container>
@ -47,6 +45,7 @@
[blobFile]="blobFile"
(error)="onUnsupportedFile()"
(submit)="onSubmitFile($event)"
(imageLoaded)="isContentReady = true"
(isSaving)="isSaving.emit($event)"
/>
</ng-container>
@ -58,7 +57,8 @@
[mimeType]="mimeType"
[blobFile]="blobFile"
[fileName]="internalFileName"
(error)="onUnsupportedFile()" />
(error)="onUnsupportedFile()"
(canPlay)="isContentReady = true"/>
</ng-container>
<ng-container *ngSwitchCase="'text'">

View File

@ -7,6 +7,18 @@
background-color: var(--adf-theme-background-card-color);
}
.adf-viewer-render-main-loader {
position: fixed;
top: 64px;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.adf-viewer-render {
&-main {
width: 0;

View File

@ -18,13 +18,13 @@
import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions';
import { Location } from '@angular/common';
import { SpyLocation } from '@angular/common/testing';
import { Component, TemplateRef, ViewChild } from '@angular/core';
import { Component, DebugElement, TemplateRef, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { NoopTranslateModule, UnitTestingUtils } from '../../../testing';
import { RenderingQueueServices } from '../../services/rendering-queue.services';
import { ViewerRenderComponent } from './viewer-render.component';
import { ViewerExtensionDirective } from '@alfresco/adf-core';
import { ImgViewerComponent, MediaPlayerComponent, PdfViewerComponent, ViewerExtensionDirective } from '@alfresco/adf-core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@Component({
@ -476,4 +476,75 @@ describe('ViewerComponent', () => {
});
});
});
describe('Spinner', () => {
const getMainLoader = (): DebugElement => testingUtils.getByCSS('.adf-viewer-render-main-loader');
it('should show spinner when isLoading is true', () => {
component.isLoading = true;
fixture.detectChanges();
expect(getMainLoader()).not.toBeNull();
});
it('should show spinner until content is ready when viewerType is media', () => {
component.isLoading = false;
component.urlFile = 'some-file.mp4';
component.ngOnChanges();
fixture.detectChanges();
expect(getMainLoader()).not.toBeNull();
const mediaViewer = testingUtils.getByDirective(MediaPlayerComponent);
mediaViewer.triggerEventHandler('canPlay', null);
fixture.detectChanges();
expect(getMainLoader()).toBeNull();
expect(component.viewerType).toBe('media');
});
it('should show spinner until content is ready when viewerType is pdf', () => {
component.isLoading = false;
component.urlFile = 'some-url.pdf';
component.ngOnChanges();
fixture.detectChanges();
expect(getMainLoader()).not.toBeNull();
const pdfViewer = testingUtils.getByDirective(PdfViewerComponent);
pdfViewer.triggerEventHandler('pagesLoaded', null);
fixture.detectChanges();
expect(getMainLoader()).toBeNull();
expect(component.viewerType).toBe('pdf');
});
it('should show spinner until content is ready when viewerType is image', () => {
component.isLoading = false;
component.urlFile = 'some-url.png';
component.ngOnChanges();
fixture.detectChanges();
expect(getMainLoader()).not.toBeNull();
const imgViewer = testingUtils.getByDirective(ImgViewerComponent);
imgViewer.triggerEventHandler('imageLoaded', null);
fixture.detectChanges();
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.isContentReady).toBeFalse();
});
});
});

View File

@ -142,6 +142,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
extension: string;
internalFileName: string;
viewerType: string = 'unknown';
isContentReady = false;
/**
* Returns a list of the active Viewer content extensions.
@ -184,6 +185,7 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
}
ngOnChanges() {
this.isContentReady = false;
this.isLoading = !this.blobFile && !this.urlFile;
if (this.blobFile) {

View File

@ -121,6 +121,26 @@ describe('ViewerComponent', () => {
expect(thumbnailService.getMimeTypeIcon).toHaveBeenCalledWith('application/pdf');
expect(component.mimeTypeIconUrl).toBe('application/pdf');
});
it('should reset urlFile and blobFile on onNavigateBeforeClick', () => {
component.urlFile = 'some-url';
component.blobFile = new Blob(['content'], { type: 'text/plain' });
component.onNavigateBeforeClick(new MouseEvent('click'));
expect(component.urlFile).toBe('');
expect(component.blobFile).toBeNull();
});
it('should reset urlFile and blobFile on onNavigateNextClick', () => {
component.urlFile = 'some-url';
component.blobFile = new Blob(['content'], { type: 'text/plain' });
component.onNavigateNextClick(new MouseEvent('click'));
expect(component.urlFile).toBe('');
expect(component.blobFile).toBeNull();
});
});
describe('File Name Test', () => {

View File

@ -387,10 +387,12 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
}
onNavigateBeforeClick(event: MouseEvent | KeyboardEvent) {
this.resetLoadingSpinner();
this.navigateBefore.next(event);
}
onNavigateNextClick(event: MouseEvent | KeyboardEvent) {
this.resetLoadingSpinner();
this.navigateNext.next(event);
}
@ -416,22 +418,17 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
return;
}
const key = event.keyCode;
// Left arrow
if (key === 37 && this.canNavigateBefore) {
if (event.key === 'ArrowLeft' && this.canNavigateBefore) {
event.preventDefault();
this.onNavigateBeforeClick(event);
}
// Right arrow
if (key === 39 && this.canNavigateNext) {
if (event.key === 'ArrowRight' && this.canNavigateNext) {
event.preventDefault();
this.onNavigateNextClick(event);
}
// Ctrl+F
if (key === 70 && event.ctrlKey) {
if (event.code === 'KeyF' && event.ctrlKey) {
event.preventDefault();
this.enterFullScreen();
}
@ -527,4 +524,9 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
});
}
}
private resetLoadingSpinner() {
this.urlFile = '';
this.blobFile = null;
}
}