change from expansion panel to tree

This commit is contained in:
병준 박 2019-11-15 17:32:48 +09:00
parent 21e6e2d621
commit 314dc75c96
11 changed files with 358 additions and 155 deletions

View File

@ -76,7 +76,6 @@
<ucap-profile-user-list-item
*ucapGroupExpansionPanelItem="let userInfo"
[userInfo]="userInfo"
[presence]="getStatusBulkInfo(userInfo) | async"
[sessionVerinfo]="sessionVerinfo"
(click)="onSelectBuddy(userInfo)"
(openProfile)="onClickOpenProfile($event)"

View File

@ -1,84 +1,58 @@
<mat-accordion #groupAccordion="matAccordion" [multi]="true">
<mat-expansion-panel
*ngIf="!!myProfileInfo && !checkable"
[togglePosition]="'before'"
class="groupExpansionPanel"
<cdk-virtual-scroll-viewport #cvsvGroup itemSize="80" fxFlexFill>
<ng-container
*cdkVirtualFor="let node of dataSource.expandedData$"
></ng-container>
<mat-tree
#groupTree
[dataSource]="dataSource"
[treeControl]="treeControl"
class="group-tree"
>
<mat-expansion-panel-header>
<mat-panel-title>내 프로필</mat-panel-title>
<mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header>
<ng-container>
<ng-template
[ngTemplateOutlet]="expansionPanelItemTemplateRef"
[ngTemplateOutletContext]="{ $implicit: myProfileInfo }"
>
</ng-template>
</ng-container>
</mat-expansion-panel>
<mat-expansion-panel
*ngIf="!!favoritBuddyList && favoritBuddyList.length > 0 && !checkable"
[togglePosition]="'before'"
class="groupExpansionPanel"
>
<mat-expansion-panel-header>
<mat-panel-title
>즐겨찾기
<span class="text-accent-color number"
>({{ favoritBuddyList.length }}명)</span
></mat-panel-title
>
<mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header>
<ng-container *ngFor="let favUserList of favoritBuddyList">
<ng-template
[ngTemplateOutlet]="expansionPanelItemTemplateRef"
[ngTemplateOutletContext]="{ $implicit: favUserList }"
>
</ng-template>
</ng-container>
</mat-expansion-panel>
<mat-expansion-panel
*ngFor="let groupBuddy of groupBuddyList"
[togglePosition]="'before'"
class="groupExpansionPanel"
>
<mat-expansion-panel-header>
<mat-panel-title class="panel-title">
<div class="title-name ellipsis">{{ groupBuddy.group.name }}</div>
<span class="text-accent-color number"
>({{ groupBuddy.buddyList.length }}명)</span
>
</mat-panel-title>
<mat-panel-description>
<span class="more-spacer"></span>
<button
mat-icon-button
aria-label="group menu"
*ngIf="!checkable"
(click)="onClickMore($event, groupBuddy.group)"
>
<mat-icon>more_vert</mat-icon>
</button>
<mat-checkbox
*ngIf="checkable"
#checkbox
[checked]="getCheckedGroup(groupBuddy)"
(change)="onChangeCheck(checkbox.checked, groupBuddy)"
(click)="$event.stopPropagation()"
></mat-checkbox>
</mat-panel-description>
</mat-expansion-panel-header>
<ng-container *ngFor="let userInfo of groupBuddy.buddyList">
<ng-template
[ngTemplateOutlet]="expansionPanelItemTemplateRef"
[ngTemplateOutletContext]="{ $implicit: userInfo }"
></ng-template>
</ng-container>
</mat-expansion-panel>
</mat-accordion>
<!-- This is the tree node template for leaf nodes -->
<mat-tree-node *matTreeNodeDef="let node" style="height: 80px;">
<li>
<div class="mat-tree-node">
<ng-container
[ngTemplateOutlet]="expansionPanelItemTemplateRef"
[ngTemplateOutletContext]="{ $implicit: node?.userInfo }"
>
</ng-container>
{{ node?.userInfo?.name }}
</div>
</li>
</mat-tree-node>
<!-- This is the tree node template for expandable nodes -->
<mat-tree-node
style="height: 80px;"
*matTreeNodeDef="let node; when: hasChild"
class="tree-node-frame"
>
<li>
<div class="mat-tree-node" class="path">
<span class="horizontal-line"></span>
<button
mat-icon-button
color="accent"
matTreeNodeToggle
[attr.aria-label]="'toggle '"
>
<mat-icon class="mat-icon-rtl-mirror">
{{ treeControl.isExpanded(node) ? 'remove' : 'add' }}
</mat-icon>
</button>
<ng-container [ngSwitch]="node.nodeType">
<span *ngSwitchCase="NodeType.Profile">Profile</span>
<span *ngSwitchCase="NodeType.Favorit">Favorit</span>
<span *ngSwitchCase="NodeType.Buddy">Buddy</span>
</ng-container>
</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>

View File

@ -1,67 +1,95 @@
@charset 'utf-8';
:host {
display: flex;
flex: 1;
flex-direction: column;
.more-spacer {
flex: 1 1 auto;
.group-tree {
padding: 10px;
ul,
li {
margin-top: 0;
margin-bottom: 0;
list-style-type: none;
margin-left: 10px;
}
.group-tree-node-invisible {
display: none;
}
}
::ng-deep .groupExpansionPanel .mat-expansion-panel-body {
padding: 0;
}
.mat-expansion-panel-header {
padding: 0 20px;
.mat-expansion-panel-header-title {
align-items: center;
font-size: 13px;
}
.mat-expansion-panel-header-description {
margin-right: 0;
}
}
.mat-icon-button {
margin-right: 0;
width: inherit;
}
.mat-icon {
font-size: 20px;
}
.groupList {
.mat-expansion-panel-header {
padding: 0 20px;
.mat-content {
color: #666666;
overflow: unset;
.panel-title{
display:inline-flex;
.title-name{
display:inline-flex;
flex:1 1 auto;
}
.number{
margin-left:6px;
display: inline-flex;
flex: 0 0 auto;
}
.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);
}
}
}
.box-more-spacer {
margin-right: 0;
ul .tree-node-frame li .path > .horizontal-line {
display: inline-block;
}
::ng-deep .mat-content{
overflow: unset !important;
.boxnone {
position: relative;
.vertical-line {
background: rgba(189, 189, 189, 0.4);
bottom: 6px;
display: block;
position: absolute;
top: 0px;
width: 2px;
}
.mat-tree-node:last-child {
padding-bottom: 10px;
}
}
.number{
margin-left:6px;
display: inline-flex;
flex: 0 0 auto;
.path {
padding: 6px 4px;
+ ul {
li:last-chlid {
border-bottom: 1px solid #dddddd;
}
}
.horizontal-line {
width: 10px;
height: 1px;
background-color: #dddddd;
display: inline-block;
vertical-align: middle;
margin-left: -10px;
}
.mat-icon-button {
padding: 0;
min-width: 0;
width: 20px;
height: 20px;
flex-shrink: 0;
line-height: 20px;
.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;
box-shadow: 0 2px 1px rgba(48, 48, 48, 0.2);
border-radius: 50%;
}
}
.dept-name {
padding-left: 10px;
}
}

View File

@ -6,13 +6,15 @@ import {
EventEmitter,
ViewChild,
ContentChild,
TemplateRef
TemplateRef,
AfterViewInit,
ChangeDetectorRef
} from '@angular/core';
import { ucapAnimations } from '@ucap-webmessenger/ui';
import { GroupDetailData, UserInfo } from '@ucap-webmessenger/protocol-sync';
import { MatAccordion } from '@angular/material';
import { MatAccordion, MatTreeFlattener } from '@angular/material';
import { ExpansionPanelItemDirective } from '../directives/expansion-panel-item.directive';
import {
UserInfoSS,
@ -20,6 +22,31 @@ import {
UserInfoDN
} from '@ucap-webmessenger/protocol-query';
import { NGXLogger } from 'ngx-logger';
import { VirtualScrollTreeFlatDataSource } from '@ucap-webmessenger/ui';
import { FlatTreeControl } from '@angular/cdk/tree';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
enum NodeType {
None = 'None',
Favorit = 'Favorit',
Profile = 'Profile',
Buddy = 'Buddy'
}
interface GroupNode {
nodeType: NodeType;
userInfo?: UserInfo;
groupDetail?: GroupDetailData;
children?: GroupNode[];
}
interface FlatNode {
expandable: boolean;
level: number;
nodeType: NodeType;
userInfo?: UserInfo;
groupDetail?: GroupDetailData;
}
@Component({
selector: 'ucap-group-expansion-panel',
@ -27,15 +54,80 @@ import { NGXLogger } from 'ngx-logger';
styleUrls: ['./expansion-panel.component.scss'],
animations: ucapAnimations
})
export class ExpansionPanelComponent implements OnInit {
export class ExpansionPanelComponent implements OnInit, AfterViewInit {
@Input()
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }[];
set myProfileInfo(userInfo: UserInfo) {
if (!userInfo) {
return;
}
const groupNode: GroupNode = {
nodeType: NodeType.Profile,
children: []
};
groupNode.children.push({
nodeType: NodeType.Profile,
userInfo
});
this.profileNodes = [groupNode];
this.refreshRootNodeList();
}
@Input()
favoritBuddyList?: UserInfo[];
set favoritBuddyList(userInfoList: UserInfo[]) {
if (!userInfoList) {
return;
}
const groupNode: GroupNode = {
nodeType: NodeType.Favorit,
children: []
};
userInfoList.forEach(userInfo => {
groupNode.children.push({
nodeType: NodeType.Favorit,
userInfo
});
});
this.favoritNodes = [groupNode];
this.refreshRootNodeList();
}
@Input()
myProfileInfo?: UserInfo;
set groupBuddyList(
list: { group: GroupDetailData; buddyList: UserInfo[] }[]
) {
if (!list || 0 === list.length) {
return;
}
this.buddyNodes = [];
for (const item of list) {
const groupNode: GroupNode = {
nodeType: NodeType.Buddy,
groupDetail: item.group,
children: []
};
item.buddyList.forEach(userInfo => {
groupNode.children.push({
nodeType: NodeType.Buddy,
userInfo
});
});
this.buddyNodes.push(groupNode);
}
this.refreshRootNodeList();
}
@Input()
checkable = false;
@Input()
/** 선택된 사용자의 리스트 */
selectedUserList?: (UserInfo | UserInfoSS | UserInfoF | UserInfoDN)[] = [];
@ -54,16 +146,64 @@ export class ExpansionPanelComponent implements OnInit {
@ContentChild(ExpansionPanelItemDirective, {
read: TemplateRef,
static: true
static: false
})
expansionPanelItemTemplateRef: TemplateRef<ExpansionPanelItemDirective>;
@ViewChild('groupAccordion', { static: true }) groupAccordion: MatAccordion;
constructor(private logger: NGXLogger) {}
@ViewChild('cvsvGroup', { static: false })
cvsvGroup: CdkVirtualScrollViewport;
NodeType = NodeType;
profileNodes: GroupNode[] = [];
favoritNodes: GroupNode[] = [];
buddyNodes: GroupNode[] = [];
rootNodeList: GroupNode[] = [];
treeControl: FlatTreeControl<FlatNode>;
treeFlattener: MatTreeFlattener<GroupNode, FlatNode>;
dataSource: VirtualScrollTreeFlatDataSource<GroupNode, FlatNode>;
constructor(
private changeDetectorRef: ChangeDetectorRef,
private logger: NGXLogger
) {
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,
userInfo: node.userInfo,
groupDetail: node.groupDetail
};
},
node => node.level,
node => node.expandable,
node => node.children
);
this.dataSource = new VirtualScrollTreeFlatDataSource<GroupNode, FlatNode>(
this.treeControl,
this.treeFlattener
);
}
ngOnInit() {}
ngAfterViewInit(): void {
this.dataSource.cdkVirtualScrollViewport = this.cvsvGroup;
}
hasChild = (_: number, node: FlatNode) => node.expandable;
expandMore() {
this.groupAccordion.openAll();
}
@ -110,4 +250,13 @@ export class ExpansionPanelComponent implements OnInit {
}
return false;
}
private refreshRootNodeList(): void {
this.rootNodeList = [
...this.profileNodes,
...this.favoritNodes,
...this.buddyNodes
];
this.dataSource.data = this.rootNodeList;
}
}

View File

@ -4,9 +4,12 @@ import { ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
import { MatTreeModule } from '@angular/material/tree';
import { ExpansionPanelComponent } from './components/expansion-panel.component';
import { ExpansionPanelItemDirective } from './directives/expansion-panel-item.directive';
@ -22,9 +25,13 @@ const SERVICES = [];
CommonModule,
ReactiveFormsModule,
FlexLayoutModule,
ScrollingModule,
MatButtonModule,
MatExpansionModule,
MatIconModule,
MatTreeModule,
MatCheckboxModule
],
exports: [...COMPONENTS, ...DIRECTIVES],

View File

@ -1,5 +1,5 @@
<!--체크박스 보여줄때는 <div class="list-item checkbox" matRipple> 클래스에 checkbox만 추가-->
<div class="list-item" matRipple>
<div class="list-item" *ngIf="userInfo" matRipple>
<!--pcOn , pcOut pcOff , pcOther-->
<span
class="presence"

View File

@ -0,0 +1 @@
<div class="ucap-expansion-panel-container"></div>

View File

@ -0,0 +1,4 @@
.ucap-expansion-panel-container {
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,27 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ExpansionPanelComponent } from './expansion-panel.component';
describe('ExpansionPanelComponent', () => {
let component: ExpansionPanelComponent;
let fixture: ComponentFixture<ExpansionPanelComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ExpansionPanelComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ExpansionPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,12 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'ucap-expansion-panel',
templateUrl: './expansion-panel.component.html',
styleUrls: ['./expansion-panel.component.scss']
})
export class ExpansionPanelComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View File

@ -18,6 +18,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop';
import { FileUploadQueueComponent } from './components/file-upload-queue.component';
import { FloatActionButtonComponent } from './components/float-action-button.component';
import { FileViewerComponent } from './components/file-viewer.component';
import { ExpansionPanelComponent } from './components/expansion-panel.component';
import { BinaryViewerComponent } from './components/file-viewer/binary-viewer.component';
import { DocumentViewerComponent } from './components/file-viewer/document-viewer.component';
@ -41,7 +42,7 @@ import { BytesPipe } from './pipes/bytes.pipe';
import { LinefeedToHtmlPipe, HtmlToLinefeedPipe } from './pipes/linefeed.pipe';
import {
DateToStringForChatRoomListPipe,
DateToStringFormatPipe,
DateToStringFormatPipe
} from './pipes/dates.pipe';
import { SecondsToMinutesPipe } from './pipes/seconds-to-minutes.pipe';
@ -49,18 +50,19 @@ const COMPONENTS = [
FileUploadQueueComponent,
FloatActionButtonComponent,
FileViewerComponent,
ExpansionPanelComponent,
BinaryViewerComponent,
DocumentViewerComponent,
ImageViewerComponent,
SoundViewerComponent,
VideoViewerComponent,
VideoViewerComponent
];
const DIALOGS = [AlertDialogComponent, ConfirmDialogComponent];
const DIRECTIVES = [
ClickOutsideDirective,
FileUploadForDirective,
ImageDirective,
ImageDirective
];
const PIPES = [
BytesPipe,
@ -68,13 +70,13 @@ const PIPES = [
HtmlToLinefeedPipe,
DateToStringForChatRoomListPipe,
DateToStringFormatPipe,
SecondsToMinutesPipe,
SecondsToMinutesPipe
];
const SERVICES = [
BottomSheetService,
ClipboardService,
DialogService,
SnackBarService,
SnackBarService
];
@NgModule({
@ -90,17 +92,17 @@ const SERVICES = [
MatSnackBarModule,
MatToolbarModule,
MatTooltipModule,
DragDropModule,
DragDropModule
],
exports: [...COMPONENTS, ...DIRECTIVES, ...PIPES],
declarations: [...COMPONENTS, ...DIALOGS, ...DIRECTIVES, ...PIPES],
entryComponents: [...DIALOGS],
entryComponents: [...DIALOGS]
})
export class UCapUiModule {
public static forRoot(): ModuleWithProviders<UCapUiModule> {
return {
ngModule: UCapUiModule,
providers: [...SERVICES],
providers: [...SERVICES]
};
}
}