diff options
Diffstat (limited to 'vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree')
13 files changed, 1376 insertions, 0 deletions
diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/dragAndDrop/dragAndDrop.model.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/dragAndDrop/dragAndDrop.model.ts new file mode 100644 index 000000000..a8e9b435a --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/dragAndDrop/dragAndDrop.model.ts @@ -0,0 +1,15 @@ +/****************************************** + type - node type + isFirstLevel : node is first level + ******************************************/ + +export class DragAndDropModel { + type : string; + isFirstLevel : boolean; + + constructor(type : string, isFirstLevel : boolean){ + this.type = type; + this.isFirstLevel = isFirstLevel; + } + +} diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/dragAndDrop/dragAndDrop.service.spec.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/dragAndDrop/dragAndDrop.service.spec.ts new file mode 100644 index 000000000..1221cef5f --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/dragAndDrop/dragAndDrop.service.spec.ts @@ -0,0 +1,227 @@ +import {TestBed, getTestBed} from '@angular/core/testing'; +import { + HttpClientTestingModule, + HttpTestingController +} from '@angular/common/http/testing'; +import {NgRedux} from "@angular-redux/store"; +import {DragAndDropService} from "./dragAndDrop.service"; +import {AppState} from "../../../../shared/store/reducers"; + +class MockAppStore<T> { + dispatch(){ + + } + getState() { + return { + global: { + flags: { + "DRAG_AND_DROP_OPERATION" : true + } + }, + service: { + serviceInstance: { + "serviceInstanceId": { + vnfs: { + "vnfStoreKey": { + isMissingData: true, + vfModules: { + "vfModulesName": { + "vfModulesName": { + isMissingData: true + } + } + } + }, + + "vnfStoreKey1": { + isMissingData: false, + vfModules: { + "vfModulesName": { + "vfModulesName": { + isMissingData: false + } + } + } + } + } + } + } + } + } + } +} + +describe('Drag and drop service', () => { + let injector; + let service: DragAndDropService; + let httpMock: HttpTestingController; + let store: NgRedux<AppState>; + + beforeAll(done => (async () => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + DragAndDropService, + {provide: NgRedux, useClass: MockAppStore}] + }); + await TestBed.compileComponents(); + + injector = getTestBed(); + service = injector.get(DragAndDropService); + httpMock = injector.get(HttpTestingController); + store = injector.get(NgRedux); + })().then(done).catch(done.fail)); + + + test('drag should move element position', () => { + let nodes = [{ + "modelCustomizationId": "91415b44-753d-494c-926a-456a9172bbb9", + "modelId": "d6557200-ecf2-4641-8094-5393ae3aae60", + "modelUniqueId": "91415b44-753d-494c-926a-456a9172bbb9", + "missingData": false, + "id": "tjjongy92jn", + "action": "Create", + "inMaint": false, + "name": "yoav2_001", + "modelName": "VF_vMee 0", + "type": "VF", + "isEcompGeneratedNaming": true, + "networkStoreKey": "VF_vMee 0:0001", + "vnfStoreKey": "VF_vMee 0:0001", + "typeName": "VNF", + "menuActions": {"edit": {}, "showAuditInfo": {}, "duplicate": {}, "remove": {}, "delete": {}, "undoDelete": {}}, + "isFailed": false, + "statusProperties": [{"key": "Prov Status:", "testId": "provStatus"}, { + "key": "Orch Status:", + "testId": "orchStatus" + }], + "trackById": "di9khuolht", + "parentType": "", + "position": 0, + "children": [{ + "modelCustomizationId": "f8c040f1-7e51-4a11-aca8-acf256cfd861", + "modelId": "a27f5cfc-7f12-4f99-af08-0af9c3885c87", + "modelUniqueId": "f8c040f1-7e51-4a11-aca8-acf256cfd861", + "missingData": false, + "id": 6654971919519, + "action": "Create", + "name": "VFModule1", + "modelName": "vf_vmee0..VfVmee..base_vmme..module-0", + "type": "VFmodule", + "isEcompGeneratedNaming": true, + "dynamicInputs": [], + "dynamicModelName": "vf_vmee0..VfVmee..base_vmme..module-0bykqx", + "typeName": "M", + "menuActions": {"edit": {}, "showAuditInfo": {}, "remove": {}, "delete": {}, "undoDelete": {}}, + "isFailed": false, + "statusProperties": [{"key": "Prov Status:", "testId": "provStatus"}, { + "key": "Orch Status:", + "testId": "orchStatus" + }], + "trackById": "5pfyfah820h", + "parentType": "VNF", + "position": 0, + "errors": {} + }, { + "modelCustomizationId": "6add59e0-7fe1-4bc4-af48-f8812422ae7c", + "modelId": "41708296-e443-4c71-953f-d9a010f059e1", + "modelUniqueId": "6add59e0-7fe1-4bc4-af48-f8812422ae7c", + "missingData": false, + "id": 987761655742, + "action": "Create", + "name": "VNFModule3", + "modelName": "vf_vmee0..VfVmee..vmme_gpb..module-2", + "type": "VFmodule", + "isEcompGeneratedNaming": true, + "dynamicInputs": [], + "dynamicModelName": "vf_vmee0..VfVmee..vmme_gpb..module-2fjrrc", + "typeName": "M", + "menuActions": {"edit": {}, "showAuditInfo": {}, "remove": {}, "delete": {}, "undoDelete": {}}, + "isFailed": false, + "statusProperties": [{"key": "Prov Status:", "testId": "provStatus"}, { + "key": "Orch Status:", + "testId": "orchStatus" + }], + "trackById": "i3dllio31bb", + "parentType": "VNF", + "position": 1, + "errors": {} + }, { + "modelCustomizationId": "55b1be94-671a-403e-a26c-667e9c47d091", + "modelId": "522159d5-d6e0-4c2a-aa44-5a542a12a830", + "modelUniqueId": "55b1be94-671a-403e-a26c-667e9c47d091", + "missingData": false, + "id": 873798901625, + "action": "Create", + "name": "VFModule2", + "modelName": "vf_vmee0..VfVmee..vmme_vlc..module-1", + "type": "VFmodule", + "isEcompGeneratedNaming": true, + "dynamicInputs": [], + "dynamicModelName": "vf_vmee0..VfVmee..vmme_vlc..module-1djjni", + "typeName": "M", + "menuActions": {"edit": {}, "showAuditInfo": {}, "remove": {}, "delete": {}, "undoDelete": {}}, + "isFailed": false, + "statusProperties": [{"key": "Prov Status:", "testId": "provStatus"}, { + "key": "Orch Status:", + "testId": "orchStatus" + }], + "trackById": "w7bvw1nh47s", + "parentType": "VNF", + "position": 2, + "errors": {} + }], + "errors": {} + }, { + "modelCustomizationId": "91415b44-753d-494c-926a-456a9172bbb9", + "modelId": "d6557200-ecf2-4641-8094-5393ae3aae60", + "modelUniqueId": "91415b44-753d-494c-926a-456a9172bbb9", + "missingData": false, + "id": "dywch8hkomi", + "action": "Create", + "inMaint": false, + "name": "yoav2", + "modelName": "VF_vMee 0", + "type": "VF", + "isEcompGeneratedNaming": true, + "networkStoreKey": "VF_vMee 0", + "vnfStoreKey": "VF_vMee 0", + "typeName": "VNF", + "menuActions": {"edit": {}, "showAuditInfo": {}, "duplicate": {}, "remove": {}, "delete": {}, "undoDelete": {}}, + "isFailed": false, + "statusProperties": [{"key": "Prov Status:", "testId": "provStatus"}, { + "key": "Orch Status:", + "testId": "orchStatus" + }], + "trackById": "fjczf1urdqo", + "parentType": "", + "position": 1, + "children": [], + "errors": {} + }]; + let from = { + data: { + type: 'VF', + index: 1 + } + }; + + let to = { + parent: { + data: { + type: 'VF', + index: 0 + } + } + }; + jest.spyOn(service, 'array_move'); + + service.drag(store, "serviceInstanceId", nodes, {from, to}); + + + expect(service.array_move).toHaveBeenCalled(); + + }); + + +}); diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/dragAndDrop/dragAndDrop.service.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/dragAndDrop/dragAndDrop.service.ts new file mode 100644 index 000000000..01763c685 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/dragAndDrop/dragAndDrop.service.ts @@ -0,0 +1,78 @@ +import {Injectable} from "@angular/core"; +import {NgRedux} from "@angular-redux/store"; +import {AppState} from "../../../../shared/store/reducers"; +import {DragAndDropModel} from "./dragAndDrop.model"; +import {FeatureFlagsService, Features} from "../../../../shared/services/featureFlag/feature-flags.service"; +import * as _ from 'lodash'; + +@Injectable() +export class DragAndDropService { + + constructor(private store: NgRedux<AppState>){} + + isAllow(): boolean { + return FeatureFlagsService.getFlagState(Features.DRAG_AND_DROP_OPERATION, this.store); + } + /******************************************************************** + * manage drawing-board drag and drop operation + * @param nodes - array with elements data. + * @param tree - tree instance + * @param node - element information + * @param from - element from information + * @param to - element to information + ************************************************************/ + + drag(store, instanceId : string , nodes, {from, to}) :void{ + if (!store.getState().global.flags["DRAG_AND_DROP_OPERATION"]) return; + + let firstLevelNames : DragAndDropModel[] = [ + new DragAndDropModel('VF',true), + new DragAndDropModel('VL',true), + new DragAndDropModel('VFmodule',false) + ]; + + const fromObject = _.find(firstLevelNames, ['type', from.data.type]); + const toObject = _.find(firstLevelNames, ['type', to.parent.data.type]); + + /*********************************************************************************************** + if the type are the same and there in same level + same parent -> then change element position + ***********************************************************************************************/ + if(fromObject.isFirstLevel === toObject.isFirstLevel){ // moving element in the same level and in the first level + if(fromObject.isFirstLevel){ + this.array_move(nodes, from.index , to.parent.index, instanceId); + } else if(fromObject.isFirstLevel === toObject.isFirstLevel){ + /* check if they have the same parent */ + if(from.parent.data.trackById === to.parent.parent.data.trackById){ + let vfModules = nodes.find((parents)=> { + return parents.trackById === to.parent.parent.data.trackById; + }).children; + this.array_move(vfModules, from.index , to.parent.index, instanceId, to.parent.parent.data.vnfStoreKey); + } + } + } + } + + + /******************************************************************** + * move element inside array with elements position + * @param arr - array with elements data. + * @param originalPosition - element original position + * @param destPosition - element dest position + * @param destPinstanceIdosition - instance id + ******************************************************************/ + array_move(arr, originalPosition, destPosition, instanceId : string, parentStoreKey?) { + if (destPosition >= arr.length) { + let k = destPosition - arr.length + 1; + while (k--) { + arr.push(undefined); + } + } + arr.splice(destPosition, 0, arr.splice(originalPosition, 1)[0]); + arr.forEach((item, index) => { + if(item.position !== index){ + item.position = index; + item.updatePoistionFunction(this, item, instanceId, parentStoreKey); + } + }); + }; +} diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.component.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.component.ts new file mode 100644 index 000000000..d0715982c --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.component.ts @@ -0,0 +1,207 @@ +import {AfterViewInit, Component, EventEmitter, OnInit, Output, ViewChild,} from '@angular/core'; +import {ContextMenuComponent, ContextMenuService} from 'ngx-contextmenu'; +import {Constants} from '../../../shared/utils/constants'; +import {IDType, ITreeNode} from "angular-tree-component/dist/defs/api"; +import {TreeComponent, TreeModel, TreeNode} from "angular-tree-component"; +import {DialogService} from "ng2-bootstrap-modal"; +import {ActivatedRoute} from "@angular/router"; +import {NgRedux} from "@angular-redux/store"; +import {AppState} from "../../../shared/store/reducers"; +import {IframeService} from "../../../shared/utils/iframe.service"; +import {DuplicateService} from '../duplicate/duplicate.service'; +import {DrawingBoardTreeService, TreeNodeContextMenuModel} from "./drawing-board-tree.service"; +import {NetworkPopupService} from "../../../shared/components/genericFormPopup/genericFormServices/network/network.popup.service"; +import {VfModulePopuopService} from "../../../shared/components/genericFormPopup/genericFormServices/vfModule/vfModule.popuop.service"; +import {VnfPopupService} from "../../../shared/components/genericFormPopup/genericFormServices/vnf/vnf.popup.service"; +import {SdcUiServices} from "onap-ui-angular"; +import {HighlightPipe} from "../../../shared/pipes/highlight/highlight-filter.pipe"; +import {VnfGroupPopupService} from "../../../shared/components/genericFormPopup/genericFormServices/vnfGroup/vnfGroup.popup.service"; +import {ObjectToInstanceTreeService} from "../objectsToTree/objectToInstanceTree/objectToInstanceTree.service"; +import {SharedTreeService} from "../objectsToTree/shared.tree.service"; +import {Subject} from "rxjs/Subject"; +import {changeServiceIsDirty} from "../../../shared/storeUtil/utils/service/service.actions"; +import * as _ from 'lodash'; +import {ErrorMsgService} from "../../../shared/components/error-msg/error-msg.service"; +import {DragAndDropService} from "./dragAndDrop/dragAndDrop.service"; +import {FeatureFlagsService, Features} from "../../../shared/services/featureFlag/feature-flags.service"; +import {PopoverPlacement} from "../../../shared/components/popover/popover.component"; + +@Component({ + selector: 'drawing-board-tree', + templateUrl: './drawing-board-tree.html', + styleUrls: ['./drawing-board-tree.scss'], + providers: [HighlightPipe] +}) + +export class DrawingBoardTreeComponent implements OnInit, AfterViewInit { + _store: NgRedux<AppState>; + duplicateService: DuplicateService; + drawingBoardTreeService: DrawingBoardTreeService; + errorMsgService: ErrorMsgService; + isFilterEnabled: boolean = false; + filterValue: string = ''; + contextMenuOptions: TreeNodeContextMenuModel[]; + static triggerDeleteActionService: Subject<string> = new Subject<string>(); + static triggerUndoDeleteActionService: Subject<string> = new Subject<string>(); + static triggerreCalculateIsDirty: Subject<string> = new Subject<string>(); + @ViewChild(ContextMenuComponent) public contextMenu: ContextMenuComponent; + + constructor(private _contextMenuService: ContextMenuService, + private _iframeService: IframeService, + private dialogService: DialogService, + private store: NgRedux<AppState>, + private route: ActivatedRoute, + private _duplicateService: DuplicateService, + private modalService: SdcUiServices.ModalService, + private _drawingBoardTreeService: DrawingBoardTreeService, + private _networkPopupService: NetworkPopupService, + private _vfModulePopuopService: VfModulePopuopService, + private _vnfPopupService: VnfPopupService, + private _vnfGroupPopupService: VnfGroupPopupService, + private _errorMsgService: ErrorMsgService, + private _highlightPipe: HighlightPipe, + private _objectToInstanceTreeService: ObjectToInstanceTreeService, + private _sharedTreeService: SharedTreeService, + private _dragAndDropService : DragAndDropService) { + + this.errorMsgService = _errorMsgService; + this.duplicateService = _duplicateService; + this.drawingBoardTreeService = _drawingBoardTreeService; + this.contextMenuOptions = _drawingBoardTreeService.generateContextMenuOptions(); + + DrawingBoardTreeComponent.triggerDeleteActionService.subscribe((serviceModelId) => { + this._sharedTreeService.shouldShowDeleteInstanceWithChildrenModal(this.nodes, serviceModelId, (node, serviceModelId)=>{ + this.drawingBoardTreeService.deleteActionService(this.nodes, serviceModelId); + this.store.dispatch(changeServiceIsDirty(this.nodes, serviceModelId)); + }); + }); + + DrawingBoardTreeComponent.triggerUndoDeleteActionService.subscribe((serviceModelId) => { + this.drawingBoardTreeService.undoDeleteActionService(this.nodes, serviceModelId); + this.store.dispatch(changeServiceIsDirty(this.nodes, serviceModelId)); + }); + + DrawingBoardTreeComponent.triggerreCalculateIsDirty.subscribe((serviceModelId) => { + this.store.dispatch(changeServiceIsDirty(this.nodes, serviceModelId)); + }); + + this._store = store; + this.route + .queryParams + .subscribe(params => { + this.serviceModelId = params['serviceModelId']; + }); + } + + getNodeId(node: ITreeNode): string { + return (node.data.parentType !== "" ? (node.data.parentType + "_") : "") + node.data.typeName; + } + + updateNodes(updateData: { nodes: any, filterValue: string }): void { + this.nodes = updateData.nodes; + this.filterValue = updateData.filterValue; + } + + @Output() + highlightNode: EventEmitter<number> = new EventEmitter<number>(); + + @ViewChild('tree') tree: TreeComponent; + missingDataTooltip: string = Constants.Error.MISSING_VNF_DETAILS; + currentNode: ITreeNode = null; + flags: any; + nodes = []; + serviceModelId: string; + options = { + allowDrag: this._dragAndDropService.isAllow(), + actionMapping: { + mouse: { + drop: (tree:TreeModel, node:TreeNode, $event:any, {from, to}) => { + this._dragAndDropService.drag(this.store, this.serviceModelId, this.nodes, {from, to}); + } + } + }, + nodeHeight: 45, + dropSlotHeight: 1 + }; + parentElementClassName = 'content'; + + ngOnInit(): void { + this.store.subscribe(() => { + this.updateTree(); + }); + this.updateTree() + } + + getNodeName(node: ITreeNode, filter: string) { + return this._highlightPipe.transform(node.data.name, filter ? filter : ''); + } + + updateTree() { + const serviceInstance = this.store.getState().service.serviceInstance[this.serviceModelId]; + this.nodes = this._objectToInstanceTreeService.convertServiceInstanceToTreeData(serviceInstance, this.store.getState().service.serviceHierarchy[this.serviceModelId]); + console.log('right nodes', this.nodes); + + } + + + ngAfterViewInit(): void { + this.tree.treeModel.expandAll(); + } + + public onContextMenu($event: MouseEvent, node: ITreeNode): void { + this.flags = this.store.getState().global.flags; + + this.currentNode = node; + node.focus(); + node.setActiveAndVisible(false); + this.selectNode(node); + setTimeout(() => { + this._contextMenuService.show.next({ + contextMenu: this.contextMenu, + event: <any>$event, + item: node, + }); + $event.preventDefault(); + $event.stopPropagation(); + }, 250); + + } + + + executeMenuAction(methodName: string): void { + if (!_.isNil(this.currentNode.data.menuActions) && !_.isNil(this.currentNode.data.menuActions[methodName])) { + this.currentNode.data.menuActions[methodName]['method'](this.currentNode, this.serviceModelId); + this.store.dispatch(changeServiceIsDirty(this.nodes, this.serviceModelId)); + } + } + + isEnabled(node: ITreeNode, serviceModelId: string, methodName: string): boolean { + if (!_.isNil(this.currentNode) && !_.isNil(this.currentNode.data.menuActions) && !_.isNil(this.currentNode.data.menuActions[methodName])) { + return this.currentNode.data.menuActions[methodName]['enable'](this.currentNode, this.serviceModelId); + } + return false; + } + + isVisible(node: ITreeNode, methodName: string): boolean { + if (!_.isNil(this.currentNode) && !_.isNil(this.currentNode.data.menuActions) && !_.isNil(this.currentNode.data.menuActions[methodName])) { + return this.currentNode.data.menuActions[methodName]['visible'](this.currentNode, this.serviceModelId); + } + return false; + } + + public selectNode(node: ITreeNode): void { + node.expand(); + this._sharedTreeService.setSelectedVNF(node); + this.highlightNode.emit(node.data.modelUniqueId); + if (FeatureFlagsService.getFlagState(Features.FLAG_1906_COMPONENT_INFO, this.store)) { + node.data.onSelectedNode(node); + } + } + + expandParentByNodeId(id: IDType): void { + this.tree.treeModel.getNodeById(id).parent.expand(); + } + +} + + diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.html b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.html new file mode 100644 index 000000000..8af909ffc --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.html @@ -0,0 +1,96 @@ +<error-msg></error-msg> +<div class="drawing-board-tree" style="height: calc(100vh - 55px);"> + <div *ngIf="nodes?.length == 0" style="text-align: center; margin-top: 50px;"> + <no-content-message-and-icon class="span-over" + data-title="Please add objects (VNFs, network, modules etc.)" + title2="from the left tree to design the service instance" + subtitle="Once done, click Deploy to start instantiation" + iconPath="./assets/img/UPLOAD.svg" + iconClass="upload-icon-service-planing"></no-content-message-and-icon> + </div> + <div class="tree-header" *ngIf="nodes?.length > 0"> + <div class="title-tree">Instance:</div> + <search-component (updateNodes)="updateNodes($event)" + [nodes]="nodes" [tree]="tree" + [inputTestId]="'search-right-tree'" + *ngIf="drawingBoardTreeService.isViewEditFlagTrue()"></search-component> + </div> + <tree-root [attr.data-tests-id]="'drawing-board-tree'" #tree [nodes]="nodes" [options]="options" + id="drawing-board-tree"> + + <ng-template #treeNodeTemplate let-node let-index="index" > + <div [attr.id]="getNodeId(node)" [attr.data-tests-id]="'node-'+node.data.modelId +'-' +node.data.modelName" (click)="selectNode(node)" > + <custom-popover class="failed-popover-wrap" *ngIf= "node?.data?.isFailed" [value]= "node?.data?.statusMessage" [placement]="'left'" [popoverType]="'error'"> + <div class="failed-msg" [attr.data-tests-id]="'failed-error-message'" *ngIf= "node?.data?.isFailed">Failed</div> + </custom-popover> + <div class="instance-type" style="position: relative;"> + <div *ngIf="node?.data?.action == 'Create'" class="notShowOnViewMode notShowOnCreateMode newIcon"></div> + <div><span title="{{node.data.type}}" [attr.data-tests-id]="'node-type-indicator'">{{node?.data?.typeName}}</span></div> + </div> + <div class="model-info"> + <span class="header-info"> + <span class="property-name"> + <span class="auto-name" + [ngClass]="{'text_decoration' : drawingBoardTreeService.isTextDecoration(node)}" + [innerHtml]="getNodeName(node, filterValue) | safe : 'html'" + [attr.data-tests-id]="'node-name'" + ></span> + </span> + </span> + <tree-node-header-properties + *ngIf="(node?.data?.action !== 'Create' || node?.data?.parentType === 'VnfGroup') && !node?.data?.isFailed" + [properties]="node.data.statusProperties"></tree-node-header-properties> + </div> + <div class="scaling invalid" *ngIf="node?.data?.errors?.scalingError" [attr.data-tests-id]="'scaling-policy'"> + <span>Limit</span><span>{{node?.data?.limitMembers}}</span> + </div> + <div class="model-actions notShowOnViewMode"> + <span class="icon-browse" + [attr.data-tests-id]="'node-'+node.data.modelId +'-' +node.data.modelName+'-menu-btn'" + (click)="onContextMenu($event, node)"> + <context-menu> + <ng-template *ngFor="let contextMenuOption of contextMenuOptions" + contextMenuItem (execute)="executeMenuAction(contextMenuOption.methodName)" + [visible]="isVisible(currentNode, contextMenuOption.methodName)" + [enabled]="isEnabled(currentNode, serviceModelId, contextMenuOption.methodName)"> + <div [attr.data-tests-id]="contextMenuOption.dataTestId"> + <div style="float: left;margin-top: 3px;"> + <svg-icon + [ngClass]="contextMenuOption.iconClass" + class="icon-edit" + [size]="'small'" + [name]="contextMenuOption.iconClass"> + </svg-icon></div> + <div style="padding-left: 25px;">{{contextMenuOption.label}}</div> + </div> + </ng-template> + </context-menu> + </span> + <span + *ngIf="drawingBoardTreeService.isVNFMissingData(node, serviceModelId)" + tooltip="{{ missingDataTooltip }}" + tooltipPlacement="left" + [attr.data-tests-id]="'node-'+node.data.modelId +'-' +node.data.modelName+'-alert-icon'" + class="icon-alert" > + <svg-icon + [mode]="'warning'" + [testId]="'icon-alert'" + [size]="'medium'" + [name]="'alert-triangle-o'"> + </svg-icon> + </span> + + <!--<span *ngIf="drawingBoardTreeService.isVNFMissingData(node, serviceModelId)" class="icon-alert"--> + <!--tooltip="{{ missingDataTooltip }}" tooltipPlacement="left"--> + <!--[attr.data-tests-id]="'node-'+node.data.modelId +'-' +node.data.modelName+'-alert-icon'"></span>--> + <span *ngIf="drawingBoardTreeService.isVFModuleMissingData(node, serviceModelId)" class="icon-alert" + tooltip="{{ missingDataTooltip }}" tooltipPlacement="left" + [attr.data-tests-id]="'node-'+node.data.modelId +'-' +node.data.modelName+'-alert-icon'"></span> + </div> + </div > + </ng-template> + </tree-root> +</div> + + + diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.scss b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.scss new file mode 100644 index 000000000..be9f9f2d4 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.scss @@ -0,0 +1,405 @@ +@mixin highlight($background-color, $color) { + background: none; + padding: 0; + background-color: $background-color; + border: $color 1px solid; + color: $color; +} +@mixin highlight-toggle-children { + tree-node-expander { + .toggle-children-wrapper { + span.toggle-children { + @include highlight(white, #009FDB); + border-right: none; + .isFailed { + left: 0px !important; + } + } + } + + } +} + +#VNF > node-content-wrapper { + border: 1px dashed #D2D2D2 !important; +} + +@mixin highlight-tree-node-content { + tree-node-content { + > div { + .model-actions { + .icon-browse:before { + display: inline-block; + } + } + } + } +} + +#RETRY_EDIT drawing-board-tree tree-node-collection > div, +#RETRY drawing-board-tree tree-node-collection > div { + margin-top: 0px; + width: calc(100% - 50px); + margin-left: auto; + } + +drawing-board-tree { + flex: 1; + color: #5A5A5A; + line-height: 14px; + flex-direction:column; + + &.col-md-6,&.col-md-5{ + padding: 0; + } + .tree-header { + display: flex; + justify-content: space-between; + + .title-tree { + font-family: OpenSans-SemiBold; + font-size: 16px; + color: #191919; + text-align: left; + text-transform: uppercase; + padding-bottom: 48px; + } + .search-container { + width: 275px; + } + } + .highlight { + background-color: #9DD9EF; + } + .toggle-children-wrapper.toggle-children-wrapper-expanded { + .toggle-children:before { + color: #009FDB; + } + } + .drawing-board-tree { + width: 100%; + padding: 30px 45px; + } + + .tree-node-level-1 { + margin-bottom: 10px; + } + tree-viewport { + tree-node { + tree-node-drop-slot { + .node-drop-slot { + display: none; + } + } + & .tree-node-focused, + & .tree-node-active { + & > tree-node-wrapper { + .node-wrapper { + @include highlight-toggle-children; + .node-content-wrapper-focused, + .node-content-wrapper-active { + @include highlight-toggle-children; + @include highlight(#E6F6FB, #009FDB); + .property-name,.instance-type { + color: #009FDB !important; + position: relative; + } + .status-properties { + .status-property-value,.status-property-name { + color: #009FDB; + } + } + .icon-browse:before { + color: #5A5A5A; + } + @include highlight-tree-node-content; + } + } + } + } + & .tree-node-expanded { + > tree-node-wrapper .node-wrapper { + box-shadow: 0 0px 2px rgba(90,90,90,0.24); + } + } + + .tree-node-active .tree-children { + //border: 1px solid #009FDB; + padding-left: 45px; + } + + .tree-node.tree-node-active.tree-node-expanded { + } + + .tree-children .tree-node-leaf .node-wrapper{ + margin-left: -5px; + } + + .tree-node.tree-node-expanded > tree-node-wrapper{ + box-shadow: 0 2px 2px 0 rgba(0,0,0,.1); + position: relative; + z-index: 1; + display: block; + } + tree-node-wrapper { + .node-wrapper { + height: 45px; + &:hover { + .node-content-wrapper:not(.node-content-wrapper-focused) { + background: #F2F2F2; + .icon-browse:before { + color: #5A5A5A; + } + @include highlight-tree-node-content; + } + } + tree-node-expander { + font-family: 'icomoon' !important; + height: 100%; + .failed-msg { + + } + .toggle-children-wrapper { + padding: 0; + display: block; + height: 100%; + span.toggle-children { + display: flex; + width: 45px; + padding: 0; + top: 0; + height: inherit; + background-image: none; + background-color: white; + border: 1px solid #D2D2D2; + border-right: none; + &:before { + content: "\e900"; + font-size: 20px; + font-weight: 600; + text-align: center; + display: inline-block; + flex: auto; + align-self: center; + } + } + } + .toggle-children-wrapper-expanded { + span.toggle-children { + transform: none; + &:before { + content: "\e930"; + } + } + } + .toggle-children-placeholder { + width:45px; + } + } + + .node-content-wrapper { + padding: 0; + background: none; + box-shadow: none; + border-radius: 0; + border: 1px solid #D2D2D2; + height: 100%; + flex: 1; + .resourceGroup { + border: 1px dashed #D2D2D2 !important; + } + tree-node-content { + > div { + height: 100%; + display: flex; + align-items: center; + + .instance-type { + width: 40px; + height: 100%; + padding-top: 16px; + text-transform: uppercase; + text-align: center; + border-right: 1px solid #D2D2D2; + word-break: break-all; + color: #959595; + font-size: 13px; + font-family: OpenSans-SemiBold; + .newIcon { + background: #45B16D; + position: absolute; + top: 5%; + width: 90%; + left: 5%; + right: 5%; + border-radius: 2px; + height: 5px; + } + span { + width: 40px; + display: block; + } + } > span { + flex: 1; + display: block; + + span:nth-child(2) { + display: block; + } + } + .model-info { + padding-left: 16px; + width: 100%; + .property-name { + font-family: OpenSans-SemiBold; + font-size: 13px; + color: #191919; + //text-transform: capitalize; problematic with search + .auto-name{ + display: inline-flex;//required for search more then one sub highlight, + font-size: 13px; + } + .text_decoration{ + text-decoration: line-through; + } + + .text_decoration:after { + height: 10px; + } + } + tree-node-header-properties{ + display: block; + } + } + .scaling { + background: #4ca90c; + padding: 5px; + border-radius: 3px; + font-family: OpenSans-SemiBold; + font-size: 12px; + color: #FFF9F9; + &.invalid{ + background: #cf2a2a; + } + span:first-child{ + margin-right: 3px; + } + } + .model-actions { + display: flex; + align-items: center; + .icon-browse { + padding: 0; + width: 30px; + height: 24px; + &:before { + content: "\e924"; + font-size: 24px; + display: none; + } + &:hover:before { + color: #009FDB; + } + &:focus:before, + &:active:before { + color: #009FDB; + } + } + + .icon-alert { + padding-right: 10px; + } + } + } + } + } + } + } + tree-node-children { + .tree-children { + padding-left: 45px; + .model-info span:first-child { + flex: 1.1 !important; + } + } + } + } + } +} + + +.tree-node-level-1.tree-node.tree-node-expanded { + .failed-popover-wrap { + left: -50px !important; + position: absolute; + } +} +.tree-node-level-1 { + .failed-popover-wrap { + left: 45px !important; + position: absolute; + } +} + +.tree-node-level-2.tree-node.tree-node-leaf { + .failed-popover-wrap { + left: 135px !important; + position: absolute; + } +} + +.tree-node-level-1.tree-node.tree-node-collapsed { + .failed-popover-wrap{ + left: 0px !important; + position: absolute; + } +} + +.failed-msg{ + background: #cf2a2a; + padding: 5px; + border-radius: 3px; + font-family: OpenSans-SemiBold; + font-size: 12px; + color: #FFF9F9; +} + +.cdk-overlay-pane.ngx-contextmenu { + ul.dropdown-menu { + width: 200px; + box-shadow: none; + padding: 0; + padding-top: 10px; + margin: 0; + border: 1px solid #D2D2D2; + border-top: 3px solid #009FDB; + box-shadow: 0 0px 2px rgba(90,90,90,0.24); + border-radius: 2px; + li { + height: 40px; + a { + font-family: OpenSans-Regular; + display: flex; + align-items: center; + height: 100%; + padding: 12px; + color: #5A5A5A; + &:hover { + background: #E6F6FB; + color: #009FDB; + } + span { + padding-right: 12px; + &.icon-edit:before { + content: '\e917'; + } + &.icon-trash:before { + content: '\e937'; + } + } + } + } + } +} + + diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.service.spec.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.service.spec.ts new file mode 100644 index 000000000..1b913cfe9 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.service.spec.ts @@ -0,0 +1,147 @@ +import {TestBed, getTestBed} from '@angular/core/testing'; +import { + HttpClientTestingModule, + HttpTestingController +} from '@angular/common/http/testing'; +import {NgRedux} from "@angular-redux/store"; +import {DrawingBoardTreeService, TreeNodeContextMenuModel} from "./drawing-board-tree.service"; +import {ITreeNode} from "angular-tree-component/dist/defs/api"; + + class MockAppStore<T>{ + getState() { + return { + service : { + serviceInstance : { + "serviceInstanceId" : { + vnfs : { + "vnfStoreKey" : { + isMissingData : true, + vfModules : { + "vfModulesName" : { + "vfModulesName" : { + isMissingData : true + } + } + } + }, + + "vnfStoreKey1" : { + isMissingData : false, + vfModules : { + "vfModulesName" : { + "vfModulesName" : { + isMissingData : false + } + } + } + } + } + } + } + } + } + } +} + +describe('Drawing board tree Service', () => { + let injector; + let service: DrawingBoardTreeService; + let httpMock: HttpTestingController; + + + beforeAll(done => (async () => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + DrawingBoardTreeService, + {provide: NgRedux, useClass: MockAppStore}] + }); + await TestBed.compileComponents(); + + injector = getTestBed(); + service = injector.get(DrawingBoardTreeService); + httpMock = injector.get(HttpTestingController); + + })().then(done).catch(done.fail)); + + + + + test('generateContextMenuOptions should return list of optional context menu', () => { + const options : TreeNodeContextMenuModel[] = service.generateContextMenuOptions(); + const expected : TreeNodeContextMenuModel[] = [ + new TreeNodeContextMenuModel('edit', 'context-menu-edit', 'Edit', 'edit-file-o'), + new TreeNodeContextMenuModel('duplicate', 'context-menu-duplicate', 'Duplicate', 'copy-o'), + new TreeNodeContextMenuModel('showAuditInfo', 'context-menu-showAuditInfo', 'Show audit info', 'eye-o'), + new TreeNodeContextMenuModel('addGroupMember', 'context-menu-addGroupMember', 'Add group members', 'plus'), + new TreeNodeContextMenuModel('delete', 'context-menu-delete', 'Delete', 'trash-o'), + new TreeNodeContextMenuModel('remove', 'context-menu-remove', 'Remove', 'trash-o'), + new TreeNodeContextMenuModel('undoDelete', 'context-menu-undoDelete', 'Undo Delete', 'undo-delete') + ]; + expect(options.length).toEqual(7); + expect(options).toEqual(expected); + }); + + test('isVNFMissingData should return true if vnf isMissingData = true', () => { + let node : ITreeNode = <any>{ + data : { + type : 'VF', + vnfStoreKey : "vnfStoreKey" + } + }; + let result : boolean = service.isVNFMissingData(node, "serviceInstanceId"); + expect(result).toBeTruthy(); + }); + + + test('isVNFMissingData should return false if vnf has isMissingData = false', () => { + let node : ITreeNode = <any>{ + data : { + type : 'VFModule', + modelName : "vfModulesName", + dynamicModelName : "vfModulesName", + parent : { + vnfStoreKey : "vnfStoreKey1", + type : 'VF' + } + } + }; + let result : boolean = service.isVNFMissingData(node, "serviceInstanceId"); + expect(result).toBeFalsy(); + }); + + + test('isVFModuleMissingData should return true if vnfModule has isMissingData = true', () => { + let node : ITreeNode = <any>{ + data : { + type : 'VFModule', + modelName : "vfModulesName", + dynamicModelName : "vfModulesName", + parent : { + vnfStoreKey : "vnfStoreKey", + type : 'VF' + } + } + }; + let result : boolean = service.isVFModuleMissingData(node, "serviceInstanceId"); + expect(result).toBeFalsy(); + }); + + + test('isVFModuleMissingData should return false if vnfModule has isMissingData = false', () => { + let node : ITreeNode = <any>{ + data : { + type : 'VFModule', + modelName : "vfModulesName", + dynamicModelName : "vfModulesName", + parent : { + vnfStoreKey : "vnfStoreKey1", + type : 'VF' + } + } + }; + let result : boolean = service.isVFModuleMissingData(node, "serviceInstanceId"); + expect(result).toBeFalsy(); + }); + +}); diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.service.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.service.ts new file mode 100644 index 000000000..17f761c41 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/drawing-board-tree.service.ts @@ -0,0 +1,129 @@ +import {Injectable} from "@angular/core"; +import {ITreeNode} from "angular-tree-component/dist/defs/api"; +import * as _ from 'lodash'; +import {NgRedux} from "@angular-redux/store"; +import {AppState} from "../../../shared/store/reducers"; +import {FeatureFlagsService, Features} from "../../../shared/services/featureFlag/feature-flags.service"; +import {ServiceInstanceActions} from "../../../shared/models/serviceInstanceActions"; + +@Injectable() +export class DrawingBoardTreeService { + constructor(private store: NgRedux<AppState>){} + isVFModuleMissingData(node: ITreeNode, serviceModelId : string): boolean { + if(node.data.type === 'VFmodule' &&!_.isNil(this.store.getState().service.serviceInstance[serviceModelId].vnfs) && !_.isNil(this.store.getState().service.serviceInstance[serviceModelId].vnfs[node.parent.data.vnfStoreKey])){ + if(!_.isNil(this.store.getState().service.serviceInstance[serviceModelId].vnfs[node.parent.data.vnfStoreKey].vfModules) + && !_.isNil(this.store.getState().service.serviceInstance[serviceModelId].vnfs[node.parent.data.vnfStoreKey].vfModules[node.data.modelName]) + && !_.isNil(this.store.getState().service.serviceInstance[serviceModelId].vnfs[node.parent.data.vnfStoreKey].vfModules[node.data.modelName][node.data.dynamicModelName])){ + + return this.store.getState().service.serviceInstance[serviceModelId].vnfs[node.parent.data.vnfStoreKey].vfModules[node.data.modelName][node.data.dynamicModelName].isMissingData; + } + } + return false; + } + + isVNFMissingData(node : ITreeNode, serviceModelId : string) : boolean { + if(node.data.type == 'VF' && !_.isNil(this.store.getState().service.serviceInstance[serviceModelId].vnfs[node.data.vnfStoreKey])){ + return this.store.getState().service.serviceInstance[serviceModelId].vnfs[node.data.vnfStoreKey].isMissingData; + } + } + + isViewEditFlagTrue():boolean{ + return FeatureFlagsService.getFlagState(Features.FLAG_1902_NEW_VIEW_EDIT, this.store); + } + + /********************************************** + return all drawing board context menu options + ***********************************************/ + generateContextMenuOptions() : TreeNodeContextMenuModel[]{ + return [ + new TreeNodeContextMenuModel('edit', 'context-menu-edit', 'Edit', 'edit-file-o'), + new TreeNodeContextMenuModel('duplicate', 'context-menu-duplicate', 'Duplicate', 'copy-o'), + new TreeNodeContextMenuModel('showAuditInfo', 'context-menu-showAuditInfo', 'Show audit info', 'eye-o'), + new TreeNodeContextMenuModel('addGroupMember', 'context-menu-addGroupMember', 'Add group members', 'plus'), + new TreeNodeContextMenuModel('delete', 'context-menu-delete', 'Delete', 'trash-o'), + new TreeNodeContextMenuModel('remove', 'context-menu-remove', 'Remove', 'trash-o'), + new TreeNodeContextMenuModel('undoDelete', 'context-menu-undoDelete', 'Undo Delete', 'undo-delete') + ]; + } + + + /******************************************************************* + delete or remove all service child's on delete existing service + *******************************************************************/ + deleteActionService(nodes : ITreeNode[], serviceModelId : string){ + if(!_.isNil(nodes)){ + for(let node of nodes){ + node.data = node; + if(!_.isNil(node.children)){ + node.children.map((child)=>{ + child.data = child; + child.parent = node; + }); + } + + let menuActionsName : string = node.data.action === ServiceInstanceActions.Create ? 'remove' : 'delete'; + if(!_.isNil(node.data.menuActions) && !_.isNil(node.data.menuActions[menuActionsName])){ + node.data.menuActions[menuActionsName]['method'](node, serviceModelId) + } + + } + } + } + /******************************************************************* + undo delete all service child's on undo delete existing service + *******************************************************************/ + undoDeleteActionService(nodes : ITreeNode[], serviceModelId : string){ + if(!_.isNil(nodes)){ + for(let node of nodes){ + node.data = node; + if(!_.isNil(node.children)){ + node.children.map((child)=>{ + child.data = child; + child.parent = node; + }); + } + + if(!_.isNil(node.data.menuActions) && !_.isNil(node.data.menuActions['undoDelete'])){ + node.data.menuActions['undoDelete']['method'](node, serviceModelId) + } + } + } + } + + /*********************************************************** + return true if should add line hover the instance name + ***********************************************************/ + isTextDecoration(node) : boolean{ + return !_.isNil(node.data) && !_.isNil(node.data.action) && node.data.action.split('_').pop() === 'Delete'; + } + + + /****************************************** + should create object of instances action + ******************************************/ + generateServiceActionObject(nodes){ + let obj = {}; + let index = 0; + for(let node of nodes){ + obj[index] = {}; + index++; + } + } +} + +export class TreeNodeContextMenuModel { + methodName: string; + dataTestId: string; + label: string; + iconClass: string; + + constructor(methodName: string, + dataTestId: string, + label: string, + iconClass: string) { + this.methodName = methodName; + this.dataTestId = dataTestId; + this.label = label; + this.iconClass = iconClass; + } +} diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/instance.tree.generator.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/instance.tree.generator.ts new file mode 100644 index 000000000..188feaaba --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/instance.tree.generator.ts @@ -0,0 +1,8 @@ +import {Injectable} from "@angular/core"; + +@Injectable() +export class InstanceTreeGenerator { + convertServiceInstanceToTreeData(serviceInstance, serviceModelId : string) { + + } +} diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.html b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.html new file mode 100644 index 000000000..dae6762e0 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.html @@ -0,0 +1,5 @@ +<div class="status-properties" *ngFor="let prop of properties"> + <span class="status-property-name" [ngClass]="{'mark': prop.key=='In-maintenance'}">{{ prop.key }}</span> + <span class="status-property-value" [attr.data-tests-id]="'status-property-'+prop?.testId" >{{ prop.value }}</span> + <span class="separator">|</span> +</div> diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.scss b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.scss new file mode 100644 index 000000000..305de8cd7 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.scss @@ -0,0 +1,24 @@ +.status-properties{ + display: inline-block; + font-family: OpenSans-Regular; + font-size: 12px; + padding-top: 3px; + .status-property-name{ + color: #5A5A5A; + &.mark{ + background-color: #959595; + color: #ffffff; + } + } + .status-property-value{ + color: #191919; + } + .separator{ + padding: 0 8px; + } + &:last-child .separator{ + display: none; + } + +} + diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.spec.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.spec.ts new file mode 100644 index 000000000..047f2e89d --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.spec.ts @@ -0,0 +1,25 @@ +import {ComponentFixture, TestBed } from '@angular/core/testing'; +import { TreeNodeHeaderPropertiesComponent } from './tree-node-header-properties.component'; + +describe('TreeNodeHeaderPropertiesComponent', () => { + let component: TreeNodeHeaderPropertiesComponent; + let fixture: ComponentFixture<TreeNodeHeaderPropertiesComponent>; + + + beforeAll(done => (async () => { + TestBed.configureTestingModule({ + declarations: [ TreeNodeHeaderPropertiesComponent ] + }); + await TestBed.compileComponents(); + + fixture = TestBed.createComponent(TreeNodeHeaderPropertiesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + })().then(done).catch(done.fail)); + + + test('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.ts new file mode 100644 index 000000000..535d7ea52 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/drawing-board-tree/tree-node-header-properties/tree-node-header-properties.component.ts @@ -0,0 +1,10 @@ +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'tree-node-header-properties', + templateUrl: './tree-node-header-properties.component.html', + styleUrls: ['./tree-node-header-properties.component.scss'] +}) +export class TreeNodeHeaderPropertiesComponent { + @Input() properties : object[] = []; +} |