[ACA-4676][ACA-4678] Added Non Responsive Preview Dialog to viewer component (#8428)

* [ACA-4676] Added NonResponsivePreview dialog to download file incase file preview takes longer than a set period of time.

* [ACA-4676] Updated button positioning for non responsive preview dialog

* [ACA-4676] Added documentation for NonResponsivePreviewDialog functionality for viewer.component.ts

* [ACA-4676] Added unit tests for NonResponsivePreviewDialog

* [ACA-4676] Updated template of NonResponsivePreviewDialog to use components and directives from mat-dialog. Removed non-responsive-dialog.component.scss. Removed unused methods from non-responsive-dialog.component.ts

* [ACA-4676] Corrected typo in NonResponsivePreviewDialog unit tests

* [ACA-4676] Added test cases for NonResponsivePreviewDialog in viewer.component.ts. NOT WORKING

* [ACA-4676] Fixed test cases for non-responsive preview dialog. Moved NonResponsivePreview dialog tests to separate describe block. Updated component code to make properties and methods visible to testing environment

* [ACA-4676] Migrated viewer component test env setup from setupTestBed() to TestBed.configureTestingModule(). Moved NonResponsivePreviewDialog unit tests to inside parent Viewer component describe block

* [ACA-4676] Removed unused async tag. Added license info to non-responsive-dialog.component.ts and non-responsive-preview-actions.enum.ts

* [ACA-4676] Updated code to use "viewer" appConfig object instead of "preview-config". Added non-responsive-preview-actions.enum.ts to public-api.ts

* [ACA-4676] Resolved potential lint issues

* [ACA-4676] Updated non responsive preview to look for viewer config object inside app.config instead of preview-config

* [ACA-4676] Removed duplicate import for @adf/core. Added NonResponsiveDialogComponent to adf/core exports

* [ACA-4676] Renamed properties/config/documentation from nonResponsivePreview to downloadPrompt. Renamed NonResponsivePreviewActionsEnum to DownloadPromptActions.

* [ACA-4676] Resolved linting and unit test failures

* [ACA-4676] Changed dataType for timers to number. Updated code to use window.setTimeout(), instead of just setTimeout(). Added missing whitespace. Updated method names in demo shell to use 'downloadPrompt' naming scheme.

* [ACA-4676] Fixed incorrect import statement in viewer.module.ts for download-prompt-dialog

* [ACA-4676] Testing disabled by default behaviour of downloadPrompt feature

* [ACA-4676] Changed default value for enableDownloadPrompt and enableDownloadPromptReminders to false in app.config.json

* [ACA-4676] Removed un-needed AppConfig configurations from unit tests
This commit is contained in:
swapnil-verma-gl
2023-04-12 19:32:34 +05:30
committed by GitHub
parent 662dfd9c3a
commit 0eb0ff167b
13 changed files with 423 additions and 29 deletions

View File

@@ -391,7 +391,8 @@
"FULLSCREEN": "Activate full-screen mode",
"CLOSE": "Close",
"NEXT_FILE": "Next File",
"PREV_FILE": "Previous File"
"PREV_FILE": "Previous File",
"WAIT": "Wait"
},
"ARIA": {
"PREVIOUS_PAGE": "Previous page",
@@ -429,7 +430,11 @@
"PLACEHOLDER": "Password",
"ERROR": "Password is wrong"
},
"SUBTITLES": "Subtitles"
"SUBTITLES": "Subtitles",
"NON_RESPONSIVE_DIALOG": {
"HEADER": "Preview loading delayed",
"LABEL": "You can continue to wait, or download the document."
}
},
"ERROR_CONTENT": {
"UNKNOWN": {

View File

@@ -0,0 +1,23 @@
<div mat-dialog-title>
<h3>{{ 'ADF_VIEWER.NON_RESPONSIVE_DIALOG.HEADER' | translate }}</h3>
</div>
<mat-dialog-content>
{{ 'ADF_VIEWER.NON_RESPONSIVE_DIALOG.LABEL' | translate }}
</mat-dialog-content>
<mat-dialog-actions align="end">
<button
mat-button
id="downloadButton"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate"
[mat-dialog-close]="DownloadPromptActions.DOWNLOAD">
{{ 'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate }}
</button>
<button
mat-button
id="waitButton"
color="primary"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.WAIT' | translate"
[mat-dialog-close]="DownloadPromptActions.WAIT">
{{ 'ADF_VIEWER.ACTIONS.WAIT' | translate }}
</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,73 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { CoreTestingModule, DownloadPromptDialogComponent, DownloadPromptActions } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { MatDialogRef } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
const mockDialog = {
close: jasmine.createSpy('close')
};
describe('DownloadPromptDialogComponent', () => {
let matDialogRef: MatDialogRef<DownloadPromptDialogComponent>;
let fixture: ComponentFixture<DownloadPromptDialogComponent>;
const getButton = (buttonId: string) => {
return fixture.debugElement.query(By.css(buttonId)).nativeElement;
};
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [DownloadPromptDialogComponent],
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{ provide: MatDialogRef, useValue: mockDialog }
]
});
matDialogRef = TestBed.inject(MatDialogRef);
fixture = TestBed.createComponent(DownloadPromptDialogComponent);
fixture.detectChanges();
});
it('should emit DownloadPromptActions.WAIT and close dialog when clicking on the wait button', async () => {
const waitButton = getButton('#waitButton');
waitButton.dispatchEvent(new Event('click'));
await fixture.detectChanges();
await fixture.whenStable();
expect(matDialogRef.close).toHaveBeenCalledWith(DownloadPromptActions.WAIT);
});
it('should emit DownloadPromptActions.DOWNLOAD and close dialog when clicking on the download button', async () => {
const waitButton = getButton('#downloadButton');
waitButton.dispatchEvent(new Event('click'));
await fixture.detectChanges();
await fixture.whenStable();
expect(matDialogRef.close).toHaveBeenCalledWith(DownloadPromptActions.DOWNLOAD);
});
});

View File

@@ -0,0 +1,27 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 } from '@angular/core';
import { DownloadPromptActions } from '../../models/download-prompt.actions';
@Component({
selector: 'adf-download-prompt-dialog',
templateUrl: './download-prompt-dialog.component.html'
})
export class DownloadPromptDialogComponent {
DownloadPromptActions = DownloadPromptActions;
}

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog';
@@ -24,12 +24,15 @@ import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import {
CoreTestingModule,
setupTestBed,
EventMock,
ViewerComponent,
ViewUtilService
ViewUtilService,
AppConfigService,
DownloadPromptDialogComponent,
DownloadPromptActions
} from '@alfresco/adf-core';
import { Component } from '@angular/core';
import { of } from 'rxjs';
@Component({
selector: 'adf-viewer-container-toolbar',
@@ -135,35 +138,48 @@ describe('ViewerComponent', () => {
let element: HTMLElement;
let dialog: MatDialog;
let viewUtilService: ViewUtilService;
setupTestBed({
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
CoreTestingModule,
MatButtonModule,
MatIconModule
],
declarations: [
ViewerWithCustomToolbarComponent,
ViewerWithCustomSidebarComponent,
ViewerWithCustomOpenWithComponent,
ViewerWithCustomMoreActionsComponent,
ViewerWithCustomToolbarActionsComponent
],
providers: [
MatDialog
]
});
let appConfigService: AppConfigService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
CoreTestingModule,
MatButtonModule,
MatIconModule
],
declarations: [
ViewerWithCustomToolbarComponent,
ViewerWithCustomSidebarComponent,
ViewerWithCustomOpenWithComponent,
ViewerWithCustomMoreActionsComponent,
ViewerWithCustomToolbarActionsComponent
],
providers: [
MatDialog,
{ provide: DownloadPromptDialogComponent, useClass: DummyDialogComponent}
]
});
fixture = TestBed.createComponent(ViewerComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
dialog = TestBed.inject(MatDialog);
viewUtilService = TestBed.inject(ViewUtilService);
appConfigService = TestBed.inject(AppConfigService);
component.fileName = 'test-file.pdf';
appConfigService.config = {
...appConfigService.config,
'viewer': {
'enableDownloadPrompt': false,
'enableDownloadPromptReminder': false,
'downloadPromptDelay': 3,
'downloadPromptReminderDelay': 2
}
};
});
afterEach(() => {
@@ -604,4 +620,60 @@ describe('ViewerComponent', () => {
});
});
});
describe('Download Prompt Dialog',() => {
let dialogOpenSpy: jasmine.Spy;
beforeEach(() => {
appConfigService.config = {
...appConfigService.config,
'viewer': {
'enableDownloadPrompt': true,
'enableDownloadPromptReminder': true,
'downloadPromptDelay': 3,
'downloadPromptReminderDelay': 2
}
};
dialogOpenSpy = spyOn(dialog, 'open').and.returnValue({afterClosed: () => of(null)} as any);
component.urlFile = undefined;
component.clearDownloadPromptTimeouts();
});
it('should configure initial timeout to display non responsive dialog when initialising component', (() => {
fixture.detectChanges();
expect(component.downloadPromptTimer).toBeDefined();
}));
it('should configure reminder timeout to display non responsive dialog after initial dialog', fakeAsync( () => {
dialogOpenSpy.and.returnValue({ afterClosed: () => of(DownloadPromptActions.WAIT) } as any);
fixture.detectChanges();
tick(3000);
expect(component.downloadPromptReminderTimer).toBeDefined();
dialogOpenSpy.and.returnValue({ afterClosed: () => of(null) } as any);
flush();
discardPeriodicTasks();
}));
it('should show initial non responsive dialog after initial timeout', fakeAsync( () => {
fixture.detectChanges();
tick(3000);
fixture.detectChanges();
expect(dialogOpenSpy).toHaveBeenCalled();
}));
it('should show reminder non responsive dialog after initial dialog', fakeAsync( () => {
dialogOpenSpy.and.returnValue({ afterClosed: () => of(DownloadPromptActions.WAIT) } as any);
fixture.detectChanges();
tick(3000);
expect(dialogOpenSpy).toHaveBeenCalled();
dialogOpenSpy.and.returnValue({ afterClosed: () => of(null) } as any);
tick(2000);
expect(dialogOpenSpy).toHaveBeenCalledTimes(2);
flush();
discardPeriodicTasks();
}));
});
});

View File

@@ -30,15 +30,25 @@ import {
TemplateRef,
ViewEncapsulation
} from '@angular/core';
import { fromEvent, Subject } from 'rxjs';
import { fromEvent, Subject } from 'rxjs';
import { MatDialog } from '@angular/material/dialog';
import { ViewerToolbarComponent } from './viewer-toolbar.component';
import { ViewerOpenWithComponent } from './viewer-open-with.component';
import { ViewerMoreActionsComponent } from './viewer-more-actions.component';
import { ViewerSidebarComponent } from './viewer-sidebar.component';
import { filter, skipWhile, takeUntil } from 'rxjs/operators';
import { filter, first, skipWhile, takeUntil } from 'rxjs/operators';
import { Track } from '../models/viewer.model';
import { ViewUtilService } from '../services/view-util.service';
import { DownloadPromptDialogComponent } from './download-prompt-dialog/download-prompt-dialog.component';
import { AppConfigService } from '../../app-config';
import { DownloadPromptActions } from '../models/download-prompt.actions';
const DEFAULT_NON_PREVIEW_CONFIG = {
enableDownloadPrompt: false,
enableDownloadPromptReminder: false,
downloadPromptDelay: 50,
downloadPromptReminderDelay: 30
};
@Component({
selector: 'adf-viewer',
@@ -160,6 +170,26 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
@Input()
sidebarLeftTemplateContext: T = null;
/**
* Enable dialog box to allow user to download the previewed file, in case the preview is not responding for a set period of time.
* */
enableDownloadPrompt: boolean = false;
/**
* Enable reminder dialogs to prompt user to download the file, in case the preview is not responding for a set period of time.
* */
enableDownloadPromptReminder: boolean = false;
/**
* Initial time in seconds to wait before giving the first prompt to user to download the file
* */
downloadPromptDelay: number = 50;
/**
* Time in seconds to wait before giving the second and consequent reminders to the user to download the file.
* */
downloadPromptReminderDelay: number = 15;
/** Emitted when user clicks 'Navigate Before' ("<") button. */
@Output()
navigateBefore = new EventEmitter<MouseEvent | KeyboardEvent>();
@@ -180,10 +210,14 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
private closeViewer = true;
private keyDown$ = fromEvent<KeyboardEvent>(document, 'keydown');
private isDialogVisible: boolean = false;
public downloadPromptTimer: number;
public downloadPromptReminderTimer: number;
constructor(private el: ElementRef,
public dialog: MatDialog,
private viewUtilsService: ViewUtilService
private viewUtilsService: ViewUtilService,
private appConfigService: AppConfigService
) {
}
@@ -202,6 +236,7 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
ngOnInit(): void {
this.closeOverlayManager();
this.configureAndInitDownloadPrompt();
}
private closeOverlayManager() {
@@ -304,8 +339,64 @@ export class ViewerComponent<T> implements OnDestroy, OnInit, OnChanges {
}
ngOnDestroy() {
this.clearDownloadPromptTimeouts();
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
private configureAndInitDownloadPrompt() {
this.configureDownloadPromptProperties();
if (this.enableDownloadPrompt) {
this.initDownloadPrompt();
}
}
private configureDownloadPromptProperties() {
const nonResponsivePreviewConfig = this.appConfigService.get('viewer', DEFAULT_NON_PREVIEW_CONFIG);
this.enableDownloadPrompt = nonResponsivePreviewConfig.enableDownloadPrompt;
this.enableDownloadPromptReminder = nonResponsivePreviewConfig.enableDownloadPromptReminder;
this.downloadPromptDelay = nonResponsivePreviewConfig.downloadPromptDelay;
this.downloadPromptReminderDelay = nonResponsivePreviewConfig.downloadPromptReminderDelay;
}
private initDownloadPrompt() {
this.downloadPromptTimer = window.setTimeout(() => {
this.showOrClearDownloadPrompt();
}, this.downloadPromptDelay * 1000);
}
private showOrClearDownloadPrompt() {
if (!this.urlFile) {
this.showDownloadPrompt();
} else {
this.clearDownloadPromptTimeouts();
}
}
public clearDownloadPromptTimeouts() {
if (this.downloadPromptTimer) {
clearTimeout(this.downloadPromptTimer);
}
if (this.downloadPromptReminderTimer) {
clearTimeout(this.downloadPromptReminderTimer);
}
}
private showDownloadPrompt() {
if (!this.isDialogVisible) {
this.isDialogVisible = true;
this.dialog.open(DownloadPromptDialogComponent, { disableClose: true }).afterClosed().pipe(first()).subscribe((result: DownloadPromptActions) => {
this.isDialogVisible = false;
if (result === DownloadPromptActions.WAIT) {
if (this.enableDownloadPromptReminder) {
this.clearDownloadPromptTimeouts();
this.downloadPromptReminderTimer = window.setTimeout(() => {
this.showOrClearDownloadPrompt();
}, this.downloadPromptReminderDelay * 1000);
}
}
});
}
}
}

View File

@@ -0,0 +1,22 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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.
*/
/* Enum listing the allowed actions that can be emitted from the NonResponsivePreview dialog component */
export enum DownloadPromptActions {
'WAIT',
'DOWNLOAD'
}

View File

@@ -33,9 +33,11 @@ export * from './components/viewer-toolbar-actions.component';
export * from './components/viewer-toolbar-custom-actions.component';
export * from './components/viewer-render.component';
export * from './components/viewer.component';
export * from './components/download-prompt-dialog/download-prompt-dialog.component';
export * from './directives/viewer-extension.directive';
export * from './viewer.module';
export * from './models/viewer.model';
export * from './models/download-prompt.actions';

View File

@@ -45,6 +45,7 @@ import { DirectiveModule } from '../directives/directive.module';
import { A11yModule } from '@angular/cdk/a11y';
import { ViewerComponent } from './components/viewer.component';
import { ViewerToolbarCustomActionsComponent } from './components/viewer-toolbar-custom-actions.component';
import { DownloadPromptDialogComponent } from './components/download-prompt-dialog/download-prompt-dialog.component';
@NgModule({
imports: [
@@ -77,7 +78,8 @@ import { ViewerToolbarCustomActionsComponent } from './components/viewer-toolbar
ViewerMoreActionsComponent,
ViewerToolbarActionsComponent,
ViewerComponent,
ViewerToolbarCustomActionsComponent
ViewerToolbarCustomActionsComponent,
DownloadPromptDialogComponent
],
exports: [
ViewerRenderComponent,