blob: 40fa2bad6ea523235ba0616044694c4754fa6fdd [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2020 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, Input, Output, EventEmitter, OnInit, ViewChild } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { ModalDirective } from 'ngx-bootstrap';
import { BasketService, Basket} from './basket.service';
import { MDMItem} from '../core/mdm-item';
import { OverwriteDialogComponent } from '../core/overwrite-dialog.component';
import { NavigatorService } from '../navigator/navigator.service';
import { Node} from '../navigator/node';
import { NodeService } from '../navigator/node.service';
import { TableviewComponent } from '../tableview/tableview.component';
import { ViewComponent } from '../tableview/view.component';
import { View } from '../tableview/tableview.service';
import { QueryService, SearchResult, Row } from '../tableview/query.service';
import { deserialize } from 'class-transformer';
import {MenuItem} from 'primeng/primeng';
import * as FileSaver from 'file-saver';
import {MDMNotificationService} from '../core/mdm-notification.service';
import { TranslateService } from '@ngx-translate/core';
import { streamTranslate, TRANSLATE } from '../core/mdm-core.module';
import {ConfirmationService} from 'primeng/api';
import { Observable } from 'rxjs/Observable';
import { forkJoin, throwError } from 'rxjs';
import { map, flatMap } from 'rxjs/operators';
@Component({
selector: 'mdm-basket',
templateUrl: 'mdm-basket.component.html',
styleUrls: ['./mdm-basket.component.css'],
})
export class MDMBasketComponent implements OnInit {
@Output() onActive = new EventEmitter<Node>();
@Output() onSelect = new EventEmitter<Node>();
@Input() activeNode: Node;
exportableTypes = ['Project', 'Pool', 'Test', 'TestStep', 'Measurement'];
basketName = '';
basketContent: SearchResult = new SearchResult();
basket = 'Warenkorb';
baskets: Basket[] = [];
selectedBasket: Basket;
environments: Node[];
public selectedRow: string;
public lazySelectedRow: string;
_currentChange: any;
contextMenuItems: MenuItem[] = [
{ label: 'Remove selection from basket', icon: 'fa fa-times', command: (event) => this.removeSelected() }
];
fixedLaunchers: MenuItem[] = [
{label: 'ATFX', command: (event) => this.saveBasketAsATFX(event.originalEvent)}
];
launchers: MenuItem[] = [];
allowedExtensions: string[] = [];
@ViewChild(TableviewComponent)
tableViewComponent: TableviewComponent;
@ViewChild(ViewComponent)
viewComponent: ViewComponent;
@ViewChild('lgLoadModal')
childLoadModal: ModalDirective;
@ViewChild('lgSaveModal')
childSaveModal: ModalDirective;
@ViewChild(OverwriteDialogComponent)
overwriteDialogComponent: OverwriteDialogComponent;
constructor(private _basketService: BasketService,
private queryService: QueryService,
private navigatorService: NavigatorService,
private sanitizer: DomSanitizer,
private nodeService: NodeService,
private notificationService: MDMNotificationService,
private translateService: TranslateService,
private confirmationService: ConfirmationService) {
}
removeSelected() {
this._basketService.remove(this.tableViewComponent.menuSelectedRow.getItem());
}
ngOnInit() {
streamTranslate(this.translateService, TRANSLATE('basket.mdm-basket.remove-selection')).subscribe(
(msg: string) => this.contextMenuItems[0].label = msg);
this.nodeService.getRootNodes().subscribe(
envs => this.environments = envs,
error => this.translateService.getTranslation(TRANSLATE('basket.mdm-basket.err-cannot-load-sources'))
.subscribe(msg => this.notificationService.notifyError(msg, error)
));
this._basketService.getFileExtensions()
.map(launchers => <{ menuItems: MenuItem[], extensions: string[] }>{
menuItems: launchers.map((l: { label: string; extension: string; }) => <MenuItem> {
label: l.label,
command: (event) => this.saveBasketWithExtension(event.originalEvent, l.extension) }),
extensions: launchers.map((l: { extension: string; }) => '.' + l.extension).join(',')
})
.subscribe(({menuItems, extensions}) => {
this.launchers = this.fixedLaunchers.concat(menuItems);
this.allowedExtensions = [ 'application/xml' ].concat(extensions);
});
this.setItems(this._basketService.items);
this._basketService.itemsAdded$.subscribe(
items => this.addItems(items),
error => this.translateService.getTranslation(TRANSLATE('basket.mdm-basket.err-cannot-add-selection'))
.subscribe(msg => this.notificationService.notifyError(msg, error)
));
this._basketService.itemsRemoved$.subscribe(
items => this.removeItems(items),
error => this.translateService.getTranslation(TRANSLATE('basket.mdm-basket.err-cannot-remove-selection'))
.subscribe(msg => this.notificationService.notifyError(msg, error)
));
this.viewComponent.viewChanged$.subscribe(
() => this.setItems(this._basketService.items),
error => this.translateService.getTranslation(TRANSLATE('basket.mdm-basket.err-cannot-select-view'))
.subscribe(msg => this.notificationService.notifyError(msg, error)
));
}
onViewClick(e: Event) {
e.stopPropagation();
}
setItems(items: MDMItem[]) {
this.basketContent.rows = [];
this.addItems(items);
}
addItems(items: MDMItem[]) {
if (this.viewComponent.selectedView) {
this.queryService.queryItems(items, this.viewComponent.selectedView.columns.map(c => c.type + '.' + c.name))
.forEach(q =>
q.subscribe(
r => this.addData(r.rows),
error => this.notificationService.notifyError(
this.translateService.instant('basket.mdm-basket.err-cannot-add-items-to-shopping-basket'), error))
);
}
}
removeItems(items: MDMItem[]) {
// copy and reasign to get new object refference triggering angular change detection in tableview.
let tmp = Object.assign({}, this.basketContent);
items.forEach(item =>
tmp.rows = tmp.rows.filter(row =>
!(row.source === item.source && row.type === item.type && row.id === item.id)));
this.basketContent = tmp;
}
setView(view: View) {
console.log('setView', view);
}
saveBasket(e: Event) {
if (e) {
e.stopPropagation();
}
if (this.baskets.find(f => f.name === this.basketName) != undefined) {
this.childSaveModal.hide();
this.overwriteDialogComponent.showOverwriteModal(
this.translateService.instant('basket.mdm-basket.item-save-shopping-basket')).subscribe(
needSave => this.saveBasket2(needSave),
error => {
this.saveBasket2(false);
this.notificationService.notifyError(this.translateService.instant('basket.mdm-basket.err-save-shopping-basket'), error);
});
} else {
this.saveBasket2(true);
}
}
saveBasket2(save: boolean) {
if (save) {
this._basketService.saveBasketWithName(this.basketName);
this.childSaveModal.hide();
} else {
this.childSaveModal.show();
}
}
loadBasket(basket?: Basket) {
if (basket == undefined) {
basket = this.selectedBasket;
if (this.selectedBasket == undefined) {
return;
}
}
this.basketName = basket.name;
this.setItems(basket.items);
this._basketService.setItems(basket.items);
this.childLoadModal.hide();
}
loadBaskets() {
this._basketService.getBaskets().subscribe(
baskets => this.baskets = baskets,
error => this.notificationService.notifyError(
this.translateService.instant('basket.mdm-basket.err-cannot-load-shopping-basket'), error));
}
clearBasket(e: Event) {
e.stopPropagation();
this.basketContent = new SearchResult();
this._basketService.removeAll();
this.basketName = '';
}
showLoadModal(e: Event) {
e.stopPropagation();
this.selectedBasket = undefined;
this.loadBaskets();
this.childLoadModal.show();
}
showSaveModal(e: Event) {
e.stopPropagation();
this.loadBaskets();
this.basketName = this.selectedBasket ? this.selectedBasket.name : '';
this.childSaveModal.show();
}
stopEvent(event: Event) {
event.stopPropagation();
}
saveBasketWithExtension(event: any, extension: string) {
if (event) {
event.stopPropagation();
}
let downloadContent = new Basket(this.basketName, this._basketService.getItems());
this._basketService.getBasketAsXml(downloadContent)
.map(xml => new Blob([xml], { type: 'application/xml' }))
.subscribe(blob => this.saveAsFile(blob, extension, 'shoppingbasket'));
}
isExportable(item: MDMItem) {
return this.exportableTypes.indexOf(item.type) >= 0;
}
saveBasketAsATFX(event: any) {
if (event) {
event.stopPropagation();
}
const notExportableItems = this._basketService.getItems().filter(i => !this.isExportable(i));
const exportableItems = this._basketService.getItems().filter(i => this.isExportable(i)).map(i => i.type).join(', ');
if (exportableItems.length === 0) {
// No exportable item in shopping basket -> show message and cancel export
const msg = this.translateService.instant('basket.mdm-basket.msg-atfx-only-supports-types',
{ types: this.exportableTypes.join(', ') })
+ ' ' + this.translateService.instant('basket.mdm-basket.msg-only-not-exportable-types-selected',
{ types: notExportableItems.map(i => i.type).join(', ') });
this.confirmationService.confirm({
message: msg,
acceptLabel: 'OK',
rejectVisible: false,
});
} else if (notExportableItems.length > 0) {
// shopping basket contains not exportable items -> show message and ask if export should continue
const msg = this.translateService.instant('basket.mdm-basket.msg-atfx-only-supports-types',
{ types: this.exportableTypes.join(', ') })
+ ' ' + this.translateService.instant('basket.mdm-basket.msg-not-exportable-types-continue',
{ types: exportableItems });
this.confirmationService.confirm({
message: msg,
acceptLabel: 'Yes',
rejectLabel: 'No',
icon: 'pi pi-question-circle',
accept: () => this.exportItemsToAtfx(this._basketService.getItems().filter(i => this.isExportable(i))),
rejectVisible: true,
});
} else {
this.exportItemsToAtfx(this._basketService.getItems());
}
}
exportItemsToAtfx(basket: MDMItem[]) {
let downloadContent = new Basket(this.basketName, basket);
this._basketService.getBasketAsAtfx(downloadContent)
.map(atfx => new Blob([atfx], { type: 'application/xml' }))
.subscribe(atfx => this.saveAsFile(atfx, '.atfx'));
}
saveAsFile(blob: Blob, fileExtension: string, defaultFilename = 'export') {
let name = 'export.' + fileExtension;
if (this.basketName && this.basketName.trim().length !== 0) {
name = this.basketName + '.' + fileExtension;
}
FileSaver.saveAs(blob, name);
}
onUploadChange(event: Event) {
this._currentChange = event.target;
this.onUploadEvent(this._currentChange);
}
onUploadClick(e: Event) {
e.stopPropagation();
}
toggleSelect(basket: Basket) {
this.selectedBasket = this.selectedBasket === basket ? undefined : basket;
}
isDownloadDisabled() {
return this.basketContent.rows.length <= 0;
}
getSaveBtnTitle() {
return this.basketName
? TRANSLATE('basket.mdm-basket.tooltip-save-shopping-basket')
: TRANSLATE('basket.mdm-basket.tooltip-no-name-set');
}
private onUploadEvent(fileInput: any) {
if (fileInput.files[0]) {
const readFile = (blob: Blob): Observable<string> => Observable.create(o => {
if (!(blob instanceof Blob)) {
o.error(new Error('Parameter `blob` must be an instance of Blob.'));
return;
}
const fileReader = new FileReader();
fileReader.onload = () => o.next(fileReader.result);
fileReader.onloadend = () => o.complete();
fileReader.onabort = err => o.error(err);
fileReader.onerror = err => o.error(err);
return fileReader.readAsText(blob);
});
readFile(fileInput.files[0]).pipe(
flatMap(xml => this.parseXmlShoppingBasket(xml))
).subscribe(
basket => this.loadBasket(basket),
err => this.notificationService.notifyWarn('Could not load shopping basket from file.', err)
);
}
}
private parseXmlShoppingBasket(xml: string) {
let oParser = new DOMParser();
let oDOM = oParser.parseFromString(xml, 'application/xml');
// print the name of the root element or error message
if (oDOM.documentElement.tagName !== 'shoppingbasket') {
if (oDOM.documentElement.innerText) {
return throwError(new Error('Error while parsing shopping basket: ' + oDOM.documentElement.innerText));
} else {
return throwError(new Error('Error while parsing shopping basket: XML is missing shoppingbasket root tag'));
}
}
// load the name of the shopping basket
let name = '';
let nameTags = oDOM.documentElement.getElementsByTagName('name');
if (nameTags.length > 0) {
name = nameTags[0].textContent;
}
// load the business objectes defined by the resturi values in the xml file
let items: Observable<Node[]>[] = [];
let uris = oDOM.documentElement.getElementsByTagName('resturi');
if (uris.length === 0) {
return throwError(new Error('No resturis found!'));
}
for (let uri of <any>uris) {
let url = uri.textContent;
items.push(this.nodeService.getNodesByAbsoluteUrl(url));
}
// map business objects to MDMItems and load basket
return forkJoin(items).pipe(
map(n => n.map(x => new MDMItem(x[0].sourceName, x[0].type, x[0].id))),
map(i => new Basket(name, i)));
}
private addData(rows: Row[]) {
this.basketContent.rows.push(... rows);
// copy and reasign to get new object refference triggering angular change detection in tableview.
this.basketContent = Object.assign({}, this.basketContent);
}
onRowSelect(e: any) {
if (this.lazySelectedRow !== e.data) {
this.selectedRow = e.data;
this.basketName = e.data.name;
} else {
this.selectedRow = undefined;
this.basketName = '';
}
this.lazySelectedRow = this.selectedRow;
}
}