| /******************************************************************************** |
| * 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); |
| } |
| } |