mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[AAE-10778] Refactor Viewer (#7992)
* refactor version 1 many todo
* split render from viewer
move alfresco render in content pack
* refactor part 2
* test fixed
* fix doc
* [AAE-10778] Fix lint issues
* [AAE-10778] Fix lint issue: remove duplicated declaration
* [AAE-10778] Fix lint issue: use flex shorthand rule
* [AAE-10778] Fix FormService and WidgetComponent imports
* [AAE-10778] Fix import FormModel, FormService, FormFieldModel from adf-core
* [AAE-10778] Implement missing oninit, onchanges and ondestroy
* [AAE-10778] Replace adf-viewer with adf-alfresco-viewer, update escape command to close the viewer
* [AAE-10778] Fix unit test: fix the class name to match the 'adf-viewer-render.image-viewer-scaling' get from the appConfigService
* [AAE-10778] Fix image-viewer unit tests: replace ContentService with UrlService
* [AAE-10778] Fix unit test 'should if the extension change extension Change event be fired': emit file extension when the filename extension change
* [AAE-10778] Fix unit test: expect for internalFileName value instead of display-name id because the display name logic has been moved to the alfresco-viewer.component
* [AAE-10778] Fix unit test: remove display name it because the unknown display name value is no longer handled after refactoring
* [AAE-10778] Fix e2e: [C260096] Should the Viewer able to accept a customToolbar
* [AAE-10778] Update selector to fix e2e: '[C362265] Should the Viewer be able to download a previous version of a file'
* [AAE-10778] Update selector to fix e2e: '[C260038] Should display first page, toolbar and pagination when opening a .pdf file'
* fix aftrer rebase
* fix unit test
* [AAE-10778] Add adf viewer component that is node agnostic, show adf-alfresco-viewer or adf-viewer into file-view-component if blob or node are set
* [AAE-10778] Update viewer export path
* [AAE-10778] Update selectors since have been updated in the viewer component
* [AAE-10778] Call adf-viewer from alfresco-viewer, project adf-alfresco-viewer content to adf-viewer
* [AAE-10778] Remove full screen unit tests from alfresco-viewer component becase that logic is handled in the viewer.component
* [AAE-10778] Export toolbar custom actions component
* [AAE-10778] Pass mimeType as input to adf-viewer to update mime icon
* [AAE-10778] Remove e2e because the custom name behaviour has been removed from the file-view.component (9f21b6dc69
\#diff-4b438dc59784dce9eb7634cfeca6d8db61362966343bd3d6895a3edafdf4cfd5L129)
* [AAE-10778] Use two-way binding for showViewer change to fix C260100
* [AAE-10778] Update prefix css selectors to adf-viewer because are related to the adf-viewer component
* [AAE-10778] Update prefix css selectors to adf-viewer in the unit tests because are related to the adf-viewer component
* [AAE-10778] Update the output name to showViewerChange to navigate to primary url after closing the viewer
* [AAE-10778] Pass right and left sidebar template context to viewer component (fix C362242)
* [AAE-10778] Add allowFullScreen input to disable/enable full screen behaviour
* [AAE-10778] Handle loading visualization only inside the viewer-render component
* [AAE-10778] PDF viewer: fix mat-progress-bar is not showed during the pdf loading, center progress bar
* [AAE-10778] Remove isLoading from unit tests because no longer exists
* [AAE-10778] Remove viewerType input from adf-viewer, viewerType will be handled by viewer-render
* [AAE-10778] Remove console.log
* [AAE-10778] Remove check full screen button is not displayed on the media file because is not needed anymore, we don't need to check for the fullscreen button in the viewer component
* [AAE-10778] Check for node rendtion before to assign to urlFileContent and mimeType
* [AAE-10778] Process Services Cloud: register file-viewer widget that uses adf-alfresco-viewer component to display content from ACS
* [AAE-10778] Core: rename file-viewer widget into base-viewer, base-viewer no longer accept nodeId, but will accept urlFile and blobFile
* [AAE-10778] Process Services: register file-viewer widget that uses adf-alfresco-viewer component to display content from ACS
* [AAE-10778] Base viewer widget: show viewer only if there's a file input
* [AAE-10778] Viewer component: check for fileName when urlFile is provided as Input
* [AAE-10778] Viewer component documentation
* [AAE-10778] Update upgrade guide with viewer changes
* [AAE-10778] Fix double quote lint issue after rebase
---------
Co-authored-by: Amedeo Lepore <amedeo.lepore@hyland.com>
Co-authored-by: Amedeo Lepore <amedeo.lepore85@gmail.com>
This commit is contained in:
@@ -48,6 +48,7 @@ import { ContentPipeModule } from './pipes/content-pipe.module';
|
||||
import { NodeCommentsModule } from './node-comments/node-comments.module';
|
||||
import { TreeModule } from './tree/tree.module';
|
||||
import { SearchTextModule } from './search-text/search-text-input.module';
|
||||
import { AlfrescoViewerModule } from './viewer/alfresco-viewer.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -79,7 +80,8 @@ import { SearchTextModule } from './search-text/search-text-input.module';
|
||||
VersionCompatibilityModule,
|
||||
NodeCommentsModule,
|
||||
TreeModule,
|
||||
SearchTextModule
|
||||
SearchTextModule,
|
||||
AlfrescoViewerModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -115,7 +117,8 @@ import { SearchTextModule } from './search-text/search-text-input.module';
|
||||
VersionCompatibilityModule,
|
||||
NodeCommentsModule,
|
||||
TreeModule,
|
||||
SearchTextModule
|
||||
SearchTextModule,
|
||||
AlfrescoViewerModule
|
||||
]
|
||||
})
|
||||
export class ContentModule {
|
||||
|
@@ -0,0 +1,51 @@
|
||||
/*!
|
||||
* @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 { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { AlfrescoViewerComponent } from './components/alfresco-viewer.component';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreModule,
|
||||
CommonModule,
|
||||
MaterialModule,
|
||||
TranslateModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
FlexLayoutModule,
|
||||
A11yModule,
|
||||
ExtensionsModule
|
||||
],
|
||||
declarations: [
|
||||
AlfrescoViewerComponent
|
||||
],
|
||||
exports: [
|
||||
AlfrescoViewerComponent
|
||||
]
|
||||
})
|
||||
export class AlfrescoViewerModule {
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
<adf-viewer
|
||||
#adfViewer
|
||||
[(showViewer)]="showViewer"
|
||||
[allowGoBack]="allowGoBack"
|
||||
[showToolbar]="showToolbar"
|
||||
[overlayMode]="overlayMode"
|
||||
[allowNavigate]="allowNavigate"
|
||||
[canNavigateBefore]="canNavigateBefore"
|
||||
[canNavigateNext]="canNavigateNext"
|
||||
[allowLeftSidebar]="allowLeftSidebar"
|
||||
[allowRightSidebar]="allowRightSidebar"
|
||||
[showRightSidebar]="showRightSidebar"
|
||||
[showLeftSidebar]="showLeftSidebar"
|
||||
[allowFullScreen]="allowFullScreen"
|
||||
[sidebarRightTemplate]="sidebarRightTemplate"
|
||||
[sidebarLeftTemplate]="sidebarLeftTemplate"
|
||||
[sidebarRightTemplateContext]="sidebarRightTemplateContext"
|
||||
[sidebarLeftTemplateContext]="sidebarLeftTemplateContext"
|
||||
[fileName]="fileName"
|
||||
[mimeType]="mimeType"
|
||||
[urlFile]="urlFileContent"
|
||||
[tracks]="tracks"
|
||||
[readOnly]="readOnly"
|
||||
(navigateBefore)="onNavigateBeforeClick($event)"
|
||||
(navigateNext)="onNavigateNextClick($event)"
|
||||
(showViewerChange)="onClose()"
|
||||
(submitFile)="onSubmitFile($event)">
|
||||
|
||||
<adf-viewer-toolbar *ngIf="toolbar">
|
||||
<ng-content select="adf-viewer-toolbar"></ng-content>
|
||||
</adf-viewer-toolbar>
|
||||
|
||||
<adf-viewer-toolbar-actions *ngIf="toolbarActions">
|
||||
<ng-content select="adf-viewer-toolbar-actions"></ng-content>
|
||||
</adf-viewer-toolbar-actions>
|
||||
|
||||
<adf-viewer-more-actions *ngIf="moreActions">
|
||||
<ng-content select="adf-viewer-more-actions"></ng-content>
|
||||
</adf-viewer-more-actions>
|
||||
|
||||
<adf-viewer-open-with *ngIf="openWith">
|
||||
<ng-content select="adf-viewer-open-with"></ng-content>
|
||||
</adf-viewer-open-with>
|
||||
|
||||
<adf-viewer-sidebar *ngIf="sidebar">
|
||||
<ng-content select="adf-viewer-sidebar"></ng-content>
|
||||
</adf-viewer-sidebar>
|
||||
|
||||
<adf-viewer-toolbar-custom-actions>
|
||||
<button id="adf-alfresco-viewer-download"
|
||||
*ngIf="allowDownload"
|
||||
mat-icon-button
|
||||
[attr.aria-label]="'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate"
|
||||
title="{{ 'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate }}"
|
||||
data-automation-id="adf-toolbar-download"
|
||||
[adfNodeDownload]="nodeEntry"
|
||||
[version]="versionEntry">
|
||||
<mat-icon>file_download</mat-icon>
|
||||
</button>
|
||||
|
||||
<button id="adf-alfresco-viewer-print"
|
||||
*ngIf="allowPrint"
|
||||
mat-icon-button
|
||||
[attr.aria-label]="'ADF_VIEWER.ACTIONS.PRINT' | translate"
|
||||
title="{{ 'ADF_VIEWER.ACTIONS.PRINT' | translate }}"
|
||||
data-automation-id="adf-toolbar-print"
|
||||
(click)="onPrintContent($event)">
|
||||
<mat-icon>print</mat-icon>
|
||||
</button>
|
||||
</adf-viewer-toolbar-custom-actions>
|
||||
</adf-viewer>
|
@@ -0,0 +1 @@
|
||||
/* stylelint-disable-next-line no-empty-source */
|
@@ -0,0 +1,907 @@
|
||||
/*!
|
||||
* @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 {
|
||||
NodesApiService,
|
||||
CoreTestingModule,
|
||||
setupTestBed,
|
||||
EventMock,
|
||||
FileModel, UploadService, ViewUtilService
|
||||
} from '@alfresco/adf-core';
|
||||
import { throwError } from 'rxjs';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-viewer-container-toolbar',
|
||||
template: `
|
||||
<adf-alfresco-viewer>
|
||||
<adf-viewer-toolbar>
|
||||
<div class="custom-toolbar-element"></div>
|
||||
</adf-viewer-toolbar>
|
||||
</adf-alfresco-viewer>
|
||||
`
|
||||
})
|
||||
class ViewerWithCustomToolbarComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-viewer-container-toolbar-actions',
|
||||
template: `
|
||||
<adf-alfresco-viewer>
|
||||
<adf-viewer-toolbar-actions>
|
||||
<button mat-icon-button id="custom-button">
|
||||
<mat-icon>alarm</mat-icon>
|
||||
</button>
|
||||
</adf-viewer-toolbar-actions>
|
||||
</adf-alfresco-viewer>
|
||||
`
|
||||
})
|
||||
class ViewerWithCustomToolbarActionsComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-viewer-container-sidebar',
|
||||
template: `
|
||||
<adf-alfresco-viewer>
|
||||
<adf-viewer-sidebar>
|
||||
<div class="custom-sidebar"></div>
|
||||
</adf-viewer-sidebar>
|
||||
</adf-alfresco-viewer>
|
||||
`
|
||||
})
|
||||
class ViewerWithCustomSidebarComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-dialog-dummy',
|
||||
template: ``
|
||||
})
|
||||
class DummyDialogComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-viewer-container-open-with',
|
||||
template: `
|
||||
<adf-alfresco-viewer>
|
||||
<adf-viewer-open-with>
|
||||
<button mat-menu-item>
|
||||
<mat-icon>dialpad</mat-icon>
|
||||
<span>Option 1</span>
|
||||
</button>
|
||||
<button mat-menu-item disabled>
|
||||
<mat-icon>voicemail</mat-icon>
|
||||
<span>Option 2</span>
|
||||
</button>
|
||||
<button mat-menu-item>
|
||||
<mat-icon>notifications_off</mat-icon>
|
||||
<span>Option 3</span>
|
||||
</button>
|
||||
</adf-viewer-open-with>
|
||||
</adf-alfresco-viewer>
|
||||
`
|
||||
})
|
||||
class ViewerWithCustomOpenWithComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-viewer-container-more-actions',
|
||||
template: `
|
||||
<adf-alfresco-viewer>
|
||||
<adf-viewer-more-actions>
|
||||
<button mat-menu-item>
|
||||
<mat-icon>dialpad</mat-icon>
|
||||
<span>Action One</span>
|
||||
</button>
|
||||
<button mat-menu-item disabled>
|
||||
<mat-icon>voicemail</mat-icon>
|
||||
<span>Action Two</span>
|
||||
</button>
|
||||
<button mat-menu-item>
|
||||
<mat-icon>notifications_off</mat-icon>
|
||||
<span>Action Three</span>
|
||||
</button>
|
||||
</adf-viewer-more-actions>
|
||||
</adf-alfresco-viewer>
|
||||
`
|
||||
})
|
||||
class ViewerWithCustomMoreActionsComponent {
|
||||
}
|
||||
|
||||
|
||||
describe('AlfrescoViewerComponent', () => {
|
||||
|
||||
let component: AlfrescoViewerComponent;
|
||||
let fixture: ComponentFixture<AlfrescoViewerComponent>;
|
||||
let element: HTMLElement;
|
||||
|
||||
let nodesApiService: NodesApiService;
|
||||
let dialog: MatDialog;
|
||||
let uploadService: UploadService;
|
||||
let extensionService: AppExtensionService;
|
||||
let renditionService: RenditionViewerService;
|
||||
let viewUtilService: ViewUtilService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule,
|
||||
MatButtonModule,
|
||||
MatIconModule
|
||||
],
|
||||
declarations: [
|
||||
ViewerWithCustomToolbarComponent,
|
||||
ViewerWithCustomSidebarComponent,
|
||||
ViewerWithCustomOpenWithComponent,
|
||||
ViewerWithCustomMoreActionsComponent,
|
||||
ViewerWithCustomToolbarActionsComponent
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: RenditionViewerService, useValue: {
|
||||
getNodeRendition: () => throwError('thrown'),
|
||||
generateMediaTracksRendition: () => {}
|
||||
}
|
||||
},
|
||||
{provide: Location, useClass: SpyLocation},
|
||||
MatDialog
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AlfrescoViewerComponent);
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
|
||||
uploadService = TestBed.inject(UploadService);
|
||||
nodesApiService = TestBed.inject(NodesApiService);
|
||||
dialog = TestBed.inject(MatDialog);
|
||||
extensionService = TestBed.inject(AppExtensionService);
|
||||
renditionService = TestBed.inject(RenditionViewerService);
|
||||
viewUtilService = TestBed.inject(ViewUtilService);
|
||||
});
|
||||
|
||||
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]);
|
||||
spyOn(renditionService, 'getNodeRendition');
|
||||
spyOn(renditionService, 'generateMediaTracksRendition');
|
||||
spyOn(viewUtilService, 'getViewerType').and.returnValue('external');
|
||||
|
||||
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(renditionService.getNodeRendition).not.toHaveBeenCalled();
|
||||
expect(renditionService.generateMediaTracksRendition).not.toHaveBeenCalled();
|
||||
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');
|
||||
|
||||
nodesApiService.nodeUpdated.next({id: 'id1', name: 'file2'} as any);
|
||||
fixture.detectChanges();
|
||||
expect(component.fileName).toBe('file2');
|
||||
|
||||
nodesApiService.nodeUpdated.next({id: 'id1', name: 'file3'} as any);
|
||||
fixture.detectChanges();
|
||||
expect(component.fileName).toBe('file3');
|
||||
|
||||
nodesApiService.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<HTMLButtonElement>('[data-automation-id="adf-toolbar-next-file"]');
|
||||
expect(nextButton).not.toBeNull();
|
||||
|
||||
const prevButton = element.querySelector<HTMLButtonElement>('[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<HTMLButtonElement>('[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<HTMLButtonElement>('[data-automation-id="adf-toolbar-next-file"]');
|
||||
expect(nextButton).toBeNull();
|
||||
|
||||
const prevButton = element.querySelector<HTMLButtonElement>('[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<HTMLButtonElement>('[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<HTMLButtonElement>('[data-automation-id="adf-toolbar-next-file"]');
|
||||
expect(nextButton).not.toBeNull();
|
||||
|
||||
const prevButton = element.querySelector<HTMLButtonElement>('[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<HTMLButtonElement>('[data-automation-id="adf-toolbar-next-file"]');
|
||||
expect(nextButton).toBeNull();
|
||||
|
||||
const prevButton = element.querySelector<HTMLButtonElement>('[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<HTMLButtonElement>('[data-automation-id="adf-toolbar-next-file"]');
|
||||
expect(nextButton).toBeNull();
|
||||
|
||||
const prevButton = element.querySelector<HTMLButtonElement>('[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-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-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-viewer-close-button')).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should Click on close button hide the viewer', (done) => {
|
||||
const closeButton: any = element.querySelector('.adf-viewer-close-button');
|
||||
closeButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.querySelector('.adf-viewer-content')).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should Esc button hide the viewer', (done) => {
|
||||
EventMock.keyDown(27);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.querySelector('.adf-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-viewer-content')).toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
document.body.dispatchEvent(event);
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('.adf-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-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-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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@@ -0,0 +1,451 @@
|
||||
/*!
|
||||
* @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 {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ContentChild,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import {
|
||||
AlfrescoApiService, ContentService,
|
||||
FileModel,
|
||||
LogService,
|
||||
NodesApiService,
|
||||
Track,
|
||||
UploadService,
|
||||
ViewerComponent,
|
||||
ViewerMoreActionsComponent,
|
||||
ViewerOpenWithComponent,
|
||||
ViewerSidebarComponent,
|
||||
ViewerToolbarActionsComponent,
|
||||
ViewerToolbarComponent,
|
||||
ViewUtilService
|
||||
} from '@alfresco/adf-core';
|
||||
import { Subject } from 'rxjs';
|
||||
import {
|
||||
ContentApi,
|
||||
Node,
|
||||
NodeEntry,
|
||||
NodesApi,
|
||||
RenditionEntry,
|
||||
SharedlinksApi,
|
||||
Version,
|
||||
VersionEntry,
|
||||
VersionsApi
|
||||
} from '@alfresco/js-api';
|
||||
import { RenditionViewerService } from '../services/rendition-viewer.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-alfresco-viewer',
|
||||
templateUrl: './alfresco-viewer.component.html',
|
||||
styleUrls: ['./alfresco-viewer.component.scss'],
|
||||
host: {class: 'adf-alfresco-viewer'},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
providers: [ViewUtilService]
|
||||
})
|
||||
export class AlfrescoViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('adfViewer')
|
||||
adfViewer: ViewerComponent<{node: Node}>;
|
||||
|
||||
@ContentChild(ViewerToolbarComponent)
|
||||
toolbar: ViewerToolbarComponent;
|
||||
|
||||
@ContentChild(ViewerSidebarComponent)
|
||||
sidebar: ViewerSidebarComponent;
|
||||
|
||||
@ContentChild(ViewerToolbarActionsComponent)
|
||||
toolbarActions: ViewerToolbarActionsComponent;
|
||||
|
||||
@ContentChild(ViewerMoreActionsComponent)
|
||||
moreActions: ViewerMoreActionsComponent;
|
||||
|
||||
@ContentChild(ViewerOpenWithComponent)
|
||||
openWith: ViewerOpenWithComponent;
|
||||
|
||||
/** Node Id of the file to load. */
|
||||
@Input()
|
||||
nodeId: string = null;
|
||||
|
||||
/** Version Id of the file to load. */
|
||||
@Input()
|
||||
versionId: string = null;
|
||||
|
||||
/** Shared link id (to display shared file). */
|
||||
@Input()
|
||||
sharedLinkId: string = null;
|
||||
|
||||
/** Hide or show the viewer */
|
||||
@Input()
|
||||
showViewer = true;
|
||||
|
||||
/** Number of times the Viewer will retry fetching content Rendition.
|
||||
* There is a delay of at least one second between attempts.
|
||||
*/
|
||||
@Input()
|
||||
maxRetries = 30;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** Toggles downloading. */
|
||||
@Input()
|
||||
allowDownload = true;
|
||||
|
||||
/** Toggles printing. */
|
||||
@Input()
|
||||
allowPrint = false;
|
||||
|
||||
/** Toggles the 'Full Screen' feature. */
|
||||
@Input()
|
||||
allowFullScreen = true;
|
||||
|
||||
/** The template for the right sidebar. The template context contains the loaded node data. */
|
||||
@Input()
|
||||
sidebarRightTemplate: TemplateRef<any> = null;
|
||||
|
||||
/** The template for the left sidebar. The template context contains the loaded node data. */
|
||||
@Input()
|
||||
sidebarLeftTemplate: TemplateRef<any> = 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<MouseEvent | KeyboardEvent>();
|
||||
|
||||
/** Emitted when user clicks 'Navigate Next' (">") button. */
|
||||
@Output()
|
||||
navigateNext = new EventEmitter<MouseEvent | KeyboardEvent>();
|
||||
|
||||
/** Emitted when the viewer close */
|
||||
@Output()
|
||||
showViewerChange = new EventEmitter<boolean>();
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
private cacheBusterNumber: number;
|
||||
|
||||
versionEntry: VersionEntry;
|
||||
urlFileContent: string;
|
||||
fileName: string;
|
||||
mimeType: string;
|
||||
nodeEntry: NodeEntry;
|
||||
tracks: Track[] = [];
|
||||
readOnly: boolean = true;
|
||||
|
||||
sidebarRightTemplateContext: { node: Node } = {node: null};
|
||||
sidebarLeftTemplateContext: { node: Node } = {node: null};
|
||||
|
||||
_sharedLinksApi: SharedlinksApi;
|
||||
get sharedLinksApi(): SharedlinksApi {
|
||||
this._sharedLinksApi = this._sharedLinksApi ?? new SharedlinksApi(this.apiService.getInstance());
|
||||
return this._sharedLinksApi;
|
||||
}
|
||||
|
||||
_versionsApi: VersionsApi;
|
||||
get versionsApi(): VersionsApi {
|
||||
this._versionsApi = this._versionsApi ?? new VersionsApi(this.apiService.getInstance());
|
||||
return this._versionsApi;
|
||||
}
|
||||
|
||||
_nodesApi: NodesApi;
|
||||
get nodesApi(): NodesApi {
|
||||
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
|
||||
return this._nodesApi;
|
||||
}
|
||||
|
||||
_contentApi: ContentApi;
|
||||
get contentApi(): ContentApi {
|
||||
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
|
||||
return this._contentApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private nodesApiService: NodesApiService,
|
||||
private renditionViewerService: RenditionViewerService,
|
||||
private viewUtilService: ViewUtilService,
|
||||
private logService: LogService,
|
||||
private contentService: ContentService,
|
||||
private uploadService: UploadService,
|
||||
public dialog: MatDialog,
|
||||
private cdr: ChangeDetectorRef) {
|
||||
renditionViewerService.maxRetries = this.maxRetries;
|
||||
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.nodesApiService.nodeUpdated.pipe(
|
||||
filter((node) => node && node.id === this.nodeId &&
|
||||
(node.name !== this.fileName ||
|
||||
this.getNodeVersionProperty(this.nodeEntry.entry) !== this.getNodeVersionProperty(node))),
|
||||
takeUntil(this.onDestroy$)
|
||||
).subscribe((node) => this.onNodeUpdated(node));
|
||||
}
|
||||
|
||||
private async onNodeUpdated(node: Node) {
|
||||
if (node && node.id === this.nodeId) {
|
||||
this.generateCacheBusterNumber();
|
||||
|
||||
await this.setUpNodeFile(node);
|
||||
}
|
||||
}
|
||||
|
||||
private getNodeVersionProperty(node: Node): string {
|
||||
return node?.properties['cm:versionLabel'] ?? '';
|
||||
}
|
||||
|
||||
private async setupSharedLink() {
|
||||
this.allowGoBack = false;
|
||||
|
||||
try {
|
||||
const sharedLinkEntry = await this.sharedLinksApi.getSharedLink(this.sharedLinkId);
|
||||
await this.setUpSharedLinkFile(sharedLinkEntry);
|
||||
} catch (error) {
|
||||
this.logService.error('This sharedLink does not exist');
|
||||
this.invalidSharedLink.next();
|
||||
this.mimeType = 'invalid-link';
|
||||
this.urlFileContent = 'invalid-file';
|
||||
}
|
||||
}
|
||||
|
||||
private async setupNode() {
|
||||
try {
|
||||
this.nodeEntry = await this.nodesApi.getNode(this.nodeId, {include: ['allowableOperations']});
|
||||
if (this.versionId) {
|
||||
this.versionEntry = await this.versionsApi.getVersion(this.nodeId, this.versionId);
|
||||
await this.setUpNodeFile(this.nodeEntry.entry, this.versionEntry.entry);
|
||||
} else {
|
||||
await this.setUpNodeFile(this.nodeEntry.entry);
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
} catch (error) {
|
||||
this.urlFileContent = 'invalid-node';
|
||||
this.logService.error('This node does not exist');
|
||||
}
|
||||
}
|
||||
|
||||
private async setUpNodeFile(nodeData: Node, versionData?: Version): Promise<void> {
|
||||
|
||||
this.readOnly = !this.contentService.hasAllowableOperations(nodeData, 'update');
|
||||
let mimeType;
|
||||
let urlFileContent;
|
||||
|
||||
if (versionData && versionData.content) {
|
||||
mimeType = versionData.content.mimeType;
|
||||
} else if (nodeData.content) {
|
||||
mimeType = nodeData.content.mimeType;
|
||||
}
|
||||
|
||||
const currentFileVersion = this.nodeEntry?.entry?.properties && this.nodeEntry.entry.properties['cm:versionLabel'] ?
|
||||
encodeURI(this.nodeEntry?.entry?.properties['cm:versionLabel']) : encodeURI('1.0');
|
||||
|
||||
urlFileContent = versionData ? this.contentApi.getVersionContentUrl(this.nodeId, versionData.id) :
|
||||
this.contentApi.getContentUrl(this.nodeId);
|
||||
urlFileContent = this.cacheBusterNumber ? urlFileContent + '&' + currentFileVersion + '&' + this.cacheBusterNumber :
|
||||
urlFileContent + '&' + currentFileVersion;
|
||||
|
||||
const fileExtension = this.viewUtilService.getFileExtension(versionData ? versionData.name : nodeData.name);
|
||||
this.fileName = versionData ? versionData.name : nodeData.name;
|
||||
const viewerType = this.viewUtilService.getViewerType(fileExtension, mimeType);
|
||||
|
||||
if (viewerType === 'unknown') {
|
||||
let nodeRendition;
|
||||
if (versionData) {
|
||||
nodeRendition = await this.renditionViewerService.getNodeRendition(nodeData.id, versionData.id);
|
||||
} else {
|
||||
nodeRendition = await this.renditionViewerService.getNodeRendition(nodeData.id);
|
||||
}
|
||||
if(nodeRendition){
|
||||
urlFileContent = nodeRendition.url;
|
||||
mimeType = nodeRendition.mimeType;
|
||||
}
|
||||
} else if (viewerType === 'media') {
|
||||
this.tracks = await this.renditionViewerService.generateMediaTracksRendition(this.nodeId);
|
||||
}
|
||||
|
||||
this.mimeType = mimeType;
|
||||
this.urlFileContent = urlFileContent;
|
||||
this.sidebarRightTemplateContext.node = nodeData;
|
||||
this.sidebarLeftTemplateContext.node = nodeData;
|
||||
}
|
||||
|
||||
private async setUpSharedLinkFile(details: any) {
|
||||
let mimeType = details.entry.content.mimeType;
|
||||
const fileExtension = this.viewUtilService.getFileExtension(details.entry.name);
|
||||
this.fileName = details.entry.name;
|
||||
let urlFileContent = this.contentApi.getSharedLinkContentUrl(this.sharedLinkId, false);
|
||||
const viewerType = this.viewUtilService.getViewerType(fileExtension, mimeType);
|
||||
|
||||
if (viewerType === 'unknown') {
|
||||
({
|
||||
url: urlFileContent,
|
||||
mimeType
|
||||
} = await this.getSharedLinkRendition(this.sharedLinkId));
|
||||
}
|
||||
this.mimeType = mimeType;
|
||||
this.urlFileContent = urlFileContent;
|
||||
}
|
||||
|
||||
private async getSharedLinkRendition(sharedId: string): Promise<{ url: string; mimeType: string }> {
|
||||
try {
|
||||
const rendition: RenditionEntry = await this.sharedLinksApi.getSharedLinkRendition(sharedId, 'pdf');
|
||||
if (rendition.entry.status.toString() === 'CREATED') {
|
||||
const urlFileContent = this.contentApi.getSharedLinkRenditionUrl(sharedId, 'pdf');
|
||||
return {url: urlFileContent, mimeType: 'application/pdf'};
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
try {
|
||||
const rendition: RenditionEntry = await this.sharedLinksApi.getSharedLinkRendition(sharedId, 'imgpreview');
|
||||
if (rendition.entry.status.toString() === 'CREATED') {
|
||||
const urlFileContent = this.contentApi.getSharedLinkRenditionUrl(sharedId, 'imgpreview');
|
||||
return {url: urlFileContent, mimeType: 'image/png'};
|
||||
|
||||
}
|
||||
} catch (renditionError) {
|
||||
this.logService.error(renditionError);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private generateCacheBusterNumber() {
|
||||
this.cacheBusterNumber = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* close the viewer
|
||||
*/
|
||||
onClose() {
|
||||
this.showViewerChange.emit(this.showViewer);
|
||||
}
|
||||
|
||||
onPrintContent(event: MouseEvent) {
|
||||
if (this.allowPrint) {
|
||||
if (!event.defaultPrevented) {
|
||||
this.renditionViewerService.printFileGeneric(this.nodeId, this.mimeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSubmitFile(newImageBlob: Blob) {
|
||||
if (this?.nodeEntry?.entry?.id && !this.readOnly) {
|
||||
const newImageFile: File = new File([newImageBlob], this?.nodeEntry?.entry?.name, {type: this?.nodeEntry?.entry?.content?.mimeType});
|
||||
const newFile = new FileModel(
|
||||
newImageFile,
|
||||
{
|
||||
majorVersion: false,
|
||||
newVersion: true,
|
||||
parentId: this?.nodeEntry?.entry?.parentId,
|
||||
nodeType: this?.nodeEntry?.entry?.content?.mimeType
|
||||
},
|
||||
this?.nodeEntry?.entry?.id
|
||||
);
|
||||
this.uploadService.addToQueue(...[newFile]);
|
||||
this.uploadService.uploadFilesInTheQueue();
|
||||
}
|
||||
}
|
||||
|
||||
onNavigateBeforeClick(event: MouseEvent | KeyboardEvent) {
|
||||
this.navigateBefore.next(event);
|
||||
}
|
||||
|
||||
onNavigateNextClick(event: MouseEvent | KeyboardEvent) {
|
||||
this.navigateNext.next(event);
|
||||
}
|
||||
|
||||
isSourceDefined(): boolean {
|
||||
return !!(this.nodeId || this.sharedLinkId);
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.showViewer) {
|
||||
if (!this.isSourceDefined()) {
|
||||
throw new Error('A content source attribute value is missing.');
|
||||
}
|
||||
|
||||
if (this.nodeId) {
|
||||
this.setupNode();
|
||||
} else if (this.sharedLinkId) {
|
||||
this.setupSharedLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
}
|
18
lib/content-services/src/lib/viewer/index.ts
Normal file
18
lib/content-services/src/lib/viewer/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
export * from './public-api';
|
21
lib/content-services/src/lib/viewer/public-api.ts
Normal file
21
lib/content-services/src/lib/viewer/public-api.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
export * from './services/rendition-viewer.service';
|
||||
export * from './components/alfresco-viewer.component';
|
||||
|
||||
export * from './alfresco-viewer.module';
|
@@ -0,0 +1,306 @@
|
||||
/*!
|
||||
* @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 { Injectable } from '@angular/core';
|
||||
import { ContentApi, RenditionEntry, RenditionPaging, RenditionsApi, VersionsApi } from '@alfresco/js-api';
|
||||
import { AlfrescoApiService , LogService, Track,TranslationService, ViewUtilService } from '@alfresco/adf-core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RenditionViewerService {
|
||||
|
||||
static TARGET = '_new';
|
||||
|
||||
/**
|
||||
* Content groups based on categorization of files that can be viewed in the web browser. This
|
||||
* implementation or grouping is tied to the definition the ng component: ViewerRenderComponent
|
||||
*/
|
||||
static ContentGroup = {
|
||||
IMAGE: 'image',
|
||||
MEDIA: 'media',
|
||||
PDF: 'pdf',
|
||||
TEXT: 'text'
|
||||
};
|
||||
|
||||
/**
|
||||
* The name of the rendition with the media subtitles in the supported format
|
||||
*/
|
||||
static SUBTITLES_RENDITION_NAME = 'webvtt';
|
||||
|
||||
/**
|
||||
* Based on ViewerRenderComponent Implementation, this value is used to determine how many times we try
|
||||
* to get the rendition of a file for preview, or printing.
|
||||
*/
|
||||
maxRetries = 5;
|
||||
|
||||
/**
|
||||
* Timeout used for setInterval.
|
||||
*/
|
||||
private TRY_TIMEOUT: number = 10000;
|
||||
|
||||
|
||||
_renditionsApi: RenditionsApi;
|
||||
get renditionsApi(): RenditionsApi {
|
||||
this._renditionsApi = this._renditionsApi ?? new RenditionsApi(this.apiService.getInstance());
|
||||
return this._renditionsApi;
|
||||
}
|
||||
|
||||
_contentApi: ContentApi;
|
||||
get contentApi(): ContentApi {
|
||||
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
|
||||
return this._contentApi;
|
||||
}
|
||||
|
||||
_versionsApi: VersionsApi;
|
||||
private DEFAULT_RENDITION: string = 'imgpreview';
|
||||
|
||||
get versionsApi(): VersionsApi {
|
||||
this._versionsApi = this._versionsApi ?? new VersionsApi(this.apiService.getInstance());
|
||||
return this._versionsApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService,
|
||||
private translateService: TranslationService,
|
||||
private viewUtilsService: ViewUtilService) {
|
||||
}
|
||||
|
||||
|
||||
getRenditionUrl(nodeId: string, type: string, renditionExists: boolean): string {
|
||||
return (renditionExists && type !== RenditionViewerService.ContentGroup.IMAGE) ?
|
||||
this.contentApi.getRenditionUrl(nodeId, RenditionViewerService.ContentGroup.PDF) :
|
||||
this.contentApi.getContentUrl(nodeId, false);
|
||||
}
|
||||
|
||||
private async waitRendition(nodeId: string, renditionId: string, retries: number): Promise<RenditionEntry> {
|
||||
const rendition = await this.renditionsApi.getRendition(nodeId, renditionId);
|
||||
|
||||
if (this.maxRetries < retries) {
|
||||
const status = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'CREATED') {
|
||||
return rendition;
|
||||
} else {
|
||||
retries += 1;
|
||||
await this.wait(1000);
|
||||
return this.waitRendition(nodeId, renditionId, retries);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
private wait(ms: number): Promise<any> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async getRendition(nodeId: string, renditionId: string): Promise<RenditionEntry> {
|
||||
const renditionPaging: RenditionPaging = await this.renditionsApi.listRenditions(nodeId);
|
||||
let rendition: RenditionEntry = renditionPaging.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
|
||||
|
||||
if (rendition) {
|
||||
const status = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'NOT_CREATED') {
|
||||
try {
|
||||
await this.renditionsApi.createRendition(nodeId, {id: renditionId});
|
||||
rendition = await this.waitRendition(nodeId, renditionId, 0);
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Promise<RenditionEntry>((resolve) => resolve(rendition));
|
||||
}
|
||||
|
||||
async getNodeRendition(nodeId: string, versionId?: string): Promise<{ url: string; mimeType: string }> {
|
||||
try {
|
||||
return versionId ? await this.resolveNodeRendition(nodeId, 'pdf', versionId) :
|
||||
await this.resolveNodeRendition(nodeId, 'pdf');
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<{ url: string; mimeType: string }> {
|
||||
renditionId = renditionId.toLowerCase();
|
||||
|
||||
const supportedRendition: RenditionPaging = versionId ? await this.versionsApi.listVersionRenditions(nodeId, versionId) :
|
||||
await this.renditionsApi.listRenditions(nodeId);
|
||||
|
||||
let rendition = this.findRenditionById(supportedRendition, renditionId);
|
||||
if (!rendition) {
|
||||
renditionId = this.DEFAULT_RENDITION;
|
||||
rendition = this.findRenditionById(supportedRendition, this.DEFAULT_RENDITION);
|
||||
}
|
||||
|
||||
if (rendition) {
|
||||
const status: string = rendition.entry.status.toString();
|
||||
const mimeType: string = rendition.entry.content.mimeType;
|
||||
|
||||
if (status === 'NOT_CREATED') {
|
||||
return {url: await this.requestCreateRendition(nodeId, renditionId, versionId), mimeType:mimeType};
|
||||
} else {
|
||||
return {url: await this.handleNodeRendition(nodeId, renditionId, versionId), mimeType:mimeType};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async requestCreateRendition(nodeId: string, renditionId: string, versionId: string): Promise<string> {
|
||||
try {
|
||||
if (versionId) {
|
||||
await this.versionsApi.createVersionRendition(nodeId, versionId, {id: renditionId});
|
||||
} else {
|
||||
await this.renditionsApi.createRendition(nodeId, {id: renditionId});
|
||||
}
|
||||
try {
|
||||
return versionId ? await this.waitNodeRendition(nodeId, renditionId, versionId) : await this.waitNodeRendition(nodeId, renditionId);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private findRenditionById(supportedRendition: RenditionPaging, renditionId: string) {
|
||||
const rendition: RenditionEntry = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
|
||||
return rendition;
|
||||
}
|
||||
|
||||
private async waitNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<string> {
|
||||
let currentRetry: number = 0;
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const intervalId = setInterval(() => {
|
||||
currentRetry++;
|
||||
if (this.maxRetries >= currentRetry) {
|
||||
if (versionId) {
|
||||
this.versionsApi.getVersionRendition(nodeId, versionId, renditionId).then((rendition: RenditionEntry) => {
|
||||
const status: string = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'CREATED') {
|
||||
clearInterval(intervalId);
|
||||
return resolve(this.handleNodeRendition(nodeId, rendition.entry.content.mimeType, versionId));
|
||||
}
|
||||
}, () => reject());
|
||||
} else {
|
||||
this.renditionsApi.getRendition(nodeId, renditionId).then((rendition: RenditionEntry) => {
|
||||
const status: string = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'CREATED') {
|
||||
clearInterval(intervalId);
|
||||
return resolve(this.handleNodeRendition(nodeId, renditionId, versionId));
|
||||
}
|
||||
}, () => reject());
|
||||
}
|
||||
} else {
|
||||
clearInterval(intervalId);
|
||||
return reject();
|
||||
}
|
||||
}, this.TRY_TIMEOUT);
|
||||
});
|
||||
}
|
||||
|
||||
private async handleNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<string> {
|
||||
|
||||
const url = versionId ? this.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
|
||||
this.contentApi.getRenditionUrl(nodeId, renditionId);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
async generateMediaTracksRendition(nodeId: string): Promise<Track[]> {
|
||||
return this.isRenditionAvailable(nodeId, RenditionViewerService.SUBTITLES_RENDITION_NAME)
|
||||
.then((value) => {
|
||||
const tracks = [];
|
||||
if (value) {
|
||||
tracks.push({
|
||||
kind: 'subtitles',
|
||||
src: this.contentApi.getRenditionUrl(nodeId, RenditionViewerService.SUBTITLES_RENDITION_NAME),
|
||||
label: this.translateService.instant('ADF_VIEWER.SUBTITLES')
|
||||
});
|
||||
}
|
||||
return tracks;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logService.error('Error while retrieving ' + RenditionViewerService.SUBTITLES_RENDITION_NAME + ' rendition');
|
||||
this.logService.error(err);
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
private async isRenditionAvailable(nodeId: string, renditionId: string): Promise<boolean> {
|
||||
const renditionPaging: RenditionPaging = await this.renditionsApi.listRenditions(nodeId);
|
||||
const rendition: RenditionEntry = renditionPaging.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
|
||||
|
||||
return rendition?.entry?.status?.toString() === 'CREATED' || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a url to trigger the print dialog against, and the type of artifact that it
|
||||
* is.
|
||||
* This URL should be one that can be rendered in the browser, for example PDF, Image, or Text
|
||||
*/
|
||||
printFile(url: string, type: string): void {
|
||||
const pwa = window.open(url, RenditionViewerService.TARGET);
|
||||
if (pwa) {
|
||||
// Because of the way chrome focus and close image window vs. pdf preview window
|
||||
if (type === RenditionViewerService.ContentGroup.IMAGE) {
|
||||
pwa.onfocus = () => {
|
||||
setTimeout(() => {
|
||||
pwa.close();
|
||||
}, 500);
|
||||
};
|
||||
}
|
||||
|
||||
pwa.onload = () => {
|
||||
pwa.print();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the File Print dialog from anywhere other than the preview service, which resolves the
|
||||
* rendition of the object that can be printed from a web browser.
|
||||
* These are: images, PDF files, or PDF rendition of files.
|
||||
* We also force PDF rendition for TEXT type objects, otherwise the default URL is to download.
|
||||
* TODO there are different TEXT type objects, (HTML, plaintext, xml, etc. we should determine how these are handled)
|
||||
*/
|
||||
printFileGeneric(objectId: string, mimeType: string): void {
|
||||
const nodeId = objectId;
|
||||
const type: string = this.viewUtilsService.getViewerTypeByMimeType(mimeType);
|
||||
|
||||
this.getRendition(nodeId, RenditionViewerService.ContentGroup.PDF)
|
||||
.then((value) => {
|
||||
const url: string = this.getRenditionUrl(nodeId, type, (!!value));
|
||||
const printType = (type === RenditionViewerService.ContentGroup.PDF
|
||||
|| type === RenditionViewerService.ContentGroup.TEXT)
|
||||
? RenditionViewerService.ContentGroup.PDF : type;
|
||||
this.printFile(url, printType);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logService.error('Error with Printing');
|
||||
this.logService.error(err);
|
||||
});
|
||||
}
|
||||
}
|
@@ -44,5 +44,6 @@ export * from './lib/common/index';
|
||||
export * from './lib/tree/index';
|
||||
export * from './lib/category/index';
|
||||
export * from './lib/search-text/index';
|
||||
export * from './lib/viewer/index';
|
||||
|
||||
export * from './lib/content.module';
|
||||
|
Reference in New Issue
Block a user