mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-31 17:38:48 +00:00
Form: support for embedded document view (#1748)
* Form: support for embedded document view * unit tests and improvements - new ContentService to deal with trusted URL and downloads - unit tests and improvements for ActivitiContent component
This commit is contained in:
committed by
Mario Romano
parent
a06138136b
commit
4f154f8bca
@@ -3,6 +3,10 @@
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upload-widget__content {
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.upload-widget__icon {
|
.upload-widget__icon {
|
||||||
float: left;
|
float: left;
|
||||||
color: rgba(0, 0, 0, .26);
|
color: rgba(0, 0, 0, .26);
|
||||||
|
@@ -1,22 +1,23 @@
|
|||||||
<div class="upload-widget" *ngIf="content">
|
<div class="upload-widget" *ngIf="content">
|
||||||
<div class="mdl-card mdl-shadow--2dp">
|
<div class="mdl-card mdl-shadow--2dp upload-widget__content">
|
||||||
<div class="mdl-card__title mdl-card--expand">
|
<div *ngIf="showDocumentContent" class="mdl-card__title mdl-card--expand upload-widget__content-thumbnail">
|
||||||
<div *ngIf="content.isThumbnailSupported()">
|
<div *ngIf="content.isThumbnailSupported()">
|
||||||
<img class="img-upload-widget" [src]="sanitizeUrl(content.thumbnailUrl)">
|
<img class="img-upload-widget" [src]="content.thumbnailUrl">
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!content.isThumbnailSupported()">
|
<div *ngIf="!content.isThumbnailSupported()">
|
||||||
<i class="material-icons">image</i>
|
<i class="material-icons">image</i>
|
||||||
<div class="previewTxt">{{ 'FORM.PREVIEW.IMAGE_NOT_AVAILABLE' | translate }}</div>
|
<div class="previewTxt">{{ 'FORM.PREVIEW.IMAGE_NOT_AVAILABLE' | translate }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mdl-card__supporting-text">{{content.name}}</div>
|
<div class="mdl-card__supporting-text upload-widget__content-text">{{content.name}}</div>
|
||||||
<div class="mdl-card__actions mdl-card--border">
|
|
||||||
|
<div class="mdl-card__actions mdl-card--border upload-widget__content-actions">
|
||||||
<button (click)="openViewer(content)" class="mdl-button mdl-js-button mdl-button--icon">
|
<button (click)="openViewer(content)" class="mdl-button mdl-js-button mdl-button--icon">
|
||||||
<i class="material-icons">zoom_in</i>
|
<i class="material-icons">zoom_in</i>
|
||||||
</button>
|
</button>
|
||||||
<div (click)="download(content)" class="mdl-button mdl-js-button mdl-button--icon">
|
<button (click)="download(content)" class="mdl-button mdl-js-button mdl-button--icon">
|
||||||
<i class="material-icons">file_download</i>
|
<i class="material-icons">file_download</i>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,71 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { CoreModule } from 'ng2-alfresco-core';
|
||||||
|
|
||||||
|
import { ActivitiContent } from './activiti-content.component';
|
||||||
|
import { FormService } from './../services/form.service';
|
||||||
|
import { EcmModelService } from './../services/ecm-model.service';
|
||||||
|
import { ContentLinkModel } from './widgets/index';
|
||||||
|
|
||||||
|
describe('ActivitiContent', () => {
|
||||||
|
|
||||||
|
let fixture: ComponentFixture<ActivitiContent>;
|
||||||
|
let component: ActivitiContent;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CoreModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ActivitiContent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
FormService,
|
||||||
|
EcmModelService
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ActivitiContent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display content thumbnail', () => {
|
||||||
|
component.showDocumentContent = true;
|
||||||
|
component.content = new ContentLinkModel();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let content = fixture.debugElement.query(By.css('div.upload-widget__content-thumbnail'));
|
||||||
|
expect(content).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display content thumbnail', () => {
|
||||||
|
component.showDocumentContent = false;
|
||||||
|
component.content = new ContentLinkModel();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let content = fixture.debugElement.query(By.css('div.upload-widget__content-thumbnail'));
|
||||||
|
expect(content).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -15,18 +15,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { Component, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core';
|
||||||
Component,
|
import { AlfrescoTranslationService, LogService, ContentService } from 'ng2-alfresco-core';
|
||||||
OnChanges,
|
|
||||||
SimpleChanges,
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
EventEmitter
|
|
||||||
} from '@angular/core';
|
|
||||||
import { AlfrescoTranslationService, LogService } from 'ng2-alfresco-core';
|
|
||||||
import { FormService } from './../services/form.service';
|
import { FormService } from './../services/form.service';
|
||||||
import { ContentLinkModel } from './widgets/core/content-link.model';
|
import { ContentLinkModel } from './widgets/core/content-link.model';
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
moduleId: module.id,
|
moduleId: module.id,
|
||||||
@@ -39,6 +32,9 @@ export class ActivitiContent implements OnChanges {
|
|||||||
@Input()
|
@Input()
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
showDocumentContent: boolean = false;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
contentClick = new EventEmitter();
|
contentClick = new EventEmitter();
|
||||||
|
|
||||||
@@ -47,19 +43,16 @@ export class ActivitiContent implements OnChanges {
|
|||||||
constructor(private translate: AlfrescoTranslationService,
|
constructor(private translate: AlfrescoTranslationService,
|
||||||
protected formService: FormService,
|
protected formService: FormService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private sanitizer: DomSanitizer ) {
|
private contentService: ContentService) {
|
||||||
|
|
||||||
if (this.translate) {
|
if (this.translate) {
|
||||||
this.translate.addTranslationFolder('ng2-activiti-form', 'node_modules/ng2-activiti-form/src');
|
this.translate.addTranslationFolder('ng2-activiti-form', 'node_modules/ng2-activiti-form/src');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
let contentId = changes['id'];
|
const contentId = changes['id'];
|
||||||
if (contentId && contentId.currentValue) {
|
if (contentId && contentId.currentValue) {
|
||||||
this.loadContent(contentId.currentValue);
|
this.loadContent(contentId.currentValue);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,19 +72,18 @@ export class ActivitiContent implements OnChanges {
|
|||||||
|
|
||||||
loadThumbnailUrl(content: ContentLinkModel) {
|
loadThumbnailUrl(content: ContentLinkModel) {
|
||||||
if (this.content.isThumbnailSupported()) {
|
if (this.content.isThumbnailSupported()) {
|
||||||
|
let observable: Observable<any>;
|
||||||
|
|
||||||
if (this.content.isTypeImage()) {
|
if (this.content.isTypeImage()) {
|
||||||
this.formService.getFileRawContent(content.id).subscribe(
|
observable = this.formService.getFileRawContent(content.id);
|
||||||
(response: Blob) => {
|
|
||||||
this.content.thumbnailUrl = this.createUrlPreview(response);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
this.logService.error(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.formService.getContentThumbnailUrl(content.id).subscribe(
|
observable = this.formService.getContentThumbnailUrl(content.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (observable) {
|
||||||
|
observable.subscribe(
|
||||||
(response: Blob) => {
|
(response: Blob) => {
|
||||||
this.content.thumbnailUrl = this.createUrlPreview(response);
|
this.content.thumbnailUrl = this.contentService.createTrustedUrl(response);
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
this.logService.error(error);
|
this.logService.error(error);
|
||||||
@@ -101,44 +93,18 @@ export class ActivitiContent implements OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openViewer(content: ContentLinkModel) {
|
openViewer(content: ContentLinkModel): void {
|
||||||
this.contentClick.emit(content);
|
this.contentClick.emit(content);
|
||||||
this.logService.info('Content clicked' + content.id);
|
this.logService.info('Content clicked' + content.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download file opening it in a new window
|
* Invoke content download.
|
||||||
*/
|
*/
|
||||||
download(content) {
|
download(content: ContentLinkModel): void {
|
||||||
this.formService.getFileRawContent(content.id).subscribe(
|
this.formService.getFileRawContent(content.id).subscribe(
|
||||||
(response: Blob) => {
|
(blob: Blob) => this.contentService.downloadBlob(blob, content.name),
|
||||||
let thumbnailUrl = this.createUrlPreview(response);
|
error => this.logService.error(error)
|
||||||
this.createDownloadElement(thumbnailUrl, content.name);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
this.logService.error(error);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createDownloadElement(url: string, name: string) {
|
|
||||||
let downloadElement = window.document.createElement('a');
|
|
||||||
downloadElement.setAttribute('id', 'export-download');
|
|
||||||
downloadElement.setAttribute('href', url);
|
|
||||||
downloadElement.setAttribute('download', name);
|
|
||||||
downloadElement.setAttribute('target', '_blank');
|
|
||||||
window.document.body.appendChild(downloadElement);
|
|
||||||
downloadElement.click();
|
|
||||||
window.document.body.removeChild(downloadElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sanitizeUrl(url: string) {
|
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private createUrlPreview(blob: Blob) {
|
|
||||||
let imageUrl = window.URL.createObjectURL(blob);
|
|
||||||
let sanitize: any = this.sanitizeUrl(imageUrl);
|
|
||||||
return sanitize.changingThisBreaksApplicationSecurity;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -70,7 +70,7 @@
|
|||||||
<div *ngSwitchCase="'upload'">
|
<div *ngSwitchCase="'upload'">
|
||||||
<div *ngIf="hasFile" class="mdl-grid">
|
<div *ngIf="hasFile" class="mdl-grid">
|
||||||
<div *ngFor="let file of field.value" class="mdl-cell mdl-cell--6-col">
|
<div *ngFor="let file of field.value" class="mdl-cell mdl-cell--6-col">
|
||||||
<activiti-content [id]="file.id"></activiti-content>
|
<activiti-content [id]="file.id" [showDocumentContent]="showDocumentContent"></activiti-content>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -48,6 +48,7 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
|||||||
|
|
||||||
// upload/attach
|
// upload/attach
|
||||||
hasFile: boolean = false;
|
hasFile: boolean = false;
|
||||||
|
showDocumentContent: boolean = false;
|
||||||
|
|
||||||
constructor(private formService: FormService,
|
constructor(private formService: FormService,
|
||||||
private visibilityService: WidgetVisibilityService,
|
private visibilityService: WidgetVisibilityService,
|
||||||
@@ -60,6 +61,7 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
|||||||
this.value = this.field.value;
|
this.value = this.field.value;
|
||||||
this.visibilityService.refreshEntityVisibility(this.field);
|
this.visibilityService.refreshEntityVisibility(this.field);
|
||||||
if (this.field.params) {
|
if (this.field.params) {
|
||||||
|
this.showDocumentContent = !!this.field.params['showDocumentContent'];
|
||||||
let originalField = this.field.params['field'];
|
let originalField = this.field.params['field'];
|
||||||
if (originalField && originalField.type) {
|
if (originalField && originalField.type) {
|
||||||
this.fieldType = originalField.type;
|
this.fieldType = originalField.type;
|
||||||
|
@@ -36,7 +36,8 @@ import {
|
|||||||
AuthGuardBpm,
|
AuthGuardBpm,
|
||||||
LogService,
|
LogService,
|
||||||
LogServiceMock,
|
LogServiceMock,
|
||||||
NotificationService
|
NotificationService,
|
||||||
|
ContentService
|
||||||
} from './src/services/index';
|
} from './src/services/index';
|
||||||
|
|
||||||
import { UploadDirective } from './src/directives/upload.directive';
|
import { UploadDirective } from './src/directives/upload.directive';
|
||||||
@@ -66,6 +67,7 @@ export const ALFRESCO_CORE_PROVIDERS: any[] = [
|
|||||||
AlfrescoTranslateLoader,
|
AlfrescoTranslateLoader,
|
||||||
AlfrescoTranslationService,
|
AlfrescoTranslationService,
|
||||||
RenditionsService,
|
RenditionsService,
|
||||||
|
ContentService,
|
||||||
AuthGuard,
|
AuthGuard,
|
||||||
AuthGuardEcm,
|
AuthGuardEcm,
|
||||||
AuthGuardBpm,
|
AuthGuardBpm,
|
||||||
|
109
ng2-components/ng2-alfresco-core/src/services/content.service.ts
Normal file
109
ng2-components/ng2-alfresco-core/src/services/content.service.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { Injectable } from '@angular/core';
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ContentService {
|
||||||
|
|
||||||
|
private saveData: Function;
|
||||||
|
|
||||||
|
constructor(private sanitizer: DomSanitizer ) {
|
||||||
|
this.saveData = (function () {
|
||||||
|
let a = document.createElement('a');
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
return function (data, format, fileName) {
|
||||||
|
let blob = null;
|
||||||
|
|
||||||
|
if (format === 'blob') {
|
||||||
|
blob = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === 'data') {
|
||||||
|
blob = new Blob([data], {type: 'octet/stream'});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === 'object' || format === 'json') {
|
||||||
|
let json = JSON.stringify(data);
|
||||||
|
blob = new Blob([json], {type: 'octet/stream'});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blob) {
|
||||||
|
let url = window.URL.createObjectURL(blob);
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName;
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes content download for a Blob with a file name.
|
||||||
|
*
|
||||||
|
* @param {Blob} blob Content to download.
|
||||||
|
* @param {string} fileName Name of the resulting file.
|
||||||
|
*
|
||||||
|
* @memberOf ContentService
|
||||||
|
*/
|
||||||
|
downloadBlob(blob: Blob, fileName: string): void {
|
||||||
|
this.saveData(blob, 'blob', fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes content download for a data array with a file name.
|
||||||
|
*
|
||||||
|
* @param {*} data Data to download.
|
||||||
|
* @param {string} fileName Name of the resulting file.
|
||||||
|
*
|
||||||
|
* @memberOf ContentService
|
||||||
|
*/
|
||||||
|
downloadData(data: any, fileName: string): void {
|
||||||
|
this.saveData(data, 'data', fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes content download for a JSON object with a file name.
|
||||||
|
*
|
||||||
|
* @param {*} json JSON object to download.
|
||||||
|
* @param {any} fileName Name of the resulting file.
|
||||||
|
*
|
||||||
|
* @memberOf ContentService
|
||||||
|
*/
|
||||||
|
downloadJSON(json: any, fileName): void {
|
||||||
|
this.saveData(json, 'json', fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a trusted object URL from the Blob.
|
||||||
|
* WARNING: calling this method with untrusted user data exposes your application to XSS security risks!
|
||||||
|
* @param {Blob} blob Data to wrap into object URL
|
||||||
|
* @returns {string} Object URL content.
|
||||||
|
*
|
||||||
|
* @memberOf ContentService
|
||||||
|
*/
|
||||||
|
createTrustedUrl(blob: Blob): string {
|
||||||
|
let url = window.URL.createObjectURL(blob);
|
||||||
|
return <string> this.sanitizer.bypassSecurityTrustUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -15,6 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from './content.service';
|
||||||
export * from './storage.service';
|
export * from './storage.service';
|
||||||
export * from './alfresco-api.service';
|
export * from './alfresco-api.service';
|
||||||
export * from './alfresco-settings.service';
|
export * from './alfresco-settings.service';
|
||||||
|
Reference in New Issue
Block a user