0524 sync
This commit is contained in:
parent
4f42fc5c2d
commit
23bbfc4b63
|
@ -9,5 +9,10 @@ module.exports = (config, options) => {
|
||||||
fs: 'empty'
|
fs: 'empty'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config.resolve.alias = {
|
||||||
|
...config.resolve.alias,
|
||||||
|
'@ucap/lg-scss': path.resolve(__dirname, '..', 'src/assets/scss')
|
||||||
|
};
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
1864
package-lock.json
generated
1864
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
|
@ -68,16 +68,18 @@
|
||||||
"@ucap/ng-protocol-status": "~0.0.3",
|
"@ucap/ng-protocol-status": "~0.0.3",
|
||||||
"@ucap/ng-protocol-sync": "~0.0.3",
|
"@ucap/ng-protocol-sync": "~0.0.3",
|
||||||
"@ucap/ng-protocol-umg": "~0.0.3",
|
"@ucap/ng-protocol-umg": "~0.0.3",
|
||||||
"@ucap/ng-store-authentication": "~0.0.10",
|
"@ucap/ng-store-authentication": "~0.0.11",
|
||||||
"@ucap/ng-store-chat": "~0.0.5",
|
"@ucap/ng-store-chat": "~0.0.13",
|
||||||
"@ucap/ng-store-group": "~0.0.6",
|
"@ucap/ng-store-group": "~0.0.14",
|
||||||
"@ucap/ng-store-organization": "~0.0.4",
|
"@ucap/ng-store-organization": "~0.0.8",
|
||||||
"@ucap/ng-web-socket": "~0.0.2",
|
"@ucap/ng-web-socket": "~0.0.2",
|
||||||
"@ucap/ng-web-storage": "~0.0.3",
|
"@ucap/ng-web-storage": "~0.0.3",
|
||||||
"@ucap/ng-ui": "~0.0.4",
|
"@ucap/ng-ui": "~0.0.19",
|
||||||
"@ucap/ng-ui-organization": "~0.0.2",
|
"@ucap/ng-ui-organization": "~0.0.55",
|
||||||
"@ucap/ng-ui-authentication": "~0.0.16",
|
"@ucap/ng-ui-authentication": "~0.0.24",
|
||||||
"@ucap/ng-ui-group": "~0.0.4",
|
"@ucap/ng-ui-group": "~0.0.33",
|
||||||
|
"@ucap/ng-ui-chat": "~0.0.9",
|
||||||
|
"@ucap/ng-ui-material": "~0.0.4",
|
||||||
"@ucap/ng-ui-skin-default": "~0.0.1",
|
"@ucap/ng-ui-skin-default": "~0.0.1",
|
||||||
"@ucap/pi": "~0.0.5",
|
"@ucap/pi": "~0.0.5",
|
||||||
"@ucap/protocol": "~0.0.17",
|
"@ucap/protocol": "~0.0.17",
|
||||||
|
@ -86,7 +88,7 @@
|
||||||
"@ucap/protocol-event": "~0.0.5",
|
"@ucap/protocol-event": "~0.0.5",
|
||||||
"@ucap/protocol-file": "~0.0.4",
|
"@ucap/protocol-file": "~0.0.4",
|
||||||
"@ucap/protocol-group": "~0.0.5",
|
"@ucap/protocol-group": "~0.0.5",
|
||||||
"@ucap/protocol-info": "~0.0.5",
|
"@ucap/protocol-info": "~0.0.6",
|
||||||
"@ucap/protocol-inner": "~0.0.4",
|
"@ucap/protocol-inner": "~0.0.4",
|
||||||
"@ucap/protocol-option": "~0.0.7",
|
"@ucap/protocol-option": "~0.0.7",
|
||||||
"@ucap/protocol-ping": "~0.0.6",
|
"@ucap/protocol-ping": "~0.0.6",
|
||||||
|
@ -96,8 +98,9 @@
|
||||||
"@ucap/protocol-status": "~0.0.5",
|
"@ucap/protocol-status": "~0.0.5",
|
||||||
"@ucap/protocol-sync": "~0.0.4",
|
"@ucap/protocol-sync": "~0.0.4",
|
||||||
"@ucap/protocol-umg": "~0.0.5",
|
"@ucap/protocol-umg": "~0.0.5",
|
||||||
|
"@ucap/ui-scss": "~0.0.4",
|
||||||
"@ucap/web-socket": "~0.0.10",
|
"@ucap/web-socket": "~0.0.10",
|
||||||
"@ucap/web-storage": "~0.0.7",
|
"@ucap/web-storage": "~0.0.9",
|
||||||
"autolinker": "^3.13.0",
|
"autolinker": "^3.13.0",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"classlist.js": "^1.1.20150312",
|
"classlist.js": "^1.1.20150312",
|
||||||
|
|
|
@ -14,10 +14,16 @@ import { AppSessionResolver } from './resolvers/app-session.resolver';
|
||||||
import { AppAuthenticationService } from './services/app-authentication.service';
|
import { AppAuthenticationService } from './services/app-authentication.service';
|
||||||
import { AppNativeService } from './services/app-native.service';
|
import { AppNativeService } from './services/app-native.service';
|
||||||
import { AppService } from './services/app.service';
|
import { AppService } from './services/app.service';
|
||||||
|
import { AppChatService } from './services/app-chat.service';
|
||||||
|
|
||||||
const GUARDS = [AppAuthenticationGuard];
|
const GUARDS = [AppAuthenticationGuard];
|
||||||
const RESOLVERS = [AppSessionResolver];
|
const RESOLVERS = [AppSessionResolver];
|
||||||
const SERVICES = [AppService, AppAuthenticationService, AppNativeService];
|
const SERVICES = [
|
||||||
|
AppService,
|
||||||
|
AppAuthenticationService,
|
||||||
|
AppNativeService,
|
||||||
|
AppChatService
|
||||||
|
];
|
||||||
|
|
||||||
const axiosFactory = () => {
|
const axiosFactory = () => {
|
||||||
const i = axios.create();
|
const i = axios.create();
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
<app-layouts-top-bar></app-layouts-top-bar>
|
<div class="app-container">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
:host {
|
:host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto !important;
|
height: 100%;
|
||||||
|
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
|
||||||
import { StoreModule } from '@ngrx/store';
|
import { StoreModule } from '@ngrx/store';
|
||||||
import { EffectsModule } from '@ngrx/effects';
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
|
@ -65,6 +67,8 @@ import { environment } from '@environments';
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
|
|
||||||
|
FlexLayoutModule,
|
||||||
|
|
||||||
LoggerModule.forRoot({}),
|
LoggerModule.forRoot({}),
|
||||||
|
|
||||||
CommonApiModule.forRoot(environment.commonApiModuleConfig),
|
CommonApiModule.forRoot(environment.commonApiModuleConfig),
|
||||||
|
@ -100,7 +104,12 @@ import { environment } from '@environments';
|
||||||
OrganizationStoreModule.forRoot({}),
|
OrganizationStoreModule.forRoot({}),
|
||||||
AuthenticationStoreModule.forRoot({}),
|
AuthenticationStoreModule.forRoot({}),
|
||||||
GroupStoreModule.forRoot({ useMyDeptGroup: true, fixedGroupSeqs: [] }),
|
GroupStoreModule.forRoot({ useMyDeptGroup: true, fixedGroupSeqs: [] }),
|
||||||
ChatStoreModule.forRoot({}),
|
ChatStoreModule.forRoot({
|
||||||
|
eventRequestInitCount:
|
||||||
|
environment.productConfig.chat.eventRequestInitCount,
|
||||||
|
eventRequestDefaultCount:
|
||||||
|
environment.productConfig.chat.eventRequestDefaultCount
|
||||||
|
}),
|
||||||
|
|
||||||
OrganizationUiModule.forRoot({}),
|
OrganizationUiModule.forRoot({}),
|
||||||
|
|
||||||
|
|
|
@ -45,3 +45,11 @@ $lgRed-app-theme: mat-light-theme(
|
||||||
@include components-theme($lgRed-app-theme);
|
@include components-theme($lgRed-app-theme);
|
||||||
*/
|
*/
|
||||||
@include ucap-material-theme($lgRed-app-theme);
|
@include ucap-material-theme($lgRed-app-theme);
|
||||||
|
|
||||||
|
$ucap-ui-lg-red-theme: mat-light-theme(
|
||||||
|
$lgRed-app-primary,
|
||||||
|
$lgRed-app-accent,
|
||||||
|
$lgRed-app-warn
|
||||||
|
);
|
||||||
|
|
||||||
|
@include ucap-ui-theme($ucap-ui-lg-red-theme, $typography);
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<div class="layout-container" fxLayout="column">
|
||||||
|
<div class="layout-header" fxFlex="60px" fxLayout="row">
|
||||||
|
<div fxFlex="1 1 auto">
|
||||||
|
<ng-content
|
||||||
|
class="layout-header-content"
|
||||||
|
select="[appLayoutsDefaultDialog='header']"
|
||||||
|
></ng-content>
|
||||||
|
</div>
|
||||||
|
<div fxFlex="30px" *ngIf="!disableClose">
|
||||||
|
<button
|
||||||
|
class="icon-button btn-dialog-close"
|
||||||
|
(click)="onClickClose($event)"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-window-close"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layout-body" fxFlex="1 1 auto">
|
||||||
|
<perfect-scrollbar style="width: 100%; height: 100%;">
|
||||||
|
<ng-content
|
||||||
|
class="layout-body-content"
|
||||||
|
select="[appLayoutsDefaultDialog='body']"
|
||||||
|
></ng-content>
|
||||||
|
</perfect-scrollbar>
|
||||||
|
</div>
|
||||||
|
<div class="layout-action" fxFlex="60px">
|
||||||
|
<ng-content
|
||||||
|
class="layout-action-content"
|
||||||
|
select="[appLayoutsDefaultDialog='action']"
|
||||||
|
></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,25 @@
|
||||||
|
.layout-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.layout-header {
|
||||||
|
.layout-header-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.layout-body {
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.layout-body-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.layout-action {
|
||||||
|
.layout-action-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
|
||||||
|
import { DefaultDialogLayoutComponent } from './default-dialog.layout.component';
|
||||||
|
|
||||||
|
describe('app::layouts::DefaultDialogLayoutComponent', () => {
|
||||||
|
let component: DefaultDialogLayoutComponent;
|
||||||
|
let fixture: ComponentFixture<DefaultDialogLayoutComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [DefaultDialogLayoutComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DefaultDialogLayoutComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Inject,
|
||||||
|
ViewChild,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
ViewContainerRef,
|
||||||
|
ComponentRef,
|
||||||
|
Input,
|
||||||
|
EventEmitter,
|
||||||
|
Output
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-layouts-default-dialog',
|
||||||
|
templateUrl: './default-dialog.layout.component.html',
|
||||||
|
styleUrls: ['./default-dialog.layout.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class DefaultDialogLayoutComponent implements OnInit, OnDestroy {
|
||||||
|
@Input()
|
||||||
|
disableClose = false;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
closed = new EventEmitter<MouseEvent>();
|
||||||
|
|
||||||
|
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||||
|
|
||||||
|
private ngOnDestroySubject: Subject<boolean>;
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.ngOnDestroySubject = new Subject<boolean>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickClose(event: MouseEvent): void {
|
||||||
|
this.closed.emit(event);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,107 +1,271 @@
|
||||||
<div fxFlexFill class="layout-container">
|
<div class="layout-container" fxLayout="row">
|
||||||
<div class="navi-container">
|
<div class="navitab-page" fxFlex="0 0 60px">
|
||||||
<mat-tab-group
|
<div class="gnb">
|
||||||
#navTabGroup
|
<mat-toolbar class="mat-gnb-toolbar"
|
||||||
vertical
|
><img
|
||||||
disablePagination="true"
|
src="../../../assets/images/logo_140.png"
|
||||||
(selectedTabChange)="onSelectedTabChange($event)"
|
alt=""
|
||||||
>
|
class="img-logo"
|
||||||
<mat-tab>
|
width="32"
|
||||||
<ng-template mat-tab-label>
|
/></mat-toolbar>
|
||||||
<div class="icon-item">
|
<mat-tab-group
|
||||||
<svg
|
mat-stretch-tabs
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
animationDuration="0ms"
|
||||||
width="30"
|
backgroundColor="transparent"
|
||||||
height="30"
|
class="global-menu"
|
||||||
viewBox="0 0 24 24"
|
(selectedTabChange)="onSelectedTabChange($event)"
|
||||||
fill="none"
|
>
|
||||||
stroke="currentColor"
|
<mat-tab aria-label="Group">
|
||||||
stroke-width="1.5"
|
<ng-template mat-tab-label>
|
||||||
stroke-linecap="butt"
|
<div class="icon-item" matTooltip="그룹" matTooltipPosition="after">
|
||||||
stroke-linejoin="round"
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16.871"
|
||||||
|
height="16.8"
|
||||||
|
viewBox="0 0 16.871 16.8"
|
||||||
|
stroke-linecap="butt"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
id="icon_gnb_group_g32"
|
||||||
|
transform="translate(-17.815 -103.827)"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
id="prefix_22"
|
||||||
|
d="M18.871 21a7.733 7.733 0 0 0-7.435-8A7.733 7.733 0 0 0 4 21"
|
||||||
|
data-name="prefix_22"
|
||||||
|
transform="translate(14.815 98.627)"
|
||||||
|
style="
|
||||||
|
fill: transparent;
|
||||||
|
/*stroke: #999;*/
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
stroke-width: 2px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<g id="prefix_23" data-name="prefix_23" style="fill: none;">
|
||||||
|
<path
|
||||||
|
d="M4.093 0a4.047 4.047 0 0 1 4.092 4 4.1 4.1 0 0 1-1.591 3.167A3.931 3.931 0 0 1 4.093 8 4.047 4.047 0 0 1 0 4a4.047 4.047 0 0 1 4.093-4z"
|
||||||
|
style="stroke: none;"
|
||||||
|
transform="translate(22.157 103.827)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4.093 2C2.939 2 2 2.897 2 4s.939 2 2.093 2c.604 0 1.062-.233 1.16-.317l.017-.016.018-.015c.804-.694.897-1.275.897-1.652 0-1.103-.938-2-2.092-2m0-2c2.26 0 4.092 1.79 4.092 4 0 1.184-.526 2.248-1.591 3.167C6.098 7.614 5.14 8 4.093 8 1.833 8 0 6.21 0 4s1.832-4 4.093-4z"
|
||||||
|
style="fill: #999; stroke: none;"
|
||||||
|
transform="translate(22.157 103.827)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab aria-label="Chat">
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<div
|
||||||
|
class="icon-item"
|
||||||
|
matBadgeHidden="false"
|
||||||
|
matBadge="275"
|
||||||
|
matBadgeColor="accent"
|
||||||
|
matBadgePosition="above after"
|
||||||
>
|
>
|
||||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
<svg
|
||||||
<circle cx="12" cy="7" r="4"></circle>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</svg>
|
width="16.8"
|
||||||
</div>
|
height="16"
|
||||||
</ng-template>
|
viewBox="0 0 16.8 16"
|
||||||
</mat-tab>
|
>
|
||||||
<mat-tab>
|
<g
|
||||||
<ng-template mat-tab-label>
|
id="icon_gnb_chat_g32"
|
||||||
<div class="icon-item">
|
style="fill: none; stroke-linejoin: round;"
|
||||||
<svg
|
>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path
|
||||||
width="30"
|
d="M39.972 26.892a7.688 7.688 0 0 0-8.983 0 4.344 4.344 0 0 0 0 7.194 7.334 7.334 0 0 0 4.492 1.449c.172 0 .346-.006.519-.017l1.09 1.725a.437.437 0 0 0 .36.2h.01a.438.438 0 0 0 .359-.184l2.308-3.3a4.33 4.33 0 0 0-.154-7.07z"
|
||||||
height="30"
|
style="stroke: none;"
|
||||||
viewBox="0 0 24 24"
|
transform="translate(-27.08 -23.443)"
|
||||||
fill="none"
|
/>
|
||||||
stroke="#000000"
|
<path
|
||||||
stroke-width="1.5"
|
d="M35.48 25.443c-1.693 0-3.288.514-4.492 1.449-1.23.955-1.908 2.232-1.908 3.597 0 1.364.678 2.642 1.908 3.597 1.204.934 2.8 1.449 4.492 1.449.172 0 .346-.006.519-.017l1.09 1.725c.076.121.212.196.36.2h.01c.143 0 .278-.069.358-.184l2.308-3.297c1.133-.94 1.755-2.17 1.755-3.473 0-1.365-.678-2.643-1.908-3.597-1.204-.935-2.8-1.45-4.492-1.45m0-2c2.134 0 4.165.664 5.718 1.87 1.73 1.342 2.682 3.18 2.682 5.177 0 1.817-.8 3.52-2.259 4.824l-2.165 3.093c-.454.65-1.201 1.037-1.997 1.037h-.063c-.817-.022-1.563-.444-1.998-1.131l-.501-.794c-1.92-.11-3.724-.757-5.135-1.852-1.73-1.342-2.682-3.18-2.682-5.177 0-1.997.952-3.835 2.682-5.177 1.553-1.205 3.584-1.87 5.718-1.87z"
|
||||||
stroke-linecap="butt"
|
style="fill: #999; stroke: none;"
|
||||||
stroke-linejoin="bevel"
|
transform="translate(-27.08 -23.443)"
|
||||||
>
|
/>
|
||||||
<path
|
</g>
|
||||||
d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"
|
</svg>
|
||||||
></path>
|
</div>
|
||||||
</svg>
|
</ng-template>
|
||||||
</div>
|
</mat-tab>
|
||||||
</ng-template>
|
<mat-tab aria-label="Organization">
|
||||||
</mat-tab>
|
<ng-template mat-tab-label>
|
||||||
<mat-tab>
|
<div class="icon-item">
|
||||||
<ng-template mat-tab-label>
|
<svg
|
||||||
<div class="icon-item">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<svg
|
width="21"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
height="19"
|
||||||
width="30"
|
viewBox="0 0 21 19"
|
||||||
height="30"
|
>
|
||||||
viewBox="0 0 24 24"
|
<g
|
||||||
fill="none"
|
id="icon_gnb_organiztion_g32"
|
||||||
stroke="#000000"
|
transform="translate(-12.917 -220.25)"
|
||||||
stroke-width="1.5"
|
>
|
||||||
stroke-linecap="butt"
|
<g class="prefix__cls-1" transform="translate(19.917 220.25)">
|
||||||
stroke-linejoin="bevel"
|
<circle cx="3.5" cy="3.5" r="3.5" class="prefix__cls-3" />
|
||||||
>
|
<circle cx="3.5" cy="3.5" r="2.5" class="prefix__cls-4" />
|
||||||
<circle class="st0" cx="18.4" cy="18.5" r="3" />
|
</g>
|
||||||
<circle class="st0" cx="12" cy="5" r="3" />
|
<g class="prefix__cls-1" transform="translate(12.917 232.25)">
|
||||||
<path class="st0" d="M12.4,10.5h4c1.1,0,2,0.9,2,2v3" />
|
<circle cx="3.5" cy="3.5" r="3.5" class="prefix__cls-3" />
|
||||||
<circle class="st0" cx="6.1" cy="18.5" r="3" />
|
<circle cx="3.5" cy="3.5" r="2.5" class="prefix__cls-4" />
|
||||||
<path class="st0" d="M6.1,15.5v-3c0-1.1,0.9-2,2-2h4" />
|
</g>
|
||||||
<line class="st0" x1="12" y1="8" x2="12" y2="9" />
|
<g class="prefix__cls-1" transform="translate(19.917 232.25)">
|
||||||
</svg>
|
<circle cx="3.5" cy="3.5" r="3.5" class="prefix__cls-3" />
|
||||||
</div>
|
<circle cx="3.5" cy="3.5" r="2.5" class="prefix__cls-4" />
|
||||||
</ng-template>
|
</g>
|
||||||
</mat-tab>
|
<g class="prefix__cls-1" transform="translate(26.917 232.25)">
|
||||||
<mat-tab>
|
<circle cx="3.5" cy="3.5" r="3.5" class="prefix__cls-3" />
|
||||||
<ng-template mat-tab-label>
|
<circle cx="3.5" cy="3.5" r="2.5" class="prefix__cls-4" />
|
||||||
<div class="icon-item">
|
</g>
|
||||||
<svg
|
<path
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
d="M16.5 233.312v-3.437h13.833v3.438"
|
||||||
width="30"
|
transform="translate(0 -1.087)"
|
||||||
height="30"
|
style="
|
||||||
viewBox="0 0 24 24"
|
stroke-linecap: round;
|
||||||
fill="none"
|
stroke-linejoin: round;
|
||||||
stroke="#000000"
|
stroke: #999;
|
||||||
stroke-width="1.5"
|
stroke-width: 2px;
|
||||||
stroke-linecap="butt"
|
fill: none;
|
||||||
stroke-linejoin="bevel"
|
"
|
||||||
>
|
/>
|
||||||
<polygon
|
<path
|
||||||
points="21.368 12.001 3 21.609 3 14 11 12 3 9.794 3 2.394"
|
d="M0 0L0 6"
|
||||||
/>
|
class="prefix__cls-1"
|
||||||
</svg>
|
transform="translate(23.417 226.75)"
|
||||||
</div>
|
/>
|
||||||
</ng-template>
|
</g>
|
||||||
</mat-tab>
|
</svg>
|
||||||
</mat-tab-group>
|
</div>
|
||||||
</div>
|
</ng-template>
|
||||||
<div class="content-container" fxFlexFill>
|
</mat-tab>
|
||||||
<mat-sidenav-container autosize="true" fxFlexFill>
|
|
||||||
<mat-sidenav #leftSidenav class="left-sidenav" mode="side" opened="true">
|
<mat-tab aria-label="Message">
|
||||||
<router-outlet></router-outlet>
|
<ng-template mat-tab-label>
|
||||||
</mat-sidenav>
|
<div class="icon-item">
|
||||||
<div fxFlex="1 1 auto">
|
<svg
|
||||||
<router-outlet name="content"></router-outlet>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="21.096"
|
||||||
|
height="19.182"
|
||||||
|
viewBox="0 0 21.096 19.182"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
id="icon_gnb_message_g32"
|
||||||
|
transform="translate(-12.615 -280.248)"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M16.063 292.447l7.965 2.4 7.465 5.3 3.666-16.615z"
|
||||||
|
class="prefix__cls-1"
|
||||||
|
transform="translate(-2.448 -2.284)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M23.25 277.406l.18 5.668 4-2.692z"
|
||||||
|
class="prefix__cls-1"
|
||||||
|
transform="translate(-2.096 15.355)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0 10.416L11.025 0"
|
||||||
|
transform="translate(21.154 282.346)"
|
||||||
|
style="fill: none; stroke: #999; stroke-width: 2px;"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</mat-tab>
|
||||||
|
|
||||||
|
<mat-tab aria-label="Call">
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<div class="icon-item">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
>
|
||||||
|
<g id="icon_gnb_call_g32" transform="translate(-1.337 -1.337)">
|
||||||
|
<g style="fill: none;">
|
||||||
|
<path
|
||||||
|
d="M15.832 20A13.864 13.864 0 0 1 2 6.14 4.136 4.136 0 0 1 6.132 2a3.532 3.532 0 0 1 .692.063 3.4 3.4 0 0 1 .647.162.9.9 0 0 1 .584.675l1.229 5.4a.9.9 0 0 1-.234.828c-.117.126-.126.135-1.231.711a8.91 8.91 0 0 0 4.374 4.4c.584-1.116.593-1.125.719-1.242a.9.9 0 0 1 .826-.234L19.129 14a.9.9 0 0 1 .647.585 3.912 3.912 0 0 1 .171.657 4.3 4.3 0 0 1 .054.684A4.136 4.136 0 0 1 15.832 20z"
|
||||||
|
style="stroke: none;"
|
||||||
|
transform="translate(-.663 -.663)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.87 18c1.152 0 2.105-.938 2.13-2.094 0-.039-.001-.078-.004-.117l-3.89-.89-.14.268-.882 1.687-1.728-.798C8.985 14.96 7.083 13.047 6 10.667l-.785-1.725 1.68-.877.255-.132-.896-3.93L6.159 4h-.027C4.956 4 4 4.96 4 6.137c.004 3.168 1.237 6.146 3.472 8.385 2.234 2.239 5.204 3.474 8.363 3.478h.035m0 2h-.038C8.197 19.99 2.01 13.79 2 6.14 2 3.854 3.85 2 6.132 2h.027c.223 0 .445.021.664.063.22.033.437.087.647.162.3.106.522.362.584.675l1.23 5.4c.068.298-.02.61-.233.828-.117.126-.126.135-1.23.71.884 1.946 2.436 3.507 4.374 4.402.583-1.116.592-1.125.718-1.242.17-.166.396-.256.628-.256.066 0 .133.007.199.022l5.389 1.233c.302.07.546.291.646.585.076.213.133.433.171.657.036.226.054.455.054.684C19.966 18.186 18.124 20 15.87 20z"
|
||||||
|
style="fill: #999; stroke: none;"
|
||||||
|
transform="translate(-.663 -.663)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
|
||||||
|
<div class="btn-homepage-area">
|
||||||
|
<button mat-button aria-label="">
|
||||||
|
<em>Homepage</em>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-sidenav-container>
|
</div>
|
||||||
|
<ucap-float-action-button
|
||||||
|
*ngIf="fabButtonShow"
|
||||||
|
[buttons]="fabButtons"
|
||||||
|
(buttonClick)="onClickFab($event)"
|
||||||
|
>
|
||||||
|
</ucap-float-action-button>
|
||||||
|
</div>
|
||||||
|
<div class="content-page" fxFlex="1 1 auto" fxLayout="column">
|
||||||
|
<div class="content-body" fxFlex="1 1 auto">
|
||||||
|
<mat-sidenav-container autosize="true">
|
||||||
|
<mat-sidenav
|
||||||
|
#leftSidenav
|
||||||
|
class="left-sidenav"
|
||||||
|
mode="side"
|
||||||
|
opened="true"
|
||||||
|
>
|
||||||
|
<div class="left-sidenav-container" fxLayout="column">
|
||||||
|
<div class="top-bar" fxFlex="0 0 40px">
|
||||||
|
M-Messenger
|
||||||
|
</div>
|
||||||
|
<div fxFlex="1 1 auto">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-sidenav>
|
||||||
|
|
||||||
|
<mat-sidenav-content>
|
||||||
|
<div class="content-sidenav-container" fxLayout="column">
|
||||||
|
<div class="content-sidenav-top-bar" fxFlex="0 0 40px">
|
||||||
|
<app-layouts-top-bar></app-layouts-top-bar>
|
||||||
|
</div>
|
||||||
|
<div class="content-sidenav-body" fxFlex="1 1 auto">
|
||||||
|
<router-outlet name="content"></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-sidenav-content>
|
||||||
|
</mat-sidenav-container>
|
||||||
|
</div>
|
||||||
|
<div class="content-statusbar" fxFlex="0 0 38px">
|
||||||
|
<!--Footer-->
|
||||||
|
<div class="footer">
|
||||||
|
<div class="foot-info version-info">
|
||||||
|
<span class="var-txt current-ver">현재버전 0.0.11</span>
|
||||||
|
<span class="var-txt new-var">최신버전 0.0.11 </span>
|
||||||
|
<button mat-icon-button aria-label="icon">
|
||||||
|
<mat-icon>get_app</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="foot-info help-info">
|
||||||
|
<p><span>Help</span>Desk <em>1661-9066</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- //Footer-->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,22 +1,303 @@
|
||||||
|
@import '~@ucap/lg-scss/mixins';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.layout-container {
|
.layout-container {
|
||||||
display: flex;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.navi-container {
|
.navitab-page {
|
||||||
width: 70px;
|
//GNB /////////////////////////////////////
|
||||||
}
|
.gnb {
|
||||||
|
background-color: $gray-ref0;
|
||||||
.content-container {
|
width: 60px;
|
||||||
.left-sidenav {
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 370px;
|
justify-content: space-between;
|
||||||
height: 100%;
|
align-items: center;
|
||||||
max-width: 90%;
|
border-right: 1px solid rgba(204, 204, 204, 0.8);
|
||||||
overflow: hidden;
|
.mat-gnb-toolbar {
|
||||||
|
flex-basis: 64px;
|
||||||
|
.img-logo {
|
||||||
|
margin: 9px 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.left-container {
|
||||||
|
display: flex;
|
||||||
|
width: calc(100% - 28px);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.global-menu {
|
||||||
|
width: 100%;
|
||||||
|
background-color: $gray-ref0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.btn-homepage-area {
|
||||||
|
flex-flow: column-reverse;
|
||||||
|
position: relative;
|
||||||
|
button {
|
||||||
|
padding: 30px 0 12px;
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-image: url(../../../assets/images/ico/btn_gnb_hompage.svg);
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
left: calc(50% - 10px);
|
||||||
|
}
|
||||||
|
em {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 8px;
|
||||||
|
color: $gray-re6;
|
||||||
|
display: block;
|
||||||
|
line-height: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::ng-deep .global-menu {
|
||||||
|
//display: flex;
|
||||||
|
//flex-direction: row;
|
||||||
|
.mat-tab-header {
|
||||||
|
border-bottom: none !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.mat-tab-label-container {
|
||||||
|
.mat-tab-list {
|
||||||
|
.mat-tab-labels {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 272px;
|
||||||
|
border-bottom: none;
|
||||||
|
|
||||||
|
.mat-tab-label {
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 0 !important;
|
||||||
|
.mat-tab-label-content {
|
||||||
|
.icon-item {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
//transform: scale(0.9);
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0, 1);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
//width: 24px;
|
||||||
|
//height: 24px;
|
||||||
|
stroke: $gray-re9;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-linecap: square;
|
||||||
|
stroke-linejoin: miter;
|
||||||
|
fill: none;
|
||||||
|
g {
|
||||||
|
&#icon_gnb_organiztion_g32 {
|
||||||
|
.prefix__cls-1,
|
||||||
|
.prefix__cls-4 {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.prefix__cls-1 {
|
||||||
|
stroke: #999;
|
||||||
|
stroke-width: 2px;
|
||||||
|
}
|
||||||
|
.prefix__cls-3 {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&#icon_gnb_message_g32 {
|
||||||
|
.prefix__cls-1 {
|
||||||
|
fill: none;
|
||||||
|
stroke: #999;
|
||||||
|
stroke-width: 2px;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mat-badge-content {
|
||||||
|
right: -9px !important;
|
||||||
|
border: 1px solid #ffbf2a;
|
||||||
|
//width: 24px;
|
||||||
|
//height: 24px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
top: -10px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.mat-tab-label-active {
|
||||||
|
opacity: 0;
|
||||||
|
svg {
|
||||||
|
stroke: #fff !important;
|
||||||
|
g {
|
||||||
|
&#prefix_23,
|
||||||
|
&#icon_gnb_chat_g32,
|
||||||
|
&#icon_gnb_call_g32 {
|
||||||
|
path {
|
||||||
|
&:nth-child(2) {
|
||||||
|
fill: #fff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&#icon_gnb_organiztion_g32 {
|
||||||
|
.prefix__cls-1 {
|
||||||
|
stroke: #fff !important;
|
||||||
|
}
|
||||||
|
path {
|
||||||
|
&:nth-last-of-type(2) {
|
||||||
|
stroke: #fff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&#icon_gnb_message_g32 {
|
||||||
|
.prefix__cls-1 {
|
||||||
|
stroke: #fff !important;
|
||||||
|
}
|
||||||
|
path {
|
||||||
|
&:nth-child(3) {
|
||||||
|
stroke: #fff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[aria-selected='true'] {
|
||||||
|
opacity: 1;
|
||||||
|
.mat-tab-label-content {
|
||||||
|
.icon-item {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mat-ink-bar {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mat-tab-body-wrapper {
|
||||||
|
.mat-tab-body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/////////////////////////////////////GNB //
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-page {
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.content-body {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 38px);
|
||||||
|
|
||||||
|
mat-sidenav-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.left-sidenav {
|
||||||
|
width: 370px;
|
||||||
|
max-width: 90%;
|
||||||
|
|
||||||
|
.left-sidenav-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.top-bar {
|
||||||
|
font: {
|
||||||
|
size: 13px;
|
||||||
|
color: $gray-re70;
|
||||||
|
}
|
||||||
|
line-height: 15px;
|
||||||
|
padding: 25px 0 0 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-sidenav-content {
|
||||||
|
.content-sidenav-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.content-sidenav-body {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-drawer {
|
.content-statusbar {
|
||||||
flex: 0 0 auto;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
//Footer ////////////////////////
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
height: 38px;
|
||||||
|
border-top: 1px solid $line-color-gray01;
|
||||||
|
background-color: $white;
|
||||||
|
.foot-info {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $gray-re70;
|
||||||
|
@include font-family($font-light);
|
||||||
|
font-weight: 600;
|
||||||
|
&.version-info {
|
||||||
|
.var-txt {
|
||||||
|
padding-left: 8px;
|
||||||
|
color: $gray-re70;
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
width: 1px;
|
||||||
|
height: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #d4d4d4;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
&:first-of-type {
|
||||||
|
&::before {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.new-var {
|
||||||
|
color: $brown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.help-info {
|
||||||
|
flex-flow: row-reverse;
|
||||||
|
padding-right: 20px;
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
span {
|
||||||
|
color: $lipstick;
|
||||||
|
}
|
||||||
|
em {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//////////////////////// Footer//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,12 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild('leftSidenav', { static: true })
|
@ViewChild('leftSidenav', { static: true })
|
||||||
leftSidenav: MatSidenav;
|
leftSidenav: MatSidenav;
|
||||||
|
|
||||||
|
showStatusbar = true;
|
||||||
|
|
||||||
|
/** FAB */
|
||||||
|
fabButtonShow = true;
|
||||||
|
fabButtons: { icon: string; tooltip?: string; divisionType?: string }[];
|
||||||
|
|
||||||
private windowSizeSubscription: Subscription;
|
private windowSizeSubscription: Subscription;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -50,6 +56,8 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setTabGroup(this.router.url);
|
this.setTabGroup(this.router.url);
|
||||||
|
|
||||||
|
this.setFabInitial(NAVS[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
@ -70,6 +78,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
||||||
NAVS[event.index],
|
NAVS[event.index],
|
||||||
{ outlets: { content: 'index' } }
|
{ outlets: { content: 'index' } }
|
||||||
]);
|
]);
|
||||||
|
this.setFabInitial(NAVS[event.index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickToggleLeftSidenav() {
|
onClickToggleLeftSidenav() {
|
||||||
|
@ -81,8 +90,107 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setTabGroup(url: string) {
|
private setTabGroup(url: string) {
|
||||||
this.navTabGroup.selectedIndex = NAVS.findIndex((v) =>
|
if (!!this.navTabGroup) {
|
||||||
url.startsWith(`/${v}`)
|
this.navTabGroup.selectedIndex = NAVS.findIndex((v) =>
|
||||||
);
|
url.startsWith(`/${v}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFabInitial(type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case 'group':
|
||||||
|
{
|
||||||
|
this.fabButtonShow = true;
|
||||||
|
this.fabButtons = [
|
||||||
|
{
|
||||||
|
icon: 'add',
|
||||||
|
tooltip: '그룹 추가',
|
||||||
|
divisionType: 'GROUP_NEW_ADD'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'chat':
|
||||||
|
{
|
||||||
|
this.fabButtonShow = true;
|
||||||
|
this.fabButtons = [
|
||||||
|
{
|
||||||
|
icon: 'chat',
|
||||||
|
tooltip: '대화 추가',
|
||||||
|
divisionType: 'CAHT_NEW_ADD'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// if (environment.productConfig.CommonSetting.useTimerRoom) {
|
||||||
|
// this.fabButtons.push({
|
||||||
|
// icon: 'timer',
|
||||||
|
// tooltip: this.translateService.instant('chat.newTimerChat'),
|
||||||
|
// divisionType: 'CHAT_NEW_TIMER_ADD'
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'organization':
|
||||||
|
{
|
||||||
|
this.fabButtonShow = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'message':
|
||||||
|
{
|
||||||
|
this.fabButtonShow = true;
|
||||||
|
this.fabButtons = [
|
||||||
|
{
|
||||||
|
icon: 'add',
|
||||||
|
tooltip: '쪽지 추가',
|
||||||
|
divisionType: 'MESSAGE_NEW'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// case MainMenu.Call:
|
||||||
|
// {
|
||||||
|
// this.fabButtonShow = false;
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
this.fabButtonShow = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickFab(params: { btn: any }) {
|
||||||
|
const btn = params.btn as {
|
||||||
|
icon: string;
|
||||||
|
tooltip?: string;
|
||||||
|
divisionType?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (btn.divisionType) {
|
||||||
|
case 'GROUP_NEW_ADD':
|
||||||
|
{
|
||||||
|
this.logService.debug('GROUP_NEW_ADD');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'CAHT_NEW_ADD':
|
||||||
|
{
|
||||||
|
this.logService.debug('CAHT_NEW_ADD');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'CHAT_NEW_TIMER_ADD':
|
||||||
|
{
|
||||||
|
// if (environment.productConfig.CommonSetting.useTimerRoom) {
|
||||||
|
// this.onClickNewChat('TIMER');
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'MESSAGE_NEW':
|
||||||
|
{
|
||||||
|
this.logService.debug('MESSAGE_NEW');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,15 @@ import { TopBarComponent } from './top-bar.component';
|
||||||
import { DefaultLayoutComponent } from './default.layout.component';
|
import { DefaultLayoutComponent } from './default.layout.component';
|
||||||
import { NoNaviLayoutComponent } from './no-navi.layout.component';
|
import { NoNaviLayoutComponent } from './no-navi.layout.component';
|
||||||
|
|
||||||
|
import { DefaultDialogLayoutComponent } from './default-dialog.layout.component';
|
||||||
|
import { SelectorLayoutComponent } from './selector.layout.component';
|
||||||
|
|
||||||
export const COMPONENTS = [
|
export const COMPONENTS = [
|
||||||
TopBarComponent,
|
TopBarComponent,
|
||||||
DefaultLayoutComponent,
|
DefaultLayoutComponent,
|
||||||
NoNaviLayoutComponent
|
NoNaviLayoutComponent,
|
||||||
|
|
||||||
|
DefaultDialogLayoutComponent,
|
||||||
|
|
||||||
|
SelectorLayoutComponent
|
||||||
];
|
];
|
||||||
|
|
|
@ -1 +1,8 @@
|
||||||
<router-outlet></router-outlet>
|
<div class="layout-container" fxFlexFill fxLayout="column">
|
||||||
|
<div *ngIf="showTopbar" class="top-bar" fxFlex="50px">
|
||||||
|
<app-layouts-top-bar></app-layouts-top-bar>
|
||||||
|
</div>
|
||||||
|
<div class="layout-content" fxFlex="1 1 auto">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
.layout-container {
|
||||||
|
}
|
|
@ -6,5 +6,7 @@ import { Component } from '@angular/core';
|
||||||
styleUrls: ['./no-navi.layout.component.scss']
|
styleUrls: ['./no-navi.layout.component.scss']
|
||||||
})
|
})
|
||||||
export class NoNaviLayoutComponent {
|
export class NoNaviLayoutComponent {
|
||||||
|
showTopbar = true;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
}
|
}
|
||||||
|
|
29
src/app/layouts/components/selector.layout.component.html
Normal file
29
src/app/layouts/components/selector.layout.component.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<div class="selector">
|
||||||
|
<div class="selector-title">
|
||||||
|
<ng-content
|
||||||
|
class="layout-header-content"
|
||||||
|
select="[appLayoutsSelector='header']"
|
||||||
|
></ng-content>
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
matSuffix
|
||||||
|
aria-label="Clear"
|
||||||
|
class="btn-close"
|
||||||
|
color="accent"
|
||||||
|
>
|
||||||
|
<mat-icon>highlight_off</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="selector-contents">
|
||||||
|
<ng-content
|
||||||
|
class="layout-body-content"
|
||||||
|
select="[appLayoutsSelector='body']"
|
||||||
|
></ng-content>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<ng-content
|
||||||
|
class="layout-footer-content"
|
||||||
|
select="[appLayoutsSelector='footer']"
|
||||||
|
></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
26
src/app/layouts/components/selector.layout.component.spec.ts
Normal file
26
src/app/layouts/components/selector.layout.component.spec.ts
Normal 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 { SelectorLayoutComponent } from './selector.layout.component';
|
||||||
|
|
||||||
|
describe('ucap::ui-group::SelectorLayoutComponent', () => {
|
||||||
|
let component: SelectorLayoutComponent;
|
||||||
|
let fixture: ComponentFixture<SelectorLayoutComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [SelectorLayoutComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SelectorLayoutComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
22
src/app/layouts/components/selector.layout.component.ts
Normal file
22
src/app/layouts/components/selector.layout.component.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
Input,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-layout-selector',
|
||||||
|
templateUrl: './selector.layout.component.html',
|
||||||
|
styleUrls: ['./selector.layout.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class SelectorLayoutComponent implements OnInit, OnDestroy {
|
||||||
|
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
<mat-toolbar class=".title-bar">
|
<div class="title-bar">
|
||||||
<ucap-title-bar
|
<ucap-title-bar
|
||||||
[platform]="platform"
|
[platform]="platform"
|
||||||
[native]="native"
|
[native]="native"
|
||||||
|
@ -7,4 +7,4 @@
|
||||||
(minimized)="onMinimizedTitleBar()"
|
(minimized)="onMinimizedTitleBar()"
|
||||||
>
|
>
|
||||||
</ucap-title-bar>
|
</ucap-title-bar>
|
||||||
</mat-toolbar>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
.title-bar {
|
.title-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 100%;
|
||||||
-webkit-user-select: none;
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
display: flex;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
cursor: pointer;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,14 @@ import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||||
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
|
||||||
|
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
|
||||||
|
|
||||||
import { UiModule } from '@ucap/ng-ui';
|
import { UiModule } from '@ucap/ng-ui';
|
||||||
|
|
||||||
import { COMPONENTS } from './components';
|
import { COMPONENTS } from './components';
|
||||||
|
@ -18,16 +21,18 @@ import { DIALOGS } from './dialogs';
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|
||||||
RouterModule,
|
RouterModule,
|
||||||
|
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
|
|
||||||
|
MatButtonModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
MatTabsModule,
|
|
||||||
MatSidenavModule,
|
MatSidenavModule,
|
||||||
|
MatTabsModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
|
|
||||||
|
PerfectScrollbarModule,
|
||||||
|
|
||||||
UiModule
|
UiModule
|
||||||
],
|
],
|
||||||
exports: [...COMPONENTS, ...DIALOGS],
|
exports: [...COMPONENTS, ...DIALOGS],
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
|
||||||
|
import { AppAuthenticationModule } from '@app/ucap/authentication/authentication.module';
|
||||||
import { AppAccountSectionModule } from '@app/sections/account/account.section.module';
|
import { AppAccountSectionModule } from '@app/sections/account/account.section.module';
|
||||||
|
|
||||||
import { AppAccountRoutingPageModule } from './account-routing.page.module';
|
import { AppAccountRoutingPageModule } from './account-routing.page.module';
|
||||||
|
@ -13,6 +14,7 @@ import { COMPONENTS } from './components';
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
|
AppAuthenticationModule,
|
||||||
AppAccountSectionModule,
|
AppAccountSectionModule,
|
||||||
AppAccountRoutingPageModule
|
AppAccountRoutingPageModule
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,22 +1,3 @@
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<app-sections-account-login
|
<app-authentication-login></app-authentication-login>
|
||||||
[companyGroupCode]="companyGroupCode"
|
|
||||||
[fixedCompanyCode]="fixedCompanyCode"
|
|
||||||
[userStore]="userStore"
|
|
||||||
[useRememberMe]="useRememberMe"
|
|
||||||
[useAutoLogin]="useAutoLogin"
|
|
||||||
></app-sections-account-login>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="login-page-container" fxLayout="row">
|
|
||||||
<div fxFlex="1 1 auto">Login</div>
|
|
||||||
<div class="login-section-container">
|
|
||||||
<app-sections-account-login
|
|
||||||
[companyGroupCode]="companyGroupCode"
|
|
||||||
[fixedCompanyCode]="fixedCompanyCode"
|
|
||||||
[userStore]="userStore"
|
|
||||||
[useRememberMe]="useRememberMe"
|
|
||||||
[useAutoLogin]="useAutoLogin"
|
|
||||||
></app-sections-account-login>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
@import '../../../../assets/scss/components';
|
@import '~@ucap/lg-scss/mixins';
|
||||||
|
|
||||||
|
$login-bg-w: 100/1920;
|
||||||
|
$login-bg-h: 100/1080;
|
||||||
|
|
||||||
.login-container {
|
.login-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
// box-sizing: border-box;
|
||||||
|
// display: flex;
|
||||||
|
// flex-direction: column;
|
||||||
|
|
||||||
background-color: $bg-gray;
|
background-color: $bg-gray;
|
||||||
background-image: url(../../../../assets/images/bg/bg_login_circle_square01.svg),
|
background-image: url(../../../../assets/images/bg/bg_login_circle_square01.svg),
|
||||||
url(../../../../assets/images/bg/bg_login_circle_stroke01.svg),
|
url(../../../../assets/images/bg/bg_login_circle_stroke01.svg),
|
||||||
|
@ -54,8 +62,4 @@
|
||||||
unquote($login-bg-w * 102 + '%'), unquote($login-bg-w * 130 + '%'),
|
unquote($login-bg-w * 102 + '%'), unquote($login-bg-w * 130 + '%'),
|
||||||
unquote($login-bg-w * 184 + '%'), unquote($login-bg-w * 370 + '%'),
|
unquote($login-bg-w * 184 + '%'), unquote($login-bg-w * 370 + '%'),
|
||||||
unquote($login-bg-w * 122 + '%'), unquote($login-bg-w * 75 + '%');
|
unquote($login-bg-w * 122 + '%'), unquote($login-bg-w * 75 + '%');
|
||||||
padding-top: 5%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { IndexPageComponent } from './components/index.page.component';
|
import { IndexPageComponent } from './components/index.page.component';
|
||||||
import { SidenavPageComponent } from './components/sidenav.page.component';
|
import { SidenavPageComponent } from './components/sidenav.page.component';
|
||||||
|
import { ChatRoomPageComponent } from './components/chat-room.page.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -10,6 +11,11 @@ const routes: Routes = [
|
||||||
outlet: 'content',
|
outlet: 'content',
|
||||||
component: IndexPageComponent
|
component: IndexPageComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'chatroom',
|
||||||
|
outlet: 'content',
|
||||||
|
component: ChatRoomPageComponent
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: SidenavPageComponent
|
component: SidenavPageComponent
|
||||||
|
|
|
@ -3,17 +3,35 @@ import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||||
|
|
||||||
|
import { AppChatSectionModule } from '@app/sections/chat/chat.section.module';
|
||||||
|
|
||||||
import { AppChatRoutingPageModule } from './chat-routing.page.module';
|
import { AppChatRoutingPageModule } from './chat-routing.page.module';
|
||||||
|
|
||||||
import { IndexPageComponent } from './components/index.page.component';
|
import { UiModule } from '@ucap/ng-ui';
|
||||||
import { SidenavPageComponent } from './components/sidenav.page.component';
|
import { COMPONENTS } from './components';
|
||||||
|
|
||||||
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent];
|
|
||||||
|
|
||||||
export { IndexPageComponent, SidenavPageComponent };
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, FlexLayoutModule, AppChatRoutingPageModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
|
||||||
|
MatIconModule,
|
||||||
|
MatMenuModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatSidenavModule,
|
||||||
|
|
||||||
|
AppChatSectionModule,
|
||||||
|
AppChatRoutingPageModule,
|
||||||
|
|
||||||
|
UiModule
|
||||||
|
],
|
||||||
declarations: [...COMPONENTS],
|
declarations: [...COMPONENTS],
|
||||||
entryComponents: []
|
entryComponents: []
|
||||||
})
|
})
|
||||||
|
|
24
src/app/pages/chat/components/chat-room.page.component.html
Normal file
24
src/app/pages/chat/components/chat-room.page.component.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<div class="contents-main" fxFlexFill fxLayout="column">
|
||||||
|
<div class="subtitle" fxFlex="0 0 50px">
|
||||||
|
<app-sections-chat-info
|
||||||
|
[roomId]="roomId"
|
||||||
|
(openChatSearch)="isChatSearch = true"
|
||||||
|
(rightDrawerToggle)="onRightDrawerToggle()"
|
||||||
|
></app-sections-chat-info>
|
||||||
|
</div>
|
||||||
|
<app-sections-chat-chat-search
|
||||||
|
*ngIf="isChatSearch"
|
||||||
|
(closeChatSearch)="isChatSearch = false"
|
||||||
|
></app-sections-chat-chat-search>
|
||||||
|
<mat-drawer-container autosize fxFlex="1 1 auto" fxLayout="column">
|
||||||
|
<div class="message-area" fxFlex="1 1 auto">
|
||||||
|
<app-sections-chat-message [roomId]="roomId"></app-sections-chat-message>
|
||||||
|
</div>
|
||||||
|
<div class="message-input" fxFlex="0 0 auto">
|
||||||
|
<app-sections-chat-form [roomId]="roomId"></app-sections-chat-form>
|
||||||
|
</div>
|
||||||
|
<mat-drawer #chatRightDrawer mode="side" position="end" class="rightDrawer">
|
||||||
|
Right Sections.
|
||||||
|
</mat-drawer>
|
||||||
|
</mat-drawer-container>
|
||||||
|
</div>
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { ChatRoomPageComponent } from './chat-room.page.component';
|
||||||
|
|
||||||
|
describe('app::pages::chat::ChatRoomPageComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [RouterTestingModule],
|
||||||
|
declarations: [ChatRoomPageComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(ChatRoomPageComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should have as title 'ucap-lg-web'`, () => {
|
||||||
|
const fixture = TestBed.createComponent(ChatRoomPageComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(ChatRoomPageComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement;
|
||||||
|
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||||
|
'ucap-lg-web app is running!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
41
src/app/pages/chat/components/chat-room.page.component.ts
Normal file
41
src/app/pages/chat/components/chat-room.page.component.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
|
|
||||||
|
import { MatDrawer } from '@angular/material/sidenav';
|
||||||
|
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { QueryParams } from '../types/params.type';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-pages-chat-room',
|
||||||
|
templateUrl: './chat-room.page.component.html',
|
||||||
|
styleUrls: ['./chat-room.page.component.scss']
|
||||||
|
})
|
||||||
|
export class ChatRoomPageComponent implements OnInit, OnDestroy {
|
||||||
|
private paramsSubscription: Subscription;
|
||||||
|
isChatSearch = false;
|
||||||
|
roomId: string;
|
||||||
|
|
||||||
|
@ViewChild('chatRightDrawer', { static: false })
|
||||||
|
chatRightDrawer: MatDrawer;
|
||||||
|
|
||||||
|
constructor(private activatedRoute: ActivatedRoute) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.paramsSubscription = this.activatedRoute.queryParams.subscribe(
|
||||||
|
(params: Params) => {
|
||||||
|
const seqParam = params[QueryParams.ROOM_ID];
|
||||||
|
this.roomId = !!seqParam ? seqParam : undefined;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.paramsSubscription) {
|
||||||
|
this.paramsSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onRightDrawerToggle(): void {
|
||||||
|
this.chatRightDrawer.toggle();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,9 @@
|
||||||
import { IndexPageComponent } from './index.page.component';
|
import { IndexPageComponent } from './index.page.component';
|
||||||
import { SidenavPageComponent } from './sidenav.page.component';
|
import { SidenavPageComponent } from './sidenav.page.component';
|
||||||
|
import { ChatRoomPageComponent } from './chat-room.page.component';
|
||||||
|
|
||||||
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent];
|
export const COMPONENTS = [
|
||||||
|
IndexPageComponent,
|
||||||
|
SidenavPageComponent,
|
||||||
|
ChatRoomPageComponent
|
||||||
|
];
|
||||||
|
|
|
@ -1,3 +1,56 @@
|
||||||
<div fxFlexFill>
|
<div fxFlexFill class="sidenav-container">
|
||||||
sidenav page of chat is works!
|
<div class="chat-header">
|
||||||
|
<h3>대화</h3>
|
||||||
|
<div class="chat-menu-btn">
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="exit-room"
|
||||||
|
(click)="onToggleChackable(true)"
|
||||||
|
>
|
||||||
|
<mat-icon>exit_to_app</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="exit-close"
|
||||||
|
*ngIf="checkable"
|
||||||
|
(click)="onToggleChackable(false)"
|
||||||
|
>
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<app-sections-chat-search
|
||||||
|
(keyDownEnter)="onKeyDownSearch($event)"
|
||||||
|
(searchCancel)="onClickCancel()"
|
||||||
|
[style.display]="checkable ? 'none' : 'block'"
|
||||||
|
></app-sections-chat-search>
|
||||||
|
<div class="exitRoomInfo" *ngIf="checkable">
|
||||||
|
<div>
|
||||||
|
<strong>{{ this.selectedRoomList.length }}</strong
|
||||||
|
>/{{
|
||||||
|
!!this.searchObj.isShowSearch
|
||||||
|
? this.searchResultList.length
|
||||||
|
: this.roomList.length
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-checkbox
|
||||||
|
#allCheck
|
||||||
|
aria-label="all select exit room "
|
||||||
|
[checked]="getCheckedAllItem()"
|
||||||
|
(change)="onToggleAllItem(allCheck.checked)"
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
>
|
||||||
|
전체선택
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<app-sections-chat-list
|
||||||
|
fxFlexFill
|
||||||
|
[searchObj]="searchObj"
|
||||||
|
[checkable]="checkable"
|
||||||
|
[selectedRoomList]="selectedRoomList"
|
||||||
|
(searchResultList)="onSearchResultList($event)"
|
||||||
|
(toggleItem)="onToggleItem($event)"
|
||||||
|
></app-sections-chat-list>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,175 @@
|
||||||
import { Component, Inject } from '@angular/core';
|
import { Component, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { LogService } from '@ucap/ng-logger';
|
import { LogService } from '@ucap/ng-logger';
|
||||||
|
import { Subject, of } from 'rxjs';
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
import { takeUntil, take, map, catchError } from 'rxjs/operators';
|
||||||
|
import { RoomSelector, RoomActions } from '@ucap/ng-store-chat';
|
||||||
|
import { RoomInfo, ExitAllRequest } from '@ucap/protocol-room';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import {
|
||||||
|
ConfirmDialogComponent,
|
||||||
|
ConfirmDialogData,
|
||||||
|
ConfirmDialogResult
|
||||||
|
} from '@ucap/ng-ui';
|
||||||
|
import { I18nService } from '@ucap/ng-i18n';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pages-chat-sidenav',
|
selector: 'app-pages-chat-sidenav',
|
||||||
templateUrl: './sidenav.page.component.html',
|
templateUrl: './sidenav.page.component.html',
|
||||||
styleUrls: ['./sidenav.page.component.scss']
|
styleUrls: ['./sidenav.page.component.scss']
|
||||||
})
|
})
|
||||||
export class SidenavPageComponent {
|
export class SidenavPageComponent implements OnInit, OnDestroy {
|
||||||
constructor(private logService: LogService) {}
|
searchObj: any = {
|
||||||
|
isShowSearch: false,
|
||||||
|
searchWord: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
checkable = false;
|
||||||
|
|
||||||
|
roomList: RoomInfo[];
|
||||||
|
selectedRoomList: RoomInfo[] = [];
|
||||||
|
searchResultList: RoomInfo[] = [];
|
||||||
|
|
||||||
|
private ngOnDestroySubject: Subject<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private store: Store<any>,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private logService: LogService,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.ngOnDestroySubject = new Subject<boolean>();
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject), select(RoomSelector.rooms))
|
||||||
|
.subscribe((rooms) => {
|
||||||
|
rooms = (rooms || []).filter((info) => info.isJoinRoom);
|
||||||
|
this.roomList = rooms;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleChackable(checkable: boolean): void {
|
||||||
|
if (!!checkable) {
|
||||||
|
if (!!this.selectedRoomList && this.selectedRoomList.length > 0) {
|
||||||
|
const dialogRef = this.dialog.open<
|
||||||
|
ConfirmDialogComponent,
|
||||||
|
ConfirmDialogData,
|
||||||
|
ConfirmDialogResult
|
||||||
|
>(ConfirmDialogComponent, {
|
||||||
|
data: {
|
||||||
|
title: this.i18nService.t('room.dialog.titleExitFromRoom'),
|
||||||
|
html: this.i18nService.t('room.dialog.confirmExitFromRoom')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef
|
||||||
|
.afterClosed()
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe((result) => {
|
||||||
|
if (!!result && !!result.choice) {
|
||||||
|
this.store.dispatch(
|
||||||
|
RoomActions.delMulti({
|
||||||
|
req: {
|
||||||
|
roomIds: this.selectedRoomList.map(
|
||||||
|
(roomInfo) => roomInfo.roomId
|
||||||
|
)
|
||||||
|
} as ExitAllRequest
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selectedRoomList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkable = checkable;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCheckedAllItem(): boolean {
|
||||||
|
const targetRoomList = !!this.searchObj.isShowSearch
|
||||||
|
? this.searchResultList
|
||||||
|
: this.roomList;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!targetRoomList ||
|
||||||
|
targetRoomList.length === 0 ||
|
||||||
|
targetRoomList.filter(
|
||||||
|
(item) =>
|
||||||
|
!(
|
||||||
|
this.selectedRoomList.filter((info) => info.roomId === item.roomId)
|
||||||
|
.length > 0
|
||||||
|
)
|
||||||
|
).length > 0
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleAllItem(value: boolean): void {
|
||||||
|
if (!!value) {
|
||||||
|
const targetRoomList = !!this.searchObj.isShowSearch
|
||||||
|
? this.searchResultList
|
||||||
|
: this.roomList;
|
||||||
|
|
||||||
|
this.selectedRoomList = targetRoomList.slice();
|
||||||
|
} else {
|
||||||
|
this.selectedRoomList = [];
|
||||||
|
}
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleItem(event: { checked: boolean; roomInfo: RoomInfo }): void {
|
||||||
|
if (!!event.checked) {
|
||||||
|
if (
|
||||||
|
!this.selectedRoomList.some(
|
||||||
|
(info) => info.roomId === event.roomInfo.roomId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.selectedRoomList.push(event.roomInfo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!!this.selectedRoomList && this.selectedRoomList.length > 0) {
|
||||||
|
const index = this.selectedRoomList.findIndex(
|
||||||
|
(info) => info.roomId === event.roomInfo.roomId
|
||||||
|
);
|
||||||
|
if (index > -1) {
|
||||||
|
this.selectedRoomList.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Searching */
|
||||||
|
onKeyDownSearch(params: { searchWord: string }) {
|
||||||
|
this.searchObj = {
|
||||||
|
isShowSearch: true,
|
||||||
|
searchWord: params.searchWord
|
||||||
|
};
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
/** Searching cancel */
|
||||||
|
onClickCancel() {
|
||||||
|
this.searchObj = {
|
||||||
|
isShowSearch: false,
|
||||||
|
searchWord: ''
|
||||||
|
};
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchResultList(searchResultList: RoomInfo[]) {
|
||||||
|
this.searchResultList = searchResultList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
3
src/app/pages/chat/types/params.type.ts
Normal file
3
src/app/pages/chat/types/params.type.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export enum QueryParams {
|
||||||
|
ROOM_ID = 'roomId'
|
||||||
|
}
|
|
@ -1,3 +1,12 @@
|
||||||
<div>
|
<div fxLayout="column" class="index-container">
|
||||||
index of group
|
<div class="subtitle" fxFlex="60px">Welcome to M-Messenger</div>
|
||||||
|
<div class="content-container" fxFlex="1 1 auto" fxLayout="row">
|
||||||
|
<div class="profile-container" fxFlex="44%">
|
||||||
|
<app-organization-profile-01 [userSeq]="userSeq">
|
||||||
|
</app-organization-profile-01>
|
||||||
|
</div>
|
||||||
|
<div class="group-info-container" fxFlex="1 1 auto">
|
||||||
|
<app-sections-group-info [userSeq]="userSeq"></app-sections-group-info>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
@import '~@ucap/lg-scss/mixins';
|
||||||
|
|
||||||
|
.index-container {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 30px 30px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 60px;
|
||||||
|
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.16);
|
||||||
|
@include font-family($font-light);
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 2.73;
|
||||||
|
color: $gray-re4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-container {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-info-container {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
|
@ -21,10 +21,13 @@ export class IndexPageComponent implements OnInit, OnDestroy {
|
||||||
private logService: LogService
|
private logService: LogService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
userSeq: string;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.paramsSubscription = this.activatedRoute.queryParams.subscribe(
|
this.paramsSubscription = this.activatedRoute.queryParams.subscribe(
|
||||||
(params: Params) => {
|
(params: Params) => {
|
||||||
console.log('IndexPageComponent', params[QueryParams.ID]);
|
const seqParam = params[QueryParams.ID];
|
||||||
|
this.userSeq = !!seqParam ? seqParam : undefined;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,63 @@
|
||||||
<div fxFlexFill class="sidenav-container">
|
<div class="sidenav-container" fxFlexFill fxLayout="column">
|
||||||
<app-sections-group-search></app-sections-group-search>
|
<div class="title-section" fxFlex="0 0 50px" fxLayout="row">
|
||||||
<app-sections-group-list fxFlexFill></app-sections-group-list>
|
<div class="title">
|
||||||
|
<h3 fxFlex="1 1 auto">그룹</h3>
|
||||||
|
<div class="menu-btn" fxFlex="80px">
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
[matMenuTriggerFor]="groupViewMenu"
|
||||||
|
aria-label="group view menu"
|
||||||
|
>
|
||||||
|
<mat-icon>swap_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
[matMenuTriggerFor]="groupMenu"
|
||||||
|
aria-label="group menu"
|
||||||
|
>
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="extra-box" fxFlex="0 0 50px">
|
||||||
|
<app-organization-search-for-tenant (changed)="onKeyDownSearch($event)">
|
||||||
|
</app-organization-search-for-tenant>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div fxFlex="1 1 auto">
|
||||||
|
<app-sections-group-list
|
||||||
|
#sectionGroupList
|
||||||
|
fxFlexFill
|
||||||
|
[searchObj]="searchObj"
|
||||||
|
[showType]="showType"
|
||||||
|
></app-sections-group-list>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<mat-menu #groupMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="onClickGroupMenu('GROUP_NEW')">
|
||||||
|
{{ 'moreMenu.group.addNew' | ucapI18n }}
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onClickGroupMenu('GROUP_EXPAND_MORE')">
|
||||||
|
{{ 'moreMenu.group.expandMore' | ucapI18n }}
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onClickGroupMenu('GROUP_EXPAND_LESS')">
|
||||||
|
{{ 'moreMenu.group.expandLess' | ucapI18n }}
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onClickGroupMenu('GROUP_CHANGE_ODER')">
|
||||||
|
{{ 'moreMenu.group.changeOrder' | ucapI18n }}
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-menu #groupViewMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="onClickShowGroupMenu('ALL')">
|
||||||
|
{{ 'moreMenu.show.all' | ucapI18n }}
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onClickShowGroupMenu('ONLINE_BUDDY')">
|
||||||
|
{{ 'moreMenu.show.onlineBuddy' | ucapI18n }}
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onClickShowGroupMenu('ON_OFF')">
|
||||||
|
{{ 'moreMenu.show.onOff' | ucapI18n }}
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
|
@ -1,3 +1,32 @@
|
||||||
|
@import '~@ucap/lg-scss/mixins';
|
||||||
|
|
||||||
.sidenav-container {
|
.sidenav-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
align-content: flex-start;
|
||||||
|
background-color: #f1f2f6;
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
background-color: $white;
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 0 0 17px;
|
||||||
|
background-color: $white;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
h3 {
|
||||||
|
@include font-family-txt(18, left, $lipstick);
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.menu-btn {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,27 @@
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription, of } from 'rxjs';
|
||||||
|
import { take, map, catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
import { ActivatedRoute, Router, Params } from '@angular/router';
|
import { ActivatedRoute, Router, Params } from '@angular/router';
|
||||||
|
|
||||||
import { LogService } from '@ucap/ng-logger';
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
import { QueryParams } from '../types/params.type';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
import { LogService } from '@ucap/ng-logger';
|
||||||
|
import { GroupActions } from '@ucap/ng-store-group';
|
||||||
|
|
||||||
|
import { SelectUserDialogType } from '@app/types';
|
||||||
|
|
||||||
|
import { CreateDialogComponent } from '@app/sections/group/dialogs/create.dialog.component';
|
||||||
|
import { I18nService } from '@ucap/ng-i18n';
|
||||||
|
import { ListSectionComponent } from '@app/sections/group/components/list.section.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pages-group-sidenav',
|
selector: 'app-pages-group-sidenav',
|
||||||
|
@ -13,13 +29,113 @@ import { QueryParams } from '../types/params.type';
|
||||||
styleUrls: ['./sidenav.page.component.scss']
|
styleUrls: ['./sidenav.page.component.scss']
|
||||||
})
|
})
|
||||||
export class SidenavPageComponent implements OnInit, OnDestroy {
|
export class SidenavPageComponent implements OnInit, OnDestroy {
|
||||||
|
@ViewChild('sectionGroupList', { static: false })
|
||||||
|
sectionGroupList: ListSectionComponent;
|
||||||
|
|
||||||
|
searchObj: any = {
|
||||||
|
isShowSearch: false,
|
||||||
|
companyCode: '',
|
||||||
|
searchWord: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
showType: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private logService: LogService
|
private logService: LogService,
|
||||||
) {}
|
private i18nService: I18nService,
|
||||||
|
private store: Store<any>,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
public dialog: MatDialog
|
||||||
|
) {
|
||||||
|
this.i18nService.setDefaultNamespace('group');
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void {
|
||||||
|
this.showType = 'ALL';
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {}
|
ngOnDestroy(): void {}
|
||||||
|
|
||||||
|
onClickFab(event: MouseEvent) {}
|
||||||
|
onKeyDownSearch(params: {
|
||||||
|
isShowSearch: boolean;
|
||||||
|
companyCode: string;
|
||||||
|
searchWord: string;
|
||||||
|
}) {
|
||||||
|
this.searchObj = {
|
||||||
|
isShowSearch: params.isShowSearch,
|
||||||
|
companyCode: params.companyCode,
|
||||||
|
searchWord: params.searchWord
|
||||||
|
};
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickCancel() {
|
||||||
|
this.searchObj = {
|
||||||
|
isShowSearch: false,
|
||||||
|
companyCode: '',
|
||||||
|
searchWord: ''
|
||||||
|
};
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickGroupMenu(menuType: string) {
|
||||||
|
switch (menuType) {
|
||||||
|
case 'GROUP_NEW':
|
||||||
|
{
|
||||||
|
const dialogRef = this.dialog.open(CreateDialogComponent, {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef
|
||||||
|
.afterClosed()
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
map((result) => {}),
|
||||||
|
catchError((err) => {
|
||||||
|
return of(err);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'GROUP_EXPAND_MORE':
|
||||||
|
{
|
||||||
|
this.sectionGroupList.onExpandMore();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'GROUP_EXPAND_LESS':
|
||||||
|
{
|
||||||
|
this.sectionGroupList.onExpandLess();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'GROUP_CHANGE_ODER':
|
||||||
|
{
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickShowGroupMenu(menuType: string) {
|
||||||
|
switch (menuType) {
|
||||||
|
case 'ALL':
|
||||||
|
{
|
||||||
|
this.showType = 'ALL';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ONLINE_BUDDY':
|
||||||
|
{
|
||||||
|
this.showType = 'ONLINE_BUDDY';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ON_OFF':
|
||||||
|
{
|
||||||
|
this.showType = 'ON_OFF';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,25 +3,45 @@ import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
|
||||||
|
import { UiModule } from '@ucap/ng-ui';
|
||||||
|
|
||||||
|
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
|
||||||
|
|
||||||
import { AppGroupSectionModule } from '@app/sections/group/group.section.module';
|
import { AppGroupSectionModule } from '@app/sections/group/group.section.module';
|
||||||
|
|
||||||
import { AppGroupRoutingPageModule } from './group-routing.page.module';
|
import { AppGroupRoutingPageModule } from './group-routing.page.module';
|
||||||
|
|
||||||
import { IndexPageComponent } from './components/index.page.component';
|
import { COMPONENTS } from './components';
|
||||||
import { SidenavPageComponent } from './components/sidenav.page.component';
|
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
|
||||||
|
|
||||||
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent];
|
|
||||||
|
|
||||||
export { IndexPageComponent, SidenavPageComponent };
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
|
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatMenuModule,
|
||||||
|
|
||||||
|
AppOrganizationModule,
|
||||||
|
|
||||||
AppGroupSectionModule,
|
AppGroupSectionModule,
|
||||||
AppGroupRoutingPageModule
|
AppGroupRoutingPageModule,
|
||||||
|
|
||||||
|
UiModule,
|
||||||
|
I18nModule
|
||||||
],
|
],
|
||||||
declarations: [...COMPONENTS],
|
declarations: [...COMPONENTS],
|
||||||
entryComponents: []
|
entryComponents: [],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: UCAP_I18N_NAMESPACE,
|
||||||
|
useValue: ['group', 'common']
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class AppGroupPageModule {}
|
export class AppGroupPageModule {}
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
Index page of organization is works!
|
<div fxFlexFill>
|
||||||
|
<app-sections-organization-member-list
|
||||||
|
[searchData]="_searchData"
|
||||||
|
></app-sections-organization-member-list>
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,10 +1,58 @@
|
||||||
import { Component } from '@angular/core';
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { QueryParams } from '../types/params.type';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pages-organization-index',
|
selector: 'app-pages-organization-index',
|
||||||
templateUrl: './index.page.component.html',
|
templateUrl: './index.page.component.html',
|
||||||
styleUrls: ['./index.page.component.scss']
|
styleUrls: ['./index.page.component.scss']
|
||||||
})
|
})
|
||||||
export class IndexPageComponent {
|
export class IndexPageComponent implements OnInit, OnDestroy {
|
||||||
constructor() {}
|
// tslint:disable-next-line: variable-name
|
||||||
|
_searchData: {
|
||||||
|
companyCode: string;
|
||||||
|
searchWord: string;
|
||||||
|
isSearch: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
deptSeq: string;
|
||||||
|
|
||||||
|
private ngOnDestroySubject: Subject<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private store: Store<any>,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.ngOnDestroySubject = new Subject<boolean>();
|
||||||
|
|
||||||
|
this.activatedRoute.queryParams
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||||
|
.subscribe((params) => {
|
||||||
|
console.log('activatedRoute.queryParams');
|
||||||
|
if (!!params) {
|
||||||
|
const companyCode = params[QueryParams.DEPT_SEQ];
|
||||||
|
console.log('activatedRoute.queryParams', companyCode);
|
||||||
|
this._searchData = {
|
||||||
|
companyCode,
|
||||||
|
searchWord: '',
|
||||||
|
isSearch: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,33 @@
|
||||||
<div fxFlexFill>
|
<div class="sidenav-container" fxFlexFill fxLayout="column">
|
||||||
sidenav page of ogranization is works!
|
<div class="sub-header" fxFlex="50px" fxLayout="row">
|
||||||
|
<div fxFlex="1 1 auto">
|
||||||
|
<h3>조직도</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="menu-btn" fxFlex="0 0 40px">
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
[matMenuTriggerFor]="organizationMenu"
|
||||||
|
aria-label="organization menu"
|
||||||
|
>
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="extra-box" fxFlex="0 0 50px">
|
||||||
|
<mat-icon matPrefix class="ico-business">business</mat-icon>LG CNS
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div fxFlex="1 1 auto">
|
||||||
|
<app-organization-tree
|
||||||
|
[initialExpanded]="initialExpanded"
|
||||||
|
(clicked)="onClickedTree($event)"
|
||||||
|
></app-organization-tree>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<mat-menu #organizationMenu="matMenu">
|
||||||
|
<button mat-menu-item>Item 1</button>
|
||||||
|
<button mat-menu-item>Item 2</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
@import '~@ucap/lg-scss/mixins';
|
||||||
|
|
||||||
|
.sidenav-container {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.sub-header {
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 0 0 17px;
|
||||||
|
background-color: $white;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
h3 {
|
||||||
|
@include font-family-txt(20, left, $lipstick);
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.menu-btn {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.extra-box {
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: $gray-re3;
|
||||||
|
padding: 0 17px;
|
||||||
|
line-height: 50px;
|
||||||
|
.ico-business {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,68 @@
|
||||||
import { Component } from '@angular/core';
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { Router, ActivatedRoute, Params } from '@angular/router';
|
||||||
|
|
||||||
import { LogService } from '@ucap/ng-logger';
|
import { LogService } from '@ucap/ng-logger';
|
||||||
|
import { DeptInfo } from '@ucap/protocol-query';
|
||||||
|
|
||||||
|
import { QueryParams } from '../types/params.type';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pages-ogranization-sidenav',
|
selector: 'app-pages-ogranization-sidenav',
|
||||||
templateUrl: './sidenav.page.component.html',
|
templateUrl: './sidenav.page.component.html',
|
||||||
styleUrls: ['./sidenav.page.component.scss']
|
styleUrls: ['./sidenav.page.component.scss']
|
||||||
})
|
})
|
||||||
export class SidenavPageComponent {
|
export class SidenavPageComponent implements OnInit, OnDestroy {
|
||||||
constructor(private logService: LogService) {
|
initialExpanded: number;
|
||||||
|
|
||||||
|
private ngOnDestroySubject: Subject<void>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private logService: LogService
|
||||||
|
) {
|
||||||
this.logService.info('app-pages-ogranization-sidenav');
|
this.logService.info('app-pages-ogranization-sidenav');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.ngOnDestroySubject = new Subject<void>();
|
||||||
|
|
||||||
|
this.activatedRoute.queryParams
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||||
|
.subscribe((params) => {
|
||||||
|
if (!!params) {
|
||||||
|
const deptSeq = params[QueryParams.DEPT_SEQ];
|
||||||
|
if (!!deptSeq) {
|
||||||
|
this.initialExpanded = Number(deptSeq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickedTree(node: DeptInfo) {
|
||||||
|
const queryParams: Params = {};
|
||||||
|
queryParams[QueryParams.DEPT_SEQ] = String(node.seq);
|
||||||
|
|
||||||
|
this.router.navigate(
|
||||||
|
[
|
||||||
|
'organization',
|
||||||
|
{
|
||||||
|
outlets: { content: 'index' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
queryParams
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,29 @@ import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
|
||||||
|
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
|
||||||
|
import { AppOrganizationSectionModule } from '@app/sections/organization/organization.section.module';
|
||||||
import { AppOrganizationRoutingPageModule } from './organization-routing.page.module';
|
import { AppOrganizationRoutingPageModule } from './organization-routing.page.module';
|
||||||
|
|
||||||
import { COMPONENTS } from './components';
|
import { COMPONENTS } from './components';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, FlexLayoutModule, AppOrganizationRoutingPageModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatMenuModule,
|
||||||
|
|
||||||
|
AppOrganizationModule,
|
||||||
|
AppOrganizationSectionModule,
|
||||||
|
AppOrganizationRoutingPageModule
|
||||||
|
],
|
||||||
declarations: [...COMPONENTS],
|
declarations: [...COMPONENTS],
|
||||||
entryComponents: []
|
entryComponents: []
|
||||||
})
|
})
|
||||||
|
|
3
src/app/pages/organization/types/params.type.ts
Normal file
3
src/app/pages/organization/types/params.type.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export enum QueryParams {
|
||||||
|
DEPT_SEQ = 'dept_seq'
|
||||||
|
}
|
|
@ -1,20 +1,19 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
||||||
|
|
||||||
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
|
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
|
||||||
|
|
||||||
import { AuthenticationUiModule } from '@ucap/ng-ui-authentication';
|
import { AuthenticationUiModule } from '@ucap/ng-ui-authentication';
|
||||||
|
|
||||||
import { COMPONENTS } from './components';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
@ -26,8 +25,8 @@ import { MatSliderModule } from '@angular/material/slider';
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { COMPONENTS } from './components';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<mat-select
|
<mat-select
|
||||||
[formControl]="companyCodeFormControl"
|
[formControl]="companyCodeFormControl"
|
||||||
[value]="companyCode || fixedCompanyCode"
|
[value]="companyCode || fixedCompanyCode"
|
||||||
|
placeholder="{{ 'login.labels.selectCompany' | ucapI18n }}"
|
||||||
class="login-input-area login-select-form"
|
class="login-input-area login-select-form"
|
||||||
>
|
>
|
||||||
<mat-option
|
<mat-option
|
||||||
|
|
|
@ -1,4 +1 @@
|
||||||
import { LoginSectionComponent } from './login.section.component';
|
export const COMPONENTS = [];
|
||||||
import { LoginComponent } from './component-ui/login.component';
|
|
||||||
|
|
||||||
export const COMPONENTS = [LoginSectionComponent, LoginComponent];
|
|
||||||
|
|
|
@ -1,63 +1,67 @@
|
||||||
<ucap-authentication-login-local
|
<div class="login-section-container">
|
||||||
[companyList]="companyList"
|
<ucap-authentication-login-local
|
||||||
[fixedCompanyCode]="fixedCompanyCode"
|
[companyList]="companyList"
|
||||||
[companyCode]="userStore?.companyCode"
|
[fixedCompanyCode]="fixedCompanyCode"
|
||||||
[loginId]="userStore?.loginId"
|
[companyCode]="userStore?.companyCode"
|
||||||
[disable]="disableLoginForm"
|
[loginId]="userStore?.loginId"
|
||||||
[processing]="loginProcessing"
|
[disable]="disableLoginForm"
|
||||||
[loginTry]="loginTry"
|
[processing]="loginProcessing"
|
||||||
(login)="onLogin($event)"
|
[loginTry]="loginTry"
|
||||||
>
|
(login)="onLogin($event)"
|
||||||
<div ucapAuthenticationLogin="header">
|
>
|
||||||
<div class="logo-img">
|
<div ucapAuthenticationLogin="header">
|
||||||
<img src="../../../assets/images/logo_140.png" alt="" />
|
<div class="logo-img">
|
||||||
</div>
|
<img src="../../../assets/images/logo_140.png" alt="" />
|
||||||
<h1>Welcome to Messenger</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ucapAuthenticationLogin="footer">
|
|
||||||
<div class="login-chk-area">
|
|
||||||
<div>
|
|
||||||
<mat-checkbox
|
|
||||||
#chkUseRememberMe
|
|
||||||
*ngIf="useRememberMe"
|
|
||||||
aria-label="Remember Me"
|
|
||||||
[checked]="!!userStore && userStore.rememberMe"
|
|
||||||
>
|
|
||||||
{{ 'login.labels.rememberMe' | ucapI18n }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h1>Welcome to Messenger</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div ucapAuthenticationLogin="footer">
|
||||||
<mat-checkbox
|
<div class="login-chk-area">
|
||||||
#chkUseAutoLogin
|
<div>
|
||||||
*ngIf="useAutoLogin"
|
<mat-checkbox
|
||||||
aria-label="Auto Login"
|
#chkUseRememberMe
|
||||||
[checked]="
|
*ngIf="useRememberMe"
|
||||||
!!userStore &&
|
aria-label="Remember Me"
|
||||||
!!userStore.settings &&
|
[checked]="!!userStore && userStore.rememberMe"
|
||||||
!!userStore.settings.general &&
|
>
|
||||||
userStore.settings.general.autoLogin
|
{{ 'login.labels.rememberMe' | ucapI18n }}
|
||||||
"
|
</mat-checkbox>
|
||||||
>
|
</div>
|
||||||
{{ 'login.labels.autoLogin' | ucapI18n }}
|
|
||||||
</mat-checkbox>
|
<div>
|
||||||
|
<mat-checkbox
|
||||||
|
#chkUseAutoLogin
|
||||||
|
*ngIf="useAutoLogin"
|
||||||
|
aria-label="Auto Login"
|
||||||
|
[checked]="
|
||||||
|
!!userStore &&
|
||||||
|
!!userStore.settings &&
|
||||||
|
!!userStore.settings.general &&
|
||||||
|
userStore.settings.general.autoLogin
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ 'login.labels.autoLogin' | ucapI18n }}
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="login-pass-info">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="">{{ 'login.labels.forgotPassword' | ucapI18n }}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="" class="fir-pass">{{
|
||||||
|
'login.labels.resetPassword' | ucapI18n
|
||||||
|
}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="login-button-area">
|
||||||
|
<button type="button">
|
||||||
|
{{ 'login.labels.notesOnUse' | ucapI18n }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-pass-info">
|
</ucap-authentication-login-local>
|
||||||
<ul>
|
</div>
|
||||||
<li>
|
|
||||||
<a href="">{{ 'login.labels.forgotPassword' | ucapI18n }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="" class="fir-pass">{{
|
|
||||||
'login.labels.resetPassword' | ucapI18n
|
|
||||||
}}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="login-button-area">
|
|
||||||
<button type="button">{{ 'login.labels.NotesOnUse' | ucapI18n }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ucap-authentication-login-local>
|
|
||||||
|
|
|
@ -15,6 +15,12 @@ h1 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-section-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.login-chk-area {
|
.login-chk-area {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
73
src/app/sections/chat/chat.section.module.ts
Normal file
73
src/app/sections/chat/chat.section.module.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
|
||||||
|
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||||
|
|
||||||
|
import { MatRippleModule } from '@angular/material/core';
|
||||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
|
import { MatBadgeModule } from '@angular/material/badge';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { MatTreeModule } from '@angular/material/tree';
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
|
||||||
|
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
|
||||||
|
|
||||||
|
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
|
||||||
|
import { UiModule } from '@ucap/ng-ui';
|
||||||
|
import { ChatUiModule } from '@ucap/ng-ui-chat';
|
||||||
|
import { AppChatModule } from '@app/ucap/chat/chat.module';
|
||||||
|
|
||||||
|
import { COMPONENTS } from './components';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
|
||||||
|
MatInputModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
MatAutocompleteModule,
|
||||||
|
MatSelectModule,
|
||||||
|
MatBadgeModule,
|
||||||
|
MatMenuModule,
|
||||||
|
MatToolbarModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatRippleModule,
|
||||||
|
MatTreeModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
|
||||||
|
PerfectScrollbarModule,
|
||||||
|
ScrollingModule,
|
||||||
|
|
||||||
|
I18nModule,
|
||||||
|
UiModule,
|
||||||
|
|
||||||
|
ChatUiModule,
|
||||||
|
AppChatModule
|
||||||
|
],
|
||||||
|
exports: [...COMPONENTS],
|
||||||
|
declarations: [...COMPONENTS],
|
||||||
|
entryComponents: [],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: UCAP_I18N_NAMESPACE,
|
||||||
|
useValue: ['chat', 'common']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AppChatSectionModule {}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<div class="search-container">
|
||||||
|
<div class="searchbox ucap-mat-input-container">
|
||||||
|
<mat-form-field floatLabel="never" appearance="none" class="search-in-box">
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
#searchWordInput
|
||||||
|
type="text"
|
||||||
|
maxlength="20"
|
||||||
|
placeholder="솔루션"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
matSuffix
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Clear"
|
||||||
|
(click)="searchWordInput.value = ''"
|
||||||
|
class="btn-close"
|
||||||
|
color="accent"
|
||||||
|
>
|
||||||
|
<mat-icon>highlight_off</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-form-field>
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
color="accent"
|
||||||
|
class="btn-ico-search"
|
||||||
|
(click)="onChatSearch(searchWordInput.value)"
|
||||||
|
>
|
||||||
|
<mat-icon>search</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
color="primary"
|
||||||
|
class="btn-ico-search"
|
||||||
|
(click)="onCancel()"
|
||||||
|
>
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="search-result">
|
||||||
|
<span class="text"><strong>N</strong>건의 검색결과가 있습니다.</span>
|
||||||
|
<span class="result-count"><strong>1</strong>/N</span>
|
||||||
|
<div class="btn-area">
|
||||||
|
<button mat-icon-button class="icon-button">
|
||||||
|
<mat-icon>arrow_downward</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button class="icon-button">
|
||||||
|
<mat-icon>arrow_upward</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { SearchSectionComponent } from './search.section.component';
|
||||||
|
|
||||||
|
describe('app::sections::group::SearchSectionComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [RouterTestingModule],
|
||||||
|
declarations: [SearchSectionComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(SearchSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should have as title 'ucap-lg-web'`, () => {
|
||||||
|
const fixture = TestBed.createComponent(SearchSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(SearchSectionComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement;
|
||||||
|
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||||
|
'ucap-lg-web app is running!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
Output,
|
||||||
|
EventEmitter,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
ChangeDetectionStrategy
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sections-chat-chat-search',
|
||||||
|
templateUrl: './chat-search.section.component.html',
|
||||||
|
styleUrls: ['./chat-search.section.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class ChatSearchSectionComponent implements OnInit, OnDestroy {
|
||||||
|
searchObj: any = {
|
||||||
|
isSearch: false,
|
||||||
|
searchWord: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
chatSearch = new EventEmitter<{
|
||||||
|
isSearch: false;
|
||||||
|
searchWord: '';
|
||||||
|
}>();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
closeChatSearch = new EventEmitter<void>();
|
||||||
|
|
||||||
|
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {}
|
||||||
|
|
||||||
|
onChatSearch(searchWord: string) {
|
||||||
|
alert(searchWord);
|
||||||
|
|
||||||
|
this.searchObj = {
|
||||||
|
isSearch: true,
|
||||||
|
searchWord
|
||||||
|
};
|
||||||
|
this.chatSearch.emit(this.searchObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.closeChatSearch.emit();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
<div class="chat-list-item">
|
||||||
|
<div class="profileImage">
|
||||||
|
<img
|
||||||
|
class="thumbnail"
|
||||||
|
ucapImage
|
||||||
|
[base]="profileImageRoot"
|
||||||
|
[path]="profileImage"
|
||||||
|
[default]="defaultProfileImage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="roomName">
|
||||||
|
{{ roomName }}
|
||||||
|
<strong *ngIf="roomInfo.roomType === RoomType.Multi"
|
||||||
|
>({{ roomInfo.joinUserCount }})</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="lastMessage">{{ roomInfo.finalEventMessage }}</div>
|
||||||
|
<div class="date">{{ roomInfo.finalEventDate | ucapDate: 'LT' }}</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="noti-sum"
|
||||||
|
*ngIf="!!roomInfo.noReadCnt && roomInfo.noReadCnt > 0"
|
||||||
|
[matBadgeHidden]="roomInfo.noReadCnt === 0"
|
||||||
|
[matBadge]="roomInfo.noReadCnt"
|
||||||
|
matBadgeOverlap="true"
|
||||||
|
matBadgeColor="accent"
|
||||||
|
matBadgePosition="below after"
|
||||||
|
></span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="room-menu"
|
||||||
|
*ngIf="!checkable"
|
||||||
|
[matMenuTriggerFor]="roomMenu"
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
>
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-checkbox
|
||||||
|
*ngIf="checkable"
|
||||||
|
#checkbox
|
||||||
|
[checked]="checked"
|
||||||
|
(change)="onToggleItem(checkbox.checked)"
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
class="group-check"
|
||||||
|
>
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-menu #roomMenu="matMenu">
|
||||||
|
<span class="manu-title">{{ roomName }}</span>
|
||||||
|
<button mat-menu-item>
|
||||||
|
<mat-icon>dialpad</mat-icon>
|
||||||
|
<span>대화방 열기</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item>
|
||||||
|
<mat-icon>dialpad</mat-icon>
|
||||||
|
<span>알림 해제</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item>
|
||||||
|
<mat-icon>dialpad</mat-icon>
|
||||||
|
<span>대화방 나가기</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ChatListItemComponent } from './chat-list-item.component';
|
||||||
|
|
||||||
|
describe('ChatListItemComponent', () => {
|
||||||
|
let component: ChatListItemComponent;
|
||||||
|
let fixture: ComponentFixture<ChatListItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ ChatListItemComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ChatListItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
EventEmitter,
|
||||||
|
Output,
|
||||||
|
ChangeDetectorRef
|
||||||
|
} from '@angular/core';
|
||||||
|
import { RoomInfo, RoomType } from '@ucap/protocol-room';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chat-list-item',
|
||||||
|
templateUrl: './chat-list-item.component.html',
|
||||||
|
styleUrls: ['./chat-list-item.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class ChatListItemComponent implements OnInit, OnDestroy {
|
||||||
|
@Input()
|
||||||
|
roomInfo: RoomInfo;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
profileImageRoot: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
defaultProfileImage: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
profileImage: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
roomName: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
checkable = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
checked = false;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
toggleItem = new EventEmitter<{
|
||||||
|
checked: boolean;
|
||||||
|
roomInfo: RoomInfo;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
RoomType = RoomType;
|
||||||
|
|
||||||
|
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {}
|
||||||
|
|
||||||
|
onToggleItem(value: boolean): void {
|
||||||
|
this.toggleItem.emit({
|
||||||
|
checked: value,
|
||||||
|
roomInfo: this.roomInfo
|
||||||
|
});
|
||||||
|
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
<div class="ucap-group-expansion-container" fxFlexFill>
|
||||||
|
<cdk-virtual-scroll-viewport #cvsvList perfectScrollbar fxFlexFill>
|
||||||
|
<ng-container
|
||||||
|
*cdkVirtualFor="
|
||||||
|
let node of dataSource.expandedData$;
|
||||||
|
templateCacheSize: 0
|
||||||
|
"
|
||||||
|
></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"
|
||||||
|
>
|
||||||
|
<mat-icon class="mat-icon-rtl-mirror">
|
||||||
|
{{
|
||||||
|
treeControl.isExpanded(node) ? 'expand_less' : 'expand_more'
|
||||||
|
}}
|
||||||
|
</mat-icon>
|
||||||
|
</button>
|
||||||
|
<div class="group-info">
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="headerTemplate"
|
||||||
|
[ngTemplateOutletContext]="{ $implicit: node?.node }"
|
||||||
|
>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</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,177 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
Input,
|
||||||
|
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 { VirtualScrollTreeFlatDataSource } from '@ucap/ng-ui';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
|
||||||
|
import { RoomInfo } from '@ucap/protocol-room';
|
||||||
|
|
||||||
|
export interface ChatGroupNode {
|
||||||
|
nodeType: string;
|
||||||
|
roomInfo?: RoomInfo;
|
||||||
|
children?: ChatGroupNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlatNode {
|
||||||
|
expandable: boolean;
|
||||||
|
level: number;
|
||||||
|
node: ChatGroupNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[ucapChatExpansionNode]'
|
||||||
|
})
|
||||||
|
export class ExpansionNodeDirective {}
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[ucapChatExpansionHeader]'
|
||||||
|
})
|
||||||
|
export class ExpansionHeaderDirective {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ucap-chat-expansion',
|
||||||
|
templateUrl: './expansion.component.html',
|
||||||
|
styleUrls: ['./expansion.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class ExpansionComponent implements OnInit, OnDestroy {
|
||||||
|
@Input()
|
||||||
|
set chatGroup(list: { division: string; roomList: RoomInfo[] }[]) {
|
||||||
|
if (!list || 0 === list.length) {
|
||||||
|
} else {
|
||||||
|
list.sort((a, b) =>
|
||||||
|
a.division < b.division ? 1 : a.division > b.division ? -1 : 0
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const item of list) {
|
||||||
|
const nodeType = item.division;
|
||||||
|
const node: ChatGroupNode = {
|
||||||
|
nodeType,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
|
||||||
|
item.roomList.sort((a, b) =>
|
||||||
|
a.finalEventDate < b.finalEventDate
|
||||||
|
? 1
|
||||||
|
: a.finalEventDate > b.finalEventDate
|
||||||
|
? -1
|
||||||
|
: 0
|
||||||
|
);
|
||||||
|
|
||||||
|
item.roomList.forEach((roomInfo) => {
|
||||||
|
node.children.push({
|
||||||
|
nodeType,
|
||||||
|
roomInfo
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!!this.nodeMap.get(item.division)) {
|
||||||
|
this.nodeMap[item.division].push(node);
|
||||||
|
} else {
|
||||||
|
this.nodeMap.set(item.division, [node]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.refreshNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewChild('treeList', { static: false })
|
||||||
|
treeList: MatTree<FlatNode>;
|
||||||
|
|
||||||
|
@ViewChild('cvsvList', { static: false })
|
||||||
|
cvsvList: CdkVirtualScrollViewport;
|
||||||
|
|
||||||
|
@ViewChild(PerfectScrollbarDirective, { static: false })
|
||||||
|
psDirectiveRef?: PerfectScrollbarDirective;
|
||||||
|
|
||||||
|
@ContentChild(ExpansionNodeDirective, {
|
||||||
|
read: TemplateRef,
|
||||||
|
static: false
|
||||||
|
})
|
||||||
|
nodeTemplate: TemplateRef<ExpansionNodeDirective>;
|
||||||
|
|
||||||
|
@ContentChild(ExpansionHeaderDirective, {
|
||||||
|
read: TemplateRef,
|
||||||
|
static: false
|
||||||
|
})
|
||||||
|
headerTemplate: TemplateRef<ExpansionHeaderDirective>;
|
||||||
|
|
||||||
|
treeControl: FlatTreeControl<FlatNode>;
|
||||||
|
treeFlattener: MatTreeFlattener<ChatGroupNode, FlatNode>;
|
||||||
|
dataSource: VirtualScrollTreeFlatDataSource<ChatGroupNode, FlatNode>;
|
||||||
|
|
||||||
|
private nodeMap: Map<string, ChatGroupNode[]> = new Map();
|
||||||
|
// tslint:disable-next-line: variable-name
|
||||||
|
private _ngOnDestroySubject: Subject<void>;
|
||||||
|
|
||||||
|
constructor(private changeDetectorRef: ChangeDetectorRef) {
|
||||||
|
this.treeControl = new FlatTreeControl<FlatNode>(
|
||||||
|
(node) => node.level,
|
||||||
|
(node) => node.expandable
|
||||||
|
);
|
||||||
|
|
||||||
|
this.treeFlattener = new MatTreeFlattener<ChatGroupNode, FlatNode>(
|
||||||
|
(node: ChatGroupNode, 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<
|
||||||
|
ChatGroupNode,
|
||||||
|
FlatNode
|
||||||
|
>(this.treeControl, this.treeFlattener);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this._ngOnDestroySubject = new Subject();
|
||||||
|
|
||||||
|
this.dataSource.cdkVirtualScrollViewport = this.cvsvList;
|
||||||
|
this.treeControl.expansionModel.changed
|
||||||
|
.pipe(takeUntil(this._ngOnDestroySubject))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.cvsvList.checkViewportSize();
|
||||||
|
this.psDirectiveRef.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this._ngOnDestroySubject) {
|
||||||
|
this._ngOnDestroySubject.next();
|
||||||
|
this._ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isHeader = (_: number, node: FlatNode) => 0 === node.level;
|
||||||
|
|
||||||
|
private refreshNodes() {
|
||||||
|
const rootNode: ChatGroupNode[] = [];
|
||||||
|
this.nodeMap.forEach((node) => rootNode.push(...node));
|
||||||
|
|
||||||
|
this.dataSource.data = rootNode;
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
}
|
10
src/app/sections/chat/components/component-ui/index.ts
Normal file
10
src/app/sections/chat/components/component-ui/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { ChatListItemComponent } from './chat-list-item.component';
|
||||||
|
import {
|
||||||
|
ExpansionComponent,
|
||||||
|
ExpansionNodeDirective,
|
||||||
|
ExpansionHeaderDirective
|
||||||
|
} from './expansion.component';
|
||||||
|
|
||||||
|
export const COMPONENTS = [ChatListItemComponent, ExpansionComponent];
|
||||||
|
|
||||||
|
export const DIRECTIVES = [ExpansionNodeDirective, ExpansionHeaderDirective];
|
108
src/app/sections/chat/components/form.section.component.html
Normal file
108
src/app/sections/chat/components/form.section.component.html
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<ng-container [ngSwitch]="selectorType">
|
||||||
|
<app-chat-selector-sticker *ngSwitchCase="'STICKER'">
|
||||||
|
</app-chat-selector-sticker>
|
||||||
|
|
||||||
|
<app-chat-selector-translation
|
||||||
|
*ngSwitchCase="'TRANSLATION'"
|
||||||
|
></app-chat-selector-translation>
|
||||||
|
|
||||||
|
<app-chat-selector-file-upload *ngSwitchCase="'FILEUPLOAD'">
|
||||||
|
</app-chat-selector-file-upload>
|
||||||
|
|
||||||
|
<app-chat-selector-email-send
|
||||||
|
*ngSwitchCase="'EMAILSENDER'"
|
||||||
|
></app-chat-selector-email-send>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="chat-form-area">
|
||||||
|
<mat-form-field
|
||||||
|
class="message-text"
|
||||||
|
fxFlex
|
||||||
|
floatLabel="never"
|
||||||
|
appearance="standard"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
matInput
|
||||||
|
#replyInput
|
||||||
|
placeholder=""
|
||||||
|
name="message"
|
||||||
|
[matTextareaAutosize]="true"
|
||||||
|
[matAutosizeMaxRows]="20"
|
||||||
|
></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="button-area">
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
aria-label="attachFile"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
matTooltip="{{ 'label.attachFile' | ucapI18n }}"
|
||||||
|
matTool
|
||||||
|
(click)="onOpenSelector('FILEUPLOAD')"
|
||||||
|
>
|
||||||
|
첨부파일
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
aria-label="attachImage"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
matTooltip="{{ 'label.attachImage' | ucapI18n }}"
|
||||||
|
(click)="onOpenSelector('')"
|
||||||
|
>
|
||||||
|
이미지
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
aria-label="screenshot"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
matTooltip="{{ 'label.screenshot' | ucapI18n }}"
|
||||||
|
(click)="onOpenSelector('')"
|
||||||
|
>
|
||||||
|
캡쳐 화면 전송
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
aria-label="imoticon"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
matTooltip="{{ 'label.imoticon' | ucapI18n }}"
|
||||||
|
(click)="onOpenSelector('STICKER')"
|
||||||
|
>
|
||||||
|
이모티콘
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
aria-label="emailSend"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
matTooltip="{{ 'label.emailSend' | ucapI18n }}"
|
||||||
|
(click)="onOpenSelector('EMAILSENDER')"
|
||||||
|
>
|
||||||
|
대화내용 메일 전송
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
aria-label="translation"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
matTooltip="{{ 'label.translation' | ucapI18n }}"
|
||||||
|
(click)="onOpenSelector('TRANSLATION')"
|
||||||
|
>
|
||||||
|
대화내용 번역
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
aria-label="gams"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
matTooltip="{{ 'label.gams' | ucapI18n }}"
|
||||||
|
(click)="onOpenSelector('')"
|
||||||
|
>
|
||||||
|
+GAMS
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-fab
|
||||||
|
color="accent"
|
||||||
|
aria-label="send"
|
||||||
|
matTooltip="{{ 'label.send' | ucapI18n }}"
|
||||||
|
(click)="onOpenSelector('')"
|
||||||
|
>
|
||||||
|
<mat-icon>send</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { FormSectionComponent } from './form.section.component';
|
||||||
|
|
||||||
|
describe('app::sections::group::FormSectionComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [RouterTestingModule],
|
||||||
|
declarations: [FormSectionComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(FormSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should have as title 'ucap-lg-web'`, () => {
|
||||||
|
const fixture = TestBed.createComponent(FormSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(FormSectionComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement;
|
||||||
|
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||||
|
'ucap-lg-web app is running!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
71
src/app/sections/chat/components/form.section.component.ts
Normal file
71
src/app/sections/chat/components/form.section.component.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Input
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
import { RoomInfo } from '@ucap/protocol-room';
|
||||||
|
import { Dictionary } from '@ngrx/entity';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sections-chat-form',
|
||||||
|
templateUrl: './form.section.component.html',
|
||||||
|
styleUrls: ['./form.section.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class FormSectionComponent implements OnInit, OnDestroy {
|
||||||
|
@Input()
|
||||||
|
set roomId(roomId: string) {
|
||||||
|
this._roomId = roomId;
|
||||||
|
}
|
||||||
|
get roomId(): string {
|
||||||
|
return this._roomId;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line: variable-name
|
||||||
|
_roomId: string;
|
||||||
|
|
||||||
|
currentRoomInfo: RoomInfo;
|
||||||
|
|
||||||
|
selectorType = '';
|
||||||
|
|
||||||
|
private ngOnDestroySubject: Subject<boolean>;
|
||||||
|
constructor(
|
||||||
|
private store: Store<any>,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.ngOnDestroySubject = new Subject<boolean>();
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.pipe(
|
||||||
|
takeUntil(this.ngOnDestroySubject),
|
||||||
|
select(
|
||||||
|
(state: any) => state.chat.room.rooms.entities as Dictionary<RoomInfo>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.subscribe((rooms) => {
|
||||||
|
if (!!this.roomId) {
|
||||||
|
this.currentRoomInfo = rooms[this.roomId];
|
||||||
|
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenSelector(type: string): void {
|
||||||
|
this.selectorType = type;
|
||||||
|
}
|
||||||
|
}
|
15
src/app/sections/chat/components/index.ts
Normal file
15
src/app/sections/chat/components/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { SearchSectionComponent } from './search.section.component';
|
||||||
|
import { ListSectionComponent } from './list.section.component';
|
||||||
|
import { InfoSectionComponent } from './info.section.component';
|
||||||
|
import { ChatSearchSectionComponent } from './chat-search.section.component';
|
||||||
|
import { FormSectionComponent } from './form.section.component';
|
||||||
|
import { MessageSectionComponent } from './message.section.component';
|
||||||
|
|
||||||
|
export const COMPONENTS = [
|
||||||
|
SearchSectionComponent,
|
||||||
|
ListSectionComponent,
|
||||||
|
InfoSectionComponent,
|
||||||
|
ChatSearchSectionComponent,
|
||||||
|
FormSectionComponent,
|
||||||
|
MessageSectionComponent
|
||||||
|
];
|
74
src/app/sections/chat/components/info.section.component.html
Normal file
74
src/app/sections/chat/components/info.section.component.html
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<mat-toolbar class="info-chat-toolbar">
|
||||||
|
<mat-toolbar-row>
|
||||||
|
<div class="profileImage">
|
||||||
|
<img
|
||||||
|
class="thumbnail"
|
||||||
|
ucapImage
|
||||||
|
[base]="versionInfo2Res?.profileRoot"
|
||||||
|
[path]="roomImage"
|
||||||
|
[default]="defaultProfileImage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
>{{ roomName }}
|
||||||
|
<span
|
||||||
|
class="user-count"
|
||||||
|
*ngIf="currentRoomInfo?.roomType === RoomType.Multi"
|
||||||
|
>
|
||||||
|
(<strong>{{ currentRoomInfo?.joinUserCount }}</strong
|
||||||
|
>)
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="example-spacer"></span>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
mat-mini-fab
|
||||||
|
color="primary"
|
||||||
|
aria-label="search"
|
||||||
|
matTooltip="{{ 'label.search' | ucapI18n }}"
|
||||||
|
(click)="onOpenChatSearch()"
|
||||||
|
>
|
||||||
|
<mat-icon>search</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-mini-fab
|
||||||
|
color="primary"
|
||||||
|
aria-label="alarm"
|
||||||
|
matTooltip="{{
|
||||||
|
(currentRoomInfo?.receiveAlarm
|
||||||
|
? 'label.notificationIsOn'
|
||||||
|
: 'label.notificationIsOff'
|
||||||
|
) | ucapI18n
|
||||||
|
}}"
|
||||||
|
(click)="onToggleAlarm(currentRoomInfo)"
|
||||||
|
>
|
||||||
|
<mat-icon *ngIf="currentRoomInfo?.receiveAlarm">notifications</mat-icon>
|
||||||
|
|
||||||
|
<mat-icon *ngIf="!currentRoomInfo?.receiveAlarm"
|
||||||
|
>notifications_off</mat-icon
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-mini-fab
|
||||||
|
color="primary"
|
||||||
|
aria-label="show room users"
|
||||||
|
matTooltip="{{ 'label.showRoomUsers' | ucapI18n }}"
|
||||||
|
(click)="onOpenRoomUserList(currentRoomInfo)"
|
||||||
|
>
|
||||||
|
<mat-icon>group</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-mini-fab color="primary" aria-label="">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-mini-fab
|
||||||
|
color="primary"
|
||||||
|
aria-label="Menu"
|
||||||
|
matTooltip="{{ 'label.menu' | ucapI18n }}"
|
||||||
|
(click)="onRightDrawerToggle()"
|
||||||
|
>
|
||||||
|
<mat-icon>menu</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-toolbar-row>
|
||||||
|
</mat-toolbar>
|
15
src/app/sections/chat/components/info.section.component.scss
Normal file
15
src/app/sections/chat/components/info.section.component.scss
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
@import '~@ucap/lg-scss/mixins';
|
||||||
|
|
||||||
|
.info-chat-toolbar {
|
||||||
|
height: 50px;
|
||||||
|
min-height: 50px;
|
||||||
|
background-color: $white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-content: center;
|
||||||
|
.mat-toolbar-row {
|
||||||
|
height: 50px;
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { InfoSectionComponent } from './info.section.component';
|
||||||
|
|
||||||
|
describe('app::sections::group::InfoSectionComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [RouterTestingModule],
|
||||||
|
declarations: [InfoSectionComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(InfoSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should have as title 'ucap-lg-web'`, () => {
|
||||||
|
const fixture = TestBed.createComponent(InfoSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(InfoSectionComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement;
|
||||||
|
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||||
|
'ucap-lg-web app is running!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
224
src/app/sections/chat/components/info.section.component.ts
Normal file
224
src/app/sections/chat/components/info.section.component.ts
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
EventEmitter,
|
||||||
|
ChangeDetectorRef
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Dictionary } from '@ngrx/entity';
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
import { combineLatest, Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { RoomSelector, RoomActions } from '@ucap/ng-store-chat';
|
||||||
|
import {
|
||||||
|
LoginSelector,
|
||||||
|
ConfigurationSelector
|
||||||
|
} from '@ucap/ng-store-authentication';
|
||||||
|
import {
|
||||||
|
RoomUserMap,
|
||||||
|
RoomUserShortMap
|
||||||
|
} from '@ucap/ng-store-chat/lib/store/room/state';
|
||||||
|
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||||
|
import { RoomInfo, RoomType, UpdateRequest } from '@ucap/protocol-room';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TranslatePipe as OrganizationTranslate,
|
||||||
|
TranslateService
|
||||||
|
} from '@ucap/ng-ui-organization';
|
||||||
|
|
||||||
|
import { AppChatService } from '@app/services/app-chat.service';
|
||||||
|
import { I18nService } from '@ucap/ng-i18n';
|
||||||
|
import { VersionInfo2Response } from '@ucap/api-public';
|
||||||
|
import { LocaleCode } from '@ucap/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sections-chat-info',
|
||||||
|
templateUrl: './info.section.component.html',
|
||||||
|
styleUrls: ['./info.section.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class InfoSectionComponent implements OnInit, OnDestroy {
|
||||||
|
@Input()
|
||||||
|
set roomId(roomId: string) {
|
||||||
|
this._roomId = roomId;
|
||||||
|
|
||||||
|
// request selected room
|
||||||
|
if (!!this.roomId) {
|
||||||
|
this.store.dispatch(
|
||||||
|
RoomActions.selectedRoom({
|
||||||
|
roomId: this.roomId,
|
||||||
|
localeCode: LocaleCode.Korean
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getRoomInfo();
|
||||||
|
}
|
||||||
|
get roomId(): string {
|
||||||
|
return this._roomId;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line: variable-name
|
||||||
|
_roomId: string;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
openChatSearch = new EventEmitter<void>();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
rightDrawerToggle = new EventEmitter<void>();
|
||||||
|
|
||||||
|
versionInfo2Res: VersionInfo2Response;
|
||||||
|
loginRes: LoginResponse;
|
||||||
|
|
||||||
|
defaultProfileImage: string;
|
||||||
|
defaultProfileImageMulti: string;
|
||||||
|
|
||||||
|
currentRoomInfo: RoomInfo;
|
||||||
|
roomList: RoomInfo[];
|
||||||
|
roomUsersDictionary: Dictionary<RoomUserMap>;
|
||||||
|
roomUsersShortDictionary: Dictionary<RoomUserShortMap>;
|
||||||
|
|
||||||
|
roomName: string;
|
||||||
|
roomImage: string;
|
||||||
|
|
||||||
|
RoomType = RoomType;
|
||||||
|
organizationTranslate: OrganizationTranslate;
|
||||||
|
private ngOnDestroySubject: Subject<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private store: Store<any>,
|
||||||
|
private appChatService: AppChatService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
// language setting
|
||||||
|
this.translateService.setDefaultLang(this.i18nService.currentLng);
|
||||||
|
this.translateService.use(this.i18nService.currentLng);
|
||||||
|
this.organizationTranslate = new OrganizationTranslate(
|
||||||
|
this.translateService,
|
||||||
|
this.changeDetectorRef
|
||||||
|
);
|
||||||
|
|
||||||
|
// default image setting
|
||||||
|
this.defaultProfileImage = this.appChatService.defaultProfileImage;
|
||||||
|
this.defaultProfileImageMulti = this.appChatService.defaultProfileImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.ngOnDestroySubject = new Subject<boolean>();
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.pipe(
|
||||||
|
takeUntil(this.ngOnDestroySubject),
|
||||||
|
select(ConfigurationSelector.versionInfo2Response)
|
||||||
|
)
|
||||||
|
.subscribe((versionInfo2Res) => {
|
||||||
|
this.versionInfo2Res = versionInfo2Res;
|
||||||
|
});
|
||||||
|
|
||||||
|
combineLatest([
|
||||||
|
this.store.pipe(select(LoginSelector.loginRes)),
|
||||||
|
this.store.pipe(select(RoomSelector.rooms)),
|
||||||
|
this.store.pipe(
|
||||||
|
select(
|
||||||
|
(state: any) =>
|
||||||
|
state.chat.room.roomUsers.entities as Dictionary<RoomUserMap>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
this.store.pipe(
|
||||||
|
select(
|
||||||
|
(state: any) =>
|
||||||
|
state.chat.room.roomUsersShort.entities as Dictionary<
|
||||||
|
RoomUserShortMap
|
||||||
|
>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||||
|
.subscribe(([loginRes, rooms, roomUsers, roomUsersShort]) => {
|
||||||
|
this.loginRes = loginRes;
|
||||||
|
if (!!loginRes && !!this.roomId) {
|
||||||
|
this.roomList = rooms;
|
||||||
|
this.roomUsersDictionary = roomUsers;
|
||||||
|
this.roomUsersShortDictionary = roomUsersShort;
|
||||||
|
|
||||||
|
this.getRoomInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoomInfo(): void {
|
||||||
|
// room render.
|
||||||
|
if (!this.roomList || this.roomList.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roomName = '...';
|
||||||
|
const idx = this.roomList.findIndex(
|
||||||
|
(roomInfo) => roomInfo.roomId === this.roomId
|
||||||
|
);
|
||||||
|
if (idx > -1 && !!this.roomList && this.roomList.length > 0) {
|
||||||
|
this.currentRoomInfo = this.roomList[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!this.currentRoomInfo) {
|
||||||
|
// Setting RoomName.
|
||||||
|
this.roomName = this.appChatService.getRoomName(
|
||||||
|
this.organizationTranslate,
|
||||||
|
this.loginRes,
|
||||||
|
this.currentRoomInfo,
|
||||||
|
this.roomUsersDictionary,
|
||||||
|
this.roomUsersShortDictionary
|
||||||
|
);
|
||||||
|
|
||||||
|
this.roomImage = this.appChatService.getRoomProfileImage(
|
||||||
|
this.currentRoomInfo,
|
||||||
|
this.loginRes,
|
||||||
|
this.roomUsersDictionary,
|
||||||
|
this.roomUsersShortDictionary
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleAlarm(roomInfo: RoomInfo): void {
|
||||||
|
if (!roomInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.store.dispatch(
|
||||||
|
RoomActions.update({
|
||||||
|
req: {
|
||||||
|
roomId: roomInfo.roomId,
|
||||||
|
roomName:
|
||||||
|
roomInfo.roomName.trim().length === 0
|
||||||
|
? ' '
|
||||||
|
: roomInfo.roomName.trim(),
|
||||||
|
receiveAlarm: !roomInfo.receiveAlarm,
|
||||||
|
syncAll: false
|
||||||
|
} as UpdateRequest
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenRoomUserList(roomInfo: RoomInfo): void {}
|
||||||
|
|
||||||
|
onOpenChatSearch(): void {
|
||||||
|
this.openChatSearch.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
onRightDrawerToggle(): void {
|
||||||
|
this.rightDrawerToggle.emit();
|
||||||
|
}
|
||||||
|
}
|
30
src/app/sections/chat/components/list.section.component.html
Normal file
30
src/app/sections/chat/components/list.section.component.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<div
|
||||||
|
*ngIf="!!searchObj && !searchObj.isShowSearch"
|
||||||
|
fxFlexFill
|
||||||
|
class="list-container"
|
||||||
|
>
|
||||||
|
<app-chat-room-expansion
|
||||||
|
[checkable]="checkable"
|
||||||
|
[selectedRoomList]="selectedRoomList"
|
||||||
|
(toggleItem)="onToggleItem($event)"
|
||||||
|
(openChatRoom)="onOpenChatRoom($event)"
|
||||||
|
>
|
||||||
|
</app-chat-room-expansion>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!!searchObj && searchObj.isShowSearch">
|
||||||
|
<ucap-chat-room-list-item-01
|
||||||
|
*ngFor="let roomInfo of searchRoomList"
|
||||||
|
[roomInfo]="roomInfo"
|
||||||
|
[roomName]="getRoomName(roomInfo)"
|
||||||
|
[profileImageRoot]="versionInfo2Res?.profileRoot"
|
||||||
|
[defaultProfileImage]="defaultProfileImage"
|
||||||
|
[profileImage]="getRoomProfileImage(roomInfo)"
|
||||||
|
[checked]="getChecked(roomInfo)"
|
||||||
|
[checkable]="checkable"
|
||||||
|
(toggleItem)="onToggleItem($event)"
|
||||||
|
(openChatRoom)="onOpenChatRoom($event)"
|
||||||
|
(toggleAlarm)="onToggleAlarm($event)"
|
||||||
|
(delRoom)="onDelRoom($event)"
|
||||||
|
(click)="onClickRoomItem($event, roomInfo)"
|
||||||
|
></ucap-chat-room-list-item-01>
|
||||||
|
</div>
|
|
@ -0,0 +1,2 @@
|
||||||
|
.list-container {
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { ListSectionComponent } from './list.section.component';
|
||||||
|
|
||||||
|
describe('app::sections::group::ListSectionComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [RouterTestingModule],
|
||||||
|
declarations: [ListSectionComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(ListSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should have as title 'ucap-lg-web'`, () => {
|
||||||
|
const fixture = TestBed.createComponent(ListSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(ListSectionComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement;
|
||||||
|
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||||
|
'ucap-lg-web app is running!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
416
src/app/sections/chat/components/list.section.component.ts
Normal file
416
src/app/sections/chat/components/list.section.component.ts
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
import { Subject, combineLatest } from 'rxjs';
|
||||||
|
import { takeUntil, take } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Input,
|
||||||
|
EventEmitter,
|
||||||
|
Output,
|
||||||
|
NgZone
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FixedSizeVirtualScrollStrategy,
|
||||||
|
VIRTUAL_SCROLL_STRATEGY
|
||||||
|
} from '@angular/cdk/scrolling';
|
||||||
|
|
||||||
|
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||||
|
|
||||||
|
import { LogService } from '@ucap/ng-logger';
|
||||||
|
import { SessionStorageService } from '@ucap/ng-web-storage';
|
||||||
|
import {
|
||||||
|
LoginSelector,
|
||||||
|
ConfigurationSelector
|
||||||
|
} from '@ucap/ng-store-authentication';
|
||||||
|
import { RoomSelector, RoomActions } from '@ucap/ng-store-chat';
|
||||||
|
import {
|
||||||
|
RoomInfo,
|
||||||
|
RoomType,
|
||||||
|
ExitAllRequest,
|
||||||
|
UpdateRequest,
|
||||||
|
ExitRequest
|
||||||
|
} from '@ucap/protocol-room';
|
||||||
|
import {
|
||||||
|
RoomUserMap,
|
||||||
|
RoomUserShortMap
|
||||||
|
} from '@ucap/ng-store-chat/lib/store/room/state';
|
||||||
|
import { Dictionary } from '@ngrx/entity';
|
||||||
|
import {
|
||||||
|
TranslatePipe as OrganizationTranslate,
|
||||||
|
TranslateService
|
||||||
|
} from '@ucap/ng-ui-organization';
|
||||||
|
import { I18nService } from '@ucap/ng-i18n';
|
||||||
|
import { AppChatService } from '@app/services/app-chat.service';
|
||||||
|
import {
|
||||||
|
DateService,
|
||||||
|
ConfirmDialogComponent,
|
||||||
|
ConfirmDialogData,
|
||||||
|
ConfirmDialogResult
|
||||||
|
} from '@ucap/ng-ui';
|
||||||
|
import { VersionInfo2Response } from '@ucap/api-public';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
export class ChatVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
|
||||||
|
constructor() {
|
||||||
|
super(60, 150, 200); // (itemSize, minBufferPx, maxBufferPx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sections-chat-list',
|
||||||
|
templateUrl: './list.section.component.html',
|
||||||
|
styleUrls: ['./list.section.component.scss'],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: VIRTUAL_SCROLL_STRATEGY,
|
||||||
|
useClass: ChatVirtualScrollStrategy
|
||||||
|
}
|
||||||
|
],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class ListSectionComponent implements OnInit, OnDestroy {
|
||||||
|
@Input()
|
||||||
|
set searchObj(obj: { isShowSearch: boolean; searchWord: string }) {
|
||||||
|
this._searchObj = obj;
|
||||||
|
|
||||||
|
if (obj.isShowSearch && obj.searchWord.localeCompare('') !== 0) {
|
||||||
|
this.onRoomSearch(obj);
|
||||||
|
} else {
|
||||||
|
this._searchObj.isShowSearch = false;
|
||||||
|
this.searchRoomList = [];
|
||||||
|
}
|
||||||
|
this.searchResultList.emit(this.searchRoomList);
|
||||||
|
}
|
||||||
|
|
||||||
|
get searchObj() {
|
||||||
|
return this._searchObj;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line: variable-name
|
||||||
|
_searchObj: any;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
checkable = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
selectedRoomList: RoomInfo[] = [];
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
searchResultList = new EventEmitter<RoomInfo[]>();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
toggleItem = new EventEmitter<{
|
||||||
|
checked: boolean;
|
||||||
|
roomInfo: RoomInfo;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
delRoom = new EventEmitter<RoomInfo>();
|
||||||
|
|
||||||
|
versionInfo2Res: VersionInfo2Response;
|
||||||
|
loginRes: LoginResponse;
|
||||||
|
|
||||||
|
defaultProfileImage: string;
|
||||||
|
defaultProfileImageMulti: string;
|
||||||
|
|
||||||
|
roomList: RoomInfo[];
|
||||||
|
roomUsersDictionary: Dictionary<RoomUserMap>;
|
||||||
|
roomUsersShortDictionary: Dictionary<RoomUserShortMap>;
|
||||||
|
|
||||||
|
searchRoomList: RoomInfo[];
|
||||||
|
|
||||||
|
roomGroup: { division: string; roomList: RoomInfo[] }[];
|
||||||
|
|
||||||
|
organizationTranslate: OrganizationTranslate;
|
||||||
|
|
||||||
|
private ngOnDestroySubject: Subject<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private appChatService: AppChatService,
|
||||||
|
private dateService: DateService,
|
||||||
|
private sessionStorageService: SessionStorageService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private store: Store<any>,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private ngZone: NgZone,
|
||||||
|
private logService: LogService
|
||||||
|
) {
|
||||||
|
// language setting
|
||||||
|
this.translateService.setDefaultLang(this.i18nService.currentLng);
|
||||||
|
this.translateService.use(this.i18nService.currentLng);
|
||||||
|
this.organizationTranslate = new OrganizationTranslate(
|
||||||
|
this.translateService,
|
||||||
|
this.changeDetectorRef
|
||||||
|
);
|
||||||
|
this.i18nService.setDefaultNamespace('chat');
|
||||||
|
|
||||||
|
// default image setting
|
||||||
|
this.defaultProfileImage = this.appChatService.defaultProfileImage;
|
||||||
|
this.defaultProfileImageMulti = this.appChatService.defaultProfileImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.ngOnDestroySubject = new Subject<boolean>();
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.pipe(
|
||||||
|
takeUntil(this.ngOnDestroySubject),
|
||||||
|
select(ConfigurationSelector.versionInfo2Response)
|
||||||
|
)
|
||||||
|
.subscribe((versionInfo2Res) => {
|
||||||
|
this.versionInfo2Res = versionInfo2Res;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
|
||||||
|
.subscribe((loginRes) => {
|
||||||
|
this.loginRes = loginRes;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject), select(RoomSelector.rooms))
|
||||||
|
.subscribe((rooms) => {
|
||||||
|
rooms = (rooms || []).filter((info) => info.isJoinRoom);
|
||||||
|
this.roomList = rooms;
|
||||||
|
|
||||||
|
// groupping.
|
||||||
|
this.initGroup();
|
||||||
|
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
combineLatest([
|
||||||
|
this.store.pipe(
|
||||||
|
select(
|
||||||
|
(state: any) =>
|
||||||
|
state.chat.room.roomUsers.entities as Dictionary<RoomUserMap>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
this.store.pipe(
|
||||||
|
select(
|
||||||
|
(state: any) =>
|
||||||
|
state.chat.room.roomUsersShort.entities as Dictionary<
|
||||||
|
RoomUserShortMap
|
||||||
|
>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||||
|
.subscribe(([roomUsers, roomUsersShort]) => {
|
||||||
|
this.roomUsersDictionary = roomUsers;
|
||||||
|
this.roomUsersShortDictionary = roomUsersShort;
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initGroup() {
|
||||||
|
this.roomGroup = [];
|
||||||
|
|
||||||
|
this.roomList.forEach((roomInfo) => {
|
||||||
|
const date = roomInfo.finalEventDate;
|
||||||
|
let division = '';
|
||||||
|
try {
|
||||||
|
const value = this.dateService.get(date, 'LL');
|
||||||
|
|
||||||
|
if (value === 'Invalid date') {
|
||||||
|
division = date;
|
||||||
|
} else {
|
||||||
|
division = value;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
division = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = this.roomGroup.findIndex(
|
||||||
|
(info) => info.division === division
|
||||||
|
);
|
||||||
|
if (index > -1) {
|
||||||
|
this.roomGroup[index] = {
|
||||||
|
...this.roomGroup[index],
|
||||||
|
roomList: [...this.roomGroup[index].roomList, roomInfo]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.roomGroup.push({
|
||||||
|
division,
|
||||||
|
roomList: [roomInfo]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoomName(roomInfo: RoomInfo): string {
|
||||||
|
if (!roomInfo) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const roomName = this.appChatService.getRoomName(
|
||||||
|
this.organizationTranslate,
|
||||||
|
this.loginRes,
|
||||||
|
roomInfo,
|
||||||
|
this.roomUsersDictionary,
|
||||||
|
this.roomUsersShortDictionary
|
||||||
|
);
|
||||||
|
|
||||||
|
return roomName;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoomProfileImage(roomInfo: RoomInfo): string {
|
||||||
|
let roomImage = '';
|
||||||
|
if (!!roomInfo) {
|
||||||
|
roomImage = this.appChatService.getRoomProfileImage(
|
||||||
|
roomInfo,
|
||||||
|
this.loginRes,
|
||||||
|
this.roomUsersDictionary,
|
||||||
|
this.roomUsersShortDictionary
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return roomImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
isToday(date: any) {
|
||||||
|
return this.dateService.isToday(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRoomSearch(obj: { isShowSearch: boolean; searchWord: string }) {
|
||||||
|
const searchRoomList: RoomInfo[] = [];
|
||||||
|
|
||||||
|
this.roomList.forEach((roomInfo) => {
|
||||||
|
if (roomInfo.roomName.indexOf(obj.searchWord) > -1) {
|
||||||
|
searchRoomList.push(roomInfo);
|
||||||
|
} else {
|
||||||
|
const roomUsers = this.appChatService.getRoomUserList(
|
||||||
|
this.loginRes,
|
||||||
|
roomInfo.roomId,
|
||||||
|
this.roomUsersDictionary,
|
||||||
|
this.roomUsersShortDictionary
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
roomUsers.existUsers &&
|
||||||
|
roomUsers.users.filter(
|
||||||
|
(userInfo) =>
|
||||||
|
userInfo.name.indexOf(obj.searchWord) > -1 ||
|
||||||
|
userInfo.nameEn.indexOf(obj.searchWord) > -1 ||
|
||||||
|
userInfo.nameCn.indexOf(obj.searchWord) > -1
|
||||||
|
).length > 0
|
||||||
|
) {
|
||||||
|
searchRoomList.push(roomInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.searchRoomList = searchRoomList;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChecked(roomInfo: RoomInfo): boolean {
|
||||||
|
if (this.selectedRoomList.some((info) => info.roomId === roomInfo.roomId)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleItem(event: { checked: boolean; roomInfo: RoomInfo }): void {
|
||||||
|
this.toggleItem.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickRoomItem(event: MouseEvent, roomInfo: RoomInfo): void {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (!!this.checkable) {
|
||||||
|
this.onToggleItem({
|
||||||
|
checked: !this.getChecked(roomInfo),
|
||||||
|
roomInfo
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.onOpenChatRoom(roomInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenChatRoom(roomInfo: RoomInfo): void {
|
||||||
|
this.ngZone.run(() => {
|
||||||
|
this.router.navigate(
|
||||||
|
[
|
||||||
|
'chat',
|
||||||
|
{
|
||||||
|
outlets: { content: 'chatroom' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
queryParams: { roomId: roomInfo.roomId }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleAlarm(roomInfo: RoomInfo): void {
|
||||||
|
if (!roomInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.store.dispatch(
|
||||||
|
RoomActions.update({
|
||||||
|
req: {
|
||||||
|
roomId: roomInfo.roomId,
|
||||||
|
roomName:
|
||||||
|
roomInfo.roomName.trim().length === 0
|
||||||
|
? ' '
|
||||||
|
: roomInfo.roomName.trim(),
|
||||||
|
receiveAlarm: !roomInfo.receiveAlarm,
|
||||||
|
syncAll: false
|
||||||
|
} as UpdateRequest
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
onDelRoom(roomInfo: RoomInfo): void {
|
||||||
|
if (!roomInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogRef = this.dialog.open<
|
||||||
|
ConfirmDialogComponent,
|
||||||
|
ConfirmDialogData,
|
||||||
|
ConfirmDialogResult
|
||||||
|
>(ConfirmDialogComponent, {
|
||||||
|
data: {
|
||||||
|
title: this.i18nService.t('room.dialog.titleExitFromRoom'),
|
||||||
|
html: this.i18nService.t('room.dialog.confirmExitFromRoom')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef
|
||||||
|
.afterClosed()
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe((result) => {
|
||||||
|
if (!!result && !!result.choice) {
|
||||||
|
this.store.dispatch(
|
||||||
|
RoomActions.del({
|
||||||
|
req: {
|
||||||
|
roomId: roomInfo.roomId
|
||||||
|
} as ExitRequest
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
}
|
31
src/app/sections/chat/components/list.section.strategy.ts
Normal file
31
src/app/sections/chat/components/list.section.strategy.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
|
||||||
|
import {
|
||||||
|
VirtualScrollStrategy,
|
||||||
|
CdkVirtualScrollViewport
|
||||||
|
} from '@angular/cdk/scrolling';
|
||||||
|
import { distinctUntilChanged } from 'rxjs/operators';
|
||||||
|
|
||||||
|
export class ChatGroupVirtualScrollStrategy implements VirtualScrollStrategy {
|
||||||
|
scrolledIndexChange: Observable<number>;
|
||||||
|
|
||||||
|
private indexSubject = new Subject<number>();
|
||||||
|
private viewport: CdkVirtualScrollViewport | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.scrolledIndexChange = this.indexSubject.pipe(distinctUntilChanged());
|
||||||
|
}
|
||||||
|
|
||||||
|
attach(viewport: CdkVirtualScrollViewport): void {
|
||||||
|
this.viewport = viewport;
|
||||||
|
}
|
||||||
|
detach(): void {
|
||||||
|
this.indexSubject.complete();
|
||||||
|
this.viewport = null;
|
||||||
|
}
|
||||||
|
onContentScrolled(): void {}
|
||||||
|
onDataLengthChanged(): void {}
|
||||||
|
onContentRendered(): void {}
|
||||||
|
onRenderedOffsetChanged(): void {}
|
||||||
|
scrollToIndex(index: number, behavior: ScrollBehavior): void {}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<button mat-stroked-button class="icon-button text-accent-darkest">
|
||||||
|
<mat-icon>arrow_downward</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-stroked-button class="icon-button text-accent-darkest">
|
||||||
|
<mat-icon>arrow_upward</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="chat-area">
|
||||||
|
<div class="more-event">
|
||||||
|
<button mat-button aria-label="이모티콘">^이전 대화 보기</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div *ngFor="let event of eventList">
|
||||||
|
{{ event.senderSeq }} // {{ loginRes?.userSeq }}
|
||||||
|
{{ event.senderSeq === loginRes?.userSeq }}
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<app-chat-message-box
|
||||||
|
*ngFor="let event of eventList"
|
||||||
|
[message]="event"
|
||||||
|
[isMe]="event.senderSeq + '' === loginRes?.userSeq + ''"
|
||||||
|
[senderInfo]="getSenderInfo(event.senderSeq)"
|
||||||
|
[defaultProfileImage]="defaultProfileImage"
|
||||||
|
[profileImageRoot]="versionInfo2Res?.profileRoot"
|
||||||
|
[dateChanged]="getDateSplitter(event)"
|
||||||
|
[unreadCount]="getUnreadCount(event)"
|
||||||
|
></app-chat-message-box>
|
||||||
|
|
||||||
|
<div class="recent-receive-message">
|
||||||
|
스크롤 상단일때 도착한 최근 메시지 표시 영역
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { MessageSectionComponent } from './message.section.component';
|
||||||
|
|
||||||
|
describe('app::sections::group::MessageSectionComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [RouterTestingModule],
|
||||||
|
declarations: [MessageSectionComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(MessageSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should have as title 'ucap-lg-web'`, () => {
|
||||||
|
const fixture = TestBed.createComponent(MessageSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(MessageSectionComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement;
|
||||||
|
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||||
|
'ucap-lg-web app is running!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
172
src/app/sections/chat/components/message.section.component.ts
Normal file
172
src/app/sections/chat/components/message.section.component.ts
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
import {
|
||||||
|
Subject,
|
||||||
|
Observable,
|
||||||
|
BehaviorSubject,
|
||||||
|
Subscription,
|
||||||
|
merge
|
||||||
|
} from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Input
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Info, EventJson, EventType, FileType } from '@ucap/protocol-event';
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
import { Chatting } from '@ucap/ng-store-chat/lib/store/chatting/state';
|
||||||
|
import { ChattingSelector, RoomSelector } from '@ucap/ng-store-chat';
|
||||||
|
import {
|
||||||
|
UserInfo as RoomUserInfo,
|
||||||
|
UserInfoShort as RoomUserInfoShort
|
||||||
|
} from '@ucap/protocol-room';
|
||||||
|
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||||
|
import {
|
||||||
|
LoginSelector,
|
||||||
|
ConfigurationSelector
|
||||||
|
} from '@ucap/ng-store-authentication';
|
||||||
|
import { AppChatService } from '@app/services/app-chat.service';
|
||||||
|
import { VersionInfo2Response } from '@ucap/api-public';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sections-chat-message',
|
||||||
|
templateUrl: './message.section.component.html',
|
||||||
|
styleUrls: ['./message.section.component.scss'],
|
||||||
|
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class MessageSectionComponent implements OnInit, OnDestroy {
|
||||||
|
@Input()
|
||||||
|
set roomId(roomId: string) {
|
||||||
|
this._roomId = roomId;
|
||||||
|
|
||||||
|
this.roomIdSubject.next(roomId);
|
||||||
|
|
||||||
|
this.initializeRoomData();
|
||||||
|
}
|
||||||
|
get roomId(): string {
|
||||||
|
return this._roomId;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line: variable-name
|
||||||
|
_roomId: string;
|
||||||
|
|
||||||
|
versionInfo2Res: VersionInfo2Response;
|
||||||
|
loginRes: LoginResponse;
|
||||||
|
|
||||||
|
roomIdSubject = new Subject<string>();
|
||||||
|
|
||||||
|
chatting$: Observable<Chatting>;
|
||||||
|
roomUsers: RoomUserInfoShort[] = [];
|
||||||
|
// eventList$: Observable<Info<EventJson>[]>;
|
||||||
|
eventList: Info<EventJson>[];
|
||||||
|
|
||||||
|
defaultProfileImage: string;
|
||||||
|
EventType = EventType;
|
||||||
|
FileType = FileType;
|
||||||
|
|
||||||
|
private ngOnDestroySubject = new Subject<boolean>();
|
||||||
|
constructor(
|
||||||
|
private appChatService: AppChatService,
|
||||||
|
private store: Store<any>,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
this.defaultProfileImage = this.appChatService.defaultProfileImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.store
|
||||||
|
.pipe(
|
||||||
|
takeUntil(this.ngOnDestroySubject),
|
||||||
|
select(ConfigurationSelector.versionInfo2Response)
|
||||||
|
)
|
||||||
|
.subscribe((versionInfo2Res) => {
|
||||||
|
this.versionInfo2Res = versionInfo2Res;
|
||||||
|
});
|
||||||
|
this.store
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
|
||||||
|
.subscribe((loginRes) => {
|
||||||
|
this.loginRes = loginRes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeRoomData() {
|
||||||
|
this.chatting$ = this.store.pipe(
|
||||||
|
takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)),
|
||||||
|
select(ChattingSelector.chatting, this.roomId)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.pipe(
|
||||||
|
takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)),
|
||||||
|
select(ChattingSelector.eventList, this.roomId)
|
||||||
|
)
|
||||||
|
.subscribe((eventList) => {
|
||||||
|
this.eventList = eventList;
|
||||||
|
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.pipe(
|
||||||
|
takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)),
|
||||||
|
select(RoomSelector.roomUserShort, this.roomId)
|
||||||
|
)
|
||||||
|
.subscribe((roomUserShort) => {
|
||||||
|
if (!!roomUserShort) {
|
||||||
|
this.roomUsers = roomUserShort.userInfos;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sender Info */
|
||||||
|
getSenderInfo(
|
||||||
|
senderSeq: number
|
||||||
|
): RoomUserInfoShort | RoomUserInfo | undefined {
|
||||||
|
if (!!this.roomUsers && this.roomUsers.length) {
|
||||||
|
return this.roomUsers.find(
|
||||||
|
(userInfo) => userInfo.seq + '' === senderSeq + ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Date Splitter show check */
|
||||||
|
getDateSplitter(event: Info<EventJson>): boolean {
|
||||||
|
const curIndex = this.eventList.findIndex((v) => v.seq === event.seq);
|
||||||
|
|
||||||
|
if (curIndex === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (curIndex > 0) {
|
||||||
|
if (!this.eventList[curIndex]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !moment(this.eventList[curIndex].sendDate).isSame(
|
||||||
|
moment(this.eventList[curIndex - 1].sendDate),
|
||||||
|
'day'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unread Count calculation */
|
||||||
|
getUnreadCount(message: Info<EventJson>): string | number {
|
||||||
|
const unreadCnt = this.roomUsers
|
||||||
|
.filter(
|
||||||
|
(user) => user.isJoinRoom && user.seq + '' !== message.senderSeq + ''
|
||||||
|
)
|
||||||
|
.filter((user) => user.lastReadEventSeq < message.seq).length;
|
||||||
|
return unreadCnt === 0 ? '' : unreadCnt;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
<div class="search-container">
|
||||||
|
<div class="searchbox">
|
||||||
|
<form [formGroup]="fgSearch">
|
||||||
|
<mat-form-field floatLabel="never">
|
||||||
|
<mat-label>{{ 'room.searchRoomByName' | ucapI18n }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
#searchWordInput
|
||||||
|
type="text"
|
||||||
|
maxlength="20"
|
||||||
|
placeholder="{{ 'room.searchRoomByName' | ucapI18n }}"
|
||||||
|
formControlName="searchInput"
|
||||||
|
[matAutocomplete]="auto"
|
||||||
|
(keydown.enter)="onKeyDownEnter($event, searchWordInput.value)"
|
||||||
|
/>
|
||||||
|
<mat-autocomplete #auto="matAutocomplete">
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let filteredRecommendedWord of filteredRecommendedWordList"
|
||||||
|
[value]="filteredRecommendedWord"
|
||||||
|
>
|
||||||
|
{{ filteredRecommendedWord }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-autocomplete>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
matSuffix
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Clear"
|
||||||
|
(click)="searchWordInput.value = ''; onClickCancel()"
|
||||||
|
>
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-icon matSuffix>search</mat-icon>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { SearchSectionComponent } from './search.section.component';
|
||||||
|
|
||||||
|
describe('app::sections::group::SearchSectionComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [RouterTestingModule],
|
||||||
|
declarations: [SearchSectionComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(SearchSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should have as title 'ucap-lg-web'`, () => {
|
||||||
|
const fixture = TestBed.createComponent(SearchSectionComponent);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(SearchSectionComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement;
|
||||||
|
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||||
|
'ucap-lg-web app is running!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
147
src/app/sections/chat/components/search.section.component.ts
Normal file
147
src/app/sections/chat/components/search.section.component.ts
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
Output,
|
||||||
|
EventEmitter,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { LogService } from '@ucap/ng-logger';
|
||||||
|
import { takeUntil, debounceTime } from 'rxjs/operators';
|
||||||
|
import { Subject, combineLatest } from 'rxjs';
|
||||||
|
|
||||||
|
import { RoomSelector } from '@ucap/ng-store-chat';
|
||||||
|
import { LoginSelector } from '@ucap/ng-store-authentication';
|
||||||
|
|
||||||
|
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||||
|
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sections-chat-search',
|
||||||
|
templateUrl: './search.section.component.html',
|
||||||
|
styleUrls: ['./search.section.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class SearchSectionComponent implements OnInit, OnDestroy {
|
||||||
|
@Output()
|
||||||
|
keyDownEnter = new EventEmitter<{
|
||||||
|
searchWord: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
searchCancel = new EventEmitter<any>();
|
||||||
|
|
||||||
|
loginRes: LoginResponse;
|
||||||
|
|
||||||
|
fgSearch: FormGroup;
|
||||||
|
recommendedWordList: string[];
|
||||||
|
filteredRecommendedWordList: string[];
|
||||||
|
|
||||||
|
@ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger;
|
||||||
|
|
||||||
|
private ngOnDestroySubject: Subject<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private store: Store<any>,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.ngOnDestroySubject = new Subject<boolean>();
|
||||||
|
|
||||||
|
this.fgSearch = this.formBuilder.group({
|
||||||
|
searchInput: null
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fgSearch
|
||||||
|
.get('searchInput')
|
||||||
|
.valueChanges.pipe(debounceTime(100))
|
||||||
|
.subscribe((value) => {
|
||||||
|
if (value !== null && value.length > 0) {
|
||||||
|
this.filteredRecommendedWordList = this.recommendedWordList.filter(
|
||||||
|
(v) => {
|
||||||
|
return v.includes(value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.filteredRecommendedWordList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
|
||||||
|
.subscribe((loginRes) => {
|
||||||
|
this.loginRes = loginRes;
|
||||||
|
});
|
||||||
|
|
||||||
|
combineLatest([
|
||||||
|
this.store.pipe(select(RoomSelector.rooms)),
|
||||||
|
this.store.pipe(select(RoomSelector.roomUsers)),
|
||||||
|
this.store.pipe(select(RoomSelector.roomUsersShort))
|
||||||
|
])
|
||||||
|
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||||
|
.subscribe(([rooms, roomUsers, roomUsersShort]) => {
|
||||||
|
rooms = rooms || [];
|
||||||
|
roomUsers = roomUsers || [];
|
||||||
|
roomUsersShort = roomUsersShort || [];
|
||||||
|
|
||||||
|
const recommendedWordList = [];
|
||||||
|
for (const r of rooms) {
|
||||||
|
if (!!r.roomName && '' !== r.roomName.trim()) {
|
||||||
|
recommendedWordList.push(r.roomName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const ru of roomUsers) {
|
||||||
|
for (const u of ru.userInfos) {
|
||||||
|
if (!!this.loginRes && u.seq !== Number(this.loginRes.userSeq)) {
|
||||||
|
if (!!u.name && '' !== u.name.trim() && u.isJoinRoom) {
|
||||||
|
recommendedWordList.push(u.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const ru of roomUsersShort) {
|
||||||
|
for (const u of ru.userInfos) {
|
||||||
|
if (!!this.loginRes && u.seq !== Number(this.loginRes.userSeq)) {
|
||||||
|
if (!!u.name && '' !== u.name.trim() && u.isJoinRoom) {
|
||||||
|
recommendedWordList.push(u.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.recommendedWordList = [
|
||||||
|
...recommendedWordList.filter(
|
||||||
|
(item, index) => recommendedWordList.indexOf(item) === index
|
||||||
|
)
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDownEnter(event: KeyboardEvent, searchWord: string) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
this.autocomplete.closePanel();
|
||||||
|
|
||||||
|
this.keyDownEnter.emit({ searchWord });
|
||||||
|
}
|
||||||
|
onClickCancel() {
|
||||||
|
this.searchCancel.emit();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
<mat-card class="confirm-card mat-elevation-z dialog-creat-chat">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title
|
||||||
|
cdkDrag
|
||||||
|
cdkDragRootElement=".cdk-overlay-pane"
|
||||||
|
cdkDragHandle
|
||||||
|
>{{ data.title }}</mat-card-title
|
||||||
|
>
|
||||||
|
<button class="icon-button btn-dialog-close" (click)="onClickChoice(false)">
|
||||||
|
<i class="mdi mdi-window-close"></i>
|
||||||
|
</button>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div>
|
||||||
|
<mat-horizontal-stepper [linear]="isLinear" #stepper>
|
||||||
|
<mat-step label="Step 1" state="phone">
|
||||||
|
<ucap-local-organization-select-user
|
||||||
|
(changeUserList)="onChangeSelectedUserList($event)"
|
||||||
|
></ucap-local-organization-select-user>
|
||||||
|
<ng-template
|
||||||
|
[ngTemplateOutlet]="selectedUserListTemplate"
|
||||||
|
></ng-template>
|
||||||
|
<div>
|
||||||
|
<button mat-button>취소</button>
|
||||||
|
<button mat-button matStepperNext>
|
||||||
|
완료
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-step>
|
||||||
|
<mat-step label="Step 2" state="chat">
|
||||||
|
<div>
|
||||||
|
<mat-label>새 그룹 추가</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
#searchWordInput
|
||||||
|
placeholder="그룹 이름을 입력해주세요."
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
matSuffix
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Clear"
|
||||||
|
(click)="searchWordInput.value = ''; onClickCancel()"
|
||||||
|
>
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<ng-template
|
||||||
|
[ngTemplateOutlet]="selectedUserListTemplate"
|
||||||
|
></ng-template>
|
||||||
|
<div>
|
||||||
|
<button mat-button matStepperPrevious>Back</button>
|
||||||
|
<button mat-button (click)="onClickComplete(searchWordInput.value)">
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-step>
|
||||||
|
</mat-horizontal-stepper>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <mat-card-actions class="button-form flex-row">
|
||||||
|
<button
|
||||||
|
mat-stroked-button
|
||||||
|
(click)="onClickChoice(false)"
|
||||||
|
class="mat-primary"
|
||||||
|
>
|
||||||
|
{{ 'common.messages.no' | translate }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
[disabled]="getBtnValid()"
|
||||||
|
(click)="onClickChoice(true)"
|
||||||
|
class="mat-primary"
|
||||||
|
>
|
||||||
|
{{ 'common.messages.yes' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions> -->
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
<ng-template #selectedUserListTemplate>
|
||||||
|
<div class="list-chip">
|
||||||
|
<mat-chip-list aria-label="User selection">
|
||||||
|
<mat-chip
|
||||||
|
*ngFor="let userInfo of selectedUserList"
|
||||||
|
[selected]="getChipsRemoveYn(userInfo)"
|
||||||
|
(removed)="onClickDeleteUser(userInfo)"
|
||||||
|
>
|
||||||
|
<!-- {{ userInfo | ucapTranslate: 'name' }} -->
|
||||||
|
{{ userInfo.name }}
|
||||||
|
<mat-icon matChipRemove *ngIf="getChipsRemoveYn(userInfo)"
|
||||||
|
>clear</mat-icon
|
||||||
|
>
|
||||||
|
</mat-chip>
|
||||||
|
</mat-chip-list>
|
||||||
|
</div>
|
||||||
|
<ng-container
|
||||||
|
*ngIf="
|
||||||
|
SelectUserDialogType.NewChat === SelectUserDialogType.NewChat;
|
||||||
|
then newchatcount;
|
||||||
|
else defaultcount
|
||||||
|
"
|
||||||
|
></ng-container>
|
||||||
|
<ng-template #newchatcount>
|
||||||
|
<span [ngClass]="selectedUserList.length >= 300 ? 'text-warn-color' : ''">
|
||||||
|
{{ selectedUserList.length }} / 300
|
||||||
|
<!-- {{ environment.productConfig.CommonSetting.maxChatRoomUser - 1 }} -->
|
||||||
|
<!-- {{ 'common.units.persons' | translate }} -->
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="text-warn-color"
|
||||||
|
style="float: right;"
|
||||||
|
*ngIf="selectedUserList.length >= 300"
|
||||||
|
>
|
||||||
|
<!-- ({{
|
||||||
|
'chat.errors.maxCountOfRoomMemberWith'
|
||||||
|
| translate
|
||||||
|
: {
|
||||||
|
maxCount:
|
||||||
|
environment.productConfig.CommonSetting.maxChatRoomUser - 1
|
||||||
|
}
|
||||||
|
}}) -->
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #defaultcount>
|
||||||
|
<span>
|
||||||
|
{{ selectedUserList.length }}
|
||||||
|
<!-- {{ 'common.units.persons' | translate }} -->
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</ng-template>
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
|
||||||
|
import { CreateChatDialogComponent } from './create-chat.dialog.component';
|
||||||
|
|
||||||
|
describe('ucap::ui-organization::CreateChatDialogComponent', () => {
|
||||||
|
let component: CreateChatDialogComponent;
|
||||||
|
let fixture: ComponentFixture<CreateChatDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [CreateChatDialogComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CreateChatDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,134 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Inject,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
|
||||||
|
import {
|
||||||
|
UserInfoSS,
|
||||||
|
UserInfoF,
|
||||||
|
UserInfoDN,
|
||||||
|
DeptInfo
|
||||||
|
} from '@ucap/protocol-query';
|
||||||
|
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
CompanySelector,
|
||||||
|
DepartmentSelector
|
||||||
|
} from '@ucap/ng-store-organization';
|
||||||
|
import { Subject, combineLatest } from 'rxjs';
|
||||||
|
import { AppAuthenticationService } from '@app/services/app-authentication.service';
|
||||||
|
import { SelectUserDialogType } from '@app/types';
|
||||||
|
import { RoomInfo, UserInfo as RoomUserInfo } from '@ucap/protocol-room';
|
||||||
|
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
|
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
import { SelectUserSectionComponent } from '../../select-user.section.component';
|
||||||
|
import { GroupActions } from '@ucap/ng-store-group';
|
||||||
|
import { UserInfoTypes } from '../profile-list-item.component';
|
||||||
|
|
||||||
|
export interface CreateChatDialogData {
|
||||||
|
type?: SelectUserDialogType;
|
||||||
|
title: string;
|
||||||
|
/** CASE :: EditMember */
|
||||||
|
group?: GroupDetailData;
|
||||||
|
/** CASE :: EventForward */
|
||||||
|
ignoreRoom?: RoomInfo[];
|
||||||
|
/** CASE :: EditChatMember */
|
||||||
|
curRoomUser?: (
|
||||||
|
| UserInfo
|
||||||
|
| UserInfoSS
|
||||||
|
| UserInfoF
|
||||||
|
| UserInfoDN
|
||||||
|
| RoomUserInfo
|
||||||
|
)[];
|
||||||
|
}
|
||||||
|
export interface CreateChatDialogResult {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ucap-local-organization-create-chat.dialog',
|
||||||
|
templateUrl: './create-chat.dialog.component.html',
|
||||||
|
styleUrls: ['./create-chat.dialog.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class CreateChatDialogComponent implements OnInit, OnDestroy {
|
||||||
|
private ngOnDestroySubject = new Subject<boolean>();
|
||||||
|
isLinear = false;
|
||||||
|
firstFormGroup: FormGroup;
|
||||||
|
secondFormGroup: FormGroup;
|
||||||
|
selectedUserList: UserInfoTypes[] = [];
|
||||||
|
SelectUserDialogType = SelectUserDialogType;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<
|
||||||
|
CreateChatDialogData,
|
||||||
|
CreateChatDialogResult
|
||||||
|
>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: CreateChatDialogData,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private store: Store<any>,
|
||||||
|
private appAuthenticationService: AppAuthenticationService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@ViewChild('selectBoxUserComponent', { static: false })
|
||||||
|
selectBoxUserComponent: SelectUserSectionComponent;
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (!!this.ngOnDestroySubject) {
|
||||||
|
this.ngOnDestroySubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickCancel() {}
|
||||||
|
onClickChoice(s: boolean) {}
|
||||||
|
getBtnValid() {}
|
||||||
|
getChipsRemoveYn(userInfo: UserInfo) {}
|
||||||
|
onClickDeleteUser(userInfo: UserInfo) {}
|
||||||
|
|
||||||
|
onChangeSelectedUserList(userList: UserInfoTypes[]) {
|
||||||
|
this.selectedUserList = userList;
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
onClickComplete(groupName: string) {
|
||||||
|
switch (this.data.type) {
|
||||||
|
case SelectUserDialogType.NewGroup:
|
||||||
|
{
|
||||||
|
const userSeqs: string[] = [];
|
||||||
|
this.selectedUserList.map((user) =>
|
||||||
|
userSeqs.push(user.seq.toString())
|
||||||
|
);
|
||||||
|
this.store.dispatch(
|
||||||
|
GroupActions.create({
|
||||||
|
groupName,
|
||||||
|
targetUserSeqs: userSeqs
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SelectUserDialogType.NewChat:
|
||||||
|
{
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SelectUserDialogType.EditChatMember:
|
||||||
|
{
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SelectUserDialogType.EditMember:
|
||||||
|
{
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SelectUserDialogType.MessageForward:
|
||||||
|
{
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { CreateChatDialogComponent } from './create-chat.dialog.component';
|
||||||
|
|
||||||
|
export const DIALOGS = [CreateChatDialogComponent];
|
9
src/app/sections/group/components/component-ui/index.ts
Normal file
9
src/app/sections/group/components/component-ui/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { ProfileListItemComponent } from './profile-list-item.component';
|
||||||
|
import { ProfileComponent } from './profile.component';
|
||||||
|
import { TenantSearchComponent } from './tenant-search.component';
|
||||||
|
|
||||||
|
export const COMPONENTS = [
|
||||||
|
ProfileListItemComponent,
|
||||||
|
ProfileComponent,
|
||||||
|
TenantSearchComponent
|
||||||
|
];
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user