From 0bcc2fc62132faf36aece0eabb9fcacc226c459a Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Mon, 9 Apr 2018 16:34:27 -0400 Subject: [PATCH 1/8] WINDUP-1895: Fix performance with extremely large #s of packages --- .../windup/web/services/model/Package.java | 16 +- ui/src/main/webapp/css/windup-web.css | 180 +++++++++--------- ui/src/main/webapp/package.json | 1 + .../analysis-context-form.component.ts | 1 + .../package-registry.service.ts | 10 +- ui/src/main/webapp/src/app/app.module.ts | 3 + .../webapp/src/app/project/project.module.ts | 1 - .../application-details.component.ts | 4 +- .../js-tree-angular-wrapper.component.html | 2 +- .../js-tree-angular-wrapper.component.ts | 140 ++------------ .../webapp/src/app/shared/shared.module.ts | 4 + .../services/package-registry.service.spec.ts | 22 +-- ui/src/main/webapp/yarn.lock | 24 ++- 13 files changed, 164 insertions(+), 244 deletions(-) diff --git a/model/src/main/java/org/jboss/windup/web/services/model/Package.java b/model/src/main/java/org/jboss/windup/web/services/model/Package.java index 4d9692349..ba286f53b 100644 --- a/model/src/main/java/org/jboss/windup/web/services/model/Package.java +++ b/model/src/main/java/org/jboss/windup/web/services/model/Package.java @@ -46,11 +46,11 @@ public class Package implements Serializable private Package parent; @OneToMany(fetch = FetchType.EAGER, mappedBy = "parent") - private Set childs; + private Set children; public Package() { - this.childs = new HashSet<>(); + this.children = new HashSet<>(); } /** @@ -61,7 +61,7 @@ public Package() public Package(String name) { this.name = name; - this.childs = new HashSet<>(); + this.children = new HashSet<>(); } /** @@ -74,7 +74,7 @@ public Package(String partialName, String fullName) { this.name = partialName; this.fullName = fullName; - this.childs = new HashSet<>(); + this.children = new HashSet<>(); } public Long getId() @@ -163,9 +163,9 @@ public void setParent(Package parent) this.parent = parent; } - public Collection getChilds() + public Collection getChildren() { - return childs; + return children; } /** @@ -175,7 +175,7 @@ public Collection getChilds() */ public void addChild(Package child) { - this.childs.add(child); + this.children.add(child); } /** @@ -185,7 +185,7 @@ public void addChild(Package child) */ public void removeChild(Package child) { - this.childs.remove(child); + this.children.remove(child); } diff --git a/ui/src/main/webapp/css/windup-web.css b/ui/src/main/webapp/css/windup-web.css index c67e7f9e2..ed8eecf21 100644 --- a/ui/src/main/webapp/css/windup-web.css +++ b/ui/src/main/webapp/css/windup-web.css @@ -1,161 +1,161 @@ .nav-pf-vertical { - width: 240px; + width: 240px; } .nav-pf-vertical .list-group-item > a { - width: 240px; + width: 240px; } .form-errors { - margin-bottom: 10px; + margin-bottom: 10px; } .layout-pf.layout-pf-fixed body { - padding-top: unset; + padding-top: unset; } .container-fluid { - padding-top: 60px; + padding-top: 60px; } .wizard-content .container-fluid { - padding-top: 0px; + padding-top: 0px; } table.datatable, table.dataTable { - height: unset; + height: unset; } .pointer { - cursor: pointer; + cursor: pointer; } .page-header-no-border { - border: 0 none; - margin-bottom: 0; + border: 0 none; + margin-bottom: 0; } .page-header h1 { - margin: 15px; + margin: 15px; } .page-header h1 span.slash { - color: #555; + color: #555; } .page-header h1 { - padding-left: 12pt; + padding-left: 12pt; } .page-header h1 div.main { - border-bottom: 1px solid #add8e6; - color: #005387; - margin: 0 6em 0.5ex 0; - width: auto; + border-bottom: 1px solid #add8e6; + color: #005387; + margin: 0 6em 0.5ex 0; + width: auto; } .page-header div.desc, div.tooltipLikeMessage { - background: beige none repeat scroll 0 0; - border: 1px solid #888; - color: black; - display: inline-block; - font-size: 12pt; - font-weight: normal; - padding: 1ex 1em; + background: beige none repeat scroll 0 0; + border: 1px solid #888; + color: black; + display: inline-block; + font-size: 12pt; + font-weight: normal; + padding: 1ex 1em; } .page-header div.desc { - margin: 0 15px 15px 30px; + margin: 0 15px 15px 30px; } div.tooltipLikeMessage { - margin: 15px 0 0; + margin: 15px 0 0; } .page-header div.desc::before, div.tooltipLikeMessage::before { - color: gray; - content: ""; - cursor: help; - display: inline-block; - font-family: "Glyphicons Halflings"; - font-size: 12pt; - font-style: normal; - font-weight: normal; - line-height: 1; - padding: 0.3ex 0.3em; - position: relative; - top: 1px; - vertical-align: top; + color: gray; + content: ""; + cursor: help; + display: inline-block; + font-family: "Glyphicons Halflings"; + font-size: 12pt; + font-style: normal; + font-weight: normal; + line-height: 1; + padding: 0.3ex 0.3em; + position: relative; + top: 1px; + vertical-align: top; } .windupPieGraph div.legend td.legendLabel { - padding-left: 0.6ex; + padding-left: 0.6ex; } .clickable { - cursor: pointer; + cursor: pointer; } .label-info { - background-color: #696969; + background-color: #696969; } .label-danger { - background-color: #f04124; + background-color: #f04124; } .welcome-help-text { - padding-top: 15px; + padding-top: 15px; } th { - font-size: 12px; + font-size: 12px; } td { - font-size: 12px; + font-size: 12px; } .external-link::before { - content: '\F2D2'; - font: normal normal normal 14px/1 FontAwesome; - font-size: 12px; - padding-right: 3px; + content: '\F2D2'; + font: normal normal normal 14px/1 FontAwesome; + font-size: 12px; + padding-right: 3px; } .container-fluid .wu-frame { - background: #e8e8e8; - margin-left: 20px !important; - margin-right: 20px !important; - margin-bottom: 2px !important; + background: #e8e8e8; + margin-left: 20px !important; + margin-right: 20px !important; + margin-bottom: 2px !important; - padding-top: 0; /* override */ + padding-top: 0; /* override */ } form .wizard-form { - padding-left: 10px; - padding-right: 10px; + padding-left: 10px; + padding-right: 10px; } label.required:before { - content: '*'; - position: relative; - left: -2ex; - float: left; - width: 0; + content: '*'; + position: relative; + left: -2ex; + float: left; + width: 0; } .btn { - text-transform: capitalize; + text-transform: capitalize; } fieldset.fields-section-pf { - border-color: #adaaaa; + border-color: #adaaaa; } .form-horizontal .form-group { - margin-left: 10px; - margin-right: 10px; + margin-left: 10px; + margin-right: 10px; } .wu-horizontal-nav-content { -/* margin-top: 122px; */ - padding-top: 122px; - margin-top: 0; + /* margin-top: 122px; */ + padding-top: 122px; + margin-top: 0; } /* @@ -164,38 +164,42 @@ fieldset.fields-section-pf { } */ @media (max-width: 767px) { - .wu-horizontal-nav-content, .layout-pf.layout-pf-fixed .container-pf-nav-pf-vertical { - margin-left: 0; - margin-top: initial; - } + .wu-horizontal-nav-content, .layout-pf.layout-pf-fixed .container-pf-nav-pf-vertical { + margin-left: 0; + margin-top: initial; + } - .container-fluid { - padding-top: initial; - } + .container-fluid { + padding-top: initial; + } } @media (min-width: 768px) and (max-width: 1599px) { - .nav-pf-vertical .list-group-item > a { - width: 240px; - } + .nav-pf-vertical .list-group-item > a { + width: 240px; + } - .wu-horizontal-nav-content, .layout-pf.layout-pf-fixed .container-pf-nav-pf-vertical { - margin-left: 240px; - } + .wu-horizontal-nav-content, .layout-pf.layout-pf-fixed .container-pf-nav-pf-vertical { + margin-left: 240px; + } } @media (min-width: 1599px) { - .wu-horizontal-nav-content, .layout-pf.layout-pf-fixed .container-pf-nav-pf-vertical { - margin-left: 240px; - } + .wu-horizontal-nav-content, .layout-pf.layout-pf-fixed .container-pf-nav-pf-vertical { + margin-left: 240px; + } } /* Pushes notifications area a bit down from the horizontal navigation bar. */ .notifications-row { - padding-top: 24px; + padding-top: 24px; } .alert { - margin-top: 20px; - margin-bottom: 0px; + margin-top: 20px; + margin-bottom: 0px; } + + +@import '~angular-tree-component/dist/angular-tree-component.css'; + diff --git a/ui/src/main/webapp/package.json b/ui/src/main/webapp/package.json index e1ffa4620..587723350 100644 --- a/ui/src/main/webapp/package.json +++ b/ui/src/main/webapp/package.json @@ -40,6 +40,7 @@ "@angular/router": "^4.1.2", "@swimlane/ngx-charts": "^5.2.0", "angular-router-loader": "^0.6.0", + "angular-tree-component": "^7.1.0", "angular2-moment": "^1.3.3", "bootstrap": "3.3.7", "bootstrap-datepicker": "1.6.4", diff --git a/ui/src/main/webapp/src/app/analysis-context/analysis-context-form.component.ts b/ui/src/main/webapp/src/app/analysis-context/analysis-context-form.component.ts index 078643cc1..3b39c0c6a 100644 --- a/ui/src/main/webapp/src/app/analysis-context/analysis-context-form.component.ts +++ b/ui/src/main/webapp/src/app/analysis-context/analysis-context-form.component.ts @@ -263,6 +263,7 @@ export class AnalysisContextFormComponent extends FormComponent */ forkJoin(registeredPackagesObservables).subscribe((packageMetadataArray: PackageMetadata[]) => { + console.log("package metadata array: ", packageMetadataArray); let arrayOfRoots = [].concat(...packageMetadataArray.map((singlePackageMetadata) => singlePackageMetadata.packageTree)); let mergedRoots = this._packageRegistryService.mergePackageRoots(arrayOfRoots); mergedRoots.forEach(singleRoot => this._packageRegistryService.putHierarchy(singleRoot)); diff --git a/ui/src/main/webapp/src/app/analysis-context/package-registry.service.ts b/ui/src/main/webapp/src/app/analysis-context/package-registry.service.ts index 56813ece9..9c16fabd0 100644 --- a/ui/src/main/webapp/src/app/analysis-context/package-registry.service.ts +++ b/ui/src/main/webapp/src/app/analysis-context/package-registry.service.ts @@ -24,8 +24,8 @@ export class PackageRegistryService { public putHierarchy(aPackage: Package) { this.put(aPackage); - if (aPackage.childs) { - aPackage.childs.forEach(child => this.putHierarchy(child)); + if (aPackage.children) { + aPackage.children.forEach(child => this.putHierarchy(child)); } } @@ -60,17 +60,17 @@ export class PackageRegistryService { protected mergePackageHierarchy(aPackage: Package, packageMap: Map, parentPackage: Package = null) { let packageInMap: Package = null; - let childPackages = aPackage.childs; + let childPackages = aPackage.children; if (!packageMap.has(aPackage.fullName)) { packageInMap = Object.assign({}, aPackage); // clone object packageMap.set(aPackage.fullName, packageInMap); if (parentPackage) { - parentPackage.childs.push(packageInMap); + parentPackage.children.push(packageInMap); } - packageInMap.childs = []; + packageInMap.children = []; } else { // some magic packageInMap = packageMap.get(aPackage.fullName); diff --git a/ui/src/main/webapp/src/app/app.module.ts b/ui/src/main/webapp/src/app/app.module.ts index ce8fff224..17fcd8f68 100644 --- a/ui/src/main/webapp/src/app/app.module.ts +++ b/ui/src/main/webapp/src/app/app.module.ts @@ -35,6 +35,7 @@ import {CoreModule} from "./core/core.module"; import {ExecutionsModule} from "./executions/executions.module"; import {FileUploaderWrapper} from "./shared/upload/file-uploader-wrapper.service"; import {KeycloakService} from "./core/authentication/keycloak.service"; +import {TreeModule} from "angular-tree-component"; /** * Load all mapping data from the generated files. @@ -53,6 +54,8 @@ initializeModelMappingData(); // Moment MomentModule, + TreeModule, + CoreModule, SharedModule, ProjectModule, diff --git a/ui/src/main/webapp/src/app/project/project.module.ts b/ui/src/main/webapp/src/app/project/project.module.ts index a64ba86fd..967090769 100644 --- a/ui/src/main/webapp/src/app/project/project.module.ts +++ b/ui/src/main/webapp/src/app/project/project.module.ts @@ -7,7 +7,6 @@ import {ProjectListComponent} from "./project-list.component"; import {MigrationProjectService} from "./migration-project.service"; import {ProjectResolve} from "./project.resolve"; import {SharedModule} from "../shared/shared.module"; -import {ExecutionsModule} from "../executions/executions.module"; import {ProjectLayoutComponent} from "./project-layout.component"; @NgModule({ diff --git a/ui/src/main/webapp/src/app/reports/application-details/application-details.component.ts b/ui/src/main/webapp/src/app/reports/application-details/application-details.component.ts index 89d7f48c9..507f338f7 100644 --- a/ui/src/main/webapp/src/app/reports/application-details/application-details.component.ts +++ b/ui/src/main/webapp/src/app/reports/application-details/application-details.component.ts @@ -101,13 +101,13 @@ export class ApplicationDetailsComponent extends FilterableReportComponent imple let newTreeData:TreeData = { id: traversal.id, name: traversal.path, - childs: [], + children: [], opened: true, data: traversal.canonicalID }; if (parentTreeData) { - parentTreeData.childs.push(newTreeData); + parentTreeData.children.push(newTreeData); } else { this.applicationTree = this.applicationTree.concat(newTreeData); } diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html index 7c89b545c..0d4d1e8d0 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html @@ -1 +1 @@ -
+ diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts index 8a3a16edb..33fe0a2f8 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts @@ -1,21 +1,16 @@ import { - Component, OnInit, Input, ElementRef, SimpleChange, Output, EventEmitter, NgZone, - OnChanges, OnDestroy + Component, OnInit, Input, Output, EventEmitter, OnDestroy, ViewEncapsulation } from "@angular/core"; -import {Package} from "../generated/windup-services"; -import * as $ from "jquery"; -import 'jstree'; -import {SchedulerService} from "./scheduler.service"; /** - * Wrapper for jstree from: https://www.jstree.com/ + * Wrapper for angular tree from: https://angular2-tree.readme.io/ */ @Component({ templateUrl: './js-tree-angular-wrapper.component.html', selector: 'wu-js-tree-wrapper', - host: { 'style': 'display: block; overflow: auto;' } + //host: { 'style': 'display: block; overflow: auto;' } }) -export class JsTreeAngularWrapperComponent implements OnInit, OnChanges, OnDestroy { +export class JsTreeAngularWrapperComponent implements OnInit, OnDestroy { @Input() treeNodes: TreeData[]; @@ -24,8 +19,6 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnChanges, OnDestr treeNodesMap: {[id: string]: TreeData} = {}; - jsTree = []; - @Input() selectedNodes: TreeData[]; @@ -35,131 +28,30 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnChanges, OnDestr @Output() nodeClicked: EventEmitter = new EventEmitter(); - protected element; - - protected updateSelectionCallback: Function = () => {}; - protected static EMPTY_CALLBACK = () => {}; - - protected treeRedrawTimeout: any; - - public constructor(element: ElementRef, private _zone: NgZone, private _schedulerService: SchedulerService) { - this.element = element.nativeElement; - } - - ngOnChanges(changes: {[treeNodes: string]: SimpleChange}): any { - let jsTree = $(this.element).jstree(true); - - // This is ugly workaround to prevent recursively calling ngOnChanges from change handler - this.updateSelectionCallback = JsTreeAngularWrapperComponent.EMPTY_CALLBACK; - - if (jsTree) { - if (changes.hasOwnProperty('treeNodes')) { - let newTreeNodes: Package[] = changes['treeNodes'].currentValue; - this.jsTree = newTreeNodes.map((node) => this.transformTreeNode(node)); - (jsTree as any).settings.core.data = this.jsTree; - jsTree.redraw(true); - jsTree.refresh(true, false); - } - - if (changes.hasOwnProperty('selectedNodes')) { - // Another ugly workaround, now to give enough time to initialize jsTree first - this._schedulerService.setTimeout(this._zone.run(() => this.redrawSelection()), 100); - } - } - - // This is ugly workaround to prevent recursively calling ngOnChanges from change handler - this.updateSelectionCallback = this.updateSelectedNodes; - } - - transformTreeNode(node: any): any { - let transformed = { - id: node.id, - text: node.name, - children: [], - original: node, - state: {} - }; - - if (node.opened) { - transformed.state = { - opened: node.opened - } - } - - let self = this; + options = { + displayField: 'name', + isExpandedField: 'expanded', + hasChildrenField: 'children', + animateExpand: true + }; - if (node.childs) { - transformed.children = node.childs.map((mapNode) => self.transformTreeNode(mapNode)); - } - - this.treeNodesMap[node.id] = node; - - return transformed; + public constructor() { + setInterval(() => { + console.log("Tree data: ", this.treeNodes); + }, 10000); } ngOnInit() { - let self = this; - - if (this.treeNodes) { - this.jsTree = this.treeNodes.map((node) => self.transformTreeNode(node)); - } - - let plugins = this.hasCheckboxes ? ['checkbox'] : []; - plugins.push('sort'); - - $(this.element).jstree({ - 'plugins': plugins, - 'core': { - data: this.jsTree - }, - 'checkbox': { - 'tie_selection': false, - 'cascade': 'undetermined+down', - 'three_state': false - } - }); - - $(this.element).on('check_node.jstree uncheck_node.jstree', (event, data) => this.updateSelectionCallback(event, data)); - $(this.element).on('select_node.jstree', (event, data) => this.fireNodeClicked(event, data)); - $(this.element).on('changed.jstree loaded.jstree', (event, data) => this.redrawSelection()); } ngOnDestroy(): void { - if (this.treeRedrawTimeout) { - this._schedulerService.clearTimeout(this.treeRedrawTimeout); - this.treeRedrawTimeout = null; - } - } - - fireNodeClicked(event, data) { - this.nodeClicked.emit(this.treeNodesMap[data.node.id]); - } - - updateSelectedNodes(event, data) { - let jsTree = $(this.element).jstree(true); - - if (jsTree) { - this._zone.run(() => { - this.selectedNodes = jsTree.get_checked(false).map((id) => this.treeNodesMap[id]); - this.selectedNodesChange.emit(this.selectedNodes); - }); - } - } - - redrawSelection() { - let jsTree = $(this.element).jstree(true); - - if (jsTree && this.selectedNodes) { - let selectionIds = this.selectedNodes.map(node => node.id); - jsTree.check_node(selectionIds, null); - } } } export interface TreeData { id: number, name: string, - childs: TreeData[], + children: TreeData[], data: any, - opened: boolean + opened: boolean, } diff --git a/ui/src/main/webapp/src/app/shared/shared.module.ts b/ui/src/main/webapp/src/app/shared/shared.module.ts index 705804662..632ba1f73 100644 --- a/ui/src/main/webapp/src/app/shared/shared.module.ts +++ b/ui/src/main/webapp/src/app/shared/shared.module.ts @@ -62,6 +62,7 @@ import {FilterPipe} from "./filter/filter.pipe"; import {TableComponent} from "./table/table.component"; import {TableSortHeaderComponent} from "./table/table-sort-header.component"; import {TablePanelComponent} from "./table/table-panel.component"; +import {TreeModule} from "angular-tree-component"; @NgModule({ imports: [ @@ -72,6 +73,9 @@ import {TablePanelComponent} from "./table/table-panel.component"; ChosenModule, FileUploadModule, MomentModule, + + // Angular Tree + TreeModule, ], providers: [ BreadCrumbsService, diff --git a/ui/src/main/webapp/tests/app/services/package-registry.service.spec.ts b/ui/src/main/webapp/tests/app/services/package-registry.service.spec.ts index 3abec1403..d9a7a0c0a 100644 --- a/ui/src/main/webapp/tests/app/services/package-registry.service.spec.ts +++ b/ui/src/main/webapp/tests/app/services/package-registry.service.spec.ts @@ -16,7 +16,7 @@ describe("PackageRegistryService", () => { name: name, fullName: fullName, countClasses: 1, - childs: [], + children: [], level: level }; }; @@ -34,7 +34,7 @@ describe("PackageRegistryService", () => { expect(result[0].name).toBe('root'); expect(result[0].fullName).toBe('org.jboss.root'); expect(result[0].level).toBe(0); - expect(result[0].childs.length).toBe(0); + expect(result[0].children.length).toBe(0); expect(result[0].countClasses).toBe(2); }); @@ -58,7 +58,7 @@ describe("PackageRegistryService", () => { let secondPackage: Package = createPackage('root', 'org.jboss.root'); let packages = [ firstPackage, secondPackage ]; - packages.forEach(aPackage => aPackage.childs = [commonChild]); + packages.forEach(aPackage => aPackage.children = [commonChild]); let result = instance.mergePackageRoots(packages); @@ -68,24 +68,24 @@ describe("PackageRegistryService", () => { expect(result[0].level).toBe(0); expect(result[0].countClasses).toBe(2); - expect(result[0].childs.length).toBe(1); + expect(result[0].children.length).toBe(1); - let child = result[0].childs[0]; + let child = result[0].children[0]; expect(child.name).toBe(commonChild.name); expect(child.fullName).toBe(commonChild.fullName); expect(child.level).toBe(commonChild.level); - expect(child.childs.length).toBe(0); + expect(child.children.length).toBe(0); expect(child.countClasses).toBe(commonChild.countClasses * 2); }); it('should add different children', () => { let firstPackage: Package = createPackage('root', 'org.jboss.root'); - firstPackage.childs = [ + firstPackage.children = [ createPackage('firstOneChild', 'org.jboss.root.firstOneChild', 1) ]; let secondPackage: Package = createPackage('root', 'org.jboss.root'); - secondPackage.childs = [ + secondPackage.children = [ createPackage('secondChild', 'org.jboss.root.secondChild', 1) ]; @@ -99,9 +99,9 @@ describe("PackageRegistryService", () => { expect(result[0].level).toBe(0); expect(result[0].countClasses).toBe(2); - expect(result[0].childs.length).toBe(2); - expect(result[0].childs).toContain(firstPackage.childs[0]); - expect(result[0].childs).toContain(secondPackage.childs[0]); + expect(result[0].children.length).toBe(2); + expect(result[0].children).toContain(firstPackage.children[0]); + expect(result[0].children).toContain(secondPackage.children[0]); }); }); }); diff --git a/ui/src/main/webapp/yarn.lock b/ui/src/main/webapp/yarn.lock index cc90f004a..fc13f8e11 100644 --- a/ui/src/main/webapp/yarn.lock +++ b/ui/src/main/webapp/yarn.lock @@ -401,6 +401,14 @@ angular-router-loader@^0.6.0: dependencies: loader-utils "^1.0.2" +angular-tree-component@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/angular-tree-component/-/angular-tree-component-7.1.0.tgz#53674ea944f7147647c7e48931f5fad66237a632" + dependencies: + lodash "^4.17.5" + mobx "^3.6.2" + mobx-angular "2.1.1" + angular2-moment@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/angular2-moment/-/angular2-moment-1.3.3.tgz#569c433bbfa2448d5424f0e10dce6f8c8c9533eb" @@ -3832,6 +3840,10 @@ lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lo version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" +lodash@^4.17.5: + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" + lodash@~4.16.4: version "4.16.6" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777" @@ -4050,6 +4062,14 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd dependencies: minimist "0.0.8" +mobx-angular@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/mobx-angular/-/mobx-angular-2.1.1.tgz#d5e36539acb200186dd5a1170806b4776b9a8b88" + +mobx@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.6.2.tgz#fb9f5ff5090539a1ad54e75dc4c098b602693320" + moment@^2.16.0: version "2.17.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" @@ -4109,10 +4129,6 @@ ng2-file-upload@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ng2-file-upload/-/ng2-file-upload-1.2.1.tgz#5563c5dfd6f43fbfbe815c206e343464a0a6a197" -ng2-slim-loading-bar@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/ng2-slim-loading-bar/-/ng2-slim-loading-bar-4.0.0.tgz#7256fdde7c058f14955a3ef2e65afafb5b664603" - no-case@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.1.tgz#7aeba1c73a52184265554b7dc03baf720df80081" From 48f51460a81f1a7613dd4e2adc71c422abd8b269 Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Mon, 9 Apr 2018 17:10:37 -0400 Subject: [PATCH 2/8] Commented out some debug code --- .../src/app/shared/js-tree-angular-wrapper.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts index 33fe0a2f8..4d65ec59d 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts @@ -36,9 +36,9 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnDestroy { }; public constructor() { - setInterval(() => { - console.log("Tree data: ", this.treeNodes); - }, 10000); + // setInterval(() => { + // console.log("Tree data: ", this.treeNodes); + // }, 10000); } ngOnInit() { From 1dd720753e67aff537b049821a04cad4aba77547 Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Mon, 9 Apr 2018 18:02:25 -0400 Subject: [PATCH 3/8] Started working on selection code --- .../js-tree-angular-wrapper.component.html | 7 +++- .../js-tree-angular-wrapper.component.ts | 32 ++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html index 0d4d1e8d0..540b0e669 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html @@ -1 +1,6 @@ - + diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts index 4d65ec59d..21946d23a 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts @@ -8,17 +8,21 @@ import { @Component({ templateUrl: './js-tree-angular-wrapper.component.html', selector: 'wu-js-tree-wrapper', - //host: { 'style': 'display: block; overflow: auto;' } + host: { 'style': 'display: block; overflow: auto;' } }) export class JsTreeAngularWrapperComponent implements OnInit, OnDestroy { + + treeNodesFiltered: TreeData[]; + @Input() - treeNodes: TreeData[]; + set treeNodes(inputData:TreeData[]) { + this.treeNodesFiltered = inputData.filter(value => value.name != null && value.name != ""); + console.log("Tree nodes filtered: ", this.treeNodesFiltered); + }; @Input() hasCheckboxes: boolean = true; - treeNodesMap: {[id: string]: TreeData} = {}; - @Input() selectedNodes: TreeData[]; @@ -30,9 +34,10 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnDestroy { options = { displayField: 'name', - isExpandedField: 'expanded', - hasChildrenField: 'children', - animateExpand: true + useCheckbox: true, + useTriState: false, + animateExpand: true, + nodeHeight: 22, }; public constructor() { @@ -44,7 +49,18 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnDestroy { ngOnInit() { } - ngOnDestroy(): void { + ngOnDestroy() { + } + + selected(event) { + this.selectedNodes.push(event.node.data); + console.log("Selected: ", this.selectedNodes); + } + + deselected(event) { + console.log("Deselected event: ", event); + this.selectedNodes = this.selectedNodes.filter((item) => item.id != event.node.data.id); + console.log("De-Selected: ", this.selectedNodes); } } From a19282da0ab42cd73656e969a7c47628e46c77ea Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Tue, 10 Apr 2018 22:17:46 -0400 Subject: [PATCH 4/8] WINDUP-1895: Implemented basic sorting and selection --- .../analysis-context-form.component.html | 4 +- .../js-tree-angular-wrapper.component.html | 2 + .../js-tree-angular-wrapper.component.ts | 69 ++++++++++++++++--- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/ui/src/main/webapp/src/app/analysis-context/analysis-context-form.component.html b/ui/src/main/webapp/src/app/analysis-context/analysis-context-form.component.html index 6541e2a88..5c949053b 100644 --- a/ui/src/main/webapp/src/app/analysis-context/analysis-context-form.component.html +++ b/ui/src/main/webapp/src/app/analysis-context/analysis-context-form.component.html @@ -104,8 +104,8 @@

Loading...

Identifying packages...

- - +

All classes in the selected packages will be ignored during analysis.

diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html index 540b0e669..2e8c8907f 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html @@ -1,6 +1,8 @@ diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts index 21946d23a..fad2c1f57 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts @@ -1,6 +1,8 @@ import { - Component, OnInit, Input, Output, EventEmitter, OnDestroy, ViewEncapsulation + Component, OnInit, Input, Output, EventEmitter, OnDestroy, ViewEncapsulation, ViewChild, AfterViewInit, OnChanges, + SimpleChanges } from "@angular/core"; +import {ITreeState, TreeComponent} from "angular-tree-component"; /** * Wrapper for angular tree from: https://angular2-tree.readme.io/ @@ -10,21 +12,63 @@ import { selector: 'wu-js-tree-wrapper', host: { 'style': 'display: block; overflow: auto;' } }) -export class JsTreeAngularWrapperComponent implements OnInit, OnDestroy { +export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnDestroy { + + @ViewChild('tree') + treeComponent: TreeComponent; + + treeState: ITreeState = { + selectedNodeIds: {}, + expandedNodeIds: {}, + hiddenNodeIds: {}, + activeNodeIds: {} + }; treeNodesFiltered: TreeData[]; @Input() set treeNodes(inputData:TreeData[]) { - this.treeNodesFiltered = inputData.filter(value => value.name != null && value.name != ""); - console.log("Tree nodes filtered: ", this.treeNodesFiltered); + let sortFunc = (item1, item2) => { + return item1.name.localeCompare(item2.name); + }; + + let crawlAndSortChildren = (inputData:TreeData[]) => { + if (!inputData) + return inputData; + + inputData.forEach(child => { + child.children = crawlAndSortChildren(child.children) + }); + + return inputData.sort(sortFunc); + }; + + this.treeNodesFiltered = crawlAndSortChildren(inputData.filter(value => value.name != null && value.name != "").sort(sortFunc)); }; @Input() hasCheckboxes: boolean = true; + _selectedNodes: TreeData[]; + @Input() - selectedNodes: TreeData[]; + set selectedNodes (newSelectedNodes: TreeData[]) { + this._selectedNodes = newSelectedNodes; + + const selectedLeafNodeIds = {}; + + this._selectedNodes.forEach(selectedNode => { + selectedLeafNodeIds[selectedNode.id] = true; + }); + + this.treeState.selectedLeafNodeIds = selectedLeafNodeIds; + this.treeComponent.treeModel.setState(this.treeState); + + } + + get selectedNodes (): TreeData[] { + return this._selectedNodes; + } @Output() selectedNodesChange: EventEmitter = new EventEmitter(); @@ -35,7 +79,7 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnDestroy { options = { displayField: 'name', useCheckbox: true, - useTriState: false, + useTriState: true, animateExpand: true, nodeHeight: 22, }; @@ -46,6 +90,9 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnDestroy { // }, 10000); } + ngAfterViewInit() { + } + ngOnInit() { } @@ -53,14 +100,16 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnDestroy { } selected(event) { - this.selectedNodes.push(event.node.data); - console.log("Selected: ", this.selectedNodes); + this._selectedNodes.push(event.node.data); + console.log("Selected: ", this._selectedNodes); + this.selectedNodesChange.emit(this._selectedNodes); } deselected(event) { console.log("Deselected event: ", event); - this.selectedNodes = this.selectedNodes.filter((item) => item.id != event.node.data.id); - console.log("De-Selected: ", this.selectedNodes); + this._selectedNodes = this._selectedNodes.filter((item) => item.id != event.node.data.id); + this.selectedNodesChange.emit(this._selectedNodes); + console.log("De-Selected: ", this._selectedNodes); } } From 10789a02a494fe2a1d3123623cc457fca9c1bd05 Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Wed, 11 Apr 2018 17:52:26 -0400 Subject: [PATCH 5/8] Updated to handle large application more gracefully --- .../analysis-context.service.ts | 9 +- .../js-tree-angular-wrapper.component.html | 18 +-- .../js-tree-angular-wrapper.component.ts | 118 +++++++++++++++--- ui/src/main/webapp/yarn.lock | 6 +- 4 files changed, 122 insertions(+), 29 deletions(-) diff --git a/ui/src/main/webapp/src/app/analysis-context/analysis-context.service.ts b/ui/src/main/webapp/src/app/analysis-context/analysis-context.service.ts index cc9e021c4..7cb56c5c0 100644 --- a/ui/src/main/webapp/src/app/analysis-context/analysis-context.service.ts +++ b/ui/src/main/webapp/src/app/analysis-context/analysis-context.service.ts @@ -33,7 +33,14 @@ export class AnalysisContextService extends AbstractService { * @returns {Observable} */ saveAsDefault(analysisContext: AnalysisContext, project: MigrationProject): Observable { - let body = JSON.stringify(analysisContext); + let body = JSON.stringify(analysisContext, (key, value) => { + // This works around the cases where we store the parent as part of the value. + // It would be a circular reference without this. + if (key == "parent") + return undefined; + + return value; + }); let url = Constants.REST_BASE + this.CREATE_URL.replace('{projectId}', project.id.toString()); return this._http.put(url, body, this.JSON_OPTIONS) diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html index 2e8c8907f..a2d1fd0bb 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html @@ -1,8 +1,10 @@ - +
+ +
\ No newline at end of file diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts index fad2c1f57..184e11b0f 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts @@ -1,8 +1,8 @@ import { - Component, OnInit, Input, Output, EventEmitter, OnDestroy, ViewEncapsulation, ViewChild, AfterViewInit, OnChanges, + Component, OnInit, Input, Output, EventEmitter, OnDestroy, ViewChild, AfterViewInit, SimpleChanges } from "@angular/core"; -import {ITreeState, TreeComponent} from "angular-tree-component"; +import {ITreeState, TreeComponent, TreeNode} from "angular-tree-component"; /** * Wrapper for angular tree from: https://angular2-tree.readme.io/ @@ -24,45 +24,61 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD activeNodeIds: {} }; - treeNodesFiltered: TreeData[]; + treeNodesFiltered: TreeDataExtended[]; @Input() set treeNodes(inputData:TreeData[]) { + console.log("Tree nodes set:", inputData); + // Sort alphabetically let sortFunc = (item1, item2) => { return item1.name.localeCompare(item2.name); }; - let crawlAndSortChildren = (inputData:TreeData[]) => { + // Sort all children, and also attach the parent node + let crawlAndSortChildren = (parent:TreeDataExtended, inputData:TreeData[]):TreeDataExtended[] => { if (!inputData) - return inputData; + return inputData; inputData.forEach(child => { - child.children = crawlAndSortChildren(child.children) + let childExtended = child; + childExtended.parent = parent; + childExtended.hasChildren = !!(child.children && child.children.length); + child.children = crawlAndSortChildren(childExtended, child.children) }); - return inputData.sort(sortFunc); + return inputData.sort(sortFunc).map(treeData => treeData); }; - this.treeNodesFiltered = crawlAndSortChildren(inputData.filter(value => value.name != null && value.name != "").sort(sortFunc)); + // Filter out any empty root nodes, sort, and convert for display purposes + this.treeNodesFiltered = crawlAndSortChildren(null, inputData.filter(value => value.name != null && value.name != "").sort(sortFunc)); + + // Make sure to reset selections so that parent and child relationships are right + this.selectedNodes = this._selectedNodes; }; @Input() hasCheckboxes: boolean = true; - _selectedNodes: TreeData[]; + _selectedNodes: TreeDataExtended[]; @Input() set selectedNodes (newSelectedNodes: TreeData[]) { - this._selectedNodes = newSelectedNodes; + this._selectedNodes = newSelectedNodes; + console.log("Reselecting nodes: ", this.treeState.selectedLeafNodeIds); const selectedLeafNodeIds = {}; - this._selectedNodes.forEach(selectedNode => { - selectedLeafNodeIds[selectedNode.id] = true; - }); + if (this._selectedNodes) { + this._selectedNodes.forEach(selectedNode => { + selectedLeafNodeIds[selectedNode.id] = true; + + this.addParentSelections(selectedLeafNodeIds, selectedNode.id); + }); + } this.treeState.selectedLeafNodeIds = selectedLeafNodeIds; this.treeComponent.treeModel.setState(this.treeState); + console.log("Reselected nodes: ", selectedLeafNodeIds); } @@ -79,9 +95,12 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD options = { displayField: 'name', useCheckbox: true, - useTriState: true, + useTriState: false, animateExpand: true, nodeHeight: 22, + hasChildrenField: 'hasChildren', + childrenField: 'childNodes', + getChildren: (node:TreeNode) => node.data.children }; public constructor() { @@ -99,18 +118,87 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD ngOnDestroy() { } + private addParentSelections(selectedLeafNodeIds:{}, nodeId:number) { + console.log("parent add searching for: " + nodeId); + // 1. Find node from the main set of nodes by id + let finder = (originalNodes:TreeDataExtended[]) => { + let result = null; + + if (!originalNodes) + return null; + + originalNodes.forEach(originalNode => { + if (result) + return; + + if (originalNode.id == nodeId) { + result = originalNode; + return; + } + + result = finder(originalNode.children); + }); + + return result; + }; + + let originalNode = finder(this.treeNodesFiltered); + console.log("Original node: ", originalNode); + + while (originalNode) { + selectedLeafNodeIds[originalNode.id] = true; + originalNode = originalNode.parent; + } + + console.log("Add parent set them to: ", selectedLeafNodeIds); + } + selected(event) { this._selectedNodes.push(event.node.data); + + // This just triggers the setter method to be called + this.selectedNodes = this._selectedNodes; console.log("Selected: ", this._selectedNodes); + this.selectedNodes = this._selectedNodes; this.selectedNodesChange.emit(this._selectedNodes); } deselected(event) { console.log("Deselected event: ", event); - this._selectedNodes = this._selectedNodes.filter((item) => item.id != event.node.data.id); + let nodesToDeselect = []; + let crawlNode = (data:TreeData) => { + if (!data) + return; + + nodesToDeselect.push(data.id); + + data.children.forEach((child) => { + crawlNode(child); + }); + }; + crawlNode(event.node.data); + console.log("Should deselect: ", nodesToDeselect); + + console.log("Current: ", this._selectedNodes); + this._selectedNodes = this._selectedNodes.filter((item) => { + let index = nodesToDeselect.indexOf(item.id); + console.log("Item: ", item, index); + return index == -1; + }); + console.log("Then: ", this._selectedNodes); + + // This just triggers the setter method to be called + this.selectedNodes = this._selectedNodes; this.selectedNodesChange.emit(this._selectedNodes); console.log("De-Selected: ", this._selectedNodes); } + + +} + +export interface TreeDataExtended extends TreeData { + hasChildren: boolean, + parent: TreeDataExtended, } export interface TreeData { diff --git a/ui/src/main/webapp/yarn.lock b/ui/src/main/webapp/yarn.lock index fc13f8e11..b71fd9107 100644 --- a/ui/src/main/webapp/yarn.lock +++ b/ui/src/main/webapp/yarn.lock @@ -3836,11 +3836,7 @@ lodash@^3.8.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.0: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - -lodash@^4.17.5: +lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" From 45bf0210ea5df902677cbff5e928d574d6456f24 Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Fri, 13 Apr 2018 12:36:10 -0400 Subject: [PATCH 6/8] Commented out excessive logging --- .../js-tree-angular-wrapper.component.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts index 184e11b0f..898d2acb2 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts @@ -28,7 +28,7 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD @Input() set treeNodes(inputData:TreeData[]) { - console.log("Tree nodes set:", inputData); + //console.log("Tree nodes set:", inputData); // Sort alphabetically let sortFunc = (item1, item2) => { return item1.name.localeCompare(item2.name); @@ -64,7 +64,7 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD @Input() set selectedNodes (newSelectedNodes: TreeData[]) { this._selectedNodes = newSelectedNodes; - console.log("Reselecting nodes: ", this.treeState.selectedLeafNodeIds); + //console.log("Reselecting nodes: ", this.treeState.selectedLeafNodeIds); const selectedLeafNodeIds = {}; @@ -78,7 +78,7 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD this.treeState.selectedLeafNodeIds = selectedLeafNodeIds; this.treeComponent.treeModel.setState(this.treeState); - console.log("Reselected nodes: ", selectedLeafNodeIds); + //console.log("Reselected nodes: ", selectedLeafNodeIds); } @@ -119,7 +119,7 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD } private addParentSelections(selectedLeafNodeIds:{}, nodeId:number) { - console.log("parent add searching for: " + nodeId); + //console.log("parent add searching for: " + nodeId); // 1. Find node from the main set of nodes by id let finder = (originalNodes:TreeDataExtended[]) => { let result = null; @@ -143,14 +143,14 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD }; let originalNode = finder(this.treeNodesFiltered); - console.log("Original node: ", originalNode); + //console.log("Original node: ", originalNode); while (originalNode) { selectedLeafNodeIds[originalNode.id] = true; originalNode = originalNode.parent; } - console.log("Add parent set them to: ", selectedLeafNodeIds); + //console.log("Add parent set them to: ", selectedLeafNodeIds); } selected(event) { @@ -158,13 +158,13 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD // This just triggers the setter method to be called this.selectedNodes = this._selectedNodes; - console.log("Selected: ", this._selectedNodes); + //console.log("Selected: ", this._selectedNodes); this.selectedNodes = this._selectedNodes; this.selectedNodesChange.emit(this._selectedNodes); } deselected(event) { - console.log("Deselected event: ", event); + //console.log("Deselected event: ", event); let nodesToDeselect = []; let crawlNode = (data:TreeData) => { if (!data) @@ -177,20 +177,20 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD }); }; crawlNode(event.node.data); - console.log("Should deselect: ", nodesToDeselect); + //console.log("Should deselect: ", nodesToDeselect); - console.log("Current: ", this._selectedNodes); + //console.log("Current: ", this._selectedNodes); this._selectedNodes = this._selectedNodes.filter((item) => { let index = nodesToDeselect.indexOf(item.id); - console.log("Item: ", item, index); + //console.log("Item: ", item, index); return index == -1; }); - console.log("Then: ", this._selectedNodes); + //console.log("Then: ", this._selectedNodes); // This just triggers the setter method to be called this.selectedNodes = this._selectedNodes; this.selectedNodesChange.emit(this._selectedNodes); - console.log("De-Selected: ", this._selectedNodes); + //console.log("De-Selected: ", this._selectedNodes); } From 3687ae2e16f121019727493dfdb35f9c4f5034b9 Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Tue, 1 May 2018 21:27:56 -0400 Subject: [PATCH 7/8] Display a warning if the item cannot really be unselected --- .../js-tree-angular-wrapper.component.html | 35 +++++++++- .../js-tree-angular-wrapper.component.ts | 70 +++++++++++++------ 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html index a2d1fd0bb..26776894c 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html @@ -1,3 +1,12 @@ +
+

Already selected:

+
+
+ {{selected.name}} +
+
+

End already selected

+
+ > + +
+ + +
+ + +
+
+
+ +
\ No newline at end of file diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts index 898d2acb2..72d336782 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, Input, Output, EventEmitter, OnDestroy, ViewChild, AfterViewInit, - SimpleChanges + SimpleChanges, ApplicationRef } from "@angular/core"; import {ITreeState, TreeComponent, TreeNode} from "angular-tree-component"; @@ -73,6 +73,7 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD selectedLeafNodeIds[selectedNode.id] = true; this.addParentSelections(selectedLeafNodeIds, selectedNode.id); + this.addChildSelections(selectedLeafNodeIds, selectedNode.id); }); } @@ -103,7 +104,7 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD getChildren: (node:TreeNode) => node.data.children }; - public constructor() { + public constructor(public applicationRef:ApplicationRef) { // setInterval(() => { // console.log("Tree data: ", this.treeNodes); // }, 10000); @@ -118,31 +119,48 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD ngOnDestroy() { } - private addParentSelections(selectedLeafNodeIds:{}, nodeId:number) { - //console.log("parent add searching for: " + nodeId); - // 1. Find node from the main set of nodes by id - let finder = (originalNodes:TreeDataExtended[]) => { - let result = null; + private findNode(originalNodes:TreeDataExtended[], nodeId:number):TreeDataExtended { + let result = null; - if (!originalNodes) - return null; + if (!originalNodes) + return null; - originalNodes.forEach(originalNode => { - if (result) - return; + originalNodes.forEach(originalNode => { + if (result) + return; - if (originalNode.id == nodeId) { - result = originalNode; - return; - } + if (originalNode.id == nodeId) { + result = originalNode; + return; + } - result = finder(originalNode.children); - }); + result = this.findNode(originalNode.children, nodeId); + }); + + return result; + } + + private addChildSelections(selectedLeafNodeIds:{}, nodeId:number) { + let originalNode = this.findNode(this.treeNodesFiltered, nodeId); + if (!originalNode) + return; + + + let selectorFunction = (node:TreeDataExtended) => { + selectedLeafNodeIds[node.id] = true; - return result; + if (!node.hasChildren) + return; + + node.children.forEach(childNode => { + selectorFunction(childNode); + }); }; + selectorFunction(originalNode); + } - let originalNode = finder(this.treeNodesFiltered); + private addParentSelections(selectedLeafNodeIds:{}, nodeId:number) { + let originalNode = this.findNode(this.treeNodesFiltered, nodeId); //console.log("Original node: ", originalNode); while (originalNode) { @@ -179,6 +197,17 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD crawlNode(event.node.data); //console.log("Should deselect: ", nodesToDeselect); + let indexOfExistingNode = this._selectedNodes.findIndex(selectedNode => { + console.log("this selected node:", selectedNode, event); + return selectedNode.id == event.node.id; + }); + + if (indexOfExistingNode == -1) + { + window.alert("This cannot be unselected until all parent nodes are unselected!"); + return; + } + //console.log("Current: ", this._selectedNodes); this._selectedNodes = this._selectedNodes.filter((item) => { let index = nodesToDeselect.indexOf(item.id); @@ -190,6 +219,7 @@ export class JsTreeAngularWrapperComponent implements AfterViewInit, OnInit, OnD // This just triggers the setter method to be called this.selectedNodes = this._selectedNodes; this.selectedNodesChange.emit(this._selectedNodes); + this.applicationRef.tick(); //console.log("De-Selected: ", this._selectedNodes); } From 259fb36428d3ceae8819e7f8bd9e34645796f2ac Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Wed, 2 May 2018 16:44:37 -0400 Subject: [PATCH 8/8] Removed some debugging code --- .../js-tree-angular-wrapper.component.html | 9 --------- .../js-tree-angular-wrapper.component.ts | 19 +++++++++---------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html index 26776894c..0337e65de 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.html @@ -1,12 +1,3 @@ -
-

Already selected:

-
-
- {{selected.name}} -
-
-

End already selected

-
{ + let index = nodesToDeselect.indexOf(item.id); + //console.log("Item: ", item, index); + return index == -1; + }); + //console.log("Then: ", this._selectedNodes); } - //console.log("Current: ", this._selectedNodes); - this._selectedNodes = this._selectedNodes.filter((item) => { - let index = nodesToDeselect.indexOf(item.id); - //console.log("Item: ", item, index); - return index == -1; - }); - //console.log("Then: ", this._selectedNodes); - // This just triggers the setter method to be called this.selectedNodes = this._selectedNodes; this.selectedNodesChange.emit(this._selectedNodes); - this.applicationRef.tick(); //console.log("De-Selected: ", this._selectedNodes); }