sync
This commit is contained in:
parent
b03f36b99a
commit
aee13b8a4d
924
package-lock.json
generated
924
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -182,10 +182,10 @@
|
|||
"@ucap/ng-store-chat": "file:pack/ucap-ng-store-chat-0.0.5.tgz",
|
||||
"@ucap/ng-store-group": "file:pack/ucap-ng-store-group-0.0.6.tgz",
|
||||
"@ucap/ng-store-organization": "file:pack/ucap-ng-store-organization-0.0.4.tgz",
|
||||
"@ucap/ng-ui": "file:pack/ucap-ng-ui-0.0.4.tgz",
|
||||
"@ucap/ng-ui-authentication": "file:pack/ucap-ng-ui-authentication-0.0.16.tgz",
|
||||
"@ucap/ng-ui-group": "file:pack/ucap-ng-ui-group-0.0.4.tgz",
|
||||
"@ucap/ng-ui-organization": "file:pack/ucap-ng-ui-organization-0.0.2.tgz",
|
||||
"@ucap/ng-ui": "file:pack/ucap-ng-ui-0.0.5.tgz",
|
||||
"@ucap/ng-ui-authentication": "file:pack/ucap-ng-ui-authentication-0.0.19.tgz",
|
||||
"@ucap/ng-ui-group": "file:pack/ucap-ng-ui-group-0.0.27.tgz",
|
||||
"@ucap/ng-ui-organization": "file:pack/ucap-ng-ui-organization-0.0.12.tgz",
|
||||
"@ucap/ng-ui-skin-default": "file:pack/ucap-ng-ui-skin-default-0.0.1.tgz",
|
||||
"@ucap/ng-web-socket": "file:pack/ucap-ng-web-socket-0.0.2.tgz",
|
||||
"@ucap/ng-web-storage": "file:pack/ucap-ng-web-storage-0.0.3.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-ui-authentication",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.19",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
|
|
@ -10,17 +10,18 @@
|
|||
<mat-select
|
||||
[formControl]="companyCodeFormControl"
|
||||
[value]="companyCode || fixedCompanyCode"
|
||||
placeholder="{{ 'login.labels.selectCompany' | ucapI18n }}"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let company of companyList"
|
||||
[value]="company.companyCode"
|
||||
>{{ company.companyName }}
|
||||
>
|
||||
{{ company.companyName }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="input-lineless">
|
||||
12345
|
||||
<!--<span class="icon-img"><i class="mid mdi-account-tie-outline"></i></span>-->
|
||||
<span class="icon-img">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.82 22">
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"lib": {
|
||||
"entryFile": "src/public-api.ts",
|
||||
"umdModuleIds": {
|
||||
"ngx-perfect-scrollbar": "ngx-perfect-scrollbar",
|
||||
"@ucap/core": "@ucap/core",
|
||||
"@ucap/ng-ui": "@ucap/ng-ui"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-ui-group",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.27",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<div class="ucap-group-expansion-container" fxFlexFill>
|
||||
<cdk-virtual-scroll-viewport #cvsvList perfectScrollbar fxFlexFill>
|
||||
<ng-container
|
||||
*cdkVirtualFor="let node of dataSource.expandedData$"
|
||||
></ng-container>
|
||||
|
||||
<mat-tree #treeList [dataSource]="dataSource" [treeControl]="treeControl">
|
||||
<mat-tree-node
|
||||
*matTreeNodeDef="let node"
|
||||
[attr.node-type]="node?.nodeType"
|
||||
matRipple
|
||||
>
|
||||
<li>
|
||||
<div>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="nodeTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: node?.node }"
|
||||
></ng-container>
|
||||
</div>
|
||||
</li>
|
||||
</mat-tree-node>
|
||||
|
||||
<mat-tree-node
|
||||
*matTreeNodeDef="let node; when: isHeader"
|
||||
class="tree-node-frame ucap-clickable"
|
||||
[attr.node-type]="node?.nodeType"
|
||||
matRipple
|
||||
>
|
||||
<li class="tree-node-header" matTreeNodeToggle>
|
||||
<div class="path">
|
||||
<button
|
||||
mat-icon-button
|
||||
[attr.aria-label]="'toggle '"
|
||||
class="btn-toggle"
|
||||
*ngIf="!checkable"
|
||||
>
|
||||
<mat-icon class="mat-icon-rtl-mirror">
|
||||
{{
|
||||
treeControl.isExpanded(node) ? 'expand_less' : 'expand_more'
|
||||
}}
|
||||
</mat-icon>
|
||||
</button>
|
||||
<div class="group-info">
|
||||
<ng-container [ngSwitch]="node.nodeType">
|
||||
<ng-container
|
||||
*ngSwitchCase="NodeType.Favorite"
|
||||
[ngTemplateOutlet]="favoriteHeaderTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: node?.node }"
|
||||
>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngSwitchCase="NodeType.Default"
|
||||
[ngTemplateOutlet]="defaultHeaderTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: node?.node }"
|
||||
>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngSwitchCase="NodeType.Buddy"
|
||||
[ngTemplateOutlet]="buddyHeaderTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: node?.node }"
|
||||
>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<mat-checkbox
|
||||
*ngIf="checkable"
|
||||
#checkbox
|
||||
[checked]="isCheckedGroup(node)"
|
||||
[disabled]="!isCheckableGroup(node)"
|
||||
(change)="onChangeCheckGroup(checkbox.checked, node)"
|
||||
(click)="$event.stopPropagation()"
|
||||
class="group-check"
|
||||
>
|
||||
</mat-checkbox>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="group-header-menu"
|
||||
*ngIf="!checkable"
|
||||
(click)="
|
||||
$event.stopPropagation(); onClickHeaderMenu($event, node)
|
||||
"
|
||||
>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<ul [class.group-tree-node-invisible]="!treeControl.isExpanded(node)">
|
||||
<div *ngIf="treeControl.isExpanded(node)" class="boxnone">
|
||||
<div class="vertical-line"></div>
|
||||
<ng-container matTreeNodeOutlet></ng-container>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</div>
|
|
@ -0,0 +1,26 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { ExpansionComponent } from './expansion.component';
|
||||
|
||||
describe('ucap::ui-group::ExpansionComponent', () => {
|
||||
let component: ExpansionComponent;
|
||||
let fixture: ComponentFixture<ExpansionComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ExpansionComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ExpansionComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import { action } from '@storybook/addon-actions';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
|
||||
import { ExpansionComponent } from './expansion.component';
|
||||
|
||||
export default {
|
||||
title: 'ExpansionComponent',
|
||||
component: ExpansionComponent
|
||||
};
|
||||
|
||||
export const Text = () => ({
|
||||
component: ExpansionComponent,
|
||||
props: {
|
||||
text: 'Hello ExpansionComponent'
|
||||
}
|
||||
});
|
327
projects/ui-group/src/lib/components/expansion.component.ts
Normal file
327
projects/ui-group/src/lib/components/expansion.component.ts
Normal file
|
@ -0,0 +1,327 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
ContentChild,
|
||||
TemplateRef,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Directive
|
||||
} from '@angular/core';
|
||||
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { FlatTreeControl } from '@angular/cdk/tree';
|
||||
|
||||
import { MatTreeFlattener, MatTree } from '@angular/material/tree';
|
||||
|
||||
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
|
||||
|
||||
import { VirtualScrollTreeFlatDataSource } from '@ucap/ng-ui';
|
||||
import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query';
|
||||
|
||||
export enum NodeType {
|
||||
None = 'None',
|
||||
Profile = 'Profile',
|
||||
Favorite = 'Favorite',
|
||||
Buddy = 'Buddy',
|
||||
Default = 'Default'
|
||||
}
|
||||
|
||||
export interface GroupNode {
|
||||
nodeType: NodeType;
|
||||
userInfo?: UserInfo;
|
||||
groupDetail?: GroupDetailData;
|
||||
children?: GroupNode[];
|
||||
}
|
||||
|
||||
export interface FlatNode {
|
||||
expandable: boolean;
|
||||
level: number;
|
||||
node: GroupNode;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ucapGroupExpansionNode]'
|
||||
})
|
||||
export class ExpansionNodeDirective {}
|
||||
|
||||
@Directive({
|
||||
selector: '[ucapGroupExpansionFavoriteHeader]'
|
||||
})
|
||||
export class ExpansionFavoriteHeaderDirective {}
|
||||
|
||||
@Directive({
|
||||
selector: '[ucapGroupExpansionDefaultHeader]'
|
||||
})
|
||||
export class ExpansionDefaultHeaderDirective {}
|
||||
|
||||
@Directive({
|
||||
selector: '[ucapGroupExpansionBuddyHeader]'
|
||||
})
|
||||
export class ExpansionBuddyHeaderDirective {}
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-group-expansion',
|
||||
templateUrl: './expansion.component.html',
|
||||
styleUrls: ['./expansion.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ExpansionComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
displayOrder: NodeType[] = [];
|
||||
|
||||
@Input()
|
||||
set profile(userInfo: UserInfo) {
|
||||
if (!userInfo) {
|
||||
this.nodeMap.set(NodeType.Profile, []);
|
||||
} else {
|
||||
const node: GroupNode = {
|
||||
nodeType: NodeType.Profile,
|
||||
userInfo,
|
||||
children: []
|
||||
};
|
||||
|
||||
this.nodeMap.set(NodeType.Profile, [node]);
|
||||
}
|
||||
|
||||
this.refreshNodes();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set favorites(userInfos: UserInfo[]) {
|
||||
if (!userInfos || 0 === userInfos.length) {
|
||||
this.nodeMap.set(NodeType.Favorite, []);
|
||||
} else {
|
||||
const node: GroupNode = {
|
||||
nodeType: NodeType.Favorite,
|
||||
groupDetail: {
|
||||
seq: -9999,
|
||||
name: NodeType.Favorite,
|
||||
isActive: true,
|
||||
userSeqs: userInfos.map((userInfo) => String(userInfo.seq))
|
||||
} as GroupDetailData,
|
||||
children: []
|
||||
};
|
||||
|
||||
userInfos.forEach((userInfo) => {
|
||||
node.children.push({
|
||||
nodeType: NodeType.Favorite,
|
||||
userInfo
|
||||
});
|
||||
});
|
||||
|
||||
this.nodeMap.set(NodeType.Favorite, [node]);
|
||||
}
|
||||
|
||||
this.refreshNodes();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set groupBuddies(list: { group: GroupDetailData; buddyList: UserInfo[] }[]) {
|
||||
if (!list || 0 === list.length) {
|
||||
this.nodeMap.set(NodeType.Default, []);
|
||||
this.nodeMap.set(NodeType.Buddy, []);
|
||||
} else {
|
||||
this.nodeMap.set(NodeType.Default, []);
|
||||
this.nodeMap.set(NodeType.Buddy, []);
|
||||
|
||||
for (const item of list) {
|
||||
let nodeType = NodeType.Buddy;
|
||||
if (0 === item.group.seq) {
|
||||
nodeType = NodeType.Default;
|
||||
}
|
||||
|
||||
const node: GroupNode = {
|
||||
nodeType,
|
||||
groupDetail: item.group,
|
||||
children: []
|
||||
};
|
||||
|
||||
item.buddyList.sort((a, b) =>
|
||||
a.order < b.order
|
||||
? -1
|
||||
: a.order > b.order
|
||||
? 1
|
||||
: a.name < b.name
|
||||
? -1
|
||||
: a.name > b.name
|
||||
? 1
|
||||
: 0
|
||||
);
|
||||
|
||||
item.buddyList.forEach((userInfo) => {
|
||||
node.children.push({
|
||||
nodeType,
|
||||
groupDetail: item.group,
|
||||
userInfo
|
||||
});
|
||||
});
|
||||
|
||||
if (NodeType.Buddy === nodeType) {
|
||||
this.nodeMap.get(NodeType.Buddy).push(node);
|
||||
} else {
|
||||
this.nodeMap.get(NodeType.Default).push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.refreshNodes();
|
||||
}
|
||||
|
||||
@Input()
|
||||
checkable = false;
|
||||
|
||||
@Input()
|
||||
selectedUserList?: (UserInfo | UserInfoSS | UserInfoF | UserInfoDN)[] = [];
|
||||
|
||||
@Input()
|
||||
unselectableUserList?: (
|
||||
| UserInfo
|
||||
| UserInfoSS
|
||||
| UserInfoF
|
||||
| UserInfoDN
|
||||
)[] = [];
|
||||
|
||||
@ViewChild('treeList', { static: false })
|
||||
treeList: MatTree<FlatNode>;
|
||||
|
||||
@ViewChild('cvsvList', { static: false })
|
||||
cvsvList: CdkVirtualScrollViewport;
|
||||
|
||||
@ContentChild(ExpansionNodeDirective, {
|
||||
read: TemplateRef,
|
||||
static: false
|
||||
})
|
||||
nodeTemplate: TemplateRef<ExpansionNodeDirective>;
|
||||
|
||||
@ContentChild(ExpansionFavoriteHeaderDirective, {
|
||||
read: TemplateRef,
|
||||
static: false
|
||||
})
|
||||
favoriteHeaderTemplate: TemplateRef<ExpansionFavoriteHeaderDirective>;
|
||||
|
||||
@ContentChild(ExpansionDefaultHeaderDirective, {
|
||||
read: TemplateRef,
|
||||
static: false
|
||||
})
|
||||
defaultHeaderTemplate: TemplateRef<ExpansionDefaultHeaderDirective>;
|
||||
|
||||
@ContentChild(ExpansionBuddyHeaderDirective, {
|
||||
read: TemplateRef,
|
||||
static: false
|
||||
})
|
||||
buddyHeaderTemplate: TemplateRef<ExpansionBuddyHeaderDirective>;
|
||||
|
||||
treeControl: FlatTreeControl<FlatNode>;
|
||||
treeFlattener: MatTreeFlattener<GroupNode, FlatNode>;
|
||||
dataSource: VirtualScrollTreeFlatDataSource<GroupNode, FlatNode>;
|
||||
|
||||
NodeType = NodeType;
|
||||
|
||||
private nodeMap: Map<NodeType, GroupNode[]> = new Map();
|
||||
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef) {
|
||||
this.treeControl = new FlatTreeControl<FlatNode>(
|
||||
(node) => node.level,
|
||||
(node) => node.expandable
|
||||
);
|
||||
|
||||
this.treeFlattener = new MatTreeFlattener<GroupNode, FlatNode>(
|
||||
(node: GroupNode, level: number) => {
|
||||
return {
|
||||
expandable: !!node.children && node.children.length > 0,
|
||||
level,
|
||||
nodeType: node.nodeType,
|
||||
node
|
||||
};
|
||||
},
|
||||
(node) => node.level,
|
||||
(node) => node.expandable,
|
||||
(node) => node.children
|
||||
);
|
||||
|
||||
this.dataSource = new VirtualScrollTreeFlatDataSource<GroupNode, FlatNode>(
|
||||
this.treeControl,
|
||||
this.treeFlattener
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dataSource.cdkVirtualScrollViewport = this.cvsvList;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
|
||||
onClickHeaderMenu(event: MouseEvent, node: FlatNode) {}
|
||||
|
||||
isCheckedGroup(node: FlatNode): boolean {
|
||||
const groupDetail = node.node.groupDetail;
|
||||
|
||||
if (!groupDetail || groupDetail === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (groupDetail.userSeqs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!!this.selectedUserList && this.selectedUserList.length > 0) {
|
||||
let allExist = true;
|
||||
groupDetail.userSeqs.some((seq) => {
|
||||
if (
|
||||
this.selectedUserList.filter((item) => item.seq === seq).length === 0
|
||||
) {
|
||||
allExist = false;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return allExist;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isCheckableGroup(node: FlatNode): boolean {
|
||||
if (!!this.unselectableUserList && this.unselectableUserList.length > 0) {
|
||||
const groupDetail = node.node.groupDetail;
|
||||
let allExist = true;
|
||||
groupDetail.userSeqs.some((seq) => {
|
||||
if (
|
||||
this.unselectableUserList.filter((item) => item.seq === seq)
|
||||
.length === 0
|
||||
) {
|
||||
allExist = false;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (allExist) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onChangeCheckGroup(value: boolean, node: FlatNode) {}
|
||||
|
||||
isHeader = (_: number, node: FlatNode) =>
|
||||
NodeType.Profile !== node.node.nodeType && 0 === node.level;
|
||||
|
||||
private refreshNodes() {
|
||||
const rootNode: GroupNode[] = [];
|
||||
|
||||
for (const nodeType of this.displayOrder) {
|
||||
const subNode = this.nodeMap.get(nodeType);
|
||||
if (!!subNode && 0 < subNode.length) {
|
||||
rootNode.push(...subNode);
|
||||
}
|
||||
}
|
||||
|
||||
this.dataSource.data = rootNode;
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
|
@ -16,12 +18,23 @@ import { UiModule } from '@ucap/ng-ui';
|
|||
import { ModuleConfig } from './config/module-config';
|
||||
import { _MODULE_CONFIG } from './config/token';
|
||||
|
||||
import { ExpansionListComponent } from './components/expansion-list.component';
|
||||
import {
|
||||
ExpansionComponent,
|
||||
ExpansionNodeDirective,
|
||||
ExpansionFavoriteHeaderDirective,
|
||||
ExpansionBuddyHeaderDirective,
|
||||
ExpansionDefaultHeaderDirective
|
||||
} from './components/expansion.component';
|
||||
|
||||
const COMPONENTS = [ExpansionListComponent];
|
||||
const COMPONENTS = [ExpansionComponent];
|
||||
const DIALOGS = [];
|
||||
const PIPES = [];
|
||||
const DIRECTIVES = [];
|
||||
const DIRECTIVES = [
|
||||
ExpansionNodeDirective,
|
||||
ExpansionFavoriteHeaderDirective,
|
||||
ExpansionBuddyHeaderDirective,
|
||||
ExpansionDefaultHeaderDirective
|
||||
];
|
||||
const SERVICES = [];
|
||||
|
||||
@NgModule({
|
||||
|
@ -34,6 +47,8 @@ export class GroupUiRootModule {}
|
|||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
FlexLayoutModule,
|
||||
ScrollingModule,
|
||||
|
||||
MatButtonModule,
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
export * from './lib/config/module-config';
|
||||
|
||||
export * from './lib/components/expansion-list.component';
|
||||
export * from './lib/components/expansion.component';
|
||||
|
||||
export * from './lib/group-ui.module';
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
"lib": {
|
||||
"entryFile": "src/public-api.ts",
|
||||
"umdModuleIds": {
|
||||
"ngx-perfect-scrollbar": "ngx-perfect-scrollbar",
|
||||
"@ucap/core": "@ucap/core",
|
||||
"@ucap/ng-logger": "@ucap/ng-logger",
|
||||
"@ucap/ng-ui": "@ucap/ng-ui"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-ui-organization",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.12",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<div class="ucap-organization-profile-list-item-container"></div>
|
|
@ -0,0 +1,26 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { ProfileListItemComponent } from './profile-list-item.component';
|
||||
|
||||
describe('ucap::ui-organization::ProfileListItemComponent', () => {
|
||||
let component: ProfileListItemComponent;
|
||||
let fixture: ComponentFixture<ProfileListItemComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProfileListItemComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProfileListItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import { action } from '@storybook/addon-actions';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
|
||||
import { ProfileListItemComponent } from './profile-list-item.component';
|
||||
|
||||
export default {
|
||||
title: 'ProfileListItemComponent',
|
||||
component: ProfileListItemComponent
|
||||
};
|
||||
|
||||
export const Text = () => ({
|
||||
component: ProfileListItemComponent,
|
||||
props: {
|
||||
text: 'Hello ProfileListItemComponent'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-organization-profile-list-item',
|
||||
templateUrl: './profile-list-item.component.html',
|
||||
styleUrls: ['./profile-list-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ProfileListItemComponent implements OnInit, OnDestroy {
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<div class="ucap-organization-tree-container" fxFlexFill>
|
||||
<cdk-virtual-scroll-viewport #cvsvList perfectScrollbar fxFlexFill>
|
||||
<ng-container
|
||||
*cdkVirtualFor="let node of dataSource.expandedData$"
|
||||
></ng-container>
|
||||
|
||||
<mat-tree #treeList [dataSource]="dataSource" [treeControl]="treeControl">
|
||||
<mat-tree-node
|
||||
*matTreeNodeDef="let node"
|
||||
matTreeNodePadding
|
||||
matTreeNodePaddingIndent="20"
|
||||
class="tree-no-child"
|
||||
>
|
||||
<li
|
||||
(click)="onClickNode(node)"
|
||||
matRipple
|
||||
[ngClass]="
|
||||
currentDeptSeq === node?.data?.deptInfo?.seq ? 'current' : ''
|
||||
"
|
||||
>
|
||||
<div class="tree-node-body">
|
||||
{{ node?.data?.deptInfo | ucapOrganizationTranslate: 'name' }}
|
||||
</div>
|
||||
</li>
|
||||
</mat-tree-node>
|
||||
|
||||
<mat-tree-node
|
||||
*matTreeNodeDef="let node; when: hasChild"
|
||||
matTreeNodePadding
|
||||
matTreeNodePaddingIndent="20"
|
||||
class="tree-has-child"
|
||||
>
|
||||
<li (click)="onClickNode(node)" matRipple>
|
||||
<div
|
||||
class="tree-node-body"
|
||||
[ngClass]="
|
||||
currentDeptSeq === node?.data?.deptInfo.seq ? 'current' : ''
|
||||
"
|
||||
>
|
||||
<span class="horizontal-line"></span>
|
||||
<button
|
||||
mat-icon-button
|
||||
color="accent"
|
||||
matTreeNodeToggle
|
||||
[attr.aria-label]="'toggle ' + node?.data?.filename"
|
||||
>
|
||||
<mat-icon
|
||||
class="tree-node-expand-btn"
|
||||
[@removeAdd]="treeControl.isExpanded(node) ? 'remove' : 'add'"
|
||||
>
|
||||
{{ treeControl.isExpanded(node) ? 'remove' : 'add' }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
<span class="dept-name">{{
|
||||
node?.data?.deptInfo | ucapOrganizationTranslate: 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</div>
|
|
@ -0,0 +1,26 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { TreeComponent } from './tree.component';
|
||||
|
||||
describe('ucap::ui-organization::TreeComponent', () => {
|
||||
let component: TreeComponent;
|
||||
let fixture: ComponentFixture<TreeComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TreeComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TreeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import { action } from '@storybook/addon-actions';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
|
||||
import { TreeComponent } from './tree.component';
|
||||
|
||||
const TITLE = 'organization::TreeComponent';
|
||||
|
||||
export default {
|
||||
title: `${TITLE}`,
|
||||
component: TreeComponent
|
||||
};
|
||||
|
||||
export const Blank = () => ({
|
||||
props: {}
|
||||
});
|
169
projects/ui-organization/src/lib/components/tree.component.ts
Normal file
169
projects/ui-organization/src/lib/components/tree.component.ts
Normal file
|
@ -0,0 +1,169 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
ContentChild,
|
||||
TemplateRef,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Directive
|
||||
} from '@angular/core';
|
||||
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { FlatTreeControl } from '@angular/cdk/tree';
|
||||
|
||||
import { MatTreeFlattener, MatTree } from '@angular/material/tree';
|
||||
|
||||
import { DeptInfo } from '@ucap/protocol-query';
|
||||
|
||||
import { VirtualScrollTreeFlatDataSource } from '@ucap/ng-ui';
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||
import { trigger, transition, style, animate } from '@angular/animations';
|
||||
|
||||
export interface OrganizationNode {
|
||||
deptInfo: DeptInfo;
|
||||
children?: OrganizationNode[];
|
||||
}
|
||||
|
||||
export interface FlatNode {
|
||||
expandable: boolean;
|
||||
level: number;
|
||||
data: OrganizationNode;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-organization-tree',
|
||||
templateUrl: './tree.component.html',
|
||||
styleUrls: ['./tree.component.scss'],
|
||||
animations: [
|
||||
trigger('removeAdd', [
|
||||
transition('remove <=> add', [
|
||||
style({
|
||||
transform: `rotate(45deg)`,
|
||||
opacity: 0
|
||||
}),
|
||||
animate('.2s 0s ease-out')
|
||||
])
|
||||
])
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TreeComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
loginRes: LoginResponse;
|
||||
|
||||
@Input()
|
||||
set deptInfoList(data: { deptInfoList: DeptInfo[]; displayRoot?: boolean }) {
|
||||
const deptInfoList = data.deptInfoList;
|
||||
const displayRoot =
|
||||
undefined === data.displayRoot ? true : data.displayRoot;
|
||||
|
||||
if (!deptInfoList || 0 === deptInfoList.length) {
|
||||
return;
|
||||
}
|
||||
const nodeMap = new Map<number, OrganizationNode>();
|
||||
let rootNodeList: OrganizationNode[] = [];
|
||||
const remainChildNodeList: OrganizationNode[] = [];
|
||||
|
||||
deptInfoList.forEach((deptInfo) => {
|
||||
const node: OrganizationNode = {
|
||||
deptInfo,
|
||||
children: []
|
||||
};
|
||||
if (nodeMap.has(deptInfo.seq)) {
|
||||
this.logService.warn('duplicate seq', deptInfo.seq);
|
||||
return;
|
||||
}
|
||||
nodeMap.set(deptInfo.seq, node);
|
||||
|
||||
if (0 === deptInfo.parentSeq) {
|
||||
rootNodeList.push(node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodeMap.has(deptInfo.parentSeq)) {
|
||||
nodeMap.get(deptInfo.parentSeq).children.push(node);
|
||||
} else {
|
||||
remainChildNodeList.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
!displayRoot &&
|
||||
0 < rootNodeList.length &&
|
||||
0 === rootNodeList[0].deptInfo.parentSeq &&
|
||||
!!rootNodeList[0].children &&
|
||||
0 < rootNodeList[0].children.length
|
||||
) {
|
||||
rootNodeList = [...rootNodeList[0].children];
|
||||
}
|
||||
|
||||
this._deptInfoList = deptInfoList;
|
||||
|
||||
remainChildNodeList.forEach((node) => {
|
||||
if (nodeMap.has(node.deptInfo.parentSeq)) {
|
||||
nodeMap.get(node.deptInfo.parentSeq).children.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
this.dataSource.data = rootNodeList;
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
currentDeptSeq: number;
|
||||
// tslint:disable-next-line: variable-name
|
||||
_deptInfoList: DeptInfo[];
|
||||
|
||||
@ViewChild('treeList', { static: false })
|
||||
treeList: MatTree<FlatNode>;
|
||||
|
||||
@ViewChild('cvsvList', { static: false })
|
||||
cvsvList: CdkVirtualScrollViewport;
|
||||
|
||||
treeControl: FlatTreeControl<FlatNode>;
|
||||
treeFlattener: MatTreeFlattener<OrganizationNode, FlatNode>;
|
||||
dataSource: VirtualScrollTreeFlatDataSource<OrganizationNode, FlatNode>;
|
||||
|
||||
constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private logService: LogService
|
||||
) {
|
||||
this.treeControl = new FlatTreeControl<FlatNode>(
|
||||
(node) => node.level,
|
||||
(node) => node.expandable
|
||||
);
|
||||
|
||||
this.treeFlattener = new MatTreeFlattener<OrganizationNode, FlatNode>(
|
||||
(node: OrganizationNode, level: number) => {
|
||||
return {
|
||||
expandable: !!node.children && node.children.length > 0,
|
||||
level,
|
||||
data: node
|
||||
};
|
||||
},
|
||||
(node) => node.level,
|
||||
(node) => node.expandable,
|
||||
(node) => node.children
|
||||
);
|
||||
|
||||
this.dataSource = new VirtualScrollTreeFlatDataSource<
|
||||
OrganizationNode,
|
||||
FlatNode
|
||||
>(this.treeControl, this.treeFlattener);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dataSource.cdkVirtualScrollViewport = this.cvsvList;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
|
||||
hasChild = (_: number, node: FlatNode) => node.expandable;
|
||||
|
||||
onClickNode(node: FlatNode) {}
|
||||
}
|
|
@ -1,16 +1,31 @@
|
|||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { MatTreeModule } from '@angular/material/tree';
|
||||
|
||||
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
|
||||
|
||||
import { UiModule } from '@ucap/ng-ui';
|
||||
|
||||
import { ModuleConfig } from './config/module-config';
|
||||
import { _MODULE_CONFIG } from './config/token';
|
||||
|
||||
import { ProfileListItemComponent } from './components/profile-list-item.component';
|
||||
import { TreeComponent } from './components/tree.component';
|
||||
|
||||
import { TranslatePipe } from './pipes/translate.pipe';
|
||||
|
||||
import { TranslateService } from './services/translate.service';
|
||||
|
||||
const COMPONENTS = [];
|
||||
const COMPONENTS = [TreeComponent, ProfileListItemComponent];
|
||||
const DIALOGS = [];
|
||||
const PIPES = [TranslatePipe];
|
||||
const DIRECTIVES = [];
|
||||
|
@ -24,7 +39,21 @@ const SERVICES = [TranslateService];
|
|||
export class OrganizationUiRootModule {}
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FlexLayoutModule,
|
||||
ScrollingModule,
|
||||
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatIconModule,
|
||||
MatRippleModule,
|
||||
MatTreeModule,
|
||||
|
||||
PerfectScrollbarModule,
|
||||
|
||||
UiModule
|
||||
],
|
||||
exports: [...COMPONENTS, ...DIRECTIVES, ...PIPES],
|
||||
declarations: [...COMPONENTS, ...DIRECTIVES, ...PIPES],
|
||||
entryComponents: [...DIALOGS]
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
export * from './lib/config/module-config';
|
||||
|
||||
export * from './lib/components/profile-list-item.component';
|
||||
export * from './lib/components/tree.component';
|
||||
|
||||
export * from './lib/pipes/translate.pipe';
|
||||
|
||||
export * from './lib/services/translate.service';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-ui",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
<button mat-fab class="fab-toggler bg-accent-dark" (click)="onToggleFab()">
|
||||
<mat-icon [@fabToggler]="fabTogglerState">add</mat-icon>
|
||||
</button>
|
||||
<div [@speedDialStagger]="buttons.length">
|
||||
<div [@speedDialStagger]="_buttons.length">
|
||||
<button
|
||||
*ngFor="let btn of buttons"
|
||||
*ngFor="let btn of _buttons"
|
||||
mat-mini-fab
|
||||
[matTooltip]="btn.tooltip"
|
||||
matTooltipPosition="before"
|
||||
|
|
|
@ -38,7 +38,7 @@ export class FloatActionButtonComponent implements OnInit {
|
|||
|
||||
hideItems() {
|
||||
this.fabTogglerState = 'inactive';
|
||||
this.buttons = [];
|
||||
this._buttons = [];
|
||||
}
|
||||
|
||||
getTooltip(btn: FloatActionButton) {
|
||||
|
|
|
@ -43,11 +43,11 @@ export class SplitButtonComponent implements OnInit, OnDestroy {
|
|||
|
||||
ngOnInit() {
|
||||
this.menuCloseSubscription = this.menu.close.subscribe(() => {
|
||||
this.matMenuTrigger._handleClick = e => {};
|
||||
this.matMenuTrigger._handleClick = (e) => {};
|
||||
this.splitOpened = false;
|
||||
});
|
||||
this._handleClick = this.matMenuTrigger._handleClick;
|
||||
this.matMenuTrigger._handleClick = e => {};
|
||||
this.matMenuTrigger._handleClick = (e) => {};
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
BehaviorSubject,
|
||||
merge,
|
||||
Observable,
|
||||
Subject,
|
||||
Subscription
|
||||
} from 'rxjs';
|
||||
import { map, share } from 'rxjs/operators';
|
||||
import { BehaviorSubject, merge, Observable, Subject } from 'rxjs';
|
||||
import { map, share, takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
|
@ -21,10 +15,9 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
|
|||
private _flattenedDataSubject = new BehaviorSubject<F[]>([]);
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _connectSubject: Subject<F[]>;
|
||||
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _dataChangeSubscription: Subscription;
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _rangeChangeSubscription: Subscription;
|
||||
private _destroySubject = new Subject<boolean>();
|
||||
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _rangeSubject: BehaviorSubject<{
|
||||
|
@ -41,13 +34,17 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
|
|||
return;
|
||||
}
|
||||
this._cdkVirtualScrollViewport = cdkVirtualScrollViewport;
|
||||
this._rangeSubject = new BehaviorSubject<{ start: number; end: number }>({
|
||||
this._rangeSubject = new BehaviorSubject<{
|
||||
start: number;
|
||||
end: number;
|
||||
}>({
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
|
||||
this._rangeChangeSubscription = cdkVirtualScrollViewport.renderedRangeStream.subscribe(
|
||||
(range) => {
|
||||
cdkVirtualScrollViewport.renderedRangeStream
|
||||
.pipe(takeUntil(this._destroySubject))
|
||||
.subscribe((range) => {
|
||||
this._rangeSubject.next({
|
||||
start: range.start,
|
||||
end: range.end
|
||||
|
@ -60,8 +57,7 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
|
|||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: variable-name
|
||||
|
@ -90,14 +86,17 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
|
|||
}
|
||||
|
||||
connect(collectionViewer: CollectionViewer): Observable<F[]> {
|
||||
this._destroySubject = new Subject<boolean>();
|
||||
|
||||
this._connectSubject = new Subject<F[]>();
|
||||
|
||||
this._dataChangeSubscription = merge(
|
||||
merge(
|
||||
collectionViewer.viewChange,
|
||||
this._treeControl.expansionModel.changed,
|
||||
this._flattenedDataSubject
|
||||
)
|
||||
.pipe(
|
||||
takeUntil(this._destroySubject),
|
||||
map(() => {
|
||||
this.expandedDataSubject.next(
|
||||
this._treeFlattener.expandFlattenedNodes(
|
||||
|
@ -130,12 +129,8 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
|
|||
this._connectSubject.unsubscribe();
|
||||
}
|
||||
|
||||
if (!!this._dataChangeSubscription) {
|
||||
this._dataChangeSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
if (!!this._rangeChangeSubscription) {
|
||||
this._rangeChangeSubscription.unsubscribe();
|
||||
if (!!this._destroySubject) {
|
||||
this._destroySubject.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
|||
})
|
||||
export class CdkVirtualScrollViewportPatchDirective
|
||||
implements OnInit, OnDestroy {
|
||||
protected readonly destroySubject = new Subject();
|
||||
protected destroySubject: Subject<boolean>;
|
||||
|
||||
constructor(
|
||||
@Self()
|
||||
|
@ -20,15 +20,19 @@ export class CdkVirtualScrollViewportPatchDirective
|
|||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.destroySubject = new Subject();
|
||||
|
||||
fromEvent(document.defaultView, 'resize')
|
||||
.pipe(debounceTime(10), takeUntil(this.destroySubject))
|
||||
.pipe(takeUntil(this.destroySubject), debounceTime(10))
|
||||
.subscribe(() => {
|
||||
this.viewportComponent.checkViewportSize();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroySubject.next();
|
||||
this.destroySubject.complete();
|
||||
if (!!this.destroySubject) {
|
||||
this.destroySubject.next();
|
||||
this.destroySubject.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user