Compare commits

...

5 Commits

Author SHA1 Message Date
Park Byung Eun
eded98a6cf sync 2020-08-10 14:05:32 +09:00
Park Byung Eun
98ff58c39e 0710 sync 2020-07-11 21:09:04 +09:00
Park Byung Eun
92da6c71ce 0527 sync 2020-05-28 21:52:40 +09:00
Park Byung Eun
23bbfc4b63 0524 sync 2020-05-24 13:46:38 +09:00
Park Byung Eun
4f42fc5c2d sync 0428 2020-04-28 22:25:53 +09:00
596 changed files with 49270 additions and 5337 deletions

View File

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

View File

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

View File

@ -1,13 +1,25 @@
const path = require('path'); const path = require('path');
module.exports = (config, options) => { 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;
if (RENDERER) {
config.target = 'electron-renderer';
} else {
config.target = 'web'; config.target = 'web';
config.node = { config.node = {
global: true, global: true,
fs: 'empty' fs: 'empty'
}; };
}
config.resolve.alias = {
...config.resolve.alias,
'@ucap/lg-scss': path.resolve(__dirname, '..', 'src/assets/scss')
};
return config; return config;
}; };

7039
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,52 +5,65 @@
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"start:hmr": "ng serve --configuration hmr", "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": "ng build",
"build:production": "ng build --prod", "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", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e" "e2e": "ng e2e"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~9.0.6", "@angular/animations": "^9.1.11",
"@angular/cdk": "^9.1.3", "@angular/cdk": "^9.2.4",
"@angular/common": "~9.0.6", "@angular/common": "^9.1.11",
"@angular/compiler": "~9.0.6", "@angular/compiler": "^9.1.11",
"@angular/core": "~9.0.6", "@angular/core": "^9.1.11",
"@angular/flex-layout": "^9.0.0-beta.29", "@angular/flex-layout": "^9.0.0-beta.31",
"@angular/forms": "~9.0.6", "@angular/forms": "^9.1.11",
"@angular/material": "^9.1.3", "@angular/material": "^9.2.4",
"@angular/material-moment-adapter": "^9.1.3", "@angular/material-moment-adapter": "^9.2.4",
"@angular/platform-browser": "~9.0.6", "@angular/platform-browser": "^9.1.11",
"@angular/platform-browser-dynamic": "~9.0.6", "@angular/platform-browser-dynamic": "^9.1.11",
"@angular/router": "~9.0.6", "@angular/router": "^9.1.11",
"@ngrx/effects": "^9.0.0", "@ngrx/effects": "^9.2.0",
"@ngrx/entity": "^9.0.0", "@ngrx/entity": "^9.2.0",
"@ngrx/router-store": "^9.0.0", "@ngrx/router-store": "^9.2.0",
"@ngrx/store": "^9.0.0", "@ngrx/store": "^9.2.0",
"@ucap/api": "~0.0.2", "@ucap/api": "~0.0.5",
"@ucap/api-common": "~0.0.3", "@ucap/api-common": "~0.0.12",
"@ucap/api-external": "~0.0.5", "@ucap/api-contact": "~0.0.5",
"@ucap/api-message": "~0.0.3", "@ucap/api-external": "~0.0.8",
"@ucap/api-prompt": "~0.0.3", "@ucap/api-message": "~0.0.7",
"@ucap/api-public": "~0.0.4", "@ucap/api-prompt": "~0.0.6",
"@ucap/core": "~0.0.7", "@ucap/api-public": "~0.0.6",
"@ucap/logger": "~0.0.12", "@ucap/api-webex": "~0.0.2",
"@ucap/native": "~0.0.6", "@ucap/core": "~0.0.15",
"@ucap/native-browser": "~0.0.5", "@ucap/domain-authentication": "~0.0.4",
"@ucap/domain-authorization": "~0.0.3",
"@ucap/domain-call": "~0.0.4",
"@ucap/domain-chat": "~0.0.3",
"@ucap/domain-common": "~0.0.1",
"@ucap/domain-group": "~0.0.2",
"@ucap/domain-message": "~0.0.1",
"@ucap/domain-organization": "~0.0.1",
"@ucap/domain-status": "~0.0.1",
"@ucap/electron-native": "~0.0.19",
"@ucap/i18n": "~0.0.2",
"@ucap/logger": "~0.0.14",
"@ucap/native": "~0.0.27",
"@ucap/ng-api-common": "~0.0.1", "@ucap/ng-api-common": "~0.0.1",
"@ucap/ng-api-external": "~0.0.1", "@ucap/ng-api-external": "~0.0.1",
"@ucap/ng-api-message": "~0.0.1", "@ucap/ng-api-message": "~0.0.1",
"@ucap/ng-api-prompt": "~0.0.1", "@ucap/ng-api-prompt": "~0.0.1",
"@ucap/ng-api-public": "~0.0.1", "@ucap/ng-api-public": "~0.0.1",
"@ucap/ng-core": "~0.0.1", "@ucap/ng-api-webex": "~0.0.1",
"@ucap/ng-api-contact": "~0.0.2",
"@ucap/ng-core": "~0.0.9",
"@ucap/ng-logger": "~0.0.2", "@ucap/ng-logger": "~0.0.2",
"@ucap/ng-i18n": "~0.0.6", "@ucap/ng-i18n": "~0.0.8",
"@ucap/ng-native": "~0.0.1", "@ucap/ng-native": "~0.0.12",
"@ucap/ng-native-browser": "~0.0.1",
"@ucap/ng-pi": "~0.0.1", "@ucap/ng-pi": "~0.0.1",
"@ucap/ng-protocol": "~0.0.3", "@ucap/ng-protocol": "~0.0.3",
"@ucap/ng-protocol-authentication": "~0.0.3", "@ucap/ng-protocol-authentication": "~0.0.3",
@ -68,36 +81,41 @@
"@ucap/ng-protocol-status": "~0.0.3", "@ucap/ng-protocol-status": "~0.0.3",
"@ucap/ng-protocol-sync": "~0.0.3", "@ucap/ng-protocol-sync": "~0.0.3",
"@ucap/ng-protocol-umg": "~0.0.3", "@ucap/ng-protocol-umg": "~0.0.3",
"@ucap/ng-store-authentication": "~0.0.10", "@ucap/ng-store-authentication": "~0.0.17",
"@ucap/ng-store-chat": "~0.0.5", "@ucap/ng-store-chat": "~0.0.74",
"@ucap/ng-store-group": "~0.0.6", "@ucap/ng-store-group": "~0.0.25",
"@ucap/ng-store-organization": "~0.0.4", "@ucap/ng-store-organization": "~0.0.23",
"@ucap/ng-store-call": "~0.0.7",
"@ucap/ng-web-socket": "~0.0.2", "@ucap/ng-web-socket": "~0.0.2",
"@ucap/ng-web-storage": "~0.0.3", "@ucap/ng-web-storage": "~0.0.3",
"@ucap/ng-ui": "~0.0.4", "@ucap/ng-ui": "~0.0.108",
"@ucap/ng-ui-organization": "~0.0.2", "@ucap/ng-ui-organization": "~0.0.222",
"@ucap/ng-ui-authentication": "~0.0.16", "@ucap/ng-ui-authentication": "~0.0.32",
"@ucap/ng-ui-group": "~0.0.3", "@ucap/ng-ui-group": "~0.0.87",
"@ucap/ng-ui-chat": "~0.0.80",
"@ucap/ng-ui-call": "~0.0.15",
"@ucap/ng-ui-material": "~0.0.4",
"@ucap/ng-ui-skin-default": "~0.0.1", "@ucap/ng-ui-skin-default": "~0.0.1",
"@ucap/pi": "~0.0.5", "@ucap/pi": "~0.0.9",
"@ucap/protocol": "~0.0.17", "@ucap/protocol": "~0.0.20",
"@ucap/protocol-authentication": "~0.0.5", "@ucap/protocol-authentication": "~0.0.7",
"@ucap/protocol-buddy": "~0.0.5", "@ucap/protocol-buddy": "~0.0.6",
"@ucap/protocol-event": "~0.0.5", "@ucap/protocol-event": "~0.0.11",
"@ucap/protocol-file": "~0.0.4", "@ucap/protocol-file": "~0.0.7",
"@ucap/protocol-group": "~0.0.5", "@ucap/protocol-group": "~0.0.6",
"@ucap/protocol-info": "~0.0.5", "@ucap/protocol-info": "~0.0.10",
"@ucap/protocol-inner": "~0.0.4", "@ucap/protocol-inner": "~0.0.5",
"@ucap/protocol-option": "~0.0.7", "@ucap/protocol-option": "~0.0.9",
"@ucap/protocol-ping": "~0.0.6", "@ucap/protocol-ping": "~0.0.7",
"@ucap/protocol-query": "~0.0.5", "@ucap/protocol-query": "~0.0.8",
"@ucap/protocol-room": "~0.0.5", "@ucap/protocol-room": "~0.0.9",
"@ucap/protocol-service": "~0.0.4", "@ucap/protocol-service": "~0.0.5",
"@ucap/protocol-status": "~0.0.5", "@ucap/protocol-status": "~0.0.6",
"@ucap/protocol-sync": "~0.0.4", "@ucap/protocol-sync": "~0.0.8",
"@ucap/protocol-umg": "~0.0.5", "@ucap/protocol-umg": "~0.0.5",
"@ucap/ui-scss": "~0.0.5",
"@ucap/web-socket": "~0.0.10", "@ucap/web-socket": "~0.0.10",
"@ucap/web-storage": "~0.0.5", "@ucap/web-storage": "~0.0.9",
"autolinker": "^3.13.0", "autolinker": "^3.13.0",
"axios": "^0.19.2", "axios": "^0.19.2",
"classlist.js": "^1.1.20150312", "classlist.js": "^1.1.20150312",
@ -106,13 +124,13 @@
"file-type": "^14.1.4", "file-type": "^14.1.4",
"i18next": "^19.3.3", "i18next": "^19.3.3",
"i18next-browser-languagedetector": "^4.0.2", "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", "i18next-xhr-backend": "^3.2.2",
"libphonenumber-js": "^1.7.47", "libphonenumber-js": "^1.7.47",
"memoize-one": "^5.1.1",
"moment": "^2.24.0", "moment": "^2.24.0",
"moment-timezone": "^0.5.28", "moment-timezone": "^0.5.28",
"ngx-perfect-scrollbar": "^9.0.0", "ngx-perfect-scrollbar": "^9.0.0",
"ngx-virtual-scroller": "^4.0.3",
"pino": "^6.0.0", "pino": "^6.0.0",
"queueing-subject": "^0.3.4", "queueing-subject": "^0.3.4",
"rxjs": "~6.5.4", "rxjs": "~6.5.4",
@ -123,16 +141,16 @@
"devDependencies": { "devDependencies": {
"@angular-builders/custom-webpack": "^9.0.0", "@angular-builders/custom-webpack": "^9.0.0",
"@angular-devkit/build-angular": "~0.900.6", "@angular-devkit/build-angular": "~0.900.6",
"@angular/cli": "~9.0.6", "@angular/cli": "^9.1.9",
"@angular/compiler-cli": "~9.0.6", "@angular/compiler-cli": "^9.1.11",
"@angular/language-service": "~9.0.6", "@angular/language-service": "^9.1.11",
"@angularclass/hmr": "^2.1.3", "@angularclass/hmr": "^2.1.3",
"@ngrx/store-devtools": "^9.0.0", "@ngrx/store-devtools": "^9.0.0",
"@types/i18next-node-fs-backend": "^2.1.0",
"@types/jasmine": "~3.5.0", "@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3", "@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",
"codelyzer": "^5.1.2", "codelyzer": "^5.1.2",
"cross-env": "^7.0.2",
"fs-extra": "^9.0.0", "fs-extra": "^9.0.0",
"jasmine-core": "~3.5.0", "jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~4.2.1", "jasmine-spec-reporter": "~4.2.1",
@ -145,6 +163,7 @@
"ts-node": "~8.3.0", "ts-node": "~8.3.0",
"tslint": "~5.18.0", "tslint": "~5.18.0",
"typescript": "~3.7.5", "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,12 +12,32 @@ import { AppAuthenticationGuard } from './guards/app-authentication.guard';
import { AppSessionResolver } from './resolvers/app-session.resolver'; import { AppSessionResolver } from './resolvers/app-session.resolver';
import { AppAuthenticationService } from './services/app-authentication.service'; import { AppAuthenticationService } from './services/app-authentication.service';
import { AppNotificationService } from './services/app-notification.service';
import { AppNativeService } from './services/app-native.service'; import { AppNativeService } from './services/app-native.service';
import { AppService } from './services/app.service'; import { AppService } from './services/app.service';
import { AppCallService } from './services/app-call.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';
import { AppUiService } from './services/app-ui.service';
import { AppOrganizationService } from './services/app-organization.service';
const GUARDS = [AppAuthenticationGuard]; const GUARDS = [AppAuthenticationGuard];
const RESOLVERS = [AppSessionResolver]; const RESOLVERS = [AppSessionResolver];
const SERVICES = [AppService, AppAuthenticationService, AppNativeService]; const SERVICES = [
AppService,
AppAuthenticationService,
AppNativeService,
AppFileService,
AppCallService,
AppChatService,
AppNotificationService,
AppOrganizationService,
AppGroupService,
AppAccountService,
AppUiService
];
const axiosFactory = () => { const axiosFactory = () => {
const i = axios.create(); const i = axios.create();
@ -27,6 +47,9 @@ const axiosFactory = () => {
return i; return i;
}; };
const nativeServiceFactory = (nativeService: any) =>
new environment.productConfig.nativeServiceClass(nativeService);
const appInit = (appService: AppService) => { const appInit = (appService: AppService) => {
return () => appService.initialize(); return () => appService.initialize();
}; };
@ -43,7 +66,7 @@ const appInit = (appService: AppService) => {
}, },
{ {
provide: UCAP_NATIVE_SERVICE, provide: UCAP_NATIVE_SERVICE,
useClass: environment.productConfig.nativeServiceClass, useFactory: nativeServiceFactory,
deps: [AXIOS_INSTANCE], deps: [AXIOS_INSTANCE],
multi: false multi: false
}, },

View File

@ -6,11 +6,12 @@ import { NoNaviLayoutComponent } from '@app/layouts/components/no-navi.layout.co
import { AppAuthenticationGuard } from '@app/guards/app-authentication.guard'; import { AppAuthenticationGuard } from '@app/guards/app-authentication.guard';
import { AppSessionResolver } from './resolvers/app-session.resolver'; import { AppSessionResolver } from './resolvers/app-session.resolver';
import { NavigationType } from './types';
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
redirectTo: '/group/(content:index)', redirectTo: `/${NavigationType.Group}/(content:index)`,
pathMatch: 'full' pathMatch: 'full'
}, },
{ {
@ -38,35 +39,35 @@ const routes: Routes = [
}, },
children: [ children: [
{ {
path: 'organization', path: NavigationType.Organization,
loadChildren: () => loadChildren: () =>
import('./pages/organization/organization.page.module').then( import('./pages/organization/organization.page.module').then(
(m) => m.AppOrganizationPageModule (m) => m.AppOrganizationPageModule
) )
}, },
{ {
path: 'group', path: NavigationType.Group,
loadChildren: () => loadChildren: () =>
import('./pages/group/group.page.module').then( import('./pages/group/group.page.module').then(
(m) => m.AppGroupPageModule (m) => m.AppGroupPageModule
) )
}, },
{ {
path: 'chat', path: NavigationType.Chat,
loadChildren: () => loadChildren: () =>
import('./pages/chat/chat.page.module').then( import('./pages/chat/chat.page.module').then(
(m) => m.AppChatPageModule (m) => m.AppChatPageModule
) )
}, },
{ {
path: 'call', path: NavigationType.Call,
loadChildren: () => loadChildren: () =>
import('./pages/call/call.page.module').then( import('./pages/call/call.page.module').then(
(m) => m.AppCallPageModule (m) => m.AppCallPageModule
) )
}, },
{ {
path: 'message', path: NavigationType.Message,
loadChildren: () => loadChildren: () =>
import('./pages/message/message.page.module').then( import('./pages/message/message.page.module').then(
(m) => m.AppMessagePageModule (m) => m.AppMessagePageModule
@ -81,7 +82,9 @@ const routes: Routes = [
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes)], imports: [
RouterModule.forRoot(routes, { useHash: true, enableTracing: false })
],
exports: [RouterModule] exports: [RouterModule]
}) })
export class AppRoutingModule {} export class AppRoutingModule {}

View File

@ -1,2 +1,3 @@
<app-layouts-top-bar></app-layouts-top-bar> <div class="app-container">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div>

View File

@ -1,4 +1,9 @@
:host { :host {
width: 100%; width: 100%;
height: auto !important; height: 100%;
.app-container {
width: 100%;
height: 100%;
}
} }

View File

@ -1,10 +1,27 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { fromEvent, interval, Subject } from 'rxjs';
import { debounce, takeUntil } from 'rxjs/operators';
import {
Component,
OnDestroy,
OnInit,
AfterViewInit,
Renderer2,
Inject
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { NativeService } from '@ucap/native';
import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native';
import { AppActions } from '@app/store/actions'; import { AppActions } from '@app/store/actions';
import { fromEvent, interval, Subscription } from 'rxjs';
import { debounce } from 'rxjs/operators'; import { AppAuthenticationService } from './services/app-authentication.service';
import { AnimationBuilder, style, animate } from '@angular/animations';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -12,17 +29,40 @@ import { debounce } from 'rxjs/operators';
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent implements OnInit, OnDestroy { export class AppComponent implements OnInit, OnDestroy {
private resizeWindowSubscription: Subscription; private ngOnDestroySubject: Subject<void> = new Subject();
constructor(private store: Store<any>) { constructor(
this.resizeWindowSubscription = fromEvent(window, 'resize') private router: Router,
.pipe(debounce(() => interval(100))) private renderer2: Renderer2,
private store: Store<any>,
private appAuthenticationService: AppAuthenticationService,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
@Inject(DOCUMENT) private _document: any,
private _animationBuilder: AnimationBuilder
) {
fromEvent(window, 'resize')
.pipe(
takeUntil(this.ngOnDestroySubject),
debounce(() => interval(100))
)
.subscribe((event: any) => { .subscribe((event: any) => {
this.dispatchWindowSize({ this.dispatchWindowSize({
width: event.target.innerWidth, width: event.target.innerWidth,
height: event.target.innerHeight height: event.target.innerHeight
}); });
}); });
fromEvent(window, 'unload')
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((event: any) => {
this.nativeService.app_postDestroy();
});
// fromEvent(window, 'beforeunload')
// .pipe(takeUntil(this.ngOnDestroySubject))
// .subscribe((event: any) => {
// this.appAuthenticationService.logout();
// });
} }
ngOnInit(): void { ngOnInit(): void {
@ -30,11 +70,59 @@ export class AppComponent implements OnInit, OnDestroy {
width: window.innerWidth, width: window.innerWidth,
height: window.innerHeight height: window.innerHeight
}); });
this.nativeService.app_postInit().then((info) => {
if (!info) {
return;
}
if (!!info.initUrl) {
this.router.navigateByUrl(info.initUrl);
}
});
const splash = this._document.body.querySelector('#ucap-lg-web-splash');
const player = this._animationBuilder
.build([
style({ opacity: '1' }),
animate(
'400ms ease',
style({
opacity: '0',
zIndex: '-10'
})
)
])
.create(splash);
setTimeout(() => {
player.play();
}, 0);
// const preloader = this.renderer2.selectRootElement(
// '#ucap-lg-web-preloader'
// );
// const fadeEffect = setInterval(() => {
// if (!preloader.style.opacity) {
// this.renderer2.setStyle(preloader, 'opacity', 1);
// }
// if (preloader.style.opacity > 0) {
// this.renderer2.setStyle(
// preloader,
// 'opacity',
// preloader.style.opacity - 0.1
// );
// } else {
// this.renderer2.setStyle(preloader, 'display', 'none');
// clearInterval(fadeEffect);
// }
// }, 200);
// this.renderer2.setStyle(preloader, 'display', 'none');
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (!!this.resizeWindowSubscription) { if (!!this.ngOnDestroySubject) {
this.resizeWindowSubscription.unsubscribe(); this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
} }
} }

View File

@ -1,7 +1,9 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core'; import { FlexLayoutModule } from '@angular/flex-layout';
import { StoreModule } from '@ngrx/store'; import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects'; import { EffectsModule } from '@ngrx/effects';
@ -13,8 +15,10 @@ import { LoggerModule } from '@ucap/ng-logger';
import { CommonApiModule } from '@ucap/ng-api-common'; import { CommonApiModule } from '@ucap/ng-api-common';
import { PublicApiModule } from '@ucap/ng-api-public'; import { PublicApiModule } from '@ucap/ng-api-public';
import { ExternalApiModule } from '@ucap/ng-api-external'; import { ExternalApiModule } from '@ucap/ng-api-external';
import { ContactApiModule } from '@ucap/ng-api-contact';
import { MessageApiModule } from '@ucap/ng-api-message'; import { MessageApiModule } from '@ucap/ng-api-message';
import { PromptApiModule } from '@ucap/ng-api-prompt'; import { PromptApiModule } from '@ucap/ng-api-prompt';
import { WebexApiModule } from '@ucap/ng-api-webex';
import { PiModule } from '@ucap/ng-pi'; import { PiModule } from '@ucap/ng-pi';
@ -44,6 +48,7 @@ import { OrganizationStoreModule } from '@ucap/ng-store-organization';
import { AuthenticationStoreModule } from '@ucap/ng-store-authentication'; import { AuthenticationStoreModule } from '@ucap/ng-store-authentication';
import { GroupStoreModule } from '@ucap/ng-store-group'; import { GroupStoreModule } from '@ucap/ng-store-group';
import { ChatStoreModule } from '@ucap/ng-store-chat'; import { ChatStoreModule } from '@ucap/ng-store-chat';
import { CallStoreModule } from '@ucap/ng-store-call';
import { OrganizationUiModule } from '@ucap/ng-ui-organization'; import { OrganizationUiModule } from '@ucap/ng-ui-organization';
@ -57,7 +62,10 @@ import { effects } from '@app/store/effects';
import { ROOT_REDUCERS } from '@app/store/reducers'; import { ROOT_REDUCERS } from '@app/store/reducers';
import { metaReducers } from '@app/store/state'; import { metaReducers } from '@app/store/state';
import { AppAccountDialogModule } from '@app/dialogs/account/account.dialog.module';
import { environment } from '@environments'; import { environment } from '@environments';
import { MatDialogModule } from '@angular/material/dialog';
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
@ -65,49 +73,7 @@ import { environment } from '@environments';
BrowserModule, BrowserModule,
BrowserAnimationsModule, BrowserAnimationsModule,
LoggerModule.forRoot({}), FlexLayoutModule,
CommonApiModule.forRoot(environment.commonApiModuleConfig),
PublicApiModule.forRoot(environment.publicApiModuleConfig),
ExternalApiModule.forRoot(environment.externalApiModuleConfig),
MessageApiModule.forRoot(environment.messageApiModuleConfig),
PromptApiModule.forRoot(environment.promptApiModuleConfig),
PiModule.forRoot(environment.piModuleConfig),
ProtocolModule.forRoot(environment.protocolModuleConfig),
AuthenticationProtocolModule.forRoot({}),
BuddyProtocolModule.forRoot({}),
EventProtocolModule.forRoot({}),
FileProtocolModule.forRoot({}),
GroupProtocolModule.forRoot({}),
InfoProtocolModule.forRoot({}),
InnerProtocolModule.forRoot({}),
OptionProtocolModule.forRoot({}),
PingProtocolModule.forRoot(environment.pingProtocolModuleConfig),
QueryProtocolModule.forRoot({}),
RoomProtocolModule.forRoot({}),
ServiceProtocolModule.forRoot({}),
StatusProtocolModule.forRoot({}),
SyncProtocolModule.forRoot({}),
UmgProtocolModule.forRoot({}),
WebSocketModule.forRoot(environment.webSocketModuleConfig),
WebStorageModule.forRoot({}),
I18nModule.forRoot({}),
OrganizationStoreModule.forRoot({}),
AuthenticationStoreModule.forRoot({}),
GroupStoreModule.forRoot({ useMyDeptGroup: true, fixedGroupSeqs: [] }),
ChatStoreModule.forRoot({}),
OrganizationUiModule.forRoot({}),
AppProviderModule,
AppRoutingModule,
AppLayoutsModule,
/** /**
* StoreModule.forRoot is imported once in the root module, accepting a reducer * StoreModule.forRoot is imported once in the root module, accepting a reducer
@ -157,7 +123,66 @@ import { environment } from '@environments';
* *
* See: https://ngrx.io/guide/effects#registering-root-effects * See: https://ngrx.io/guide/effects#registering-root-effects
*/ */
EffectsModule.forRoot([...effects]) EffectsModule.forRoot([...effects]),
MatDialogModule,
LoggerModule.forRoot({}),
CommonApiModule.forRoot(environment.commonApiModuleConfig),
PublicApiModule.forRoot(environment.publicApiModuleConfig),
ExternalApiModule.forRoot(environment.externalApiModuleConfig),
ContactApiModule.forRoot(environment.contactApiModuleConfig),
MessageApiModule.forRoot(environment.messageApiModuleConfig),
PromptApiModule.forRoot(environment.promptApiModuleConfig),
WebexApiModule.forRoot(environment.webexApiModuleConfig),
PiModule.forRoot(environment.piModuleConfig),
ProtocolModule.forRoot(environment.protocolModuleConfig),
AuthenticationProtocolModule.forRoot({}),
BuddyProtocolModule.forRoot({}),
EventProtocolModule.forRoot({}),
FileProtocolModule.forRoot({}),
GroupProtocolModule.forRoot({}),
InfoProtocolModule.forRoot({}),
InnerProtocolModule.forRoot({}),
OptionProtocolModule.forRoot({}),
PingProtocolModule.forRoot(environment.pingProtocolModuleConfig),
QueryProtocolModule.forRoot({}),
RoomProtocolModule.forRoot({}),
ServiceProtocolModule.forRoot({}),
StatusProtocolModule.forRoot({}),
SyncProtocolModule.forRoot({}),
UmgProtocolModule.forRoot({}),
WebSocketModule.forRoot(environment.webSocketModuleConfig),
WebStorageModule.forRoot({}),
I18nModule.forRoot({}),
OrganizationStoreModule.forRoot({}),
AuthenticationStoreModule.forRoot({}),
GroupStoreModule.forRoot({ useMyDeptGroup: true, fixedGroupSeqs: [] }),
ChatStoreModule.forRoot({
eventRequestInitCount:
environment.productConfig.chat.eventRequestInitCount,
eventRequestDefaultCount:
environment.productConfig.chat.eventRequestDefaultCount
}),
CallStoreModule.forRoot({
historyRequestDefaultCount:
environment.productConfig.call.historyRequestDefaultCount
}),
OrganizationUiModule.forRoot({}),
AppProviderModule,
AppRoutingModule,
AppLayoutsModule,
AppAccountDialogModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -16,164 +16,40 @@ $typography: mat-typography-config(
// Setup the typography // Setup the typography
@include angular-material-typography($typography); @include angular-material-typography($typography);
@mixin components-theme($theme) {
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Define the default theme // @ Define theme --LG RED
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// 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, 600);
$lgRed-app-accent: mat-palette($ucap-color-accent, 700);
// Define the primary, accent and warn palettes // The warn palette is optional (defaults to red).
$default-primary-palette: mat-palette($mat-indigo); $lgRed-app-warn: mat-palette($ucap-color-warn, 500);
$default-accent-palette: mat-palette($mat-light-blue, 600, 400, 700);
$default-warn-palette: mat-palette($mat-red);
// Create the Material theme object // Create the theme object (a Sass map containing all of the palettes).
$theme: mat-light-theme( $lgRed-app-theme: mat-light-theme(
$default-primary-palette, $lgRed-app-primary,
$default-accent-palette, $lgRed-app-accent,
$default-warn-palette $lgRed-app-warn
); );
// Add ".theme-default" class to the body to activate this theme. // Include theme styles for core and each component used in your app.
// Class name must start with "theme-" !!! // Alternatively, you can import and @include the theme mixins for each component
/*body.theme-default { // that you are using.
// Create an Angular Material theme from the $theme map @include angular-material-theme($lgRed-app-theme);
@include angular-material-theme($theme);
// Apply the theme to the user components // Apply the theme to the user components
@include components-theme($theme); /*
@include ucap-material-theme($theme); @include components-theme($lgRed-app-theme);
}*/ */
@include ucap-material-theme($lgRed-app-theme);
// ----------------------------------------------------------------------------------------------------- $ucap-ui-lg-red-theme: mat-light-theme(
// @ Define a blue-gray dark theme $lgRed-app-primary,
// ----------------------------------------------------------------------------------------------------- $lgRed-app-accent,
$lgRed-app-warn
// Define the primary, accent and warn palettes
$blue-gray-dark-theme-primary-palette: mat-palette($mat-blue);
$blue-gray-dark-theme-accent-palette: mat-palette($mat-blue-gray);
$blue-gray-dark-theme-warn-palette: mat-palette($mat-red);
// Create the Material theme object
$blue-gray-dark-theme: mat-dark-theme(
$blue-gray-dark-theme-primary-palette,
$blue-gray-dark-theme-accent-palette,
$blue-gray-dark-theme-warn-palette
); );
// Add ".theme-blue-gray-dark" class to the body to activate this theme. @include ucap-ui-theme($ucap-ui-lg-red-theme, $typography);
// Class name must start with "theme-" !!!
body.theme-blue-gray-dark {
// Generate the Angular Material theme
@include angular-material-theme($blue-gray-dark-theme);
// Apply the theme to the user components
@include components-theme($blue-gray-dark-theme);
}
// -----------------------------------------------------------------------------------------------------
// @ Define a pink dark theme
// -----------------------------------------------------------------------------------------------------
// Define the primary, accent and warn palettes
$pink-dark-theme-primary-palette: mat-palette($mat-pink);
$pink-dark-theme-accent-palette: mat-palette($mat-pink);
$pink-dark-theme-warn-palette: mat-palette($mat-red);
// Create the Material theme object
$pink-dark-theme: mat-dark-theme(
$pink-dark-theme-primary-palette,
$pink-dark-theme-accent-palette,
$pink-dark-theme-warn-palette
);
// Add ".theme-pink-dark" class to the body to activate this theme.
// Class name must start with "theme-" !!!
body.theme-pink-dark {
// Generate the Angular Material theme
@include angular-material-theme($pink-dark-theme);
// Apply the theme to the user components
@include components-theme($pink-dark-theme);
}
// -----------------------------------------------------------------------------------------------------
// @ Define a pink light theme --LG RED 변경 예정(샘플링)
// -----------------------------------------------------------------------------------------------------
// Define the primary, accent and warn palettes
$lgRed-light-theme-primary-palette: mat-palette($mat-grey, 800);
$lgRed-light-theme-accent-palette: mat-palette($lg-red, 400);
$lgRed-light-theme-warn-palette: mat-palette($mat-cyan);
// Create the Material theme object
$lgRed-light-theme: mat-light-theme(
$lgRed-light-theme-primary-palette,
$lgRed-light-theme-accent-palette,
$lgRed-light-theme-warn-palette
);
// Add ".theme-pink-dark" class to the body to activate this theme.
// Class name must start with "theme-" !!!
body.theme-lgRed {
// Generate the Angular Material theme
@include angular-material-theme($lgRed-light-theme);
// Apply the theme to the user components
@include components-theme($lgRed-light-theme);
@include ucap-material-theme($lgRed-light-theme);
}
// -----------------------------------------------------------------------------------------------------
//aqua-blue-daesang
// -----------------------------------------------------------------------------------------------------
$aquaBlue-light-theme-primary-palette: mat-palette($daesang-grey, 900);
$aquaBlue-theme-accent-palette: mat-palette($aquaBlue-daesang);
$aquaBlue-theme-warn-palette: mat-palette($mat-orange);
// Create the Material theme object
$aquaBlue-light-theme: mat-light-theme(
$aquaBlue-light-theme-primary-palette,
$aquaBlue-theme-accent-palette,
$aquaBlue-theme-warn-palette
);
// Add ".theme-pink-dark" class to the body to activate this theme.
// Class name must start with "theme-" !!!
body.theme-default {
// Generate the Angular Material theme
@include angular-material-theme($aquaBlue-light-theme);
// Apply the theme to the user components
@include components-theme($aquaBlue-light-theme);
@include ucap-material-theme($aquaBlue-light-theme);
}
// -----------------------------------------------------------------------------------------------------
// @ Define a red theme --LF 변경 예정(샘플링)
// -----------------------------------------------------------------------------------------------------
// Define the primary, accent and warn palettes
$lfRed-light-theme-primary-palette: mat-palette($lf-blue-grey, 800);
$lfRed-light-theme-accent-palette: mat-palette($lf-red, 400);
$lfRed-light-theme-warn-palette: mat-palette($lf-amber);
// Create the Material theme object
$lfRed-light-theme: mat-light-theme(
$lfRed-light-theme-primary-palette,
$lfRed-light-theme-accent-palette,
$lfRed-light-theme-warn-palette
);
// Add ".theme-pink-dark" class to the body to activate this theme.
// Class name must start with "theme-" !!!
body.theme-lfRed {
// Generate the Angular Material theme
@include angular-material-theme($lfRed-light-theme);
// Apply the theme to the user components
@include components-theme($lfRed-light-theme);
@include ucap-material-theme($lfRed-light-theme);
}

View File

@ -0,0 +1,51 @@
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 { UiscrollingModule } from '@ucap/ng-ui/scrolling';
import { AppLayoutsModule } from '@app/layouts/layouts.module';
import { AppAccountSectionModule } from '@app/sections/account/account.section.module';
import { AppAuthenticationModule } from '@app/ucap/authentication/authentication.module';
import { COMPONENTS } from './components';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
FlexLayoutModule,
MatButtonModule,
MatCheckboxModule,
MatIconModule,
MatInputModule,
MatRadioModule,
MatSelectModule,
MatTabsModule,
I18nModule,
UiscrollingModule,
AppAuthenticationModule,
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,377 @@
<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
[value]="settings.general.autoLaunch"
(change)="onChangeForAutoStartOnBoot($event)"
>
{{
'authentication:login.settings.autoStartOnBoot'
| ucapI18n
}}
</mat-checkbox>
</li>
<li>
<mat-checkbox
[value]="settings.general.autoLogin"
(change)="onChangeForAutoLogin($event)"
>
{{ 'authentication:login.settings.autoLogin' | ucapI18n }}
</mat-checkbox>
</li>
<li>
<mat-checkbox
[value]="settings.general.startupHideWindow"
(change)="onChangeForAutoHide($event)"
>
{{ '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]="settings.general.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]="settings.general.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
[class.ucap-select-placeholder-value]="
!!timezonePlaceholder
"
#selectForTimezone
[formControl]="formControlForTimezone"
[placeholder]="timezonePlaceholder"
[value]="settings.general.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"
[value]="String(settings.notification.use)"
(change)="onChangeForReceiveNotification($event)"
>
<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
[value]="settings.notification.method"
(selectionChange)="
onSelectionChangeMethodOfNotification($event)
"
>
<mat-option [value]="NotificationMethod.Sound">
{{
'organization:settings.notification.methodTypeSound'
| ucapI18n
}}
</mat-option>
<mat-option [value]="NotificationMethod.Alert">
{{
'organization:settings.notification.methodTypeAlert'
| ucapI18n
}}
</mat-option>
<mat-option [value]="NotificationMethod.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
[value]="String(settings.notification.alertExposureTime)"
(selectionChange)="
onSelectionChangeAlertExposureTimeOfNotification($event)
"
>
<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
[value]="settings.notification.receiveForMessage"
(change)="
onChangeForReceiveForMessageOfNotification($event)
"
>
{{
'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" *ngIf="'electron' === platform">
<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="" />
<button
mat-button
matSuffix
mat-icon-button
aria-label="file"
>
<mat-icon>folder</mat-icon>
</button>
</mat-form-field>
</div>
<button
mat-stroked-button
color="primary"
class="btn-folder-first"
>
폴더 초기화
</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">
<!-- 비밀번호 -->
<app-authentication-change-password></app-authentication-change-password>
</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;
&:first-of-type {
border-top: 0;
padding: 20px 16px 10px;
}
.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;
}
}
.settings-radio-group {
padding: 6px 0;
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,245 @@
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 { FormControl } from '@angular/forms';
import {
MatDialogRef,
MAT_DIALOG_DATA,
MatDialog
} from '@angular/material/dialog';
import { MatOptionSelectionChange } from '@angular/material/core';
import { MatSelectChange, MatSelect } from '@angular/material/select';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MatRadioChange } from '@angular/material/radio';
import { ObjectUtil } from '@ucap/core';
import { NotificationMethod } from '@ucap/domain-common';
import { NativeService, NativeType } from '@ucap/native';
import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native';
import { I18nService } from '@ucap/ng-i18n';
import { DateService } from '@ucap/ng-ui/date';
import { VirtualScrollViewportComponent } from '@ucap/ng-ui/scrolling';
import { TranslateService } from '@ucap/ng-ui-organization';
import { environment } from '@environments';
import {
Settings,
GeneralSetting,
NotificationSetting,
ChatSetting,
PresenceSetting
} from '@app/models/settings';
export interface TimezoneData {
displayName: string;
name: string;
}
export interface SettingsDialogData {
settings: Settings;
}
export interface SettingsDialogResult {
settings: Settings;
}
@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';
settings: Settings;
timezoneList: TimezoneData[];
timezonePlaceholder: string;
supportedLanguages = environment.productConfig.supportedLanguages;
supportedHrLanguages =
environment.productConfig.organization.supportedLanguages;
String = String;
NotificationMethod = NotificationMethod;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
public dialogRef: MatDialogRef<SettingsDialogData, SettingsDialogResult>,
@Inject(MAT_DIALOG_DATA) public data: SettingsDialogData,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
private dateService: DateService,
private i18nService: I18nService,
private translateService: TranslateService,
private changeDetectorRef: ChangeDetectorRef,
public matDialog: MatDialog
) {
this.nativeService.platform_nativeType().then((type) => {
// this.platform = 'electron';
// return;
switch (type) {
case NativeType.Browser:
this.platform = 'browser';
break;
case NativeType.Electron:
this.platform = 'electron';
break;
default:
break;
}
});
this.settings = ObjectUtil.deepClone(data.settings);
}
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();
}
}
onChangeForAutoStartOnBoot(event: MatCheckboxChange) {
this.settings.general.autoLaunch = event.checked;
}
onChangeForAutoLogin(event: MatCheckboxChange) {
this.settings.general.autoLogin = event.checked;
}
onChangeForAutoHide(event: MatCheckboxChange) {
this.settings.general.startupHideWindow = event.checked;
}
onSelectionChangeLanguage(event: MatSelectChange) {
this.i18nService.changeLanguage(event.value);
this.settings.general.locale = event.value;
}
onSelectionChangeHrLanguage(event: MatSelectChange) {
this.translateService.use(event.value);
this.settings.general.hrInfoLocale = event.value;
}
onOpenedChangeTimezone(opened: boolean) {
if (opened) {
this.setTimezoneData();
this.vsTimezone.checkViewportSize();
}
}
onSelectionChangeTimezone(event: MatOptionSelectionChange) {
if (!event.isUserInput) {
return;
}
this.dateService.use(event.source.value);
this.settings.general.timezone = event.source.value;
}
onChangeForReceiveNotification(event: MatRadioChange) {
const use = 'true' === event.value;
console.log('onChangeForReceiveNotification', use);
}
onSelectionChangeMethodOfNotification(event: MatSelectChange) {
console.log('onSelectionChangeMethodOfNotification', event.value);
}
onSelectionChangeAlertExposureTimeOfNotification(event: MatSelectChange) {
const v = Number(event.value);
console.log('onSelectionChangeAlertExposureTimeOfNotification', v);
}
onChangeForReceiveForMessageOfNotification(event: MatCheckboxChange) {
console.log('onChangeForReceiveForMessageOfNotification', event.checked);
}
onClosed(event: MouseEvent): void {
this.dialogRef.close({ settings: this.data.settings });
}
onCancel() {
this.dialogRef.close({ settings: this.data.settings });
}
onConfirm() {
this.dialogRef.close({
settings: this.settings
});
}
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]
}`;
if (-1 < displayName.indexOf('undefined')) {
console.log('timezone', name);
}
timezoneList.push({
displayName,
name
});
if (name === this.settings.general.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.settings.general.timezone
);
if (-1 !== timezoneIndex) {
if (!!this.vsTimezone && !!this.selectForTimezone) {
this.vsTimezone.scrollToIndex(timezoneIndex, 'start');
this.selectForTimezone.value = this.timezoneList[timezoneIndex].name;
}
}
}
}

View File

@ -12,6 +12,7 @@ import {
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { LogService } from '@ucap/ng-logger';
import { PiService } from '@ucap/ng-pi'; import { PiService } from '@ucap/ng-pi';
import { LoginActions } from '@ucap/ng-store-authentication'; import { LoginActions } from '@ucap/ng-store-authentication';
@ -26,7 +27,8 @@ export class AppAuthenticationGuard implements CanActivate {
private piService: PiService, private piService: PiService,
private appAuthenticationService: AppAuthenticationService, private appAuthenticationService: AppAuthenticationService,
private store: Store<any>, private store: Store<any>,
private router: Router private router: Router,
private logService: LogService
) {} ) {}
canActivate( canActivate(
@ -38,11 +40,19 @@ export class AppAuthenticationGuard implements CanActivate {
| Observable<boolean | UrlTree> | Observable<boolean | UrlTree>
| Promise<boolean | UrlTree> { | Promise<boolean | UrlTree> {
return new Promise<boolean | UrlTree>((resolve, reject) => { return new Promise<boolean | UrlTree>((resolve, reject) => {
if (this.appAuthenticationService.loggedIn()) { const loggedIn = this.appAuthenticationService.loggedIn();
if (loggedIn) {
resolve(true); resolve(true);
} else { return;
}
const userStore = this.appAuthenticationService.useAutoLogin(); const userStore = this.appAuthenticationService.useAutoLogin();
if (!!userStore) { if (!userStore) {
this.router.navigateByUrl('/account/login');
resolve(false);
return;
}
const loginSession = this.appAuthenticationService.getLoginSession(); const loginSession = this.appAuthenticationService.getLoginSession();
const onWebLoginFailure = (error: any) => { const onWebLoginFailure = (error: any) => {
@ -83,14 +93,8 @@ export class AppAuthenticationGuard implements CanActivate {
}, },
(error) => { (error) => {
onWebLoginFailure(error); onWebLoginFailure(error);
}, }
() => {}
); );
} else {
this.router.navigateByUrl('/account/login');
resolve(false);
}
}
}); });
} }
} }

View File

@ -0,0 +1,36 @@
<div class="layout-container" fxLayout="column">
<div class="layout-header" fxFlex="50px" fxLayout="row">
<div fxFlex="1 1 auto" class="layout-header-container">
<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"
(click)="onClickClose($event)"
>
<i class="mdi mdi-window-close"></i>
</button>
</div>
</div>
<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
class="layout-action-content"
select="[appLayoutsDefaultDialog='action']"
></ng-content>
</div>
</div>

View File

@ -0,0 +1,40 @@
.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 #666;
margin: 0 16px;
&-container {
flex: 1 1 auto;
box-sizing: border-box;
width: calc(100% - 30px);
}
.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 { DefaultDialogLayoutComponent } from './default-dialog.layout.component';
describe('app::layouts::DefaultDialogLayoutComponent', () => {
let component: DefaultDialogLayoutComponent;
let fixture: ComponentFixture<DefaultDialogLayoutComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DefaultDialogLayoutComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DefaultDialogLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

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-dialog',
templateUrl: './default-dialog.layout.component.html',
styleUrls: ['./default-dialog.layout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DefaultDialogLayoutComponent implements OnInit, OnDestroy {
@Input()
disableClose = false;
@Output()
closed = new EventEmitter<MouseEvent>();
constructor(private changeDetectorRef: ChangeDetectorRef) {}
private ngOnDestroySubject: Subject<boolean>;
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onClickClose(event: MouseEvent): void {
this.closed.emit(event);
}
}

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

@ -1,107 +1,489 @@
<div fxFlexFill class="layout-container"> <div
<div class="navi-container"> class="layout-container"
fxLayout="row"
[ngClass]="'electron==' ? 'electron' : ''"
>
<div class="navitab-page">
<div class="gnb">
<mat-toolbar class="mat-gnb-toolbar"
><img
src="assets/images/logo_140.png"
alt=""
class="img-logo"
width="32"
/></mat-toolbar>
<mat-tab-group <mat-tab-group
#navTabGroup #navTabGroup
vertical mat-stretch-tabs
disablePagination="true" animationDuration="0ms"
backgroundColor="transparent"
class="global-menu"
(selectedTabChange)="onSelectedTabChange($event)" (selectedTabChange)="onSelectedTabChange($event)"
> >
<mat-tab> <mat-tab [aria-label]="NavigationType.Group">
<ng-template mat-tab-label> <ng-template mat-tab-label>
<div class="icon-item"> <div
<svg class="icon-item"
xmlns="http://www.w3.org/2000/svg" [matTooltip]="'common:tooltip.group' | ucapI18n"
width="30" matTooltipPosition="after"
height="30" (click)="onClickToggleLeftSidenav()"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
> >
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<div class="icon-item">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="30" width="16.871"
height="30" height="16.8"
viewBox="0 0 24 24" viewBox="0 0 16.871 16.8"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt" stroke-linecap="butt"
stroke-linejoin="bevel" >
<g
id="icon_gnb_group_g32"
transform="translate(-17.815 -103.827)"
> >
<path <path
d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" id="prefix_22"
></path> d="M18.871 21a7.733 7.733 0 0 0-7.435-8A7.733 7.733 0 0 0 4 21"
</svg> data-name="prefix_22"
</div> transform="translate(14.815 98.627)"
</ng-template> style="
</mat-tab> fill: transparent;
<mat-tab> /*stroke: #999;*/
<ng-template mat-tab-label> stroke-linecap: round;
<div class="icon-item"> stroke-miterlimit: 10;
<svg stroke-width: 2px;
xmlns="http://www.w3.org/2000/svg" "
width="30"
height="30"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<circle class="st0" cx="18.4" cy="18.5" r="3" />
<circle class="st0" cx="12" cy="5" r="3" />
<path class="st0" d="M12.4,10.5h4c1.1,0,2,0.9,2,2v3" />
<circle class="st0" cx="6.1" cy="18.5" r="3" />
<path class="st0" d="M6.1,15.5v-3c0-1.1,0.9-2,2-2h4" />
<line class="st0" x1="12" y1="8" x2="12" y2="9" />
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<div class="icon-item">
<svg
xmlns="http://www.w3.org/2000/svg"
width="30"
height="30"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<polygon
points="21.368 12.001 3 21.609 3 14 11 12 3 9.794 3 2.394"
/> />
<g id="prefix_23" data-name="prefix_23" style="fill: none;">
<path
d="M4.093 0a4.047 4.047 0 0 1 4.092 4 4.1 4.1 0 0 1-1.591 3.167A3.931 3.931 0 0 1 4.093 8 4.047 4.047 0 0 1 0 4a4.047 4.047 0 0 1 4.093-4z"
style="stroke: none;"
transform="translate(22.157 103.827)"
/>
<path
d="M4.093 2C2.939 2 2 2.897 2 4s.939 2 2.093 2c.604 0 1.062-.233 1.16-.317l.017-.016.018-.015c.804-.694.897-1.275.897-1.652 0-1.103-.938-2-2.092-2m0-2c2.26 0 4.092 1.79 4.092 4 0 1.184-.526 2.248-1.591 3.167C6.098 7.614 5.14 8 4.093 8 1.833 8 0 6.21 0 4s1.832-4 4.093-4z"
style="fill: #999; stroke: none;"
transform="translate(22.157 103.827)"
/>
</g>
</g>
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab [aria-label]="NavigationType.Chat">
<ng-template mat-tab-label>
<div
class="icon-item"
[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"
width="16.8"
height="16"
viewBox="0 0 16.8 16"
>
<g
id="icon_gnb_chat_g32"
style="fill: none; stroke-linejoin: round;"
>
<path
d="M39.972 26.892a7.688 7.688 0 0 0-8.983 0 4.344 4.344 0 0 0 0 7.194 7.334 7.334 0 0 0 4.492 1.449c.172 0 .346-.006.519-.017l1.09 1.725a.437.437 0 0 0 .36.2h.01a.438.438 0 0 0 .359-.184l2.308-3.3a4.33 4.33 0 0 0-.154-7.07z"
style="stroke: none;"
transform="translate(-27.08 -23.443)"
/>
<path
d="M35.48 25.443c-1.693 0-3.288.514-4.492 1.449-1.23.955-1.908 2.232-1.908 3.597 0 1.364.678 2.642 1.908 3.597 1.204.934 2.8 1.449 4.492 1.449.172 0 .346-.006.519-.017l1.09 1.725c.076.121.212.196.36.2h.01c.143 0 .278-.069.358-.184l2.308-3.297c1.133-.94 1.755-2.17 1.755-3.473 0-1.365-.678-2.643-1.908-3.597-1.204-.935-2.8-1.45-4.492-1.45m0-2c2.134 0 4.165.664 5.718 1.87 1.73 1.342 2.682 3.18 2.682 5.177 0 1.817-.8 3.52-2.259 4.824l-2.165 3.093c-.454.65-1.201 1.037-1.997 1.037h-.063c-.817-.022-1.563-.444-1.998-1.131l-.501-.794c-1.92-.11-3.724-.757-5.135-1.852-1.73-1.342-2.682-3.18-2.682-5.177 0-1.997.952-3.835 2.682-5.177 1.553-1.205 3.584-1.87 5.718-1.87z"
style="fill: #999; stroke: none;"
transform="translate(-27.08 -23.443)"
/>
</g>
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab [aria-label]="NavigationType.Organization">
<ng-template mat-tab-label>
<div
class="icon-item"
[matTooltip]="'common:tooltip.organization' | ucapI18n"
matTooltipPosition="after"
(click)="onClickToggleLeftSidenav()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="19"
viewBox="0 0 21 19"
>
<g
id="icon_gnb_organiztion_g32"
transform="translate(-12.917 -220.25)"
>
<g class="prefix__cls-1" transform="translate(19.917 220.25)">
<circle cx="3.5" cy="3.5" r="3.5" class="prefix__cls-3" />
<circle cx="3.5" cy="3.5" r="2.5" class="prefix__cls-4" />
</g>
<g class="prefix__cls-1" transform="translate(12.917 232.25)">
<circle cx="3.5" cy="3.5" r="3.5" class="prefix__cls-3" />
<circle cx="3.5" cy="3.5" r="2.5" class="prefix__cls-4" />
</g>
<g class="prefix__cls-1" transform="translate(19.917 232.25)">
<circle cx="3.5" cy="3.5" r="3.5" class="prefix__cls-3" />
<circle cx="3.5" cy="3.5" r="2.5" class="prefix__cls-4" />
</g>
<g class="prefix__cls-1" transform="translate(26.917 232.25)">
<circle cx="3.5" cy="3.5" r="3.5" class="prefix__cls-3" />
<circle cx="3.5" cy="3.5" r="2.5" class="prefix__cls-4" />
</g>
<path
d="M16.5 233.312v-3.437h13.833v3.438"
transform="translate(0 -1.087)"
style="
stroke-linecap: round;
stroke-linejoin: round;
stroke: #999;
stroke-width: 2px;
fill: none;
"
/>
<path
d="M0 0L0 6"
class="prefix__cls-1"
transform="translate(23.417 226.75)"
/>
</g>
</svg>
</div>
</ng-template>
</mat-tab>
<!-- <mat-tab [aria-label]="NavigationType.Message">
<ng-template mat-tab-label>
<div
class="icon-item"
[matTooltip]="'common:tooltip.message' | ucapI18n"
matTooltipPosition="after"
(click)="onClickToggleLeftSidenav()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="21.096"
height="19.182"
viewBox="0 0 21.096 19.182"
>
<g
id="icon_gnb_message_g32"
transform="translate(-12.615 -280.248)"
>
<path
d="M16.063 292.447l7.965 2.4 7.465 5.3 3.666-16.615z"
class="prefix__cls-1"
transform="translate(-2.448 -2.284)"
/>
<path
d="M23.25 277.406l.18 5.668 4-2.692z"
class="prefix__cls-1"
transform="translate(-2.096 15.355)"
/>
<path
d="M0 10.416L11.025 0"
transform="translate(21.154 282.346)"
style="fill: none; stroke: #999; stroke-width: 2px;"
/>
</g>
</svg>
</div>
</ng-template>
</mat-tab> -->
<mat-tab [aria-label]="NavigationType.Call">
<ng-template mat-tab-label>
<div
class="icon-item"
[matTooltip]="'common:tooltip.call' | ucapI18n"
matTooltipPosition="after"
(click)="onClickToggleLeftSidenav()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 18 18"
>
<g id="icon_gnb_call_g32" transform="translate(-1.337 -1.337)">
<g style="fill: none;">
<path
d="M15.832 20A13.864 13.864 0 0 1 2 6.14 4.136 4.136 0 0 1 6.132 2a3.532 3.532 0 0 1 .692.063 3.4 3.4 0 0 1 .647.162.9.9 0 0 1 .584.675l1.229 5.4a.9.9 0 0 1-.234.828c-.117.126-.126.135-1.231.711a8.91 8.91 0 0 0 4.374 4.4c.584-1.116.593-1.125.719-1.242a.9.9 0 0 1 .826-.234L19.129 14a.9.9 0 0 1 .647.585 3.912 3.912 0 0 1 .171.657 4.3 4.3 0 0 1 .054.684A4.136 4.136 0 0 1 15.832 20z"
style="stroke: none;"
transform="translate(-.663 -.663)"
/>
<path
d="M15.87 18c1.152 0 2.105-.938 2.13-2.094 0-.039-.001-.078-.004-.117l-3.89-.89-.14.268-.882 1.687-1.728-.798C8.985 14.96 7.083 13.047 6 10.667l-.785-1.725 1.68-.877.255-.132-.896-3.93L6.159 4h-.027C4.956 4 4 4.96 4 6.137c.004 3.168 1.237 6.146 3.472 8.385 2.234 2.239 5.204 3.474 8.363 3.478h.035m0 2h-.038C8.197 19.99 2.01 13.79 2 6.14 2 3.854 3.85 2 6.132 2h.027c.223 0 .445.021.664.063.22.033.437.087.647.162.3.106.522.362.584.675l1.23 5.4c.068.298-.02.61-.233.828-.117.126-.126.135-1.23.71.884 1.946 2.436 3.507 4.374 4.402.583-1.116.592-1.125.718-1.242.17-.166.396-.256.628-.256.066 0 .133.007.199.022l5.389 1.233c.302.07.546.291.646.585.076.213.133.433.171.657.036.226.054.455.054.684C19.966 18.186 18.124 20 15.87 20z"
style="fill: #999; stroke: none;"
transform="translate(-.663 -.663)"
/>
</g>
</g>
</svg> </svg>
</div> </div>
</ng-template> </ng-template>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
<div class="btn-homepage-area">
<button mat-button aria-label="Homepage" matTooltip="Homepage">
<!--<em>Homepage</em>-->
</button>
</div>
</div>
</div>
<div class="content-page" fxFlex="1 1 auto" fxLayout="column">
<div class="content-body" fxFlex="1 1 auto">
<mat-sidenav-container hasBackdrop="false" autosize="true">
<mat-sidenav
#leftSidenav
class="left-sidenav"
mode="side"
opened="true"
[disableClose]="true"
(openedChange)="onOpenStart($event)"
>
<ucap-float-action-button
*ngIf="fabButtonShow"
[buttons]="fabButtons"
[useCustomDefaultIcon]="fabUseCustomDefaultIcon"
(buttonClick)="onClickFab($event)"
>
<div
*ngIf="curNavi === NavigationType.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="curNavi === NavigationType.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="curNavi === NavigationType.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="curNavi === NavigationType.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
</div> </div>
<div class="content-container" fxFlexFill>
<mat-sidenav-container autosize="true" fxFlexFill>
<mat-sidenav #leftSidenav class="left-sidenav" mode="side" opened="true">
<router-outlet></router-outlet>
</mat-sidenav>
<div fxFlex="1 1 auto"> <div fxFlex="1 1 auto">
<router-outlet></router-outlet>
</div>
</div>
</mat-sidenav>
<mat-sidenav-content>
<div class="content-sidenav-container" fxLayout="column">
<div class="content-sidenav-top-bar" fxFlex="0 0 40px">
<app-layouts-top-bar>
<div class="content-sidenav-top-bar-content">
<div class="toolbar-info-area date-info toolbar-drag-area">
<div class="today">
<span>Today</span>{{ moment().format('YYYY.MM.DD') }}
</div>
</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"
[matMenuTriggerFor]="profileMenu"
#profileMenuTrigger="matMenuTrigger"
>
<app-organization-profile-image-01
[userInfo]="user.info"
[versionInfo]="versionInfo"
(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> <router-outlet name="content"></router-outlet>
</div> </div>
</div>
</mat-sidenav-content>
</mat-sidenav-container> </mat-sidenav-container>
</div> </div>
<div class="content-statusbar" fxFlex="0 0 38px">
<!--Footer-->
<div class="footer">
<div class="foot-info version-info">
<span class="var-txt current-ver" (click)="onClickForOpenRoom()"
>현재버전 0.0.11</span
>
<span class="var-txt new-var">최신버전 0.0.11 </span>
<button mat-icon-button aria-label="icon">
<mat-icon>get_app</mat-icon>
</button>
</div>
<div class="foot-info help-info">
<p><span>Help</span>Desk <em>1661-9066</em></p>
</div>
</div>
<!-- //Footer-->
</div>
</div>
</div> </div>
<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

@ -1,22 +1,368 @@
.layout-container { @import '~@ucap/lg-scss/mixins';
display: flex;
.navi-container { :host {
width: 70px; width: 100%;
height: 100%;
}
.layout-container {
width: 100%;
height: 100%;
&.electron {
border: 1px solid #aaa;
} }
.content-container { .navitab-page {
.left-sidenav { //GNB /////////////////////////////////////
display: flex;
flex: 0 0 auto;
width: 60px;
@include screen(xs) {
width: 60px;
}
.gnb {
//background-color: $gray-ref0;
background-color: #f1f2f6;
width: 100%;
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 370px; justify-content: space-between;
align-items: center;
border-right: 1px solid rgba(0, 0, 0, 0.2);
.mat-gnb-toolbar {
flex-basis: 40px;
padding: 2px 12px 10px;
.img-logo {
margin: 10px 0 0 1px;
}
}
.left-container {
display: flex;
width: calc(100% - 28px);
height: 100%; height: 100%;
max-width: 90%; }
overflow: hidden; .global-menu {
width: 100%;
//background-color: $gray-ref0;
flex-grow: 1;
}
.btn-homepage-area {
flex-flow: column-reverse;
position: relative;
button {
/*
padding: 30px 0 12px;
&::before {
content: '';
width: 20px;
height: 20px;
background-image: url(../../../assets/images/ico/btn_gnb_hompage.svg);
display: block;
position: absolute;
top: 9px;
left: calc(50% - 10px);
}
*/
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;
color: $gray-re6;
display: block;
line-height: 9px;
font-weight: 600;
}
}
}
}
/////////////////////////////////////GNB //
} }
.content-drawer { .content-page {
flex: 0 0 auto; width: calc(100% - 60px);
height: 100%;
.content-body {
width: 100%;
height: calc(100% - 38px);
mat-sidenav-container {
width: 100%;
height: 100%;
.left-sidenav {
width: 370px;
max-width: 90%;
border-right: 1px solid rgba(0, 0, 0, 0.2);
.left-sidenav-container {
width: 100%;
height: 100%;
.top-bar {
font: {
size: 13px;
color: $gray-re70;
}
height: 40px;
display: flex;
align-items: center;
padding: 0 16px;
-webkit-app-region: drag;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
}
}
mat-sidenav-content {
.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;
align-items: center;
&.toolbar-drag-area {
-webkit-app-region: drag;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
&.date-info {
@include font-family($font-light);
font-weight: 600;
font-size: 12px;
color: $gray-re70;
display: flex;
flex: 1 1 auto;
padding-left: 30px;
height: 100%;
@include screen(mid) {
padding-left: 16px;
}
.today {
display: flex;
flex-direction: row;
align-items: center;
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;
}
@include screen(mid) {
display: none;
}
}
}
&.toolbar-ctrl {
flex-flow: row-reverse;
margin-left: auto;
.topbar-search {
//개발예정으로 개발후 오픈
display: none;
order: 2;
margin-right: 8px;
.ico-search-icon {
width: 18px;
height: 18px;
font-size: 18px;
line-height: 18px;
color: #707070;
}
}
.my-profile {
height: 28px;
width: 28px;
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;
}
}
}
}
}
.content-statusbar {
width: 100%;
height: 100%;
//Footer ////////////////////////
.footer {
display: flex;
justify-content: space-between;
width: 100%;
height: 38px;
border-top: 1px solid rgba(0, 0, 0, 0.2);
background-color: $white;
.foot-info {
display: flex;
flex-grow: 1;
align-items: center;
font-size: 12px;
color: $gray-re70;
@include font-family($font-light);
font-weight: 600;
&.version-info {
display: none;
.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;
height: 10px;
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 {
color: $brown;
}
}
}
&.help-info {
flex-flow: row-reverse;
padding-right: 20px;
p {
margin: 0;
text-align: right;
span {
color: $lipstick;
}
em {
margin-left: 10px;
white-space: nowrap;
}
}
}
}
}
//////////////////////// Footer//
} }
} }
} }
// 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,18 +1,62 @@
import { Subscription } from 'rxjs'; import moment from 'moment';
import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core'; import { Subject, of } from 'rxjs';
import { Router } from '@angular/router'; import { takeUntil, filter, take, map, catchError } from 'rxjs/operators';
import { Component, ViewChild, OnDestroy, OnInit, Inject } from '@angular/core';
import {
Router,
RouterEvent,
NavigationEnd,
PRIMARY_OUTLET,
ActivatedRoute,
Params
} from '@angular/router';
import { Store, select } from '@ngrx/store'; 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 { MatTabChangeEvent, MatTabGroup } from '@angular/material/tabs';
import { MatSidenav } from '@angular/material/sidenav'; import { MatSidenav } from '@angular/material/sidenav';
import { User, UserInfoSS } from '@ucap/domain-organization';
import { LogService } from '@ucap/ng-logger'; 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 { QueryParams as ChatQueryParams } from '@app/pages/chat/types/params.type';
import { AppAccountService } from '@app/services/app-account.service';
import { AppCallService } from '@app/services/app-call.service';
import { AppUiService } from '@app/services/app-ui.service';
import { QueryParams as OrganizationParams } from '@app/pages/organization/types/params.type';
import { AddUserDialogComponent } from '@app/sections/group/dialogs/add-user.dialog.component';
import {
CreateDialogComponent as ChatCreateDialogComponent,
CreateDialogData as ChatCreateDialogData,
CreateDialogResult as ChatCreateDialogResult
} from '@app/sections/chat/dialogs/create.dialog.component';
import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native';
import { NativeService } from '@ucap/native';
import { AppActions } from '@app/store/actions';
import { NavigationType } from '@app/types';
import { I18nService } from '@ucap/ng-i18n';
import {
DialpadDialogComponent,
DialpadDialogData,
DialpadDialogResult
} from '@app/sections/call/dialogs/dialpad.dialog.component';
import { VersionInfo } from '@ucap/domain-authentication';
const NAVS = ['group', 'chat', 'organization', 'message']; const NAVS = [
NavigationType.Group,
NavigationType.Chat,
NavigationType.Organization,
NavigationType.Message,
NavigationType.Call
];
@Component({ @Component({
selector: 'app-layouts-default', selector: 'app-layouts-default',
@ -24,65 +68,362 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
navTabGroup: MatTabGroup; navTabGroup: MatTabGroup;
@ViewChild('leftSidenav', { static: true }) @ViewChild('leftSidenav', { static: true })
leftSidenav: MatSidenav; set leftSidenav(leftSidenav: MatSidenav) {
this._leftSidenav = leftSidenav;
this.appUiService.leftSidenav = leftSidenav;
}
get leftSidenav(): MatSidenav {
return this._leftSidenav;
}
// tslint:disable-next-line: variable-name
_leftSidenav: MatSidenav;
private windowSizeSubscription: Subscription; @ViewChild('profileMenuTrigger', { static: true })
profileMenuTrigger: MatMenuTrigger;
showStatusbar = true;
queryParams: Params;
unreadCountChat = 0;
/** FAB */
fabButtonShow = true;
fabUseCustomDefaultIcon = true; // default in this prj
fabButtons: { icon: string; tooltip?: string; divisionType?: string }[];
versionInfo: VersionInfo;
user: User;
/** Navigation */
NavigationType = NavigationType;
initMenu = NavigationType.Group;
curNavi: NavigationType;
moment = moment;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor( constructor(
private router: Router, private router: Router,
private i18nSevice: I18nService,
private activatedRoute: ActivatedRoute,
private store: Store<any>, private store: Store<any>,
private logService: LogService private appAccountService: AppAccountService,
) {} private appCallService: AppCallService,
private appUiService: AppUiService,
ngOnInit(): void { private logService: LogService,
this.windowSizeSubscription = this.store public dialog: MatDialog,
.pipe(select(AppSelector.windowSize)) @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService
.subscribe((size) => { ) {
if (size.width < 780) { this.setFabInitial(this.initMenu);
if (this.leftSidenav.opened) { this.router.events
this.leftSidenav.close(); .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;
} }
} else { const index = p.segments[0].path as NavigationType;
if (!this.leftSidenav.opened) { this.setTabGroup(index);
this.leftSidenav.open(); this.setFabInitial(index);
} }
break;
default:
break;
} }
}); });
this.setTabGroup(this.router.url); this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params) => {
this.queryParams = params;
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo) => {
this.versionInfo = versionInfo;
});
}
ngOnInit(): void {
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user))
.subscribe((user) => {
this.user = user;
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(RoomSelector.unreadTotal)
)
.subscribe((unreadTotal) => {
this.unreadCountChat = unreadTotal;
});
// this.store
// .pipe(
// takeUntil(this.ngOnDestroySubject),
// select(PresenceSelector.selectAllStatusBulkInfo)
// )
// .subscribe((allBulkInfo) => {
// // .includes(String(this.user.info.seq))
// if (!allBulkInfo || (!!allBulkInfo && allBulkInfo.length === 0)) {
// return;
// }
// const userStatusinfo = allBulkInfo.filter(
// (bulkInfo) => bulkInfo.userSeq === String(this.user.info.seq)
// )[0];
// const status = PresenceUtil.isOnline(userStatusinfo, PresenceType.PC);
// if (!status && userStatusinfo.pcStatus === StatusCode.Offline) {
// this.store.dispatch(
// PresenceActions.bulkInfo({
// divCd: 'myBulk',
// userSeqs: [String(this.user.info.seq)]
// })
// );
// }
// });
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (!this.windowSizeSubscription) { if (!!this.ngOnDestroySubject) {
this.windowSizeSubscription.unsubscribe(); this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
} }
} }
onOpenProfile(userInfo: UserInfoSS) {
this.profileMenuTrigger.openMenu();
}
onSelectedTabChange(event: MatTabChangeEvent) { onSelectedTabChange(event: MatTabChangeEvent) {
if (4 === event.index) { const naviMenu: NavigationType = event.tab.ariaLabel as NavigationType;
this.router.navigate( const commands: any = [naviMenu, { outlets: { content: 'index' } }];
['group', { outlets: { content: 'chat/index' } }], const orgInitialParams: Params = {};
{}
); // CASE :: Chat.
if (
naviMenu === NavigationType.Chat && // is chat.
!!this.queryParams &&
!!this.queryParams[ChatQueryParams.ROOM_ID]
) {
// 다른 화면에서 채팅으로 바로 유입할 경우에는 navigate 초기화를 무시한다.
this.queryParams = undefined;
return; return;
} }
this.router.navigate([
NAVS[event.index], // CASE :: Organization.
{ outlets: { content: 'index' } } if (naviMenu === NavigationType.Organization && !!this.user) {
]); orgInitialParams[OrganizationParams.DEPT_SEQ] = String(
this.user.departmentCode
);
}
this.router.navigate(commands, { queryParams: orgInitialParams });
this.appUiService.openLeftSidenavOnNarrowMode();
this.setFabInitial(naviMenu);
} }
onClickToggleLeftSidenav() { onClickToggleLeftSidenav() {
if (this.leftSidenav.opened) { this.appUiService.toggleLeftSidenavOnNarrowMode();
this.leftSidenav.close(); }
} else {
this.leftSidenav.open(); setFabInitial(type: NavigationType) {
this.curNavi = type;
switch (type) {
case NavigationType.Group:
{
this.fabButtonShow = true;
this.fabButtons = [
{
icon: 'add',
tooltip: this.i18nSevice.t('group:dialog.title.addNewBuddy'),
divisionType: 'GROUP_NEW_ADD'
}
];
}
break;
case NavigationType.Chat:
{
this.fabButtonShow = true;
this.fabButtons = [
{
icon: 'chat',
tooltip: this.i18nSevice.t('chat:dialog.title.newChatRoom'),
divisionType: 'CAHT_NEW_ADD'
}
];
// if (environment.productConfig.CommonSetting.useTimerRoom) {
// this.fabButtons.push({
// icon: 'timer',
// tooltip: this.translateService.instant('chat.newTimerChat'),
// divisionType: 'CHAT_NEW_TIMER_ADD'
// });
// }
}
break;
case NavigationType.Organization:
{
this.fabButtonShow = false;
}
break;
case NavigationType.Message:
{
this.fabButtonShow = true;
this.fabButtons = [
{
icon: 'add',
tooltip: '쪽지 추가',
divisionType: 'MESSAGE_NEW'
}
];
}
break;
case NavigationType.Call:
{
this.fabButtonShow = true;
this.fabButtons = [
{
icon: 'dialpad',
tooltip: this.i18nSevice.t('call:label.dialpad'),
divisionType: 'OPEN_DIALPAD'
}
];
}
break;
default: {
this.fabButtonShow = false;
}
} }
} }
onClickFab(params: { btn: any }) {
const btn = params.btn as {
icon: string;
tooltip?: string;
divisionType?: string;
};
switch (btn.divisionType) {
case 'GROUP_NEW_ADD':
{
const dialogRef = this.dialog.open(AddUserDialogComponent, {
panelClass: 'max-create-dialog'
});
dialogRef
.afterOpened()
.pipe(take(1))
.subscribe(() => dialogRef.componentInstance.psUpdate());
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
break;
case 'CAHT_NEW_ADD':
{
const dialogRef = this.dialog.open<
ChatCreateDialogComponent,
ChatCreateDialogData,
ChatCreateDialogResult
>(ChatCreateDialogComponent, {
panelClass: 'max-create-dialog',
data: {}
});
}
break;
case 'CHAT_NEW_TIMER_ADD':
{
// if (environment.productConfig.CommonSetting.useTimerRoom) {
// this.onClickNewChat('TIMER');
// }
}
break;
case 'MESSAGE_NEW':
{
this.logService.debug('MESSAGE_NEW');
}
break;
case 'OPEN_DIALPAD':
{
this.dialog.open<
DialpadDialogComponent,
DialpadDialogData,
DialpadDialogResult
>(DialpadDialogComponent, {
panelClass: 'mid-create-dialog',
data: {}
});
}
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();
}
onClickForOpenRoom() {
this.nativeService.chat_openRoom('/chat/chatroom?roomId=1000');
}
private setTabGroup(url: string) { private setTabGroup(url: string) {
this.navTabGroup.selectedIndex = NAVS.findIndex((v) => if (!!this.navTabGroup) {
url.startsWith(`/${v}`) this.navTabGroup.selectedIndex = NAVS.findIndex((v) => url === v);
}
}
onOpenStart(opened: boolean) {
this.store.dispatch(
AppActions.openedLiftSideNav({
opened
})
); );
} }
} }

View File

@ -3,8 +3,17 @@ import { TopBarComponent } from './top-bar.component';
import { DefaultLayoutComponent } from './default.layout.component'; import { DefaultLayoutComponent } from './default.layout.component';
import { NoNaviLayoutComponent } from './no-navi.layout.component'; import { NoNaviLayoutComponent } from './no-navi.layout.component';
import { DefaultDialogLayoutComponent } from './default-dialog.layout.component';
import { DefaultDrawerLayoutComponent } from './default-drawer.layout.component';
import { SelectorLayoutComponent } from './selector.layout.component';
export const COMPONENTS = [ export const COMPONENTS = [
TopBarComponent, TopBarComponent,
DefaultLayoutComponent, DefaultLayoutComponent,
NoNaviLayoutComponent NoNaviLayoutComponent,
DefaultDialogLayoutComponent,
DefaultDrawerLayoutComponent,
SelectorLayoutComponent
]; ];

View File

@ -1 +1,8 @@
<router-outlet></router-outlet> <div class="layout-container" fxFlexFill fxLayout="column">
<div *ngIf="showTopbar" class="top-bar" fxFlex="50px">
<app-layouts-top-bar class="no-navi-top-bar"></app-layouts-top-bar>
</div>
<div class="layout-content" fxFlex="1 1 auto">
<router-outlet></router-outlet>
</div>
</div>

View File

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

View File

@ -6,5 +6,7 @@ import { Component } from '@angular/core';
styleUrls: ['./no-navi.layout.component.scss'] styleUrls: ['./no-navi.layout.component.scss']
}) })
export class NoNaviLayoutComponent { export class NoNaviLayoutComponent {
showTopbar = true;
constructor() {} constructor() {}
} }

View File

@ -0,0 +1,30 @@
<div class="selector">
<div class="selector-title">
<ng-content
class="layout-header-content"
select="[appLayoutsSelector='header']"
></ng-content>
<button
mat-icon-button
matSuffix
aria-label="Clear"
class="btn-close"
color="primary"
(click)="onClickClose($event)"
>
<mat-icon>close</mat-icon>
</button>
</div>
<div class="selector-contents">
<ng-content
class="layout-body-content"
select="[appLayoutsSelector='body']"
></ng-content>
</div>
<div class="footer">
<ng-content
class="layout-footer-content"
select="[appLayoutsSelector='footer']"
></ng-content>
</div>
</div>

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

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

View File

@ -0,0 +1,30 @@
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
EventEmitter,
Output
} from '@angular/core';
@Component({
selector: 'app-layout-selector',
templateUrl: './selector.layout.component.html',
styleUrls: ['./selector.layout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SelectorLayoutComponent implements OnInit, OnDestroy {
@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,11 @@
<mat-toolbar class=".title-bar"> <div class="title-bar">
<ucap-title-bar <ucap-title-bar
[platform]="platform" [platform]="platform"
[native]="native" [windowState]="windowState"
(closed)="onClosedTitleBar()" (closed)="onClosed()"
(maximized)="onMaximizedTitleBar()" (maximized)="onMaximized($event)"
(minimized)="onMinimizedTitleBar()" (minimized)="onMinimized()"
> >
<ng-content></ng-content>
</ucap-title-bar> </ucap-title-bar>
</mat-toolbar> </div>

View File

@ -1,12 +1,6 @@
.title-bar { .title-bar {
width: 100%; width: 100%;
height: 50px; height: 100%;
-webkit-user-select: none;
-webkit-app-region: drag;
position: fixed;
right: 0;
top: 0;
display: flex;
padding: 0; padding: 0;
cursor: pointer; background-color: 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 { Store } from '@ngrx/store';
import { NativeService, WindowState, NativeType } from '@ucap/native';
import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native';
@Component({ @Component({
selector: 'app-layouts-top-bar', selector: 'app-layouts-top-bar',
templateUrl: './top-bar.component.html', templateUrl: './top-bar.component.html',
@ -9,17 +18,64 @@ import { Store } from '@ngrx/store';
}) })
export class TopBarComponent implements OnInit, OnDestroy { export class TopBarComponent implements OnInit, OnDestroy {
platform = 'win32'; 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;
}
});
}
onClosedTitleBar() {} ngOnInit() {
this.nativeService
.window_onState$()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((windowState) => {
this.windowState = windowState;
});
}
onMaximizedTitleBar() {} ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onMinimizedTitleBar() {} onClosed() {
this.nativeService.window_close();
}
onMaximized(altKey: boolean) {
this.nativeService.window_maximize(altKey);
}
onMinimized() {
this.nativeService.window_minimize();
}
} }

View File

@ -5,12 +5,24 @@ import { RouterModule } from '@angular/router';
import { FlexLayoutModule } from '@angular/flex-layout'; 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 { MatIconModule } from '@angular/material/icon';
import { MatTabsModule } from '@angular/material/tabs'; import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav'; import { MatSidenavModule } from '@angular/material/sidenav';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { UiModule } from '@ucap/ng-ui'; import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { UiButtonModule } from '@ucap/ng-ui/button';
import { UiNativeModule } from '@ucap/ng-ui/native';
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
import { COMPONENTS } from './components'; import { COMPONENTS } from './components';
import { DIALOGS } from './dialogs'; import { DIALOGS } from './dialogs';
@ -18,20 +30,37 @@ import { DIALOGS } from './dialogs';
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
RouterModule, RouterModule,
FlexLayoutModule, FlexLayoutModule,
MatBadgeModule,
MatButtonModule,
MatIconModule, MatIconModule,
MatTabsModule, MatMenuModule,
MatSelectModule,
MatSidenavModule, MatSidenavModule,
MatTabsModule,
MatToolbarModule, MatToolbarModule,
MatTooltipModule,
UiModule PerfectScrollbarModule,
I18nModule,
UiButtonModule,
UiNativeModule,
AppOrganizationModule
], ],
exports: [...COMPONENTS, ...DIALOGS], exports: [...COMPONENTS, ...DIALOGS],
declarations: [...COMPONENTS, ...DIALOGS], declarations: [...COMPONENTS, ...DIALOGS],
entryComponents: [...DIALOGS] entryComponents: [...DIALOGS],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['common']
}
]
}) })
export class AppLayoutsModule {} export class AppLayoutsModule {}

View File

@ -0,0 +1,7 @@
import { SortViewType } from '@app/pages/group/types/sort-view.type';
export interface GroupOpenInfo {
lastGroupSeq: number;
groupSeqs: number[];
showType: SortViewType;
}

View File

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

View File

@ -1,9 +1,9 @@
import { import {
GeneralSetting as UCAPGeneralSetting, GeneralSetting as UCAPGeneralSetting,
NotificationSetting as UCAPNotificationSetting, NotificationSetting as UCAPNotificationSetting
ChatSetting as UCAPChatSetting, } from '@ucap/domain-common';
PresenceSetting as UCAPPresenceSetting import { PresenceSetting as UCAPPresenceSetting } from '@ucap/domain-status';
} from '@ucap/protocol-option'; import { ChatSetting as UCAPChatSetting } from '@ucap/domain-chat';
// tslint:disable-next-line: no-empty-interface // tslint:disable-next-line: no-empty-interface
export interface GeneralSetting extends UCAPGeneralSetting {} export interface GeneralSetting extends UCAPGeneralSetting {}

View File

@ -1,4 +1,4 @@
import { UserStore as UCAPUserStore } from '@ucap/core'; import { UserStore as UCAPUserStore } from '@ucap/domain-authentication';
import { Settings } from './settings'; import { Settings } from './settings';

View File

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

View File

@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { AppAuthenticationModule } from '@app/ucap/authentication/authentication.module';
import { AppAccountSectionModule } from '@app/sections/account/account.section.module'; import { AppAccountSectionModule } from '@app/sections/account/account.section.module';
import { AppAccountRoutingPageModule } from './account-routing.page.module'; import { AppAccountRoutingPageModule } from './account-routing.page.module';
@ -13,6 +14,7 @@ import { COMPONENTS } from './components';
imports: [ imports: [
CommonModule, CommonModule,
FlexLayoutModule, FlexLayoutModule,
AppAuthenticationModule,
AppAccountSectionModule, AppAccountSectionModule,
AppAccountRoutingPageModule AppAccountRoutingPageModule
], ],

View File

@ -1,12 +1,3 @@
<div class="login-page-container" fxLayout="row"> <div class="login-container">
<div fxFlex="1 1 auto">Login</div> <app-authentication-login></app-authentication-login>
<div class="login-section-container">
<app-sections-account-login
[companyGroupCode]="companyGroupCode"
[fixedCompanyCode]="fixedCompanyCode"
[userStore]="userStore"
[useRememberMe]="useRememberMe"
[useAutoLogin]="useAutoLogin"
></app-sections-account-login>
</div>
</div> </div>

View File

@ -1,6 +1,67 @@
.login-page-container { @import '~@ucap/lg-scss/mixins';
.login-section-container {
width: 400px; $login-bg-w: 100/1920;
margin: 40px; $login-bg-h: 100/1080;
}
.login-container {
width: 100%;
height: 100%;
overflow: auto;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: $bg-gray;
background-image: url(/assets/images/bg/bg_login_circle_square01.svg),
url(/assets/images/bg/bg_login_circle_stroke01.svg),
url(/assets/images/bg/bg_login_circle01.svg),
url(/assets/images/bg/bg_login_circle03.svg),
url(/assets/images/bg/bg_login_circle_diagonal01.svg),
url(/assets/images/bg/bg_login_circle_square02.svg),
url(/assets/images/bg/bg_login_circle04.svg),
url(/assets/images/bg/bg_login_circle05.svg),
url(/assets/images/bg/bg_login_circle06.svg),
url(/assets/images/bg/bg_login_circle07.svg),
url(/assets/images/bg/bg_login_circle08.svg),
url(/assets/images/bg/bg_login_circle_diagonal02.svg),
url(/assets/images/bg/bg_login_circle_diagonal03.svg),
url(/assets/images/bg/bg_login_circle_stroke02.svg),
url(/assets/images/bg/bg_login_circle_stroke03.svg),
url(/assets/images/bg/bg_login_circle_stroke04.svg),
url(/assets/images/bg/bg_login_circle_stroke05.svg),
url(/assets/images/bg/bg_login_polygon01.svg),
url(/assets/images/bg/bg_login_polygon02.svg);
background-repeat: no-repeat;
background-position: unquote($login-bg-w * 323 + '%')
unquote($login-bg-h * 179 + '%'),
unquote($login-bg-w * -147 + '%') unquote($login-bg-h * 18 + '%'),
unquote($login-bg-w * 285 + '%') unquote($login-bg-h * 226 + '%'),
unquote($login-bg-w * 1235 + '%') unquote($login-bg-h * -101 + '%'),
unquote($login-bg-w * 1397 + '%') unquote($login-bg-h * 163 + '%'),
unquote($login-bg-w * 1569 + '%') unquote($login-bg-h * 580 + '%'),
unquote($login-bg-w * 426 + '%') unquote($login-bg-h * 293 + '%'),
unquote($login-bg-w * 1531 + '%') unquote($login-bg-h * 250 + '%'),
unquote($login-bg-w * 1774 + '%') unquote($login-bg-h * 166 + '%'),
unquote($login-bg-w * 1362 + '%') unquote($login-bg-h * 673 + '%'),
unquote($login-bg-w * 152 + '%') unquote($login-bg-h * 730 + '%'),
unquote($login-bg-w * 286 + '%') unquote($login-bg-h * 719 + '%'),
unquote($login-bg-w * 683 + '%') unquote($login-bg-h * 593 + '%'),
unquote($login-bg-w * 498 + '%') unquote($login-bg-h * 453 + '%'),
unquote($login-bg-w * 1709 + '%') unquote($login-bg-h * 599 + '%'),
unquote($login-bg-w * 1395 + '%') unquote($login-bg-h * 989 + '%'),
unquote(100 + $login-bg-w * 89 + '%') unquote(100 + $login-bg-h * 137 + '%'),
unquote($login-bg-w * 90 + '%') unquote($login-bg-h * 463 + '%'),
unquote($login-bg-w * 549 + '%') unquote($login-bg-h * 874 + '%');
background-size: unquote($login-bg-w * 79 + '%'),
unquote($login-bg-w * 333 + '%'), unquote($login-bg-w * 84 + '%'),
unquote($login-bg-w * 172 + '%'), unquote($login-bg-w * 210 + '%'),
unquote($login-bg-w * 94 + '%'), unquote($login-bg-w * 44 + '%'),
unquote($login-bg-w * 118 + '%'), unquote($login-bg-w * 52 + '%'),
unquote($login-bg-w * 70 + '%'), unquote($login-bg-w * 172 + '%'),
unquote($login-bg-w * 82 + '%'), unquote($login-bg-w * 135 + '%'),
unquote($login-bg-w * 102 + '%'), unquote($login-bg-w * 130 + '%'),
unquote($login-bg-w * 184 + '%'), unquote($login-bg-w * 370 + '%'),
unquote($login-bg-w * 122 + '%'), unquote($login-bg-w * 75 + '%');
} }

View File

@ -6,6 +6,8 @@ import { environment } from '@environments';
import { UserStore } from '@app/models/user-store'; import { UserStore } from '@app/models/user-store';
import { AppKey } from '@app/types/app-key.type'; import { AppKey } from '@app/types/app-key.type';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'app-pages-account-login', selector: 'app-pages-account-login',
@ -23,14 +25,24 @@ export class LoginPageComponent implements OnInit, OnDestroy {
readonly fixedCompanyCode = environment.companyConfig.fixedCompanyCode; readonly fixedCompanyCode = environment.companyConfig.fixedCompanyCode;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(private localStorageService: LocalStorageService) {} constructor(private localStorageService: LocalStorageService) {}
ngOnInit(): void { ngOnInit(): void {
this.userStore = this.localStorageService.encGet<UserStore>( this.localStorageService
.encGet$<UserStore>(
AppKey.UserStore, AppKey.UserStore,
environment.productConfig.localEncriptionKey environment.productConfig.localEncriptionKey
); )
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((userStore) => (this.userStore = userStore));
} }
ngOnDestroy(): void {} ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
} }

View File

@ -0,0 +1,22 @@
<div class="logout-container">
<div class="logout-content">
<div class="logout-title"></div>
<p class="guide-text">
서비스를 이용하시려면 다시 로그인하여 주시기 바랍니다.<br />
<span
>항상 더 좋은 서비스로 고객님께 보답하는 M-Messenger가 되겠습니다.</span
>
</p>
<!--<p class="guide-text">
다른 기기에서 동일한 계정으로 로그인되었습니다.<br />본인이 아닌 경우
비밀번호 변경을 하시기 바랍니다.
<span
>항상 더 좋은 서비스로 고객님께 보답하는 M-Messenger가 되겠습니다.</span
>
</p>-->
<span class="guide-move">OO초 후 로그인 페이지로 자동 이동 됩니다 </span>
</div>
</div>

View File

@ -0,0 +1,59 @@
@import '~@ucap/lg-scss/mixins';
.logout-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #f3f4f5;
background-image: url(/assets/images/ico/img_logout.png);
background-position: 50% bottom;
background-repeat: no-repeat;
background-size: 90% auto;
@include screen(xs) {
background-size: 100% auto;
}
.logout-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
max-width: 80%;
transform: translateY(-70%);
.logout-title {
display: block;
width: 250px;
height: 62px;
text-align: center;
background: url(/assets/images/ico/img_logout_text.svg);
}
.guide-text {
font-size: 1.6em;
color: #666;
font-weight: 600;
text-align: center;
@include screen(xs) {
font-size: 1.4em;
}
span {
display: inline-flex;
font-size: 0.7em;
color: #999;
font-weight: normal;
margin-top: 0.7em;
}
}
.guide-move {
display: inline-flex;
padding: 0.8em 1.4em;
border-radius: 100px;
background-color: #fd578a;
color: #ffffff;
}
}
}

View File

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

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

@ -3,12 +3,19 @@ import { Routes, RouterModule } from '@angular/router';
import { IndexPageComponent } from './components/index.page.component'; import { IndexPageComponent } from './components/index.page.component';
import { SidenavPageComponent } from './components/sidenav.page.component'; import { SidenavPageComponent } from './components/sidenav.page.component';
import { CallHistoryPageComponent } from './components/call-history.page.component';
const routes: Routes = [ const routes: Routes = [
{ {
path: 'index', path: 'index',
outlet: 'content', outlet: 'content',
component: IndexPageComponent // component: IndexPageComponent
component: CallHistoryPageComponent
},
{
path: 'callhistory',
outlet: 'content',
component: CallHistoryPageComponent
}, },
{ {
path: '', path: '',

View File

@ -3,18 +3,52 @@ import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTabsModule } from '@angular/material/tabs';
import { UiDateModule } from '@ucap/ng-ui/date';
import { UiPhoneModule } from '@ucap/ng-ui/phone';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
import { AppCallSectionModule } from '@app/sections/call/call.section.module';
import { AppCallRoutingPageModule } from './call-routing.page.module'; import { AppCallRoutingPageModule } from './call-routing.page.module';
import { COMPONENTS } from './components';
import { IndexPageComponent } from './components/index.page.component'; import { AppGroupModule } from '@app/ucap/group/group.module';
import { SidenavPageComponent } from './components/sidenav.page.component';
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent];
export { IndexPageComponent, SidenavPageComponent };
@NgModule({ @NgModule({
imports: [CommonModule, FlexLayoutModule, AppCallRoutingPageModule], imports: [
CommonModule,
FlexLayoutModule,
MatButtonModule,
MatIconModule,
MatTabsModule,
I18nModule,
UiDateModule,
UiPhoneModule,
OrganizationUiModule,
AppOrganizationModule,
AppCallSectionModule,
AppCallRoutingPageModule,
AppGroupModule
],
declarations: [...COMPONENTS], declarations: [...COMPONENTS],
entryComponents: [] entryComponents: [],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['call', 'common']
}
]
}) })
export class AppCallPageModule {} export class AppCallPageModule {}

View File

@ -0,0 +1,58 @@
<div class="contents-main" fxFlexFill fxLayout="column">
<div class="subtitle" fxFlex="0 0 50px" fxLayout="row">
<div class="title">{{ 'call:label.callHistory' | ucapI18n }}</div>
<div class="call-info" fxLayout="row">
<div class="info send">
<mat-icon>call_made</mat-icon>{{ 'call:label.send' | ucapI18n }}
</div>
<div class="info receive">
<mat-icon>call_received</mat-icon>{{ 'call:label.recieve' | ucapI18n }}
</div>
<div class="info away">
<mat-icon>call_missed</mat-icon>{{ 'call:label.missed' | ucapI18n }}
</div>
<div class="info unanswered">
<mat-icon>call_missed_outgoing</mat-icon
>{{ 'call:label.unanswered' | ucapI18n }}
</div>
</div>
</div>
<div class="user-title" fxFlex="0 0 90px" fxLayout="row">
<div class="user-profile-info">
<div class="profile-image">
<app-group-profile-image-01
[userInfo]="userInfo"
[versionInfo]="versionInfo"
(openProfile)="onOpenProfile($event)"
></app-group-profile-image-01>
</div>
<div class="user-info">
<div class="user-n-g">
<div class="user-name">
{{ userInfo | ucapOrganizationTranslate: 'name':undefined:'' }}
</div>
<div class="user-grade">
{{ userInfo | ucapOrganizationTranslate: 'grade':undefined:' ' }}
</div>
</div>
<div class="dept-name">
{{ userInfo | ucapOrganizationTranslate: 'deptName':undefined:' ' }}
</div>
</div>
</div>
<div class="user-call-info" fxLayout="row">
<div *ngIf="!!userInfo && !!userInfo.hpNumber" class="mobile">
{{ userInfo.hpNumber | ucapPhoneNumber }}
<img src="assets/images/ico/btn_list_mobile_a24.svg" alt="" />
</div>
<div *ngIf="!!userInfo && !!userInfo.lineNumber" class="lineNumber">
{{ userInfo.lineNumber | ucapPhoneNumber }}
<img src="assets/images/ico/btn_list_call_a24.svg" alt="" />
</div>
</div>
</div>
<div class="call-info-container" fxFlex="1 1 auto">
<app-sections-call-info></app-sections-call-info>
</div>
</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

@ -0,0 +1,32 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { CallHistoryPageComponent } from './call-history.page.component';
describe('app::pages::chat::CallHistoryPageComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [CallHistoryPageComponent]
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(CallHistoryPageComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'ucap-lg-web'`, () => {
const fixture = TestBed.createComponent(CallHistoryPageComponent);
const app = fixture.componentInstance;
});
it('should render title', () => {
const fixture = TestBed.createComponent(CallHistoryPageComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain(
'ucap-lg-web app is running!'
);
});
});

View File

@ -0,0 +1,129 @@
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef
} from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil, withLatestFrom } from 'rxjs/operators';
import { QueryParams } from '../types/params.type';
import { Store, select } from '@ngrx/store';
import { UserInfoSS } from '@ucap/domain-organization';
import { UserSelector } from '@ucap/ng-store-organization';
import { AppOrganizationService } from '@app/services/app-organization.service';
import { ConfigurationSelector } from '@ucap/ng-store-authentication';
import {
ProfileDialogComponent,
ProfileDialogData,
ProfileDialogResult
} from '@app/sections/organization/dialogs/profile.dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { VersionInfo } from '@ucap/domain-authentication';
@Component({
selector: 'app-pages-call-history',
templateUrl: './call-history.page.component.html',
styleUrls: ['./call-history.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CallHistoryPageComponent implements OnInit, OnDestroy {
isMe: boolean;
userSeq: string;
userInfo: UserInfoSS;
versionInfo: VersionInfo;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private dialog: MatDialog,
private store: Store<any>,
private appOrganizationService: AppOrganizationService,
private activatedRoute: ActivatedRoute,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit(): void {
this.activatedRoute.queryParams
.pipe(
takeUntil(this.ngOnDestroySubject),
withLatestFrom(this.store.pipe(select(UserSelector.user)))
)
.subscribe(([params, user]) => {
const seqParam = params[QueryParams.ID];
// initializing by userSeq Change.
if (this.userSeq !== seqParam) {
}
// setting userSeq.
this.userSeq = !!seqParam
? seqParam
: !!user
? String(user.info.seq)
: undefined;
if (!!user && this.userSeq === String(user.info.seq)) {
this.isMe = true;
} else {
this.isMe = false;
}
if (!!this.userSeq) {
const self = this;
this.appOrganizationService
.getUserInfo({ userSeq: this.userSeq, user })
.then((result) => {
console.log('getUserInfo : ', result);
self.userInfo = result.userInfo;
})
.catch((e) => {
self.userInfo = undefined;
})
.finally(() => {
this.changeDetectorRef.markForCheck();
});
} else {
this.userInfo = undefined;
this.changeDetectorRef.markForCheck();
}
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo) => {
this.versionInfo = versionInfo;
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onOpenProfile(userInfo: UserInfoSS): void {
if (!!userInfo) {
this.dialog.open<
ProfileDialogComponent,
ProfileDialogData,
ProfileDialogResult
>(ProfileDialogComponent, {
panelClass: 'mid-create-dialog',
data: {
userSeq: userInfo.seq
}
});
}
}
}

View File

@ -1 +1,39 @@
Index page of call is works! <div class="index-page-call-info">
<div class="ico-page-call">
<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="call-index-copy">
{{ 'call:noSelectHistory' | ucapI18n }}
</p>
</div>

View File

@ -0,0 +1,33 @@
@import '~@ucap/lg-scss/mixins';
.index-page-call-info {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.ico-page-call {
width: 166px;
height: 142px;
margin-top: -80px;
}
.call-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;
}
@include screen(xs) {
.ico-page-call {
width: 120px;
height: auto;
margin-top: -40px;
}
.call-index-copy {
font-size: 1.2em;
margin: 20px 0 0;
}
}
}

View File

@ -1,4 +1,9 @@
import { IndexPageComponent } from './index.page.component'; import { IndexPageComponent } from './index.page.component';
import { SidenavPageComponent } from './sidenav.page.component'; import { SidenavPageComponent } from './sidenav.page.component';
import { CallHistoryPageComponent } from './call-history.page.component';
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent]; export const COMPONENTS = [
IndexPageComponent,
SidenavPageComponent,
CallHistoryPageComponent
];

View File

@ -1,3 +1,21 @@
<div fxFlexFill> <div class="sidenav-container call" fxFlexFill fxLayout="column">
sidenav page of call is works! <div class="title-section" fxFlex="0 0 50px" fxLayout="row">
<div class="title">
<h3 fxFlex="1 1 auto">{{ 'call:label.call' | ucapI18n }}</h3>
</div>
</div>
<div class="extra-box" fxFlex="0 0 50px">
<app-organization-search-for-tenant
[isBackspaceCanceled]="isBackspaceCanceled"
[(searchData)]="companySearchData"
(canceled)="onSearchCancel()"
>
</app-organization-search-for-tenant>
</div>
<div fxFlex="1 1 auto">
<app-sections-call-list
[searchObj]="companySearchData"
></app-sections-call-list>
</div>
</div> </div>

View File

@ -0,0 +1,29 @@
@import '~@ucap/lg-scss/mixins';
.sidenav-container.call {
overflow: hidden;
display: flex;
flex-flow: column;
align-content: flex-start;
background-color: #f1f2f6;
.title-section {
display: flex;
flex-flow: column;
background-color: $white;
.title {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
padding: 0 0 0 17px;
background-color: $white;
align-items: center;
width: 100%;
h3 {
@include font-family-txt(18, left, $lipstick);
align-items: center;
font-weight: 600;
}
}
}
}

View File

@ -1,13 +1,109 @@
import { Component, Inject } from '@angular/core'; import { Subject, combineLatest } from 'rxjs';
import { Router } from '@angular/router'; import { takeUntil } from 'rxjs/operators';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef
} from '@angular/core';
import { Store, select } from '@ngrx/store';
import { User } from '@ucap/domain-organization';
import { LoginSession, LoginInfo } from '@ucap/domain-authentication';
import { SearchType, SearchDirectionByDateType } from '@ucap/domain-call';
import { CallHistoryInfoRequest } from '@ucap/api-contact';
import { LogService } from '@ucap/ng-logger'; import { LogService } from '@ucap/ng-logger';
import { CallActions } from '@ucap/ng-store-call';
import { UserSelector } from '@ucap/ng-store-organization';
import { LoginSelector } from '@ucap/ng-store-authentication';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import moment from 'moment';
import { environment } from '@environments';
@Component({ @Component({
selector: 'app-pages-call-sidenav', selector: 'app-pages-call-sidenav',
templateUrl: './sidenav.page.component.html', templateUrl: './sidenav.page.component.html',
styleUrls: ['./sidenav.page.component.scss'] styleUrls: ['./sidenav.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class SidenavPageComponent { export class SidenavPageComponent implements OnInit, OnDestroy {
constructor(private logService: LogService) {} set companySearchData(searchData: SearchData) {
if (!!searchData && searchData.searchWord !== '') {
this._companySearchData = { ...searchData, bySearch: true };
} else {
this._companySearchData = { ...searchData, bySearch: false };
}
}
get companySearchData() {
return this._companySearchData;
}
// tslint:disable-next-line: variable-name
_companySearchData: SearchData;
isBackspaceCanceled = true;
user: User;
loginInfo: LoginInfo;
loginSession: LoginSession;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private store: Store<any>,
private appAuthenticationService: AppAuthenticationService,
private changeDetectorRef: ChangeDetectorRef,
private logService: LogService
) {}
ngOnInit(): void {
combineLatest([
this.store.pipe(select(UserSelector.user)),
this.store.pipe(select(LoginSelector.loginInfo))
])
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe(([user, loginInfo]) => {
if (!!user && !!loginInfo) {
this.user = user;
this.loginInfo = loginInfo;
this.loginSession = this.appAuthenticationService.getLoginSession();
this._retrieveCallHistory();
}
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onSearchCancel() {
this.companySearchData = { ...this.companySearchData, searchWord: '' };
}
private _retrieveCallHistory() {
const searchStartDate = moment(new Date()).format('YYYY-MM-DD_HH:mm:ss');
const req: CallHistoryInfoRequest = {
userSeq: String(this.user.info.seq),
deviceType: this.loginSession.deviceType,
token: this.loginInfo.tokenString,
searchType: SearchType.All,
searchUserSeq: '',
searchNumber: '',
searchDate: '',
searchStartDate,
searchListCount:
environment.productConfig.call.historyRequestDefaultCount,
searchDirection: SearchDirectionByDateType.Before
};
this.store.dispatch(CallActions.callHistory({ req }));
}
} }

View File

@ -0,0 +1,3 @@
export enum QueryParams {
ID = 'id'
}

View File

@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router';
import { IndexPageComponent } from './components/index.page.component'; import { IndexPageComponent } from './components/index.page.component';
import { SidenavPageComponent } from './components/sidenav.page.component'; import { SidenavPageComponent } from './components/sidenav.page.component';
import { ChatRoomPageComponent } from './components/chat-room.page.component';
const routes: Routes = [ const routes: Routes = [
{ {
@ -10,6 +11,11 @@ const routes: Routes = [
outlet: 'content', outlet: 'content',
component: IndexPageComponent component: IndexPageComponent
}, },
{
path: 'chatroom',
outlet: 'content',
component: ChatRoomPageComponent
},
{ {
path: '', path: '',
component: SidenavPageComponent component: SidenavPageComponent

View File

@ -3,18 +3,52 @@ import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatTooltipModule } from '@angular/material/tooltip';
import { AppChatSectionModule } from '@app/sections/chat/chat.section.module';
import { AppChatRoutingPageModule } from './chat-routing.page.module'; import { AppChatRoutingPageModule } from './chat-routing.page.module';
import { IndexPageComponent } from './components/index.page.component'; import { UiCoreModule } from '@ucap/ng-ui/core';
import { SidenavPageComponent } from './components/sidenav.page.component'; import { UiDateModule } from '@ucap/ng-ui/date';
import { UiViewerModule } from '@ucap/ng-ui/viewer';
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent]; import { COMPONENTS } from './components';
import { UCAP_I18N_NAMESPACE, I18nModule } from '@ucap/ng-i18n';
export { IndexPageComponent, SidenavPageComponent };
@NgModule({ @NgModule({
imports: [CommonModule, FlexLayoutModule, AppChatRoutingPageModule], imports: [
CommonModule,
FlexLayoutModule,
MatIconModule,
MatMenuModule,
MatCheckboxModule,
MatButtonModule,
MatSidenavModule,
MatTooltipModule,
UiCoreModule,
UiDateModule,
UiViewerModule,
AppChatSectionModule,
AppChatRoutingPageModule,
I18nModule
],
declarations: [...COMPONENTS], declarations: [...COMPONENTS],
entryComponents: [] entryComponents: [],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['chat', 'common']
}
]
}) })
export class AppChatPageModule {} export class AppChatPageModule {}

View File

@ -0,0 +1,87 @@
<div class="contents-main" fxFlexFill fxLayout="column">
<div class="subtitle" fxFlex="0 0 50px">
<app-sections-chat-info
[roomId]="roomId"
(openChatSearch)="isChatSearch = true"
(rightDrawerToggle)="onRightDrawerToggle($event)"
></app-sections-chat-info>
</div>
<app-sections-chat-chat-search
[isChatSearch]="isChatSearch"
(chatSearch)="onChatSearch($event)"
(closeChatSearch)="isChatSearch = false"
></app-sections-chat-chat-search>
<mat-drawer-container autosize fxFlex="1 1 auto" fxLayout="column">
<div
class="message-box-container"
fxFlex="1 1 auto"
fxLayout="column"
ucapFileUploadFor01
(fileSelected)="onFileSelected($event)"
(fileDragEnter)="onFileDragEnter($event)"
(fileDragOver)="onFileDragOver($event)"
(fileDragLeave)="onFileDragLeave($event)"
>
<div class="message-area" fxFlex="1 1 auto">
<app-sections-chat-message
#chatMessageSections
[roomId]="roomId"
[translationSimpleview]="translationSimpleview"
[eventSendTrigger$]="eventSendTriggerSubject.asObservable()"
></app-sections-chat-message>
</div>
<div class="message-input" fxFlex="0 0 auto">
<app-sections-chat-form
#chatForm
[roomId]="roomId"
(changeTranslationSimpleview)="translationSimpleview = $event"
(eventSendTrigger)="eventSendTriggerSubject.next($event)"
(openFormSelector)="onOpenFormSelector()"
></app-sections-chat-form>
</div>
</div>
<mat-drawer #chatRightDrawer mode="side" position="end" class="rightDrawer">
<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

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

View File

@ -0,0 +1,143 @@
import { Subject, BehaviorSubject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Store } from '@ngrx/store';
import { MatDrawer } from '@angular/material/sidenav';
import { ChattingActions } from '@ucap/ng-store-chat';
import { MessageSectionComponent } from '@app/sections/chat/components/message.section.component';
import {
FormSectionComponent,
SelectorType
} from '@app/sections/chat/components/form.section.component';
import { QueryParams } from '../types/params.type';
import { ChatDrawType } from '../types/chat-draw.type';
import { DrawInfo } from '../models/draw-info';
import { SearchInfo } from '../models/search-info';
@Component({
selector: 'app-pages-chat-room',
templateUrl: './chat-room.page.component.html',
styleUrls: ['./chat-room.page.component.scss']
})
export class ChatRoomPageComponent implements OnInit, OnDestroy {
isChatSearch = false;
searchObj: SearchInfo = {
isShowSearch: false,
searchWord: ''
};
roomId: string;
translationSimpleview = false;
drawerType: ChatDrawType | null;
returnDrawerType: ChatDrawType | null;
eventSendTriggerSubject: BehaviorSubject<any> = new BehaviorSubject<any>(0);
@ViewChild('chatMessageSections', { static: false })
chatMessageSections: MessageSectionComponent;
@ViewChild('chatRightDrawer', { static: false })
chatRightDrawer: MatDrawer;
@ViewChild('chatForm', { static: false })
chatForm: FormSectionComponent;
ChatDrawType = ChatDrawType;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private store: Store<any>,
private activatedRoute: ActivatedRoute
) {}
ngOnInit(): void {
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params: Params) => {
const seqParam = params[QueryParams.ROOM_ID];
// initializing by roomId Change.
if (this.roomId !== seqParam) {
// close Right Drawer.
if (!!this.chatRightDrawer) {
this.chatRightDrawer.close();
}
// close Chat Search area.
this.isChatSearch = false;
this.searchObj = {
isShowSearch: false,
searchWord: ''
};
}
// setting roomId.
this.roomId = !!seqParam ? seqParam : undefined;
});
}
ngOnDestroy(): void {
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({}));
}
/** About Form Selector */
onOpenFormSelector() {
if (!!this.chatMessageSections) {
this.chatMessageSections.refreshAndScrollToBottom();
}
}
/** About Right drawer */
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();
}
/** About File Drag & Drop */
onFileSelected(fileList: FileList) {
if (!!this.chatForm) {
this.chatForm.onDragAndDropFileupload(fileList); // direct upload
// this.chatForm.onDragAndDropStandbyFileupload(fileList); // standby and confirm upload.(complate not yet)
}
}
onFileDragEnter(event: DataTransferItemList) {
// if (!!this.chatForm) {
// this.chatForm.onOpenSelector(SelectorType.FILEUPLOAD);
// }
}
onFileDragOver(event: DragEvent) {}
onFileDragLeave(event: DragEvent) {
// if (!!this.chatForm) {
// this.chatForm.clearSelector();
// }
}
/** About Chat search. */
onChatSearch(search: SearchInfo) {
this.searchObj = search;
}
}

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,33 @@
@import '~@ucap/lg-scss/mixins';
.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;
}
@include screen(xs) {
.ico-page-chat {
width: 120px;
height: auto;
margin-top: -40px;
}
.chat-index-copy {
font-size: 1.2em;
margin: 20px 0 0;
}
}
}

View File

@ -1,4 +1,9 @@
import { IndexPageComponent } from './index.page.component'; import { IndexPageComponent } from './index.page.component';
import { SidenavPageComponent } from './sidenav.page.component'; import { SidenavPageComponent } from './sidenav.page.component';
import { ChatRoomPageComponent } from './chat-room.page.component';
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent]; export const COMPONENTS = [
IndexPageComponent,
SidenavPageComponent,
ChatRoomPageComponent
];

View File

@ -1,3 +1,87 @@
<div fxFlexFill> <div fxFlexFill class="sidenav-container">
sidenav page of chat is works! <div class="chat-header">
<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>
<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
aria-label="exit-close"
*ngIf="checkable"
(click)="onToggleChackable(false)"
>
<mat-icon>close</mat-icon>
</button>
</div>
</div>
<app-sections-chat-search
(keyDownEnter)="onKeyDownSearch($event)"
(searchCancel)="onClickCancel()"
[style.display]="checkable ? 'none' : 'block'"
></app-sections-chat-search>
<div class="exitRoomInfo" *ngIf="checkable">
<div 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 class="chat-exit-checkbox">
<mat-checkbox
#allCheck
aria-label="all select exit room "
[checked]="getCheckedAllItem()"
(change)="onToggleAllItem(allCheck.checked)"
(click)="$event.stopPropagation()"
>
</mat-checkbox>
</div>
</div>
<app-sections-chat-list
fxFlexFill
[searchObj]="searchObj"
[checkable]="checkable"
[selectedRoomList]="selectedRoomList"
(searchResultList)="onSearchResultList($event)"
(toggleItem)="onToggleItem($event)"
></app-sections-chat-list>
</div> </div>

View File

@ -0,0 +1,55 @@
@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;
align-items: center;
width: 100%;
height: 50px;
min-height: 50px;
padding: 0 5px 0 17px;
background-color: $white;
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

@ -1,13 +1,224 @@
import { Component, Inject } from '@angular/core'; import { Subject, of } from 'rxjs';
import { Router } from '@angular/router'; import { takeUntil, take, map, catchError } from 'rxjs/operators';
import { Component, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Store, select } from '@ngrx/store';
import { MatDialog } from '@angular/material/dialog';
import { RoomInfo } from '@ucap/domain-chat';
import { ExitAllRequest } from '@ucap/protocol-room';
import { LogService } from '@ucap/ng-logger'; import { LogService } from '@ucap/ng-logger';
import { I18nService } from '@ucap/ng-i18n';
import { RoomSelector, RoomActions } from '@ucap/ng-store-chat';
import {
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
} from '@ucap/ng-ui/core';
import { AppChatService } from '@app/services/app-chat.service';
import { AppRoomSelector } from '@app/store/state';
import { SearchInfo } from '../models/search-info';
import { QueryParams } from '../types/params.type';
@Component({ @Component({
selector: 'app-pages-chat-sidenav', selector: 'app-pages-chat-sidenav',
templateUrl: './sidenav.page.component.html', templateUrl: './sidenav.page.component.html',
styleUrls: ['./sidenav.page.component.scss'] styleUrls: ['./sidenav.page.component.scss']
}) })
export class SidenavPageComponent { export class SidenavPageComponent implements OnInit, OnDestroy {
constructor(private logService: LogService) {} searchObj: SearchInfo = {
isShowSearch: false,
searchWord: ''
};
checkable = false;
historyRoomId: string;
roomList: RoomInfo[];
selectedRoomList: RoomInfo[] = [];
searchResultList: RoomInfo[] = [];
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private store: Store<any>,
private dialog: MatDialog,
private i18nService: I18nService,
private logService: LogService,
private activatedRoute: ActivatedRoute,
private appChatService: AppChatService,
private changeDetectorRef: ChangeDetectorRef
) {
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(AppRoomSelector.historyRoomId)
)
.subscribe((historyRoomId) => {
this.historyRoomId = historyRoomId;
});
}
ngOnInit(): void {
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params: Params) => {
const seqParam = params[QueryParams.ROOM_ID];
if (!!seqParam) {
this.appChatService.setHistoryRoomId(seqParam);
}
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))
.subscribe((rooms) => {
rooms = (rooms || []).filter((info) => info.isJoinRoom);
this.roomList = rooms;
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
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('chat:label.exitFromRoom'),
html: this.i18nService.t('chat:dialog.confirmExitFromRoom')
}
});
dialogRef
.afterClosed()
.pipe(take(1))
.subscribe((result) => {
if (!!result && !!result.choice) {
this.store.dispatch(
RoomActions.delMulti({
req: {
roomIds: this.selectedRoomList.map(
(roomInfo) => roomInfo.roomId
)
} as ExitAllRequest
})
);
self.selectedRoomList = [];
self.checkable = false;
}
});
}
} else {
this.selectedRoomList = [];
}
this.checkable = checkable;
}
getCheckedAllItem(): boolean {
const targetRoomList = !!this.searchObj.isShowSearch
? this.searchResultList
: this.roomList;
if (
!targetRoomList ||
targetRoomList.length === 0 ||
targetRoomList.filter(
(item) =>
!(
this.selectedRoomList.filter((info) => info.roomId === item.roomId)
.length > 0
)
).length > 0
) {
return false;
} else {
return true;
}
}
onToggleAllItem(value: boolean): void {
if (!!value) {
const targetRoomList = !!this.searchObj.isShowSearch
? this.searchResultList
: this.roomList;
this.selectedRoomList = targetRoomList.slice();
} else {
this.selectedRoomList = [];
}
this.changeDetectorRef.markForCheck();
}
onToggleItem(event: { checked: boolean; roomInfo: RoomInfo }): void {
if (!!event.checked) {
if (
!this.selectedRoomList.some(
(info) => info.roomId === event.roomInfo.roomId
)
) {
this.selectedRoomList.push(event.roomInfo);
}
} else {
if (!!this.selectedRoomList && this.selectedRoomList.length > 0) {
const index = this.selectedRoomList.findIndex(
(info) => info.roomId === event.roomInfo.roomId
);
if (index > -1) {
this.selectedRoomList.splice(index, 1);
}
}
}
}
/** Searching */
onKeyDownSearch(params: { searchWord: string }) {
this.searchObj = {
isShowSearch: true,
searchWord: params.searchWord
} as SearchInfo;
this.changeDetectorRef.markForCheck();
}
/** Searching cancel */
onClickCancel() {
this.searchObj = {
isShowSearch: false,
searchWord: ''
} as SearchInfo;
this.changeDetectorRef.markForCheck();
}
onSearchResultList(searchResultList: RoomInfo[]) {
this.searchResultList = searchResultList;
}
} }

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

@ -0,0 +1,3 @@
export enum QueryParams {
ROOM_ID = 'roomId'
}

View File

@ -1,3 +1,47 @@
<div> <div fxLayout="column" class="index-container">
index of group <!--<div class="subtitle" fxFlex="60px">Welcome to M-Messenger</div>-->
<div class="content-container" fxFlex="1 1 auto" fxLayout="row">
<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"
[companyList]="companyList"
(openChat)="onOpenChat($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"
[ngClass]="activeLink === 1 ? 'active' : ''"
fxFlex="1 1 auto"
>
<app-sections-group-info [userSeq]="userSeq"></app-sections-group-info>
</div>
</div>
</div> </div>

View File

@ -0,0 +1,119 @@
@import '~@ucap/lg-scss/mixins';
.index-container {
height: 100%;
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 {
display: flex;
align-items: center;
height: 60px;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.16);
@include font-family($font-light);
font-size: 22px;
font-weight: 600;
line-height: 2.73;
color: $gray-re4;
}
.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: 60px;
z-index: 10;
width: calc(100% - 60px);
background-color: rgba(255, 255, 255, 0.3);
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: 0 0 460px !important;
max-width: inherit !important;
border-radius: 2px;
@include screen(custom, min, 1540) {
flex: 0 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;
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,37 +1,269 @@
import { Subscription } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil, take, map } 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 { Router, ActivatedRoute, Params } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { Store, select } from '@ngrx/store';
import { FileUploadItem } from '@ucap/domain-common';
import {
UserInfoSS,
UserInfoF,
UserInfoUpdateType,
User,
Company
} from '@ucap/domain-organization';
import { FileProfileSaveRequest } from '@ucap/api-common';
import { ConferenceCreateRequest } from '@ucap/api-prompt';
import { LogService } from '@ucap/ng-logger'; import { LogService } from '@ucap/ng-logger';
import { I18nService } from '@ucap/ng-i18n';
import { UserSelector, CompanySelector } from '@ucap/ng-store-organization';
import { BuddySelector } from '@ucap/ng-store-group';
import {
LoginSelector,
ConfigurationSelector
} from '@ucap/ng-store-authentication';
import { UserInfoTypes, GroupManageType } 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 { QueryParams } from '../types/params.type';
import { AppChatService } from '@app/services/app-chat.service';
import {
EditUserDialogComponent,
EditUserDialogData,
EditUserDialogResult
} from '@app/sections/group/dialogs/edit-user.dialog.component';
import { LoginInfo, VersionInfo } from '@ucap/domain-authentication';
@Component({ @Component({
selector: 'app-pages-group-index', selector: 'app-pages-group-index',
templateUrl: './index.page.component.html', templateUrl: './index.page.component.html',
styleUrls: ['./index.page.component.scss'] styleUrls: ['./index.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class IndexPageComponent implements OnInit, OnDestroy { export class IndexPageComponent implements OnInit, OnDestroy {
private paramsSubscription: Subscription; private ngOnDestroySubject: Subject<void> = new Subject();
constructor( constructor(
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private store: Store<any>,
private router: Router, 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 = undefined;
user: User;
loginInfo: LoginInfo;
versionInfo: VersionInfo;
activeLink = 0;
tabName: string;
profileName: string;
buddyList: UserInfoTypes[];
companyList: Company[];
ngOnInit(): void { ngOnInit(): void {
this.paramsSubscription = this.activatedRoute.queryParams.subscribe( this.activatedRoute.queryParams
(params: Params) => { .pipe(takeUntil(this.ngOnDestroySubject))
console.log('IndexPageComponent', params[QueryParams.ID]); .subscribe((params: Params) => {
const seqParam = params[QueryParams.ID];
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.loginInfo))
.subscribe((loginInfo) => {
this.loginInfo = loginInfo;
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo) => {
this.versionInfo = versionInfo;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(BuddySelector.buddies))
.subscribe((buddies) => {
this.buddyList = buddies;
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(CompanySelector.companyList)
)
.subscribe((companyList) => {
if (!companyList) {
return;
}
this.companyList = companyList;
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (!!this.paramsSubscription) { if (!!this.ngOnDestroySubject) {
this.paramsSubscription.unsubscribe(); this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
} }
} }
onOpenChat(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) {
const loginSession = this.appAuthenticationService.getLoginSession();
const req: ConferenceCreateRequest = {
userSeq: String(this.user.info.seq),
deviceType: loginSession.deviceType,
tokenKey: this.loginInfo.tokenString,
targetUserSeqs: [userSeq]
};
this.appChatService.openVideoConference(req);
}
onToggleFavorit(params: { userInfo: UserInfoSS; isFavorite: boolean }) {
this.appGroupService.updateBuddy(params.userInfo, params.isFavorite);
}
onToggleBuddy(params: { userInfo: UserInfoSS; isBuddy: boolean }) {
if (params.isBuddy) {
// 동료추가
const dialogRef = this.dialog.open<
EditUserDialogComponent,
EditUserDialogData,
EditUserDialogResult
>(EditUserDialogComponent, {
panelClass: 'max-create-dialog',
data: {
title: this.i18nService.t('group:dialog.title.addBuddy'),
type: GroupManageType.Add,
userInfos: [params.userInfo]
}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result: EditUserDialogResult) => {
if (!!result) {
this.appGroupService.addBuddy(result);
}
})
)
.subscribe();
} else {
// 동료삭제
this.appGroupService.removeBuddy(params.userInfo).then(() => {
this.router.navigate(
[
'group',
{
outlets: { content: 'index' }
}
],
{
queryParams: { id: Number(params.userInfo.seq) }
}
);
});
}
}
onUploadProfileImage(profileImageFileUploadItem: FileUploadItem) {
const loginSession = this.appAuthenticationService.getLoginSession();
const profile = {
userSeq: String(this.user.info.seq),
deviceType: loginSession.deviceType,
token: this.loginInfo.tokenString,
file: profileImageFileUploadItem.file,
fileUploadItem: profileImageFileUploadItem
} as FileProfileSaveRequest;
this.appFileServie.fileProfileSave(
profile,
this.versionInfo.profileUploadUrl
);
}
onUpdateIntro(intro: string) {
this.appGroupService.updateIntro(intro, UserInfoUpdateType.Intro);
}
onUpdateNickname(params: { userInfo: UserInfoTypes; nickname: string }) {
const isBuddy = this.appGroupService.checkBuddy(
this.buddyList,
params.userInfo
);
if (!isBuddy) {
return;
}
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,4 +1,80 @@
<div fxFlexFill class="sidenav-container"> <div class="sidenav-container group" fxFlexFill fxLayout="column">
<app-sections-group-search></app-sections-group-search> <div class="title-section" fxFlex="0 0 50px" fxLayout="row">
<app-sections-group-list fxFlexFill></app-sections-group-list> <div class="title">
<h3 fxFlex="1 1 auto">{{ 'group:label.group' | ucapI18n }}</h3>
<div class="menu-btn">
<button
mat-icon-button
[matMenuTriggerFor]="groupViewMenu"
aria-label="group view menu"
>
<mat-icon>swap_vert</mat-icon>
</button>
<button
mat-icon-button
[matMenuTriggerFor]="groupMenu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</div>
</div>
<div class="extra-box" fxFlex="0 0 50px">
<app-organization-search-for-tenant
[isBackspaceCanceled]="isBackspaceCanceled"
[(searchData)]="companySearchData"
(canceled)="onSearchCancel()"
>
</app-organization-search-for-tenant>
</div>
<div fxFlex="1 1 auto">
<app-sections-group-list
#sectionGroupList
[searchData]="companySearchData"
[showType]="showType"
(clickUser)="onClickUser($event)"
(openProfile)="onOpenProfile($event)"
></app-sections-group-list>
</div>
</div> </div>
<mat-menu #groupMenu="matMenu">
<button mat-menu-item (click)="onClickGroupMenu('GROUP_NEW')">
<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')">
<mat-icon matPrefix>keyboard_arrow_down</mat-icon
>{{ 'group:contextMenu.expandMore' | ucapI18n }}
</button>
<button mat-menu-item (click)="onClickGroupMenu('GROUP_EXPAND_LESS')">
<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(sortViewType.all)">
<mat-icon matPrefix>{{ showGroupMenuIcon(sortViewType.all) }}</mat-icon
>{{ 'group:contextMenu.all' | ucapI18n }}
</button>
<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(sortViewType.onOff)">
<mat-icon matPrefix>{{ showGroupMenuIcon(sortViewType.onOff) }}</mat-icon
>{{ 'group:contextMenu.onOffBuddy' | ucapI18n }}
</button>
</mat-menu>

View File

@ -1,3 +1,29 @@
.sidenav-container { @import '~@ucap/lg-scss/mixins';
.sidenav-container.group {
overflow: hidden; overflow: hidden;
display: flex;
flex-flow: column;
align-content: flex-start;
background-color: #f1f2f6;
.title-section {
display: flex;
flex-flow: column;
background-color: $white;
.title {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
padding: 0 0 0 17px;
background-color: $white;
align-items: center;
width: 100%;
h3 {
@include font-family-txt(18, left, $lipstick);
align-items: center;
font-weight: 600;
}
}
}
} }

View File

@ -1,37 +1,208 @@
import { Subscription } from 'rxjs'; import { of, Subject } from 'rxjs';
import { take, map, catchError, takeUntil } from 'rxjs/operators';
import { Component, OnInit, OnDestroy } from '@angular/core'; import {
import { ActivatedRoute, Router, Params } from '@angular/router'; Component,
OnInit,
OnDestroy,
ChangeDetectorRef,
ViewChild,
ChangeDetectionStrategy,
NgZone
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { MatDialog } from '@angular/material/dialog';
import { UserInfo } from '@ucap/domain-organization';
import { LogService } from '@ucap/ng-logger'; import { LogService } from '@ucap/ng-logger';
import { I18nService } from '@ucap/ng-i18n';
import { QueryParams } from '../types/params.type'; import { SearchData } from '@app/ucap/organization/models/search-data';
import { LoginSession } from '@app/models/login-session';
import { AppUiService } from '@app/services/app-ui.service';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { CreateDialogComponent } from '@app/sections/group/dialogs/create.dialog.component';
import { ListSectionComponent } from '@app/sections/group/components/list.section.component';
import { SortViewType } from '../types/sort-view.type';
@Component({ @Component({
selector: 'app-pages-group-sidenav', selector: 'app-pages-group-sidenav',
templateUrl: './sidenav.page.component.html', templateUrl: './sidenav.page.component.html',
styleUrls: ['./sidenav.page.component.scss'] styleUrls: ['./sidenav.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class SidenavPageComponent implements OnInit, OnDestroy { export class SidenavPageComponent implements OnInit, OnDestroy {
private queryParamsSubscription: Subscription; @ViewChild('sectionGroupList', { static: false })
sectionGroupList: ListSectionComponent;
set companySearchData(searchData: SearchData) {
if (!!searchData && searchData.searchWord !== '') {
this._companySearchData = { ...searchData, bySearch: true };
} else {
this._companySearchData = { ...searchData, bySearch: false };
}
}
get companySearchData() {
return this._companySearchData;
}
// tslint:disable-next-line: variable-name
_companySearchData: SearchData;
isBackspaceCanceled = true;
showType: SortViewType;
sortViewType = SortViewType;
private ngOnDestroySubject: Subject<void> = new Subject();
private loginSession: LoginSession;
constructor( constructor(
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private router: Router, private router: Router,
private logService: LogService private appAuthenticationService: AppAuthenticationService,
private logService: LogService,
private i18nService: I18nService,
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
public dialog: MatDialog,
private appUiService: AppUiService,
private ngZone: NgZone
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.queryParamsSubscription = this.activatedRoute.queryParams.subscribe( this.showType = SortViewType.all;
(params: Params) => { this.showGroupMenuIcon(SortViewType.all);
console.log('SidenavPageComponent', params[QueryParams.ID]);
this.appAuthenticationService
.getLoginSession$()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((loginSession) => {
this.loginSession = loginSession;
if (
!!this.loginSession &&
!!this.loginSession.groupInfo &&
!!this.loginSession.groupInfo.showType
) {
this.showType = this.loginSession.groupInfo.showType;
this.showGroupMenuIcon(this.loginSession.groupInfo.showType);
} }
); });
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (!!this.queryParamsSubscription) { if (!!this.ngOnDestroySubject) {
this.queryParamsSubscription.unsubscribe(); this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
} }
} }
onClickFab(event: MouseEvent) {}
onClickGroupMenu(menuType: string) {
switch (menuType) {
case 'GROUP_NEW':
{
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 'GROUP_EXPAND_MORE':
{
this.sectionGroupList.onExpandMore();
}
break;
case 'GROUP_EXPAND_LESS':
{
this.sectionGroupList.onExpandLess();
}
break;
case 'GROUP_CHANGE_ODER':
{
}
break;
}
}
onClickShowGroupMenu(menuType: SortViewType) {
switch (menuType) {
case SortViewType.all:
{
this.showType = SortViewType.all;
}
break;
case SortViewType.onlineBuddy:
{
this.showType = SortViewType.onlineBuddy;
}
break;
case SortViewType.onOff:
{
this.showType = SortViewType.onOff;
}
break;
}
if (
!!this.loginSession &&
!!this.loginSession.groupInfo &&
!!this.loginSession.groupInfo.showType
) {
this.appAuthenticationService.setLoginSession({
...this.loginSession,
groupInfo: {
groupSeqs: [],
lastGroupSeq: 0,
showType: this.showType
}
});
}
}
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.ngZone.run(() => {
this.router.navigate(
[
'group',
{
outlets: { content: 'index' }
}
],
{
queryParams: { id: Number(userInfo.seq) }
}
);
this.appUiService.closeLeftSidenavOnNarrowMode();
});
}
onOpenProfile(userInfo: UserInfo) {
this.onClickUser(userInfo);
}
} }

View File

@ -3,25 +3,46 @@ import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTabsModule } from '@angular/material/tabs';
// import { UiModule } from '@ucap/ng-ui';
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
import { AppGroupSectionModule } from '@app/sections/group/group.section.module'; import { AppGroupSectionModule } from '@app/sections/group/group.section.module';
import { AppGroupRoutingPageModule } from './group-routing.page.module'; import { AppGroupRoutingPageModule } from './group-routing.page.module';
import { IndexPageComponent } from './components/index.page.component'; import { COMPONENTS } from './components';
import { SidenavPageComponent } from './components/sidenav.page.component'; import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent];
export { IndexPageComponent, SidenavPageComponent };
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
FlexLayoutModule, FlexLayoutModule,
MatButtonModule,
MatIconModule,
MatMenuModule,
MatTabsModule,
AppOrganizationModule,
AppGroupSectionModule, AppGroupSectionModule,
AppGroupRoutingPageModule AppGroupRoutingPageModule,
I18nModule
], ],
declarations: [...COMPONENTS], declarations: [...COMPONENTS],
entryComponents: [] entryComponents: [],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['group', 'common']
}
]
}) })
export class AppGroupPageModule {} export class AppGroupPageModule {}

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 +1,16 @@
Index page of organization is works! <div class="index-page-container" fxLayout="column">
<!-- search start-->
<div fxFlex="0 0 50px">
<app-organization-search-for-tenant
[isBackspaceCanceled]="isBackspaceCanceled"
[(searchData)]="companySearchData"
(canceled)="onCanceledSearch()"
>
</app-organization-search-for-tenant>
</div>
<!-- search end-->
<div class="member-list-body" fxFlex="1 1 auto">
<app-sections-organization-member-list [searchData]="deptSearchData">
</app-sections-organization-member-list>
</div>
</div>

View File

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

View File

@ -1,10 +1,128 @@
import { Component } from '@angular/core'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectorRef,
ChangeDetectionStrategy
} from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { ParamsUtil } from '@ucap/ng-core';
import { UserStore } from '@app/models/user-store';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { QueryParams } from '../types/params.type';
@Component({ @Component({
selector: 'app-pages-organization-index', selector: 'app-pages-organization-index',
templateUrl: './index.page.component.html', templateUrl: './index.page.component.html',
styleUrls: ['./index.page.component.scss'] styleUrls: ['./index.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class IndexPageComponent { export class IndexPageComponent implements OnInit, OnDestroy {
constructor() {} set companySearchData(searchData: SearchData) {
this._companySearchData = searchData;
this.onChangedCompanySearch();
}
get companySearchData() {
return this._companySearchData;
}
// tslint:disable-next-line: variable-name
_companySearchData: SearchData;
deptSearchData: SearchData;
isBackspaceCanceled = true;
deptSeq: string;
private ngOnDestroySubject: Subject<void> = new Subject();
private userStore: UserStore;
constructor(
private appAuthenticationService: AppAuthenticationService,
private router: Router,
private activatedRoute: ActivatedRoute,
private changeDetectorRef: ChangeDetectorRef
) {
this.userStore = this.appAuthenticationService.getUserStore();
}
ngOnInit(): void {
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params) => {
if (!!params) {
const deptSeq = params[QueryParams.DEPT_SEQ];
const companyCode = params[QueryParams.COMPANY_CODE];
const searchWord = params[QueryParams.SEARCH_WORD];
const bySearch = ParamsUtil.getParameter<boolean>(
params,
QueryParams.BY_SEARCH,
false
);
if (!!deptSeq && this.deptSeq !== deptSeq) {
this.deptSeq = deptSeq;
}
this.deptSearchData = {
deptSeq: bySearch ? undefined : deptSeq,
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();
}
}
onChangedCompanySearch() {
const queryParams: Params = {};
queryParams[QueryParams.COMPANY_CODE] = this._companySearchData.companyCode;
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',
{
outlets: { content: 'index' }
}
],
{
queryParams
}
);
}
} }

View File

@ -1,3 +1,19 @@
<div fxFlexFill> <div class="sidenav-container" fxFlexFill fxLayout="column">
sidenav page of ogranization is works! <div class="sub-header" fxFlex="50px" fxLayout="row">
<div fxFlex="1 1 auto">
<h3>{{ 'organization:label.organization' | ucapI18n }}</h3>
</div>
</div>
<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">
<app-organization-tree
[initialExpanded]="initialExpanded"
[windowSizeMode]="windowSizeMode"
(clicked)="onClickedTree($event)"
></app-organization-tree>
</div>
</div> </div>

View File

@ -0,0 +1,33 @@
@import '~@ucap/lg-scss/mixins';
.sidenav-container {
overflow: hidden;
.sub-header {
justify-content: space-between;
padding: 0 0 0 17px;
background-color: $white;
align-items: center;
width: 100%;
h3 {
@include font-family-txt(20, left, $lipstick);
align-items: center;
font-weight: 600;
}
.menu-btn {
justify-self: end;
}
}
.extra-box {
align-items: center;
font-weight: 600;
font-size: 16px;
color: $gray-re3;
padding: 0 17px;
line-height: 50px;
.ico-business {
vertical-align: middle;
margin-right: 10px;
}
}
}

View File

@ -1,14 +1,135 @@
import { Component } from '@angular/core'; import { Subject, combineLatest } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectorRef,
ChangeDetectionStrategy
} from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { Store, select } from '@ngrx/store';
import { DeptInfo } from '@ucap/domain-organization';
import { LogService } from '@ucap/ng-logger'; import { LogService } from '@ucap/ng-logger';
import { DepartmentSelector, UserSelector } from '@ucap/ng-store-organization';
import { LoginSelector } from '@ucap/ng-store-authentication';
import { AppUiService } from '@app/services/app-ui.service';
import { AppSelector } from '@app/store/state';
import { WindowSizeMode } from '@app/types/window-size-mode.type';
import { environment } from '@environments';
import { QueryParams } from '../types/params.type';
@Component({ @Component({
selector: 'app-pages-ogranization-sidenav', selector: 'app-pages-ogranization-sidenav',
templateUrl: './sidenav.page.component.html', templateUrl: './sidenav.page.component.html',
styleUrls: ['./sidenav.page.component.scss'] styleUrls: ['./sidenav.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class SidenavPageComponent { export class SidenavPageComponent implements OnInit, OnDestroy {
constructor(private logService: LogService) { initialExpanded: number;
displayRoot = false;
displayRootDept: DeptInfo;
windowSizeMode: WindowSizeMode;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
private appUiService: AppUiService,
private logService: LogService
) {
this.logService.info('app-pages-ogranization-sidenav'); this.logService.info('app-pages-ogranization-sidenav');
} }
ngOnInit(): void {
// this.activatedRoute.queryParams
// .pipe(takeUntil(this.ngOnDestroySubject))
// .subscribe((params) => {
// if (!!params) {
// const deptSeq = params[QueryParams.DEPT_SEQ];
// if (!!deptSeq) {
// this.initialExpanded = Number(deptSeq);
// }
// }
// });
combineLatest([
this.activatedRoute.queryParams,
this.store.pipe(select(UserSelector.user))
])
.pipe(takeUntil(this.ngOnDestroySubject))
.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'
);
}
}
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(AppSelector.windowSizeMode)
)
.subscribe((windowSizeMode) => {
this.windowSizeMode = windowSizeMode;
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onClickedTree(node: DeptInfo) {
const queryParams: Params = {};
queryParams[QueryParams.DEPT_SEQ] = String(node.seq);
this.router.navigate(
[
'organization',
{
outlets: { content: 'index' }
}
],
{
queryParams
}
);
this.appUiService.closeLeftSidenavOnNarrowMode();
}
} }

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