This commit is contained in:
Park Byung Eun 2020-05-05 21:06:50 +09:00
parent b03f36b99a
commit aee13b8a4d
33 changed files with 1451 additions and 421 deletions

918
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -182,10 +182,10 @@
"@ucap/ng-store-chat": "file:pack/ucap-ng-store-chat-0.0.5.tgz", "@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-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-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": "file:pack/ucap-ng-ui-0.0.5.tgz",
"@ucap/ng-ui-authentication": "file:pack/ucap-ng-ui-authentication-0.0.16.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.4.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.2.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-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-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", "@ucap/ng-web-storage": "file:pack/ucap-ng-web-storage-0.0.3.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "@ucap/ng-ui-authentication", "name": "@ucap/ng-ui-authentication",
"version": "0.0.16", "version": "0.0.19",
"publishConfig": { "publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/" "registry": "https://nexus.loafle.net/repository/npm-ucap/"
}, },

View File

@ -10,17 +10,18 @@
<mat-select <mat-select
[formControl]="companyCodeFormControl" [formControl]="companyCodeFormControl"
[value]="companyCode || fixedCompanyCode" [value]="companyCode || fixedCompanyCode"
placeholder="{{ 'login.labels.selectCompany' | ucapI18n }}"
> >
<mat-option <mat-option
*ngFor="let company of companyList" *ngFor="let company of companyList"
[value]="company.companyCode" [value]="company.companyCode"
>{{ company.companyName }} >
{{ company.companyName }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div class="input-lineless"> <div class="input-lineless">
12345
<!--<span class="icon-img"><i class="mid mdi-account-tie-outline"></i></span>--> <!--<span class="icon-img"><i class="mid mdi-account-tie-outline"></i></span>-->
<span class="icon-img"> <span class="icon-img">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.82 22"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.82 22">

View File

@ -4,6 +4,7 @@
"lib": { "lib": {
"entryFile": "src/public-api.ts", "entryFile": "src/public-api.ts",
"umdModuleIds": { "umdModuleIds": {
"ngx-perfect-scrollbar": "ngx-perfect-scrollbar",
"@ucap/core": "@ucap/core", "@ucap/core": "@ucap/core",
"@ucap/ng-ui": "@ucap/ng-ui" "@ucap/ng-ui": "@ucap/ng-ui"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@ucap/ng-ui-group", "name": "@ucap/ng-ui-group",
"version": "0.0.4", "version": "0.0.27",
"publishConfig": { "publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/" "registry": "https://nexus.loafle.net/repository/npm-ucap/"
}, },

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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'
}
});

View 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();
}
}

View File

@ -1,6 +1,8 @@
import { NgModule, ModuleWithProviders } from '@angular/core'; import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ScrollingModule } from '@angular/cdk/scrolling'; import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatRippleModule } from '@angular/material/core'; import { MatRippleModule } from '@angular/material/core';
@ -16,12 +18,23 @@ import { UiModule } from '@ucap/ng-ui';
import { ModuleConfig } from './config/module-config'; import { ModuleConfig } from './config/module-config';
import { _MODULE_CONFIG } from './config/token'; 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 DIALOGS = [];
const PIPES = []; const PIPES = [];
const DIRECTIVES = []; const DIRECTIVES = [
ExpansionNodeDirective,
ExpansionFavoriteHeaderDirective,
ExpansionBuddyHeaderDirective,
ExpansionDefaultHeaderDirective
];
const SERVICES = []; const SERVICES = [];
@NgModule({ @NgModule({
@ -34,6 +47,8 @@ export class GroupUiRootModule {}
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
FlexLayoutModule,
ScrollingModule, ScrollingModule,
MatButtonModule, MatButtonModule,

View File

@ -4,6 +4,6 @@
export * from './lib/config/module-config'; 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'; export * from './lib/group-ui.module';

View File

@ -4,7 +4,9 @@
"lib": { "lib": {
"entryFile": "src/public-api.ts", "entryFile": "src/public-api.ts",
"umdModuleIds": { "umdModuleIds": {
"ngx-perfect-scrollbar": "ngx-perfect-scrollbar",
"@ucap/core": "@ucap/core", "@ucap/core": "@ucap/core",
"@ucap/ng-logger": "@ucap/ng-logger",
"@ucap/ng-ui": "@ucap/ng-ui" "@ucap/ng-ui": "@ucap/ng-ui"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@ucap/ng-ui-organization", "name": "@ucap/ng-ui-organization",
"version": "0.0.2", "version": "0.0.12",
"publishConfig": { "publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/" "registry": "https://nexus.loafle.net/repository/npm-ucap/"
}, },

View File

@ -0,0 +1 @@
<div class="ucap-organization-profile-list-item-container"></div>

View File

@ -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();
});
});

View File

@ -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'
}
});

View File

@ -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 {}
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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: {}
});

View 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) {}
}

View File

@ -1,16 +1,31 @@
import { NgModule, ModuleWithProviders } from '@angular/core'; import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common'; 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 { UiModule } from '@ucap/ng-ui';
import { ModuleConfig } from './config/module-config'; import { ModuleConfig } from './config/module-config';
import { _MODULE_CONFIG } from './config/token'; 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 { TranslatePipe } from './pipes/translate.pipe';
import { TranslateService } from './services/translate.service'; import { TranslateService } from './services/translate.service';
const COMPONENTS = []; const COMPONENTS = [TreeComponent, ProfileListItemComponent];
const DIALOGS = []; const DIALOGS = [];
const PIPES = [TranslatePipe]; const PIPES = [TranslatePipe];
const DIRECTIVES = []; const DIRECTIVES = [];
@ -24,7 +39,21 @@ const SERVICES = [TranslateService];
export class OrganizationUiRootModule {} export class OrganizationUiRootModule {}
@NgModule({ @NgModule({
imports: [CommonModule, UiModule], imports: [
CommonModule,
FlexLayoutModule,
ScrollingModule,
MatButtonModule,
MatCheckboxModule,
MatIconModule,
MatRippleModule,
MatTreeModule,
PerfectScrollbarModule,
UiModule
],
exports: [...COMPONENTS, ...DIRECTIVES, ...PIPES], exports: [...COMPONENTS, ...DIRECTIVES, ...PIPES],
declarations: [...COMPONENTS, ...DIRECTIVES, ...PIPES], declarations: [...COMPONENTS, ...DIRECTIVES, ...PIPES],
entryComponents: [...DIALOGS] entryComponents: [...DIALOGS]

View File

@ -4,6 +4,9 @@
export * from './lib/config/module-config'; 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/pipes/translate.pipe';
export * from './lib/services/translate.service'; export * from './lib/services/translate.service';

View File

@ -1,6 +1,6 @@
{ {
"name": "@ucap/ng-ui", "name": "@ucap/ng-ui",
"version": "0.0.4", "version": "0.0.5",
"publishConfig": { "publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/" "registry": "https://nexus.loafle.net/repository/npm-ucap/"
}, },

View File

@ -7,9 +7,9 @@
<button mat-fab class="fab-toggler bg-accent-dark" (click)="onToggleFab()"> <button mat-fab class="fab-toggler bg-accent-dark" (click)="onToggleFab()">
<mat-icon [@fabToggler]="fabTogglerState">add</mat-icon> <mat-icon [@fabToggler]="fabTogglerState">add</mat-icon>
</button> </button>
<div [@speedDialStagger]="buttons.length"> <div [@speedDialStagger]="_buttons.length">
<button <button
*ngFor="let btn of buttons" *ngFor="let btn of _buttons"
mat-mini-fab mat-mini-fab
[matTooltip]="btn.tooltip" [matTooltip]="btn.tooltip"
matTooltipPosition="before" matTooltipPosition="before"

View File

@ -38,7 +38,7 @@ export class FloatActionButtonComponent implements OnInit {
hideItems() { hideItems() {
this.fabTogglerState = 'inactive'; this.fabTogglerState = 'inactive';
this.buttons = []; this._buttons = [];
} }
getTooltip(btn: FloatActionButton) { getTooltip(btn: FloatActionButton) {

View File

@ -43,11 +43,11 @@ export class SplitButtonComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.menuCloseSubscription = this.menu.close.subscribe(() => { this.menuCloseSubscription = this.menu.close.subscribe(() => {
this.matMenuTrigger._handleClick = e => {}; this.matMenuTrigger._handleClick = (e) => {};
this.splitOpened = false; this.splitOpened = false;
}); });
this._handleClick = this.matMenuTrigger._handleClick; this._handleClick = this.matMenuTrigger._handleClick;
this.matMenuTrigger._handleClick = e => {}; this.matMenuTrigger._handleClick = (e) => {};
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View File

@ -1,11 +1,5 @@
import { import { BehaviorSubject, merge, Observable, Subject } from 'rxjs';
BehaviorSubject, import { map, share, takeUntil } from 'rxjs/operators';
merge,
Observable,
Subject,
Subscription
} from 'rxjs';
import { map, share } from 'rxjs/operators';
import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
@ -21,10 +15,9 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
private _flattenedDataSubject = new BehaviorSubject<F[]>([]); private _flattenedDataSubject = new BehaviorSubject<F[]>([]);
// tslint:disable-next-line: variable-name // tslint:disable-next-line: variable-name
private _connectSubject: Subject<F[]>; private _connectSubject: Subject<F[]>;
// tslint:disable-next-line: variable-name // tslint:disable-next-line: variable-name
private _dataChangeSubscription: Subscription; private _destroySubject = new Subject<boolean>();
// tslint:disable-next-line: variable-name
private _rangeChangeSubscription: Subscription;
// tslint:disable-next-line: variable-name // tslint:disable-next-line: variable-name
private _rangeSubject: BehaviorSubject<{ private _rangeSubject: BehaviorSubject<{
@ -41,13 +34,17 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
return; return;
} }
this._cdkVirtualScrollViewport = cdkVirtualScrollViewport; this._cdkVirtualScrollViewport = cdkVirtualScrollViewport;
this._rangeSubject = new BehaviorSubject<{ start: number; end: number }>({ this._rangeSubject = new BehaviorSubject<{
start: number;
end: number;
}>({
start: 0, start: 0,
end: 1 end: 1
}); });
this._rangeChangeSubscription = cdkVirtualScrollViewport.renderedRangeStream.subscribe( cdkVirtualScrollViewport.renderedRangeStream
(range) => { .pipe(takeUntil(this._destroySubject))
.subscribe((range) => {
this._rangeSubject.next({ this._rangeSubject.next({
start: range.start, start: range.start,
end: range.end end: range.end
@ -60,8 +57,7 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
) )
); );
} }
} });
);
} }
// tslint:disable-next-line: variable-name // tslint:disable-next-line: variable-name
@ -90,14 +86,17 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
} }
connect(collectionViewer: CollectionViewer): Observable<F[]> { connect(collectionViewer: CollectionViewer): Observable<F[]> {
this._destroySubject = new Subject<boolean>();
this._connectSubject = new Subject<F[]>(); this._connectSubject = new Subject<F[]>();
this._dataChangeSubscription = merge( merge(
collectionViewer.viewChange, collectionViewer.viewChange,
this._treeControl.expansionModel.changed, this._treeControl.expansionModel.changed,
this._flattenedDataSubject this._flattenedDataSubject
) )
.pipe( .pipe(
takeUntil(this._destroySubject),
map(() => { map(() => {
this.expandedDataSubject.next( this.expandedDataSubject.next(
this._treeFlattener.expandFlattenedNodes( this._treeFlattener.expandFlattenedNodes(
@ -130,12 +129,8 @@ export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
this._connectSubject.unsubscribe(); this._connectSubject.unsubscribe();
} }
if (!!this._dataChangeSubscription) { if (!!this._destroySubject) {
this._dataChangeSubscription.unsubscribe(); this._destroySubject.complete();
}
if (!!this._rangeChangeSubscription) {
this._rangeChangeSubscription.unsubscribe();
} }
} }
} }

View File

@ -11,7 +11,7 @@ import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
}) })
export class CdkVirtualScrollViewportPatchDirective export class CdkVirtualScrollViewportPatchDirective
implements OnInit, OnDestroy { implements OnInit, OnDestroy {
protected readonly destroySubject = new Subject(); protected destroySubject: Subject<boolean>;
constructor( constructor(
@Self() @Self()
@ -20,15 +20,19 @@ export class CdkVirtualScrollViewportPatchDirective
) {} ) {}
ngOnInit() { ngOnInit() {
this.destroySubject = new Subject();
fromEvent(document.defaultView, 'resize') fromEvent(document.defaultView, 'resize')
.pipe(debounceTime(10), takeUntil(this.destroySubject)) .pipe(takeUntil(this.destroySubject), debounceTime(10))
.subscribe(() => { .subscribe(() => {
this.viewportComponent.checkViewportSize(); this.viewportComponent.checkViewportSize();
}); });
} }
ngOnDestroy() { ngOnDestroy() {
if (!!this.destroySubject) {
this.destroySubject.next(); this.destroySubject.next();
this.destroySubject.complete(); this.destroySubject.complete();
} }
} }
}