diff --git a/demo-shell/src/app/components/file-view/file-view.component.html b/demo-shell/src/app/components/file-view/file-view.component.html
index 667a6db885..e1e3851c39 100644
--- a/demo-shell/src/app/components/file-view/file-view.component.html
+++ b/demo-shell/src/app/components/file-view/file-view.component.html
@@ -4,8 +4,8 @@
-
@@ -300,6 +300,7 @@
+
{
if (node && node.isFile) {
- this.isCommentEnabled = this.contentServices.hasPermissions(node, PermissionsEnum.NOT_CONSUMER) ||
- this.contentServices.hasAllowableOperations(node, AllowableOperationsEnum.UPDATE);
+ this.isCommentEnabled = this.contentService.hasPermissions(node, PermissionsEnum.NOT_CONSUMER) ||
+ this.contentService.hasAllowableOperations(node, AllowableOperationsEnum.UPDATE);
this.nodeId = id;
return;
}
@@ -90,6 +91,9 @@ export class FileViewComponent implements OnInit {
},
() => this.router.navigate(['/files', id])
);
+ } else{
+ this.urlFile = this.contentService.createTrustedUrl(this.preview.content);
+ this.filename = this.preview.name;
}
});
}
diff --git a/lib/core/src/lib/viewer/components/img-viewer.component.ts b/lib/core/src/lib/viewer/components/img-viewer.component.ts
index 3f2d30b240..1f554b9769 100644
--- a/lib/core/src/lib/viewer/components/img-viewer.component.ts
+++ b/lib/core/src/lib/viewer/components/img-viewer.component.ts
@@ -26,7 +26,6 @@ import {
EventEmitter, AfterViewInit, ViewChild, HostListener, OnDestroy
} from '@angular/core';
import { AppConfigService } from '../../app-config/app-config.service';
-import { UrlService } from '../../services/url.service';
import Cropper from 'cropperjs';
@Component({
@@ -47,9 +46,6 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input()
urlFile: string;
- @Input()
- blobFile: Blob;
-
@Input()
fileName: string;
@@ -73,8 +69,7 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
}
constructor(
- private appConfigService: AppConfigService,
- private urlService: UrlService) {
+ private appConfigService: AppConfigService) {
this.initializeScaling();
}
@@ -138,12 +133,7 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
}
ngOnChanges(changes: SimpleChanges) {
- const blobFile = changes['blobFile'];
- if (blobFile && blobFile.currentValue) {
- this.urlFile = this.urlService.createTrustedUrl(this.blobFile);
- return;
- }
- if (!this.urlFile && !this.blobFile) {
+ if (!this.urlFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
}
diff --git a/lib/core/src/lib/viewer/components/media-player.component.ts b/lib/core/src/lib/viewer/components/media-player.component.ts
index 1052adeecd..40f374b73a 100644
--- a/lib/core/src/lib/viewer/components/media-player.component.ts
+++ b/lib/core/src/lib/viewer/components/media-player.component.ts
@@ -16,7 +16,6 @@
*/
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation, Output, EventEmitter } from '@angular/core';
-import { ContentService } from '../../services/content.service';
import { Track } from '../models/viewer.model';
@Component({
@@ -31,9 +30,6 @@ export class MediaPlayerComponent implements OnChanges {
@Input()
urlFile: string;
- @Input()
- blobFile: Blob;
-
@Input()
mimeType: string;
@@ -47,18 +43,11 @@ export class MediaPlayerComponent implements OnChanges {
@Output()
error = new EventEmitter();
- constructor(private contentService: ContentService) {
+ constructor() {
}
ngOnChanges(changes: SimpleChanges) {
- const blobFile = changes['blobFile'];
-
- if (blobFile && blobFile.currentValue) {
- this.urlFile = this.contentService.createTrustedUrl(this.blobFile);
- return;
- }
-
- if (!this.urlFile && !this.blobFile) {
+ if (!this.urlFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
}
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer.component.ts b/lib/core/src/lib/viewer/components/pdf-viewer.component.ts
index 0a30ac33b5..d7a3b12cad 100644
--- a/lib/core/src/lib/viewer/components/pdf-viewer.component.ts
+++ b/lib/core/src/lib/viewer/components/pdf-viewer.component.ts
@@ -56,9 +56,6 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
@Input()
urlFile: string;
- @Input()
- blobFile: Blob;
-
@Input()
fileName: string;
@@ -149,21 +146,6 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
}
ngOnChanges(changes: SimpleChanges) {
- const blobFile = changes['blobFile'];
-
- if (blobFile && blobFile.currentValue) {
- const reader = new FileReader();
- reader.onload = async () => {
- const pdfSource: PDFSource = {
- ...this.pdfjsDefaultOptions,
- data: reader.result,
- withCredentials: this.appConfigService.get('auth.withCredentials', undefined)
- };
- this.executePdf(pdfSource);
- };
- reader.readAsArrayBuffer(blobFile.currentValue);
- }
-
const urlFile = changes['urlFile'];
if (urlFile && urlFile.currentValue) {
const pdfSource: PDFSource = {
@@ -179,7 +161,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
this.executePdf(pdfSource);
}
- if (!this.urlFile && !this.blobFile) {
+ if (!this.urlFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
}
diff --git a/lib/core/src/lib/viewer/components/txt-viewer.component.ts b/lib/core/src/lib/viewer/components/txt-viewer.component.ts
index 7caba710ad..268fd509e6 100644
--- a/lib/core/src/lib/viewer/components/txt-viewer.component.ts
+++ b/lib/core/src/lib/viewer/components/txt-viewer.component.ts
@@ -31,27 +31,18 @@ export class TxtViewerComponent implements OnChanges {
@Input()
urlFile: any;
- @Input()
- blobFile: Blob;
-
content: string | ArrayBuffer;
constructor(private http: HttpClient, private appConfigService: AppConfigService) {
}
ngOnChanges(changes: SimpleChanges): Promise {
-
- const blobFile = changes['blobFile'];
- if (blobFile && blobFile.currentValue) {
- return this.readBlob(blobFile.currentValue);
- }
-
const urlFile = changes['urlFile'];
if (urlFile && urlFile.currentValue) {
return this.getUrlContent(urlFile.currentValue);
}
- if (!this.urlFile && !this.blobFile) {
+ if (!this.urlFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
@@ -71,20 +62,4 @@ export class TxtViewerComponent implements OnChanges {
});
}
- private readBlob(blob: Blob): Promise {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
-
- reader.onload = () => {
- this.content = reader.result;
- resolve();
- };
-
- reader.onerror = (error: any) => {
- reject(error);
- };
-
- reader.readAsText(blob);
- });
- }
}
diff --git a/lib/core/src/lib/viewer/components/viewer-render.component.html b/lib/core/src/lib/viewer/components/viewer-render.component.html
index 5f1f3fec92..a6168c368a 100644
--- a/lib/core/src/lib/viewer/components/viewer-render.component.html
+++ b/lib/core/src/lib/viewer/components/viewer-render.component.html
@@ -37,7 +37,6 @@
@@ -62,15 +60,13 @@
[urlFile]="urlFile"
[tracks]="tracks"
[mimeType]="mimeType"
- [blobFile]="blobFile"
[fileName]="internalFileName"
(error)="onUnsupportedFile()">
-
+
diff --git a/lib/core/src/lib/viewer/components/viewer-render.component.ts b/lib/core/src/lib/viewer/components/viewer-render.component.ts
index e77009942b..28c2b62c3b 100644
--- a/lib/core/src/lib/viewer/components/viewer-render.component.ts
+++ b/lib/core/src/lib/viewer/components/viewer-render.component.ts
@@ -42,10 +42,6 @@ export class ViewerRenderComponent implements OnChanges, OnInit, OnDestroy {
@Input()
urlFile = '';
- /** Loads a Blob File */
- @Input()
- blobFile: Blob;
-
/** Toggles the 'Full Screen' feature. */
@Input()
allowFullScreen = true;
@@ -144,22 +140,12 @@ export class ViewerRenderComponent implements OnChanges, OnInit, OnDestroy {
ngOnChanges() {
this.isLoading = true;
- if (this.blobFile) {
- this.setUpBlobData();
- } else if (this.urlFile) {
+ if (this.urlFile) {
this.setUpUrlFile();
}
this.isLoading = false;
}
- private setUpBlobData() {
- this.internalFileName = this.fileName;
- this.internalViewerType = this.viewUtilService.getViewerTypeByMimeType(this.blobFile.type);
-
- this.extensionChange.emit(this.blobFile.type);
- this.scrollTop();
- }
-
private setUpUrlFile() {
this.internalFileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile);
this.extension = this.viewUtilService.getFileExtension(this.internalFileName);
diff --git a/lib/core/src/lib/viewer/components/viewer.component.html b/lib/core/src/lib/viewer/components/viewer.component.html
new file mode 100644
index 0000000000..3b7cde144b
--- /dev/null
+++ b/lib/core/src/lib/viewer/components/viewer.component.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
{{ fileName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/core/src/lib/viewer/components/viewer.component.scss b/lib/core/src/lib/viewer/components/viewer.component.scss
new file mode 100644
index 0000000000..9a8173d19a
--- /dev/null
+++ b/lib/core/src/lib/viewer/components/viewer.component.scss
@@ -0,0 +1,77 @@
+/* stylelint-disable scss/at-extend-no-missing-placeholder */
+.adf-full-screen {
+ width: 100%;
+ height: 100%;
+ background-color: var(--theme-card-bg-color);
+}
+
+.adf-alfresco-viewer {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+
+ .mat-toolbar {
+ color: var(--theme-text-color);
+
+ .adf-toolbar-title {
+ width: auto;
+ }
+ }
+
+ &-main {
+ width: 0;
+ }
+
+ &__mimeicon {
+ vertical-align: middle;
+ height: 18px;
+ width: 18px;
+ }
+
+ &-toolbar {
+ .mat-toolbar {
+ background-color: var(--theme-card-bg-bold-color);
+ }
+ }
+
+ &__file-title {
+ text-align: center;
+ }
+
+ &__display-name {
+ font-size: var(--theme-subheading-2-font-size);
+ opacity: 0.87;
+ line-height: 1.5;
+ letter-spacing: -0.4px;
+ font-weight: normal;
+ font-style: normal;
+ font-stretch: normal;
+ max-width: 400px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ display: inline-block;
+ vertical-align: middle;
+ color: var(--theme-text-fg-color);
+ }
+
+ &-inline-container {
+ @extend .adf-full-screen;
+ }
+
+ &__sidebar {
+ width: 350px;
+ display: block;
+ padding: 0;
+ background-color: var(--theme-background-color);
+ box-shadow: 0 2px 4px 0 var(--theme-text-fg-shadow-color);
+ overflow: auto;
+
+ &__right {
+ border-left: 1px solid var(--theme-border-color);
+ }
+
+ &__left {
+ border-right: 1px solid var(--theme-border-color);
+ }
+ }
+}
diff --git a/lib/core/src/lib/viewer/components/viewer.component.spec.ts b/lib/core/src/lib/viewer/components/viewer.component.spec.ts
new file mode 100644
index 0000000000..b20eac596e
--- /dev/null
+++ b/lib/core/src/lib/viewer/components/viewer.component.spec.ts
@@ -0,0 +1,944 @@
+/*!
+ * @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 { Location } from '@angular/common';
+import { SpyLocation } from '@angular/common/testing';
+import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+
+import { TranslateModule } from '@ngx-translate/core';
+import { MatDialog } from '@angular/material/dialog';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { NodeEntry, VersionEntry } from '@alfresco/js-api';
+import { AlfrescoViewerComponent, RenditionViewerService } from '@alfresco/adf-content-services';
+import {
+ AlfrescoApiService,
+ CoreTestingModule,
+ setupTestBed,
+ EventMock,
+ FileModel, UploadService
+} from '@alfresco/adf-core';
+import { throwError } from 'rxjs';
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'adf-viewer-container-toolbar',
+ template: `
+
+
+
+
+
+ `
+})
+class ViewerWithCustomToolbarComponent {
+}
+
+@Component({
+ selector: 'adf-viewer-container-toolbar-actions',
+ template: `
+
+
+
+
+
+ `
+})
+class ViewerWithCustomToolbarActionsComponent {
+}
+
+@Component({
+ selector: 'adf-viewer-container-sidebar',
+ template: `
+
+
+
+
+
+ `
+})
+class ViewerWithCustomSidebarComponent {
+}
+
+@Component({
+ selector: 'adf-dialog-dummy',
+ template: ``
+})
+class DummyDialogComponent {
+}
+
+@Component({
+ selector: 'adf-viewer-container-open-with',
+ template: `
+
+
+
+
+
+
+
+ `
+})
+class ViewerWithCustomOpenWithComponent {
+}
+
+@Component({
+ selector: 'adf-viewer-container-more-actions',
+ template: `
+
+
+
+
+
+
+
+ `
+})
+class ViewerWithCustomMoreActionsComponent {
+}
+
+
+describe('AlfrescoViewerComponent', () => {
+
+ let component: AlfrescoViewerComponent;
+ let fixture: ComponentFixture;
+ let element: HTMLElement;
+
+ let alfrescoApiService: AlfrescoApiService;
+ let dialog: MatDialog;
+ let uploadService: UploadService;
+ let extensionService: AppExtensionService;
+
+ setupTestBed({
+ imports: [
+ NoopAnimationsModule,
+ TranslateModule.forRoot(),
+ CoreTestingModule,
+ MatButtonModule,
+ MatIconModule
+ ],
+ declarations: [
+ ViewerWithCustomToolbarComponent,
+ ViewerWithCustomSidebarComponent,
+ ViewerWithCustomOpenWithComponent,
+ ViewerWithCustomMoreActionsComponent,
+ ViewerWithCustomToolbarActionsComponent
+ ],
+ providers: [
+ {
+ provide: RenditionViewerService, useValue: {
+ getNodeRendition: () => throwError('thrown')
+ }
+ },
+ {provide: Location, useClass: SpyLocation},
+ MatDialog
+ ]
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AlfrescoViewerComponent);
+ element = fixture.nativeElement;
+ component = fixture.componentInstance;
+
+ 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 to display node by id', fakeAsync(() => {
+ const extension: ViewerExtensionRef = {
+ component: 'custom.component',
+ id: 'custom.component.id',
+ fileExtension: '*'
+ };
+ spyOn(extensionService, 'getViewerExtensions').and.returnValue([extension]);
+
+ fixture = TestBed.createComponent(AlfrescoViewerComponent);
+ element = fixture.nativeElement;
+ component = fixture.componentInstance;
+
+ spyOn(component.nodesApi, 'getNode').and.callFake(() => Promise.resolve(new NodeEntry({entry: {}})));
+
+ component.nodeId = '37f7f34d-4e64-4db6-bb3f-5c89f7844251';
+ component.ngOnChanges();
+
+ fixture.detectChanges();
+ tick(100);
+
+ expect(component.nodesApi.getNode).toHaveBeenCalled();
+ expect(component.viewerType).toBe('external');
+ expect(component.isLoading).toBeFalsy();
+ expect(element.querySelector('[data-automation-id="custom.component"]')).not.toBeNull();
+ }));
+
+
+ });
+
+ describe('MimeType handling', () => {
+
+ it('should node without content show unkonwn', (done) => {
+ const displayName = 'the-name';
+ const contentUrl = '/content/url/path';
+
+ component.nodeId = '12';
+ spyOn(component['nodesApi'], 'getNode').and.returnValue(Promise.resolve(new NodeEntry({
+ entry: {content: {name: displayName, id: '12'}}
+ })));
+
+ spyOn(component['contentApi'], 'getContentUrl').and.returnValue(contentUrl);
+
+ component.ngOnChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(element.querySelector('adf-viewer-unknown-format')).toBeDefined();
+ done();
+ });
+ });
+ });
+
+ it('should change display name every time node changes', fakeAsync(() => {
+ spyOn(component['nodesApi'], 'getNode').and.returnValues(
+ Promise.resolve(new NodeEntry({entry: {name: 'file1', content: {}}})),
+ Promise.resolve(new NodeEntry({entry: {name: 'file2', content: {}}}))
+ );
+
+ component.showViewer = true;
+
+ component.nodeId = 'id1';
+ component.ngOnChanges();
+ tick();
+
+ expect(component.fileName).toBe('file1');
+
+ component.nodeId = 'id2';
+ component.ngOnChanges();
+ tick();
+
+ expect(component.fileName).toBe('file2');
+ }));
+
+ it('should append version of the file to the file content URL', fakeAsync(() => {
+ spyOn(component['nodesApi'], 'getNode').and.returnValue(
+ Promise.resolve(new NodeEntry({
+ entry: {
+ name: 'file1.pdf',
+ content: {},
+ properties: {'cm:versionLabel': '10'}
+ }
+ }))
+ );
+ spyOn(component['versionsApi'], 'getVersion').and.returnValue(Promise.resolve(undefined));
+
+ component.nodeId = 'id1';
+ component.showViewer = true;
+
+ component.versionId = null;
+ component.ngOnChanges();
+ tick();
+
+ expect(component.fileName).toBe('file1.pdf');
+ expect(component.urlFileContent).toContain('/public/alfresco/versions/1/nodes/id1/content?attachment=false&10');
+ }));
+
+ it('should change display name every time node\`s version changes', fakeAsync(() => {
+ spyOn(component['nodesApi'], 'getNode').and.returnValue(
+ Promise.resolve(new NodeEntry({entry: {name: 'node1', content: {}}}))
+ );
+
+ spyOn(component['versionsApi'], 'getVersion').and.returnValues(
+ Promise.resolve(new VersionEntry({entry: {name: 'file1', content: {}}})),
+ Promise.resolve(new VersionEntry({entry: {name: 'file2', content: {}}}))
+ );
+
+ component.nodeId = 'id1';
+ component.showViewer = true;
+
+ component.versionId = '1.0';
+ component.ngOnChanges();
+ tick();
+
+ expect(component.fileName).toBe('file1');
+
+ component.versionId = '1.1';
+ component.ngOnChanges();
+ tick();
+
+ expect(component.fileName).toBe('file2');
+ }));
+
+ it('should update node only if node name changed', fakeAsync(() => {
+ spyOn(component['nodesApi'], 'getNode').and.returnValues(
+ Promise.resolve(new NodeEntry({entry: {name: 'file1', content: {}}}))
+ );
+
+ component.showViewer = true;
+
+ component.nodeId = 'id1';
+ fixture.detectChanges();
+ component.ngOnChanges();
+ tick();
+
+ expect(component.fileName).toBe('file1');
+
+ alfrescoApiService.nodeUpdated.next({id: 'id1', name: 'file2'} as any);
+ fixture.detectChanges();
+ expect(component.fileName).toBe('file2');
+
+ alfrescoApiService.nodeUpdated.next({id: 'id1', name: 'file3'} as any);
+ fixture.detectChanges();
+ expect(component.fileName).toBe('file3');
+
+ alfrescoApiService.nodeUpdated.next({id: 'id2', name: 'file4'} as any);
+ fixture.detectChanges();
+ expect(component.fileName).toBe('file3');
+ expect(component.nodeId).toBe('id1');
+ }));
+
+ describe('Viewer Example Component Rendering', () => {
+
+ it('should use custom toolbar', (done) => {
+ const customFixture = TestBed.createComponent(ViewerWithCustomToolbarComponent);
+ const customElement: HTMLElement = customFixture.nativeElement;
+
+ customFixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(customElement.querySelector('.custom-toolbar-element')).toBeDefined();
+ done();
+ });
+ });
+
+ it('should use custom toolbar actions', (done) => {
+ const customFixture = TestBed.createComponent(ViewerWithCustomToolbarActionsComponent);
+ const customElement: HTMLElement = customFixture.nativeElement;
+
+ customFixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(customElement.querySelector('#custom-button')).toBeDefined();
+ done();
+ });
+ });
+
+ it('should use custom info drawer', (done) => {
+ const customFixture = TestBed.createComponent(ViewerWithCustomSidebarComponent);
+ const customElement: HTMLElement = customFixture.nativeElement;
+
+ customFixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(customElement.querySelector('.custom-info-drawer-element')).toBeDefined();
+ done();
+ });
+ });
+
+ it('should use custom open with menu', (done) => {
+ const customFixture = TestBed.createComponent(ViewerWithCustomOpenWithComponent);
+ const customElement: HTMLElement = customFixture.nativeElement;
+
+ customFixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(customElement.querySelector('.adf-viewer-container-open-with')).toBeDefined();
+ done();
+ });
+ });
+
+ it('should use custom more actions menu', (done) => {
+ const customFixture = TestBed.createComponent(ViewerWithCustomMoreActionsComponent);
+ const customElement: HTMLElement = customFixture.nativeElement;
+
+ customFixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(customElement.querySelector('.adf-viewer-container-more-actions')).toBeDefined();
+ done();
+ });
+
+ });
+ });
+
+ describe('error handling', () => {
+
+ it('should show unknown view when node file not found', (done) => {
+ spyOn(component['nodesApi'], 'getNode')
+ .and.returnValue(Promise.reject({}));
+
+ component.nodeId = 'the-node-id-of-the-file-to-preview';
+ component.mimeType = null;
+
+ component.ngOnChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(element.querySelector('adf-viewer-unknown-format')).not.toBeNull();
+ done();
+ });
+ });
+
+ it('should show unknown view when sharedLink file not found', (done) => {
+ spyOn(component['sharedLinksApi'], 'getSharedLink')
+ .and.returnValue(Promise.reject({}));
+
+ component.sharedLinkId = 'the-Shared-Link-id';
+ component.mimeType = null;
+ component.nodeId = null;
+
+ component.ngOnChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(element.querySelector('adf-viewer-unknown-format')).not.toBeNull();
+ done();
+ });
+
+ });
+
+ it('should raise an event when the shared link is invalid', fakeAsync(() => {
+ spyOn(component['sharedLinksApi'], 'getSharedLink')
+ .and.returnValue(Promise.reject({}));
+
+ component.sharedLinkId = 'the-Shared-Link-id';
+ component.mimeType = null;
+ component.nodeId = null;
+
+ component.invalidSharedLink.subscribe((emittedValue) => {
+ expect(emittedValue).toBeUndefined();
+ });
+
+ component.ngOnChanges();
+ }));
+//
+ });
+
+ describe('Toolbar', () => {
+
+ it('should show only next file button', async () => {
+ component.allowNavigate = true;
+ component.canNavigateBefore = false;
+ component.canNavigateNext = true;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const nextButton = element.querySelector('[data-automation-id="adf-toolbar-next-file"]');
+ expect(nextButton).not.toBeNull();
+
+ const prevButton = element.querySelector('[data-automation-id="adf-toolbar-pref-file"]');
+ expect(prevButton).toBeNull();
+ });
+
+ it('should provide tooltip for next file button', async () => {
+ component.allowNavigate = true;
+ component.canNavigateBefore = false;
+ component.canNavigateNext = true;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const nextButton = element.querySelector('[data-automation-id="adf-toolbar-next-file"]');
+ expect(nextButton.title).toBe('ADF_VIEWER.ACTIONS.NEXT_FILE');
+ });
+
+ it('should show only previous file button', async () => {
+ component.allowNavigate = true;
+ component.canNavigateBefore = true;
+ component.canNavigateNext = false;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const nextButton = element.querySelector('[data-automation-id="adf-toolbar-next-file"]');
+ expect(nextButton).toBeNull();
+
+ const prevButton = element.querySelector('[data-automation-id="adf-toolbar-pref-file"]');
+ expect(prevButton).not.toBeNull();
+ });
+
+ it('should provide tooltip for the previous file button', async () => {
+ component.allowNavigate = true;
+ component.canNavigateBefore = true;
+ component.canNavigateNext = false;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const prevButton = element.querySelector('[data-automation-id="adf-toolbar-pref-file"]');
+ expect(prevButton.title).toBe('ADF_VIEWER.ACTIONS.PREV_FILE');
+ });
+
+ it('should show both file navigation buttons', async () => {
+ component.allowNavigate = true;
+ component.canNavigateBefore = true;
+ component.canNavigateNext = true;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const nextButton = element.querySelector('[data-automation-id="adf-toolbar-next-file"]');
+ expect(nextButton).not.toBeNull();
+
+ const prevButton = element.querySelector('[data-automation-id="adf-toolbar-pref-file"]');
+ expect(prevButton).not.toBeNull();
+ });
+
+ it('should not show navigation buttons', async () => {
+ component.allowNavigate = false;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const nextButton = element.querySelector('[data-automation-id="adf-toolbar-next-file"]');
+ expect(nextButton).toBeNull();
+
+ const prevButton = element.querySelector('[data-automation-id="adf-toolbar-pref-file"]');
+ expect(prevButton).toBeNull();
+ });
+
+ it('should now show navigation buttons even if navigation enabled', async () => {
+ component.allowNavigate = true;
+ component.canNavigateBefore = false;
+ component.canNavigateNext = false;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const nextButton = element.querySelector('[data-automation-id="adf-toolbar-next-file"]');
+ expect(nextButton).toBeNull();
+
+ const prevButton = element.querySelector('[data-automation-id="adf-toolbar-pref-file"]');
+ expect(prevButton).toBeNull();
+ });
+
+ it('should render fullscreen button', () => {
+ expect(element.querySelector('[data-automation-id="adf-toolbar-fullscreen"]')).toBeDefined();
+ });
+
+ it('should render default download button', (done) => {
+ component.allowDownload = true;
+
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('[data-automation-id="adf-toolbar-download"]')).toBeDefined();
+ done();
+ });
+ });
+
+ it('should not render default download button', (done) => {
+ component.allowDownload = false;
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('[data-automation-id="adf-toolbar-download"]')).toBeNull();
+ done();
+ });
+ });
+
+ it('should render default print button', (done) => {
+ component.allowPrint = true;
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('[data-automation-id="adf-toolbar-print"]')).toBeDefined();
+ done();
+ });
+ });
+
+ it('should not render default print button', (done) => {
+ component.allowPrint = false;
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('[data-automation-id="adf-toolbar-print"]')).toBeNull();
+ done();
+ });
+ });
+
+ it('should invoke print action with the toolbar button', (done) => {
+ component.allowPrint = true;
+ fixture.detectChanges();
+
+ spyOn(component, 'onPrintContent').and.stub();
+
+ const button: HTMLButtonElement = element.querySelector('[data-automation-id="adf-toolbar-print"]') as HTMLButtonElement;
+ button.click();
+
+ fixture.whenStable().then(() => {
+ expect(component.onPrintContent).toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('should get and assign node for download', (done) => {
+ component.nodeId = '12';
+ const displayName = 'the-name';
+ const nodeDetails = {
+ entry: {name: displayName, id: '12', content: {mimeType: 'txt'}}
+ };
+
+ const contentUrl = '/content/url/path';
+
+ const node = new NodeEntry(nodeDetails);
+
+ spyOn(component['nodesApi'], 'getNode').and.returnValue(Promise.resolve(node));
+ spyOn(component['contentApi'], 'getContentUrl').and.returnValue(contentUrl);
+
+ component.ngOnChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(component.nodeEntry).toBe(node);
+ done();
+ });
+ });
+
+ it('should render close viewer button if it is not a shared link', (done) => {
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(element.querySelector('[data-automation-id="adf-toolbar-back"]')).toBeDefined();
+ expect(element.querySelector('[data-automation-id="adf-toolbar-back"]')).not.toBeNull();
+ done();
+ });
+ });
+
+ it('should not render close viewer button if it is a shared link', (done) => {
+ spyOn(component['sharedLinksApi'], 'getSharedLink')
+ .and.returnValue(Promise.reject({}));
+
+ component.sharedLinkId = 'the-Shared-Link-id';
+ component.mimeType = null;
+
+ component.ngOnChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(element.querySelector('[data-automation-id="adf-toolbar-back"]')).toBeNull();
+ done();
+ });
+ });
+
+ });
+
+ describe('Base component', () => {
+
+ beforeEach(() => {
+ component.mimeType = 'application/pdf';
+ component.nodeId = 'id1';
+
+ fixture.detectChanges();
+ });
+
+ describe('SideBar Test', () => {
+
+ it('should NOT display sidebar if is not allowed', (done) => {
+ component.showRightSidebar = true;
+ component.allowRightSidebar = false;
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ const sidebar = element.querySelector('#adf-right-sidebar');
+ expect(sidebar).toBeNull();
+ done();
+ });
+ });
+
+ it('should display sidebar on the right side', (done) => {
+ component.allowRightSidebar = true;
+ component.showRightSidebar = true;
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ const sidebar = element.querySelector('#adf-right-sidebar');
+ expect(getComputedStyle(sidebar).order).toEqual('4');
+ done();
+ });
+ });
+
+ it('should NOT display left sidebar if is not allowed', (done) => {
+ component.showLeftSidebar = true;
+ component.allowLeftSidebar = false;
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ const sidebar = element.querySelector('#adf-left-sidebar');
+ expect(sidebar).toBeNull();
+ done();
+ });
+
+ });
+
+ it('should display sidebar on the left side', (done) => {
+ component.allowLeftSidebar = true;
+ component.showLeftSidebar = true;
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ const sidebar = element.querySelector('#adf-left-sidebar');
+ expect(getComputedStyle(sidebar).order).toEqual('1');
+ done();
+ });
+ });
+ });
+
+ describe('View', () => {
+
+ describe('Overlay mode true', () => {
+
+ beforeEach(() => {
+ component.overlayMode = true;
+ component.fileName = 'fake-test-file.pdf';
+ fixture.detectChanges();
+ });
+
+ it('should header be present if is overlay mode', () => {
+ expect(element.querySelector('.adf-alfresco-viewer-toolbar')).not.toBeNull();
+ });
+
+ it('should Name File be present if is overlay mode ', (done) => {
+ component.ngOnChanges();
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(element.querySelector('#adf-alfresco-viewer-display-name').textContent).toEqual('fake-test-file.pdf');
+ done();
+ });
+ });
+
+ it('should Close button be present if overlay mode', (done) => {
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(element.querySelector('.adf-alfresco-viewer-close-button')).not.toBeNull();
+ done();
+ });
+ });
+
+ it('should Click on close button hide the viewer', (done) => {
+ const closeButton: any = element.querySelector('.adf-alfresco-viewer-close-button');
+ closeButton.click();
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('.adf-alfresco-viewer-content')).toBeNull();
+ done();
+ });
+ });
+
+ it('should Esc button hide the viewer', (done) => {
+ EventMock.keyDown(27);
+
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('.adf-alfresco-viewer-content')).toBeNull();
+ done();
+ });
+ });
+
+ it('should not close the viewer on Escape event if dialog was opened', (done) => {
+ const event = new KeyboardEvent('keydown', {
+ bubbles: true,
+ keyCode: 27
+ } as KeyboardEventInit);
+
+ const dialogRef = dialog.open(DummyDialogComponent);
+
+ dialogRef.afterClosed().subscribe(() => {
+ EventMock.keyDown(27);
+ fixture.detectChanges();
+ expect(element.querySelector('.adf-alfresco-viewer-content')).toBeNull();
+ done();
+ });
+
+ fixture.detectChanges();
+
+ document.body.dispatchEvent(event);
+ fixture.detectChanges();
+ expect(element.querySelector('.adf-alfresco-viewer-content')).not.toBeNull();
+ });
+ });
+
+ describe('Overlay mode false', () => {
+
+ beforeEach(() => {
+ component.overlayMode = false;
+ fixture.detectChanges();
+ });
+
+ it('should Esc button not hide the viewer if is not overlay mode', (done) => {
+ EventMock.keyDown(27);
+
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ expect(element.querySelector('.adf-alfresco-viewer-content')).not.toBeNull();
+ done();
+ });
+ });
+ });
+ });
+
+ describe('Attribute', () => {
+
+ it('should FileNodeId present not thrown any error ', () => {
+ component.showViewer = true;
+ component.nodeId = 'file-node-id';
+
+ expect(() => {
+ component.ngOnChanges();
+ }).not.toThrow();
+ });
+
+
+ it('should showViewer default value be true', () => {
+ expect(component.showViewer).toBe(true);
+ });
+
+ it('should viewer be hide if showViewer value is false', () => {
+ component.showViewer = false;
+
+ fixture.detectChanges();
+ expect(element.querySelector('.adf-alfresco-viewer-content')).toBeNull();
+ });
+ });
+
+ describe('Events', () => {
+
+ it('should update version when emitted by image-viewer and user has update permissions', () => {
+ spyOn(uploadService, 'uploadFilesInTheQueue').and.callFake(() => {
+ });
+ spyOn(uploadService, 'addToQueue');
+ component.readOnly = false;
+ component.nodeEntry = new NodeEntry({
+ entry: {
+ name: 'fakeImage.png',
+ id: '12',
+ content: {mimeType: 'img/png'}
+ }
+ });
+ const data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
+ const fakeBlob = new Blob([data], {type: 'image/png'});
+ const newImageFile: File = new File([fakeBlob], component?.nodeEntry?.entry?.name, {type: component?.nodeEntry?.entry?.content?.mimeType});
+ const newFile = new FileModel(
+ newImageFile,
+ {
+ majorVersion: false,
+ newVersion: true,
+ parentId: component?.nodeEntry?.entry?.parentId,
+ nodeType: component?.nodeEntry?.entry?.content?.mimeType
+ },
+ component.nodeEntry.entry?.id
+ );
+ component.onSubmitFile(fakeBlob);
+ fixture.detectChanges();
+
+ expect(uploadService.addToQueue).toHaveBeenCalledWith(...[newFile]);
+ expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalled();
+ });
+
+ it('should not update version when emitted by image-viewer and user doesn`t have update permissions', () => {
+ spyOn(uploadService, 'uploadFilesInTheQueue').and.callFake(() => {
+ });
+ component.readOnly = true;
+ component.nodeEntry = new NodeEntry({
+ entry: {
+ name: 'fakeImage.png',
+ id: '12',
+ content: {mimeType: 'img/png'}
+ }
+ });
+ const data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
+ const fakeBlob = new Blob([data], {type: 'image/png'});
+ component.onSubmitFile(fakeBlob);
+ fixture.detectChanges();
+
+ expect(uploadService.uploadFilesInTheQueue).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('Viewer component - Full Screen Mode - Mocking fixture element', () => {
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AlfrescoViewerComponent);
+ element = fixture.nativeElement;
+ component = fixture.componentInstance;
+
+ component.showToolbar = true;
+ component.nodeId = 'fake-node-id';
+ component.mimeType = 'application/pdf';
+ fixture.detectChanges();
+ });
+
+ it('should use standard mode', () => {
+ const domElement = jasmine.createSpyObj('el', ['requestFullscreen']);
+ spyOn(fixture.nativeElement, 'querySelector').and.returnValue(domElement);
+
+ component.enterFullScreen();
+ expect(domElement.requestFullscreen).toHaveBeenCalled();
+ });
+
+ it('should use webkit prefix', () => {
+ const domElement = jasmine.createSpyObj('el', ['webkitRequestFullscreen']);
+ spyOn(fixture.nativeElement, 'querySelector').and.returnValue(domElement);
+
+ component.enterFullScreen();
+ expect(domElement.webkitRequestFullscreen).toHaveBeenCalled();
+ });
+
+ it('should use moz prefix', () => {
+ const domElement = jasmine.createSpyObj('el', ['mozRequestFullScreen']);
+ spyOn(fixture.nativeElement, 'querySelector').and.returnValue(domElement);
+
+ component.enterFullScreen();
+ expect(domElement.mozRequestFullScreen).toHaveBeenCalled();
+ });
+
+ it('should use ms prefix', () => {
+ const domElement = jasmine.createSpyObj('el', ['msRequestFullscreen']);
+ spyOn(fixture.nativeElement, 'querySelector').and.returnValue(domElement);
+
+ component.enterFullScreen();
+ expect(domElement.msRequestFullscreen).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/lib/core/src/lib/viewer/components/viewer.component.ts b/lib/core/src/lib/viewer/components/viewer.component.ts
new file mode 100644
index 0000000000..04cae341b1
--- /dev/null
+++ b/lib/core/src/lib/viewer/components/viewer.component.ts
@@ -0,0 +1,233 @@
+/*!
+ * @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,
+ ContentChild,
+ ElementRef,
+ EventEmitter,
+ HostListener,
+ Input,
+ OnDestroy,
+ OnInit,
+ Output,
+ TemplateRef,
+ ViewEncapsulation
+} from '@angular/core';
+import { Subject } from 'rxjs';
+import { MatDialog } from '@angular/material/dialog';
+import { ViewUtilService } from '../services/view-util.service';
+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";
+
+@Component({
+ selector: 'adf-viewer',
+ templateUrl: './alfresco-viewer.component.html',
+ styleUrls: ['./alfresco-viewer.component.scss'],
+ host: {class: 'adf-alfresco-viewer'},
+ encapsulation: ViewEncapsulation.None,
+ providers: [ViewUtilService]
+})
+export class ViewerComponent implements OnInit, OnDestroy {
+
+ @ContentChild(ViewerToolbarComponent)
+ toolbar: ViewerToolbarComponent;
+
+ @ContentChild(ViewerSidebarComponent)
+ sidebar: ViewerSidebarComponent;
+
+ @ContentChild(ViewerOpenWithComponent)
+ mnuOpenWith: ViewerOpenWithComponent;
+
+ @ContentChild(ViewerMoreActionsComponent)
+ mnuMoreActions: ViewerMoreActionsComponent;
+
+ /** If you want to load an external file that does not come from ACS you
+ * can use this URL to specify where to load the file from.
+ */
+ @Input()
+ urlFile = '';
+
+ /** Override Content filename. */
+ @Input()
+ fileName: string;
+
+ /** Hide or show the viewer */
+ @Input()
+ showViewer = true;
+
+ /** Allows `back` navigation */
+ @Input()
+ allowGoBack = true;
+
+ /** Hide or show the toolbar */
+ @Input()
+ showToolbar = true;
+
+ /** If `true` then show the Viewer as a full page over the current content.
+ * Otherwise fit inside the parent div.
+ */
+ @Input()
+ overlayMode = false;
+
+ /** Toggles before/next navigation. You can use the arrow buttons to navigate
+ * between documents in the collection.
+ */
+ @Input()
+ allowNavigate = false;
+
+ /** Toggles the "before" ("<") button. Requires `allowNavigate` to be enabled. */
+ @Input()
+ canNavigateBefore = true;
+
+ /** Toggles the next (">") button. Requires `allowNavigate` to be enabled. */
+ @Input()
+ canNavigateNext = true;
+
+ /** Allow the left the sidebar. */
+ @Input()
+ allowLeftSidebar = false;
+
+ /** Allow the right sidebar. */
+ @Input()
+ allowRightSidebar = false;
+
+ /** Toggles right sidebar visibility. Requires `allowRightSidebar` to be set to `true`. */
+ @Input()
+ showRightSidebar = false;
+
+ /** Toggles left sidebar visibility. Requires `allowLeftSidebar` to be set to `true`. */
+ @Input()
+ showLeftSidebar = false;
+
+ /** The template for the right sidebar. The template context contains the loaded node data. */
+ @Input()
+ sidebarRightTemplate: TemplateRef = null;
+
+ /** The template for the left sidebar. The template context contains the loaded node data. */
+ @Input()
+ sidebarLeftTemplate: TemplateRef = null;
+
+ /** Emitted when the shared link used is not valid. */
+ @Output()
+ invalidSharedLink = new EventEmitter();
+
+ /** Emitted when user clicks 'Navigate Before' ("<") button. */
+ @Output()
+ navigateBefore = new EventEmitter();
+
+ /** Emitted when user clicks 'Navigate Next' (">") button. */
+ @Output()
+ navigateNext = new EventEmitter();
+
+ /** Emitted when the viewer close */
+ @Output()
+ close = new EventEmitter();
+
+ private onDestroy$ = new Subject();
+
+ isLoading: boolean;
+ urlFileContent: string;
+ viewerType: any;
+ mimeType: string;
+ readOnly: boolean = true;
+
+ sidebarRightTemplateContext: { node: Node } = {node: null};
+ sidebarLeftTemplateContext: { node: Node } = {node: null};
+
+ constructor(private el: ElementRef,
+ public dialog: MatDialog) {
+ }
+
+
+ onNavigateBeforeClick(event: MouseEvent | KeyboardEvent) {
+ this.navigateBefore.next(event);
+ }
+
+ onNavigateNextClick(event: MouseEvent | KeyboardEvent) {
+ this.navigateNext.next(event);
+ }
+
+ /**
+ * close the viewer
+ */
+ onClose() {
+ this.showViewer = false;
+ this.close.emit(this.showViewer);
+ }
+
+ toggleRightSidebar() {
+ this.showRightSidebar = !this.showRightSidebar;
+ }
+
+ toggleLeftSidebar() {
+ this.showLeftSidebar = !this.showLeftSidebar;
+ }
+
+ @HostListener('document:keyup', ['$event'])
+ handleKeyboardEvent(event: KeyboardEvent) {
+ if (event && event.defaultPrevented) {
+ return;
+ }
+
+ const key = event.keyCode;
+
+ // Left arrow
+ if (key === 37 && this.canNavigateBefore) {
+ event.preventDefault();
+ this.onNavigateBeforeClick(event);
+ }
+
+ // Right arrow
+ if (key === 39 && this.canNavigateNext) {
+ event.preventDefault();
+ this.onNavigateNextClick(event);
+ }
+
+ // Ctrl+F
+ if (key === 70 && event.ctrlKey) {
+ event.preventDefault();
+ this.enterFullScreen();
+ }
+ }
+
+ /**
+ * Triggers full screen mode with a main content area displayed.
+ */
+ enterFullScreen(): void {
+ const container = this.el.nativeElement.querySelector('.adf-viewer__fullscreen-container');
+ if (container) {
+ if (container.requestFullscreen) {
+ container.requestFullscreen();
+ } else if (container.webkitRequestFullscreen) {
+ container.webkitRequestFullscreen();
+ } else if (container.mozRequestFullScreen) {
+ container.mozRequestFullScreen();
+ } else if (container.msRequestFullscreen) {
+ container.msRequestFullscreen();
+ }
+ }
+ }
+
+ ngOnDestroy() {
+ this.onDestroy$.next(true);
+ this.onDestroy$.complete();
+ }
+
+}
diff --git a/lib/core/src/lib/viewer/public-api.ts b/lib/core/src/lib/viewer/public-api.ts
index a32a35ea9d..566f9b632a 100644
--- a/lib/core/src/lib/viewer/public-api.ts
+++ b/lib/core/src/lib/viewer/public-api.ts
@@ -31,6 +31,7 @@ export * from './components/viewer-sidebar.component';
export * from './components/viewer-toolbar.component';
export * from './components/viewer-toolbar-actions.component';
export * from './components/viewer-render.component';
+export * from './components/viewer.component';
export * from './directives/viewer-extension.directive';
diff --git a/lib/core/src/lib/viewer/viewer.module.ts b/lib/core/src/lib/viewer/viewer.module.ts
index 01d8dbe2d1..dbb7984f45 100644
--- a/lib/core/src/lib/viewer/viewer.module.ts
+++ b/lib/core/src/lib/viewer/viewer.module.ts
@@ -43,6 +43,7 @@ import { ViewerExtensionDirective } from './directives/viewer-extension.directiv
import { ViewerToolbarActionsComponent } from './components/viewer-toolbar-actions.component';
import { DirectiveModule } from '../directives/directive.module';
import { A11yModule } from '@angular/cdk/a11y';
+import { ViewerComponent } from "./components/viewer.component";
@NgModule({
imports: [
@@ -73,7 +74,8 @@ import { A11yModule } from '@angular/cdk/a11y';
ViewerSidebarComponent,
ViewerOpenWithComponent,
ViewerMoreActionsComponent,
- ViewerToolbarActionsComponent
+ ViewerToolbarActionsComponent,
+ ViewerComponent
],
exports: [
ViewerRenderComponent,
@@ -90,7 +92,8 @@ import { A11yModule } from '@angular/cdk/a11y';
ViewerSidebarComponent,
ViewerOpenWithComponent,
ViewerMoreActionsComponent,
- ViewerToolbarActionsComponent
+ ViewerToolbarActionsComponent,
+ ViewerComponent
]
})
export class ViewerModule {