0710 sync

This commit is contained in:
Park Byung Eun 2020-07-11 21:09:04 +09:00
parent 92da6c71ce
commit 98ff58c39e
468 changed files with 29578 additions and 9309 deletions

View File

@ -12,5 +12,5 @@
},
"go.testFlags": ["-v"],
"go.testTimeout": "100s",
"debug.node.autoAttach": "off"
"debug.node.autoAttach": "on"
}

View File

@ -37,6 +37,10 @@
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
},
{
"replace": "src/environments/native/native.ts",
"with": "src/environments/native/native.browser.prod.ts"
}
],
"optimization": true,
@ -51,7 +55,7 @@
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
"maximumError": "6mb"
},
{
"type": "anyComponentStyle",
@ -60,16 +64,19 @@
}
]
},
"production-es5": {
"production-renderer": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
},
{
"replace": "src/environments/native/native.ts",
"with": "src/environments/native/native.renderer.prod.ts"
}
],
"es5BrowserSupport": true,
"outputPath": "dist/ucap-lg-web-es5",
"tsConfig": "tsconfig.app.es5.json",
"outputPath": "dist/ucap-lg-renderer",
"tsConfig": "tsconfig.app.renderer.json",
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
@ -82,7 +89,7 @@
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
"maximumError": "6mb"
},
{
"type": "anyComponentStyle",
@ -96,18 +103,26 @@
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.hmr.ts"
},
{
"replace": "src/environments/native/native.ts",
"with": "src/environments/native/native.browser.ts"
}
]
},
"hmr-es5": {
"hmr-renderer": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.hmr.ts"
},
{
"replace": "src/environments/native/native.ts",
"with": "src/environments/native/native.renderer.ts"
}
],
"es5BrowserSupport": true,
"tsConfig": "tsconfig.app.es5.json"
"es5BrowserSupport": false,
"tsConfig": "tsconfig.app.renderer.json"
}
}
},
@ -121,16 +136,16 @@
"production": {
"browserTarget": "ucap-lg-web:build:production"
},
"production-5": {
"browserTarget": "ucap-lg-web:build:production-es5"
"production-renderer": {
"browserTarget": "ucap-lg-web:build:production-renderer"
},
"hmr": {
"hmr": true,
"browserTarget": "ucap-lg-web:build:hmr"
},
"hmr-es5": {
"hmr-renderer": {
"hmr": true,
"browserTarget": "ucap-lg-web:build:hmr-es5"
"browserTarget": "ucap-lg-web:build:hmr-renderer"
}
}
},

View File

@ -1,13 +1,20 @@
const path = require('path');
module.exports = (config, options) => {
const PRODUCTION = process.env.NODE_ENV === 'production';
const PRODUCTION =
!!process.env.NODE_ENV && 'production' === process.env.NODE_ENV;
const RENDERER =
!!process.env.NATIVE_ENV && 'renderer' === process.env.NATIVE_ENV;
config.target = 'web';
config.node = {
global: true,
fs: 'empty'
};
if (RENDERER) {
config.target = 'electron-renderer';
} else {
config.target = 'web';
config.node = {
global: true,
fs: 'empty'
};
}
config.resolve.alias = {
...config.resolve.alias,

1175
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,42 +5,41 @@
"ng": "ng",
"start": "ng serve",
"start:hmr": "ng serve --configuration hmr",
"start:hmr-es5": "ng serve --configuration hmr-es5",
"start:hmr-renderer": "ng serve --configuration hmr-renderer",
"build": "ng build",
"build:production": "ng build --prod",
"build:production-es5": "ng build --configuration production-es5",
"build:production-renderer": "cross-env NATIVE_ENV=renderer ng build --prod --configuration production-renderer --base-href ./",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~9.0.6",
"@angular/cdk": "^9.1.3",
"@angular/common": "~9.0.6",
"@angular/compiler": "~9.0.6",
"@angular/core": "~9.0.6",
"@angular/flex-layout": "^9.0.0-beta.29",
"@angular/forms": "~9.0.6",
"@angular/material": "^9.1.3",
"@angular/material-moment-adapter": "^9.1.3",
"@angular/platform-browser": "~9.0.6",
"@angular/platform-browser-dynamic": "~9.0.6",
"@angular/router": "~9.0.6",
"@ngrx/effects": "^9.0.0",
"@ngrx/entity": "^9.0.0",
"@ngrx/router-store": "^9.0.0",
"@ngrx/store": "^9.0.0",
"@ucap/api": "~0.0.2",
"@ucap/api-common": "~0.0.5",
"@angular/animations": "^9.1.11",
"@angular/cdk": "^9.2.4",
"@angular/common": "^9.1.11",
"@angular/compiler": "^9.1.11",
"@angular/core": "^9.1.11",
"@angular/flex-layout": "^9.0.0-beta.31",
"@angular/forms": "^9.1.11",
"@angular/material": "^9.2.4",
"@angular/material-moment-adapter": "^9.2.4",
"@angular/platform-browser": "^9.1.11",
"@angular/platform-browser-dynamic": "^9.1.11",
"@angular/router": "^9.1.11",
"@ngrx/effects": "^9.2.0",
"@ngrx/entity": "^9.2.0",
"@ngrx/router-store": "^9.2.0",
"@ngrx/store": "^9.2.0",
"@ucap/api": "~0.0.4",
"@ucap/api-common": "~0.0.11",
"@ucap/api-external": "~0.0.5",
"@ucap/api-message": "~0.0.3",
"@ucap/api-prompt": "~0.0.3",
"@ucap/api-public": "~0.0.4",
"@ucap/core": "~0.0.10",
"@ucap/logger": "~0.0.12",
"@ucap/native": "~0.0.6",
"@ucap/native-browser": "~0.0.5",
"@ucap/core": "~0.0.14",
"@ucap/logger": "~0.0.13",
"@ucap/native": "~0.0.19",
"@ucap/ng-api-common": "~0.0.1",
"@ucap/ng-api-external": "~0.0.1",
"@ucap/ng-api-message": "~0.0.1",
@ -49,8 +48,7 @@
"@ucap/ng-core": "~0.0.7",
"@ucap/ng-logger": "~0.0.2",
"@ucap/ng-i18n": "~0.0.6",
"@ucap/ng-native": "~0.0.1",
"@ucap/ng-native-browser": "~0.0.1",
"@ucap/ng-native": "~0.0.5",
"@ucap/ng-pi": "~0.0.1",
"@ucap/ng-protocol": "~0.0.3",
"@ucap/ng-protocol-authentication": "~0.0.3",
@ -68,35 +66,35 @@
"@ucap/ng-protocol-status": "~0.0.3",
"@ucap/ng-protocol-sync": "~0.0.3",
"@ucap/ng-protocol-umg": "~0.0.3",
"@ucap/ng-store-authentication": "~0.0.11",
"@ucap/ng-store-chat": "~0.0.16",
"@ucap/ng-store-group": "~0.0.14",
"@ucap/ng-store-organization": "~0.0.8",
"@ucap/ng-store-authentication": "~0.0.14",
"@ucap/ng-store-chat": "~0.0.66",
"@ucap/ng-store-group": "~0.0.22",
"@ucap/ng-store-organization": "~0.0.20",
"@ucap/ng-web-socket": "~0.0.2",
"@ucap/ng-web-storage": "~0.0.3",
"@ucap/ng-ui": "~0.0.19",
"@ucap/ng-ui-organization": "~0.0.83",
"@ucap/ng-ui-authentication": "~0.0.25",
"@ucap/ng-ui-group": "~0.0.33",
"@ucap/ng-ui-chat": "~0.0.12",
"@ucap/ng-ui": "0.0.97",
"@ucap/ng-ui-organization": "~0.0.202",
"@ucap/ng-ui-authentication": "~0.0.29",
"@ucap/ng-ui-group": "~0.0.78",
"@ucap/ng-ui-chat": "~0.0.72",
"@ucap/ng-ui-material": "~0.0.4",
"@ucap/ng-ui-skin-default": "~0.0.1",
"@ucap/pi": "~0.0.5",
"@ucap/pi": "~0.0.8",
"@ucap/protocol": "~0.0.17",
"@ucap/protocol-authentication": "~0.0.5",
"@ucap/protocol-buddy": "~0.0.5",
"@ucap/protocol-event": "~0.0.5",
"@ucap/protocol-file": "~0.0.4",
"@ucap/protocol-event": "~0.0.6",
"@ucap/protocol-file": "~0.0.6",
"@ucap/protocol-group": "~0.0.5",
"@ucap/protocol-info": "~0.0.6",
"@ucap/protocol-info": "~0.0.9",
"@ucap/protocol-inner": "~0.0.4",
"@ucap/protocol-option": "~0.0.7",
"@ucap/protocol-ping": "~0.0.6",
"@ucap/protocol-query": "~0.0.5",
"@ucap/protocol-room": "~0.0.6",
"@ucap/protocol-room": "~0.0.7",
"@ucap/protocol-service": "~0.0.4",
"@ucap/protocol-status": "~0.0.5",
"@ucap/protocol-sync": "~0.0.4",
"@ucap/protocol-sync": "~0.0.6",
"@ucap/protocol-umg": "~0.0.5",
"@ucap/ui-scss": "~0.0.5",
"@ucap/web-socket": "~0.0.10",
@ -109,13 +107,13 @@
"file-type": "^14.1.4",
"i18next": "^19.3.3",
"i18next-browser-languagedetector": "^4.0.2",
"i18next-node-fs-backend": "^2.1.3",
"i18next-fs-backend": "^1.0.6",
"i18next-xhr-backend": "^3.2.2",
"libphonenumber-js": "^1.7.47",
"memoize-one": "^5.1.1",
"moment": "^2.24.0",
"moment-timezone": "^0.5.28",
"ngx-perfect-scrollbar": "^9.0.0",
"ngx-virtual-scroller": "^4.0.3",
"pino": "^6.0.0",
"queueing-subject": "^0.3.4",
"rxjs": "~6.5.4",
@ -126,16 +124,16 @@
"devDependencies": {
"@angular-builders/custom-webpack": "^9.0.0",
"@angular-devkit/build-angular": "~0.900.6",
"@angular/cli": "~9.0.6",
"@angular/compiler-cli": "~9.0.6",
"@angular/language-service": "~9.0.6",
"@angular/cli": "^9.1.9",
"@angular/compiler-cli": "^9.1.11",
"@angular/language-service": "^9.1.11",
"@angularclass/hmr": "^2.1.3",
"@ngrx/store-devtools": "^9.0.0",
"@types/i18next-node-fs-backend": "^2.1.0",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"cross-env": "^7.0.2",
"fs-extra": "^9.0.0",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~4.2.1",
@ -148,6 +146,7 @@
"ts-node": "~8.3.0",
"tslint": "~5.18.0",
"typescript": "~3.7.5",
"webpack-bundle-analyzer": "^3.7.0"
"webpack-bundle-analyzer": "^3.7.0",
"webpack-node-externals": "^1.7.2"
}
}

View File

@ -12,10 +12,13 @@ import { AppAuthenticationGuard } from './guards/app-authentication.guard';
import { AppSessionResolver } from './resolvers/app-session.resolver';
import { AppAuthenticationService } from './services/app-authentication.service';
import { AppNotificationService } from './services/app-notification.service';
import { AppNativeService } from './services/app-native.service';
import { AppService } from './services/app.service';
import { AppChatService } from './services/app-chat.service';
import { AppFileService } from './services/app-file.service';
import { AppGroupService } from './services/app-group.service';
import { AppAccountService } from './services/app-account.service';
const GUARDS = [AppAuthenticationGuard];
const RESOLVERS = [AppSessionResolver];
@ -24,7 +27,10 @@ const SERVICES = [
AppAuthenticationService,
AppNativeService,
AppFileService,
AppChatService
AppChatService,
AppNotificationService,
AppGroupService,
AppAccountService
];
const axiosFactory = () => {
@ -35,6 +41,9 @@ const axiosFactory = () => {
return i;
};
const nativeServiceFactory = (nativeService: any) =>
new environment.productConfig.nativeServiceClass(nativeService);
const appInit = (appService: AppService) => {
return () => appService.initialize();
};
@ -51,7 +60,7 @@ const appInit = (appService: AppService) => {
},
{
provide: UCAP_NATIVE_SERVICE,
useClass: environment.productConfig.nativeServiceClass,
useFactory: nativeServiceFactory,
deps: [AXIOS_INSTANCE],
multi: false
},

View File

@ -81,7 +81,9 @@ const routes: Routes = [
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
imports: [
RouterModule.forRoot(routes, { useHash: true, enableTracing: false })
],
exports: [RouterModule]
})
export class AppRoutingModule {}

View File

@ -1,28 +1,48 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import {
Component,
OnDestroy,
OnInit,
AfterViewInit,
Renderer2
} from '@angular/core';
import { Store } from '@ngrx/store';
import { AppActions } from '@app/store/actions';
import { fromEvent, interval, Subscription } from 'rxjs';
import { debounce } from 'rxjs/operators';
import { fromEvent, interval, Subject } from 'rxjs';
import { debounce, takeUntil } from 'rxjs/operators';
import { AppAuthenticationService } from './services/app-authentication.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
private resizeWindowSubscription: Subscription;
export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(private store: Store<any>) {
this.resizeWindowSubscription = fromEvent(window, 'resize')
.pipe(debounce(() => interval(100)))
constructor(
private renderer2: Renderer2,
private store: Store<any>,
private appAuthenticationService: AppAuthenticationService
) {
fromEvent(window, 'resize')
.pipe(
takeUntil(this.ngOnDestroySubject),
debounce(() => interval(100))
)
.subscribe((event: any) => {
this.dispatchWindowSize({
width: event.target.innerWidth,
height: event.target.innerHeight
});
});
// fromEvent(window, 'beforeunload')
// .pipe(takeUntil(this.ngOnDestroySubject))
// .subscribe((event: any) => {
// this.appAuthenticationService.logout();
// });
}
ngOnInit(): void {
@ -33,11 +53,19 @@ export class AppComponent implements OnInit, OnDestroy {
}
ngOnDestroy(): void {
if (!!this.resizeWindowSubscription) {
this.resizeWindowSubscription.unsubscribe();
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
ngAfterViewInit(): void {
const preloader = this.renderer2.selectRootElement(
'#ucap-lg-web-preloader'
);
this.renderer2.setStyle(preloader, 'display', 'none');
}
private dispatchWindowSize(size: { width: number; height: number }) {
this.store.dispatch(AppActions.windowResized(size));
}

View File

@ -59,6 +59,8 @@ import { effects } from '@app/store/effects';
import { ROOT_REDUCERS } from '@app/store/reducers';
import { metaReducers } from '@app/store/state';
import { AppAccountDialogModule } from '@app/dialogs/account/account.dialog.module';
import { environment } from '@environments';
@NgModule({
@ -69,6 +71,56 @@ import { environment } from '@environments';
FlexLayoutModule,
/**
* StoreModule.forRoot is imported once in the root module, accepting a reducer
* function or object map of reducer functions. If passed an object of
* reducers, combineReducers will be run creating your application
* meta-reducer. This returns all providers for an @ngrx/store
* based application.
*/
StoreModule.forRoot(ROOT_REDUCERS, {
metaReducers,
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true,
strictStateSerializability: true,
strictActionSerializability: true
}
}),
/**
* @ngrx/router-store keeps router state up-to-date in the store.
*/
StoreRouterConnectingModule.forRoot({
routerState: RouterState.Minimal
}),
/**
* Store devtools instrument the store retaining past versions of state
* and recalculating new states. This enables powerful time-travel
* debugging.
*
* To use the debugger, install the Redux Devtools extension for either
* Chrome or Firefox
*
* See: https://github.com/zalmoxisus/redux-devtools-extension
*/
StoreDevtoolsModule.instrument({
name: 'UCAP Store App'
// In a production build you would want to disable the Store Devtools
// logOnly: environment.production,
}),
/**
* EffectsModule.forRoot() is imported once in the root module and
* sets up the effects class to be initialized immediately when the
* application starts.
*
* See: https://ngrx.io/guide/effects#registering-root-effects
*/
EffectsModule.forRoot([...effects]),
LoggerModule.forRoot({}),
CommonApiModule.forRoot(environment.commonApiModuleConfig),
@ -118,55 +170,7 @@ import { environment } from '@environments';
AppLayoutsModule,
/**
* StoreModule.forRoot is imported once in the root module, accepting a reducer
* function or object map of reducer functions. If passed an object of
* reducers, combineReducers will be run creating your application
* meta-reducer. This returns all providers for an @ngrx/store
* based application.
*/
StoreModule.forRoot(ROOT_REDUCERS, {
metaReducers,
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true,
strictStateSerializability: true,
strictActionSerializability: true
}
}),
/**
* @ngrx/router-store keeps router state up-to-date in the store.
*/
StoreRouterConnectingModule.forRoot({
routerState: RouterState.Minimal
}),
/**
* Store devtools instrument the store retaining past versions of state
* and recalculating new states. This enables powerful time-travel
* debugging.
*
* To use the debugger, install the Redux Devtools extension for either
* Chrome or Firefox
*
* See: https://github.com/zalmoxisus/redux-devtools-extension
*/
StoreDevtoolsModule.instrument({
name: 'UCAP Store App'
// In a production build you would want to disable the Store Devtools
// logOnly: environment.production,
}),
/**
* EffectsModule.forRoot() is imported once in the root module and
* sets up the effects class to be initialized immediately when the
* application starts.
*
* See: https://ngrx.io/guide/effects#registering-root-effects
*/
EffectsModule.forRoot([...effects])
AppAccountDialogModule
],
providers: [],
bootstrap: [AppComponent]

View File

@ -22,7 +22,7 @@ $typography: mat-typography-config(
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$lgRed-app-primary: mat-palette($ucap-color-primary);
$lgRed-app-primary: mat-palette($ucap-color-primary, 600);
$lgRed-app-accent: mat-palette($ucap-color-accent, 700);
// The warn palette is optional (defaults to red).

View File

@ -0,0 +1,48 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatTabsModule } from '@angular/material/tabs';
import { I18nModule } from '@ucap/ng-i18n';
import { UiModule } from '@ucap/ng-ui';
import { AppLayoutsModule } from '@app/layouts/layouts.module';
import { AppAccountSectionModule } from '@app/sections/account/account.section.module';
import { COMPONENTS } from './components';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
FlexLayoutModule,
MatButtonModule,
MatCheckboxModule,
MatIconModule,
MatInputModule,
MatRadioModule,
MatSelectModule,
MatTabsModule,
I18nModule,
UiModule,
AppLayoutsModule,
AppAccountSectionModule
],
exports: [...COMPONENTS],
declarations: [...COMPONENTS],
entryComponents: [...COMPONENTS]
})
export class AppAccountDialogModule {}

View File

@ -0,0 +1,3 @@
import { SettingsDialogComponent } from './settings.dialog.component';
export const COMPONENTS = [SettingsDialogComponent];

View File

@ -0,0 +1,471 @@
<div class="dialog-container">
<app-layouts-default-dialog
[disableClose]="false"
(closed)="onClosed($event)"
class="create-dialog-container"
>
<div appLayoutsDefaultDialog="header">
{{ 'organization:settings.label' | ucapI18n }}
</div>
<div class="dialog-body" appLayoutsDefaultDialog="body">
<mat-tab-group mat-stretch-tabs>
<mat-tab>
<ng-template mat-tab-label>
<p>{{ 'organization:settings.sub.general' | ucapI18n }}</p>
</ng-template>
<div class="default-settings-area">
<!-- 기본설정 -->
<div
*ngIf="'electron' === platform"
class="messenger-settings-area login-setting-box"
>
<div class="title-settings-subject">
<span>{{
'authentication:login.settings.login' | ucapI18n
}}</span>
<button mat-icon-button color="accent" class="btn-subject-info">
<mat-icon>info_outline</mat-icon>
</button>
</div>
<div class="settings-contents">
<ul>
<li>
<mat-checkbox>
{{
'authentication:login.settings.autoStartOnBoot'
| ucapI18n
}}
</mat-checkbox>
</li>
<li>
<mat-checkbox>
{{ 'authentication:login.settings.autoLogin' | ucapI18n }}
</mat-checkbox>
</li>
<li>
<mat-checkbox>
{{ 'authentication:login.settings.autoHide' | ucapI18n }}
</mat-checkbox>
</li>
</ul>
</div>
</div>
<div class="messenger-settings-area language-setting-box">
<div class="title-settings-subject">
<span>{{
'organization:settings.language.messenger' | ucapI18n
}}</span>
<button mat-icon-button color="accent" class="btn-subject-info">
<mat-icon>info_outline</mat-icon>
</button>
</div>
<div class="settings-contents">
<mat-form-field
color="accent"
appearance="standard"
class="setting-select-obj ucap-mat-input-container"
>
<mat-select
[value]="generalSetting.locale"
(selectionChange)="onSelectionChangeLanguage($event)"
>
<mat-option
*ngFor="let languageCode of supportedLanguages"
[value]="languageCode"
>
{{ 'locale:languages.' + languageCode | ucapI18n }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="messenger-settings-area language-setting-box">
<div class="title-settings-subject">
<span>{{
'organization:settings.language.hr' | ucapI18n
}}</span>
<button mat-icon-button color="accent" class="btn-subject-info">
<mat-icon>info_outline</mat-icon>
</button>
</div>
<div class="settings-contents">
<mat-form-field
color="accent"
appearance="standard"
class="setting-select-obj ucap-mat-input-container"
>
<mat-select
[value]="generalSetting.hrInfoLocale"
(selectionChange)="onSelectionChangeHrLanguage($event)"
>
<mat-option
*ngFor="let languageCode of supportedHrLanguages"
[value]="languageCode"
>
{{ 'locale:languages.' + languageCode | ucapI18n }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="messenger-settings-area time-setting-box">
<div class="title-settings-subject">
<span>{{ 'organization:settings.timezone' | ucapI18n }}</span>
<button mat-icon-button color="accent" class="btn-subject-info">
<mat-icon>info_outline</mat-icon>
</button>
</div>
<div class="settings-contents">
<mat-form-field
color="accent"
appearance="standard"
class="setting-select-obj ucap-mat-input-container"
>
<mat-select
#selectForTimezone
[formControl]="formControlForTimezone"
[placeholder]="timezonePlaceholder"
[value]="generalSetting.timezone"
(openedChange)="onOpenedChangeTimezone($event)"
>
<ucap-virtual-scroll-viewport
class="general-timezone-viewport"
#vsTimezone
itemSize="48"
[style.height.px]="5 * 48"
>
<mat-option
*ucapVirtualFor="let timezone of timezoneList"
[value]="timezone.name"
(onSelectionChange)="onSelectionChangeTimezone($event)"
>
{{ timezone.displayName }}
</mat-option>
</ucap-virtual-scroll-viewport>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<p>{{ 'organization:settings.sub.notification' | ucapI18n }}</p>
</ng-template>
<div class="allim-settings-area">
<!-- 알림 -->
<div class="messenger-settings-area">
<div class="title-settings-subject">
{{ 'organization:settings.notification.receival' | ucapI18n }}
</div>
<div class="settings-contents">
<mat-radio-group
aria-label="Select an type of alarm"
class="settings-radio-group"
>
<mat-radio-button value="true">
{{
'organization:settings.notification.receive' | ucapI18n
}}
</mat-radio-button>
<mat-radio-button value="false">
{{
'organization:settings.notification.notReceive' | ucapI18n
}}
</mat-radio-button>
</mat-radio-group>
</div>
</div>
<div class="messenger-settings-area allim-way-box">
<div class="title-settings-subject">
{{ 'organization:settings.notification.method' | ucapI18n }}
</div>
<div class="settings-contents">
<mat-form-field
color="accent"
appearance="standard"
class="setting-select-obj ucap-mat-input-container"
>
<mat-select>
<mat-option value="sound">
{{
'organization:settings.notification.methodTypeSound'
| ucapI18n
}}
</mat-option>
<mat-option value="alert">
{{
'organization:settings.notification.methodTypeAlert'
| ucapI18n
}}
</mat-option>
<mat-option value="soundAndAlert">
{{
'organization:settings.notification.methodTypeSoundAndAlert'
| ucapI18n
}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="messenger-settings-area allim-time-box">
<div class="title-settings-subject">
{{
'organization:settings.notification.settingOfAlertWindow'
| ucapI18n
}}
</div>
<div class="settings-contents">
<mat-form-field
color="accent"
appearance="standard"
class="setting-select-obj ucap-mat-input-container"
>
<mat-select>
<mat-option value="5">
5{{ 'common:units.second' | ucapI18n }}
</mat-option>
<mat-option value="10">
10{{ 'common:units.second' | ucapI18n }}
</mat-option>
<mat-option value="15">
15{{ 'common:units.second' | ucapI18n }}
</mat-option>
<mat-option value="20">
20{{ 'common:units.second' | ucapI18n }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="messenger-settings-area">
<div class="title-settings-subject">
{{
'organization:settings.notification.receiveForMobile'
| ucapI18n
}}
</div>
<div class="settings-contents">
<ul>
<li>
<mat-checkbox>
{{
'organization:settings.notification.receiveForMessageTypePopup'
| ucapI18n
}}
</mat-checkbox>
</li>
</ul>
</div>
</div>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<p>{{ 'chat:settings.label' | ucapI18n }}</p>
</ng-template>
<div class="chat-settings-area">
<!-- 대화 -->
<div class="messenger-settings-area">
<div class="title-settings-subject">파일 전송</div>
<div class="settings-contents02">
<div class="subtitle-settings-info">
다운로드 폴더
</div>
<div class="settings-sub-content sub-set-content">
<mat-form-field
color="accent"
class="setting-input-obj input-set-obj ucap-mat-input-container"
>
<input
matInput
placeholder=""
value=""
[readonly]="'browser' === platform"
/>
<button
mat-button
matSuffix
mat-icon-button
aria-label="file"
[disabled]="'browser' === platform"
>
<mat-icon>folder</mat-icon>
</button>
</mat-form-field>
</div>
<button
mat-stroked-button
color="primary"
class="btn-folder-first"
[disabled]="'browser' === platform"
>
폴더 초기화
</button>
</div>
</div>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<p>{{ 'call:settings.label' | ucapI18n }}</p>
</ng-template>
<div class="call-settings-area">
<!-- 통화 -->
<div class="messenger-settings-area">
<div class="title-settings-subject">
<span>Click to Call</span>
<em>
| PC Messenger에서 Click to Call 기능을 사용할 기기 설정</em
>
</div>
<div class="settings-contents">
<mat-radio-group
aria-label="Select an type of alarm"
class="settings-radio-group"
>
<mat-radio-button value="true">휴대폰</mat-radio-button>
<mat-radio-button value="false">사무실</mat-radio-button>
</mat-radio-group>
</div>
</div>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<p>{{ 'authentication:password.settings.label' | ucapI18n }}</p>
</ng-template>
<div class="secret-num-settings-area">
<!-- 비밀번호 -->
<div class="messenger-settings-area">
<div class="title-settings-subject">
{{ 'authentication:password.fields.changePassword' | ucapI18n }}
</div>
<div class="settings-contents02">
<div class="subtitle-settings-info">
{{
'authentication:password.fields.currentPassword' | ucapI18n
}}
</div>
<div class="settings-sub-content">
<mat-form-field
color="accent"
class="setting-input-obj ucap-mat-input-container"
>
<input
matInput
placeholder="{{
'authentication:password.placeholder.currentPassword'
| ucapI18n
}}"
value=""
/>
<button
mat-button
matSuffix
mat-icon-button
aria-label="action"
>
<mat-icon>done</mat-icon>
</button>
<mat-hint>Hint</mat-hint>
</mat-form-field>
</div>
</div>
<div class="settings-contents02">
<div class="subtitle-settings-info">
{{ 'authentication:password.fields.newPassword' | ucapI18n }}
</div>
<div class="settings-sub-content">
<mat-form-field
color="accent"
class="setting-input-obj ucap-mat-input-container"
>
<input
matInput
placeholder="{{
'authentication:password.placeholder.newPassword'
| ucapI18n
}}"
value=""
/>
<button
mat-button
matSuffix
mat-icon-button
aria-label="action"
>
<mat-icon>done</mat-icon>
</button>
<mat-hint
>반드시 영어 소문자, 숫자, 특수문자 중 2가지 이상 사용해야
합니다.</mat-hint
>
</mat-form-field>
<mat-form-field
color="accent"
class="setting-input-obj ucap-mat-input-container"
>
<input
matInput
placeholder="{{
'authentication:password.placeholder.newPasswordConfirm'
| ucapI18n
}}"
value=""
/>
<button
mat-button
matSuffix
mat-icon-button
aria-label="action"
>
<mat-icon>done</mat-icon>
</button>
<mat-error>Error</mat-error>
</mat-form-field>
</div>
</div>
<div class="pass-info-box">
<dl>
<dt>
<mat-icon color="accent" class="bullet-ico-info"
>info_outline</mat-icon
>
{{ 'authentication:password.notice.condition' | ucapI18n }}
</dt>
<dd>
{{ 'authentication:password.notice.condition1' | ucapI18n }}
</dd>
<dd>
{{ 'authentication:password.notice.condition2' | ucapI18n }}
</dd>
<dd>
{{ 'authentication:password.notice.condition3' | ucapI18n }}
</dd>
<dd>
{{ 'authentication:password.notice.condition4' | ucapI18n }}
</dd>
<dd>
{{ 'authentication:password.notice.condition5' | ucapI18n }}
</dd>
</dl>
</div>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
<div appLayoutsDefaultDialog="action" class="btn-box">
<button mat-stroked-button class="mat-basic" (click)="onCancel()">
{{ 'common:messages.cancel' | ucapI18n }}
</button>
<button mat-flat-button class="bg-primary-darkest" (click)="onConfirm()">
{{ 'common:messages.confirm' | ucapI18n }}
</button>
</div>
</app-layouts-default-dialog>
</div>

View File

@ -0,0 +1,224 @@
@import '~@ucap/lg-scss/mixins';
.dialog-container {
width: 100%;
height: 100%;
.dialog-body {
width: 100%;
height: 100%;
.messenger-settings-area {
border-top: 10px solid #f1f2f6;
background-color: #fff;
display: flex;
flex-direction: column;
width: 100%;
padding: 10px 16px 9px;
&:first-of-type {
border-top: 0;
padding: 20px 16px 9px;
}
.title-settings-subject {
color: #5c444b;
font-size: 1.071em;
font-weight: 600;
line-height: 1.2;
padding: 6px 0 7px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
min-height: 30px;
span {
flex: 0 0 auto;
}
em {
flex: 0 1 auto;
justify-self: start;
font-style: normal;
font-size: 0.929em;
color: #999;
font-weight: 400;
@include wordBreak();
@include ellipsis-column(1);
width: 100%;
}
.btn-subject-info {
width: 30px;
height: 30px;
line-height: 30px;
font-size: 18px;
}
}
.settings-contents {
ul {
li {
padding: 6px 0 7px;
}
}
.settings-radio-group {
padding: 6px 0 7px;
height: 42px;
display: flex;
flex-direction: row;
align-items: center;
.mat-radio-button {
margin-left: 20%;
&:first-child {
margin-left: 0;
}
}
}
}
.settings-contents02 {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 25px;
@include screen(xs) {
flex-direction: column;
}
.subtitle-settings-info {
font-size: 0.929em;
color: #262626;
align-self: baseline;
@include screen(xs) {
flex: 1 0 auto;
}
}
.settings-sub-content {
display: flex;
flex-direction: column;
margin-left: 25px;
width: 60%;
@include screen(xs) {
flex: 0 1 auto;
width: 100% !important;
margin-left: 0;
}
.setting-input-obj {
@include ucapMatFormField(0, 0, 0%, 100%, 100%, 60px, 11px);
overflow: hidden;
margin-top: 15px;
&:first-of-type {
margin-top: 0;
@include screen(xs) {
margin-top: 15px;
}
}
.mat-hint {
line-height: 1.3;
}
}
&.sub-set-content {
width: auto;
flex: 1 1 auto;
.input-set-obj {
@include ucapMatFormField(0, 0, 0%, 100%, 100%, 55px, 11px);
}
}
}
}
}
.default-settings-area {
display: flex;
flex-direction: column;
align-items: flex-start;
.login-setting-box {
}
.language-setting-box,
.time-setting-box {
.settings-contents {
width: 50%;
height: 50px;
@include screen(mid) {
width: 70%;
}
@include screen(xs) {
width: 100%;
}
.setting-select-obj {
@include ucapMatFormField(0, 0, 100%, 100%, 100%, 40px, 28px);
//@include ucapMatSelect(25px, 0 0);
.general-timezone-viewport {
height: 250px;
width: 100%;
}
}
}
}
}
.allim-settings-area {
display: flex;
flex-direction: column;
align-items: flex-start;
.allim-way-box,
.allim-time-box {
.settings-contents {
width: 50%;
height: 50px;
@include screen(mid) {
width: 70%;
}
@include screen(xs) {
width: 100%;
}
.setting-select-obj {
@include ucapMatFormField(0, 0, 100%, 100%, 100%, 40px, 28px);
//@include ucapMatSelect(25px, 0 0);
}
}
}
}
.chat-settings-area {
}
.call-settings-area {
}
.secret-num-settings-area {
.pass-info-box {
dl {
border: 1px solid #dfe0e8;
background-color: #f7f8fa;
dt {
height: 30px;
background-color: #fff;
display: flex;
flex-direction: row;
align-items: center;
font-size: 0.857em;
font-weight: 600;
.bullet-ico-info {
flex: 0 0 36px;
text-align: center;
font-size: 16px;
width: 30px;
height: 30px;
line-height: 30px;
}
}
dd {
font-size: 0.857em;
color: #666;
line-height: 1.6;
margin: 2px 36px 3px;
&:first-of-type {
margin-top: 10px;
}
&:last-of-type {
margin-bottom: 10px;
}
}
}
}
}
}
.btn-box {
display: flex;
flex-direction: row;
justify-content: flex-end;
button {
@include ucap-button-flat-stroked(120px);
}
}
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { SettingsDialogComponent } from './settings.dialog.component';
describe('app::account::SettingsDialogComponent', () => {
let component: SettingsDialogComponent;
let fixture: ComponentFixture<SettingsDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SettingsDialogComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SettingsDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,189 @@
import moment from 'moment';
import 'moment-timezone';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject,
ViewChild
} from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
MatDialog
} from '@angular/material/dialog';
import { MatSelectChange, MatSelect } from '@angular/material/select';
import { NativeService, NativeType } from '@ucap/native';
import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native';
import { environment } from '@environments';
import {
Settings,
GeneralSetting,
NotificationSetting,
ChatSetting,
PresenceSetting
} from '@app/models/settings';
import { I18nService } from '@ucap/ng-i18n';
import { VirtualScrollViewportComponent } from '@ucap/ng-ui';
import { FormControl } from '@angular/forms';
import { MatOptionSelectionChange } from '@angular/material/core';
export interface TimezoneData {
displayName: string;
name: string;
}
export interface SettingsDialogData {
settings: Settings;
}
export interface SettingsDialogResult {}
@Component({
selector: 'app-sections-account-settings',
templateUrl: './settings.dialog.component.html',
styleUrls: ['./settings.dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SettingsDialogComponent implements OnInit, OnDestroy {
@ViewChild('vsTimezone', { static: false })
vsTimezone: VirtualScrollViewportComponent;
@ViewChild('selectForTimezone', { static: false })
selectForTimezone: MatSelect;
formControlForTimezone = new FormControl();
platform: 'browser' | 'electron' = 'electron';
generalSetting: GeneralSetting;
notificationSetting: NotificationSetting;
chatSetting: ChatSetting;
presenceSetting: PresenceSetting;
timezoneList: TimezoneData[];
timezonePlaceholder: string;
supportedLanguages = environment.productConfig.supportedLanguages;
supportedHrLanguages =
environment.productConfig.organization.supportedLanguages;
constructor(
public dialogRef: MatDialogRef<SettingsDialogData, SettingsDialogResult>,
@Inject(MAT_DIALOG_DATA) public data: SettingsDialogData,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
private i18nService: I18nService,
private changeDetectorRef: ChangeDetectorRef,
public matDialog: MatDialog
) {
this.nativeService.platform_nativeType().then((type) => {
switch (type) {
case NativeType.Browser:
this.platform = 'browser';
break;
case NativeType.Electron:
this.platform = 'electron';
break;
default:
break;
}
});
this.generalSetting = data.settings.general;
this.notificationSetting = data.settings.notification;
this.chatSetting = data.settings.chat;
this.presenceSetting = data.settings.presence;
}
private ngOnDestroySubject: Subject<void> = new Subject();
ngOnInit(): void {
this.generateTimezoneData();
this.i18nService.languageChanged$
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((lang) => {
this.generateTimezoneData();
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onSelectionChangeLanguage(event: MatSelectChange) {}
onSelectionChangeHrLanguage(event: MatSelectChange) {}
onOpenedChangeTimezone(opened: boolean) {
if (opened) {
this.setTimezoneData();
this.vsTimezone.checkViewportSize();
}
}
onSelectionChangeTimezone(event: MatOptionSelectionChange) {
if (!event.isUserInput) {
return;
}
}
onClosed(event: MouseEvent): void {
this.dialogRef.close();
}
onCancel() {}
onConfirm() {}
private generateTimezoneData() {
const timezoneData = this.i18nService.t('locale:timezone', {
returnObjects: true
});
let timezoneList: TimezoneData[] = [];
for (const name of moment.tz.names()) {
const displayName = `(UTC${moment.tz(name).format('Z')}) ${
timezoneData[name]
}`;
timezoneList.push({
displayName,
name
});
if (name === this.generalSetting.timezone) {
this.timezonePlaceholder = displayName;
}
}
timezoneList = timezoneList.sort((a: TimezoneData, b: TimezoneData) => {
return a.displayName.localeCompare(b.displayName);
});
this.timezoneList = timezoneList;
}
private setTimezoneData() {
const timezoneIndex = this.timezoneList.findIndex(
(t) => t.name === this.generalSetting.timezone
);
if (-1 !== timezoneIndex) {
if (!!this.vsTimezone && !!this.selectForTimezone) {
this.vsTimezone.scrollToIndex(timezoneIndex);
this.selectForTimezone.value = this.timezoneList[timezoneIndex].name;
}
}
}
}

View File

@ -1,11 +1,17 @@
<div class="layout-container" fxLayout="column">
<div class="layout-header" fxFlex="60px" fxLayout="row">
<div class="layout-header" fxFlex="50px" fxLayout="row">
<div fxFlex="1 1 auto">
<ng-content
class="layout-header-content"
select="[appLayoutsDefaultDialog='header']"
></ng-content>
</div>
<div fxFlex="1 0 auto">
<ng-content
class="layout-sub-header-content"
select="[appLayoutsDefaultDialog='sub-header']"
></ng-content>
</div>
<div fxFlex="30px" *ngIf="!disableClose">
<button
class="icon-button btn-dialog-close"
@ -15,13 +21,11 @@
</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 class="layout-body" fxFlex="1 1 auto" perfectScrollbar>
<ng-content
class="layout-body-content"
select="[appLayoutsDefaultDialog='body']"
></ng-content>
</div>
<div class="layout-action" fxFlex="60px">
<ng-content

View File

@ -3,6 +3,13 @@
height: 100%;
.layout-header {
align-items: center;
justify-content: space-between;
color: #333;
font-weight: 600;
font-size: 1.143em;
border-bottom: 1px solid #666;
margin: 0 16px;
.layout-header-content {
width: 100%;
height: 100%;
@ -17,6 +24,9 @@
}
}
.layout-action {
display: flex;
align-items: center;
justify-content: flex-end;
.layout-action-content {
width: 100%;
height: 100%;

View File

@ -4,14 +4,8 @@ import {
Component,
OnInit,
OnDestroy,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject,
ViewChild,
ComponentFactoryResolver,
ViewContainerRef,
ComponentRef,
Input,
EventEmitter,
Output
@ -40,6 +34,7 @@ export class DefaultDialogLayoutComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}

View File

@ -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="[appLayoutsDefaultDrawer='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="[appLayoutsDefaultDrawer='body']"
></ng-content>
</perfect-scrollbar>
</div>
<div class="layout-action" fxFlex="60px">
<ng-content
class="layout-action-content"
select="[appLayoutsDefaultDrawer='action']"
></ng-content>
</div>
</div>

View File

@ -0,0 +1,35 @@
.layout-container {
width: 100%;
height: 100%;
.layout-header {
align-items: center;
justify-content: space-between;
color: #333;
font-weight: 600;
font-size: 1.143em;
border-bottom: 1px solid #333;
margin: 0 16px;
.layout-header-content {
width: 100%;
height: 100%;
}
}
.layout-body {
overflow: auto;
.layout-body-content {
width: 100%;
height: 100%;
}
}
.layout-action {
display: flex;
align-items: center;
justify-content: flex-end;
.layout-action-content {
width: 100%;
height: 100%;
}
}
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { DefaultDrawerLayoutComponent } from './default-drawer.layout.component';
describe('app::layouts::DefaultDrawerLayoutComponent', () => {
let component: DefaultDrawerLayoutComponent;
let fixture: ComponentFixture<DefaultDrawerLayoutComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DefaultDrawerLayoutComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DefaultDrawerLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,45 @@
import { Subject } from 'rxjs';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input,
EventEmitter,
Output
} from '@angular/core';
@Component({
selector: 'app-layouts-default-drawer',
templateUrl: './default-drawer.layout.component.html',
styleUrls: ['./default-drawer.layout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DefaultDrawerLayoutComponent 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.next();
this.ngOnDestroySubject.complete();
}
}
onClickClose(event: MouseEvent): void {
this.closed.emit(event);
}
}

View File

@ -3,12 +3,13 @@
<div class="gnb">
<mat-toolbar class="mat-gnb-toolbar"
><img
src="../../../assets/images/logo_140.png"
src="assets/images/logo_140.png"
alt=""
class="img-logo"
width="32"
/></mat-toolbar>
<mat-tab-group
#navTabGroup
mat-stretch-tabs
animationDuration="0ms"
backgroundColor="transparent"
@ -17,7 +18,12 @@
>
<mat-tab aria-label="Group">
<ng-template mat-tab-label>
<div class="icon-item" matTooltip="그룹" matTooltipPosition="after">
<div
class="icon-item"
[matTooltip]="'common:tooltip.group' | ucapI18n"
matTooltipPosition="after"
(click)="onClickToggleLeftSidenav()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16.871"
@ -63,10 +69,13 @@
<ng-template mat-tab-label>
<div
class="icon-item"
matBadgeHidden="false"
matBadge="275"
[matTooltip]="'common:tooltip.chat' | ucapI18n"
matTooltipPosition="after"
[matBadgeHidden]="unreadCountChat <= 0"
[matBadge]="unreadCountChat"
matBadgeColor="accent"
matBadgePosition="above after"
(click)="onClickToggleLeftSidenav()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -95,7 +104,12 @@
</mat-tab>
<mat-tab aria-label="Organization">
<ng-template mat-tab-label>
<div class="icon-item">
<div
class="icon-item"
[matTooltip]="'common:tooltip.organization' | ucapI18n"
matTooltipPosition="after"
(click)="onClickToggleLeftSidenav()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
@ -146,7 +160,12 @@
<mat-tab aria-label="Message">
<ng-template mat-tab-label>
<div class="icon-item">
<div
class="icon-item"
[matTooltip]="'common:tooltip.message' | ucapI18n"
matTooltipPosition="after"
(click)="onClickToggleLeftSidenav()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="21.096"
@ -180,7 +199,12 @@
<mat-tab aria-label="Call">
<ng-template mat-tab-label>
<div class="icon-item">
<div
class="icon-item"
[matTooltip]="'common:tooltip.call' | ucapI18n"
matTooltipPosition="after"
(click)="onClickToggleLeftSidenav()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
@ -208,27 +232,159 @@
</mat-tab-group>
<div class="btn-homepage-area">
<button mat-button aria-label="">
<em>Homepage</em>
<button mat-button aria-label="Homepage" matTooltip="Homepage">
<!--<em>Homepage</em>-->
</button>
</div>
</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-container hasBackdrop="false" autosize="true">
<mat-sidenav
#leftSidenav
class="left-sidenav"
mode="side"
opened="true"
[disableClose]="true"
>
<ucap-float-action-button
*ngIf="fabButtonShow"
[buttons]="fabButtons"
[useCustomDefaultIcon]="fabUseCustomDefaultIcon"
(buttonClick)="onClickFab($event)"
>
<div *ngIf="tabIndex === 'group'" ucapFloatActionButton="mainIcon">
<mat-icon class="ico-font-float">
<svg
xmlns="http://www.w3.org/2000/svg"
width="44"
height="44"
viewBox="0 0 44 44"
>
<g transform="translate(4 7)">
<path
fill="none"
d="M0 0H44V44H0z"
transform="translate(-4 -7)"
/>
<path
d="M0 0L12 0"
class="prefix_cls-2"
transform="translate(24.357 7.26)"
/>
<path
d="M0 0L12 0"
class="prefix_cls-2"
transform="rotate(90 14.549 16.049)"
/>
<path
fill="rgba(0,0,0,0)"
stroke-linecap="round"
stroke-linejoin="bevel"
stroke-miterlimit="10"
stroke-width="3px"
d="M26.25 24.125a11.125 11.125 0 1 0-22.25 0"
class="prefix_cls-3"
transform="translate(-3.5 2.657)"
/>
<g fill="none" stroke="none">
<path
d="M7.08 0a7.08 7.08 0 0 1 7.08 7.08 7.318 7.318 0 0 1-2.753 5.6 6.7 6.7 0 0 1-4.327 1.479A7.08 7.08 0 0 1 7.08 0z"
transform="translate(5 1)"
/>
<path
d="M7.08 3C4.83 3 3 4.83 3 7.08c0 2.249 1.83 4.08 4.08 4.08 1.164 0 2.06-.466 2.289-.678l.026-.024.026-.024c1.558-1.374 1.738-2.573 1.738-3.354C11.16 4.83 9.33 3 7.08 3m0-3c3.91 0 7.08 3.17 7.08 7.08 0 2.095-.91 3.978-2.753 5.604-.857.793-2.512 1.475-4.326 1.475C3.17 14.16 0 10.99 0 7.08S3.17 0 7.08 0z"
class="prefix_cls-4"
transform="translate(5 1)"
/>
</g>
</g>
</svg>
</mat-icon>
</div>
<div *ngIf="tabIndex === 'chat'" ucapFloatActionButton="mainIcon">
<mat-icon class="ico-font-float">
<svg
xmlns="http://www.w3.org/2000/svg"
width="44"
height="44"
viewBox="0 0 44 44"
>
<g transform="translate(-10 -10)">
<path
fill="none"
d="M0 0H44V44H0z"
transform="translate(10 10)"
/>
<path
d="M0 0L12 0"
class="prefix_cls-2"
transform="translate(40.625 22.552)"
/>
<path
d="M0 0L12 0"
class="prefix_cls-2"
transform="rotate(90 15.036 31.829)"
/>
<path
fill="rgba(102,102,102,0)"
class="prefix_cls-3"
stroke-linejoin="round"
stroke-width="3px"
d="M50.353 28.582a13.829 13.829 0 0 0-17.546 0 10.012 10.012 0 0 0 0 15.588 13.406 13.406 0 0 0 8.773 3.139c.337 0 .676-.012 1.013-.036l2.129 3.737a.837.837 0 0 0 .7.433h.02a.832.832 0 0 0 .7-.4l4.511-7.143a9.995 9.995 0 0 0-.3-15.319z"
data-name="icon_float_chat"
transform="translate(-16.272 -6.943)"
/>
</g>
</svg>
</mat-icon>
</div>
<div
*ngIf="tabIndex === 'message'"
ucapFloatActionButton="mainIcon"
>
<mat-icon class="ico-font-float">
<svg
xmlns="http://www.w3.org/2000/svg"
width="44"
height="44"
viewBox="0 0 44 44"
>
<g transform="translate(-10 -9.5)">
<path
fill="none"
d="M0 0H44V44H0z"
transform="translate(10 9.5)"
/>
<path
d="M0 0L12 0"
class="prefix_cls-2"
transform="translate(40.5 38.26)"
/>
<path
d="M0 0L12 0"
class="prefix_cls-2"
transform="rotate(90 7.12 39.62)"
/>
<path
fill="none"
class="prefix_cls-3"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="3px"
d="M22.425 3.3a3.851 3.851 0 0 1 5.447 5.447L9.489 27.129 2 29.172l2.043-7.489z"
data-name="icon_float_message"
transform="translate(11.5 14.328)"
/>
</g>
</svg>
</mat-icon>
</div>
<div *ngIf="tabIndex === 'call'" ucapFloatActionButton="mainIcon">
<mat-icon class="ico-font-dialpad">dialpad</mat-icon>
</div>
</ucap-float-action-button>
<div class="left-sidenav-container" fxLayout="column">
<div class="top-bar" fxFlex="0 0 40px">
M-Messenger
@ -242,7 +398,41 @@
<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>
<app-layouts-top-bar>
<div class="content-sidenav-top-bar-content">
<div class="toolbar-info-area date-info">
<span>Today</span>{{ moment().format('YYYY.MM.DD') }}
</div>
<div class="toolbar-info-area toolbar-ctrl">
<!--Search-->
<div class="topbar-search">
<button
mat-icon-button
aria-label="search icon"
(click)="onClickSearch($event)"
>
<mat-icon class="ico-search-icon">search</mat-icon>
</button>
</div>
<!--My Profile -->
<div
class="my-profile"
matTooltip="프로필 버튼"
matTooltipPosition="below"
matTooltipHideDelay="1000"
[matMenuTriggerFor]="profileMenu"
#profileMenuTrigger="matMenuTrigger"
>
<app-organization-profile-image-01
[userInfo]="user.info"
[versionInfo]="versionInfo2Res"
(openProfile)="onOpenProfile($event)"
></app-organization-profile-image-01>
</div>
</div>
</div>
</app-layouts-top-bar>
</div>
<div class="content-sidenav-body" fxFlex="1 1 auto">
<router-outlet name="content"></router-outlet>
@ -269,3 +459,16 @@
</div>
</div>
</div>
<mat-menu #profileMenu="matMenu" class="profile-menu-panel">
<ng-template matMenuContent>
<app-organization-profile-menu-01
(selectedProfileManage)="onSelectedProfileManage()"
(selectedNotice)="onSelectedNotice()"
(selectedSettings)="onSelectedSettings()"
(selectedLogout)="onSelectedLogout()"
(selectedExit)="onSelectedExit()"
(done)="onDoneForProfileMenu()"
></app-organization-profile-menu-01>
</ng-template>
</mat-menu>

View File

@ -12,18 +12,20 @@
.navitab-page {
//GNB /////////////////////////////////////
.gnb {
background-color: $gray-ref0;
//background-color: $gray-ref0;
background-color: #f1f2f6;
width: 60px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
border-right: 1px solid rgba(204, 204, 204, 0.8);
border-right: 1px solid rgba(0, 0, 0, 0.2);
.mat-gnb-toolbar {
flex-basis: 64px;
flex-basis: 40px;
padding: 2px 12px 10px;
.img-logo {
margin: 9px 0 5px;
margin: 6px 0 0 1px;
}
}
.left-container {
@ -33,13 +35,14 @@
}
.global-menu {
width: 100%;
background-color: $gray-ref0;
//background-color: $gray-ref0;
flex-grow: 1;
}
.btn-homepage-area {
flex-flow: column-reverse;
position: relative;
button {
/*
padding: 30px 0 12px;
&::before {
content: '';
@ -51,6 +54,21 @@
top: 9px;
left: calc(50% - 10px);
}
*/
padding: 30px 0 12px;
border-radius: 21px;
min-width: 42px;
&::before {
content: '';
width: 30px;
height: 30px;
background-image: url(../../../assets/images/ico/btn_gnb_hompage.svg);
background-size: 30px;
display: block;
position: absolute;
top: 5px;
left: calc(50% - 15px);
}
em {
font-style: normal;
font-size: 8px;
@ -61,139 +79,6 @@
}
}
}
::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 //
}
@ -213,6 +98,7 @@
.left-sidenav {
width: 370px;
max-width: 90%;
border-right: 1px solid rgba(0, 0, 0, 0.2);
.left-sidenav-container {
width: 100%;
@ -222,8 +108,10 @@
size: 13px;
color: $gray-re70;
}
line-height: 15px;
padding: 25px 0 0 17px;
height: 40px;
display: flex;
align-items: center;
padding: 0 16px;
}
}
}
@ -232,6 +120,96 @@
.content-sidenav-container {
width: 100%;
height: 100%;
overflow: hidden; //20200611
.content-sidenav-top-bar {
.content-sidenav-top-bar-content {
height: 40px;
width: auto;
background-color: transparent;
display: flex;
flex-direction: row;
align-items: center;
flex-grow: 1;
font-size: 12px;
justify-content: space-between;
.toolbar-info-area {
display: flex;
flex-grow: 1;
align-items: center;
&.date-info {
@include font-family($font-light);
font-weight: 600;
font-size: 12px;
color: $gray-re70;
padding-left: 30px;
@include screen(mid) {
padding-left: 16px;
display: none;
}
span {
width: 54px;
height: 16px;
border-radius: 30px;
border: solid 1px $lipstick;
background-color: #ffffff;
font-size: 11px;
display: flex;
align-items: center;
justify-content: center;
color: $lipstick;
margin-right: 8px;
}
}
&.toolbar-ctrl {
flex-flow: row-reverse;
.topbar-search {
order: 2;
margin-right: 8px;
.ico-search-icon {
width: 18px;
height: 18px;
font-size: 18px;
line-height: 18px;
color: #707070;
}
}
.my-profile {
height: 30px;
width: 30px;
margin-right: 20px;
order: 1;
//profile /////////////
.user-profile-thumb {
@include profile-avatar-default(
0,
14,
$green,
18px
); //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 , 모바일 아이콘 bg크기
.presence {
//PC 상태
@include presence-state(10px); //원크기
position: relative;
align-self: flex-end;
margin-left: -10px;
order: 2;
}
.profileImage {
@include avatar-img(30px, 0); //아바타 크기, 왼쪽공간
order: 1;
}
}
///// profile//
}
.app-layout-native-title-bar-actions {
order: 0;
}
}
}
}
}
.content-sidenav-body {
overflow: auto;
@ -250,7 +228,7 @@
justify-content: space-between;
width: 100%;
height: 38px;
border-top: 1px solid $line-color-gray01;
border-top: 1px solid rgba(0, 0, 0, 0.2);
background-color: $white;
.foot-info {
display: flex;
@ -264,6 +242,13 @@
.var-txt {
padding-left: 8px;
color: $gray-re70;
text-align: center;
@include screen(custom, max, 414) {
padding-left: 22px;
flex-basis: 38%;
flex-grow: 0;
line-height: 1.2;
}
&::before {
content: '';
width: 1px;
@ -271,10 +256,19 @@
display: inline-block;
background-color: #d4d4d4;
margin-right: 8px;
@include screen(custom, max, 414) {
margin-left: -10px;
}
}
&:first-of-type {
@include screen(custom, max, 414) {
padding-left: 0;
}
&::before {
width: 0;
@include screen(custom, max, 414) {
margin-left: -4px;
}
}
}
&.new-var {
@ -287,11 +281,13 @@
padding-right: 20px;
p {
margin: 0;
text-align: right;
span {
color: $lipstick;
}
em {
margin-left: 10px;
white-space: nowrap;
}
}
}
@ -301,3 +297,41 @@
}
}
}
// Float action button
.ico-font-float {
svg {
.prefix_cls-2 {
fill: transparent;
stroke-width: 2px;
stroke: rgba(255, 255, 255, 1);
}
.prefix_cls-3 {
stroke: rgba(255, 255, 255, 1);
}
.prefix_cls-4 {
fill: rgba(255, 255, 255, 1);
}
}
&:hover {
svg {
.prefix_cls-2 {
stroke: rgba(255, 255, 255, 0.7);
}
.prefix_cls-3 {
stroke: rgba(255, 255, 255, 0.7);
}
.prefix_cls-4 {
fill: rgba(255, 255, 255, 0.7);
}
}
}
}
.ico-font-dialpad {
font-size: 28px !important;
line-height: 44px;
&:hover {
color: rgba(255, 255, 255, 0.7);
}
}

View File

@ -1,17 +1,40 @@
import { Subscription } from 'rxjs';
import moment from 'moment';
import { Subject, of } from 'rxjs';
import { takeUntil, filter, take, map, catchError } from 'rxjs/operators';
import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import {
Router,
RouterEvent,
NavigationEnd,
PRIMARY_OUTLET,
ActivatedRoute,
Params
} from '@angular/router';
import { Store, select } from '@ngrx/store';
import { MatDialog } from '@angular/material/dialog';
import { MatMenuTrigger } from '@angular/material/menu';
import { MatTabChangeEvent, MatTabGroup } from '@angular/material/tabs';
import { MatSidenav } from '@angular/material/sidenav';
import { VersionInfo2Response } from '@ucap/api-public';
import { UserInfoSS } from '@ucap/protocol-query';
import { LogService } from '@ucap/ng-logger';
import { UserSelector } from '@ucap/ng-store-organization';
import { ConfigurationSelector } from '@ucap/ng-store-authentication';
import { RoomSelector } from '@ucap/ng-store-chat';
import { AppSelector } from '@app/store/state';
import { AppChatService } from '@app/services/app-chat.service';
import { QueryParams as ChatQueryParams } from '@app/pages/chat/types/params.type';
import { CreateDialogComponent } from '@app/sections/group/dialogs/create.dialog.component';
import { AppAccountService } from '@app/services/app-account.service';
import { QueryParams as OrganizationParams } from '@app/pages/organization/types/params.type';
import { User } from '@ucap/protocol-info';
const NAVS = ['group', 'chat', 'organization', 'message'];
@ -27,79 +50,170 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
@ViewChild('leftSidenav', { static: true })
leftSidenav: MatSidenav;
isShowLeftSideNav = false;
@ViewChild('profileMenuTrigger', { static: true })
profileMenuTrigger: MatMenuTrigger;
showStatusbar = true;
tabIndex: string;
queryParams: Params;
unreadCountChat = 0;
/** FAB */
fabButtonShow = true;
fabUseCustomDefaultIcon = true; // default in this prj
fabButtons: { icon: string; tooltip?: string; divisionType?: string }[];
versionInfo2Res: VersionInfo2Response;
user: User;
private windowSizeSubscription: Subscription;
moment = moment;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private store: Store<any>,
private appAccountService: AppAccountService,
private appChatService: AppChatService,
private logService: LogService
) {}
private logService: LogService,
public dialog: MatDialog
) {
this.setFabInitial(NAVS[0]);
this.router.events
.pipe(
takeUntil(this.ngOnDestroySubject),
filter((event) => event instanceof RouterEvent)
)
.subscribe((event) => {
switch (event.constructor) {
case NavigationEnd:
{
const t = this.router.parseUrl((event as NavigationEnd).url);
const p = t.root.children[PRIMARY_OUTLET];
if (!p || !p.segments || 0 === p.segments.length) {
break;
}
const index = p.segments[0].path;
this.setTabGroup(index);
this.setFabInitial(index);
}
break;
default:
break;
}
});
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params) => {
this.queryParams = params;
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
});
}
ngOnInit(): void {
this.windowSizeSubscription = this.store
.pipe(select(AppSelector.windowSize))
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(AppSelector.windowSize))
.subscribe((size) => {
if (size.width < 780) {
if (this.leftSidenav.opened) {
this.leftSidenav.close();
}
this.isShowLeftSideNav = false;
this.leftSidenav.mode = 'over';
} else {
if (!this.leftSidenav.opened) {
this.leftSidenav.open();
}
this.isShowLeftSideNav = true;
this.leftSidenav.mode = 'side';
}
});
this.setTabGroup(this.router.url);
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user))
.subscribe((user) => {
this.user = user;
});
this.setFabInitial(NAVS[0]);
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(RoomSelector.unreadTotal)
)
.subscribe((unreadTotal) => {
this.unreadCountChat = unreadTotal;
});
}
ngOnDestroy(): void {
if (!this.windowSizeSubscription) {
this.windowSizeSubscription.unsubscribe();
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onOpenProfile(userInfo: UserInfoSS) {
this.profileMenuTrigger.openMenu();
}
onSelectedTabChange(event: MatTabChangeEvent) {
if (4 === event.index) {
this.router.navigate(
['group', { outlets: { content: 'chat/index' } }],
{}
);
return;
}
this.router.navigate([
const commands: any = [
NAVS[event.index],
{ outlets: { content: 'index' } }
]);
];
const orgInitialParams: Params = {};
if (
event.index === 1 && // is chat.
!!this.queryParams &&
!!this.queryParams[ChatQueryParams.ROOM_ID]
) {
// 다른 화면에서 채팅으로 바로 유입할 경우에는 navigate 초기화를 무시한다.
this.queryParams = undefined;
return;
}
// if (!!this.tabIndex && this.tabIndex === 'chat') {
// if (!!this.queryParams && !!this.queryParams[ChatQueryParams.ROOM_ID]) {
// return;
// } else {
// }
// } else {
// }
if (event.index === 2 && !!this.user) {
orgInitialParams[OrganizationParams.DEPT_SEQ] = String(
this.user.departmentCode
);
}
this.router.navigate(commands, { queryParams: orgInitialParams });
if (!this.isShowLeftSideNav) {
this.leftSidenav.open();
}
this.setFabInitial(NAVS[event.index]);
}
onClickToggleLeftSidenav() {
if (this.leftSidenav.opened) {
this.leftSidenav.close();
} else {
this.leftSidenav.open();
}
}
private setTabGroup(url: string) {
if (!!this.navTabGroup) {
this.navTabGroup.selectedIndex = NAVS.findIndex((v) =>
url.startsWith(`/${v}`)
);
if (!this.isShowLeftSideNav) {
this.leftSidenav.toggle();
}
}
setFabInitial(type: string) {
this.tabIndex = type;
switch (type) {
case 'group':
{
@ -172,7 +286,20 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
switch (btn.divisionType) {
case 'GROUP_NEW_ADD':
{
this.logService.debug('GROUP_NEW_ADD');
const dialogRef = this.dialog.open(CreateDialogComponent, {
panelClass: 'max-create-dialog'
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
break;
case 'CAHT_NEW_ADD':
@ -195,4 +322,37 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
break;
}
}
onClickSearch(event: Event) {
event.stopPropagation();
}
onSelectedProfileManage() {
this.profileMenuTrigger.closeMenu();
}
onSelectedNotice() {
this.profileMenuTrigger.closeMenu();
}
onSelectedSettings() {
this.profileMenuTrigger.closeMenu();
this.appAccountService.dialogForSettings();
}
onSelectedLogout() {
this.router.navigate(['/account/logout']);
}
onSelectedExit() {}
onDoneForProfileMenu() {
this.profileMenuTrigger.closeMenu();
}
private setTabGroup(url: string) {
if (!!this.navTabGroup) {
this.navTabGroup.selectedIndex = NAVS.findIndex((v) => url === v);
}
}
}

View File

@ -4,6 +4,7 @@ import { DefaultLayoutComponent } from './default.layout.component';
import { NoNaviLayoutComponent } from './no-navi.layout.component';
import { DefaultDialogLayoutComponent } from './default-dialog.layout.component';
import { DefaultDrawerLayoutComponent } from './default-drawer.layout.component';
import { SelectorLayoutComponent } from './selector.layout.component';
export const COMPONENTS = [
@ -12,6 +13,7 @@ export const COMPONENTS = [
NoNaviLayoutComponent,
DefaultDialogLayoutComponent,
DefaultDrawerLayoutComponent,
SelectorLayoutComponent
];

View File

@ -1,2 +1,3 @@
.layout-container {
background-color: #f3f4f5;
}

View File

@ -9,9 +9,10 @@
matSuffix
aria-label="Clear"
class="btn-close"
color="accent"
color="primary"
(click)="onClickClose($event)"
>
<mat-icon>highlight_off</mat-icon>
<mat-icon>close</mat-icon>
</button>
</div>
<div class="selector-contents">

View File

@ -0,0 +1,17 @@
.selector {
border-bottom: 1px solid #ccc;
.selector-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 5px 0 16px;
//border-top: 1px solid #ccc;
}
.selector-contents {
background-color: #fff;
border-top: 1px solid #ccc;
}
.footer {
}
}

View File

@ -2,9 +2,10 @@ import {
Component,
OnInit,
OnDestroy,
Input,
ChangeDetectionStrategy,
ChangeDetectorRef
ChangeDetectorRef,
EventEmitter,
Output
} from '@angular/core';
@Component({
@ -14,9 +15,16 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SelectorLayoutComponent implements OnInit, OnDestroy {
@Output()
closed = new EventEmitter<void>();
constructor(private changeDetectorRef: ChangeDetectorRef) {}
ngOnInit(): void {}
ngOnDestroy(): void {}
onClickClose(event: MouseEvent): void {
this.closed.emit();
}
}

View File

@ -1,10 +1,10 @@
<div class="title-bar">
<ucap-title-bar
[platform]="platform"
[native]="native"
(closed)="onClosedTitleBar()"
(maximized)="onMaximizedTitleBar()"
(minimized)="onMinimizedTitleBar()"
>
<ng-content></ng-content>
</ucap-title-bar>
</div>

View File

@ -2,5 +2,5 @@
width: 100%;
height: 100%;
padding: 0;
background-color: #ffffff;
background-color: transparent;
}

View File

@ -1,7 +1,16 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
import * as detectBrowser from 'detect-browser';
import { Store } from '@ngrx/store';
import { NativeService, WindowState, NativeType } from '@ucap/native';
import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native';
@Component({
selector: 'app-layouts-top-bar',
templateUrl: './top-bar.component.html',
@ -9,13 +18,54 @@ import { Store } from '@ngrx/store';
})
export class TopBarComponent implements OnInit, OnDestroy {
platform = 'win32';
native = true;
windowState: WindowState;
constructor(private store: Store<any>) {}
private ngOnDestroySubject: Subject<void> = new Subject();
ngOnInit() {}
constructor(
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
private store: Store<any>
) {
this.nativeService.platform_nativeType().then((type) => {
switch (type) {
case NativeType.Browser:
this.platform = 'browser';
break;
case NativeType.Electron:
{
const info = detectBrowser.detect();
if (info.os.startsWith('Windows')) {
this.platform = 'win32';
} else if (info.os.startsWith('Mac OS')) {
this.platform = 'darwin';
} else if (info.os.startsWith('Linux')) {
this.platform = 'linux';
} else {
}
}
break;
ngOnDestroy(): void {}
default:
break;
}
});
}
ngOnInit() {
this.nativeService
.window_onState$()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((windowState) => {
this.windowState = windowState;
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onClosedTitleBar() {}

View File

@ -5,21 +5,25 @@ import { RouterModule } from '@angular/router';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatTabsModule } from '@angular/material/tabs';
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 { AppOrganizationModule } from '@app/ucap/organization/organization.module';
import { COMPONENTS } from './components';
import { DIALOGS } from './dialogs';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { MatSelectModule } from '@angular/material/select';
@NgModule({
imports: [
@ -28,17 +32,22 @@ import { MatSelectModule } from '@angular/material/select';
FlexLayoutModule,
MatBadgeModule,
MatButtonModule,
MatIconModule,
MatMenuModule,
MatSelectModule,
MatSidenavModule,
MatTabsModule,
MatToolbarModule,
MatSelectModule,
MatTooltipModule,
PerfectScrollbarModule,
I18nModule,
UiModule
UiModule,
AppOrganizationModule
],
exports: [...COMPONENTS, ...DIALOGS],
declarations: [...COMPONENTS, ...DIALOGS],
@ -46,7 +55,7 @@ import { MatSelectModule } from '@angular/material/select';
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['chat', 'common']
useValue: ['common']
}
]
})

View File

@ -0,0 +1,4 @@
export interface GroupOpenInfo {
lastGroupSeq: number;
groupSeqs: number[];
}

View File

@ -1,8 +1,10 @@
import { LoginSession as UCAPLoginSession } from '@ucap/core';
import { GroupOpenInfo } from './group-open-info';
export interface LoginSession extends UCAPLoginSession {
loginPw?: string;
initPw?: boolean;
encData?: string;
alive?: boolean;
groupInfo?: GroupOpenInfo;
}

View File

@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router';
import { ForgotPasswordPageComponent } from './components/forgot-password.page.component';
import { LoginPageComponent } from './components/login.page.component';
import { LogoutPageComponent } from './components/logout.page.component';
import { ResetPasswordPageComponent } from './components/reset-password.page.component';
const routes: Routes = [
@ -17,6 +18,10 @@ const routes: Routes = [
{
path: 'reset_password',
component: ResetPasswordPageComponent
},
{
path: 'logout',
component: LogoutPageComponent
}
];

View File

@ -25,13 +25,11 @@ export class LoginPageComponent implements OnInit, OnDestroy {
readonly fixedCompanyCode = environment.companyConfig.fixedCompanyCode;
private ngOnDestroySubject = new Subject<boolean>();
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(private localStorageService: LocalStorageService) {}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.localStorageService
.encGet$<UserStore>(
AppKey.UserStore,
@ -43,6 +41,7 @@ export class LoginPageComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}

View File

@ -0,0 +1 @@
<div class="logout-container">Logout</div>

View File

@ -0,0 +1,6 @@
@import '~@ucap/lg-scss/mixins';
.logout-container {
width: 100%;
height: 100%;
}

View File

@ -1,28 +1,28 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { LoginSectionComponent } from './login.section.component';
import { LogoutPageComponent } from './logout.page.component';
describe('app::sections::account::LoginSectionComponent', () => {
describe('app::pages::account::LogoutPageComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [LoginSectionComponent]
declarations: [LogoutPageComponent]
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(LoginSectionComponent);
const fixture = TestBed.createComponent(LogoutPageComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'ucap-lg-web'`, () => {
const fixture = TestBed.createComponent(LoginSectionComponent);
const fixture = TestBed.createComponent(LogoutPageComponent);
const app = fixture.componentInstance;
});
it('should render title', () => {
const fixture = TestBed.createComponent(LoginSectionComponent);
const fixture = TestBed.createComponent(LogoutPageComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain(

View File

@ -0,0 +1,30 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { LocalStorageService } from '@ucap/ng-web-storage';
import { environment } from '@environments';
import { UserStore } from '@app/models/user-store';
import { AppKey } from '@app/types/app-key.type';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-pages-account-logout',
templateUrl: './logout.page.component.html',
styleUrls: ['./logout.page.component.scss']
})
export class LogoutPageComponent implements OnInit, OnDestroy {
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(private localStorageService: LocalStorageService) {}
ngOnInit(): void {}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
}

View File

@ -8,6 +8,7 @@ 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 { MatTooltipModule } from '@angular/material/tooltip';
import { AppChatSectionModule } from '@app/sections/chat/chat.section.module';
@ -27,6 +28,7 @@ import { UCAP_I18N_NAMESPACE, I18nModule } from '@ucap/ng-i18n';
MatCheckboxModule,
MatButtonModule,
MatSidenavModule,
MatTooltipModule,
AppChatSectionModule,
AppChatRoutingPageModule,

View File

@ -3,22 +3,72 @@
<app-sections-chat-info
[roomId]="roomId"
(openChatSearch)="isChatSearch = true"
(rightDrawerToggle)="onRightDrawerToggle()"
(rightDrawerToggle)="onRightDrawerToggle($event)"
></app-sections-chat-info>
</div>
<app-sections-chat-chat-search
*ngIf="isChatSearch"
[isChatSearch]="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 class="message-box-container" fxFlex="1 1 auto" fxLayout="column">
<div class="message-area" fxFlex="1 1 auto">
<app-sections-chat-message
[roomId]="roomId"
[translationSimpleview]="translationSimpleview"
[eventSendTrigger$]="eventSendTriggerSubject.asObservable()"
></app-sections-chat-message>
</div>
<div class="message-input" fxFlex="0 0 auto">
<app-sections-chat-form
[roomId]="roomId"
(changeTranslationSimpleview)="translationSimpleview = $event"
(eventSendTrigger)="eventSendTriggerSubject.next($event)"
></app-sections-chat-form>
</div>
</div>
<mat-drawer #chatRightDrawer mode="side" position="end" class="rightDrawer">
Right Sections.
<ng-container [ngSwitch]="drawerType">
<app-drawer-chat-attach-data
*ngSwitchCase="
[
ChatDrawType.AttachImage,
ChatDrawType.AttachVideo,
ChatDrawType.AttachFile
].includes(drawerType)
? drawerType
: ''
"
[roomId]="roomId"
[drawerType]="drawerType"
(closed)="onRightDrawerClose()"
></app-drawer-chat-attach-data>
<app-drawer-chat-users
*ngSwitchCase="ChatDrawType.RoomUsers"
[roomId]="roomId"
(closed)="onRightDrawerClose()"
(rightDrawerToggle)="onRightDrawerToggle($event)"
></app-drawer-chat-users>
<app-drawer-chat-add-users
*ngSwitchCase="ChatDrawType.Invite"
[roomId]="roomId"
[returnChatDrawerType]="returnDrawerType"
(closed)="onRightDrawerClose()"
(rightDrawerToggle)="onRightDrawerToggle($event)"
></app-drawer-chat-add-users>
<app-drawer-chat-add-group
*ngSwitchCase="ChatDrawType.AddGroup"
[roomId]="roomId"
[returnChatDrawerType]="returnDrawerType"
(closed)="onRightDrawerClose()"
(rightDrawerToggle)="onRightDrawerToggle($event)"
></app-drawer-chat-add-group>
<app-drawer-chat-setting
*ngSwitchCase="ChatDrawType.Setting"
[roomId]="roomId"
(closed)="onRightDrawerClose()"
></app-drawer-chat-setting>
</ng-container>
</mat-drawer>
</mat-drawer-container>
</div>

View File

@ -0,0 +1,33 @@
@import '~@ucap/lg-scss/mixins';
.contents-main {
position: relative;
.subtitle {
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16);
position: relative;
z-index: 4;
}
.message-box-container {
overflow: hidden;
height: 100%;
.message-area {
position: relative;
height: 100%;
overflow: hidden;
}
.message-input {
max-height: 70%;
overflow-x: hidden;
overflow-y: auto;
padding: 1px 0;
background-color: $white;
}
}
.rightDrawer {
min-width: 360px;
max-width: 100%;
@include screen(xs) {
min-width: 100%;
}
}
}

View File

@ -3,8 +3,13 @@ import { ActivatedRoute, Params } from '@angular/router';
import { MatDrawer } from '@angular/material/sidenav';
import { Subscription } from 'rxjs';
import { Subscription, Subject, BehaviorSubject } from 'rxjs';
import { QueryParams } from '../types/params.type';
import { ChatDrawType } from '../types/chat-draw.type';
import { takeUntil } from 'rxjs/operators';
import { DrawInfo } from '../models/draw-info';
import { Store } from '@ngrx/store';
import { RoomActions, ChattingActions } from '@ucap/ng-store-chat';
@Component({
selector: 'app-pages-chat-room',
@ -12,30 +17,65 @@ import { QueryParams } from '../types/params.type';
styleUrls: ['./chat-room.page.component.scss']
})
export class ChatRoomPageComponent implements OnInit, OnDestroy {
private paramsSubscription: Subscription;
isChatSearch = false;
roomId: string;
translationSimpleview = false;
drawerType: ChatDrawType | null;
returnDrawerType: ChatDrawType | null;
eventSendTriggerSubject: BehaviorSubject<any> = new BehaviorSubject<any>(0);
@ViewChild('chatRightDrawer', { static: false })
chatRightDrawer: MatDrawer;
constructor(private activatedRoute: ActivatedRoute) {}
ChatDrawType = ChatDrawType;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private store: Store<any>,
private activatedRoute: ActivatedRoute
) {}
ngOnInit(): void {
this.paramsSubscription = this.activatedRoute.queryParams.subscribe(
(params: Params) => {
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params: Params) => {
const seqParam = params[QueryParams.ROOM_ID];
// initializing by roomId Change.
if (this.roomId !== seqParam) {
if (!!this.chatRightDrawer) {
this.chatRightDrawer.close();
}
}
// setting roomId.
this.roomId = !!seqParam ? seqParam : undefined;
}
);
});
}
ngOnDestroy(): void {
if (!!this.paramsSubscription) {
this.paramsSubscription.unsubscribe();
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
if (!!this.eventSendTriggerSubject) {
this.eventSendTriggerSubject.complete();
}
// this.store.dispatch(RoomActions.clearSelectedRoom({ roomId: this.roomId }));
this.store.dispatch(ChattingActions.clearActiveRoomId({}));
}
onRightDrawerToggle(): void {
this.chatRightDrawer.toggle();
onRightDrawerToggle(type: DrawInfo | null): void {
this.drawerType = type.chatDrawType;
this.returnDrawerType = !!type.returnDrawType ? type.returnDrawType : null;
this.chatRightDrawer.open();
}
onRightDrawerClose(): void {
this.drawerType = null;
this.returnDrawerType = null;
this.chatRightDrawer.close();
}
}

View File

@ -1 +1,39 @@
Index page of chat is works!
<div class="index-page-chat-info">
<div class="ico-page-chat">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 166 142">
<g transform="translate(-152.267 -127.865)">
<path
d="M0 0H166V142H0z"
data-name="square-01"
transform="translate(152.267 127.865)"
style="fill: none;"
/>
<path
d="M45.694 2a43.788 43.788 0 0 1 33.84 71.495l8.744 8.756a4.269 4.269 0 0 1-3.235 7.311H45.694a43.781 43.781 0 0 1 0-87.562z"
data-name="comment-dots-01"
transform="translate(199.283 158.998)"
style="stroke: #bababa; stroke-width: 4px; fill: #fff;"
/>
<path
d="M26.015 2A24.012 24.012 0 0 0 7.458 41.206l-4.8 4.8a2.341 2.341 0 0 0 1.774 4.009h21.583a24.009 24.009 0 0 0 0-48.015z"
data-name="comment-dots-02"
transform="translate(167.028 170.336)"
style="fill: #999;"
/>
<path
d="M33.725 5.917a5.628 5.628 0 1 1 11.242 0 5.628 5.628 0 1 1-11.242 0zm-16.862 0A5.775 5.775 0 0 1 22.483 0 5.775 5.775 0 0 1 28.1 5.917a5.774 5.774 0 0 1-5.621 5.917 5.774 5.774 0 0 1-5.617-5.917zM0 5.917A5.775 5.775 0 0 1 5.621 0a5.775 5.775 0 0 1 5.621 5.917 5.774 5.774 0 0 1-5.621 5.917A5.774 5.774 0 0 1 0 5.917z"
transform="translate(223.585 197.682)"
style="fill: #bababa;"
/>
<path
d="M13.476 22.894l-7.509-6.82L3.41 18.38l10.066 9.143L35.086 7.9l-2.539-2.31z"
transform="translate(174.515 180.942)"
style="fill: #fff;"
/>
</g>
</svg>
</div>
<p class="chat-index-copy">
{{ 'chat:room.noSelectRoom' | ucapI18n }}
</p>
</div>

View File

@ -0,0 +1,21 @@
.index-page-chat-info {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.ico-page-chat {
width: 166px;
height: 142px;
margin-top: -80px;
}
.chat-index-copy {
font-size: 1.429em;
color: #666;
padding: 10px 20px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
margin: 50px 0 0;
}
}

View File

@ -1,13 +1,41 @@
<div fxFlexFill class="sidenav-container">
<div class="chat-header">
<h3>{{ 'label.chat' | ucapI18n }}</h3>
<h3>{{ 'chat:label.chat' | ucapI18n }}</h3>
<div class="chat-menu-btn">
<button
mat-icon-button
aria-label="exit-room"
[matTooltip]="'chat:label.exitFromRoom' | ucapI18n"
(click)="onToggleChackable(true)"
>
<mat-icon>exit_to_app</mat-icon>
<mat-icon>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<g transform="translate(-1)">
<path
d="M0 0h24v24H0z"
transform="translate(1)"
style="fill: none;"
/>
<g transform="translate(3 3)">
<path
d="M8.963 3h8.693a1.962 1.962 0 0 1 1.963 1.963v3.926h-1.963V4.963H8.963V18.7h8.693v-3.922h1.963V18.7a1.962 1.962 0 0 1-1.963 1.963H8.963A1.969 1.969 0 0 1 7 18.7V4.963A1.969 1.969 0 0 1 8.963 3z"
style="fill: #666;"
transform="translate(-7 -3)"
/>
<path
d="M13.459 14.542l1.384 1.384 4.907-4.907-4.907-4.908-1.384 1.384 2.532 2.542H6.5V12h9.491z"
style="fill: #666;"
transform="translate(-.19 -2.326)"
/>
</g>
</g>
</svg>
</mat-icon>
</button>
<button
mat-icon-button
@ -25,15 +53,19 @@
[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 class="chat-exit-info">
{{ 'chat:label.exitFromRoom' | ucapI18n }}
<span
>( <strong>{{ this.selectedRoomList.length }}</strong
>/{{
!!this.searchObj.isShowSearch
? this.searchResultList.length
: this.roomList.length
}}
)</span
>
</div>
<div>
<div class="chat-exit-checkbox">
<mat-checkbox
#allCheck
aria-label="all select exit room "
@ -41,7 +73,6 @@
(change)="onToggleAllItem(allCheck.checked)"
(click)="$event.stopPropagation()"
>
전체선택
</mat-checkbox>
</div>
</div>

View File

@ -0,0 +1,53 @@
@import '~@ucap/lg-scss/mixins';
.sidenav-container {
overflow: hidden;
display: flex;
flex-flow: column;
align-content: flex-start;
background-color: #f1f2f6;
.chat-header {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
padding: 0 5px 0 17px;
background-color: $white;
align-items: center;
width: 100%;
h3 {
@include font-family-txt(18, left, $lipstick);
align-items: center;
font-weight: 600;
}
.chat-menu-btn {
justify-self: end;
}
}
.exitRoomInfo {
height: 50px;
min-height: 50px;
max-height: 50px;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
padding: 0 17px;
background-color: $white;
align-items: center;
width: 100%;
.chat-exit-info {
font-size: 1em;
color: $gray-re3;
@include font-family($font-semibold, normal);
font-weight: 600;
span {
color: $gray-re9;
strong {
color: $lipstick;
}
}
}
.chat-exit-checkbox {
justify-self: self-end;
}
}
}

View File

@ -13,6 +13,12 @@ import {
ConfirmDialogResult
} from '@ucap/ng-ui';
import { I18nService } from '@ucap/ng-i18n';
import { SearchInfo } from '../models/search-info';
import { ActivatedRoute, Params } from '@angular/router';
import { QueryParams } from '../types/params.type';
import { AppChatService } from '@app/services/app-chat.service';
import { SessionStorageService } from '@ucap/ng-web-storage';
import { AppKey } from '@app/types';
@Component({
selector: 'app-pages-chat-sidenav',
@ -20,32 +26,56 @@ import { I18nService } from '@ucap/ng-i18n';
styleUrls: ['./sidenav.page.component.scss']
})
export class SidenavPageComponent implements OnInit, OnDestroy {
searchObj: any = {
searchObj: SearchInfo = {
isShowSearch: false,
searchWord: ''
};
checkable = false;
historyRoomId: string;
roomList: RoomInfo[];
selectedRoomList: RoomInfo[] = [];
searchResultList: RoomInfo[] = [];
private ngOnDestroySubject: Subject<boolean>;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private store: Store<any>,
private dialog: MatDialog,
private i18nService: I18nService,
private logService: LogService,
private sessionStorageService: SessionStorageService,
private activatedRoute: ActivatedRoute,
private appChatService: AppChatService,
private changeDetectorRef: ChangeDetectorRef
) {}
) {
this.historyRoomId = this.sessionStorageService.get<string>(
AppKey.HistoryRoomId
);
}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params: Params) => {
const seqParam = params[QueryParams.ROOM_ID];
// language setting
this.i18nService.setDefaultNamespace('chat');
this.sessionStorageService.set<string>(
AppKey.HistoryRoomId,
!!seqParam ? seqParam : undefined
);
if (
seqParam === undefined &&
!!this.historyRoomId &&
this.historyRoomId.length > 0
) {
// exist history roomId.
this.appChatService.openRoombyRoomId(this.historyRoomId);
}
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(RoomSelector.rooms))
@ -57,6 +87,7 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
@ -64,14 +95,16 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
onToggleChackable(checkable: boolean): void {
if (!!checkable) {
if (!!this.selectedRoomList && this.selectedRoomList.length > 0) {
const self = this;
const dialogRef = this.dialog.open<
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
>(ConfirmDialogComponent, {
panelClass: 'min-create-dialog',
data: {
title: this.i18nService.t('dialog.title.exitFromRoom'),
html: this.i18nService.t('dialog.confirmExitFromRoom')
title: this.i18nService.t('chat:label.exitFromRoom'),
html: this.i18nService.t('chat:dialog.confirmExitFromRoom')
}
});
@ -89,6 +122,8 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
} as ExitAllRequest
})
);
self.selectedRoomList = [];
self.checkable = false;
}
});
}
@ -131,7 +166,7 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
} else {
this.selectedRoomList = [];
}
this.changeDetectorRef.detectChanges();
this.changeDetectorRef.markForCheck();
}
onToggleItem(event: { checked: boolean; roomInfo: RoomInfo }): void {
@ -160,16 +195,16 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
this.searchObj = {
isShowSearch: true,
searchWord: params.searchWord
};
this.changeDetectorRef.detectChanges();
} as SearchInfo;
this.changeDetectorRef.markForCheck();
}
/** Searching cancel */
onClickCancel() {
this.searchObj = {
isShowSearch: false,
searchWord: ''
};
this.changeDetectorRef.detectChanges();
} as SearchInfo;
this.changeDetectorRef.markForCheck();
}
onSearchResultList(searchResultList: RoomInfo[]) {

View File

@ -0,0 +1,6 @@
import { ChatDrawType } from '../types/chat-draw.type';
export interface DrawInfo {
chatDrawType: ChatDrawType;
returnDrawType?: ChatDrawType;
}

View File

@ -0,0 +1,4 @@
export interface SearchInfo {
isShowSearch: boolean;
searchWord: string;
}

View File

@ -0,0 +1,9 @@
export enum ChatDrawType {
AttachImage = 'ATTACHIMAGE',
AttachVideo = 'ATTACHVIDEO',
AttachFile = 'ATTACHFILE',
Invite = 'INVITE',
Setting = 'SETTING',
RoomUsers = 'ROOMUSERS',
AddGroup = 'ADDGROUP'
}

View File

@ -1,11 +1,45 @@
<div fxLayout="column" class="index-container">
<div class="subtitle" fxFlex="60px">Welcome to M-Messenger</div>
<!--<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">
<nav mat-tab-nav-bar color="accent" class="group-main-nav">
<a
mat-tab-link
(click)="activeLink = 0"
[active]="activeLink === 0 ? 'link' : undefined"
>{{ profileName }}</a
>
<a
mat-tab-link
(click)="activeLink = 1"
[active]="activeLink === 1 ? 'link' : undefined"
>{{ tabName }}</a
>
</nav>
<div
class="profile-container"
[ngClass]="activeLink === 0 ? 'active' : ''"
fxFlex="44%"
>
<app-organization-profile-01
[userSeq]="userSeq"
(openChat)="onOpenCaht($event)"
(sendMessage)="onSendMessage($event)"
(sendCall)="onSendCall($event)"
(sendSms)="onSendSms($event)"
(createConference)="onCreateConference($event)"
(toggleFavorit)="onToggleFavorit($event)"
(toggleBuddy)="onToggleBuddy($event)"
(uploadProfileImage)="onUploadProfileImage($event)"
(updateIntro)="onUpdateIntro($event)"
(updateNickname)="onUpdateNickname($event)"
>
</app-organization-profile-01>
</div>
<div class="group-info-container" fxFlex="1 1 auto">
<div
class="group-info-container"
[ngClass]="activeLink === 1 ? 'active' : ''"
fxFlex="1 1 auto"
>
<app-sections-group-info [userSeq]="userSeq"></app-sections-group-info>
</div>
</div>

View File

@ -2,8 +2,18 @@
.index-container {
height: 100%;
padding: 0 30px 30px;
padding: 30px 30px;
overflow: auto;
@include screen(lg) {
padding: 0 20px 30px;
}
@include screen(mid) {
padding: 0 15px 30px;
}
@include screen(xs) {
padding: 0;
}
}
.subtitle {
@ -18,12 +28,91 @@
color: $gray-re4;
}
.profile-container {
height: 100%;
overflow: auto;
}
.content-container {
@include screen(lg) {
display: flex;
flex-direction: column !important;
}
@include screen(mid) {
display: flex;
flex-direction: column !important;
}
.group-main-nav {
display: none;
}
@include screen(xs) {
position: relative;
overflow: hidden;
.group-main-nav {
display: block;
position: fixed;
height: 49px;
top: 40px;
left: 75px;
z-index: 10;
width: calc(100% - 90px);
a {
width: 50%;
text-decoration: none;
color: rgba(51, 51, 51, 0.6);
font-weight: 600;
&.mat-tab-label-active {
color: rgba(51, 51, 51, 1);
opacity: 1;
}
}
}
}
.profile-container {
height: 100%;
overflow: auto;
flex: 1 0 460px !important;
max-width: inherit !important;
border-radius: 2px;
@include screen(custom, min, 1540) {
flex: 1 0 44% !important;
}
@include screen(lg) {
flex: 1 0 auto !important;
max-width: 100% !important;
height: auto;
}
@include screen(mid) {
flex: 1 0 auto !important;
max-width: 100% !important;
height: auto;
}
@include screen(xs) {
display: none;
&.active {
display: block;
height: 100%;
overflow: hidden;
}
}
}
.group-info-container {
height: 100%;
overflow: auto;
.group-info-container {
height: 100%;
overflow: auto;
flex: 5 1 auto !important;
@include screen(lg) {
flex: 1 0 auto !important;
max-width: 100% !important;
height: auto;
}
@include screen(mid) {
flex: 1 0 auto !important;
max-width: 100% !important;
height: auto;
}
@include screen(xs) {
display: none;
&.active {
display: block;
height: 100%;
background-color: #fff;
}
}
}
}

View File

@ -1,40 +1,194 @@
import { Subscription } from 'rxjs';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Component, OnInit, OnDestroy } from '@angular/core';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectorRef,
ChangeDetectionStrategy
} from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { Store, select } from '@ngrx/store';
import { FileUploadItem } from '@ucap/api';
import { FileProfileSaveRequest } from '@ucap/api-common';
import { VersionInfo2Response } from '@ucap/api-public';
import { UserInfoSS, UserInfoF } from '@ucap/protocol-query';
import { UserInfoUpdateType, User } from '@ucap/protocol-info';
import { LoginResponse } from '@ucap/protocol-authentication';
import { LogService } from '@ucap/ng-logger';
import { I18nService } from '@ucap/ng-i18n';
import { UserSelector } from '@ucap/ng-store-organization';
import {
LoginSelector,
ConfigurationSelector
} from '@ucap/ng-store-authentication';
import { UserInfoTypes } from '@app/types';
import { AppFileService } from '@app/services/app-file.service';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { AppGroupService } from '@app/services/app-group.service';
import { QueryParams } from '../types/params.type';
import { AppChatService } from '@app/services/app-chat.service';
@Component({
selector: 'app-pages-group-index',
templateUrl: './index.page.component.html',
styleUrls: ['./index.page.component.scss']
styleUrls: ['./index.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class IndexPageComponent implements OnInit, OnDestroy {
private paramsSubscription: Subscription;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private activatedRoute: ActivatedRoute,
private store: Store<any>,
private router: Router,
private logService: LogService
) {}
private logService: LogService,
private dialog: MatDialog,
private i18nService: I18nService,
private appFileServie: AppFileService,
private appAuthenticationService: AppAuthenticationService,
private changeDetectorRef: ChangeDetectorRef,
private appChatService: AppChatService,
private appGroupService: AppGroupService
) {
this.profileName = this.i18nService.t('organization:profile.me');
this.tabName = this.i18nService.t('organization:profile.chatWithUs');
}
userSeq: string;
userSeq: string = undefined;
user: User;
loginRes: LoginResponse;
versionInfo2Res: VersionInfo2Response;
activeLink = 0;
tabName: string;
profileName: string;
ngOnInit(): void {
this.paramsSubscription = this.activatedRoute.queryParams.subscribe(
(params: Params) => {
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params: Params) => {
const seqParam = params[QueryParams.ID];
this.userSeq = !!seqParam ? seqParam : undefined;
}
);
if (!!seqParam) {
this.userSeq = seqParam;
} else {
this.userSeq = '';
}
this._refreshProfile();
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user))
.subscribe((user) => {
this.user = user;
this._refreshProfile();
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
});
}
ngOnDestroy(): void {
if (!!this.paramsSubscription) {
this.paramsSubscription.unsubscribe();
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onOpenCaht(userInfo: UserInfoSS) {
this.appChatService.newOpenRoom(
[Number(userInfo.seq) as any],
false,
this.user
);
}
onSendMessage(userInfo: UserInfoSS) {}
onSendCall(call: string) {}
onSendSms(employeeNum: string) {}
onCreateConference(userSeq: number) {}
onToggleFavorit(params: { userInfo: UserInfoSS; isFavorite: boolean }) {
this.appGroupService.updateBuddy(params.userInfo, params.isFavorite);
}
onToggleBuddy(params: { userInfo: UserInfoSS; isBuddy: boolean }) {
this.appGroupService
.updateBuddyByToggle(params)
.then((isRemoveBuddy) => {
if (isRemoveBuddy) {
this.router.navigate(
[
'group',
{
outlets: { content: 'index' }
}
],
{
queryParams: { id: Number(params.userInfo.seq) }
}
);
}
})
.catch((reson) => this.logService.error(reson));
}
onUploadProfileImage(profileImageFileUploadItem: FileUploadItem) {
const loginSession = this.appAuthenticationService.getLoginSession();
const profile = {
userSeq: String(this.user.info.seq),
deviceType: loginSession.deviceType,
token: this.loginRes.tokenString,
file: profileImageFileUploadItem.file,
fileUploadItem: profileImageFileUploadItem
} as FileProfileSaveRequest;
this.appFileServie.fileProfileSave(
profile,
this.versionInfo2Res.profileUploadUrl
);
}
onUpdateIntro(intro: string) {
this.appGroupService.updateIntro(intro, UserInfoUpdateType.Intro);
}
onUpdateNickname(params: { userInfo: UserInfoTypes; nickname: string }) {
this.appGroupService.updateNickname(
params.userInfo as UserInfoF,
params.nickname
);
}
private _refreshProfile() {
if (!this.user || undefined === this.userSeq) {
return;
}
if ('' === this.userSeq) {
this.userSeq = String(this.user.info.seq);
}
if (String(this.user.info.seq) === String(this.userSeq)) {
this.profileName = this.i18nService.t('organization:profile.me');
this.tabName = this.i18nService.t('organization:profile.unreadChat');
} else {
this.profileName = this.i18nService.t('organization:profile.other');
this.tabName = this.i18nService.t('organization:profile.chatWithUs');
}
this.changeDetectorRef.markForCheck();
}
}

View File

@ -1,8 +1,8 @@
<div class="sidenav-container" fxFlexFill fxLayout="column">
<div class="sidenav-container group" fxFlexFill fxLayout="column">
<div class="title-section" fxFlex="0 0 50px" fxLayout="row">
<div class="title">
<h3 fxFlex="1 1 auto">그룹</h3>
<div class="menu-btn" fxFlex="80px">
<h3 fxFlex="1 1 auto">{{ 'group:label.group' | ucapI18n }}</h3>
<div class="menu-btn">
<button
mat-icon-button
[matMenuTriggerFor]="groupViewMenu"
@ -21,43 +21,58 @@
</div>
</div>
<div class="extra-box" fxFlex="0 0 50px">
<app-organization-search-for-tenant [(searchData)]="companySearchData">
<app-organization-search-for-tenant
[(searchData)]="companySearchData"
(canceled)="onSearchCancel()"
>
</app-organization-search-for-tenant>
</div>
<div fxFlex="1 1 auto">
<app-sections-group-list
#sectionGroupList
fxFlexFill
[searchData]="companySearchData"
[showType]="showType"
(clickUser)="onClickUser($event)"
></app-sections-group-list>
</div>
</div>
<mat-menu #groupMenu="matMenu">
<button mat-menu-item (click)="onClickGroupMenu('GROUP_NEW')">
{{ 'moreMenu.group.addNew' | ucapI18n }}
<mat-icon matPrefix class="material-icons-outlined">person_add</mat-icon
>{{ 'group:contextMenu.addNewGroup' | ucapI18n }}
</button>
<button mat-menu-item (click)="onClickGroupMenu('GROUP_EXPAND_MORE')">
{{ 'moreMenu.group.expandMore' | ucapI18n }}
<mat-icon matPrefix>keyboard_arrow_down</mat-icon
>{{ 'group:contextMenu.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 }}
<mat-icon matPrefix>keyboard_arrow_up</mat-icon
>{{ 'group:contextMenu.expandLess' | ucapI18n }}
</button>
<!-- <button mat-menu-item (click)="onClickGroupMenu('GROUP_CHANGE_ODER')">
<mat-icon matPrefix>low_priority</mat-icon
>{{ 'group:contextMenu.changeOrder' | ucapI18n }}
</button> -->
</mat-menu>
<mat-menu #groupViewMenu="matMenu">
<button mat-menu-item (click)="onClickShowGroupMenu('ALL')">
{{ 'moreMenu.show.all' | ucapI18n }}
<button mat-menu-item (click)="onClickShowGroupMenu(sortViewType.all)">
<mat-icon matPrefix>{{ showGroupMenuIcon(sortViewType.all) }}</mat-icon
>{{ 'group:contextMenu.all' | ucapI18n }}
</button>
<button mat-menu-item (click)="onClickShowGroupMenu('ONLINE_BUDDY')">
{{ 'moreMenu.show.onlineBuddy' | ucapI18n }}
<button
mat-menu-item
(click)="onClickShowGroupMenu(sortViewType.onlineBuddy)"
>
<mat-icon matPrefix>{{
showGroupMenuIcon(sortViewType.onlineBuddy)
}}</mat-icon
>{{ 'group:contextMenu.onlineBuddy' | ucapI18n }}
</button>
<button mat-menu-item (click)="onClickShowGroupMenu('ON_OFF')">
{{ 'moreMenu.show.onOff' | ucapI18n }}
<button mat-menu-item (click)="onClickShowGroupMenu(sortViewType.onOff)">
<mat-icon matPrefix>{{ showGroupMenuIcon(sortViewType.onOff) }}</mat-icon
>{{ 'group:contextMenu.onOffBuddy' | ucapI18n }}
</button>
</mat-menu>

View File

@ -1,6 +1,6 @@
@import '~@ucap/lg-scss/mixins';
.sidenav-container {
.sidenav-container.group {
overflow: hidden;
display: flex;
flex-flow: column;
@ -24,9 +24,6 @@
align-items: center;
font-weight: 600;
}
.menu-btn {
justify-self: end;
}
}
}
}

View File

@ -1,39 +1,46 @@
import { of, Subject } from 'rxjs';
import { take, map, catchError, takeUntil } from 'rxjs/operators';
import { take, map, catchError } from 'rxjs/operators';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectorRef,
ViewChild
ViewChild,
ChangeDetectionStrategy
} from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { MatDialog } from '@angular/material/dialog';
import { ParamsUtil } from '@ucap/ng-core';
import { LogService } from '@ucap/ng-logger';
import { I18nService } from '@ucap/ng-i18n';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { CreateDialogComponent } from '@app/sections/group/dialogs/create.dialog.component';
import { ListSectionComponent } from '@app/sections/group/components/list.section.component';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { QueryParams } from '@app/pages/organization/types/params.type';
import { UserInfo } from '@ucap/protocol-sync';
import { SortViewType } from '../types/sort-view.type';
@Component({
selector: 'app-pages-group-sidenav',
templateUrl: './sidenav.page.component.html',
styleUrls: ['./sidenav.page.component.scss']
styleUrls: ['./sidenav.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SidenavPageComponent implements OnInit, OnDestroy {
@ViewChild('sectionGroupList', { static: false })
sectionGroupList: ListSectionComponent;
set companySearchData(searchData: SearchData) {
this._companySearchData = searchData;
if (!!searchData && searchData.searchWord !== '') {
this._companySearchData = { ...searchData, bySearch: true };
} else {
this._companySearchData = { ...searchData, bySearch: false };
}
}
get companySearchData() {
return this._companySearchData;
@ -41,9 +48,10 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
// tslint:disable-next-line: variable-name
_companySearchData: SearchData;
showType: string;
showType: SortViewType;
sortViewType = SortViewType;
private ngOnDestroySubject: Subject<boolean>;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private activatedRoute: ActivatedRoute,
@ -53,17 +61,19 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
public dialog: MatDialog
) {
this.i18nService.setDefaultNamespace('group');
}
) {}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.showType = 'ALL';
this.showType = SortViewType.all;
this.showGroupMenuIcon(SortViewType.all);
}
ngOnDestroy(): void {}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onClickFab(event: MouseEvent) {}
@ -72,8 +82,7 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
case 'GROUP_NEW':
{
const dialogRef = this.dialog.open(CreateDialogComponent, {
width: '100%',
height: '100%'
panelClass: 'max-create-dialog'
});
dialogRef
@ -105,23 +114,48 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
}
}
onClickShowGroupMenu(menuType: string) {
onClickShowGroupMenu(menuType: SortViewType) {
switch (menuType) {
case 'ALL':
case SortViewType.all:
{
this.showType = 'ALL';
this.showType = SortViewType.all;
}
break;
case 'ONLINE_BUDDY':
case SortViewType.onlineBuddy:
{
this.showType = 'ONLINE_BUDDY';
this.showType = SortViewType.onlineBuddy;
}
break;
case 'ON_OFF':
case SortViewType.onOff:
{
this.showType = 'ON_OFF';
this.showType = SortViewType.onOff;
}
break;
}
}
onSearchCancel() {
this.companySearchData = { ...this.companySearchData, searchWord: '' };
}
showGroupMenuIcon(menuType: SortViewType): string {
if (this.showType === menuType) {
return 'check_circle';
}
return 'check_circle_outline';
}
onClickUser(userInfo: UserInfo) {
this.router.navigate(
[
'group',
{
outlets: { content: 'index' }
}
],
{
queryParams: { id: Number(userInfo.seq) }
}
);
}
}

View File

@ -6,6 +6,7 @@ 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 { MatTabsModule } from '@angular/material/tabs';
import { UiModule } from '@ucap/ng-ui';
@ -26,6 +27,7 @@ import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
MatButtonModule,
MatIconModule,
MatMenuModule,
MatTabsModule,
AppOrganizationModule,

View File

@ -0,0 +1,5 @@
export enum SortViewType {
all = 'ALL',
onlineBuddy = 'ONLINE_BUDDY',
onOff = 'ON_OFF'
}

View File

@ -1 +1,8 @@
Index page of message is works!
<!--Index page of message is works!-->
<div class="index-page-empty">
<div class="ico-coming-soon"></div>
<div class="coming-soon-index-copy">
<span>Coming Soon</span>
<span class="guide-text">곧 새로운 모습으로 찾아 뵙겠습니다.</span>
</div>
</div>

View File

@ -0,0 +1,33 @@
.index-page-empty {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.ico-coming-soon {
width: 200px;
height: 200px;
margin-top: -100px;
background-image: url(../../../../assets/images/ico/img_coming_soon.png);
background-size: 100% auto;
}
.coming-soon-index-copy {
text-align: center;
font-size: 2.4em;
color: #666;
font-weight: 600;
padding: 0 20px;
//border-top: 1px solid #ccc;
//border-bottom: 1px solid #ccc;
span {
display: block;
}
.guide-text {
padding-top: 10px;
font-size: 0.54em;
color: #999;
font-weight: normal;
}
}
}

View File

@ -1,7 +1,10 @@
<div class="index-page-container" fxLayout="column">
<!-- search start-->
<div fxFlex="0 0 50px">
<app-organization-search-for-tenant [(searchData)]="companySearchData">
<app-organization-search-for-tenant
[(searchData)]="companySearchData"
(canceled)="onCanceledSearch()"
>
</app-organization-search-for-tenant>
</div>
<!-- search end-->

View File

@ -1,4 +1,5 @@
.index-page-container {
width: 100%;
height: 100%;
overflow: hidden;
}

View File

@ -1,21 +1,28 @@
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectorRef,
ChangeDetectionStrategy
} from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { Store } from '@ngrx/store';
import { ParamsUtil } from '@ucap/ng-core';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { QueryParams } from '../types/params.type';
import { UserStore } from '@app/models/user-store';
@Component({
selector: 'app-pages-organization-index',
templateUrl: './index.page.component.html',
styleUrls: ['./index.page.component.scss']
styleUrls: ['./index.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class IndexPageComponent implements OnInit, OnDestroy {
set companySearchData(searchData: SearchData) {
@ -32,18 +39,19 @@ export class IndexPageComponent implements OnInit, OnDestroy {
deptSeq: string;
private ngOnDestroySubject: Subject<boolean>;
private ngOnDestroySubject: Subject<void> = new Subject();
private userStore: UserStore;
constructor(
private store: Store<any>,
private appAuthenticationService: AppAuthenticationService,
private router: Router,
private activatedRoute: ActivatedRoute,
private changeDetectorRef: ChangeDetectorRef
) {}
) {
this.userStore = this.appAuthenticationService.getUserStore();
}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params) => {
@ -57,22 +65,31 @@ export class IndexPageComponent implements OnInit, OnDestroy {
false
);
if (!!deptSeq && this.deptSeq !== deptSeq) {
this.deptSeq = deptSeq;
}
this.deptSearchData = {
deptSeq: bySearch ? undefined : deptSeq,
companyCode: bySearch ? companyCode : undefined,
searchWord: bySearch ? searchWord : undefined,
companyCode: !!companyCode
? companyCode
: this.userStore.companyCode,
searchWord: bySearch ? decodeURIComponent(searchWord) : undefined,
bySearch
};
this._companySearchData = {
...this.deptSearchData
};
this.changeDetectorRef.markForCheck();
}
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
@ -80,9 +97,21 @@ export class IndexPageComponent implements OnInit, OnDestroy {
onChangedCompanySearch() {
const queryParams: Params = {};
queryParams[QueryParams.COMPANY_CODE] = this._companySearchData.companyCode;
queryParams[QueryParams.SEARCH_WORD] = this._companySearchData.searchWord;
queryParams[QueryParams.SEARCH_WORD] = encodeURIComponent(
this._companySearchData.searchWord
);
queryParams[QueryParams.BY_SEARCH] = String(true);
this._navigate(queryParams);
}
onCanceledSearch() {
const queryParams: Params = {};
queryParams[QueryParams.DEPT_SEQ] = String(this.deptSeq);
this._navigate(queryParams);
}
private _navigate(queryParams: Params = {}) {
this.router.navigate(
[
'organization',

View File

@ -1,22 +1,12 @@
<div class="sidenav-container" fxFlexFill fxLayout="column">
<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>
<h3>{{ 'organization:label.organization' | ucapI18n }}</h3>
</div>
</div>
<div class="extra-box" fxFlex="0 0 50px">
<mat-icon matPrefix class="ico-business">business</mat-icon>LG CNS
<div *ngIf="!!displayRootDept" class="extra-box" fxFlex="0 0 50px">
<mat-icon matPrefix class="ico-business">business</mat-icon
>{{ displayRootDept | ucapOrganizationTranslate: 'name' }}
</div>
<div fxFlex="1 1 auto">
@ -26,8 +16,3 @@
></app-organization-tree>
</div>
</div>
<mat-menu #organizationMenu="matMenu">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</mat-menu>

View File

@ -1,27 +1,44 @@
import { Subject } from 'rxjs';
import { Subject, combineLatest } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectorRef,
ChangeDetectionStrategy
} from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { Store, select } from '@ngrx/store';
import { LogService } from '@ucap/ng-logger';
import { DeptInfo } from '@ucap/protocol-query';
import { DepartmentSelector, UserSelector } from '@ucap/ng-store-organization';
import { LoginSelector } from '@ucap/ng-store-authentication';
import { environment } from '@environments';
import { QueryParams } from '../types/params.type';
@Component({
selector: 'app-pages-ogranization-sidenav',
templateUrl: './sidenav.page.component.html',
styleUrls: ['./sidenav.page.component.scss']
styleUrls: ['./sidenav.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SidenavPageComponent implements OnInit, OnDestroy {
initialExpanded: number;
displayRoot = false;
displayRootDept: DeptInfo;
private ngOnDestroySubject: Subject<void>;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
private logService: LogService
) {
@ -29,22 +46,56 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
}
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);
// }
// }
// });
this.activatedRoute.queryParams
combineLatest([
this.activatedRoute.queryParams,
this.store.pipe(select(UserSelector.user))
])
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params) => {
.subscribe(([params, user]) => {
let existParams = false;
if (!!params) {
const deptSeq = params[QueryParams.DEPT_SEQ];
if (!!deptSeq) {
existParams = true;
this.initialExpanded = Number(deptSeq);
}
}
if (!existParams) {
this.initialExpanded = user.departmentCode;
}
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(DepartmentSelector.departmentInfoList)
)
.subscribe((deptInfoList) => {
if (!environment.productConfig.organization.displayRoot) {
if (!!deptInfoList && deptInfoList.length > 0) {
this.displayRootDept = deptInfoList.find(
(item) => item.type === 'R'
);
}
}
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
@ -52,7 +103,6 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
onClickedTree(node: DeptInfo) {
const queryParams: Params = {};
queryParams[QueryParams.DEPT_SEQ] = String(node.seq);
queryParams[QueryParams.BY_SEARCH] = String(false);
this.router.navigate(
[

View File

@ -12,6 +12,9 @@ import { AppOrganizationSectionModule } from '@app/sections/organization/organiz
import { AppOrganizationRoutingPageModule } from './organization-routing.page.module';
import { COMPONENTS } from './components';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { UiModule } from '@ucap/ng-ui';
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
@NgModule({
imports: [
@ -24,9 +27,19 @@ import { COMPONENTS } from './components';
AppOrganizationModule,
AppOrganizationSectionModule,
AppOrganizationRoutingPageModule
AppOrganizationRoutingPageModule,
I18nModule,
OrganizationUiModule,
UiModule
],
declarations: [...COMPONENTS],
entryComponents: []
entryComponents: [],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['organization', 'common']
}
]
})
export class AppOrganizationPageModule {}

View File

@ -4,67 +4,49 @@ import { ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { AuthenticationUiModule } from '@ucap/ng-ui-authentication';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSliderModule } from '@angular/material/slider';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatToolbarModule } from '@angular/material/toolbar';
import { I18nModule } from '@ucap/ng-i18n';
import { UiModule } from '@ucap/ng-ui';
import { AuthenticationUiModule } from '@ucap/ng-ui-authentication';
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
import { AppLayoutsModule } from '@app/layouts/layouts.module';
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
import { COMPONENTS } from './components';
@NgModule({
imports: [
CommonModule,
FlexLayoutModule,
MatCheckboxModule,
I18nModule,
AuthenticationUiModule,
ReactiveFormsModule,
FlexLayoutModule,
MatButtonModule,
MatButtonToggleModule,
MatCardModule,
MatDatepickerModule,
MatDialogModule,
MatCheckboxModule,
MatIconModule,
MatInputModule,
MatMenuModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatCheckboxModule,
MatRadioModule,
MatSelectModule,
MatSidenavModule,
MatSliderModule,
MatTabsModule,
MatTooltipModule,
MatToolbarModule,
MatFormFieldModule,
MatSelectModule
I18nModule,
UiModule,
AuthenticationUiModule,
OrganizationUiModule,
AppLayoutsModule,
AppOrganizationModule
],
exports: [...COMPONENTS],
declarations: [...COMPONENTS],
entryComponents: [],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['authentication']
}
]
entryComponents: []
})
export class AppAccountSectionModule {}

View File

@ -1,127 +0,0 @@
<div class="login-box">
<ng-content select="[ucapAuthenticationLogin='header']"></ng-content>
<div class="login-content">
<form name="loginForm" [formGroup]="loginForm" novalidate>
<mat-form-field
[style.display]="!!fixedCompanyCode ? 'none' : 'block'"
class="login-company"
appearance="none"
>
<mat-select
[formControl]="companyCodeFormControl"
[value]="companyCode || fixedCompanyCode"
placeholder="{{ 'login.labels.selectCompany' | ucapI18n }}"
class="login-input-area login-select-form"
>
<mat-option
*ngFor="let company of companyList"
[value]="company.companyCode"
>{{ company.companyName }}
</mat-option>
</mat-select>
</mat-form-field>
<div class="login-input-area idpass-type">
<mat-form-field
class="login-idpass-txt"
appearance="none"
floatLabel=""
>
<!-- <mat-label>{{ 'login.fields.loginId' | ucapI18n }}</mat-label> -->
<input
matInput
[formControl]="loginIdFormControl"
placeholder="{{ 'login.fields.loginId' | ucapI18n }}"
/>
</mat-form-field>
</div>
<div class="login-input-area idpass-type pass-type">
<mat-form-field class="login-idpass-txt" appearance="none">
<!-- <mat-label>{{ 'login.fields.loginPw' | ucapI18n }}</mat-label> -->
<input
matInput
type="password"
[formControl]="loginPwFormControl"
placeholder="{{ 'login.fields.loginPw' | ucapI18n }}"
#loginPw
/>
</mat-form-field>
</div>
<div class="error-container">
<mat-error
*ngIf="
companyCodeFormControl.dirty &&
companyCodeFormControl.invalid &&
companyCodeFormControl.hasError('required')
"
>
{{ 'login.errors.requireCompany' | ucapI18n }}
</mat-error>
<mat-error
*ngIf="
loginIdFormControl.dirty &&
loginIdFormControl.invalid &&
loginIdFormControl.hasError('required')
"
>
{{ 'login.errors.requireLoginId' | ucapI18n }}
</mat-error>
<mat-error
*ngIf="
loginPwFormControl.dirty &&
loginPwFormControl.invalid &&
loginPwFormControl.hasError('required')
"
>
{{ 'login.errors.requireLoginPw' | ucapI18n }}
</mat-error>
<mat-error *ngIf="loginFailed">
{{ 'login.errors.failed' | ucapI18n }}
</mat-error>
</div>
<button
mat-raised-button
class="login-input-submit"
aria-label="LOG IN"
[disabled]="
loginForm.invalid ||
disable ||
(!!loginTry && !!loginTry.remainTimeForNextTry)
"
(click)="onClickLogin()"
>
<ng-container
*ngIf="!processing && (!loginTry || !loginTry.remainTimeForNextTry)"
>
{{ 'login.labels.doLogin' | ucapI18n }}
</ng-container>
<ng-container *ngIf="!!loginTry && !!loginTry.remainTimeForNextTry">
{{ 'login.errors.attemptsExceeded' | ucapI18n }}
(
{{
moment
.utc(
moment
.duration(loginTry.remainTimeForNextTry, 'seconds')
.asMilliseconds()
)
.format('mm:ss')
}}
)
</ng-container>
<mat-spinner
*ngIf="processing && (!loginTry || !loginTry.remainTimeForNextTry)"
>
</mat-spinner>
</button>
</form>
<ng-content select="[ucapAuthenticationLogin='footer']"></ng-content>
</div>
</div>

View File

@ -1,160 +0,0 @@
@import '../../../../../assets/scss/components';
.login-box {
@extend %clearfix;
padding: 0 0 45px;
width: 420px;
margin: auto;
text-align: center;
flex-basis: auto;
align-items: center;
.logo-img {
display: block;
text-align: center;
img {
margin-bottom: 7px;
vertical-align: top;
@include screen(mid) {
width: 120px;
}
@include screen(xs) {
width: 100px;
margin-bottom: 6px;
}
}
}
@extend %guideline;
.login-content {
@extend %guideline2; //Guide Line2
margin: 30px auto 0;
.login-input-area {
border: 1px solid #cccccc;
border-radius: 2px;
width: 100%;
max-width: 420px;
min-width: 150px;
height: 60px;
background-color: $white;
margin-top: 10px;
&.login-select-form {
height: 60px;
line-height: 60px;
padding: 0 16px;
@include screen(mid) {
height: 50px;
line-height: 50px;
}
@include screen(xs) {
height: 42px;
line-height: 42px;
}
}
&:first-of-type {
margin-top: 0px;
}
&.idpass-type {
padding-left: 50px;
position: relative;
&::before {
font-family: 'material Icons';
font-size: 24px;
text-align: center;
line-height: 60px;
content: 'perm_identity';
display: block;
position: absolute;
top: 0;
left: 16px;
@include screen(mid) {
line-height: 50px;
}
@include screen(xs) {
line-height: 42px;
}
}
&.pass-type {
&::before {
content: 'https';
}
}
.login-idpass-txt {
width: 368px;
height: 60px;
line-height: 60px;
font-size: 14px;
@include screen(mid) {
width: 358 - 60 + px;
height: 50px;
line-height: 50px;
font-size: 14px;
}
@include screen(xs) {
width: 308 - 60 + px;
font-size: 14px;
height: 42px;
line-height: 42px;
}
input {
font-size: 18px;
line-height: 58px;
margin-top: 0;
vertical-align: top;
background-color: $white;
padding: 0 10px 0 5px;
@include screen(mid) {
font-size: 16px;
line-height: 48px;
}
@include screen(xs) {
font-size: 14px;
line-height: 40px;
}
}
}
}
@include screen(mid) {
margin-top: 8px;
}
}
.login-input-submit {
width: 100%;
height: 60px;
background-color: $black;
border-radius: 2px;
color: $white;
font-size: 20px;
@include font-family($font-semibold);
border: 0;
margin-top: 12px;
font-weight: 600;
cursor: pointer;
@include screen(mid) {
margin-top: 8px;
font-size: 16px;
height: 50px;
}
@include screen(xs) {
font-size: 14px;
height: 42px;
}
}
@include screen(mid) {
margin-top: 23px;
width: 350px;
.login-input-area {
height: 50px;
}
}
@include screen(xs) {
margin-top: 23px;
width: 300px;
.login-input-area {
height: 42px;
}
}
}
}
.login-company {
width: 100%;
}

View File

@ -1,92 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef } from '@angular/core';
import { I18nService, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { AuthenticationUiModule } from '../authentication-ui.module';
import { MatSelectModule } from '@angular/material/select';
import { Company } from '@ucap/api-external';
import { LogService } from '@ucap/ng-logger';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
describe('ui::authentication::LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
CommonModule,
ReactiveFormsModule,
MatButtonModule,
MatCheckboxModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatProgressSpinnerModule,
MatSelectModule
],
providers: [
AuthenticationUiModule,
// { provide: FormBuilder, useValue: new FormBuilder() },
// { provide: ChangeDetectorRef, useValue: ChangeDetectorRef },
{ provide: I18nService, useValue: new I18nService(new LogService({})) },
{
provide: UCAP_I18N_NAMESPACE,
useValue: 'authentication'
}
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
component.companyList = [
{ companyName: 'LG CNS', companyCode: 'GUC100' },
{ companyName: 'LG UCAP', companyCode: 'GUC101' }
] as Company[];
component.loginId = 'test';
component.companyCode = 'GUC100';
fixture.detectChanges();
component.ngOnInit();
expect(component).toBeTruthy();
});
it('login', (done) => {
component.companyList = [
{ companyName: 'LG CNS', companyCode: 'GUC100' },
{ companyName: 'LG UCAP', companyCode: 'GUC101' }
] as Company[];
component.loginId = 'test';
component.companyCode = 'GUC100';
component.ngOnInit();
component.login.subscribe((value) => {
console.log(value);
done();
});
component.onClickLogin();
});
});

View File

@ -1,110 +0,0 @@
import moment from 'moment';
import {
Component,
OnInit,
Input,
Output,
EventEmitter,
ViewChild,
ElementRef,
ChangeDetectorRef
} from '@angular/core';
import {
FormGroup,
FormBuilder,
Validators,
FormControl,
ValidatorFn
} from '@angular/forms';
import { Company } from '@ucap/api-external';
import { LoginTry } from '@ucap/pi';
@Component({
selector: 'ucap-authentication-login-local',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
@Input()
companyList: Company[];
@Input()
fixedCompanyCode: string;
@Input()
companyCode: string;
@Input()
loginId: string;
@Input()
disable = false;
@Input()
processing = false;
@Input()
loginTry: LoginTry;
@Output()
login = new EventEmitter<{
companyCode: string;
loginId: string;
loginPw: string;
notValid: () => void;
}>();
@ViewChild('loginPw', { static: true }) loginPwElementRef: ElementRef;
loginForm: FormGroup;
companyCodeFormControl = new FormControl('');
loginIdFormControl = new FormControl('');
loginPwFormControl = new FormControl('');
loginFailed = false;
moment = moment;
constructor(
private formBuilder: FormBuilder,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
const companyCodeValidators: ValidatorFn[] = [Validators.required];
this.companyCodeFormControl.setValidators(companyCodeValidators);
if (!!this.fixedCompanyCode) {
this.companyCodeFormControl.setValue(this.fixedCompanyCode);
}
if (!!this.companyCode) {
this.companyCodeFormControl.setValue(this.companyCode);
}
const loginIdValidators: ValidatorFn[] = [Validators.required];
this.loginIdFormControl.setValidators(loginIdValidators);
if (!!this.loginId) {
this.loginIdFormControl.setValue(this.loginId);
}
const loginPwValidators: ValidatorFn[] = [Validators.required];
this.loginPwFormControl.setValidators(loginPwValidators);
this.loginForm = this.formBuilder.group({
companyCodeFormControl: this.companyCodeFormControl,
loginIdFormControl: this.loginIdFormControl,
loginPwFormControl: this.loginPwFormControl
});
this.changeDetectorRef.detectChanges();
}
onClickLogin() {
this.login.emit({
companyCode: this.loginForm.get('companyCodeFormControl').value,
loginId: this.loginForm.get('loginIdFormControl').value,
loginPw: this.loginForm.get('loginPwFormControl').value,
notValid: () => {
this.loginFailed = true;
this.loginPwElementRef.nativeElement.focus();
}
});
}
}

View File

@ -1,67 +0,0 @@
<div class="login-section-container">
<ucap-authentication-login-local
[companyList]="companyList"
[fixedCompanyCode]="fixedCompanyCode"
[companyCode]="userStore?.companyCode"
[loginId]="userStore?.loginId"
[disable]="disableLoginForm"
[processing]="loginProcessing"
[loginTry]="loginTry"
(login)="onLogin($event)"
>
<div ucapAuthenticationLogin="header">
<div class="logo-img">
<img src="../../../assets/images/logo_140.png" alt="" />
</div>
<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>
<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>
</ucap-authentication-login-local>
</div>

View File

@ -1,117 +0,0 @@
@import '../../../../assets/scss/components';
h1 {
@include font-family($font-light);
font-size: 24px;
text-align: center;
color: $txt-color01;
font-weight: 600;
line-height: 1.2;
@include screen(mid) {
font-size: 19px;
}
@include screen(xs) {
font-size: 14px;
}
}
.login-section-container {
width: 100%;
height: 100%;
overflow: auto;
}
.login-chk-area {
margin-top: 6px;
font-size: 13px;
text-align: left;
@include screen(xs) {
font-size: 12px;
}
}
.login-pass-info {
overflow: hidden;
margin-top: 83px;
ul {
display: flex;
justify-content: center;
li {
height: 24px;
position: relative;
display: inline-flex;
align-items: center;
padding: 0 12% 0 8%;
&::before {
content: '';
height: 11px;
width: 1px;
display: flex;
background-color: $gray-re4a;
position: absolute;
top: 6.5px;
left: 0;
}
&:first-child {
padding-left: 0;
&::before {
display: none;
}
}
&:last-child {
padding-right: 0;
}
a {
line-height: 24px;
font-size: 12px;
color: $gray-re4a;
padding-left: 34px;
position: relative;
white-space: nowrap;
&::before {
font-family: 'material Icons';
font-size: 18px;
text-align: center;
content: 'search';
color: $white;
display: block;
width: 24px;
height: 24px;
border-radius: 50%;
background-color: $black;
position: absolute;
top: 0;
left: 0;
}
&.fir-pass {
&::before {
content: 'sync';
}
}
}
}
}
}
.login-button-area {
margin-top: 14px;
@include screen(xs) {
margin-top: 20px;
}
button {
border: 0;
margin: 0;
width: 100%;
height: 46px;
border-radius: 4px;
background-color: #e0e3e7;
font-size: 12px;
color: $gray-re4a;
cursor: pointer;
@include screen(mid) {
height: 38px;
}
@include screen(xs) {
height: 34px;
}
}
}

View File

@ -1,174 +0,0 @@
import { Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { Component, OnInit, OnDestroy, Input, ViewChild } from '@angular/core';
import { MatCheckbox } from '@angular/material/checkbox';
import { Store, select } from '@ngrx/store';
import { Company } from '@ucap/api-external';
import { LoginTry } from '@ucap/pi';
import { LogService } from '@ucap/ng-logger';
import { I18nService } from '@ucap/ng-i18n';
import { SessionStorageService } from '@ucap/ng-web-storage';
import { PiService } from '@ucap/ng-pi';
import { ProtocolService } from '@ucap/ng-protocol';
import { CompanyActions, CompanySelector } from '@ucap/ng-store-organization';
import { LoginActions } from '@ucap/ng-store-authentication';
import { UserStore } from '@app/models/user-store';
import { LoginSession } from '@app/models/login-session';
import { AppKey } from '@app/types/app-key.type';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
@Component({
selector: 'app-sections-account-login',
templateUrl: './login.section.component.html',
styleUrls: ['./login.section.component.scss']
})
export class LoginSectionComponent implements OnInit, OnDestroy {
@Input()
companyGroupCode: string;
@Input()
fixedCompanyCode: string;
@Input()
userStore: UserStore;
@Input()
useRememberMe: boolean;
@Input()
useAutoLogin: boolean;
@ViewChild('chkUseRememberMe', { static: false })
chkUseRememberMe: MatCheckbox;
@ViewChild('chkUseAutoLogin', { static: false })
chkUseAutoLogin: MatCheckbox;
loginSession: LoginSession;
companyList: Company[];
disableLoginForm = false;
loginProcessing = false;
loginTry: LoginTry;
private ngOnDestroySubject = new Subject<boolean>();
constructor(
private piService: PiService,
private protocolService: ProtocolService,
private sessionStorageService: SessionStorageService,
private i18nService: I18nService,
private store: Store<any>,
private appAuthenticationService: AppAuthenticationService,
private logService: LogService
) {}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.appAuthenticationService
.getLoginSession$()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((loginSession) => (this.loginSession = loginSession));
this.sessionStorageService
.get$<LoginTry>(AppKey.LoginTry)
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((loginTry) => (this.loginTry = loginTry));
this.protocolService.disconnect();
this.store.dispatch(
CompanyActions.companies({
req: { companyGroupCode: this.companyGroupCode }
})
);
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(CompanySelector.companyList)
)
.subscribe((companyList) => {
this.companyList = companyList;
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.complete();
}
}
onLogin(event: {
companyCode: string;
loginId: string;
loginPw: string;
notValid: () => void;
}) {
const useRememberMe: boolean = this.chkUseRememberMe.checked;
const useAutoLogin: boolean = this.chkUseAutoLogin.checked;
this.disableLoginForm = true;
this.loginProcessing = true;
this.piService
.login2({
companyCode: event.companyCode,
loginId: event.loginId,
loginPw: event.loginPw,
deviceType: this.loginSession.deviceType
})
.pipe(take(1))
.subscribe(
(res) => {
if ('success' !== res.status.toLowerCase()) {
this.onWebLoginFailure(event, res.status);
return;
} else {
this.store.dispatch(
LoginActions.webLoginSuccess({
companyCode: event.companyCode,
loginId: event.loginId,
loginPw: event.loginPw,
autoLogin: useAutoLogin,
rememberMe: useRememberMe,
login2Response: res
})
);
return;
}
},
(error) => {
this.onWebLoginFailure(event, error);
},
() => {
this.disableLoginForm = false;
this.loginProcessing = false;
}
);
}
onClickForgotPassword(lng: string) {
this.i18nService.changeLanguage(lng);
}
private onWebLoginFailure(
event: {
companyCode: string;
loginId: string;
loginPw: string;
notValid: () => void;
},
error: any
) {
this.store.dispatch(LoginActions.webLoginFailure({ error }));
event.notValid();
}
}

View File

@ -21,18 +21,28 @@ import { MatTreeModule } from '@angular/material/tree';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatStepperModule } from '@angular/material/stepper';
import { MatDividerModule } from '@angular/material/divider';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatRadioModule } from '@angular/material/radio';
import { MatTabsModule } from '@angular/material/tabs';
import { MatProgressBarModule } from '@angular/material/progress-bar';
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 { OrganizationUiModule } from '@ucap/ng-ui-organization';
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
import { AppChatModule } from '@app/ucap/chat/chat.module';
import { AppLayoutsModule } from '@app/layouts/layouts.module';
import { AppGroupSectionModule } from '../group/group.section.module';
import { COMPONENTS } from './components';
import { DIALOGS } from './dialogs';
import { DRAWERS } from './drawers';
import { AppGroupModule } from '@app/ucap/group/group.module';
@NgModule({
imports: [
@ -56,6 +66,11 @@ import { DIALOGS } from './dialogs';
MatTreeModule,
MatTooltipModule,
MatStepperModule,
MatDividerModule,
MatSlideToggleModule,
MatRadioModule,
MatTabsModule,
MatProgressBarModule,
PerfectScrollbarModule,
ScrollingModule,
@ -65,12 +80,16 @@ import { DIALOGS } from './dialogs';
AppLayoutsModule,
AppGroupSectionModule,
AppOrganizationModule,
ChatUiModule,
AppChatModule
OrganizationUiModule,
AppChatModule,
AppGroupModule
],
exports: [...COMPONENTS, ...DIALOGS],
declarations: [...COMPONENTS, ...DIALOGS],
entryComponents: [...DIALOGS],
exports: [...COMPONENTS, ...DIALOGS, ...DRAWERS],
declarations: [...COMPONENTS, ...DIALOGS, ...DRAWERS],
entryComponents: [...DIALOGS, ...DRAWERS],
providers: [
{
provide: UCAP_I18N_NAMESPACE,

View File

@ -1,13 +1,11 @@
<div class="search-container">
<div
class="search-container"
[ngClass]="!!isChatSearch ? 'chat-search-show' : ''"
>
<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="솔루션"
/>
<mat-label>검색어를 입력하세요</mat-label>
<input matInput #searchWordInput type="text" />
<button
mat-button
matSuffix
@ -17,7 +15,7 @@
class="btn-close"
color="accent"
>
<mat-icon>highlight_off</mat-icon>
<mat-icon>cancel</mat-icon>
</button>
</mat-form-field>
<button
@ -38,14 +36,16 @@
</button>
</div>
<div class="search-result">
<span class="text"><strong>N</strong>건의 검색결과가 있습니다.</span>
<span class="result-count"><strong>1</strong>/N</span>
<span class="text"
><strong class="txt-accent">N</strong>건의 검색결과가 있습니다.</span
>
<span class="result-count"><strong class="txt-accent">1</strong>/N</span>
<div class="btn-area">
<button mat-icon-button class="icon-button">
<mat-icon>arrow_downward</mat-icon>
<mat-icon inline>arrow_downward</mat-icon>
</button>
<button mat-icon-button class="icon-button">
<mat-icon>arrow_upward</mat-icon>
<mat-icon inline>arrow_upward</mat-icon>
</button>
</div>
</div>

View File

@ -0,0 +1,78 @@
@import '~@ucap/lg-scss/mixins';
.search-container {
padding: 0 16px;
background-color: $white;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16);
display: flex;
flex-direction: column;
align-items: center;
//position: relative;
//포지션 변경
position: absolute;
z-index: 5;
top: -80px;
width: 100%;
visibility: hidden;
transition: all 0.2s linear;
.searchbox {
display: flex;
flex-flow: row nowrap;
width: 100%;
max-width: 900px;
border: 1px solid $lipstick;
background-color: $white;
.search-in-box {
@include ucapMatFormField(0, 0, 100%, auto, auto, 38px, 38px);
padding-left: 10px;
.btn-close {
margin-top: 2px;
color: #fd78a1 !important;
}
}
.btn-ico-search {
@include ucapMatButton(38px, 38px, 0, 20px);
}
}
.search-result {
width: 100%;
max-width: 900px;
display: flex;
flex-flow: row nowrap;
height: 40px;
justify-content: space-between;
align-items: center;
.text {
flex-grow: 1;
font-size: 0.857em;
color: $gray-re3;
strong {
font-size: 0.929em;
font-weight: 600;
}
}
.result-count {
justify-self: self-end;
font-size: 0.929em;
}
.btn-area {
margin-right: -5px;
margin-left: 12px;
display: flex;
align-items: center;
&:before {
content: '';
width: 1px;
height: 10px;
display: inline-block;
background-color: $gray-rec;
}
}
}
//on
&.chat-search-show {
visibility: visible;
transition: all 0.2s linear;
top: 0;
}
}

View File

@ -5,8 +5,10 @@ import {
Output,
EventEmitter,
ChangeDetectorRef,
ChangeDetectionStrategy
ChangeDetectionStrategy,
Input
} from '@angular/core';
import { SearchInfo } from '@app/pages/chat/models/search-info';
@Component({
selector: 'app-sections-chat-chat-search',
@ -15,20 +17,19 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChatSearchSectionComponent implements OnInit, OnDestroy {
searchObj: any = {
isSearch: false,
searchWord: ''
};
@Input()
isChatSearch = false;
@Output()
chatSearch = new EventEmitter<{
isSearch: false;
searchWord: '';
}>();
chatSearch = new EventEmitter<SearchInfo>();
@Output()
closeChatSearch = new EventEmitter<void>();
searchObj: SearchInfo = {
isShowSearch: false,
searchWord: ''
};
constructor(private changeDetectorRef: ChangeDetectorRef) {}
ngOnInit(): void {}
@ -39,9 +40,9 @@ export class ChatSearchSectionComponent implements OnInit, OnDestroy {
alert(searchWord);
this.searchObj = {
isSearch: true,
isShowSearch: true,
searchWord
};
} as SearchInfo;
this.chatSearch.emit(this.searchObj);
}

View File

@ -1,64 +0,0 @@
<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>

View File

@ -1,25 +0,0 @@
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();
});
});

View File

@ -1,63 +0,0 @@
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();
}
}

View File

@ -1,63 +0,0 @@
<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>

View File

@ -1,177 +0,0 @@
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();
}
}

View File

@ -1,10 +0,0 @@
import { ChatListItemComponent } from './chat-list-item.component';
import {
ExpansionComponent,
ExpansionNodeDirective,
ExpansionHeaderDirective
} from './expansion.component';
export const COMPONENTS = [ChatListItemComponent, ExpansionComponent];
export const DIRECTIVES = [ExpansionNodeDirective, ExpansionHeaderDirective];

View File

@ -1,123 +1,159 @@
<ng-container [ngSwitch]="selectorType">
<app-chat-selector-sticker
*ngSwitchCase="SelectorType.STICKER"
(selectedSticker)="onSelectedSticker($event)"
(closeSticker)="selectorType = SelectorType.EMPTY"
>
</app-chat-selector-sticker>
<div class="ucap-chat-input-container">
<ng-container [ngSwitch]="selectorType">
<app-chat-selector-sticker
*ngSwitchCase="SelectorType.STICKER"
(selectedSticker)="onSelectedSticker($event)"
(closed)="selectorType = SelectorType.EMPTY"
>
</app-chat-selector-sticker>
<app-chat-selector-translation
*ngSwitchCase="SelectorType.TRANSLATION"
></app-chat-selector-translation>
<app-chat-selector-translation
*ngSwitchCase="SelectorType.TRANSLATION"
(closed)="
selectorType = SelectorType.EMPTY; translationPreviewInfo = null
"
[destLocale]="destLocale"
[simpleView]="translationSimpleview"
[preView]="translationPreview"
[translationPreviewInfo]="translationPreviewInfo"
(changeTranslationSimpleview)="onChangeTranslationSimpleView($event)"
(changeTranslationPreview)="onChangeTranslationPreView($event)"
(changeDestLocale)="onChangeDestLocale($event)"
(sendTranslationMessage)="onSendTranslationMessage($event)"
(translationMessage)="send()"
></app-chat-selector-translation>
<app-chat-selector-file-upload
#fileUploadSelector
*ngSwitchCase="SelectorType.FILEUPLOAD"
>
</app-chat-selector-file-upload>
<app-chat-selector-file-upload
#fileUploadSelector
*ngSwitchCase="SelectorType.FILEUPLOAD"
(closed)="selectorType = SelectorType.EMPTY"
>
</app-chat-selector-file-upload>
<app-chat-selector-email-send
*ngSwitchCase="SelectorType.EMAILSENDER"
></app-chat-selector-email-send>
</ng-container>
<app-chat-selector-email-send
*ngSwitchCase="SelectorType.EMAILSENDER"
(sendEventEmail)="onSendEventEmail($event)"
(closed)="selectorType = SelectorType.EMPTY"
></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
#messageInput
placeholder=""
name="message"
[matTextareaAutosize]="true"
[matAutosizeMaxRows]="20"
(keydown)="onKeydown($event)"
></textarea>
</mat-form-field>
<div class="chat-form-area ucap-mat-input-container">
<mat-form-field
class="message-text"
fxFlex
floatLabel="never"
appearance="none"
>
<mat-label>{{ 'chat:label.inputChatMessage' | ucapI18n }}</mat-label>
<!-- <textarea
matInput
#messageInput
name="message"
[matTextareaAutosize]="true"
(keydown.enter)="onKeydown($event)"
></textarea> -->
<input
matInput
#messageInput
name="message"
(keydown.enter)="onKeydown($event)"
/>
</mat-form-field>
<input
type="file"
#fileInput
style="display: none;"
multiple
(change)="onChangeFileInput()"
/>
</div>
<div class="button-area">
<button
mat-button
aria-label="attachFile"
matTooltipPosition="above"
matTooltip="{{ 'label.attachFile' | ucapI18n }}"
(click)="clearSelector(); fileInput.click()"
>
첨부파일
</button>
<button
mat-button
aria-label="attachImage"
matTooltipPosition="above"
matTooltip="{{ 'label.attachImage' | ucapI18n }}"
(click)="onOpenSelector(SelectorType.EMPTY)"
>
이미지
</button>
<button
mat-button
aria-label="screenshot"
matTooltipPosition="above"
matTooltip="{{ 'label.screenshot' | ucapI18n }}"
(click)="onOpenSelector(SelectorType.EMPTY)"
>
캡쳐 화면 전송
</button>
<button
mat-button
aria-label="imoticon"
matTooltipPosition="above"
matTooltip="{{ 'label.imoticon' | ucapI18n }}"
(click)="onOpenSelector(SelectorType.STICKER)"
>
이모티콘
</button>
<button
mat-button
aria-label="emailSend"
matTooltipPosition="above"
matTooltip="{{ 'label.emailSend' | ucapI18n }}"
(click)="onOpenSelector(SelectorType.EMAILSENDER)"
>
대화내용 메일 전송
</button>
<button
mat-button
aria-label="translation"
matTooltipPosition="above"
matTooltip="{{ 'label.translation' | ucapI18n }}"
(click)="onOpenSelector(SelectorType.TRANSLATION)"
>
대화내용 번역
</button>
<button
mat-button
aria-label="gams"
matTooltipPosition="above"
matTooltip="{{ 'label.gams' | ucapI18n }}"
(click)="onOpenSelector(SelectorType.EMPTY)"
>
+GAMS
</button>
<button
mat-fab
color="accent"
aria-label="send"
matTooltip="{{ 'label.send' | ucapI18n }}"
(click)="send()"
>
<mat-icon>send</mat-icon>
</button>
<input
type="file"
#fileInput
style="display: none;"
multiple
(change)="onChangeFileInput()"
/>
</div>
<div class="button-area">
<button
mat-icon-button
aria-label="attachFile"
matTooltipPosition="above"
matTooltip="{{ 'label.attachFile' | ucapI18n }}"
class="btn-icon-chat"
(click)="clearSelector(); fileInput.accept = '*.*'; fileInput.click()"
>
<mat-icon>attach_file</mat-icon>
</button>
<button
mat-icon-button
aria-label="attachImage"
matTooltipPosition="above"
matTooltip="{{ 'label.attachImage' | ucapI18n }}"
class="btn-icon-chat"
(click)="
clearSelector(); fileInput.accept = 'image/*,video/*'; fileInput.click()
"
>
<mat-icon class="material-icons-outlined">image</mat-icon>
</button>
<!-- <button
mat-icon-button
*ngIf="!!authRes && !!authRes.useCapturePcScreen"
aria-label="screenshot"
matTooltipPosition="above"
matTooltip="{{ 'label.screenshot' | ucapI18n }}"
class="btn-icon-chat"
(click)="onOpenSelector(SelectorType.EMPTY)"
>
<mat-icon>filter_center_focus</mat-icon>
</button> -->
<button
mat-icon-button
aria-label="imoticon"
matTooltipPosition="above"
matTooltip="{{ 'label.imoticon' | ucapI18n }}"
class="btn-icon-chat"
(click)="onOpenSelector(SelectorType.STICKER)"
>
<mat-icon>sentiment_satisfied</mat-icon>
</button>
<button
mat-icon-button
*ngIf="!!authRes && !!authRes.canSendEmail"
aria-label="emailSend"
matTooltipPosition="above"
matTooltip="{{ 'label.emailSend' | ucapI18n }}"
class="btn-icon-chat"
(click)="onOpenSelector(SelectorType.EMAILSENDER)"
>
<mat-icon class="material-icons-outlined">drafts</mat-icon>
</button>
<button
mat-icon-button
*ngIf="!!authRes && !!authRes.canTranslation"
aria-label="translation"
matTooltipPosition="above"
matTooltip="{{ 'label.translation' | ucapI18n }}"
class="btn-icon-chat"
(click)="onOpenSelector(SelectorType.TRANSLATION)"
>
<mat-icon>translate</mat-icon>
</button>
<!-- <button
mat-icon-button
*ngIf="!!authRes && !!authRes.useGams"
aria-label="gams"
matTooltipPosition="above"
matTooltip="{{ 'label.gams' | ucapI18n }}"
class="btn-icon-chat btn-icon-chat-gams"
(click)="onOpenSelector(SelectorType.EMPTY)"
>
<strong>+GAMS</strong>
</button> -->
<button
mat-mini-fab
class="btn-message-send"
aria-label="send"
matTooltip="{{ 'label.send' | ucapI18n }}"
(click)="send()"
>
<mat-icon>arrow_upward</mat-icon>
</button>
</div>
</div>

View File

@ -0,0 +1,60 @@
@import '~@ucap/lg-scss/mixins';
.ucap-chat-input-container {
display: flex;
flex-direction: column;
justify-content: space-between;
border-top: 1px solid #ccc;
background-color: $white;
.chat-form-area {
max-height: 100%;
min-height: 20px;
background-color: $white;
padding-left: 30px;
font-size: 0.929em;
margin: 8px 0;
overflow-x: hidden;
overflow-y: auto;
flex: 1 1 auto;
@include screen(xs) {
padding-left: 16px;
}
textarea {
min-height: 22px;
overflow: hidden;
}
}
.button-area {
display: flex;
flex-direction: row;
align-items: center;
height: 34px;
background-color: rgba(0, 0, 0, 0.02);
border-top: 1px solid #eeeeee;
padding: 0 16px;
flex: 0 0 0;
@include screen(xs) {
padding: 0;
}
.btn-icon-chat {
@include ucapMatButton(34px, 34px, 0, 34px);
&.btn-icon-chat-gams {
width: 60px;
@include font-family($font-semibold);
font-weight: 600;
}
}
.btn-message-send {
width: 38px;
height: 38px;
box-shadow: 0px 2px 2px 0 rgba(0, 0, 0, 0.3);
border: solid 3px #ffffff;
background-image: linear-gradient(225deg, #fadfaa 5%, #f92465 95%);
font-size: 1.714em;
position: absolute;
z-index: 10;
bottom: 25px;
right: 20px;
}
}
}

View File

@ -1,4 +1,4 @@
import { Subject, of, Observable, forkJoin } from 'rxjs';
import { Subject, of, merge } from 'rxjs';
import { takeUntil, map, catchError, take } from 'rxjs/operators';
import {
@ -9,51 +9,68 @@ import {
ChangeDetectorRef,
Input,
ViewChild,
ElementRef
ElementRef,
EventEmitter,
Output
} from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Dictionary } from '@ngrx/entity';
import { Store, select } from '@ngrx/store';
import { MatDialog } from '@angular/material/dialog';
import { StickerFilesInfo } from '@ucap/ng-core';
import { StatusCode, FileUploadItem } from '@ucap/api';
import {
TranslationSaveResponse,
TranslationSaveRequest
} from '@ucap/api-common';
import { VersionInfo2Response } from '@ucap/api-public';
import {
SendEventMailType,
SendEventEmailRequest,
SendEventEmailResponse,
StatusCode as PiStatusCode
} from '@ucap/pi';
import { RoomInfo } from '@ucap/protocol-room';
import {
SendRequest as SendEventRequest,
EventType
EventType,
MassTranslationEventJson,
TranslationEventJson
} from '@ucap/protocol-event';
import { LoginResponse } from '@ucap/protocol-authentication';
import { AuthResponse } from '@ucap/protocol-query';
import { User } from '@ucap/protocol-info';
import { ChattingActions } from '@ucap/ng-store-chat';
import { I18nService } from '@ucap/ng-i18n';
import { LogService } from '@ucap/ng-logger';
import { CommonApiService } from '@ucap/ng-api-common';
import { PiService } from '@ucap/ng-pi';
import { UserSelector } from '@ucap/ng-store-organization';
import {
LoginSelector,
ConfigurationSelector
ConfigurationSelector,
AuthorizationSelector
} from '@ucap/ng-store-authentication';
import { StickerFilesInfo, KEY_STICKER_HISTORY } from '@ucap/ng-core';
import { ChattingSelector, RoomSelector } from '@ucap/ng-store-chat';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
AlertDialogResult,
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
} from '@ucap/ng-ui';
import { I18nService } from '@ucap/ng-i18n';
import { LogService } from '@ucap/ng-logger';
import { MatDialog } from '@angular/material/dialog';
import {
TranslationSaveResponse,
MassTalkSaveRequest,
FileTalkSaveResponse,
FileTalkSaveRequest
} from '@ucap/api-common';
import { environment } from '@environments';
import { LocalStorageService } from '@ucap/ng-web-storage';
import { CommonApiService } from '@ucap/ng-api-common';
import { LoginSession } from '@app/models/login-session';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { StatusCode, FileUploadItem } from '@ucap/api';
import { AppFileService } from '@app/services/app-file.service';
import { VersionInfo2Response } from '@ucap/api-public';
import { FileUploadSelectorComponent } from '@app/ucap/chat/components/file-upload.selector.component';
import { FileUtil } from '@ucap/core';
import { AppChatService } from '@app/services/app-chat.service';
import { environment } from '@environments';
export enum SelectorType {
EMPTY = '',
STICKER = 'STICKER',
@ -69,9 +86,17 @@ export enum SelectorType {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FormSectionComponent implements OnInit, OnDestroy {
private roomIdSubject = new Subject<string>();
private ngOnDestroySubject: Subject<void> = new Subject();
@Input()
set roomId(roomId: string) {
this._roomId = roomId;
if (!!roomId && this.roomId !== roomId) {
this._roomId = roomId;
this.roomIdSubject.next(roomId);
this.initializeRoomData();
}
}
get roomId(): string {
return this._roomId;
@ -79,9 +104,17 @@ export class FormSectionComponent implements OnInit, OnDestroy {
// tslint:disable-next-line: variable-name
_roomId: string;
@Output()
changeTranslationSimpleview = new EventEmitter<boolean>();
@Output()
eventSendTrigger = new EventEmitter<any>();
versionInfo2Res: VersionInfo2Response;
loginSession: LoginSession;
loginRes: LoginResponse;
user: User;
authRes: AuthResponse;
currentRoomInfo: RoomInfo;
@ -91,8 +124,9 @@ export class FormSectionComponent implements OnInit, OnDestroy {
selectedSticker: StickerFilesInfo;
/** About Translation */
translationSimpleview = false;
translationPreview = false;
isTranslationProcess = false;
translationSimpleview = true;
translationPreview = true;
destLocale = 'en'; // default English :: en
translationPreviewInfo: {
previewInfo: TranslationSaveResponse | null;
@ -108,14 +142,13 @@ export class FormSectionComponent implements OnInit, OnDestroy {
SelectorType = SelectorType;
private ngOnDestroySubject: Subject<boolean>;
constructor(
private piService: PiService,
private appFileService: AppFileService,
private appChatService: AppChatService,
private store: Store<any>,
private i18nService: I18nService,
private dialog: MatDialog,
private localStorageService: LocalStorageService,
private logService: LogService,
private appAuthenticationService: AppAuthenticationService,
private commonApiService: CommonApiService,
@ -123,8 +156,6 @@ export class FormSectionComponent implements OnInit, OnDestroy {
) {}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
@ -133,56 +164,83 @@ export class FormSectionComponent implements OnInit, OnDestroy {
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user))
.subscribe((user) => {
this.user = user;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
});
this.appAuthenticationService
.getLoginSession$()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((loginSession) => (this.loginSession = loginSession));
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(
(state: any) => state.chat.room.rooms.entities as Dictionary<RoomInfo>
)
select(AuthorizationSelector.authResponse)
)
.subscribe((rooms) => {
if (!!this.roomId) {
this.currentRoomInfo = rooms[this.roomId];
this.changeDetectorRef.detectChanges();
}
.subscribe((authRes) => {
this.authRes = authRes;
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
if (!!this.roomIdSubject) {
this.roomIdSubject.next();
this.roomIdSubject.complete();
}
}
initializeRoomData() {
if (!!this.messageInput) {
this.messageInput.nativeElement.value = '';
}
this.selectorType = SelectorType.EMPTY;
this.translationSimpleview = false;
this.changeTranslationSimpleview.emit(false);
this.translationPreview = true;
this.destLocale = 'en'; // default English :: en
this.translationPreviewInfo = null;
this.store
.pipe(
takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)),
select(RoomSelector.room, this.roomId)
)
.subscribe((room) => {
this.currentRoomInfo = room;
this.changeDetectorRef.markForCheck();
});
this.loginSession = this.appAuthenticationService.getLoginSession();
}
/** About Selector */
onOpenSelector(type: SelectorType): void {
this.selectorType = type;
this.changeDetectorRef.detectChanges();
this.changeDetectorRef.markForCheck();
}
clearSelector(): void {
this.selectorType = SelectorType.EMPTY;
this.selectedSticker = null;
this.changeDetectorRef.detectChanges();
this.changeDetectorRef.markForCheck();
}
/** Element Handling */
focus(clearField: boolean = true): void {
if (!!this.messageInput) {
if (!!clearField) {
if (this.selectorType !== SelectorType.TRANSLATION) {
this.clearSelector();
}
this.messageInput.nativeElement.value = '';
this.clearSelector();
}
this.messageInput.nativeElement.focus();
}
@ -201,6 +259,7 @@ export class FormSectionComponent implements OnInit, OnDestroy {
} else {
// selector open
self.onOpenSelector(SelectorType.FILEUPLOAD);
self.changeDetectorRef.detectChanges();
// FileuploadItem Init. & FileSelector Init.
const fileUploadItems = FileUploadItem.fromFiles(fileList);
@ -213,6 +272,7 @@ export class FormSectionComponent implements OnInit, OnDestroy {
self.appChatService
.sendMessageOfAttachFile(
self.loginRes,
self.user,
self.loginSession.deviceType,
self.currentRoomInfo.roomId,
fileUploadItems
@ -226,10 +286,13 @@ export class FormSectionComponent implements OnInit, OnDestroy {
}
})
.catch((err) => {
alert(err);
self.clearSelector();
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onUploadComplete();
}
const msg = this.i18nService.t('common:file.errors.failToUpload');
alert(msg);
});
}
})
@ -240,13 +303,16 @@ export class FormSectionComponent implements OnInit, OnDestroy {
}
onKeydown(event: KeyboardEvent) {
if (event.key === 'PageUp' || event.key === 'PageDown') {
event.preventDefault();
return false;
} else if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
this.send();
}
// if (event.key === 'PageUp' || event.key === 'PageDown') {
// event.preventDefault();
// return false;
// } else if (event.key === 'Enter' && !event.shiftKey) {
// event.preventDefault();
// this.send();
// }
event.preventDefault();
event.stopPropagation();
this.send();
}
onSelectedSticker(stickerInfo: StickerFilesInfo) {
@ -254,9 +320,37 @@ export class FormSectionComponent implements OnInit, OnDestroy {
this.focus(false);
}
onPasteReply(event: ClipboardEvent) {
event.preventDefault();
// this.platform_readFromClipboard().then(async (data) => {
// console.log(data);
// console.log(data.html);
// console.log(data.image);
// console.log(data.imageDataUrl);
// console.log(data.text);
// console.log(JSON.stringify(event.clipboardData.items));
// if ((!!data.image && !!data.text) || !!data.image) {
// }
// });
// for (const item of items) {
// if (item.type.indexOf('image') === 0) {
// blob = item.getAsFile();
// }
// }
// console.log(JSON.stringify(event.clipboardData.items));
// this.nativeService.platform_readFromClipboard().then(async (data) => {
// console.log(data);
// console.log(JSON.stringify(event.clipboardData.items));
// if ((!!data.image && !!data.text) || !!data.image) {
// }
// });
}
async send() {
const roomId = this.currentRoomInfo.roomId;
const userSeq = this.loginRes.userSeq;
const userSeq = String(this.user.info.seq);
let message = this.messageInput.nativeElement.value;
if (!!message || message.trim().length > 0) {
@ -272,11 +366,11 @@ export class FormSectionComponent implements OnInit, OnDestroy {
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
panelClass: 'min-create-dialog',
data: {
title: this.i18nService.t('errors.label'),
message: this.i18nService.t('errors.inputChatMessage')
},
panelClass: ''
title: this.i18nService.t('chat:errors.label'),
message: this.i18nService.t('chat:errors.inputChatMessage')
}
});
return;
}
@ -301,14 +395,7 @@ export class FormSectionComponent implements OnInit, OnDestroy {
this.clearSelector();
} else {
this.appChatService.sendMessageOfTranslate(
this.loginRes,
this.loginSession.deviceType,
this.destLocale,
roomId,
message,
this.selectedSticker
);
this.sendMessageOfTranslate(message);
}
} else if (!!this.selectedSticker) {
/** CASE : Sticker */
@ -326,6 +413,7 @@ export class FormSectionComponent implements OnInit, OnDestroy {
/** CASE : MASS TEXT */
this.appChatService.sendMessageOfMassText(
this.loginRes,
this.user,
this.loginSession.deviceType,
roomId,
message
@ -335,6 +423,205 @@ export class FormSectionComponent implements OnInit, OnDestroy {
this.appChatService.sendMessageOfNormal(userSeq, roomId, message);
}
this.eventSendTrigger.emit(0);
this.focus();
}
sendMessageOfTranslate(message: string) {
if (!!this.isTranslationProcess) {
return;
}
this.isTranslationProcess = true;
this.commonApiService
.translationSave({
userSeq: String(this.user.info.seq),
token: this.loginRes.tokenString,
deviceType: this.loginSession.deviceType,
original: message,
roomId: this.roomId,
srcLocale: '',
destLocale: this.destLocale
} as TranslationSaveRequest)
.pipe(
take(1),
map((res) => {
if (res.statusCode === StatusCode.Success) {
let sentMessage = '';
let eventType = EventType.Translation;
let previewObject: TranslationEventJson | MassTranslationEventJson;
if (res.translationSeq > 0) {
// Mass Text Translation
previewObject = res;
sentMessage = res.returnJson;
eventType = EventType.MassTranslation;
} else {
// Normal Text Translation
previewObject = {
locale: this.destLocale,
original: message,
translation: res.translation,
stickername: '',
stickerfile: !!this.selectedSticker
? this.selectedSticker.index
: ''
};
sentMessage = JSON.stringify(previewObject);
eventType = EventType.Translation;
}
if (!!this.translationPreview) {
this.translationPreviewInfo = {
previewInfo: res,
translationType: eventType
};
this.changeDetectorRef.markForCheck();
} else {
this._sendTranslationMessage(sentMessage, eventType);
}
} else {
// error
this._translationError();
}
}),
catchError((error) => {
this._translationError();
return of(this.logService.error('error', error));
})
)
.subscribe(() => {
this.isTranslationProcess = false;
});
}
onChangeTranslationSimpleView(value: boolean) {
this.translationSimpleview = value;
this.changeTranslationSimpleview.emit(value);
}
onChangeTranslationPreView(value: boolean) {
this.translationPreview = value;
}
onChangeDestLocale(destLocale: string) {
this.destLocale = destLocale;
}
onSendTranslationMessage(params: {
previewInfo: TranslationSaveResponse | null;
translationType: EventType.Translation | EventType.MassTranslation;
}) {
let sentMessage = '';
if (params.translationType === EventType.MassTranslation) {
// Mass Text Translation
sentMessage = params.previewInfo.returnJson;
} else {
sentMessage = JSON.stringify({
locale: params.previewInfo.destLocale,
original: params.previewInfo.original,
translation: params.previewInfo.translation,
stickername: '',
stickerfile: !!this.selectedSticker ? this.selectedSticker.index : ''
});
}
this._sendTranslationMessage(sentMessage, params.translationType);
}
private _translationError() {
this.isTranslationProcess = false;
this.dialog.open<AlertDialogComponent, AlertDialogData, AlertDialogResult>(
AlertDialogComponent,
{
data: {
title: this.i18nService.t('chat:errors.label'),
message: this.i18nService.t('chat:errors.translateServerError')
},
panelClass: 'min-create-dialog'
}
);
}
private _sendTranslationMessage(sentMessage: string, eventType: EventType) {
this.appChatService.sendMessageOfTranslate(
String(this.user.info.seq),
this.roomId,
eventType,
sentMessage
);
this.translationPreviewInfo = undefined;
this.focus();
}
onSendEventEmail(type: SendEventMailType): void {
this.store
.pipe(take(1), select(ChattingSelector.eventList, this._roomId))
.subscribe((eventList) => {
if (!!eventList && eventList.length > 0) {
const self = this;
const dialogRef = this.dialog.open<
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
>(ConfirmDialogComponent, {
panelClass: 'min-create-dialog',
data: {
title: this.i18nService.t('chat:label.emailSend'),
html: this.i18nService.t(
type === SendEventMailType.ALL
? 'chat:dialog.confirmSendEventEmailAll'
: 'chat:dialog.confirmSendEventEmailMe'
)
}
});
dialogRef
.afterClosed()
.pipe(take(1))
.subscribe((result) => {
if (!!result && !!result.choice) {
const req: SendEventEmailRequest = {
userSeq: String(this.user.info.seq),
deviceType: this.loginSession.deviceType,
tokenKey: this.loginRes.tokenString,
roomSeq: this._roomId,
eventSeq: String(eventList[0].seq),
sendType: type
};
this.piService
.sendEventEmail(req)
.pipe(take(1))
.subscribe(
(res: SendEventEmailResponse) => {
if (res.intStatusCode === PiStatusCode.Success) {
this.dialog.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
panelClass: 'min-create-dialog',
data: {
title: this.i18nService.t('chat:label.emailSend'),
message: this.i18nService.t(
'chat:dialog.sendEventEmailSuccess'
)
}
});
self.selectorType = SelectorType.EMPTY;
} else {
this.logService.error(res.strErrorMessage);
}
},
(error) => {
this.logService.error(error);
},
() => {}
);
this.logService.debug(
this.i18nService.t('chat:dialog.sendEventEmailSuccess')
);
}
});
}
});
}
}

View File

@ -1,74 +1,163 @@
<mat-toolbar class="info-chat-toolbar">
<mat-toolbar-row>
<div class="profileImage">
<img
class="thumbnail"
ucapImage
[base]="versionInfo2Res?.profileRoot"
[path]="roomImage"
[default]="defaultProfileImage"
/>
<mat-toolbar-row class="info-chat-toolbar-content">
<div class="chat-room-profile">
<div class="profile-image">
<img
class="thumbnail"
ucapImage
[base]="versionInfo2Res?.profileRoot"
[path]="roomImage"
[default]="
currentRoomInfo?.roomType === RoomType.Multi
? defaultProfileImageMulti
: defaultProfileImage
"
/>
</div>
</div>
<span
>{{ roomName }}
<span
class="user-count"
<span class="chat-room-subject"
><mat-icon
*ngIf="!!currentRoomInfo?.isTimeRoom"
class="ico-timer-unit material-icons-outlined"
>timer</mat-icon
><span>{{ roomName }}</span>
<strong
class="user-count text-accent-color"
*ngIf="currentRoomInfo?.roomType === RoomType.Multi"
>
(<strong>{{ currentRoomInfo?.joinUserCount }}</strong
>)
</span>
{{ currentRoomInfo?.joinUserCount }}
</strong>
</span>
<span class="example-spacer"></span>
<div>
<div class="btn-chat-room-top">
<button
mat-mini-fab
mat-icon-button
color="primary"
aria-label="search"
matTooltip="{{ 'label.search' | ucapI18n }}"
matTooltip="{{ 'chat:label.search' | ucapI18n }}"
(click)="onOpenChatSearch()"
>
<mat-icon>search</mat-icon>
</button>
<button
mat-mini-fab
mat-icon-button
color="primary"
aria-label="alarm"
matTooltip="{{
(currentRoomInfo?.receiveAlarm
? 'label.notificationIsOn'
: 'label.notificationIsOff'
? 'chat:label.turnOnRoomAlert'
: 'chat:label.turnOffRoomAlert'
) | ucapI18n
}}"
(click)="onToggleAlarm(currentRoomInfo)"
>
<mat-icon *ngIf="currentRoomInfo?.receiveAlarm">notifications</mat-icon>
<mat-icon
*ngIf="currentRoomInfo?.receiveAlarm"
class="material-icons-outlined"
>notifications</mat-icon
>
<mat-icon *ngIf="!currentRoomInfo?.receiveAlarm"
<mat-icon
*ngIf="!currentRoomInfo?.receiveAlarm"
class="material-icons-outlined"
>notifications_off</mat-icon
>
</button>
<button
mat-mini-fab
<!-- <button
mat-icon-button
class="btn-icon-favorite"
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 }}"
matTooltip="{{ 'chat:label.favorite' | ucapI18n }}"
(click)="onRightDrawerToggle()"
>
<mat-icon>menu</mat-icon>
<mat-icon>star_outline</mat-icon>
</button> -->
<button
mat-icon-button
color="primary"
aria-label=""
[matMenuTriggerFor]="chatRoomMenu"
matTooltip="{{ 'chat:label.menu' | ucapI18n }}"
>
<mat-icon>more_horiz</mat-icon>
</button>
</div>
</mat-toolbar-row>
</mat-toolbar>
<mat-menu #chatRoomMenu="matMenu" class="chat-menu-box">
<div class="menu-title" (click)="$event.stopPropagation()">
{{ roomName }}
</div>
<mat-divider></mat-divider>
<div class="chat-menu-box-body">
<div class="data">
<div class="sub-title" (click)="$event.stopPropagation()">
<mat-icon class="material-icons-outlined">inbox</mat-icon
><span>{{ 'chat:label.data' | ucapI18n }}</span>
<!--<mat-slide-toggle labelPosition="before" class="simpleview">
</mat-slide-toggle>-->
</div>
<div class="btn">
<button mat-button (click)="onClickRoomMenu('ATTACH_IMAGE')">
{{ 'chat:label.image' | ucapI18n }}
</button>
<button mat-button (click)="onClickRoomMenu('ATTACH_VIDEO')">
{{ 'chat:label.video' | ucapI18n }}
</button>
<button mat-button (click)="onClickRoomMenu('ATTACH_FILE')">
{{ 'chat:label.attachFile' | ucapI18n }}
</button>
</div>
</div>
<!-- <button
mat-menu-item
*ngIf="getShowContextMenu('EVENT')"
(click)="onClickRoomMenu('EVENT')"
>
<mat-icon class="material-icons-outlined">calendar_today</mat-icon
>{{ 'chat:label.event' | ucapI18n }}
</button> -->
<button
mat-menu-item
*ngIf="getShowContextMenu('ROOM_USERS')"
(click)="onClickRoomMenu('ROOM_USERS')"
>
<mat-icon>person_outline</mat-icon
>{{ 'chat:label.showRoomUsers' | ucapI18n }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('CHANGE_ROOM_USERS')"
(click)="onClickRoomMenu('CHANGE_ROOM_USERS')"
>
<mat-icon class="material-icons-outlined">person_add</mat-icon
>{{ 'chat:label.addRoomUsers' | ucapI18n }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('ADD_GROUP')"
(click)="onClickRoomMenu('ADD_GROUP')"
>
<mat-icon class="material-icons-outlined">group_add</mat-icon
>{{ 'chat:label.addGroup' | ucapI18n }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('SETTING')"
(click)="onClickRoomMenu('SETTING')"
>
<mat-icon class="material-icons-outlined">settings</mat-icon
>{{ 'chat:label.roomSetting' | ucapI18n }}
</button>
<button
mat-menu-item
class="btn-exit"
*ngIf="getShowContextMenu('EXIT')"
(click)="onClickRoomMenu('EXIT')"
>
{{ 'chat:label.exitFromRoom' | ucapI18n }}
</button>
</div>
</mat-menu>

View File

@ -4,12 +4,73 @@
height: 50px;
min-height: 50px;
background-color: $white;
display: flex;
flex-direction: row;
justify-content: space-between;
align-content: center;
.mat-toolbar-row {
.info-chat-toolbar-content {
height: 50px;
min-height: 50px;
display: flex;
flex-direction: row;
align-content: center;
justify-content: space-between;
@include screen(xs) {
padding-right: 0;
}
.chat-room-profile {
align-self: center;
margin-right: 15px;
height: 38px;
flex: 0 0 0;
@include profile-avatar-default(
0,
14,
$warm-pink,
18px
); //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 , 모바일 아이콘 bg크기
.profile-image {
@include avatar-img(36px, 0); //아바타 크기, 왼쪽공간
background-color: #d1f6ff;
}
}
.chat-room-subject {
flex: 1 1 auto;
color: $gray-re3;
font-size: 14px;
font-weight: 600;
display: flex !important;
flex-direction: row;
align-items: center;
@include ellipsis-column(1);
.ico-timer-unit {
max-width: 18px !important;
height: 18px !important;
line-height: 18px;
font-size: 12px;
text-align: center;
border-radius: 50%;
margin-right: 6px;
flex: 0 0 24px;
}
span {
flex: 0 1 auto;
@include ellipsis(1);
//width: ;
}
strong {
flex: 1 1 auto;
display: flex;
flex-direction: row;
padding-left: 4px;
}
}
.btn-chat-room-top {
flex: 1 0 160px;
align-items: center;
display: flex;
flex-direction: inline-flex;
justify-content: flex-end;
//대화방 즐겨찾기 - 개발보류
.btn-icon-favorite {
display: none;
}
}
}
}

View File

@ -1,3 +1,6 @@
import { combineLatest, Subject, merge } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
Component,
OnInit,
@ -8,22 +11,26 @@ import {
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 { Store, select } from '@ngrx/store';
import { Dictionary } from '@ngrx/entity';
import { MatDialog } from '@angular/material/dialog';
import { LocaleCode } from '@ucap/core';
import { I18nService } from '@ucap/ng-i18n';
import { VersionInfo2Response } from '@ucap/api-public';
import { RoomInfo, RoomType, UpdateRequest } from '@ucap/protocol-room';
import { User } from '@ucap/protocol-info';
import { UserSelector } from '@ucap/ng-store-organization';
import { ConfigurationSelector } from '@ucap/ng-store-authentication';
import {
RoomSelector,
RoomActions,
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';
} from '@ucap/ng-store-chat';
import {
TranslatePipe as OrganizationTranslate,
@ -31,9 +38,8 @@ import {
} 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';
import { ChatDrawType } from '@app/pages/chat/types/chat-draw.type';
import { DrawInfo } from '@app/pages/chat/models/draw-info';
@Component({
selector: 'app-sections-chat-info',
@ -42,10 +48,17 @@ import { LocaleCode } from '@ucap/core';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class InfoSectionComponent implements OnInit, OnDestroy {
private roomIdSubject = new Subject<string>();
private ngOnDestroySubject: Subject<void> = new Subject();
@Input()
set roomId(roomId: string) {
this._roomId = roomId;
this.roomIdSubject.next(roomId);
this.initializeRoomData();
// request selected room
if (!!this.roomId) {
this.store.dispatch(
@ -55,8 +68,6 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
})
);
}
this.getRoomInfo();
}
get roomId(): string {
return this._roomId;
@ -68,31 +79,31 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
openChatSearch = new EventEmitter<void>();
@Output()
rightDrawerToggle = new EventEmitter<void>();
rightDrawerToggle = new EventEmitter<DrawInfo>();
versionInfo2Res: VersionInfo2Response;
loginRes: LoginResponse;
user: User;
defaultProfileImage: string;
defaultProfileImageMulti: string;
currentRoomInfo: RoomInfo;
roomList: RoomInfo[];
roomUsersDictionary: Dictionary<RoomUserMap>;
roomUsersShortDictionary: Dictionary<RoomUserShortMap>;
roomUsersMap: RoomUserMap;
roomUsersShortMap: 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 dialog: MatDialog,
private changeDetectorRef: ChangeDetectorRef
) {
// language setting
@ -105,12 +116,10 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
// default image setting
this.defaultProfileImage = this.appChatService.defaultProfileImage;
this.defaultProfileImageMulti = this.appChatService.defaultProfileImage;
this.defaultProfileImageMulti = this.appChatService.defaultProfileImageMulti;
}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
@ -119,76 +128,58 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
.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.next();
this.ngOnDestroySubject.complete();
}
if (!!this.roomIdSubject) {
this.roomIdSubject.next();
this.roomIdSubject.complete();
}
}
initializeRoomData() {
combineLatest([
this.store.pipe(select(UserSelector.user)),
this.store.pipe(select(RoomSelector.room, this.roomId)),
this.store.pipe(select(RoomSelector.roomUser, this.roomId)),
this.store.pipe(select(RoomSelector.roomUserShort, this.roomId))
])
.pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)))
.subscribe(([user, room, roomUsers, roomUsersShort]) => {
this.user = user;
this.currentRoomInfo = room;
this.roomUsersMap = roomUsers;
this.roomUsersShortMap = roomUsersShort;
this.getRoomInfo();
});
}
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.user,
this.currentRoomInfo,
this.roomUsersDictionary,
this.roomUsersShortDictionary
this.roomUsersMap,
this.roomUsersShortMap
);
this.roomImage = this.appChatService.getRoomProfileImage(
this.user,
this.currentRoomInfo,
this.loginRes,
this.roomUsersDictionary,
this.roomUsersShortDictionary
this.roomUsersMap,
this.roomUsersShortMap
);
}
this.changeDetectorRef.detectChanges();
this.changeDetectorRef.markForCheck();
}
onToggleAlarm(roomInfo: RoomInfo): void {
@ -209,11 +200,9 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
} as UpdateRequest
})
);
this.changeDetectorRef.detectChanges();
this.changeDetectorRef.markForCheck();
}
onOpenRoomUserList(roomInfo: RoomInfo): void {}
onOpenChatSearch(): void {
this.openChatSearch.emit();
}
@ -221,4 +210,64 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
onRightDrawerToggle(): void {
this.rightDrawerToggle.emit();
}
onClickRoomMenu(type: string): void {
switch (type) {
case 'ATTACH_IMAGE':
this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.AttachImage });
break;
case 'ATTACH_VIDEO':
this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.AttachVideo });
break;
case 'ATTACH_FILE':
this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.AttachFile });
break;
case 'ADD_GROUP':
this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.AddGroup });
break;
case 'ROOM_USERS':
this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.RoomUsers });
break;
case 'CHANGE_ROOM_USERS':
this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.Invite });
break;
case 'SETTING':
this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.Setting });
break;
case 'EXIT':
this.appChatService.exitRoomDialog(this.currentRoomInfo);
break;
}
}
getShowContextMenu(menuType: string) {
if (
['EVENT', 'ROOM_USERS', 'CHANGE_ROOM_USERS', 'ADD_GROUP', 'SETTING'].some(
(v) => v === menuType
)
) {
if (
!this.currentRoomInfo ||
!this.currentRoomInfo.roomType ||
[RoomType.Mytalk, RoomType.Allim, RoomType.Bot, RoomType.Link].some(
(v) => v === this.currentRoomInfo.roomType
)
) {
return false;
}
}
// else if (['CHAT_EXPORT'].some((v) => v === menuType)) {
// if (
// !this.currentRoomInfo ||
// !this.currentRoomInfo.roomType ||
// [RoomType.Allim, RoomType.Bot, RoomType.Link].some(
// (v) => v === this.currentRoomInfo.roomType
// )
// ) {
// return false;
// }
// }
return true;
}
}

View File

@ -1,30 +1,12 @@
<div
*ngIf="!!searchObj && !searchObj.isShowSearch"
fxFlexFill
class="list-container"
>
<div fxFlexFill class="list-container">
<app-chat-room-expansion
[searchObj]="searchObj"
[currentRoomId]="currentRoomId"
[checkable]="checkable"
[selectedRoomList]="selectedRoomList"
(toggleItem)="onToggleItem($event)"
(openChatRoom)="onOpenChatRoom($event)"
(searchResultList)="onSearchResultList($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>

View File

@ -1,2 +1,4 @@
.list-container {
height: calc(100% - 90px) !important;
min-height: auto !important;
}

View File

@ -1,5 +1,5 @@
import { Subject, combineLatest } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
Component,
@ -9,91 +9,24 @@ import {
ChangeDetectorRef,
Input,
EventEmitter,
Output,
NgZone
Output
} 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)
}
}
import { RoomInfo } from '@ucap/protocol-room';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { QueryParams } from '@app/pages/chat/types/params.type';
import { SearchInfo } from '@app/pages/chat/models/search-info';
@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;
searchObj: SearchInfo;
@Input()
checkable = false;
@ -110,277 +43,53 @@ export class ListSectionComponent implements OnInit, OnDestroy {
roomInfo: RoomInfo;
}>();
@Output()
delRoom = new EventEmitter<RoomInfo>();
versionInfo2Res: VersionInfo2Response;
loginRes: LoginResponse;
defaultProfileImage: string;
defaultProfileImageMulti: string;
currentRoomId: string;
roomList: RoomInfo[];
roomUsersDictionary: Dictionary<RoomUserMap>;
roomUsersShortDictionary: Dictionary<RoomUserShortMap>;
searchRoomList: RoomInfo[];
roomGroup: { division: string; roomList: RoomInfo[] }[];
organizationTranslate: OrganizationTranslate;
private ngOnDestroySubject: Subject<boolean>;
private ngOnDestroySubject: Subject<void> = new Subject();
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 activatedRoute: ActivatedRoute,
private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone,
private logService: LogService
) {
// default image setting
this.defaultProfileImage = this.appChatService.defaultProfileImage;
this.defaultProfileImageMulti = this.appChatService.defaultProfileImage;
}
) {}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
// 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');
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;
});
combineLatest([
this.store.pipe(select(RoomSelector.rooms)),
this.store.pipe(select(RoomSelector.standbyRooms))
])
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe(([rooms, standbyRooms]) => {
rooms = (rooms || []).filter((info) => {
return (
info.isJoinRoom &&
!standbyRooms.find((standbyRoom) => standbyRoom === info.roomId)
);
});
this.roomList = rooms;
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();
.subscribe((params: Params) => {
const seqParam = params[QueryParams.ROOM_ID];
this.currentRoomId = !!seqParam ? seqParam : undefined;
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
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' }
}
],
this.router.navigate(
[
'chat',
{
queryParams: { roomId: roomInfo.roomId }
outlets: { content: 'chatroom' }
}
);
});
}
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('dialog.title.exitFromRoom'),
html: this.i18nService.t('dialog.confirmExitFromRoom')
],
{
queryParams: { roomId: roomInfo.roomId }
}
});
);
}
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();
onSearchResultList(searchResultList: RoomInfo[]) {
this.searchResultList.emit(searchResultList);
}
}

View File

@ -1,31 +0,0 @@
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 {}
}

Some files were not shown because too many files have changed in this diff Show More