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 <ucap-profile-user-list-item
*ucapGroupExpansionPanelItem="let userInfo" *ucapGroupExpansionPanelItem="let userInfo"
[userInfo]="userInfo" [userInfo]="userInfo"
[presence]="getStatusBulkInfo(userInfo) | async"
[sessionVerinfo]="sessionVerinfo" [sessionVerinfo]="sessionVerinfo"
(click)="onSelectBuddy(userInfo)" (click)="onSelectBuddy(userInfo)"
(openProfile)="onClickOpenProfile($event)" (openProfile)="onClickOpenProfile($event)"

View File

@ -1,84 +1,58 @@
<mat-accordion #groupAccordion="matAccordion" [multi]="true"> <cdk-virtual-scroll-viewport #cvsvGroup itemSize="80" fxFlexFill>
<mat-expansion-panel <ng-container
*ngIf="!!myProfileInfo && !checkable" *cdkVirtualFor="let node of dataSource.expandedData$"
[togglePosition]="'before'" ></ng-container>
class="groupExpansionPanel" <mat-tree
#groupTree
[dataSource]="dataSource"
[treeControl]="treeControl"
class="group-tree"
> >
<mat-expansion-panel-header> <!-- This is the tree node template for leaf nodes -->
<mat-panel-title>내 프로필</mat-panel-title> <mat-tree-node *matTreeNodeDef="let node" style="height: 80px;">
<mat-panel-description> </mat-panel-description> <li>
</mat-expansion-panel-header> <div class="mat-tree-node">
<ng-container
<ng-container>
<ng-template
[ngTemplateOutlet]="expansionPanelItemTemplateRef" [ngTemplateOutlet]="expansionPanelItemTemplateRef"
[ngTemplateOutletContext]="{ $implicit: myProfileInfo }" [ngTemplateOutletContext]="{ $implicit: node?.userInfo }"
> >
</ng-template>
</ng-container> </ng-container>
</mat-expansion-panel> {{ node?.userInfo?.name }}
</div>
<mat-expansion-panel </li>
*ngIf="!!favoritBuddyList && favoritBuddyList.length > 0 && !checkable" </mat-tree-node>
[togglePosition]="'before'" <!-- This is the tree node template for expandable nodes -->
class="groupExpansionPanel" <mat-tree-node
style="height: 80px;"
*matTreeNodeDef="let node; when: hasChild"
class="tree-node-frame"
> >
<mat-expansion-panel-header> <li>
<mat-panel-title <div class="mat-tree-node" class="path">
>즐겨찾기 <span class="horizontal-line"></span>
<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 <button
mat-icon-button mat-icon-button
aria-label="group menu" color="accent"
*ngIf="!checkable" matTreeNodeToggle
(click)="onClickMore($event, groupBuddy.group)" [attr.aria-label]="'toggle '"
> >
<mat-icon>more_vert</mat-icon> <mat-icon class="mat-icon-rtl-mirror">
{{ treeControl.isExpanded(node) ? 'remove' : 'add' }}
</mat-icon>
</button> </button>
<mat-checkbox <ng-container [ngSwitch]="node.nodeType">
*ngIf="checkable" <span *ngSwitchCase="NodeType.Profile">Profile</span>
#checkbox <span *ngSwitchCase="NodeType.Favorit">Favorit</span>
[checked]="getCheckedGroup(groupBuddy)" <span *ngSwitchCase="NodeType.Buddy">Buddy</span>
(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> </ng-container>
</mat-expansion-panel> </div>
</mat-accordion> <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'; @charset 'utf-8';
:host { .group-tree {
display: flex; padding: 10px;
flex: 1; ul,
flex-direction: column; li {
margin-top: 0;
.more-spacer { margin-bottom: 0;
flex: 1 1 auto; list-style-type: none;
margin-left: 10px;
}
.group-tree-node-invisible {
display: none;
} }
} }
::ng-deep .groupExpansionPanel .mat-expansion-panel-body { .tree-node-frame {
padding: 0; li {
.path {
.horizontal-line {
display: none;
} }
.mat-expansion-panel-header { }
padding: 0 20px; }
.mat-expansion-panel-header-title { .mat-tree-node {
align-items: center; min-height: 30px;
font-size: 13px; 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);
} }
.mat-expansion-panel-header-description {
margin-right: 0;
} }
} }
ul .tree-node-frame li .path > .horizontal-line {
display: inline-block;
}
.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;
}
}
.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 { .mat-icon-button {
margin-right: 0; padding: 0;
width: inherit; min-width: 0;
} width: 20px;
.mat-icon { height: 20px;
font-size: 20px; flex-shrink: 0;
} line-height: 20px;
.groupList { .mat-icon-rtl-mirror {
.mat-expansion-panel-header { border: 1px solid #dddddd;
padding: 0 20px; padding: 2px;
.mat-content { font-size: 14px;
color: #666666; min-width: 14px;
overflow: unset; min-height: 14px;
.panel-title{ line-height: 14px;
display:inline-flex; width: 20px;
.title-name{ height: 20px;
display:inline-flex; box-shadow: 0 2px 1px rgba(48, 48, 48, 0.2);
flex:1 1 auto; border-radius: 50%;
}
.number{
margin-left:6px;
display: inline-flex;
flex: 0 0 auto;
} }
} }
.dept-name {
padding-left: 10px;
} }
} }
}
.box-more-spacer {
margin-right: 0;
}
::ng-deep .mat-content{
overflow: unset !important;
}
.number{
margin-left:6px;
display: inline-flex;
flex: 0 0 auto;
}

View File

@ -6,13 +6,15 @@ import {
EventEmitter, EventEmitter,
ViewChild, ViewChild,
ContentChild, ContentChild,
TemplateRef TemplateRef,
AfterViewInit,
ChangeDetectorRef
} from '@angular/core'; } from '@angular/core';
import { ucapAnimations } from '@ucap-webmessenger/ui'; import { ucapAnimations } from '@ucap-webmessenger/ui';
import { GroupDetailData, UserInfo } from '@ucap-webmessenger/protocol-sync'; 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 { ExpansionPanelItemDirective } from '../directives/expansion-panel-item.directive';
import { import {
UserInfoSS, UserInfoSS,
@ -20,6 +22,31 @@ import {
UserInfoDN UserInfoDN
} from '@ucap-webmessenger/protocol-query'; } from '@ucap-webmessenger/protocol-query';
import { NGXLogger } from 'ngx-logger'; 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({ @Component({
selector: 'ucap-group-expansion-panel', selector: 'ucap-group-expansion-panel',
@ -27,15 +54,80 @@ import { NGXLogger } from 'ngx-logger';
styleUrls: ['./expansion-panel.component.scss'], styleUrls: ['./expansion-panel.component.scss'],
animations: ucapAnimations animations: ucapAnimations
}) })
export class ExpansionPanelComponent implements OnInit { export class ExpansionPanelComponent implements OnInit, AfterViewInit {
@Input() @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() @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() @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() @Input()
checkable = false; checkable = false;
@Input() @Input()
/** 선택된 사용자의 리스트 */ /** 선택된 사용자의 리스트 */
selectedUserList?: (UserInfo | UserInfoSS | UserInfoF | UserInfoDN)[] = []; selectedUserList?: (UserInfo | UserInfoSS | UserInfoF | UserInfoDN)[] = [];
@ -54,16 +146,64 @@ export class ExpansionPanelComponent implements OnInit {
@ContentChild(ExpansionPanelItemDirective, { @ContentChild(ExpansionPanelItemDirective, {
read: TemplateRef, read: TemplateRef,
static: true static: false
}) })
expansionPanelItemTemplateRef: TemplateRef<ExpansionPanelItemDirective>; expansionPanelItemTemplateRef: TemplateRef<ExpansionPanelItemDirective>;
@ViewChild('groupAccordion', { static: true }) groupAccordion: MatAccordion; @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() {} ngOnInit() {}
ngAfterViewInit(): void {
this.dataSource.cdkVirtualScrollViewport = this.cvsvGroup;
}
hasChild = (_: number, node: FlatNode) => node.expandable;
expandMore() { expandMore() {
this.groupAccordion.openAll(); this.groupAccordion.openAll();
} }
@ -110,4 +250,13 @@ export class ExpansionPanelComponent implements OnInit {
} }
return false; 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 { FlexLayoutModule } from '@angular/flex-layout';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion'; import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatTreeModule } from '@angular/material/tree';
import { ExpansionPanelComponent } from './components/expansion-panel.component'; import { ExpansionPanelComponent } from './components/expansion-panel.component';
import { ExpansionPanelItemDirective } from './directives/expansion-panel-item.directive'; import { ExpansionPanelItemDirective } from './directives/expansion-panel-item.directive';
@ -22,9 +25,13 @@ const SERVICES = [];
CommonModule, CommonModule,
ReactiveFormsModule, ReactiveFormsModule,
FlexLayoutModule, FlexLayoutModule,
ScrollingModule,
MatButtonModule, MatButtonModule,
MatExpansionModule, MatExpansionModule,
MatIconModule, MatIconModule,
MatTreeModule,
MatCheckboxModule MatCheckboxModule
], ],
exports: [...COMPONENTS, ...DIRECTIVES], exports: [...COMPONENTS, ...DIRECTIVES],

View File

@ -1,5 +1,5 @@
<!--체크박스 보여줄때는 <div class="list-item checkbox" matRipple> 클래스에 checkbox만 추가--> <!--체크박스 보여줄때는 <div class="list-item checkbox" matRipple> 클래스에 checkbox만 추가-->
<div class="list-item" matRipple> <div class="list-item" *ngIf="userInfo" matRipple>
<!--pcOn , pcOut pcOff , pcOther--> <!--pcOn , pcOut pcOff , pcOther-->
<span <span
class="presence" 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 { FileUploadQueueComponent } from './components/file-upload-queue.component';
import { FloatActionButtonComponent } from './components/float-action-button.component'; import { FloatActionButtonComponent } from './components/float-action-button.component';
import { FileViewerComponent } from './components/file-viewer.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 { BinaryViewerComponent } from './components/file-viewer/binary-viewer.component';
import { DocumentViewerComponent } from './components/file-viewer/document-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 { LinefeedToHtmlPipe, HtmlToLinefeedPipe } from './pipes/linefeed.pipe';
import { import {
DateToStringForChatRoomListPipe, DateToStringForChatRoomListPipe,
DateToStringFormatPipe, DateToStringFormatPipe
} from './pipes/dates.pipe'; } from './pipes/dates.pipe';
import { SecondsToMinutesPipe } from './pipes/seconds-to-minutes.pipe'; import { SecondsToMinutesPipe } from './pipes/seconds-to-minutes.pipe';
@ -49,18 +50,19 @@ const COMPONENTS = [
FileUploadQueueComponent, FileUploadQueueComponent,
FloatActionButtonComponent, FloatActionButtonComponent,
FileViewerComponent, FileViewerComponent,
ExpansionPanelComponent,
BinaryViewerComponent, BinaryViewerComponent,
DocumentViewerComponent, DocumentViewerComponent,
ImageViewerComponent, ImageViewerComponent,
SoundViewerComponent, SoundViewerComponent,
VideoViewerComponent, VideoViewerComponent
]; ];
const DIALOGS = [AlertDialogComponent, ConfirmDialogComponent]; const DIALOGS = [AlertDialogComponent, ConfirmDialogComponent];
const DIRECTIVES = [ const DIRECTIVES = [
ClickOutsideDirective, ClickOutsideDirective,
FileUploadForDirective, FileUploadForDirective,
ImageDirective, ImageDirective
]; ];
const PIPES = [ const PIPES = [
BytesPipe, BytesPipe,
@ -68,13 +70,13 @@ const PIPES = [
HtmlToLinefeedPipe, HtmlToLinefeedPipe,
DateToStringForChatRoomListPipe, DateToStringForChatRoomListPipe,
DateToStringFormatPipe, DateToStringFormatPipe,
SecondsToMinutesPipe, SecondsToMinutesPipe
]; ];
const SERVICES = [ const SERVICES = [
BottomSheetService, BottomSheetService,
ClipboardService, ClipboardService,
DialogService, DialogService,
SnackBarService, SnackBarService
]; ];
@NgModule({ @NgModule({
@ -90,17 +92,17 @@ const SERVICES = [
MatSnackBarModule, MatSnackBarModule,
MatToolbarModule, MatToolbarModule,
MatTooltipModule, MatTooltipModule,
DragDropModule, DragDropModule
], ],
exports: [...COMPONENTS, ...DIRECTIVES, ...PIPES], exports: [...COMPONENTS, ...DIRECTIVES, ...PIPES],
declarations: [...COMPONENTS, ...DIALOGS, ...DIRECTIVES, ...PIPES], declarations: [...COMPONENTS, ...DIALOGS, ...DIRECTIVES, ...PIPES],
entryComponents: [...DIALOGS], entryComponents: [...DIALOGS]
}) })
export class UCapUiModule { export class UCapUiModule {
public static forRoot(): ModuleWithProviders<UCapUiModule> { public static forRoot(): ModuleWithProviders<UCapUiModule> {
return { return {
ngModule: UCapUiModule, ngModule: UCapUiModule,
providers: [...SERVICES], providers: [...SERVICES]
}; };
} }
} }