blob: d372911d3e79382c6b13b95099d27786b8c5940b [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
import { MenuItem, ConfirmationService, DialogService, DynamicDialogRef } from 'primeng/api';
import { TranslateService} from '@ngx-translate/core';
import { Observable } from 'rxjs';
import * as FileSaver from 'file-saver';
import { TRANSLATE } from '@core/mdm-core.module';
import { Node, FileSize, Attribute, MDMLink, ContextGroup } from '@navigator/node';
import { FilesAttachableService } from '../../services/files-attachable.service';
import { ContextFilesService } from '../../services/context-files.service';
import { FileService } from '../../services/file.service';
import { FileExplorerColumn, FileExplorerRow, FileLinkContextWrapper } from '../../model/file-explorer.model';
import { FileUploadDialogComponent } from '../file-upload-dialog/file-upload-dialog.component';
import { ContextAttributeIdentifier } from '@details/model/details.model';
@Component({
selector: 'mdm5-file-explorer',
templateUrl: './file-explorer.component.html',
styleUrls: ['../file-explorer.css'],
providers: [ConfirmationService]
})
export class FileExplorerComponent implements OnInit, OnChanges {
private readonly CONTEXT_COMPONENT = 'ContextComponent';
// holding column configuration for table
public columnConfigs: FileExplorerColumn[] = [];
// holding file metadata for table
public fileMetas: FileExplorerRow[] = [];
// holding table selection
public selectedFileMeta: FileExplorerRow;
// holding node selected in navigator
@Input() public node: Node;
@Input() public contextDescribable?: Node;
@Input() public attribute: Attribute;
@Input() public contextGroup?: ContextGroup;
@Input() public contextType?: string;
@Input() public readOnly: boolean;
public links: MDMLink[];
// items for context menu
public ctxMenuItems: MenuItem[];
public constructor(
private filesAttachableService: FilesAttachableService,
private contextFilesService: ContextFilesService,
private translateService: TranslateService,
private confirmationService: ConfirmationService,
private dialogService: DialogService,
private fileService: FileService) {}
public ngOnInit() {
this.initColumns();
// workaround for ctx menu translation, since pipe cannot be passed into primeng contextmenu.
this.initCtxMenu();
this.translateService.onLangChange.subscribe(() => this.initCtxMenu());
this.init();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['node']) {
this.init();
}
}
// initialize component data
private init() {
if (this.node != undefined) {
this.fileMetas = [];
this.selectedFileMeta = undefined;
if (this.attribute != undefined) {
this.links = this.getValues<MDMLink[]>();
if (this.links != undefined && this.links.length > 0) {
this.loadFileSizes();
this.fileMetas = this.links.map(mdmLink => this.link2Row(mdmLink));
}
}
}
}
// defines column configuration.
private initColumns() {
this.columnConfigs = [];
this.columnConfigs.push(new FileExplorerColumn('filename', TRANSLATE('file-explorer.file-explorer.hdr-filename')));
this.columnConfigs.push(new FileExplorerColumn('description', TRANSLATE('file-explorer.file-explorer.hdr-description')));
this.columnConfigs.push(new FileExplorerColumn('type', TRANSLATE('file-explorer.file-explorer.hdr-type')));
this.columnConfigs.push(new FileExplorerColumn('size', TRANSLATE('file-explorer.file-explorer.hdr-size')));
}
// defines context menu configuration.
private initCtxMenu() {
this.ctxMenuItems = [];
this.translateService.get('file-explorer.file-explorer.btn-preview-file')
.subscribe(t => this.ctxMenuItems.push(
{ label: t,
icon: 'fa fa-search',
command: (event) => this.onPreviewFile(event)
})
);
this.translateService.get('file-explorer.file-explorer.btn-download-file')
.subscribe(t => this.ctxMenuItems.push(
{ label: t,
icon: 'fa fa-arrow-circle-o-down',
command: (event) => this.onDownloadFile(event)
})
);
this.translateService.get('file-explorer.file-explorer.btn-delete-file')
.subscribe(t => this.ctxMenuItems.push(
{ label: t,
icon: 'fa fa-times',
command: (event) => this.onDeleteFile(event)
})
);
}
// listener to refresh selected node
public onRefresh() {
this.reloadSelectedNode();
}
// reloads the selected node (and consequently all file data)
private reloadSelectedNode() {
this.init();
}
// mapping function to create row data object from MDMLink array entry
private link2Row(mdmLink: MDMLink) {
if (mdmLink == undefined) {
return new FileExplorerRow(undefined, undefined, undefined, undefined, '-');
}
let nameIndex = mdmLink.remotePath.lastIndexOf('/');
let name = mdmLink.remotePath.substring(nameIndex + 1);
return new FileExplorerRow(mdmLink.remotePath, name, mdmLink.description, mdmLink.mimeType, mdmLink.size);
}
// loads file sizes
private loadFileSizes() {
// case for files attached to context attributes
if (this.node.type === this.CONTEXT_COMPONENT) {
// NOT PROPERLY IMPLEMENTED YET. Remove line below and add proper loading mechanism for all sizes at once
this.links.forEach(l => this.loadSizeLazy(l.remotePath));
} else { // case for files attached to filesAttachables
this.filesAttachableService.loadFileSizes(this.node).subscribe(fileSizes => this.updateFileSizes(fileSizes));
}
}
// load size information from server for single file
private loadSizeLazy(remotePath: string) {
// case for files attached to context attributes
if (this.contextDescribable != undefined) {
// NOT PROPERLY IMPLEMENTED YET. Remove lines below and add proper loading mechanism for all sizes at once
if (this.contextGroup != undefined) {
const link = this.links.find(l => l.remotePath === remotePath);
if (link != undefined) {
this.contextFilesService.loadFileSize(link, this.getIdent())
.subscribe(fileSize => this.updateFileSize(fileSize));
}
}
} else { // case for files attached to filesAttachables
this.filesAttachableService.loadFileSize(remotePath, this.node)
.subscribe(fileSize => this.updateFileSize(fileSize));
}
}
// update size in row data for table
private updateFileSizes(fileSizes: FileSize[]) {
if (this.fileMetas != undefined && fileSizes != undefined && fileSizes.length > 0) {
fileSizes.forEach(fileSize => this.updateFileSize(fileSize));
}
}
// update size in row data for table for single file
private updateFileSize(fileSize: FileSize) {
this.fileMetas.find(fm => fm.remotePath === fileSize.remotePath).size = fileSize.size;
}
public onShowUploadDialog() {
let ref: DynamicDialogRef;
// case for files attached to context attributes
if (this.contextDescribable != undefined) {
ref = this.dialogService.open(FileUploadDialogComponent, {
data: { contextDescribable: this.contextDescribable,
node: this.node,
contextType: this.contextType,
contextGroup: this.contextGroup,
attribute: this.attribute
},
header: 'File upload wizard for ' + this.node.name,
width: '70%'
});
// case for files attached to filesAttachables
} else {
ref = this.dialogService.open(FileUploadDialogComponent, {
data: { node: this.node,
attribute: this.attribute
},
header: 'File upload wizard for ' + this.node.name,
width: '70%'
});
}
ref.onClose.subscribe(linkObs => this.handleUploadDialogResponse(linkObs));
}
// reflects file upload in client side state
private handleUploadDialogResponse(linkObs: Observable<MDMLink>) {
if (linkObs != undefined) {
linkObs.subscribe(link => this.handleUpload(link));
}
}
// listener to load blob from db and initialize download dialog for saving file to local filesystem.
public onDownloadFile(event: MouseEvent) {
if (this.selectedFileMeta != undefined) {
this.loadFile().subscribe(blob => FileSaver.saveAs(blob, this.selectedFileMeta.filename));
}
}
private loadFile() {
if (this.contextDescribable != undefined) {
// case for files attached to context attributes
if (this.contextGroup != undefined) {
const link = this.findLink(this.selectedFileMeta);
if (link != undefined) {
return this.contextFilesService.loadFile(link, this.getIdent());
}
return Observable.throwError('Cannot find link!');
}
return Observable.throwError('ContextGroup is undefined!');
} else {
// case for files attached to filesAttachables
return this.filesAttachableService.loadFile(this.selectedFileMeta.remotePath, this.node);
}
}
// listener for file preview
public onPreviewFile(event: MouseEvent) {
if (this.selectedFileMeta != undefined) {
this.loadFile().subscribe(blob => this.handlePreview(blob));
}
}
// handle preview for different browsers. Opens download dialog if preview not possible.
private handlePreview(blob: Blob) {
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, this.selectedFileMeta.filename);
} else {
window.open(URL.createObjectURL(blob));
}
}
// listener for delete file button. Opens confirm dialog.
public onDeleteFile(event: MouseEvent) {
this.translateService.get('file-explorer.file-explorer.msg-confirm-delete-file-from-db')
.subscribe(msg =>
this.confirmationService.confirm({
message: msg,
key: 'fileExplorerConfirmation',
accept: () => this.deleteFileConfirmed()
})
);
}
// Actually triggers file delete from db, when confirmed in delete dialog
public deleteFileConfirmed() {
if (this.contextDescribable != undefined) {
const fileLink = this.findLink(this.selectedFileMeta);
if (fileLink != undefined) {
this.contextFilesService.deleteFile(fileLink, this.getIdent()).subscribe(link => this.handleDelete(link));
}
} else { // case for files attached to filesAttachables
this.filesAttachableService.deleteFile(this.selectedFileMeta.remotePath, this.node)
.subscribe(link => this.handleDelete(link));
}
this.selectedFileMeta = undefined;
}
// removes row to fileMetas to reflect server side file delete in client state.
private removeRow(link: MDMLink) {
if (link != undefined) {
const i = this.fileMetas.findIndex(row => link.remotePath === row.remotePath);
if (i >= 0) {
this.fileMetas.splice(i, 1);
}
}
}
// adds row to fileMetas to reflect file upload in client state. Lazy loads filesize.
private addRow(link: MDMLink) {
if (link != undefined) {
this.fileMetas.push(this.link2Row(link));
if (link.size == undefined) {
this.loadSizeLazy(link.remotePath);
}
}
}
private findLink(fileMeta: FileExplorerRow) {
if (fileMeta != undefined) {
return this.links.find(l => l.remotePath === fileMeta.remotePath);
}
}
private getValues<T>() {
if (this.attribute != undefined && this.attribute.value != undefined) {
const tmp = this.contextGroup != undefined ? this.attribute.value[this.contextGroup] : this.attribute.value;
return tmp !== '' ? tmp : [];
}
}
private handleUpload(link: MDMLink) {
this.addRow(link);
if (this.contextGroup != undefined) {
let value = this.attribute.value[this.contextGroup];
if (value == undefined || value === '') {
value = [];
}
value.push(link);
this.fireOnChange();
}
}
private handleDelete(link: MDMLink) {
this.removeRow(link);
let index = this.attribute.value[this.contextGroup].findIndex( p => p.remotePath === link.remotePath);
if (this.contextGroup != undefined) {
this.attribute.value[this.contextGroup].splice(index, 1 );
this.fireOnChange();
}
}
private fireOnChange() {
const fileLinkContextWrapper = new FileLinkContextWrapper();
fileLinkContextWrapper.attribute = this.attribute;
fileLinkContextWrapper.contextComponent = this.node;
fileLinkContextWrapper.contextType = this.contextType;
this.fileService.fireOnFileChanged(fileLinkContextWrapper);
}
private getIdent() {
return new ContextAttributeIdentifier(this.contextDescribable, this.node, this.attribute, this.contextGroup, this.contextType);
}
}