[ADF-1623] routing integration for Viewer (#2404)

* routed viewer (demo app)

* toolbar support

* app menu component for demo shell

* navigate back button

* fix unit tests

* improve viewer type detection and rendering

* download button

* automatic pdf rendition, spinners, ui tweaks

* border for pdf pages

* scroll top support

* docs update

* info drawer placeholder
This commit is contained in:
Denys Vuika
2017-10-03 11:57:23 +01:00
committed by Maurizio Vitale
parent 55a999b492
commit 93a87af4a5
40 changed files with 674 additions and 846 deletions

View File

@@ -1,97 +1 @@
<md-toolbar color="primary" *ngIf="!isAPageWithHeaderBar()" class="adf-app-toolbar" >
<adf-userinfo
class="adf-app-user-profile"
[menuPositionX]="'before'"
[menuPositionY]="'above'">
</adf-userinfo>
<span class="adf-app-hide-xsmall" >ADF Demo Application</span>
<div class="adf-app-menu-spacer"></div>
<search-bar></search-bar>
<a class="adf-app-hide-xsmall adf-app-hide-small" md-button data-automation-id="home" href="" routerLink="/">Home</a>
<a class="adf-app-hide-xsmall adf-app-hide-small" md-button data-automation-id="files" href="" routerLink="/files">Content Services</a>
<a class="adf-app-hide-xsmall adf-app-hide-small" md-button data-automation-id="activiti" href="" routerLink="/activiti">Process Services</a>
<a class="adf-app-hide-xsmall adf-app-hide-small" md-button data-automation-id="login" href="" routerLink="/login">Login</a>
<theme-picker></theme-picker>
<button md-icon-button [mdMenuTriggerFor]="langMenu">
<md-icon>language</md-icon>
</button>
<md-menu #langMenu="mdMenu">
<button md-menu-item (click)="changeLanguage('en')">English</button>
<button md-menu-item (click)="changeLanguage('it')">Italian</button>
<button md-menu-item (click)="changeLanguage('ru')">Russian</button>
</md-menu>
<button md-icon-button [mdMenuTriggerFor]="appMenu">
<md-icon>more_vert</md-icon>
</button>
<md-menu #appMenu="mdMenu">
<a md-menu-item href="" routerLink="/">
<md-icon>home</md-icon>
<span>Home</span>
</a>
<a md-menu-item href="" routerLink="/files">
<md-icon>folder_open</md-icon>
<span>Content Services</span>
</a>
<a md-menu-item href="" routerLink="/activiti">
<md-icon>device_hub</md-icon>
<span>Process Services</span>
</a>
<a md-menu-item href="" routerLink="/login">
<md-icon>vpn_key</md-icon>
<span>Login</span>
</a>
<a md-menu-item href="" routerLink="/dl-custom-sources">
<md-icon>extension</md-icon>
<span>DL: Custom Sources</span>
</a>
<a md-menu-item href="" routerLink="/datatable">
<md-icon>view_module</md-icon>
<span>DataTable</span>
</a>
<a md-menu-item href="" routerLink="/form">
<md-icon>poll</md-icon>
<span>Form</span>
</a>
<a md-menu-item href="" routerLink="/form-list">
<md-icon>library_books</md-icon>
<span>Form List</span>
</a>
<a md-menu-item href="" routerLink="/uploader">
<md-icon>file_upload</md-icon>
<span>Uploader</span>
</a>
<a md-menu-item href="" routerLink="/webscript">
<md-icon>extension</md-icon>
<span>Webscript</span>
</a>
<a md-menu-item href="" routerLink="/tag">
<md-icon>local_offer</md-icon>
<span>Tag</span>
</a>
<a md-menu-item href="" routerLink="/social">
<md-icon>thumb_up</md-icon>
<span>Social</span>
</a>
<a md-menu-item href="" routerLink="/settings">
<md-icon>settings</md-icon>
<span>Settings</span>
</a>
<a md-menu-item href="" routerLink="/about">
<md-icon>info_outline</md-icon>
<span>About</span>
</a>
<button md-menu-item adf-logout>
<md-icon>exit_to_app</md-icon>
<span>Logout</span>
</button>
</md-menu>
</md-toolbar>
<router-outlet></router-outlet>

View File

@@ -16,33 +16,24 @@
*/
import { Component, ViewEncapsulation } from '@angular/core';
import { AlfrescoSettingsService, AlfrescoTranslationService, PageTitle, StorageService } from 'ng2-alfresco-core';
import { AlfrescoSettingsService, PageTitle, StorageService } from 'ng2-alfresco-core';
@Component({
selector: 'adf-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss', './theme.scss'],
styleUrls: ['./theme.scss'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
searchTerm: string = '';
constructor(private settingsService: AlfrescoSettingsService,
private translateService: AlfrescoTranslationService,
private storage: StorageService,
pageTitle: PageTitle) {
this.setProvider();
pageTitle.setTitle();
}
isAPageWithHeaderBar(): boolean {
return location.pathname === '/login' || location.pathname === '/settings';
}
changeLanguage(lang: string) {
this.translateService.use(lang);
}
private setProvider() {
if (this.storage.hasItem(`providers`)) {
this.settingsService.setProviders(this.storage.getItem(`providers`));

View File

@@ -41,6 +41,8 @@ import { ChartsModule } from 'ng2-charts';
import { AppComponent } from './app.component';
import { routing } from './app.routes';
import { CustomEditorsModule } from './components/activiti/custom-editor/custom-editor.component';
import { AppMenuComponent } from './components/app-menu/app-menu.component';
import { FileViewComponent } from './components/file-view/file-view.component';
import { FormListDemoComponent } from './components/form/form-list-demo.component';
import { ThemePickerModule } from './components/theme-picker/theme-picker';
import { MaterialModule } from './material.module';
@@ -119,7 +121,9 @@ import {
SettingsComponent,
FormDemoComponent,
FormListDemoComponent,
CustomSourcesComponent
CustomSourcesComponent,
FileViewComponent,
AppMenuComponent
],
providers: [
{ provide: AppConfigService, useClass: DebugAppConfigService },

View File

@@ -39,6 +39,7 @@ import {
} from './components/index';
import { UploadButtonComponent } from 'ng2-alfresco-upload';
import { FileViewComponent } from './components/file-view/file-view.component';
import { CustomSourcesComponent } from './components/files/custom-sources.component';
import { FormListDemoComponent } from './components/form/form-list-demo.component';
@@ -64,6 +65,11 @@ export const appRoutes: Routes = [
component: FilesComponent,
canActivate: [AuthGuardEcm]
},
{
path: 'files/:nodeId/view',
component: FileViewComponent,
canActivate: [ AuthGuardEcm ]
},
{
path: 'dl-custom-sources',
component: CustomSourcesComponent,

View File

@@ -1,3 +1,5 @@
<adf-app-menu></adf-app-menu>
<div class="about-container">
<h3>Server settings</h3>
<md-list>

View File

@@ -17,7 +17,7 @@
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import { AlfrescoAuthenticationService, AppConfigService, BpmProductVersionModel, DiscoveryApiService, EcmProductVersionModel, LogService } from 'ng2-alfresco-core';
import { AlfrescoAuthenticationService, AppConfigService, BpmProductVersionModel, DiscoveryApiService, EcmProductVersionModel } from 'ng2-alfresco-core';
import { ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
@Component({
@@ -40,7 +40,6 @@ export class AboutComponent implements OnInit {
constructor(private http: Http,
private appConfig: AppConfigService,
private authService: AlfrescoAuthenticationService,
private logService: LogService,
private discovery: DiscoveryApiService) {
}
@@ -62,13 +61,11 @@ export class AboutComponent implements OnInit {
let regexp = new RegExp('^(ng2-activiti|ng2-alfresco|alfresco-)');
let alfrescoPackages = Object.keys(response.json().dependencies).filter((val) => {
this.logService.log(val);
return regexp.test(val);
});
let alfrescoPackagesTableRepresentation = [];
alfrescoPackages.forEach((val) => {
this.logService.log(response.json().dependencies[val]);
alfrescoPackagesTableRepresentation.push({
name: val,
version: response.json().dependencies[val].version
@@ -77,8 +74,6 @@ export class AboutComponent implements OnInit {
this.gitHubLinkCreation(alfrescoPackagesTableRepresentation);
this.logService.log(alfrescoPackagesTableRepresentation);
this.data = new ObjectDataTableAdapter(alfrescoPackagesTableRepresentation, [
{type: 'text', key: 'name', title: 'Name', sortable: true},
{type: 'text', key: 'version', title: 'Version', sortable: true}

View File

@@ -1,3 +1,5 @@
<adf-app-menu></adf-app-menu>
<md-tab-group [(selectedIndex)]="activeTab">
<md-tab id="tasks-header" href="#tasks" label="{{'PS-TAB.TASKS-TAB' | translate}}">
<div class="page-content">

View File

@@ -22,6 +22,7 @@ import { AppDefinitionRepresentationModel } from 'ng2-activiti-tasklist';
@Component({
selector: 'activiti-apps-view',
template: `
<adf-app-menu></adf-app-menu>
<activiti-apps (appClick)="onAppClicked($event)"></activiti-apps>
`
})

View File

@@ -0,0 +1,96 @@
<md-toolbar color="primary" class="adf-app-toolbar">
<adf-userinfo
class="adf-app-user-profile"
[menuPositionX]="'before'"
[menuPositionY]="'above'">
</adf-userinfo>
<span class="adf-app-hide-xsmall">ADF Demo Application</span>
<div class="adf-app-menu-spacer"></div>
<search-bar></search-bar>
<a class="adf-app-hide-xsmall adf-app-hide-small" md-button data-automation-id="home" href="" routerLink="/">Home</a>
<a class="adf-app-hide-xsmall adf-app-hide-small" md-button data-automation-id="files" href="" routerLink="/files">Content Services</a>
<a class="adf-app-hide-xsmall adf-app-hide-small" md-button data-automation-id="activiti" href="" routerLink="/activiti">Process Services</a>
<a class="adf-app-hide-xsmall adf-app-hide-small" md-button data-automation-id="login" href="" routerLink="/login">Login</a>
<theme-picker></theme-picker>
<button md-icon-button [mdMenuTriggerFor]="langMenu">
<md-icon>language</md-icon>
</button>
<md-menu #langMenu="mdMenu">
<button md-menu-item (click)="changeLanguage('en')">English</button>
<button md-menu-item (click)="changeLanguage('it')">Italian</button>
<button md-menu-item (click)="changeLanguage('ru')">Russian</button>
</md-menu>
<button md-icon-button [mdMenuTriggerFor]="appMenu">
<md-icon>more_vert</md-icon>
</button>
<md-menu #appMenu="mdMenu">
<a md-menu-item href="" routerLink="/">
<md-icon>home</md-icon>
<span>Home</span>
</a>
<a md-menu-item href="" routerLink="/files">
<md-icon>folder_open</md-icon>
<span>Content Services</span>
</a>
<a md-menu-item href="" routerLink="/activiti">
<md-icon>device_hub</md-icon>
<span>Process Services</span>
</a>
<a md-menu-item href="" routerLink="/login">
<md-icon>vpn_key</md-icon>
<span>Login</span>
</a>
<a md-menu-item href="" routerLink="/dl-custom-sources">
<md-icon>extension</md-icon>
<span>DL: Custom Sources</span>
</a>
<a md-menu-item href="" routerLink="/datatable">
<md-icon>view_module</md-icon>
<span>DataTable</span>
</a>
<a md-menu-item href="" routerLink="/form">
<md-icon>poll</md-icon>
<span>Form</span>
</a>
<a md-menu-item href="" routerLink="/form-list">
<md-icon>library_books</md-icon>
<span>Form List</span>
</a>
<a md-menu-item href="" routerLink="/uploader">
<md-icon>file_upload</md-icon>
<span>Uploader</span>
</a>
<a md-menu-item href="" routerLink="/webscript">
<md-icon>extension</md-icon>
<span>Webscript</span>
</a>
<a md-menu-item href="" routerLink="/tag">
<md-icon>local_offer</md-icon>
<span>Tag</span>
</a>
<a md-menu-item href="" routerLink="/social">
<md-icon>thumb_up</md-icon>
<span>Social</span>
</a>
<a md-menu-item href="" routerLink="/settings">
<md-icon>settings</md-icon>
<span>Settings</span>
</a>
<a md-menu-item href="" routerLink="/about">
<md-icon>info_outline</md-icon>
<span>About</span>
</a>
<button md-menu-item adf-logout>
<md-icon>exit_to_app</md-icon>
<span>Logout</span>
</button>
</md-menu>
</md-toolbar>

View File

@@ -1,6 +1,5 @@
@import '~@angular/material/theming';
.adf-app-user-profile {
margin-right: 10px;
}

View File

@@ -0,0 +1,34 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component } from '@angular/core';
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
@Component({
selector: 'adf-app-menu',
templateUrl: 'app-menu.component.html',
styleUrls: ['app-menu.component.scss']
})
export class AppMenuComponent {
constructor(private translateService: AlfrescoTranslationService) {
}
changeLanguage(lang: string) {
this.translateService.use(lang);
}
}

View File

@@ -1,3 +1,5 @@
<adf-app-menu></adf-app-menu>
<div class="p-10">
<alfresco-datatable
[data]="data"

View File

@@ -0,0 +1,9 @@
<ng-container *ngIf="nodeId">
<adf-viewer [fileNodeId]="nodeId">
<extension-viewer [supportedExtensions]="['obj','3DS']" #extension>
<ng-template let-urlFileContent="urlFileContent" let-extension="extension" >
<threed-viewer [urlFile]="urlFileContent" [extension]="extension" ></threed-viewer>
</ng-template>
</extension-viewer>
</adf-viewer>
</ng-container>

View File

@@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { AlfrescoApiService } from 'ng2-alfresco-core';
@Component({
selector: 'adf-file-view',
templateUrl: 'file-view.component.html'
})
export class FileViewComponent implements OnInit {
nodeId: string = null;
constructor(
private router: Router,
private route: ActivatedRoute,
private apiService: AlfrescoApiService) {}
ngOnInit() {
this.route.params.subscribe(params => {
const id = params.nodeId;
if (id) {
this.apiService.getInstance().nodes.getNodeInfo(id).then(
(node) => {
if (node && node.isFile) {
this.nodeId = id;
return;
}
this.router.navigate(['/files', id]);
},
() => this.router.navigate(['/files', id])
);
}
});
}
}

View File

@@ -1,3 +1,4 @@
<adf-app-menu></adf-app-menu>
<adf-toolbar>
<adf-toolbar-title>
<md-select [(ngModel)]="selectedSource">

View File

@@ -1,3 +1,5 @@
<adf-app-menu></adf-app-menu>
<div class="container">
<div class="adf-demo-site-container-style" id="demo-container">
<adf-sites-dropdown (change)="getSiteContent($event)">
@@ -225,14 +227,6 @@
</div>
<div class="container">
<section *ngIf="!useViewerDialog">
<md-slide-toggle [color]="'primary'" [(ngModel)]="useInlineViewer">Use inline viewer (no overlay)</md-slide-toggle>
</section>
<section>
<md-slide-toggle [color]="'primary'" [(ngModel)]="useViewerDialog">Use File Viewer dialog</md-slide-toggle>
</section>
<section>
<md-slide-toggle [color]="'primary'" [(ngModel)]="multiselect">Multiselect (with checkboxes)</md-slide-toggle>
</section>
@@ -309,29 +303,3 @@
</div>
<file-uploading-dialog #fileDialog></file-uploading-dialog>
<div *ngIf="showViewer">
<ng-container *ngIf="!useInlineViewer">
<adf-viewer
[(showViewer)]="showViewer"
[fileNodeId]="fileNodeId"
[overlayMode]="true">
<extension-viewer [supportedExtensions]="['obj','3DS']" #extension>
<ng-template let-urlFileContent="urlFileContent" let-extension="extension" >
<threed-viewer [urlFile]="urlFileContent" [extension]="extension" ></threed-viewer>
</ng-template>
</extension-viewer>
</adf-viewer>
</ng-container>
<ng-container *ngIf="useInlineViewer">
<div class="adf-not-overlay-viewer">
<adf-viewer
[(showViewer)]="showViewer"
[fileNodeId]="fileNodeId">
</adf-viewer>
</div>
</ng-container>
</div>

View File

@@ -27,8 +27,6 @@ import {
import { DataColumn, DataRow } from 'ng2-alfresco-datatable';
import { DocumentListComponent, PermissionStyleModel } from 'ng2-alfresco-documentlist';
import { ViewerService } from 'ng2-alfresco-viewer';
const DEFAULT_FOLDER_TO_SHOW = '-my-';
@Component({
@@ -46,8 +44,6 @@ export class FilesComponent implements OnInit {
toolbarColor = 'default';
useDropdownBreadcrumb = false;
useViewerDialog = true;
useInlineViewer = false;
selectionModes = [
{ value: 'none', viewValue: 'None' },
@@ -91,27 +87,14 @@ export class FilesComponent implements OnInit {
private contentService: AlfrescoContentService,
private dialog: MdDialog,
private translateService: AlfrescoTranslationService,
private viewerService: ViewerService,
private router: Router,
@Optional() private route: ActivatedRoute) {
}
showFile(event) {
if (this.useViewerDialog) {
if (event.value.entry.isFile) {
this.viewerService
.showViewerForNode(event.value.entry)
.then(result => {
console.log(result);
});
}
} else {
if (event.value.entry.isFile) {
this.fileNodeId = event.value.entry.id;
this.showViewer = true;
} else {
this.showViewer = false;
}
const entry = event.value.entry;
if (entry && entry.isFile) {
this.router.navigate(['/files', entry.id, 'view']);
}
}

View File

@@ -1,3 +1,5 @@
<adf-app-menu></adf-app-menu>
<div class="form-container">
<adf-form [form]="form">
</adf-form>

View File

@@ -22,6 +22,7 @@ import { ActivitiForm } from 'ng2-activiti-form';
@Component({
selector: 'form-list-demo',
template: `
<adf-app-menu></adf-app-menu>
<adf-form-list [forms]="formList" (row-dblclick)="onRowDblClick($event)">
</adf-form-list>
<div class="form-container" *ngIf="!isEmptyForm()">

View File

@@ -1,3 +1,5 @@
<adf-app-menu></adf-app-menu>
<!-- DOCUMENT LIST-->
<md-card class="adf-home-card">
<md-card-title class="adf-home-card-title adf-primary-background-color" routerLink="/files">

View File

@@ -1,3 +1,5 @@
<adf-app-menu></adf-app-menu>
<label for="nodeId"><b>Insert Node Id</b></label><br>
<input id="nodeId" type="text" size="48" [(ngModel)]="nodeId"><br>
<md-grid-list cols="2" rowHeight="100px">

View File

@@ -1,3 +1,5 @@
<adf-app-menu></adf-app-menu>
<label for="nodeId"><b>Insert Node Id</b></label><br>
<input id="nodeId" type="text" size="48" [(ngModel)]="nodeId"><br>
<div class="adf-tag-example-area">

View File

@@ -21,6 +21,7 @@ import { LogService } from 'ng2-alfresco-core';
@Component({
selector: 'alfresco-webscript-demo',
template: `
<adf-app-menu></adf-app-menu>
<label for="script-path"><b>Insert a scriptPath</b></label><br>
<input id="script-path" type="text" size="48" [(ngModel)]="scriptPath"><br>
<label for="context-root"><b>Insert a contextRoot</b></label><br>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -7,7 +7,7 @@ See it live: [Viewer Quickstart](https://embed.plnkr.co/iTuG1lFIXfsP95l6bDW6/)
<!-- toc -->
- [Basic usage](#basic-usage)
* [Properties](#properties)
- [Properties](#properties)
- [Details](#details)
* [Supported file formats](#supported-file-formats)
* [PDF Conversion](#pdf-conversion)
@@ -39,9 +39,9 @@ Using with file url:
</adf-viewer>
```
### Properties
## Properties
| Attribute | Options | Default | Description |
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| fileNodeId | string | | Node Id of the file to load |
| urlFile | string | | If you want to load an external file that does not come from ECM you can use this Url where to load the file |
@@ -50,6 +50,13 @@ Using with file url:
| showViewer | boolean | true | Hide or show the viewer |
| showToolbar | boolean | true | Hide or show the toolbars |
| displayName | string | | You can specify the name of the file |
| allowGoBack | boolean | true | Allow `back` navigation |
| allowOpenWith | boolean | true | Toggle `Open With` options |
| allowDownload | boolean | true | Toggle download feature |
| allowPrint | boolean | true | Toggle printing feature |
| allowShare | boolean | true | Toggle sharing feature |
| allowInfoDrawer | boolean | true | Toogle info drawer feature |
| showInfoDrawer | boolean | false | Toggles info drawer visibility. Requires `allowInfoDrawer` to be set to `true`. |
## Details
@@ -63,9 +70,7 @@ Using with file url:
### PDF Conversion
![Rendition](docassets/images/renditions.png)
Note for unsupported extensions the viewer will offer the possibility to convert to PDF if that kind of extension is supported by the [content service renditions service](https://community.alfresco.com/docs/DOC-5879-rendition-service)
For unsupported extensions or mime types the viewer will try to fetch PDF rendition utilising the [renditions service api](https://community.alfresco.com/docs/DOC-5879-rendition-service)
### Configuring PDF.js library

View File

@@ -23,12 +23,13 @@ import { MaterialModule } from './src/material.module';
export { ViewerComponent } from './src/components/viewer.component';
import { ImgViewerComponent } from './src/components/imgViewer.component';
import { MediaPlayerComponent } from './src/components/mediaPlayer.component';
import { NotSupportedFormatComponent } from './src/components/notSupportedFormat.component';
import { PdfViewerComponent } from './src/components/pdfViewer.component';
import { TxtViewerComponent } from './src/components/txtViewer.component';
import { UnknownFormatComponent } from './src/components/unknown-format/unknown-format.component';
import { PdfViewComponent } from './src/components/viewer-dialog/pdf-view/pdf-view.component';
import { ViewerDialogComponent } from './src/components/viewer-dialog/viewer-dialog.component';
import { ViewerComponent } from './src/components/viewer.component';
import { ExtensionViewerDirective } from './src/directives/extension-viewer.directive';
import { RenderingQueueServices } from './src/services/rendering-queue.services';
@@ -38,33 +39,33 @@ export { ViewerDialogComponent } from './src/components/viewer-dialog/viewer-dia
export { ViewerDialogSettings } from './src/components/viewer-dialog/viewer-dialog.settings';
export { ViewerService } from './src/services/viewer.service';
export const VIEWER_DIRECTIVES: any[] = [
ViewerComponent,
ImgViewerComponent,
TxtViewerComponent,
MediaPlayerComponent,
NotSupportedFormatComponent,
PdfViewerComponent,
ExtensionViewerDirective,
ViewerDialogComponent,
PdfViewComponent
];
export function declarations() {
return [
ViewerComponent,
ImgViewerComponent,
TxtViewerComponent,
MediaPlayerComponent,
PdfViewerComponent,
ExtensionViewerDirective,
ViewerDialogComponent,
PdfViewComponent,
UnknownFormatComponent
];
}
@NgModule({
imports: [
CoreModule,
MaterialModule
],
declarations: [
...VIEWER_DIRECTIVES
],
declarations: declarations(),
providers: [
RenderingQueueServices,
ViewerService
],
exports: [
MaterialModule,
...VIEWER_DIRECTIVES
...declarations()
],
entryComponents: [
ViewerDialogComponent

View File

@@ -7,6 +7,7 @@
justify-content: center;
height: 90vh;
img {
width: 100%;
object-fit: contain;
}
}

View File

@@ -1,47 +0,0 @@
<md-card *ngIf="!isConversionFinished">
<md-card-title>
Unknown format
</md-card-title>
<md-card-content>
<h4>File '<span>{{nameFile}}</span>' is of an unsupported format</h4>
<md-progress-bar
*ngIf="isConversionStarted"
mode="indeterminate"
data-automation-id="viewer-conversion-spinner">
</md-progress-bar>
</md-card-content>
<md-card-actions>
<button
md-button
data-automation-id="viewer-download-button"
(click)="download()">
<md-icon>cloud_download</md-icon>
Download
</button>
<button
md-button
*ngIf="convertible"
[disabled]="isConversionStarted"
data-automation-id="viewer-convert-button"
(click)="convertToPdf()">
<md-icon>insert_drive_file</md-icon>
Convert to PDF
</button>
<button
md-button
*ngIf="displayable"
data-automation-id="viewer-display-button"
(click)="showPDF()">
<md-icon>insert_drive_file</md-icon>
Show PDF
</button>
</md-card-actions>
</md-card>
<adf-pdf-viewer
*ngIf="isConversionFinished"
[showToolbar]="showToolbar"
[urlFile]="renditionUrl"
[nameFile]="nameFile"
data-automation-id="pdf-rendition-viewer">
</adf-pdf-viewer>

View File

@@ -1,5 +0,0 @@
.adf-not-supported-format {
.mat-card {
max-width: 400px;
}
}

View File

@@ -1,271 +0,0 @@
/*!
* @license
* Copyright 2016 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 { DebugElement } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ContentService, CoreModule, RenditionsService } from 'ng2-alfresco-core';
import { Subject } from 'rxjs/Subject';
import { MaterialModule } from './../material.module';
import { NotSupportedFormatComponent } from './notSupportedFormat.component';
import { PdfViewerComponent } from './pdfViewer.component';
interface RenditionResponse {
entry: {
status: string
};
}
describe('NotSupportedFormatComponent', () => {
const nodeId = 'not-supported-node-id';
let component: NotSupportedFormatComponent;
let service: ContentService;
let fixture: ComponentFixture<NotSupportedFormatComponent>;
let debug: DebugElement;
let element: HTMLElement;
let renditionsService: RenditionsService;
let renditionSubject: Subject<RenditionResponse>;
let conversionSubject: Subject<any>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
MaterialModule
],
declarations: [
NotSupportedFormatComponent,
PdfViewerComponent
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NotSupportedFormatComponent);
service = fixture.debugElement.injector.get(ContentService);
debug = fixture.debugElement;
element = fixture.nativeElement;
component = fixture.componentInstance;
component.nodeId = nodeId;
renditionSubject = new Subject<RenditionResponse>();
conversionSubject = new Subject<any>();
renditionsService = TestBed.get(RenditionsService);
spyOn(renditionsService, 'getRendition').and.returnValue(renditionSubject);
spyOn(renditionsService, 'convert').and.returnValue(conversionSubject);
});
describe('View', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should display the Download button', () => {
expect(element.querySelector('[data-automation-id="viewer-download-button"]')).not.toBeNull();
});
it('should display the name of the file', () => {
component.nameFile = 'Example Content.xls';
fixture.detectChanges();
expect(element.querySelector('h4 span').innerHTML).toEqual('Example Content.xls');
});
it('should NOT show loading spinner by default', () => {
expect(element.querySelector('[data-automation-id="viewer-conversion-spinner"]')).toBeNull('Conversion spinner should NOT be shown by default');
});
});
describe('Convertibility to pdf', () => {
it('should not show the "Convert to PDF" button by default', () => {
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="viewer-convert-button"]')).toBeNull();
});
it('should be checked on ngInit', () => {
fixture.detectChanges();
expect(renditionsService.getRendition).toHaveBeenCalledWith(nodeId, 'pdf');
});
it('should NOT be checked on ngInit if nodeId is not set', () => {
component.nodeId = null;
fixture.detectChanges();
expect(renditionsService.getRendition).not.toHaveBeenCalled();
});
it('should show the "Convert to PDF" button if the node is convertible', async(() => {
fixture.detectChanges();
renditionSubject.next({ entry: { status: 'NOT_CREATED' } });
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="viewer-convert-button"]')).not.toBeNull();
});
}));
it('should NOT show the "Convert to PDF" button if the node is NOT convertible', async(() => {
component.convertible = true;
fixture.detectChanges();
renditionSubject.error(new Error('Mocked error'));
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="viewer-convert-button"]')).toBeNull();
});
}));
it('should NOT show the "Convert to PDF" button if the node is already converted', async(() => {
renditionSubject.next({ entry: { status: 'CREATED' } });
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="viewer-convert-button"]')).toBeNull();
});
}));
it('should start the conversion when clicking on the "Convert to PDF" button', () => {
component.convertible = true;
fixture.detectChanges();
const convertButton = debug.query(By.css('[data-automation-id="viewer-convert-button"]'));
convertButton.triggerEventHandler('click', null);
fixture.detectChanges();
const conversionSpinner = debug.query(By.css('[data-automation-id="viewer-conversion-spinner"]'));
expect(renditionsService.convert).toHaveBeenCalled();
expect(conversionSpinner).not.toBeNull();
});
it('should remove the spinner if an error happens during conversion', () => {
component.convertToPdf();
conversionSubject.error('whatever');
fixture.detectChanges();
const conversionSpinner = debug.query(By.css('[data-automation-id="viewer-conversion-spinner"]'));
expect(conversionSpinner).toBeNull();
});
it('should remove the spinner and show the pdf if conversion has finished', () => {
component.convertToPdf();
conversionSubject.complete();
fixture.detectChanges();
const conversionSpinner = debug.query(By.css('[data-automation-id="viewer-conversion-spinner"]'));
const pdfRenditionViewer = debug.query(By.css('[data-automation-id="pdf-rendition-viewer"]'));
expect(conversionSpinner).toBeNull();
expect(pdfRenditionViewer).not.toBeNull();
});
it('should unsubscribe from the conversion subscription on ngOnDestroy', () => {
component.convertToPdf();
component.ngOnDestroy();
conversionSubject.complete();
fixture.detectChanges();
const pdfRenditionViewer = debug.query(By.css('[data-automation-id="pdf-rendition-viewer"]'));
expect(pdfRenditionViewer).toBeNull();
});
it('should not throw error on ngOnDestroy if the conversion hasn\'t started at all' , () => {
const callNgOnDestroy = () => {
component.ngOnDestroy();
};
expect(callNgOnDestroy).not.toThrowError();
});
});
describe('User Interaction', () => {
beforeEach(() => {
fixture.detectChanges();
});
describe('Download', () => {
it('should call download method if Click on Download button', () => {
spyOn(window, 'open');
component.urlFile = 'test';
let downloadButton: any = element.querySelector('[data-automation-id="viewer-download-button"]');
downloadButton.click();
expect(window.open).toHaveBeenCalled();
});
it('should call content service download method if Click on Download button', () => {
spyOn(service, 'downloadBlob');
component.blobFile = new Blob();
let downloadButton: any = element.querySelector('[data-automation-id="viewer-download-button"]');
downloadButton.click();
expect(service.downloadBlob).toHaveBeenCalled();
});
});
describe('Conversion', () => {
function clickOnConvertButton() {
renditionSubject.next({ entry: { status: 'NOT_CREATED' } });
fixture.detectChanges();
let convertButton: any = element.querySelector('[data-automation-id="viewer-convert-button"]');
convertButton.click();
fixture.detectChanges();
}
it('should show loading spinner and disable the "Convert to PDF button" after the button was clicked', () => {
clickOnConvertButton();
let convertButton: any = element.querySelector('[data-automation-id="viewer-convert-button"]');
expect(element.querySelector('[data-automation-id="viewer-conversion-spinner"]')).not.toBeNull('Conversion spinner should be shown');
expect(convertButton.disabled).toBe(true);
});
it('should re-enable the "Convert to PDF button" and hide spinner after unsuccessful conversion and hide loading spinner', () => {
clickOnConvertButton();
conversionSubject.error(new Error());
fixture.detectChanges();
let convertButton: any = element.querySelector('[data-automation-id="viewer-convert-button"]');
expect(element.querySelector('[data-automation-id="viewer-conversion-spinner"]')).toBeNull('Conversion spinner should be shown');
expect(convertButton.disabled).toBe(false);
});
it('should show the pdf rendition after successful conversion', () => {
clickOnConvertButton();
conversionSubject.next();
conversionSubject.complete();
fixture.detectChanges();
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="pdf-rendition-viewer"]')).not.toBeNull('Pdf rendition should be shown.');
});
});
});
});

View File

@@ -1,133 +0,0 @@
/*!
* @license
* Copyright 2016 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, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ContentService, RenditionsService } from 'ng2-alfresco-core';
const DEFAULT_CONVERSION_ENCODING = 'pdf';
@Component({
selector: 'adf-not-supported-format',
templateUrl: './notSupportedFormat.component.html',
styleUrls: ['./notSupportedFormat.component.scss'],
host: { 'class': 'adf-not-supported-format' },
encapsulation: ViewEncapsulation.None
})
export class NotSupportedFormatComponent implements OnInit, OnDestroy {
@Input()
nameFile: string;
@Input()
urlFile: string;
@Input()
blobFile: Blob;
@Input()
nodeId: string|null = null;
@Input()
showToolbar: boolean = true;
convertible: boolean = false;
displayable: boolean = false;
isConversionStarted: boolean = false;
isConversionFinished: boolean = false;
renditionUrl: string|null = null;
conversionsubscription: any = null;
constructor(
private contentService: ContentService,
private renditionsService: RenditionsService) {}
/**
* Checks for available renditions if the nodeId is present
*/
ngOnInit() {
if (this.nodeId) {
this.checkRendition();
}
}
/**
* Download file opening it in a new window
*/
download() {
if (this.urlFile) {
window.open(this.urlFile);
} else {
this.contentService.downloadBlob(this.blobFile, this.nameFile);
}
}
/**
* Update component's button according to the given rendition's availability
*
* @param {string} encoding - the rendition id
*/
checkRendition(encoding: string = DEFAULT_CONVERSION_ENCODING): void {
this.renditionsService.getRendition(this.nodeId, encoding)
.subscribe(
(response) => {
const status = response.entry.status.toString();
if (status === 'NOT_CREATED') {
this.convertible = true;
this.displayable = false;
} else if (status === 'CREATED') {
this.convertible = false;
this.displayable = true;
}
},
() => {
this.convertible = false;
this.displayable = false;
}
);
}
/**
* Set the component to loading state and send the conversion starting signal to parent component
*/
convertToPdf(): void {
this.isConversionStarted = true;
this.conversionsubscription = this.renditionsService.convert(this.nodeId, DEFAULT_CONVERSION_ENCODING)
.subscribe({
error: (error) => { this.isConversionStarted = false; },
complete: () => { this.showPDF(); }
});
}
/**
* Show the PDF rendition of the node
*/
showPDF(): void {
this.renditionUrl = this.renditionsService.getRenditionUrl(this.nodeId, DEFAULT_CONVERSION_ENCODING);
this.isConversionStarted = false;
this.isConversionFinished = true;
}
/**
* Kills the subscription polling if it has been started
*/
ngOnDestroy(): void {
if (this.isConversionStarted) {
this.conversionsubscription.unsubscribe();
}
}
}

View File

@@ -8,6 +8,7 @@
overflow: hidden;
opacity: 0.2;
line-height: 1.0;
border: 1px solid gray;
& > div {
color: transparent;

View File

@@ -0,0 +1,6 @@
<div class="adf-viewer__unknown-format-view">
<div>
<md-icon class="icon">wifi_tethering</md-icon>
<div class="label">Document preview could not be loaded.</div>
</div>
</div>

View File

@@ -0,0 +1,8 @@
.adf-viewer__unknown-format-view {
height: 90vh;
text-align: center;
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
}

View File

@@ -0,0 +1,26 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component } from '@angular/core';
@Component({
selector: 'adf-viewer-unknown-format',
templateUrl: 'unknown-format.component.html',
styleUrls: ['unknown-format.component.scss']
})
export class UnknownFormatComponent {
}

View File

@@ -4,80 +4,151 @@
[class.adf-viewer-inline-container]="!overlayMode">
<div class="adf-viewer-content">
<ng-container *ngIf="overlayMode">
<ng-container *ngIf="showToolbar">
<adf-toolbar color="default" class="adf-viewer-toolbar">
<adf-toolbar-title>
<span class="adf-viewer-filename">{{ displayName }}</span>
<button *ngIf="allowGoBack"
class="adf-viewer-close-button"
md-icon-button
mdTooltip="Back"
(click)="onBackButtonClick()">
<md-icon>arrow_back</md-icon>
</button>
<img [src]="mimeType | adfMimeTypeIcon">
<span>{{ displayName }}</span>
</adf-toolbar-title>
<button
md-icon-button
class="adf-viewer-close-button"
mdTooltip="Close and go back"
mdTooltipPosition="before"
(click)="close()"
aria-label="Close">
<md-icon>close</md-icon>
<ng-container *ngIf="allowOpenWith">
<button md-button [mdMenuTriggerFor]="mnuOpenWith">
<span>Open with</span>
<md-icon>arrow_drop_down</md-icon>
</button>
<md-menu #mnuOpenWith="mdMenu" [overlapTrigger]="false">
<button md-menu-item>
<md-icon>dialpad</md-icon>
<span>Option 1</span>
</button>
<button md-menu-item disabled>
<md-icon>voicemail</md-icon>
<span>Option 2</span>
</button>
<button md-menu-item>
<md-icon>notifications_off</md-icon>
<span>Option 3</span>
</button>
</md-menu>
</ng-container>
<adf-toolbar-divider></adf-toolbar-divider>
<button *ngIf="allowDownload" md-icon-button mdTooltip="Download" (click)="download()">
<md-icon>file_download</md-icon>
</button>
<button *ngIf="allowPrint" md-icon-button mdTooltip="Print">
<md-icon>print</md-icon>
</button>
<button *ngIf="allowShare" md-icon-button mdTooltip="Share">
<md-icon>share</md-icon>
</button>
<button md-icon-button [mdMenuTriggerFor]="mnuMoreActions" mdTooltip="More actions">
<md-icon>more_vert</md-icon>
</button>
<md-menu #mnuMoreActions="mdMenu">
<button md-menu-item>
<md-icon>dialpad</md-icon>
<span>Action One</span>
</button>
<button md-menu-item disabled>
<md-icon>voicemail</md-icon>
<span>Action Two</span>
</button>
<button md-menu-item>
<md-icon>notifications_off</md-icon>
<span>Action Three</span>
</button>
</md-menu>
<ng-container *ngIf="allowInfoDrawer">
<adf-toolbar-divider></adf-toolbar-divider>
<button md-icon-button mdTooltip="Info"
[color]="showInfoDrawer ? 'accent' : 'default'"
(click)="showInfoDrawer = !showInfoDrawer">
<md-icon>info_outline</md-icon>
</button>
</ng-container>
</adf-toolbar>
</ng-container>
<div class="adf-viewer-layout">
<ng-container *ngIf="isLoading">
<div class="adf-viewer__loading-screen">
<h2>Loading</h2>
<div>
<md-spinner></md-spinner>
</div>
</div>
</ng-container>
<div *ngIf="!isLoading" class="adf-viewer-layout">
<div class="adf-viewer-layout-content">
<div *ngIf="isLoaded()">
<div class="adf-viewer-content-container">
<div>
<div class="adf-viewer-content-container" [ngSwitch]="viewerType">
<ng-container *ngIf="isPdf()">
<adf-pdf-viewer
[showToolbar]="showToolbar"
[blobFile]="blobFile"
[urlFile]="urlFileContent"
[nameFile]="displayName">
</adf-pdf-viewer>
<ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer [blobFile]="blobFile" [urlFile]="urlFileContent" [nameFile]="displayName"></adf-pdf-viewer>
</ng-container>
<ng-container *ngIf="isImage()">
<adf-img-viewer
[urlFile]="urlFileContent"
[nameFile]="displayName"
[blobFile]="blobFile">
</adf-img-viewer>
<ng-container *ngSwitchCase="'image'">
<adf-img-viewer [urlFile]="urlFileContent" [nameFile]="displayName" [blobFile]="blobFile"></adf-img-viewer>
</ng-container>
<ng-container *ngIf="isMedia()">
<adf-media-player
[urlFile]="urlFileContent"
[mimeType]="mimeType"
[blobFile]="blobFile"
[nameFile]="displayName">
</adf-media-player>
<ng-container *ngSwitchCase="'media'">
<adf-media-player [urlFile]="urlFileContent" [mimeType]="mimeType" [blobFile]="blobFile" [nameFile]="displayName"></adf-media-player>
</ng-container>
<ng-container *ngIf="isText() && !isExternalSupportedExtension()">
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFileContent" [blobFile]="blobFile"></adf-txt-viewer>
</ng-container>
<span *ngFor="let extensionTemplate of extensionTemplates">
<ng-template
*ngIf="extensionTemplate.isVisible"
[ngTemplateOutlet]="extensionTemplate.template"
[ngOutletContext]="{ urlFileContent: urlFileContent, extension:extension }">
</ng-template>
</span>
<ng-container *ngSwitchCase="'custom'">
<span *ngFor="let extensionTemplate of extensionTemplates">
<ng-template
*ngIf="extensionTemplate.isVisible"
[ngTemplateOutlet]="extensionTemplate.template"
[ngOutletContext]="{ urlFileContent: urlFileContent, extension:extension }">
</ng-template>
</span>
</ng-container>
<div *ngIf="!supportedExtension()" class="adf-viewer-unknown-content">
<adf-not-supported-format
*ngIf="!extensionTemplate"
[urlFile]="urlFileContent"
[blobFile]="blobFile"
[nameFile]="displayName"
[showToolbar]="showToolbar"
[nodeId]="fileNodeId">
</adf-not-supported-format>
</div>
<ng-container *ngSwitchDefault>
<adf-viewer-unknown-format></adf-viewer-unknown-format>
</ng-container>
</div>
</div>
</div>
<ng-container *ngIf="showInfoDrawer">
<div class="adf-viewer__info-drawer">
<md-tab-group md-stretch-tabs>
<md-tab label="Details">
<md-card>
DETAILS
</md-card>
</md-tab>
<md-tab label="Activity">
<md-card>
Activity
</md-card>
</md-tab>
</md-tab-group>
</div>
</ng-container>
</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
$adf-viewer-background-color: #515151;
$adf-viewer-background-color: #f5f5f5;
@mixin full-screen() {
width: 100%;
@@ -33,7 +33,7 @@ $adf-viewer-background-color: #515151;
@include full-screen();
display: flex;
flex-direction: column;
flex-direction: row;
overflow-y: auto;
overflow-x: hidden;
position: relative;
@@ -41,6 +41,7 @@ $adf-viewer-background-color: #515151;
.adf-viewer-content {
@include full-screen();
flex: 1;
}
}
@@ -66,4 +67,33 @@ $adf-viewer-background-color: #515151;
align-items: center;
display: flex;
}
&__loading-screen {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 85vh;
.md-spinner {
margin: 0 auto;
}
}
&__info-drawer {
width: 350px;
display: block;
padding: 8px 0;
background-color: #fafafa;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.27);
border-left: 1px solid rgba(0, 0, 0, 0.07);
.mat-tab-label {
text-transform: uppercase;
}
.mat-card {
margin: 6px;
}
}
}

View File

@@ -15,6 +15,8 @@
* limitations under the License.
*/
import { Location } from '@angular/common';
import { SpyLocation } from '@angular/common/testing';
import { DebugElement } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
@@ -25,9 +27,9 @@ import { EventMock } from '../assets/event.mock';
import { RenderingQueueServices } from '../services/rendering-queue.services';
import { ImgViewerComponent } from './imgViewer.component';
import { MediaPlayerComponent } from './mediaPlayer.component';
import { NotSupportedFormatComponent } from './notSupportedFormat.component';
import { PdfViewerComponent } from './pdfViewer.component';
import { TxtViewerComponent } from './txtViewer.component';
import { UnknownFormatComponent } from './unknown-format/unknown-format.component';
import { ViewerComponent } from './viewer.component';
declare let jasmine: any;
@@ -49,12 +51,13 @@ describe('ViewerComponent', () => {
ViewerComponent,
PdfViewerComponent,
TxtViewerComponent,
NotSupportedFormatComponent,
MediaPlayerComponent,
ImgViewerComponent
ImgViewerComponent,
UnknownFormatComponent
],
providers: [
RenderingQueueServices
RenderingQueueServices,
{ provide: Location, useClass: SpyLocation }
]
}).compileComponents();
}));
@@ -128,10 +131,6 @@ describe('ViewerComponent', () => {
expect(element.querySelector('header')).toBeNull();
});
it('should Close button be not present if is not overlay mode', () => {
expect(element.querySelector('.adf-viewer-close-button')).toBeNull();
});
it('should Esc button not hide the viewer if is not overlay mode', () => {
EventMock.keyDown(27);
fixture.detectChanges();
@@ -182,7 +181,7 @@ describe('ViewerComponent', () => {
});
});
describe('Exteznsion Type Test', () => {
describe('Extension Type Test', () => {
it('should extension file pdf be loaded', (done) => {
component.urlFile = 'base/src/assets/fake-test-file.pdf';
@@ -243,13 +242,13 @@ describe('ViewerComponent', () => {
});
});
it('should the not supported div be loaded if the file is a not supported extension', (done) => {
it('should display [unknown format] for unsupported extensions', (done) => {
component.urlFile = 'fake-url-file.unsupported';
component.mimeType = '';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-not-supported-format')).not.toBeNull();
expect(element.querySelector('adf-viewer-unknown-format')).toBeDefined();
done();
});
});
@@ -333,17 +332,6 @@ describe('ViewerComponent', () => {
done();
});
});
it('should not display the media player if the file identified by mimetype is a media but with not supported extension', (done) => {
component.urlFile = 'content';
component.mimeType = 'video/avi';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-media-player')).toBeNull();
done();
});
});
});
describe('Events', () => {

View File

@@ -15,9 +15,10 @@
* limitations under the License.
*/
import { Location } from '@angular/common';
import { Component, EventEmitter, HostListener, Input, OnChanges, OnDestroy, Output, TemplateRef, ViewEncapsulation } from '@angular/core';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { AlfrescoApiService, LogService } from 'ng2-alfresco-core';
import { AlfrescoApiService, BaseEvent, LogService, RenditionsService } from 'ng2-alfresco-core';
@Component({
selector: 'adf-viewer, alfresco-viewer',
@@ -44,30 +45,70 @@ export class ViewerComponent implements OnDestroy, OnChanges {
showViewer: boolean = true;
@Input()
showToolbar: boolean = true;
showToolbar = true;
@Input()
displayName: string;
displayName: string = 'Unknown';
@Input()
allowGoBack = true;
@Input()
allowOpenWith = true;
@Input()
allowDownload = true;
@Input()
allowPrint = true;
@Input()
allowShare = true;
@Input()
allowInfoDrawer = true;
@Input()
showInfoDrawer = false;
@Output()
showViewerChange: EventEmitter<boolean> = new EventEmitter<boolean>();
goBack = new EventEmitter<BaseEvent<any>>();
@Output()
extensionChange: EventEmitter<String> = new EventEmitter<String>();
showViewerChange = new EventEmitter<boolean>();
@Output()
extensionChange = new EventEmitter<string>();
viewerType: string = 'unknown';
downloadUrl: string = null;
fileName: string = 'document';
isLoading: boolean = false;
extensionTemplates: { template: TemplateRef<any>, isVisible: boolean }[] = [];
externalExtensions: string[] = [];
urlFileContent: string;
otherMenu: any;
extension: string;
mimeType: string;
loaded: boolean = false;
constructor(private apiService: AlfrescoApiService,
private logService: LogService) {
}
private extensions = {
image: ['png', 'jpg', 'jpeg', 'gif', 'bpm'],
media: ['wav', 'mp4', 'mp3', 'webm', 'ogg'],
text: ['txt', 'xml', 'js', 'html'],
pdf: ['pdf']
};
private mimeTypes = [
{ mimeType: 'application/x-javascript', type: 'text' },
{ mimeType: 'application/pdf', type: 'pdf' }
];
constructor(
private apiService: AlfrescoApiService,
private logService: LogService,
private location: Location,
private renditionService: RenditionsService) {}
ngOnChanges(changes) {
if (this.showViewer) {
@@ -77,28 +118,66 @@ export class ViewerComponent implements OnDestroy, OnChanges {
return new Promise((resolve, reject) => {
if (this.blobFile) {
this.isLoading = true;
this.mimeType = this.blobFile.type;
this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
this.allowDownload = false;
// TODO: wrap blob into the data url and allow downloading
this.extensionChange.emit(this.mimeType);
this.isLoading = false;
this.scrollTop();
resolve();
} else if (this.urlFile) {
this.isLoading = true;
let filenameFromUrl = this.getFilenameFromUrl(this.urlFile);
this.displayName = filenameFromUrl ? filenameFromUrl : '';
this.displayName = filenameFromUrl || 'Unknown';
this.extension = this.getFileExtension(filenameFromUrl);
this.extensionChange.emit(this.extension);
this.urlFileContent = this.urlFile;
this.downloadUrl = this.urlFile;
this.fileName = this.displayName;
this.viewerType = this.getViewerTypeByExtension(this.extension);
if (this.viewerType === 'unknown') {
this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
}
this.extensionChange.emit(this.extension);
this.isLoading = false;
this.scrollTop();
resolve();
} else if (this.fileNodeId) {
this.isLoading = true;
this.apiService.getInstance().nodes.getNodeInfo(this.fileNodeId).then(
(data: MinimalNodeEntryEntity) => {
this.mimeType = data.content.mimeType;
this.displayName = data.name;
this.urlFileContent = this.apiService.getInstance().content.getContentUrl(data.id);
this.extension = this.getFileExtension(data.name);
this.fileName = data.name;
this.downloadUrl = this.apiService.getInstance().content.getContentUrl(data.id, true);
this.viewerType = this.getViewerTypeByExtension(this.extension);
if (this.viewerType === 'unknown') {
this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
}
if (this.viewerType === 'unknown') {
this.displayAsPdf(data.id);
} else {
this.isLoading = false;
}
this.extensionChange.emit(this.extension);
this.loaded = true;
this.scrollTop();
resolve();
},
(error) => {
this.isLoading = false;
reject(error);
this.logService.error('This node does not exist');
}
@@ -108,6 +187,79 @@ export class ViewerComponent implements OnDestroy, OnChanges {
}
}
scrollTop() {
window.scrollTo(0, 1);
}
getViewerTypeByMimeType(mimeType: string) {
if (mimeType) {
mimeType = mimeType.toLowerCase();
if (mimeType.startsWith('image/')) {
return 'image';
}
if (mimeType.startsWith('text/')) {
return 'text';
}
if (mimeType.startsWith('video/')) {
return 'media';
}
if (mimeType.startsWith('audio/')) {
return 'media';
}
const registered = this.mimeTypes.find(t => t.mimeType === mimeType);
if (registered) {
return registered.type;
}
}
return 'unknown';
}
getViewerTypeByExtension(extension: string) {
if (extension) {
extension = extension.toLowerCase();
}
if (this.isCustomViewerExtension(extension)) {
return 'custom';
}
if (this.extensions.image.indexOf(extension) >= 0) {
return 'image';
}
if (this.extensions.media.indexOf(extension) >= 0) {
return 'media';
}
if (this.extensions.text.indexOf(extension) >= 0) {
return 'text';
}
if (this.extensions.pdf.indexOf(extension) >= 0) {
return 'pdf';
}
return 'unknown';
}
onBackButtonClick() {
if (this.overlayMode) {
this.close();
} else {
const event = new BaseEvent<any>();
this.goBack.next(event);
if (!event.defaultPrevented) {
this.location.back();
}
}
}
/**
* close the viewer
*/
@@ -127,7 +279,6 @@ export class ViewerComponent implements OnDestroy, OnChanges {
this.urlFileContent = '';
this.displayName = '';
this.fileNodeId = null;
this.loaded = false;
this.extension = null;
this.mimeType = null;
}
@@ -157,113 +308,19 @@ export class ViewerComponent implements OnDestroy, OnChanges {
* @param {string} fileName - file name
* @returns {string} file name extension
*/
private getFileExtension(fileName: string): string {
getFileExtension(fileName: string): string {
return fileName.split('.').pop().toLowerCase();
}
/**
* Check if the content is an image through the extension or mime type
*
* @returns {boolean}
*/
public isImage(): boolean {
return this.isImageExtension() || this.isImageMimeType();
}
isCustomViewerExtension(extension: string): boolean {
const extensions = this.externalExtensions || [];
/**
* Check if the content is a media through the extension or mime type
*
* @returns {boolean}
*/
public isMedia(): boolean {
return this.isMediaExtension(this.extension) || this.isMediaMimeType();
}
/**
* check if the current file is a supported image extension
*
* @returns {boolean}
*/
private isImageExtension(): boolean {
return this.extension === 'png' || this.extension === 'jpg' ||
this.extension === 'jpeg' || this.extension === 'gif' || this.extension === 'bmp';
}
/**
* check if the current file has an image-based mimetype
*
* @returns {boolean}
*/
private isMediaMimeType(): boolean {
let mimeExtension;
if (this.mimeType && this.mimeType.indexOf('/')) {
mimeExtension = this.mimeType.substr(this.mimeType.indexOf('/') + 1, this.mimeType.length);
}
return (this.mimeType && (this.mimeType.indexOf('video/') === 0 || this.mimeType.indexOf('audio/') === 0)) && this.isMediaExtension(mimeExtension);
}
/**
* check if the current file is a supported media extension
* @param {string} extension
*
* @returns {boolean}
*/
private isMediaExtension(extension: string): boolean {
return extension === 'wav' || extension === 'mp4' || extension === 'mp3' || extension === 'WebM' || extension === 'Ogg';
}
/**
* check if the current file has an image-based mimetype
*
* @returns {boolean}
*/
private isImageMimeType(): boolean {
return this.mimeType && this.mimeType.indexOf('image/') === 0;
}
/**
* check if the current file is a supported pdf extension
*
* @returns {boolean}
*/
public isPdf(): boolean {
return this.extension === 'pdf' || this.mimeType === 'application/pdf';
}
/**
* check if the current file is a supported txt extension
*
* @returns {boolean}
*/
public isText(): boolean {
return this.extension === 'txt' || this.mimeType === 'text/txt' || this.mimeType === 'text/plain';
}
/**
* check if the current file is a supported extension
*
* @returns {boolean}
*/
supportedExtension(): boolean {
return this.isImage() || this.isPdf() || this.isMedia() || this.isText() || this.isExternalSupportedExtension();
}
/**
* Check if the file is compatible with one of the extension
*
* @returns {boolean}
*/
isExternalSupportedExtension(): boolean {
let externalType: string;
if (this.externalExtensions && (this.externalExtensions instanceof Array)) {
externalType = this.externalExtensions.find((externalExtension) => {
return externalExtension.toLowerCase() === this.extension;
});
if (extension && extensions.length > 0) {
extension = extension.toLowerCase();
return extensions.indexOf(extension) >= 0;
}
return !!externalType;
return false;
}
/**
@@ -278,12 +335,56 @@ export class ViewerComponent implements OnDestroy, OnChanges {
}
}
/**
* return true if the data about the node in the ecm are loaded
*
* @returns {boolean}
*/
isLoaded(): boolean {
return this.fileNodeId ? this.loaded : true;
download() {
if (this.allowDownload && this.downloadUrl && this.fileName) {
const link = document.createElement('a');
link.style.display = 'none';
link.download = this.fileName;
link.href = this.downloadUrl;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
private displayAsPdf(nodeId: string) {
this.isLoading = true;
this.renditionService.getRendition(nodeId, 'pdf').subscribe(
(response) => {
const status = response.entry.status.toString();
if (status === 'CREATED') {
this.isLoading = false;
this.showPdfRendition(nodeId);
} else if (status === 'NOT_CREATED') {
this.renditionService.convert(nodeId, 'pdf').subscribe({
complete: () => {
this.isLoading = false;
this.showPdfRendition(nodeId);
},
error: (error) => {
this.isLoading = false;
console.log(error);
}
});
} else {
this.isLoading = false;
}
},
(err) => {
this.isLoading = false;
console.log(err);
}
);
}
private showPdfRendition(nodeId: string) {
if (nodeId) {
this.viewerType = 'pdf';
this.urlFileContent = this.renditionService.getRenditionUrl(nodeId, 'pdf');
}
}
}

View File

@@ -15,6 +15,8 @@
* limitations under the License.
*/
import { Location } from '@angular/common';
import { SpyLocation } from '@angular/common/testing';
import { ElementRef } from '@angular/core';
import { Injector } from '@angular/core';
import { async, getTestBed, TestBed } from '@angular/core/testing';
@@ -28,7 +30,7 @@ export class MockElementRef extends ElementRef {
}
}
describe('ExtensionViewerComponent', () => {
describe('ExtensionViewerDirective', () => {
let injector: Injector;
let extensionViewerDirective: ExtensionViewerDirective;
let viewerComponent: ViewerComponent;
@@ -37,6 +39,7 @@ describe('ExtensionViewerComponent', () => {
TestBed.configureTestingModule({
imports: [CoreModule],
providers: [
{ provide: Location, useClass: SpyLocation },
ExtensionViewerDirective,
{provide: ElementRef, useClass: MockElementRef},
ViewerComponent