diff --git a/src/app/app.module.ts b/src/app/app.module.ts index eea32a3f51..44853ef00a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -89,6 +89,7 @@ import { AngularSplitModule } from 'angular-split'; import { PersonTemplateComponent } from './project/board/person-template/person-template.component'; import { AddressTemplateComponent } from './project/board/address-template/address-template.component'; import { OrganisationTemplateComponent } from './project/board/organisation-template/organisation-template.component'; +import { EditListItemComponent } from './project/list/list-item-form/edit-list-item/edit-list-item.component'; // translate: AoT requires an exported function for factories export function HttpLoaderFactory(httpClient: HttpClient) { @@ -160,7 +161,8 @@ export function HttpLoaderFactory(httpClient: HttpClient) { ContactsTabViewComponent, PersonTemplateComponent, AddressTemplateComponent, - OrganisationTemplateComponent + OrganisationTemplateComponent, + EditListItemComponent ], imports: [ AppRoutingModule, diff --git a/src/app/main/dialog/dialog.component.html b/src/app/main/dialog/dialog.component.html index 45a19c019c..9857c98371 100644 --- a/src/app/main/dialog/dialog.component.html +++ b/src/app/main/dialog/dialog.component.html @@ -148,6 +148,13 @@ + +
+ + + +
+
diff --git a/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.html b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.html new file mode 100644 index 0000000000..84fd1bd958 --- /dev/null +++ b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.html @@ -0,0 +1,45 @@ + + +
+ + + + + {{ formInvalidMessage }} + +

+ + + + + +
+ + + + + + + +
+
diff --git a/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.scss b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.scss new file mode 100644 index 0000000000..78c30554a4 --- /dev/null +++ b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.scss @@ -0,0 +1,4 @@ +.invalid-form{ + color: red; + font-size: 11px; +} diff --git a/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.spec.ts b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.spec.ts new file mode 100644 index 0000000000..6f0da3155f --- /dev/null +++ b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.spec.ts @@ -0,0 +1,152 @@ +import { Component, DebugElement, OnInit, ViewChild } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ApiResponseData, ListNodeInfoResponse, ListsEndpointAdmin, UpdateChildNodeRequest } from '@dasch-swiss/dsp-js'; +import { DspActionModule, DspApiConnectionToken, ProgressIndicatorComponent } from '@dasch-swiss/dsp-ui'; +import { TranslateModule } from '@ngx-translate/core'; +import { of } from 'rxjs'; +import { AjaxResponse } from 'rxjs/ajax'; +import { EditListItemComponent } from './edit-list-item.component'; + +/** + * Test host component to simulate parent component. + */ +@Component({ + template: `` +}) +class TestHostComponent implements OnInit { + + @ViewChild('editListItem') editListItem: EditListItemComponent; + + iri = 'http://rdfh.ch/lists/0001/otherTreeList01'; + + projectIri = 'http://rdfh.ch/projects/0001'; + + constructor() {} + + ngOnInit() { + } + +} + +describe('EditListItemComponent', () => { + let testHostComponent: TestHostComponent; + let testHostFixture: ComponentFixture; + let editListItemComponentDe: DebugElement; + let formInvalidMessageDe: DebugElement; + + beforeEach(async(() => { + + const listsEndpointSpyObj = { + admin: { + listsEndpoint: jasmine.createSpyObj('listsEndpoint', ['getListNodeInfo', 'updateChildNode']) + } + }; + + TestBed.configureTestingModule({ + declarations: [ + EditListItemComponent, + TestHostComponent, + ProgressIndicatorComponent, + ], + imports: [ + BrowserAnimationsModule, + DspActionModule, + TranslateModule.forRoot() + ], + providers: [ + { + provide: DspApiConnectionToken, + useValue: listsEndpointSpyObj + } + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + const dspConnSpy = TestBed.inject(DspApiConnectionToken); + + (dspConnSpy.admin.listsEndpoint as jasmine.SpyObj).getListNodeInfo.and.callFake( + () => { + const response = new ListNodeInfoResponse(); + response.nodeinfo.id = 'http://rdfh.ch/lists/0001/otherTreeList01'; + response.nodeinfo.labels = [{'value': 'Tree list node 01', 'language': 'en'}]; + response.nodeinfo.comments = [{'value': 'My comment', 'language': 'en'}]; + return of(ApiResponseData.fromAjaxResponse({response} as AjaxResponse)); + } + ); + + testHostFixture = TestBed.createComponent(TestHostComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); + + expect(testHostComponent).toBeTruthy(); + + const hostCompDe = testHostFixture.debugElement; + editListItemComponentDe = hostCompDe.query(By.directive(EditListItemComponent)); + expect(editListItemComponentDe).toBeTruthy(); + }); + + it('should assign labels and comments', () => { + const dspConnSpy = TestBed.inject(DspApiConnectionToken); + expect(testHostComponent.editListItem.labels).toEqual([{'value': 'Tree list node 01', 'language': 'en'}]); + expect(testHostComponent.editListItem.comments).toEqual([{'value': 'My comment', 'language': 'en'}]); + expect(dspConnSpy.admin.listsEndpoint.getListNodeInfo).toHaveBeenCalledTimes(1); + expect(dspConnSpy.admin.listsEndpoint.getListNodeInfo).toHaveBeenCalledWith('http://rdfh.ch/lists/0001/otherTreeList01'); + + }); + + it('should update labels when the value changes', () => { + expect(testHostComponent.editListItem.labels).toEqual([{'value': 'Tree list node 01', 'language': 'en'}]); + testHostComponent.editListItem.handleData([{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}], 'labels'); + expect(testHostComponent.editListItem.labels).toEqual([{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}]); + expect(testHostComponent.editListItem.saveButtonDisabled).toBeFalsy(); + testHostComponent.editListItem.handleData([], 'labels'); + expect(testHostComponent.editListItem.saveButtonDisabled).toBeTruthy(); + testHostFixture.detectChanges(); + formInvalidMessageDe = editListItemComponentDe.query(By.css('span.invalid-form')); + expect(formInvalidMessageDe.nativeElement.innerText).toEqual('A label is required.'); + }); + + it('should update comments when the value changes', () => { + expect(testHostComponent.editListItem.comments).toEqual([{'value': 'My comment', 'language': 'en'}]); + testHostComponent.editListItem.handleData([{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}], 'comments'); + expect(testHostComponent.editListItem.comments).toEqual([{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}]); + expect(testHostComponent.editListItem.saveButtonDisabled).toBeFalsy(); + testHostComponent.editListItem.handleData([], 'comments'); + expect(testHostComponent.editListItem.saveButtonDisabled).toBeFalsy(); + }); + + it('should update the child node info', () => { + const dspConnSpy = TestBed.inject(DspApiConnectionToken); + + testHostComponent.editListItem.handleData([{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}], 'labels'); + testHostComponent.editListItem.handleData([{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}], 'comments'); + + (dspConnSpy.admin.listsEndpoint as jasmine.SpyObj).updateChildNode.and.callFake( + () => { + const response = new ListNodeInfoResponse(); + response.nodeinfo.id = 'http://rdfh.ch/lists/0001/otherTreeList01'; + response.nodeinfo.labels = [{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}]; + response.nodeinfo.comments = [{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}]; + + expect(testHostComponent.editListItem.labels).toEqual(response.nodeinfo.labels); + expect(testHostComponent.editListItem.comments).toEqual(response.nodeinfo.comments); + + return of(ApiResponseData.fromAjaxResponse({response} as AjaxResponse)); + } + ); + + const childNodeUpdateData: UpdateChildNodeRequest = new UpdateChildNodeRequest(); + childNodeUpdateData.projectIri = testHostComponent.editListItem.projectIri; + childNodeUpdateData.listIri = testHostComponent.editListItem.iri; + childNodeUpdateData.labels = testHostComponent.editListItem.labels; + childNodeUpdateData.comments = testHostComponent.editListItem.comments; + + testHostComponent.editListItem.updateChildNode(); + expect(dspConnSpy.admin.listsEndpoint.updateChildNode).toHaveBeenCalledTimes(1); + expect(dspConnSpy.admin.listsEndpoint.updateChildNode).toHaveBeenCalledWith(childNodeUpdateData); + }); +}); diff --git a/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.ts b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.ts new file mode 100644 index 0000000000..31abe3ab52 --- /dev/null +++ b/src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.ts @@ -0,0 +1,130 @@ +import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; +import { ApiResponseData, ApiResponseError, ChildNodeInfoResponse, KnoraApiConnection, List, ListNodeInfo, ListNodeInfoResponse, StringLiteral, UpdateChildNodeRequest } from '@dasch-swiss/dsp-js'; +import { DspApiConnectionToken } from '@dasch-swiss/dsp-ui'; + +@Component({ + selector: 'app-edit-list-item', + templateUrl: './edit-list-item.component.html', + styleUrls: ['./edit-list-item.component.scss'] +}) +export class EditListItemComponent implements OnInit { + loading: boolean; + + @Input() iri: string; + + @Input() projectIri: string; + + @Output() closeDialog: EventEmitter = new EventEmitter(); + + // the list node being edited + listNode: ListNodeInfo; + + // local arrays to use when updating the list node + labels: StringLiteral[]; + comments: StringLiteral[]; + + /** + * error checking on the following fields + */ + formErrors = { + label: { + 'required': 'A label is required.' + } + }; + + /** + * in case of an API error + */ + errorMessage: any; + + saveButtonDisabled = false; + + formInvalidMessage: string; + + constructor(@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection) { } + + ngOnInit(): void { + this.loading = true; + + // get list + this._dspApiConnection.admin.listsEndpoint.getListNodeInfo(this.iri).subscribe( + (response: ApiResponseData) => { + this.listNode = response.body.nodeinfo; + this.buildForm(response.body.nodeinfo); + }, + (error: ApiResponseError) => { + console.error(error); + } + ); + } + + /** + * Separates the labels and comments of a list node into two local arrays. + * + * @param listNode info about a list node + */ + buildForm(listNode: ListNodeInfo): void { + + this.labels = []; + this.comments = []; + + if (listNode && listNode.id) { + this.labels = listNode.labels; + this.comments = listNode.comments; + } + + this.loading = false; + } + + /** + * Called from the template any time the labels or comments are changed to update the local arrays. + * At least one label is required. Otherwise, the 'update' button will be disabled. + * + * @param data the data that was changed + * @param type the type of data that was changed + */ + handleData(data: StringLiteral[], type: string) { + switch (type) { + case 'labels': + this.labels = data; + break; + + case 'comments': + this.comments = data; + break; + } + + if (this.labels.length === 0) { + // invalid form, don't let user submit + this.saveButtonDisabled = true; + this.formInvalidMessage = this.formErrors.label.required; + } else { + this.saveButtonDisabled = false; + this.formInvalidMessage = null; + } + } + + /** + * Called from the template when the 'update' button is clicked. + * Sends a request to DSP-API to update the list node with the data inside the two local arrays. + */ + updateChildNode() { + const childNodeUpdateData: UpdateChildNodeRequest = new UpdateChildNodeRequest(); + childNodeUpdateData.projectIri = this.projectIri; + childNodeUpdateData.listIri = this.iri; + childNodeUpdateData.labels = this.labels; + childNodeUpdateData.comments = this.comments.length > 0 ? this.comments : []; + + this._dspApiConnection.admin.listsEndpoint.updateChildNode(childNodeUpdateData).subscribe( + (response: ApiResponseData) => { + this.loading = false; + this.closeDialog.emit(response.body.nodeinfo); + }, + (error: ApiResponseError) => { + this.errorMessage = error; + this.loading = false; + } + ); + } + +} diff --git a/src/app/project/list/list-item-form/list-item-form.component.html b/src/app/project/list/list-item-form/list-item-form.component.html index aabbb69d3a..9c0fc5b8ee 100644 --- a/src/app/project/list/list-item-form/list-item-form.component.html +++ b/src/app/project/list/list-item-form/list-item-form.component.html @@ -1,10 +1,10 @@
+ (dataChanged)="handleData($event)" [language]="language" (enter)="createChildNode()">
- -
- + +
+ (dataChanged)="handleData($event)" [language]="language"> - - - +
+
+ +
+
diff --git a/src/app/project/list/list-item-form/list-item-form.component.scss b/src/app/project/list/list-item-form/list-item-form.component.scss index ea0945de93..05a38548df 100644 --- a/src/app/project/list/list-item-form/list-item-form.component.scss +++ b/src/app/project/list/list-item-form/list-item-form.component.scss @@ -13,12 +13,69 @@ } .add-node-btn, .edit-node-btn, -.progress-indicator { +.progress-indicator +.update-success-btn { margin: 0 0 0 8px; } +.update-success-btn { + color: green !important; + cursor: default; +} + .new-list-item { display: inline-flex; margin: 8px 0 0 40px; width: 640px; } + +.list-item { + position: relative; + + .action-bubble { + position: absolute; + right: 5px; + top: 0px; + border: 1px solid #e4e4e4; + border-radius: 10px; + padding: 0 1px; + background-color: #e4e4e4; + z-index: 2; + box-shadow: #949494 1px 4px 5px 0px; + + .button-container { + + button { + cursor: pointer; + border: none; + padding: 2px; + outline: none; + background-color: transparent; + color: #000000; + margin: 0 2px; + border-radius: 10px; + transition: background-color ease-out 0.5s; + min-width: inherit; + line-height: normal; + + .material-icons { + font-size: 18px; + } + + .mat-icon { + width: 18px; + height: 18px; + vertical-align: middle; + } + } + + button.info { + cursor: default; + } + + button:hover { + background-color: #c7c7c7; + } + } + } +} diff --git a/src/app/project/list/list-item-form/list-item-form.component.ts b/src/app/project/list/list-item-form/list-item-form.component.ts index 8d43a90f6c..dcca6ec66d 100644 --- a/src/app/project/list/list-item-form/list-item-form.component.ts +++ b/src/app/project/list/list-item-form/list-item-form.component.ts @@ -1,21 +1,48 @@ +import { trigger, state, style, transition, animate } from '@angular/animations'; import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; import { FormGroup } from '@angular/forms'; +import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { ApiResponseData, ApiResponseError, + ChildNodeInfo, CreateChildNodeRequest, KnoraApiConnection, + ListInfoResponse, ListNodeInfo, ListNodeInfoResponse, StringLiteral } from '@dasch-swiss/dsp-js'; import { DspApiConnectionToken } from '@dasch-swiss/dsp-ui'; +import { DialogComponent } from 'src/app/main/dialog/dialog.component'; import { ErrorHandlerService } from 'src/app/main/error/error-handler.service'; @Component({ selector: 'app-list-item-form', templateUrl: './list-item-form.component.html', - styleUrls: ['./list-item-form.component.scss'] + styleUrls: ['./list-item-form.component.scss'], + animations: [ + // the fade-in/fade-out animation. + // https://www.kdechant.com/blog/angular-animations-fade-in-and-fade-out + trigger('simpleFadeAnimation', [ + + // the "in" style determines the "resting" state of the element when it is visible. + state('in', style({opacity: 1})), + + // fade in when created. + transition(':enter', [ + // the styles start from this point when the element appears + style({opacity: 0}), + // and animate toward the "in" state above + animate(150) + ]), + + // fade out when destroyed. + transition(':leave', + // fading out uses a different syntax, with the "style" being passed into animate() + animate(150, style({opacity: 0}))) + ]) + ] }) export class ListItemFormComponent implements OnInit { @@ -52,16 +79,12 @@ export class ListItemFormComponent implements OnInit { placeholder: string = 'Append item to '; - /** - * form group for the form controller - */ - form: FormGroup; - - updateData: boolean = false; + showActionBubble: boolean = false; constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, - private _errorHandler: ErrorHandlerService + private _errorHandler: ErrorHandlerService, + private _dialog: MatDialog ) { } ngOnInit() { @@ -75,8 +98,13 @@ export class ListItemFormComponent implements OnInit { // it can be used in the input placeholder if (this.parentIri) { this._dspApiConnection.admin.listsEndpoint.getListNodeInfo(this.parentIri).subscribe( - (response: ApiResponseData) => { - this.placeholder += response.body.nodeinfo.labels[0].value; + (response: ApiResponseData) => { + if (response.body instanceof ListInfoResponse) { // root node + this.placeholder += response.body.listinfo.labels[0].value; + } else { // child node + this.placeholder += response.body.nodeinfo.labels[0].value; + } + this.initComponent = false; }, (error: ApiResponseError) => { @@ -86,7 +114,11 @@ export class ListItemFormComponent implements OnInit { } } - submitData() { + /** + * Called from the template when the plus button is clicked. + * Sends the info to make a new child node to DSP-API and refreshes the UI to show the newly added node at the end of the list. + */ + createChildNode() { if (!this.labels.length) { return; @@ -94,45 +126,40 @@ export class ListItemFormComponent implements OnInit { this.loading = true; - if (this.iri && this.updateData) { - // edit mode - // TODO: update node method not yet implemented; Waiting for Knora API + // generate the data payload + const listItem: CreateChildNodeRequest = new CreateChildNodeRequest(); + listItem.parentNodeIri = this.parentIri; + listItem.projectIri = this.projectIri; + listItem.name = this.projectcode + '-' + Math.random().toString(36).substr(2) + Math.random().toString(36).substr(2); + + // initialize labels + let i = 0; + for (const l of this.labels) { + listItem.labels[i] = new StringLiteral(); + listItem.labels[i].language = l.language; + listItem.labels[i].value = l.value; + i++; + } + listItem.comments = []; // TODO: comments are not yet implemented in the template - // TODO: remove setTimeout after testing position of progress indicator - setTimeout(() => { + // send payload to dsp-api's api + this._dspApiConnection.admin.listsEndpoint.createChildNode(listItem).subscribe( + (response: ApiResponseData) => { + this.refreshParent.emit(response.body.nodeinfo); this.loading = false; - }, 500); - - } else { - // generate the data payload - const listItem: CreateChildNodeRequest = new CreateChildNodeRequest(); - listItem.parentNodeIri = this.parentIri; - listItem.projectIri = this.projectIri; - listItem.name = this.projectcode + '-' + Math.random().toString(36).substr(2) + Math.random().toString(36).substr(2); - - // initialize labels - let i = 0; - for (const l of this.labels) { - listItem.labels[i] = new StringLiteral(); - listItem.labels[i].language = l.language; - listItem.labels[i].value = l.value; - i++; + }, + (error: ApiResponseError) => { + this._errorHandler.showMessage(error); } - listItem.comments = []; // TODO: comments are not yet implemented in the template - - // send payload to dsp-api's api - this._dspApiConnection.admin.listsEndpoint.createChildNode(listItem).subscribe( - (response: ApiResponseData) => { - this.refreshParent.emit(response.body.nodeinfo); - this.loading = false; - }, - (error: ApiResponseError) => { - this._errorHandler.showMessage(error); - } - ); - } + ); } + /** + * Called from the template any time the label changes. + * Currently only implemented for labels because entering comments is not yet supported. + * + * @param data the data that was changed. + */ handleData(data: StringLiteral[]) { // this shouldn't run on the init... if (!this.initComponent) { @@ -140,7 +167,49 @@ export class ListItemFormComponent implements OnInit { } } - toggleBtn(show: boolean) { - this.updateData = show; + /** + * Show action bubble with various CRUD buttons when hovered over. + */ + mouseEnter() { + this.showActionBubble = true; + } + + /** + * Hide action bubble with various CRUD buttons when not hovered over. + */ + mouseLeave() { + this.showActionBubble = false; + } + + /** + * Called when the 'edit' button is clicked. + * + * @param mode mode to tell DialogComponent which part of the template to show. + * @param name label of the node; for now this is always the first label in the array. + * @param iri iri of the node. + */ + openDialog(mode: string, name: string, iri?: string): void { + const dialogConfig: MatDialogConfig = { + width: '640px', + position: { + top: '112px' + }, + data: { mode: mode, title: name, id: iri, project: this.projectIri } + }; + + // open the dialog box + const dialogRef = this._dialog.open( + DialogComponent, + dialogConfig + ); + + dialogRef.afterClosed().subscribe((data: ChildNodeInfo) => { + // update the view if data was passed back + // data is only passed back when clicking the 'update' button + if (data) { + this.refreshParent.emit(data as ListNodeInfo); + this.labels = data.labels; + } + }); } } diff --git a/src/app/project/list/list-item/list-item.component.html b/src/app/project/list/list-item/list-item.component.html index f3449d82d3..0a9153a4f1 100644 --- a/src/app/project/list/list-item/list-item.component.html +++ b/src/app/project/list/list-item/list-item.component.html @@ -8,8 +8,7 @@ - + diff --git a/src/app/project/list/list-item/list-item.component.ts b/src/app/project/list/list-item/list-item.component.ts index cca137e290..850d97f6d2 100644 --- a/src/app/project/list/list-item/list-item.component.ts +++ b/src/app/project/list/list-item/list-item.component.ts @@ -2,6 +2,7 @@ import { Component, Inject, Input, OnInit } from '@angular/core'; import { ApiResponseData, ApiResponseError, + ChildNodeInfo, KnoraApiConnection, ListNode, ListResponse @@ -30,37 +31,40 @@ export class ListItemComponent implements OnInit { expandedNode: string; - loading: boolean; - constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, private _errorHandler: ErrorHandlerService ) { } ngOnInit() { - this.loading = true; - // in case of child node: do not run the following request + // in case of parent node: do not run the following request if (!this.childNode) { this._dspApiConnection.admin.listsEndpoint.getList(this.parentIri).subscribe( (result: ApiResponseData) => { this.list = result.body.list.children; this.language = result.body.list.listinfo.labels[0].language; - - this.loading = false; }, (error: ApiResponseError) => { this._errorHandler.showMessage(error); } ); } - } + /** + * Checks if parent node should show its children. + * @param id id of parent node. + */ showChildren(id: string): boolean { return (id === this.expandedNode); } + /** + * Called from template when the 'expand' button is clicked. + * + * @param id id of parent node for which the 'expand' button was clicked. + */ toggleChildren(id: string) { if (this.showChildren(id)) { @@ -71,20 +75,30 @@ export class ListItemComponent implements OnInit { } + /** + * Called when the 'refreshParent' event from ListItemFormComponent is triggered. + * + * @param data info about the node; can be a root node or child node. + * @param firstNode states whether or not the node is a new child node; defaults to false. + */ updateView(data: ListNode, firstNode: boolean = false) { - this.loading = true; - // update the view by updating the existing list - if (firstNode) { - // in case of new child node, we have to use the children from list - const index: number = this.list.findIndex(item => item.id === this.expandedNode); - this.list[index].children.push(data); - + if (data instanceof ChildNodeInfo) { + this.list[data.position].labels = data.labels; + this.list[data.position].comments = data.comments; } else { - this.list.push(data); - } + // update the view by updating the existing list + if (firstNode) { + // in case of new child node, we have to use the children from list + const index: number = this.list.findIndex(item => item.id === this.expandedNode); + this.list[index].children.push(data); + + } else { + this.list.push(data); + } - data.children = []; + data.children = []; + } }