| import { Component, OnInit, Input, Output, ViewChild, ViewEncapsulation, EventEmitter } from '@angular/core'; |
| import { ActivatedRoute } from '@angular/router'; |
| import { MatDialog } from '@angular/material/dialog'; |
| |
| import * as _ from 'lodash'; |
| |
| import { Node } from '../models/node.model'; |
| import { Settings } from '../../core/settings'; |
| import { InputDialogComponent } from '../dialog/input-dialog/input-dialog.component'; |
| import { ConfirmDialogComponent } from '../dialog/confirm-dialog/confirm-dialog.component'; |
| |
| @Component({ |
| selector: 'hi-node-viewer', |
| templateUrl: './node-viewer.component.html', |
| styleUrls: ['./node-viewer.component.scss'], |
| // Since we are importing external styles in this component |
| // we will not use Shadow DOM at all to make sure the styles apply |
| encapsulation: ViewEncapsulation.None |
| }) |
| export class NodeViewerComponent implements OnInit { |
| |
| @ViewChild('simpleTable', {static: true}) simpleTable; |
| @ViewChild('listTable', {static: true}) listTable; |
| @ViewChild('mapTable', {static: true}) mapTable; |
| |
| @Output('update') |
| change: EventEmitter<Node> = new EventEmitter<Node>(); |
| |
| @Output('create') |
| create: EventEmitter<Node> = new EventEmitter<Node>(); |
| |
| @Output('delete') |
| delete: EventEmitter<Node> = new EventEmitter<Node>(); |
| |
| @Input() |
| unlockable = false; |
| |
| @Input() |
| loadingIndicator = false; |
| |
| private _editable = false; |
| |
| protected _obj: any; |
| protected node: Node; |
| |
| headerHeight = Settings.tableHeaderHeight; |
| rowHeight = Settings.tableRowHeight; |
| sorts = [ |
| { prop: 'name', dir: 'asc'} |
| ]; |
| keyword = ''; |
| columns = { |
| simpleConfigs: [ |
| { |
| name: 'Name', |
| editable: false |
| }, |
| { |
| name: 'Value', |
| editable: false |
| } |
| ], |
| listConfigs: [ |
| { |
| name: 'Value', |
| editable: false |
| } |
| ] |
| }; |
| |
| _simpleConfigs: any[]; |
| |
| _listConfigs: any[]; |
| |
| _mapConfigs: any[]; |
| |
| // MODE 1: use directly in components |
| @Input() |
| set obj(value: any) { |
| if (value != null) { |
| this._obj = value; |
| this.node = new Node(value); |
| } |
| } |
| get obj(): any { |
| return this._obj; |
| } |
| set editable(value: boolean) { |
| this._editable = value; |
| this.columns.simpleConfigs[1].editable = this._editable; |
| this.columns.listConfigs[0].editable = this._editable; |
| } |
| get editable() { |
| return this._editable; |
| } |
| get simpleConfigs(): any[] { |
| return this.node ? _.filter(this.node.simpleFields, config => { |
| return config.name.toLowerCase().indexOf(this.keyword) >= 0 |
| || config.value.toLowerCase().indexOf(this.keyword) >= 0; |
| }) : []; |
| } |
| get listConfigs(): any[] { |
| return this.node ? _.filter(this.node.listFields, config => { |
| return config.name.toLowerCase().indexOf(this.keyword) >= 0 |
| || _.some(config.value as any[], subconfig => { |
| return subconfig.value.toLowerCase().indexOf(this.keyword) >= 0; |
| }); |
| }) : []; |
| } |
| get mapConfigs(): any[] { |
| return this.node ? _.filter(this.node.mapFields, config => { |
| return config.name.toLowerCase().indexOf(this.keyword) >= 0 |
| || _.some(config.value as any[], subconfig => { |
| return subconfig.name.toLowerCase().indexOf(this.keyword) >= 0 |
| || subconfig.value.toLowerCase().indexOf(this.keyword) >= 0; |
| }); |
| }) : []; |
| } |
| |
| constructor( |
| protected dialog: MatDialog, |
| protected route: ActivatedRoute |
| ) { } |
| |
| ngOnInit() { |
| // MODE 2: use in router |
| if (this.route.snapshot.data.path) { |
| const path = this.route.snapshot.data.path; |
| |
| // try parent data first |
| this.obj = _.get(this.route.parent, `snapshot.data.${ path }`); |
| |
| if (this.obj == null) { |
| // try self data then |
| this.obj = _.get(this.route.snapshot.data, path); |
| } |
| } |
| } |
| |
| updateFilter(event) { |
| this.keyword = event.target.value.toLowerCase().trim(); |
| |
| // Whenever the filter changes, always go back to the first page |
| if (this.simpleTable) { |
| this.simpleTable.offset = 0; |
| } |
| if (this.listTable) { |
| this.listTable.offset = 0; |
| } |
| if (this.mapTable) { |
| this.mapTable.offset = 0; |
| } |
| } |
| |
| getNameCellClass({ value }): any { |
| return { |
| // highlight HELIX own configs |
| 'primary': _.snakeCase(value).toUpperCase() === value |
| }; |
| } |
| |
| onCreate(type) { |
| this.dialog |
| .open(InputDialogComponent, { |
| data: { |
| title: `Create a new ${ type } configuration`, |
| message: 'Please enter the name of the new configuration. You\'ll be able to add values later:', |
| values: { |
| name: { |
| label: 'the name of the new configuration' |
| } |
| } |
| } |
| }) |
| .afterClosed() |
| .subscribe(result => { |
| if (result) { |
| const entry = [{ |
| name: result.name.value, |
| value: [] |
| }]; |
| |
| const newNode: Node = new Node(null); |
| if (type === 'list') { |
| newNode.listFields = entry; |
| } else if (type === 'map') { |
| newNode.mapFields = entry; |
| } |
| |
| this.create.emit(newNode); |
| } |
| }); |
| } |
| |
| beforeDelete(type, row) { |
| this.dialog |
| .open(ConfirmDialogComponent, { |
| data: { |
| title: 'Confirmation', |
| message: 'Are you sure you want to delete this configuration?' |
| } |
| }) |
| .afterClosed() |
| .subscribe(result => { |
| if (result) { |
| this.onDelete(type, row); |
| } |
| }); |
| } |
| |
| onDelete(type, row) { |
| const newNode: Node = new Node(null); |
| |
| if (type === 'simple') { |
| newNode.appendSimpleField(row.name, ''); |
| } else if (type === 'list') { |
| newNode.listFields = [{ name: row.name, value: [] }]; |
| } else if (type === 'map') { |
| newNode.mapFields = [{ name: row.name, value: null }]; |
| } |
| |
| this.delete.emit(newNode); |
| } |
| |
| created(type, data, key) { |
| const newNode: Node = new Node(null); |
| |
| switch (type) { |
| case 'simple': |
| newNode.appendSimpleField(data.name.value, data.value.value); |
| break; |
| |
| case 'list': |
| if (key) { |
| const entry = _.find(this.node.listFields, {'name': key}); |
| // Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'. |
| // Property 'value' does not exist on type 'number'.ts(2339) |
| // @ts-ignore |
| entry.value.push({ |
| name: '', |
| value: data.value.value |
| }); |
| // Argument of type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...' is not assignable to parameter of type 'ListFieldObject'. |
| // Type 'number' is not assignable to type 'ListFieldObject'.ts(2345) |
| // @ts-ignore |
| newNode.listFields.push(entry); |
| } |
| break; |
| |
| case 'map': |
| if (key) { |
| const entry = _.find(this.node.mapFields, {'name': key}); |
| // Property 'value' does not exist on type 'number | MapFieldObject | ((searchElement: MapFieldObject, fromIndex?: number) => boolean) | (() ...'. |
| // Property 'value' does not exist on type 'number'.ts(2339) |
| // @ts-ignore |
| _.forEach(entry.value, (item: any) => { |
| newNode.appendMapField(key, item.name, item.value); |
| }); |
| newNode.appendMapField(key, data.name.value, data.value.value); |
| } |
| break; |
| } |
| |
| this.create.emit(newNode); |
| } |
| |
| edited(type, {row, column, value}, key, isDeleting) { |
| if (!isDeleting && column.name !== 'Value') { |
| return; |
| } |
| |
| const newNode: Node = new Node(null); |
| |
| switch (type) { |
| case 'simple': |
| newNode.appendSimpleField(row.name, value); |
| break; |
| |
| case 'list': |
| if (key) { |
| const entry = _.find(this.node.listFields, {'name': key}); |
| // Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'. |
| // Property 'value' does not exist on type 'number'.ts(2339) |
| // @ts-ignore |
| const index = _.findIndex(entry.value, {'value': row.value}); |
| if (isDeleting) { |
| // Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'. |
| // Property 'value' does not exist on type 'number'.ts(2339) |
| // @ts-ignore |
| entry.value.splice(index, 1); |
| } else { |
| // Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'. |
| // Property 'value' does not exist on type 'number'.ts(2339) |
| // @ts-ignore |
| entry.value[index].value = value; |
| } |
| // Argument of type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...' is not assignable to parameter of type 'ListFieldObject'. |
| // Type 'number' is not assignable to type 'ListFieldObject'.ts(2345) |
| // @ts-ignore |
| newNode.listFields.push(entry); |
| } |
| break; |
| |
| case 'map': |
| if (key) { |
| // have to fetch all other configs under this key |
| const entry = _.find(this.node.mapFields, {'name': key}); |
| newNode.mapFields = [{ name: key, value: [] }]; |
| |
| // Property 'value' does not exist on type 'number | MapFieldObject | ((searchElement: MapFieldObject, fromIndex?: number) => boolean) | (() ...'. |
| // Property 'value' does not exist on type 'number'.ts(2339) |
| // @ts-ignore |
| _.forEach(entry.value, (item: any) => { |
| if (item.name === row.name) { |
| if (!isDeleting) { |
| newNode.appendMapField(key, item.name, value); |
| } |
| } else { |
| newNode.appendMapField(key, item.name, item.value); |
| } |
| }); |
| } |
| break; |
| } |
| |
| this.change.emit(newNode); |
| } |
| } |