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;
|
||||
}
|
||||
|
||||
.upload-widget__content {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.upload-widget__icon {
|
||||
float: left;
|
||||
color: rgba(0, 0, 0, .26);
|
||||
|
@@ -1,22 +1,23 @@
|
||||
<div class="upload-widget" *ngIf="content">
|
||||
<div class="mdl-card mdl-shadow--2dp">
|
||||
<div class="mdl-card__title mdl-card--expand">
|
||||
<div class="mdl-card mdl-shadow--2dp upload-widget__content">
|
||||
<div *ngIf="showDocumentContent" class="mdl-card__title mdl-card--expand upload-widget__content-thumbnail">
|
||||
<div *ngIf="content.isThumbnailSupported()">
|
||||
<img class="img-upload-widget" [src]="sanitizeUrl(content.thumbnailUrl)">
|
||||
<img class="img-upload-widget" [src]="content.thumbnailUrl">
|
||||
</div>
|
||||
<div *ngIf="!content.isThumbnailSupported()">
|
||||
<i class="material-icons">image</i>
|
||||
<div class="previewTxt">{{ 'FORM.PREVIEW.IMAGE_NOT_AVAILABLE' | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-card__supporting-text">{{content.name}}</div>
|
||||
<div class="mdl-card__actions mdl-card--border">
|
||||
<div class="mdl-card__supporting-text upload-widget__content-text">{{content.name}}</div>
|
||||
|
||||
<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">
|
||||
<i class="material-icons">zoom_in</i>
|
||||
</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>
|
||||
</div>
|
||||
</button>
|
||||
</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.
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import { AlfrescoTranslationService, LogService } from 'ng2-alfresco-core';
|
||||
import { Component, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { AlfrescoTranslationService, LogService, ContentService } from 'ng2-alfresco-core';
|
||||
import { FormService } from './../services/form.service';
|
||||
import { ContentLinkModel } from './widgets/core/content-link.model';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -39,6 +32,9 @@ export class ActivitiContent implements OnChanges {
|
||||
@Input()
|
||||
id: string;
|
||||
|
||||
@Input()
|
||||
showDocumentContent: boolean = false;
|
||||
|
||||
@Output()
|
||||
contentClick = new EventEmitter();
|
||||
|
||||
@@ -47,19 +43,16 @@ export class ActivitiContent implements OnChanges {
|
||||
constructor(private translate: AlfrescoTranslationService,
|
||||
protected formService: FormService,
|
||||
private logService: LogService,
|
||||
private sanitizer: DomSanitizer ) {
|
||||
|
||||
private contentService: ContentService) {
|
||||
if (this.translate) {
|
||||
this.translate.addTranslationFolder('ng2-activiti-form', 'node_modules/ng2-activiti-form/src');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
let contentId = changes['id'];
|
||||
const contentId = changes['id'];
|
||||
if (contentId && contentId.currentValue) {
|
||||
this.loadContent(contentId.currentValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,19 +72,18 @@ export class ActivitiContent implements OnChanges {
|
||||
|
||||
loadThumbnailUrl(content: ContentLinkModel) {
|
||||
if (this.content.isThumbnailSupported()) {
|
||||
let observable: Observable<any>;
|
||||
|
||||
if (this.content.isTypeImage()) {
|
||||
this.formService.getFileRawContent(content.id).subscribe(
|
||||
(response: Blob) => {
|
||||
this.content.thumbnailUrl = this.createUrlPreview(response);
|
||||
},
|
||||
error => {
|
||||
this.logService.error(error);
|
||||
}
|
||||
);
|
||||
observable = this.formService.getFileRawContent(content.id);
|
||||
} else {
|
||||
this.formService.getContentThumbnailUrl(content.id).subscribe(
|
||||
observable = this.formService.getContentThumbnailUrl(content.id);
|
||||
}
|
||||
|
||||
if (observable) {
|
||||
observable.subscribe(
|
||||
(response: Blob) => {
|
||||
this.content.thumbnailUrl = this.createUrlPreview(response);
|
||||
this.content.thumbnailUrl = this.contentService.createTrustedUrl(response);
|
||||
},
|
||||
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.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(
|
||||
(response: Blob) => {
|
||||
let thumbnailUrl = this.createUrlPreview(response);
|
||||
this.createDownloadElement(thumbnailUrl, content.name);
|
||||
},
|
||||
error => {
|
||||
this.logService.error(error);
|
||||
}
|
||||
(blob: Blob) => this.contentService.downloadBlob(blob, 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 *ngIf="hasFile" class="mdl-grid">
|
||||
<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>
|
||||
|
@@ -48,6 +48,7 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
|
||||
// upload/attach
|
||||
hasFile: boolean = false;
|
||||
showDocumentContent: boolean = false;
|
||||
|
||||
constructor(private formService: FormService,
|
||||
private visibilityService: WidgetVisibilityService,
|
||||
@@ -60,6 +61,7 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
this.value = this.field.value;
|
||||
this.visibilityService.refreshEntityVisibility(this.field);
|
||||
if (this.field.params) {
|
||||
this.showDocumentContent = !!this.field.params['showDocumentContent'];
|
||||
let originalField = this.field.params['field'];
|
||||
if (originalField && originalField.type) {
|
||||
this.fieldType = originalField.type;
|
||||
|
@@ -36,7 +36,8 @@ import {
|
||||
AuthGuardBpm,
|
||||
LogService,
|
||||
LogServiceMock,
|
||||
NotificationService
|
||||
NotificationService,
|
||||
ContentService
|
||||
} from './src/services/index';
|
||||
|
||||
import { UploadDirective } from './src/directives/upload.directive';
|
||||
@@ -66,6 +67,7 @@ export const ALFRESCO_CORE_PROVIDERS: any[] = [
|
||||
AlfrescoTranslateLoader,
|
||||
AlfrescoTranslationService,
|
||||
RenditionsService,
|
||||
ContentService,
|
||||
AuthGuard,
|
||||
AuthGuardEcm,
|
||||
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.
|
||||
*/
|
||||
|
||||
export * from './content.service';
|
||||
export * from './storage.service';
|
||||
export * from './alfresco-api.service';
|
||||
export * from './alfresco-settings.service';
|
||||
|
Reference in New Issue
Block a user