-
-
+
+
+
+
+
+
+
+ {{ node.name }}
-
-
-
-
+
+
+
+
+
+
+
+
+ {{ node.name }}
+
+
+
+
+
+
diff --git a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.scss b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.scss
index afb8d863..1f74f884 100644
--- a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.scss
+++ b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.scss
@@ -1,6 +1,6 @@
@charset 'utf-8';
.organization-tree {
- padding:10px;
+ padding: 10px;
ul,
li {
margin-top: 0;
@@ -13,83 +13,83 @@
}
}
-.tree-node-frame{
- li{
- .path{
- .horizontal-line{
- display:none;
+.tree-node-frame {
+ li {
+ .path {
+ .horizontal-line {
+ display: none;
}
}
}
.mat-tree-node {
min-height: 30px;
font-size: 13px;
- padding-left:20px;
- margin-top:4px;
- &:hover {
- background-color: #f4f4f4;
- border:1px solid #cccccc;
- border-radius:4px;
- box-shadow: 0 1px 4px rgba(32, 33, 36, 0.1);
+ padding-left: 20px;
+ margin-top: 4px;
+ &:hover {
+ background-color: #f4f4f4;
+ border: 1px solid #cccccc;
+ border-radius: 4px;
+ box-shadow: 0 1px 4px rgba(32, 33, 36, 0.1);
}
}
}
-ul .tree-node-frame li .path > .horizontal-line{
- display:inline-block;
+ul .tree-node-frame li .path > .horizontal-line {
+ display: inline-block;
}
-.boxnone{
- position:relative;
- .vertical-line{
- background: rgba(189,189,189,.4);
+.boxnone {
+ position: relative;
+ .vertical-line {
+ background: rgba(189, 189, 189, 0.4);
bottom: 6px;
display: block;
position: absolute;
top: 0px;
width: 2px;
}
- .mat-nested-tree-node:last-child{
- padding-bottom:10px;
+ .mat-tree-node:last-child {
+ padding-bottom: 10px;
}
}
.path {
padding: 6px 4px;
- + ul{
- li:last-chlid{
- border-bottom:1px solid #dddddd;
+ + ul {
+ li:last-chlid {
+ border-bottom: 1px solid #dddddd;
}
}
- .horizontal-line{
- width:10px;
- height:1px;
+ .horizontal-line {
+ width: 10px;
+ height: 1px;
background-color: #dddddd;
- display:inline-block;
+ display: inline-block;
vertical-align: middle;
- margin-left:-10px;
+ margin-left: -10px;
}
- .mat-icon-button{
+ .mat-icon-button {
padding: 0;
min-width: 0;
width: 20px;
height: 20px;
flex-shrink: 0;
line-height: 20px;
- .mat-icon-rtl-mirror{
+ .mat-icon-rtl-mirror {
border: 1px solid #dddddd;
padding: 2px;
font-size: 14px;
min-width: 14px;
min-height: 14px;
line-height: 14px;
- width:20px;
- height:20px;
+ width: 20px;
+ height: 20px;
box-shadow: 0 2px 1px rgba(48, 48, 48, 0.2);
border-radius: 50%;
}
}
- .dept-name{
- padding-left:10px;
+ .dept-name {
+ padding-left: 10px;
}
}
diff --git a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.ts b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.ts
index 3af7dc58..37d54111 100644
--- a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.ts
+++ b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.ts
@@ -8,39 +8,26 @@ import {
EventEmitter,
AfterViewInit
} from '@angular/core';
-import { MatTreeNestedDataSource, MatTree } from '@angular/material';
-import { NestedTreeControl } from '@angular/cdk/tree';
-import { BehaviorSubject } from 'rxjs';
+import { MatTreeFlattener, MatTree } from '@angular/material';
import { NGXLogger } from 'ngx-logger';
import { DeptInfo } from '@ucap-webmessenger/protocol-query';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
+import { FlatTreeControl } from '@angular/cdk/tree';
+import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
+import { VirtualScrollTreeFlatDataSource } from '@ucap-webmessenger/ui';
-export class OraganizationNode {
- private childNodeBehaviorSubject: BehaviorSubject
;
- private childNodeList: OraganizationNode[];
+interface OrganizationNode {
+ deptInfo: DeptInfo;
+ name: string;
+ children?: OrganizationNode[];
+}
- get title(): string {
- return this.deptInfo.name;
- }
-
- get children(): BehaviorSubject {
- if (!this.childNodeBehaviorSubject) {
- this.childNodeBehaviorSubject = new BehaviorSubject(
- undefined === this.childNodeList ? [] : this.childNodeList
- );
- }
- return this.childNodeBehaviorSubject;
- }
-
- constructor(public deptInfo: DeptInfo) {}
-
- addChild(childNode: OraganizationNode) {
- if (!this.childNodeList) {
- this.childNodeList = [];
- }
- this.childNodeList.push(childNode);
- }
+/** Flat node with expandable and level information */
+interface FlatNode {
+ expandable: boolean;
+ name: string;
+ level: number;
}
@Component({
@@ -55,87 +42,96 @@ export class TreeComponent implements OnInit, AfterViewInit {
@Input()
loginRes: LoginResponse;
@Input()
- set oraganizationList(deptInfo: DeptInfo[]) {
- const nodeMap = new Map();
- const rootNodeList: OraganizationNode[] = [];
- const remainChildNodeList: OraganizationNode[] = [];
- let myNode: OraganizationNode;
+ set oraganizationList(deptInfoList: DeptInfo[]) {
+ if (!deptInfoList || 0 === deptInfoList.length) {
+ return;
+ }
+ const nodeMap = new Map();
+ const rootNodeList: OrganizationNode[] = [];
+ const remainChildNodeList: OrganizationNode[] = [];
+ let myNode: OrganizationNode;
- deptInfo.forEach(value => {
- const node = new OraganizationNode(value);
- if (nodeMap.has(value.seq)) {
- this.logger.warn('duplicate seq', value.seq);
+ deptInfoList.forEach(deptInfo => {
+ const node: OrganizationNode = {
+ deptInfo,
+ name: deptInfo.name,
+ children: []
+ };
+ if (nodeMap.has(deptInfo.seq)) {
+ this.logger.warn('duplicate seq', deptInfo.seq);
return;
}
- nodeMap.set(value.seq, node);
+ nodeMap.set(deptInfo.seq, node);
- if (value.seq === this.loginRes.departmentCode) {
+ if (deptInfo.seq === this.loginRes.departmentCode) {
myNode = node;
}
- if (0 === value.parentSeq) {
+ if (0 === deptInfo.parentSeq) {
rootNodeList.push(node);
return;
}
- if (nodeMap.has(value.parentSeq)) {
- nodeMap.get(value.parentSeq).addChild(node);
+ if (nodeMap.has(deptInfo.parentSeq)) {
+ nodeMap.get(deptInfo.parentSeq).children.push(node);
} else {
remainChildNodeList.push(node);
}
});
- remainChildNodeList.forEach(value => {
- if (nodeMap.has(value.deptInfo.parentSeq)) {
- nodeMap.get(value.deptInfo.parentSeq).addChild(value);
+ remainChildNodeList.forEach(node => {
+ if (nodeMap.has(node.deptInfo.parentSeq)) {
+ nodeMap.get(node.deptInfo.parentSeq).children.push(node);
}
});
-
this.dataSource.data = rootNodeList;
- // console.log('myNode', myNode);
- // console.log('this.dataSource.data', this.dataSource.data[0]);
- // this.treeControl.expandDescendants(this.dataSource.data[2]);
- // console.log('this.dataSource', this.dataSource);
- // console.log('this.dataSource.data', this.dataSource.data);
- // console.log('this.treeControl', this.treeControl);
- // console.log('this.treeControl.dataNodes', this.treeControl.dataNodes);
-
- // const myNode = this.treeControl.dataNodes.filter(
- // node => node.deptInfo.seq === this.loginRes.departmentCode
- // );
- // if (!!myNode && myNode.length > 0) {
- // this.treeControl.expand(myNode[0]);
- // }
}
- @ViewChild('orgranizationTree', { static: true })
- orgranizationTree: MatTree;
+ @ViewChild('cvsvOrganization', { static: false })
+ cvsvOrganization: CdkVirtualScrollViewport;
- levels = new Map();
- treeControl: NestedTreeControl;
+ @ViewChild('orgranizationTree', { static: false })
+ orgranizationTree: MatTree;
- dataSource: MatTreeNestedDataSource;
+ treeControl: FlatTreeControl;
+ treeFlattener: MatTreeFlattener;
+ dataSource: VirtualScrollTreeFlatDataSource;
constructor(
private changeDetectorRef: ChangeDetectorRef,
private logger: NGXLogger
) {
- this.treeControl = new NestedTreeControl(
- this.getChildren
+ this.treeControl = new FlatTreeControl(
+ node => node.level,
+ node => node.expandable
);
- this.dataSource = new MatTreeNestedDataSource();
+ this.treeFlattener = new MatTreeFlattener(
+ (node: OrganizationNode, level: number) => {
+ return {
+ expandable: !!node.children && node.children.length > 0,
+ name: node.name,
+ level
+ };
+ },
+ node => node.level,
+ node => node.expandable,
+ node => node.children
+ );
+ this.dataSource = new VirtualScrollTreeFlatDataSource<
+ OrganizationNode,
+ FlatNode
+ >(this.treeControl, this.treeFlattener);
}
ngOnInit() {}
- ngAfterViewInit(): void {}
+ ngAfterViewInit(): void {
+ this.dataSource.cdkVirtualScrollViewport = this.cvsvOrganization;
+ }
- getChildren = (node: OraganizationNode) => node.children;
+ hasChild = (_: number, node: FlatNode) => node.expandable;
- hasChildren = (index: number, node: OraganizationNode) =>
- 0 < node.children.value.length;
-
- onClickNode(node: OraganizationNode) {
+ onClickNode(node: OrganizationNode) {
this.selected.emit(node.deptInfo);
}
}
diff --git a/projects/ucap-webmessenger-ui-organization/src/lib/ucap-ui-organization.module.ts b/projects/ucap-webmessenger-ui-organization/src/lib/ucap-ui-organization.module.ts
index 14ee7887..e4cb4074 100644
--- a/projects/ucap-webmessenger-ui-organization/src/lib/ucap-ui-organization.module.ts
+++ b/projects/ucap-webmessenger-ui-organization/src/lib/ucap-ui-organization.module.ts
@@ -2,6 +2,8 @@ import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
+import { FlexLayoutModule } from '@angular/flex-layout';
+
import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatButtonModule } from '@angular/material/button';
@@ -21,6 +23,7 @@ const SERVICES = [];
CommonModule,
ReactiveFormsModule,
+ FlexLayoutModule,
ScrollingModule,
MatButtonModule,
diff --git a/projects/ucap-webmessenger-ui/src/lib/data-source/virtual-scroll-tree-flat.data-source.ts b/projects/ucap-webmessenger-ui/src/lib/data-source/virtual-scroll-tree-flat.data-source.ts
new file mode 100644
index 00000000..c2f66074
--- /dev/null
+++ b/projects/ucap-webmessenger-ui/src/lib/data-source/virtual-scroll-tree-flat.data-source.ts
@@ -0,0 +1,126 @@
+import { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { FlatTreeControl } from '@angular/cdk/tree';
+import {
+ BehaviorSubject,
+ merge,
+ Observable,
+ Subject,
+ Subscription
+} from 'rxjs';
+import { map, share } from 'rxjs/operators';
+import { MatTreeFlattener } from '@angular/material';
+import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
+
+export class VirtualScrollTreeFlatDataSource extends DataSource {
+ private flattenedDataSubject = new BehaviorSubject([]);
+
+ private expandedDataSubject = new BehaviorSubject([]);
+ expandedData$: Observable;
+
+ private connectSubject: Subject;
+ private dataChangeSubscription: Subscription;
+ private rangeChangeSubscription: Subscription;
+
+ private rangeSubject: BehaviorSubject<{
+ start: number;
+ end: number;
+ }>;
+
+ // tslint:disable-next-line: variable-name
+ private _cdkVirtualScrollViewport: CdkVirtualScrollViewport;
+ set cdkVirtualScrollViewport(
+ cdkVirtualScrollViewport: CdkVirtualScrollViewport
+ ) {
+ if (!cdkVirtualScrollViewport) {
+ return;
+ }
+ this._cdkVirtualScrollViewport = cdkVirtualScrollViewport;
+ this.rangeSubject = new BehaviorSubject<{ start: number; end: number }>({
+ start: 0,
+ end: 1
+ });
+
+ this.rangeChangeSubscription = cdkVirtualScrollViewport.renderedRangeStream.subscribe(
+ range => {
+ this.rangeSubject.next({
+ start: range.start,
+ end: range.end
+ });
+ if (!!this.connectSubject) {
+ this.connectSubject.next(
+ this.expandedDataSubject.value.slice(
+ this.rangeSubject.value.start,
+ this.rangeSubject.value.end
+ )
+ );
+ }
+ }
+ );
+ }
+
+ // tslint:disable-next-line: variable-name
+ private _data: BehaviorSubject;
+ get data() {
+ return this._data.value;
+ }
+ set data(value: T[]) {
+ this._data.next(value);
+ this.flattenedDataSubject.next(this.treeFlattener.flattenNodes(this.data));
+ this.treeControl.dataNodes = this.flattenedDataSubject.value;
+ }
+
+ constructor(
+ private treeControl: FlatTreeControl,
+ private treeFlattener: MatTreeFlattener,
+ initialData: T[] = []
+ ) {
+ super();
+ this._data = new BehaviorSubject(initialData);
+ this.expandedData$ = this.expandedDataSubject.asObservable().pipe(share());
+ }
+
+ connect(collectionViewer: CollectionViewer): Observable {
+ this.connectSubject = new Subject();
+
+ this.dataChangeSubscription = merge(
+ collectionViewer.viewChange,
+ this.treeControl.expansionModel.changed,
+ this.flattenedDataSubject
+ )
+ .pipe(
+ map(() => {
+ this.expandedDataSubject.next(
+ this.treeFlattener.expandFlattenedNodes(
+ this.flattenedDataSubject.value,
+ this.treeControl
+ )
+ );
+
+ return !this.rangeSubject
+ ? this.expandedDataSubject.value
+ : this.expandedDataSubject.value.slice(
+ this.rangeSubject.value.start,
+ this.rangeSubject.value.end
+ );
+ })
+ )
+ .subscribe(datas => {
+ this.connectSubject.next(datas);
+ if (!!this._cdkVirtualScrollViewport) {
+ this._cdkVirtualScrollViewport.checkViewportSize();
+ }
+ });
+
+ return this.connectSubject.asObservable();
+ }
+
+ disconnect() {
+ if (!!this.dataChangeSubscription) {
+ this.dataChangeSubscription.unsubscribe();
+ }
+
+ if (!!this.rangeChangeSubscription) {
+ this.rangeChangeSubscription.unsubscribe();
+ }
+ }
+}
diff --git a/projects/ucap-webmessenger-ui/src/public-api.ts b/projects/ucap-webmessenger-ui/src/public-api.ts
index 50a0fdd2..39f04b1a 100644
--- a/projects/ucap-webmessenger-ui/src/public-api.ts
+++ b/projects/ucap-webmessenger-ui/src/public-api.ts
@@ -13,6 +13,8 @@ export * from './lib/components/file-upload-queue.component';
export * from './lib/components/file-viewer.component';
export * from './lib/components/float-action-button.component';
+export * from './lib/data-source/virtual-scroll-tree-flat.data-source';
+
export * from './lib/dialogs/alert.dialog.component';
export * from './lib/dialogs/confirm.dialog.component';