| 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) => |
| 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) => |
| config.name.toLowerCase().indexOf(this.keyword) >= 0 || |
| _.some( |
| config.value as any[], |
| (subconfig) => |
| subconfig.value.toLowerCase().indexOf(this.keyword) >= 0 |
| ) |
| ) |
| : []; |
| } |
| get mapConfigs(): any[] { |
| return this.node |
| ? _.filter( |
| this.node.mapFields, |
| (config) => |
| config.name.toLowerCase().indexOf(this.keyword) >= 0 || |
| _.some( |
| config.value as any[], |
| (subconfig) => |
| 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); |
| } |
| } |