This commit is contained in:
Park Byung Eun 2020-08-10 14:05:32 +09:00
parent 98ff58c39e
commit eded98a6cf
284 changed files with 9356 additions and 3347 deletions

6
package-lock.json generated
View File

@ -2022,9 +2022,9 @@
"integrity": "sha512-GA9MDcwCvtxI0gOysgRm7DDHIfKfhCkDSa69QBFIEgWbob2OdGYKvxfymr6lGdl+vY7AqjlJXvSFkA1b0rTy1A=="
},
"@ucap/ng-core": {
"version": "0.0.7",
"resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-core/-/ng-core-0.0.7.tgz",
"integrity": "sha512-ZC6LE3A0bg+REGbzDI/i1ad7mGpKsw6X0UtZ+Q8TUthHNv0DfWEieHFCgfYTRY1u022XyQ4ViOsrq9KunU1vfw=="
"version": "0.0.8",
"resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-core/-/ng-core-0.0.8.tgz",
"integrity": "sha512-VMvitw2PnaGo4tsdD9EJcvzVi5Kt+xjzA6z09uLavPpAWViFXTx7P76N1nt7XMgACRAMOTMyNocWTbFs6oKsqA=="
},
"@ucap/ng-i18n": {
"version": "0.0.6",

View File

@ -31,24 +31,39 @@
"@ngrx/entity": "^9.2.0",
"@ngrx/router-store": "^9.2.0",
"@ngrx/store": "^9.2.0",
"@ucap/api": "~0.0.4",
"@ucap/api-common": "~0.0.11",
"@ucap/api-external": "~0.0.5",
"@ucap/api-message": "~0.0.3",
"@ucap/api-prompt": "~0.0.3",
"@ucap/api-public": "~0.0.4",
"@ucap/core": "~0.0.14",
"@ucap/logger": "~0.0.13",
"@ucap/native": "~0.0.19",
"@ucap/api": "~0.0.5",
"@ucap/api-common": "~0.0.12",
"@ucap/api-contact": "~0.0.5",
"@ucap/api-external": "~0.0.8",
"@ucap/api-message": "~0.0.7",
"@ucap/api-prompt": "~0.0.6",
"@ucap/api-public": "~0.0.6",
"@ucap/api-webex": "~0.0.2",
"@ucap/core": "~0.0.15",
"@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-external": "~0.0.1",
"@ucap/ng-api-message": "~0.0.1",
"@ucap/ng-api-prompt": "~0.0.1",
"@ucap/ng-api-public": "~0.0.1",
"@ucap/ng-core": "~0.0.7",
"@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-i18n": "~0.0.6",
"@ucap/ng-native": "~0.0.5",
"@ucap/ng-i18n": "~0.0.8",
"@ucap/ng-native": "~0.0.12",
"@ucap/ng-pi": "~0.0.1",
"@ucap/ng-protocol": "~0.0.3",
"@ucap/ng-protocol-authentication": "~0.0.3",
@ -66,35 +81,37 @@
"@ucap/ng-protocol-status": "~0.0.3",
"@ucap/ng-protocol-sync": "~0.0.3",
"@ucap/ng-protocol-umg": "~0.0.3",
"@ucap/ng-store-authentication": "~0.0.14",
"@ucap/ng-store-chat": "~0.0.66",
"@ucap/ng-store-group": "~0.0.22",
"@ucap/ng-store-organization": "~0.0.20",
"@ucap/ng-store-authentication": "~0.0.17",
"@ucap/ng-store-chat": "~0.0.74",
"@ucap/ng-store-group": "~0.0.25",
"@ucap/ng-store-organization": "~0.0.23",
"@ucap/ng-store-call": "~0.0.7",
"@ucap/ng-web-socket": "~0.0.2",
"@ucap/ng-web-storage": "~0.0.3",
"@ucap/ng-ui": "0.0.97",
"@ucap/ng-ui-organization": "~0.0.202",
"@ucap/ng-ui-authentication": "~0.0.29",
"@ucap/ng-ui-group": "~0.0.78",
"@ucap/ng-ui-chat": "~0.0.72",
"@ucap/ng-ui": "~0.0.108",
"@ucap/ng-ui-organization": "~0.0.222",
"@ucap/ng-ui-authentication": "~0.0.32",
"@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/pi": "~0.0.8",
"@ucap/protocol": "~0.0.17",
"@ucap/protocol-authentication": "~0.0.5",
"@ucap/protocol-buddy": "~0.0.5",
"@ucap/protocol-event": "~0.0.6",
"@ucap/protocol-file": "~0.0.6",
"@ucap/protocol-group": "~0.0.5",
"@ucap/protocol-info": "~0.0.9",
"@ucap/protocol-inner": "~0.0.4",
"@ucap/protocol-option": "~0.0.7",
"@ucap/protocol-ping": "~0.0.6",
"@ucap/protocol-query": "~0.0.5",
"@ucap/protocol-room": "~0.0.7",
"@ucap/protocol-service": "~0.0.4",
"@ucap/protocol-status": "~0.0.5",
"@ucap/protocol-sync": "~0.0.6",
"@ucap/pi": "~0.0.9",
"@ucap/protocol": "~0.0.20",
"@ucap/protocol-authentication": "~0.0.7",
"@ucap/protocol-buddy": "~0.0.6",
"@ucap/protocol-event": "~0.0.11",
"@ucap/protocol-file": "~0.0.7",
"@ucap/protocol-group": "~0.0.6",
"@ucap/protocol-info": "~0.0.10",
"@ucap/protocol-inner": "~0.0.5",
"@ucap/protocol-option": "~0.0.9",
"@ucap/protocol-ping": "~0.0.7",
"@ucap/protocol-query": "~0.0.8",
"@ucap/protocol-room": "~0.0.9",
"@ucap/protocol-service": "~0.0.5",
"@ucap/protocol-status": "~0.0.6",
"@ucap/protocol-sync": "~0.0.8",
"@ucap/protocol-umg": "~0.0.5",
"@ucap/ui-scss": "~0.0.5",
"@ucap/web-socket": "~0.0.10",

View File

@ -15,10 +15,13 @@ import { AppAuthenticationService } from './services/app-authentication.service'
import { AppNotificationService } from './services/app-notification.service';
import { AppNativeService } from './services/app-native.service';
import { AppService } from './services/app.service';
import { 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 RESOLVERS = [AppSessionResolver];
@ -27,10 +30,13 @@ const SERVICES = [
AppAuthenticationService,
AppNativeService,
AppFileService,
AppCallService,
AppChatService,
AppNotificationService,
AppOrganizationService,
AppGroupService,
AppAccountService
AppAccountService,
AppUiService
];
const axiosFactory = () => {

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 { AppSessionResolver } from './resolvers/app-session.resolver';
import { NavigationType } from './types';
const routes: Routes = [
{
path: '',
redirectTo: '/group/(content:index)',
redirectTo: `/${NavigationType.Group}/(content:index)`,
pathMatch: 'full'
},
{
@ -38,35 +39,35 @@ const routes: Routes = [
},
children: [
{
path: 'organization',
path: NavigationType.Organization,
loadChildren: () =>
import('./pages/organization/organization.page.module').then(
(m) => m.AppOrganizationPageModule
)
},
{
path: 'group',
path: NavigationType.Group,
loadChildren: () =>
import('./pages/group/group.page.module').then(
(m) => m.AppGroupPageModule
)
},
{
path: 'chat',
path: NavigationType.Chat,
loadChildren: () =>
import('./pages/chat/chat.page.module').then(
(m) => m.AppChatPageModule
)
},
{
path: 'call',
path: NavigationType.Call,
loadChildren: () =>
import('./pages/call/call.page.module').then(
(m) => m.AppCallPageModule
)
},
{
path: 'message',
path: NavigationType.Message,
loadChildren: () =>
import('./pages/message/message.page.module').then(
(m) => m.AppMessagePageModule

View File

@ -1,30 +1,44 @@
import { fromEvent, interval, Subject } from 'rxjs';
import { debounce, takeUntil } from 'rxjs/operators';
import {
Component,
OnDestroy,
OnInit,
AfterViewInit,
Renderer2
Renderer2,
Inject
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Router } from '@angular/router';
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 { fromEvent, interval, Subject } from 'rxjs';
import { debounce, takeUntil } from 'rxjs/operators';
import { AppAuthenticationService } from './services/app-authentication.service';
import { AnimationBuilder, style, animate } from '@angular/animations';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
export class AppComponent implements OnInit, OnDestroy {
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private router: Router,
private renderer2: Renderer2,
private store: Store<any>,
private appAuthenticationService: AppAuthenticationService
private appAuthenticationService: AppAuthenticationService,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
@Inject(DOCUMENT) private _document: any,
private _animationBuilder: AnimationBuilder
) {
fromEvent(window, 'resize')
.pipe(
@ -38,6 +52,12 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
});
});
fromEvent(window, 'unload')
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((event: any) => {
this.nativeService.app_postDestroy();
});
// fromEvent(window, 'beforeunload')
// .pipe(takeUntil(this.ngOnDestroySubject))
// .subscribe((event: any) => {
@ -50,6 +70,53 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
width: window.innerWidth,
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 {
@ -59,13 +126,6 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
}
}
ngAfterViewInit(): void {
const preloader = this.renderer2.selectRootElement(
'#ucap-lg-web-preloader'
);
this.renderer2.setStyle(preloader, 'display', 'none');
}
private dispatchWindowSize(size: { width: number; height: number }) {
this.store.dispatch(AppActions.windowResized(size));
}

View File

@ -15,8 +15,10 @@ import { LoggerModule } from '@ucap/ng-logger';
import { CommonApiModule } from '@ucap/ng-api-common';
import { PublicApiModule } from '@ucap/ng-api-public';
import { ExternalApiModule } from '@ucap/ng-api-external';
import { ContactApiModule } from '@ucap/ng-api-contact';
import { MessageApiModule } from '@ucap/ng-api-message';
import { PromptApiModule } from '@ucap/ng-api-prompt';
import { WebexApiModule } from '@ucap/ng-api-webex';
import { PiModule } from '@ucap/ng-pi';
@ -46,6 +48,7 @@ import { OrganizationStoreModule } from '@ucap/ng-store-organization';
import { AuthenticationStoreModule } from '@ucap/ng-store-authentication';
import { GroupStoreModule } from '@ucap/ng-store-group';
import { ChatStoreModule } from '@ucap/ng-store-chat';
import { CallStoreModule } from '@ucap/ng-store-call';
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
@ -62,6 +65,7 @@ import { metaReducers } from '@app/store/state';
import { AppAccountDialogModule } from '@app/dialogs/account/account.dialog.module';
import { environment } from '@environments';
import { MatDialogModule } from '@angular/material/dialog';
@NgModule({
declarations: [AppComponent],
@ -121,13 +125,17 @@ import { environment } from '@environments';
*/
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),
@ -162,6 +170,10 @@ import { environment } from '@environments';
eventRequestDefaultCount:
environment.productConfig.chat.eventRequestDefaultCount
}),
CallStoreModule.forRoot({
historyRequestDefaultCount:
environment.productConfig.call.historyRequestDefaultCount
}),
OrganizationUiModule.forRoot({}),

View File

@ -13,11 +13,12 @@ import { MatSelectModule } from '@angular/material/select';
import { MatTabsModule } from '@angular/material/tabs';
import { I18nModule } from '@ucap/ng-i18n';
import { UiModule } from '@ucap/ng-ui';
import { 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({
@ -36,7 +37,9 @@ import { COMPONENTS } from './components';
I18nModule,
UiModule,
UiscrollingModule,
AppAuthenticationModule,
AppLayoutsModule,
AppAccountSectionModule

View File

@ -30,7 +30,10 @@
<div class="settings-contents">
<ul>
<li>
<mat-checkbox>
<mat-checkbox
[value]="settings.general.autoLaunch"
(change)="onChangeForAutoStartOnBoot($event)"
>
{{
'authentication:login.settings.autoStartOnBoot'
| ucapI18n
@ -38,12 +41,18 @@
</mat-checkbox>
</li>
<li>
<mat-checkbox>
<mat-checkbox
[value]="settings.general.autoLogin"
(change)="onChangeForAutoLogin($event)"
>
{{ 'authentication:login.settings.autoLogin' | ucapI18n }}
</mat-checkbox>
</li>
<li>
<mat-checkbox>
<mat-checkbox
[value]="settings.general.startupHideWindow"
(change)="onChangeForAutoHide($event)"
>
{{ 'authentication:login.settings.autoHide' | ucapI18n }}
</mat-checkbox>
</li>
@ -66,7 +75,7 @@
class="setting-select-obj ucap-mat-input-container"
>
<mat-select
[value]="generalSetting.locale"
[value]="settings.general.locale"
(selectionChange)="onSelectionChangeLanguage($event)"
>
<mat-option
@ -95,7 +104,7 @@
class="setting-select-obj ucap-mat-input-container"
>
<mat-select
[value]="generalSetting.hrInfoLocale"
[value]="settings.general.hrInfoLocale"
(selectionChange)="onSelectionChangeHrLanguage($event)"
>
<mat-option
@ -122,10 +131,13 @@
class="setting-select-obj ucap-mat-input-container"
>
<mat-select
[class.ucap-select-placeholder-value]="
!!timezonePlaceholder
"
#selectForTimezone
[formControl]="formControlForTimezone"
[placeholder]="timezonePlaceholder"
[value]="generalSetting.timezone"
[value]="settings.general.timezone"
(openedChange)="onOpenedChangeTimezone($event)"
>
<ucap-virtual-scroll-viewport
@ -162,6 +174,8 @@
<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">
{{
@ -186,20 +200,25 @@
appearance="standard"
class="setting-select-obj ucap-mat-input-container"
>
<mat-select>
<mat-option value="sound">
<mat-select
[value]="settings.notification.method"
(selectionChange)="
onSelectionChangeMethodOfNotification($event)
"
>
<mat-option [value]="NotificationMethod.Sound">
{{
'organization:settings.notification.methodTypeSound'
| ucapI18n
}}
</mat-option>
<mat-option value="alert">
<mat-option [value]="NotificationMethod.Alert">
{{
'organization:settings.notification.methodTypeAlert'
| ucapI18n
}}
</mat-option>
<mat-option value="soundAndAlert">
<mat-option [value]="NotificationMethod.SoundAndAlert">
{{
'organization:settings.notification.methodTypeSoundAndAlert'
| ucapI18n
@ -222,7 +241,12 @@
appearance="standard"
class="setting-select-obj ucap-mat-input-container"
>
<mat-select>
<mat-select
[value]="String(settings.notification.alertExposureTime)"
(selectionChange)="
onSelectionChangeAlertExposureTimeOfNotification($event)
"
>
<mat-option value="5">
5{{ 'common:units.second' | ucapI18n }}
</mat-option>
@ -249,7 +273,12 @@
<div class="settings-contents">
<ul>
<li>
<mat-checkbox>
<mat-checkbox
[value]="settings.notification.receiveForMessage"
(change)="
onChangeForReceiveForMessageOfNotification($event)
"
>
{{
'organization:settings.notification.receiveForMessageTypePopup'
| ucapI18n
@ -269,7 +298,7 @@
<!-- 대화 -->
<div class="messenger-settings-area">
<div class="title-settings-subject">파일 전송</div>
<div class="settings-contents02">
<div class="settings-contents02" *ngIf="'electron' === platform">
<div class="subtitle-settings-info">
다운로드 폴더
</div>
@ -278,18 +307,12 @@
color="accent"
class="setting-input-obj input-set-obj ucap-mat-input-container"
>
<input
matInput
placeholder=""
value=""
[readonly]="'browser' === platform"
/>
<input matInput placeholder="" value="" />
<button
mat-button
matSuffix
mat-icon-button
aria-label="file"
[disabled]="'browser' === platform"
>
<mat-icon>folder</mat-icon>
</button>
@ -299,7 +322,6 @@
mat-stroked-button
color="primary"
class="btn-folder-first"
[disabled]="'browser' === platform"
>
폴더 초기화
</button>
@ -338,123 +360,7 @@
</ng-template>
<div class="secret-num-settings-area">
<!-- 비밀번호 -->
<div class="messenger-settings-area">
<div class="title-settings-subject">
{{ 'authentication:password.fields.changePassword' | ucapI18n }}
</div>
<div class="settings-contents02">
<div class="subtitle-settings-info">
{{
'authentication:password.fields.currentPassword' | ucapI18n
}}
</div>
<div class="settings-sub-content">
<mat-form-field
color="accent"
class="setting-input-obj ucap-mat-input-container"
>
<input
matInput
placeholder="{{
'authentication:password.placeholder.currentPassword'
| ucapI18n
}}"
value=""
/>
<button
mat-button
matSuffix
mat-icon-button
aria-label="action"
>
<mat-icon>done</mat-icon>
</button>
<mat-hint>Hint</mat-hint>
</mat-form-field>
</div>
</div>
<div class="settings-contents02">
<div class="subtitle-settings-info">
{{ 'authentication:password.fields.newPassword' | ucapI18n }}
</div>
<div class="settings-sub-content">
<mat-form-field
color="accent"
class="setting-input-obj ucap-mat-input-container"
>
<input
matInput
placeholder="{{
'authentication:password.placeholder.newPassword'
| ucapI18n
}}"
value=""
/>
<button
mat-button
matSuffix
mat-icon-button
aria-label="action"
>
<mat-icon>done</mat-icon>
</button>
<mat-hint
>반드시 영어 소문자, 숫자, 특수문자 중 2가지 이상 사용해야
합니다.</mat-hint
>
</mat-form-field>
<mat-form-field
color="accent"
class="setting-input-obj ucap-mat-input-container"
>
<input
matInput
placeholder="{{
'authentication:password.placeholder.newPasswordConfirm'
| ucapI18n
}}"
value=""
/>
<button
mat-button
matSuffix
mat-icon-button
aria-label="action"
>
<mat-icon>done</mat-icon>
</button>
<mat-error>Error</mat-error>
</mat-form-field>
</div>
</div>
<div class="pass-info-box">
<dl>
<dt>
<mat-icon color="accent" class="bullet-ico-info"
>info_outline</mat-icon
>
{{ 'authentication:password.notice.condition' | ucapI18n }}
</dt>
<dd>
{{ 'authentication:password.notice.condition1' | ucapI18n }}
</dd>
<dd>
{{ 'authentication:password.notice.condition2' | ucapI18n }}
</dd>
<dd>
{{ 'authentication:password.notice.condition3' | ucapI18n }}
</dd>
<dd>
{{ 'authentication:password.notice.condition4' | ucapI18n }}
</dd>
<dd>
{{ 'authentication:password.notice.condition5' | ucapI18n }}
</dd>
</dl>
</div>
</div>
<app-authentication-change-password></app-authentication-change-password>
</div>
</mat-tab>
</mat-tab-group>

View File

@ -12,10 +12,10 @@
display: flex;
flex-direction: column;
width: 100%;
padding: 10px 16px 9px;
padding: 10px 16px;
&:first-of-type {
border-top: 0;
padding: 20px 16px 9px;
padding: 20px 16px 10px;
}
.title-settings-subject {
color: #5c444b;
@ -52,11 +52,11 @@
.settings-contents {
ul {
li {
padding: 6px 0 7px;
padding: 6px 0;
}
}
.settings-radio-group {
padding: 6px 0 7px;
padding: 6px 0;
height: 42px;
display: flex;
flex-direction: row;

View File

@ -13,17 +13,27 @@ import {
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';
@ -34,10 +44,6 @@ import {
ChatSetting,
PresenceSetting
} from '@app/models/settings';
import { I18nService } from '@ucap/ng-i18n';
import { VirtualScrollViewportComponent } from '@ucap/ng-ui';
import { FormControl } from '@angular/forms';
import { MatOptionSelectionChange } from '@angular/material/core';
export interface TimezoneData {
displayName: string;
@ -47,7 +53,9 @@ export interface TimezoneData {
export interface SettingsDialogData {
settings: Settings;
}
export interface SettingsDialogResult {}
export interface SettingsDialogResult {
settings: Settings;
}
@Component({
selector: 'app-sections-account-settings',
@ -66,10 +74,7 @@ export class SettingsDialogComponent implements OnInit, OnDestroy {
platform: 'browser' | 'electron' = 'electron';
generalSetting: GeneralSetting;
notificationSetting: NotificationSetting;
chatSetting: ChatSetting;
presenceSetting: PresenceSetting;
settings: Settings;
timezoneList: TimezoneData[];
timezonePlaceholder: string;
@ -78,15 +83,24 @@ export class SettingsDialogComponent implements OnInit, OnDestroy {
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';
@ -99,14 +113,9 @@ export class SettingsDialogComponent implements OnInit, OnDestroy {
}
});
this.generalSetting = data.settings.general;
this.notificationSetting = data.settings.notification;
this.chatSetting = data.settings.chat;
this.presenceSetting = data.settings.presence;
this.settings = ObjectUtil.deepClone(data.settings);
}
private ngOnDestroySubject: Subject<void> = new Subject();
ngOnInit(): void {
this.generateTimezoneData();
@ -124,9 +133,27 @@ export class SettingsDialogComponent implements OnInit, OnDestroy {
}
}
onSelectionChangeLanguage(event: MatSelectChange) {}
onChangeForAutoStartOnBoot(event: MatCheckboxChange) {
this.settings.general.autoLaunch = event.checked;
}
onSelectionChangeHrLanguage(event: MatSelectChange) {}
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) {
@ -139,15 +166,41 @@ export class SettingsDialogComponent implements OnInit, OnDestroy {
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();
this.dialogRef.close({ settings: this.data.settings });
}
onCancel() {}
onCancel() {
this.dialogRef.close({ settings: this.data.settings });
}
onConfirm() {}
onConfirm() {
this.dialogRef.close({
settings: this.settings
});
}
private generateTimezoneData() {
const timezoneData = this.i18nService.t('locale:timezone', {
@ -159,11 +212,14 @@ export class SettingsDialogComponent implements OnInit, OnDestroy {
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.generalSetting.timezone) {
if (name === this.settings.general.timezone) {
this.timezonePlaceholder = displayName;
}
}
@ -176,12 +232,12 @@ export class SettingsDialogComponent implements OnInit, OnDestroy {
private setTimezoneData() {
const timezoneIndex = this.timezoneList.findIndex(
(t) => t.name === this.generalSetting.timezone
(t) => t.name === this.settings.general.timezone
);
if (-1 !== timezoneIndex) {
if (!!this.vsTimezone && !!this.selectForTimezone) {
this.vsTimezone.scrollToIndex(timezoneIndex);
this.vsTimezone.scrollToIndex(timezoneIndex, 'start');
this.selectForTimezone.value = this.timezoneList[timezoneIndex].name;
}
}

View File

@ -1,6 +1,6 @@
<div class="layout-container" fxLayout="column">
<div class="layout-header" fxFlex="50px" fxLayout="row">
<div fxFlex="1 1 auto">
<div fxFlex="1 1 auto" class="layout-header-container">
<ng-content
class="layout-header-content"
select="[appLayoutsDefaultDialog='header']"

View File

@ -10,6 +10,11 @@
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%;

View File

@ -1,5 +1,9 @@
<div class="layout-container" fxLayout="row">
<div class="navitab-page" fxFlex="0 0 60px">
<div
class="layout-container"
fxLayout="row"
[ngClass]="'electron==' ? 'electron' : ''"
>
<div class="navitab-page">
<div class="gnb">
<mat-toolbar class="mat-gnb-toolbar"
><img
@ -16,7 +20,7 @@
class="global-menu"
(selectedTabChange)="onSelectedTabChange($event)"
>
<mat-tab aria-label="Group">
<mat-tab [aria-label]="NavigationType.Group">
<ng-template mat-tab-label>
<div
class="icon-item"
@ -65,7 +69,7 @@
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Chat">
<mat-tab [aria-label]="NavigationType.Chat">
<ng-template mat-tab-label>
<div
class="icon-item"
@ -102,7 +106,7 @@
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Organization">
<mat-tab [aria-label]="NavigationType.Organization">
<ng-template mat-tab-label>
<div
class="icon-item"
@ -158,7 +162,7 @@
</ng-template>
</mat-tab>
<mat-tab aria-label="Message">
<!-- <mat-tab [aria-label]="NavigationType.Message">
<ng-template mat-tab-label>
<div
class="icon-item"
@ -195,9 +199,9 @@
</svg>
</div>
</ng-template>
</mat-tab>
</mat-tab> -->
<mat-tab aria-label="Call">
<mat-tab [aria-label]="NavigationType.Call">
<ng-template mat-tab-label>
<div
class="icon-item"
@ -247,6 +251,7 @@
mode="side"
opened="true"
[disableClose]="true"
(openedChange)="onOpenStart($event)"
>
<ucap-float-action-button
*ngIf="fabButtonShow"
@ -254,7 +259,10 @@
[useCustomDefaultIcon]="fabUseCustomDefaultIcon"
(buttonClick)="onClickFab($event)"
>
<div *ngIf="tabIndex === 'group'" ucapFloatActionButton="mainIcon">
<div
*ngIf="curNavi === NavigationType.Group"
ucapFloatActionButton="mainIcon"
>
<mat-icon class="ico-font-float">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -303,7 +311,10 @@
</svg>
</mat-icon>
</div>
<div *ngIf="tabIndex === 'chat'" ucapFloatActionButton="mainIcon">
<div
*ngIf="curNavi === NavigationType.Chat"
ucapFloatActionButton="mainIcon"
>
<mat-icon class="ico-font-float">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -341,7 +352,7 @@
</mat-icon>
</div>
<div
*ngIf="tabIndex === 'message'"
*ngIf="curNavi === NavigationType.Message"
ucapFloatActionButton="mainIcon"
>
<mat-icon class="ico-font-float">
@ -381,7 +392,10 @@
</svg>
</mat-icon>
</div>
<div *ngIf="tabIndex === 'call'" ucapFloatActionButton="mainIcon">
<div
*ngIf="curNavi === NavigationType.Call"
ucapFloatActionButton="mainIcon"
>
<mat-icon class="ico-font-dialpad">dialpad</mat-icon>
</div>
</ucap-float-action-button>
@ -400,9 +414,11 @@
<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">
<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">
@ -418,15 +434,12 @@
<!--My Profile -->
<div
class="my-profile"
matTooltip="프로필 버튼"
matTooltipPosition="below"
matTooltipHideDelay="1000"
[matMenuTriggerFor]="profileMenu"
#profileMenuTrigger="matMenuTrigger"
>
<app-organization-profile-image-01
[userInfo]="user.info"
[versionInfo]="versionInfo2Res"
[versionInfo]="versionInfo"
(openProfile)="onOpenProfile($event)"
></app-organization-profile-image-01>
</div>
@ -445,7 +458,9 @@
<!--Footer-->
<div class="footer">
<div class="foot-info version-info">
<span class="var-txt current-ver">현재버전 0.0.11</span>
<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>

View File

@ -8,13 +8,23 @@
.layout-container {
width: 100%;
height: 100%;
&.electron {
border: 1px solid #aaa;
}
.navitab-page {
//GNB /////////////////////////////////////
display: flex;
flex: 0 0 auto;
width: 60px;
@include screen(xs) {
width: 60px;
}
.gnb {
//background-color: $gray-ref0;
background-color: #f1f2f6;
width: 60px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
@ -25,7 +35,7 @@
flex-basis: 40px;
padding: 2px 12px 10px;
.img-logo {
margin: 6px 0 0 1px;
margin: 10px 0 0 1px;
}
}
.left-container {
@ -62,7 +72,7 @@
content: '';
width: 30px;
height: 30px;
background-image: url(../../../assets/images/ico/btn_gnb_hompage.svg);
background-image: url(/assets/images/ico/btn_gnb_hompage.svg);
background-size: 30px;
display: block;
position: absolute;
@ -112,6 +122,9 @@
display: flex;
align-items: center;
padding: 0 16px;
-webkit-app-region: drag;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
}
}
@ -134,18 +147,28 @@
justify-content: space-between;
.toolbar-info-area {
display: flex;
flex-grow: 1;
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;
display: none;
}
.today {
display: flex;
flex-direction: row;
align-items: center;
span {
width: 54px;
height: 16px;
@ -160,10 +183,17 @@
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 {
@ -175,8 +205,8 @@
}
}
.my-profile {
height: 30px;
width: 30px;
height: 28px;
width: 28px;
margin-right: 20px;
order: 1;
//profile /////////////
@ -239,6 +269,7 @@
@include font-family($font-light);
font-weight: 600;
&.version-info {
display: none;
.var-txt {
padding-left: 8px;
color: $gray-re70;

View File

@ -3,7 +3,7 @@ import moment from 'moment';
import { Subject, of } from 'rxjs';
import { takeUntil, filter, take, map, catchError } from 'rxjs/operators';
import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core';
import { Component, ViewChild, OnDestroy, OnInit, Inject } from '@angular/core';
import {
Router,
RouterEvent,
@ -20,23 +20,43 @@ import { MatMenuTrigger } from '@angular/material/menu';
import { MatTabChangeEvent, MatTabGroup } from '@angular/material/tabs';
import { MatSidenav } from '@angular/material/sidenav';
import { VersionInfo2Response } from '@ucap/api-public';
import { UserInfoSS } from '@ucap/protocol-query';
import { User, UserInfoSS } from '@ucap/domain-organization';
import { LogService } from '@ucap/ng-logger';
import { UserSelector } from '@ucap/ng-store-organization';
import { ConfigurationSelector } from '@ucap/ng-store-authentication';
import { RoomSelector } from '@ucap/ng-store-chat';
import { AppSelector } from '@app/store/state';
import { AppChatService } from '@app/services/app-chat.service';
import { QueryParams as ChatQueryParams } from '@app/pages/chat/types/params.type';
import { CreateDialogComponent } from '@app/sections/group/dialogs/create.dialog.component';
import { AppAccountService } from '@app/services/app-account.service';
import { 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 { User } from '@ucap/protocol-info';
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({
selector: 'app-layouts-default',
@ -48,16 +68,21 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
navTabGroup: MatTabGroup;
@ViewChild('leftSidenav', { static: true })
leftSidenav: MatSidenav;
isShowLeftSideNav = false;
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;
@ViewChild('profileMenuTrigger', { static: true })
profileMenuTrigger: MatMenuTrigger;
showStatusbar = true;
tabIndex: string;
queryParams: Params;
unreadCountChat = 0;
@ -66,23 +91,30 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
fabButtonShow = true;
fabUseCustomDefaultIcon = true; // default in this prj
fabButtons: { icon: string; tooltip?: string; divisionType?: string }[];
versionInfo2Res: VersionInfo2Response;
versionInfo: VersionInfo;
user: User;
/** Navigation */
NavigationType = NavigationType;
initMenu = NavigationType.Group;
curNavi: NavigationType;
moment = moment;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private router: Router,
private i18nSevice: I18nService,
private activatedRoute: ActivatedRoute,
private store: Store<any>,
private appAccountService: AppAccountService,
private appChatService: AppChatService,
private appCallService: AppCallService,
private appUiService: AppUiService,
private logService: LogService,
public dialog: MatDialog
public dialog: MatDialog,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService
) {
this.setFabInitial(NAVS[0]);
this.setFabInitial(this.initMenu);
this.router.events
.pipe(
takeUntil(this.ngOnDestroySubject),
@ -97,7 +129,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
if (!p || !p.segments || 0 === p.segments.length) {
break;
}
const index = p.segments[0].path;
const index = p.segments[0].path as NavigationType;
this.setTabGroup(index);
this.setFabInitial(index);
}
@ -116,32 +148,14 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
.subscribe((versionInfo) => {
this.versionInfo = versionInfo;
});
}
ngOnInit(): void {
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(AppSelector.windowSize))
.subscribe((size) => {
if (size.width < 780) {
if (this.leftSidenav.opened) {
this.leftSidenav.close();
}
this.isShowLeftSideNav = false;
this.leftSidenav.mode = 'over';
} else {
if (!this.leftSidenav.opened) {
this.leftSidenav.open();
}
this.isShowLeftSideNav = true;
this.leftSidenav.mode = 'side';
}
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user))
.subscribe((user) => {
@ -156,6 +170,31 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
.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 {
@ -170,14 +209,13 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
}
onSelectedTabChange(event: MatTabChangeEvent) {
const commands: any = [
NAVS[event.index],
{ outlets: { content: 'index' } }
];
const naviMenu: NavigationType = event.tab.ariaLabel as NavigationType;
const commands: any = [naviMenu, { outlets: { content: 'index' } }];
const orgInitialParams: Params = {};
// CASE :: Chat.
if (
event.index === 1 && // is chat.
naviMenu === NavigationType.Chat && // is chat.
!!this.queryParams &&
!!this.queryParams[ChatQueryParams.ROOM_ID]
) {
@ -185,55 +223,45 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
this.queryParams = undefined;
return;
}
// if (!!this.tabIndex && this.tabIndex === 'chat') {
// if (!!this.queryParams && !!this.queryParams[ChatQueryParams.ROOM_ID]) {
// return;
// } else {
// }
// } else {
// }
if (event.index === 2 && !!this.user) {
// CASE :: Organization.
if (naviMenu === NavigationType.Organization && !!this.user) {
orgInitialParams[OrganizationParams.DEPT_SEQ] = String(
this.user.departmentCode
);
}
this.router.navigate(commands, { queryParams: orgInitialParams });
if (!this.isShowLeftSideNav) {
this.leftSidenav.open();
}
this.setFabInitial(NAVS[event.index]);
this.appUiService.openLeftSidenavOnNarrowMode();
this.setFabInitial(naviMenu);
}
onClickToggleLeftSidenav() {
if (!this.isShowLeftSideNav) {
this.leftSidenav.toggle();
}
this.appUiService.toggleLeftSidenavOnNarrowMode();
}
setFabInitial(type: string) {
this.tabIndex = type;
setFabInitial(type: NavigationType) {
this.curNavi = type;
switch (type) {
case 'group':
case NavigationType.Group:
{
this.fabButtonShow = true;
this.fabButtons = [
{
icon: 'add',
tooltip: '그룹 추가',
tooltip: this.i18nSevice.t('group:dialog.title.addNewBuddy'),
divisionType: 'GROUP_NEW_ADD'
}
];
}
break;
case 'chat':
case NavigationType.Chat:
{
this.fabButtonShow = true;
this.fabButtons = [
{
icon: 'chat',
tooltip: '대화 추가',
tooltip: this.i18nSevice.t('chat:dialog.title.newChatRoom'),
divisionType: 'CAHT_NEW_ADD'
}
];
@ -247,12 +275,12 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
// }
}
break;
case 'organization':
case NavigationType.Organization:
{
this.fabButtonShow = false;
}
break;
case 'message':
case NavigationType.Message:
{
this.fabButtonShow = true;
this.fabButtons = [
@ -264,11 +292,18 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
];
}
break;
// case MainMenu.Call:
// {
// this.fabButtonShow = false;
// }
// 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;
@ -286,10 +321,14 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
switch (btn.divisionType) {
case 'GROUP_NEW_ADD':
{
const dialogRef = this.dialog.open(CreateDialogComponent, {
const dialogRef = this.dialog.open(AddUserDialogComponent, {
panelClass: 'max-create-dialog'
});
dialogRef
.afterOpened()
.pipe(take(1))
.subscribe(() => dialogRef.componentInstance.psUpdate());
dialogRef
.afterClosed()
.pipe(
@ -304,7 +343,14 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
break;
case 'CAHT_NEW_ADD':
{
this.appChatService.newOpenRoomDialog();
const dialogRef = this.dialog.open<
ChatCreateDialogComponent,
ChatCreateDialogData,
ChatCreateDialogResult
>(ChatCreateDialogComponent, {
panelClass: 'max-create-dialog',
data: {}
});
}
break;
case 'CHAT_NEW_TIMER_ADD':
@ -320,6 +366,19 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
this.logService.debug('MESSAGE_NEW');
}
break;
case 'OPEN_DIALPAD':
{
this.dialog.open<
DialpadDialogComponent,
DialpadDialogData,
DialpadDialogResult
>(DialpadDialogComponent, {
panelClass: 'mid-create-dialog',
data: {}
});
}
break;
}
}
@ -350,9 +409,21 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
this.profileMenuTrigger.closeMenu();
}
onClickForOpenRoom() {
this.nativeService.chat_openRoom('/chat/chatroom?roomId=1000');
}
private setTabGroup(url: string) {
if (!!this.navTabGroup) {
this.navTabGroup.selectedIndex = NAVS.findIndex((v) => url === v);
}
}
onOpenStart(opened: boolean) {
this.store.dispatch(
AppActions.openedLiftSideNav({
opened
})
);
}
}

View File

@ -1,6 +1,6 @@
<div class="layout-container" fxFlexFill fxLayout="column">
<div *ngIf="showTopbar" class="top-bar" fxFlex="50px">
<app-layouts-top-bar></app-layouts-top-bar>
<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>

View File

@ -1,9 +1,10 @@
<div class="title-bar">
<ucap-title-bar
[platform]="platform"
(closed)="onClosedTitleBar()"
(maximized)="onMaximizedTitleBar()"
(minimized)="onMinimizedTitleBar()"
[windowState]="windowState"
(closed)="onClosed()"
(maximized)="onMaximized($event)"
(minimized)="onMinimized()"
>
<ng-content></ng-content>
</ucap-title-bar>

View File

@ -67,9 +67,15 @@ export class TopBarComponent implements OnInit, OnDestroy {
}
}
onClosedTitleBar() {}
onMaximizedTitleBar() {}
onMinimizedTitleBar() {}
onClosed() {
this.nativeService.window_close();
}
onMaximized(altKey: boolean) {
this.nativeService.window_maximize(altKey);
}
onMinimized() {
this.nativeService.window_minimize();
}
}

View File

@ -18,7 +18,9 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { UiModule } from '@ucap/ng-ui';
import { UiButtonModule } from '@ucap/ng-ui/button';
import { UiNativeModule } from '@ucap/ng-ui/native';
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
@ -45,7 +47,9 @@ import { DIALOGS } from './dialogs';
PerfectScrollbarModule,
I18nModule,
UiModule,
UiButtonModule,
UiNativeModule,
AppOrganizationModule
],

View File

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

View File

@ -1,4 +1,4 @@
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 {

View File

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

View File

@ -14,25 +14,25 @@ $login-bg-h: 100/1080;
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-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 + '%'),

View File

@ -1 +1,22 @@
<div class="logout-container">Logout</div>
<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

@ -3,4 +3,57 @@
.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

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

View File

@ -3,18 +3,52 @@ import { CommonModule } from '@angular/common';
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 { IndexPageComponent } from './components/index.page.component';
import { SidenavPageComponent } from './components/sidenav.page.component';
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent];
export { IndexPageComponent, SidenavPageComponent };
import { COMPONENTS } from './components';
import { AppGroupModule } from '@app/ucap/group/group.module';
@NgModule({
imports: [CommonModule, FlexLayoutModule, AppCallRoutingPageModule],
imports: [
CommonModule,
FlexLayoutModule,
MatButtonModule,
MatIconModule,
MatTabsModule,
I18nModule,
UiDateModule,
UiPhoneModule,
OrganizationUiModule,
AppOrganizationModule,
AppCallSectionModule,
AppCallRoutingPageModule,
AppGroupModule
],
declarations: [...COMPONENTS],
entryComponents: []
entryComponents: [],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['call', 'common']
}
]
})
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 { 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>
sidenav page of call is works!
<div class="sidenav-container call" fxFlexFill fxLayout="column">
<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>

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 { Router } from '@angular/router';
import { Subject, combineLatest } from 'rxjs';
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 { 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({
selector: 'app-pages-call-sidenav',
templateUrl: './sidenav.page.component.html',
styleUrls: ['./sidenav.page.component.scss']
styleUrls: ['./sidenav.page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SidenavPageComponent {
constructor(private logService: LogService) {}
export class SidenavPageComponent implements OnInit, OnDestroy {
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

@ -14,7 +14,10 @@ import { AppChatSectionModule } from '@app/sections/chat/chat.section.module';
import { AppChatRoutingPageModule } from './chat-routing.page.module';
import { UiModule } from '@ucap/ng-ui';
import { UiCoreModule } from '@ucap/ng-ui/core';
import { UiDateModule } from '@ucap/ng-ui/date';
import { UiViewerModule } from '@ucap/ng-ui/viewer';
import { COMPONENTS } from './components';
import { UCAP_I18N_NAMESPACE, I18nModule } from '@ucap/ng-i18n';
@ -30,11 +33,14 @@ import { UCAP_I18N_NAMESPACE, I18nModule } from '@ucap/ng-i18n';
MatSidenavModule,
MatTooltipModule,
UiCoreModule,
UiDateModule,
UiViewerModule,
AppChatSectionModule,
AppChatRoutingPageModule,
I18nModule,
UiModule
I18nModule
],
declarations: [...COMPONENTS],
entryComponents: [],

View File

@ -8,12 +8,23 @@
</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">
<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()"
@ -21,9 +32,11 @@
</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>

View File

@ -1,15 +1,25 @@
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 { Subscription, Subject, BehaviorSubject } from 'rxjs';
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 { takeUntil } from 'rxjs/operators';
import { DrawInfo } from '../models/draw-info';
import { Store } from '@ngrx/store';
import { RoomActions, ChattingActions } from '@ucap/ng-store-chat';
import { SearchInfo } from '../models/search-info';
@Component({
selector: 'app-pages-chat-room',
@ -18,6 +28,11 @@ import { RoomActions, ChattingActions } from '@ucap/ng-store-chat';
})
export class ChatRoomPageComponent implements OnInit, OnDestroy {
isChatSearch = false;
searchObj: SearchInfo = {
isShowSearch: false,
searchWord: ''
};
roomId: string;
translationSimpleview = false;
@ -25,9 +40,15 @@ export class ChatRoomPageComponent implements OnInit, OnDestroy {
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();
@ -42,11 +63,20 @@ export class ChatRoomPageComponent implements OnInit, OnDestroy {
.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.
@ -68,6 +98,14 @@ export class ChatRoomPageComponent implements OnInit, OnDestroy {
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;
@ -78,4 +116,28 @@ export class ChatRoomPageComponent implements OnInit, OnDestroy {
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,3 +1,4 @@
@import '~@ucap/lg-scss/mixins';
.index-page-chat-info {
width: 100%;
height: 100%;
@ -18,4 +19,15 @@
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

@ -10,10 +10,12 @@
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
padding: 0 5px 0 17px;
background-color: $white;
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;

View File

@ -1,24 +1,32 @@
import { Subject, of } from 'rxjs';
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 { Subject, of } from 'rxjs';
import { Store, select } from '@ngrx/store';
import { takeUntil, take, map, catchError } from 'rxjs/operators';
import { I18nService } from '@ucap/ng-i18n';
import { RoomSelector, RoomActions } from '@ucap/ng-store-chat';
import { RoomInfo, ExitAllRequest } from '@ucap/protocol-room';
import { MatDialog } from '@angular/material/dialog';
import {
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
} from '@ucap/ng-ui';
import { I18nService } from '@ucap/ng-i18n';
import { SearchInfo } from '../models/search-info';
import { ActivatedRoute, Params } from '@angular/router';
import { QueryParams } from '../types/params.type';
} from '@ucap/ng-ui/core';
import { AppChatService } from '@app/services/app-chat.service';
import { SessionStorageService } from '@ucap/ng-web-storage';
import { AppKey } from '@app/types';
import { AppRoomSelector } from '@app/store/state';
import { SearchInfo } from '../models/search-info';
import { QueryParams } from '../types/params.type';
@Component({
selector: 'app-pages-chat-sidenav',
@ -46,14 +54,18 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
private dialog: MatDialog,
private i18nService: I18nService,
private logService: LogService,
private sessionStorageService: SessionStorageService,
private activatedRoute: ActivatedRoute,
private appChatService: AppChatService,
private changeDetectorRef: ChangeDetectorRef
) {
this.historyRoomId = this.sessionStorageService.get<string>(
AppKey.HistoryRoomId
);
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(AppRoomSelector.historyRoomId)
)
.subscribe((historyRoomId) => {
this.historyRoomId = historyRoomId;
});
}
ngOnInit(): void {
@ -62,10 +74,9 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
.subscribe((params: Params) => {
const seqParam = params[QueryParams.ROOM_ID];
this.sessionStorageService.set<string>(
AppKey.HistoryRoomId,
!!seqParam ? seqParam : undefined
);
if (!!seqParam) {
this.appChatService.setHistoryRoomId(seqParam);
}
if (
seqParam === undefined &&

View File

@ -22,7 +22,8 @@
>
<app-organization-profile-01
[userSeq]="userSeq"
(openChat)="onOpenCaht($event)"
[companyList]="companyList"
(openChat)="onOpenChat($event)"
(sendMessage)="onSendMessage($event)"
(sendCall)="onSendCall($event)"
(sendSms)="onSendSms($event)"

View File

@ -48,9 +48,10 @@
position: fixed;
height: 49px;
top: 40px;
left: 75px;
left: 60px;
z-index: 10;
width: calc(100% - 90px);
width: calc(100% - 60px);
background-color: rgba(255, 255, 255, 0.3);
a {
width: 50%;
text-decoration: none;
@ -66,11 +67,11 @@
.profile-container {
height: 100%;
overflow: auto;
flex: 1 0 460px !important;
flex: 0 0 460px !important;
max-width: inherit !important;
border-radius: 2px;
@include screen(custom, min, 1540) {
flex: 1 0 44% !important;
flex: 0 0 44% !important;
}
@include screen(lg) {
flex: 1 0 auto !important;

View File

@ -1,5 +1,5 @@
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { takeUntil, take, map } from 'rxjs/operators';
import {
Component,
@ -14,22 +14,29 @@ import { MatDialog } from '@angular/material/dialog';
import { Store, select } from '@ngrx/store';
import { FileUploadItem } from '@ucap/api';
import { FileUploadItem } from '@ucap/domain-common';
import {
UserInfoSS,
UserInfoF,
UserInfoUpdateType,
User,
Company
} from '@ucap/domain-organization';
import { FileProfileSaveRequest } from '@ucap/api-common';
import { VersionInfo2Response } from '@ucap/api-public';
import { UserInfoSS, UserInfoF } from '@ucap/protocol-query';
import { UserInfoUpdateType, User } from '@ucap/protocol-info';
import { LoginResponse } from '@ucap/protocol-authentication';
import { ConferenceCreateRequest } from '@ucap/api-prompt';
import { LogService } from '@ucap/ng-logger';
import { I18nService } from '@ucap/ng-i18n';
import { UserSelector } from '@ucap/ng-store-organization';
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 } from '@app/types';
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';
@ -37,6 +44,13 @@ import { AppGroupService } from '@app/services/app-group.service';
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({
selector: 'app-pages-group-index',
templateUrl: './index.page.component.html',
@ -65,11 +79,13 @@ export class IndexPageComponent implements OnInit, OnDestroy {
userSeq: string = undefined;
user: User;
loginRes: LoginResponse;
versionInfo2Res: VersionInfo2Response;
loginInfo: LoginInfo;
versionInfo: VersionInfo;
activeLink = 0;
tabName: string;
profileName: string;
buddyList: UserInfoTypes[];
companyList: Company[];
ngOnInit(): void {
this.activatedRoute.queryParams
@ -91,18 +107,36 @@ export class IndexPageComponent implements OnInit, OnDestroy {
this._refreshProfile();
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginInfo))
.subscribe((loginInfo) => {
this.loginInfo = loginInfo;
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
.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;
});
}
@ -113,7 +147,7 @@ export class IndexPageComponent implements OnInit, OnDestroy {
}
}
onOpenCaht(userInfo: UserInfoSS) {
onOpenChat(userInfo: UserInfoSS) {
this.appChatService.newOpenRoom(
[Number(userInfo.seq) as any],
false,
@ -123,15 +157,48 @@ export class IndexPageComponent implements OnInit, OnDestroy {
onSendMessage(userInfo: UserInfoSS) {}
onSendCall(call: string) {}
onSendSms(employeeNum: string) {}
onCreateConference(userSeq: number) {}
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 }) {
this.appGroupService
.updateBuddyByToggle(params)
.then((isRemoveBuddy) => {
if (isRemoveBuddy) {
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',
@ -143,9 +210,8 @@ export class IndexPageComponent implements OnInit, OnDestroy {
queryParams: { id: Number(params.userInfo.seq) }
}
);
});
}
})
.catch((reson) => this.logService.error(reson));
}
onUploadProfileImage(profileImageFileUploadItem: FileUploadItem) {
const loginSession = this.appAuthenticationService.getLoginSession();
@ -153,14 +219,14 @@ export class IndexPageComponent implements OnInit, OnDestroy {
const profile = {
userSeq: String(this.user.info.seq),
deviceType: loginSession.deviceType,
token: this.loginRes.tokenString,
token: this.loginInfo.tokenString,
file: profileImageFileUploadItem.file,
fileUploadItem: profileImageFileUploadItem
} as FileProfileSaveRequest;
this.appFileServie.fileProfileSave(
profile,
this.versionInfo2Res.profileUploadUrl
this.versionInfo.profileUploadUrl
);
}
onUpdateIntro(intro: string) {
@ -168,6 +234,15 @@ export class IndexPageComponent implements OnInit, OnDestroy {
}
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

View File

@ -22,6 +22,7 @@
</div>
<div class="extra-box" fxFlex="0 0 50px">
<app-organization-search-for-tenant
[isBackspaceCanceled]="isBackspaceCanceled"
[(searchData)]="companySearchData"
(canceled)="onSearchCancel()"
>
@ -34,6 +35,7 @@
[searchData]="companySearchData"
[showType]="showType"
(clickUser)="onClickUser($event)"
(openProfile)="onOpenProfile($event)"
></app-sections-group-list>
</div>
</div>

View File

@ -1,5 +1,5 @@
import { of, Subject } from 'rxjs';
import { take, map, catchError } from 'rxjs/operators';
import { take, map, catchError, takeUntil } from 'rxjs/operators';
import {
Component,
@ -7,7 +7,8 @@ import {
OnDestroy,
ChangeDetectorRef,
ViewChild,
ChangeDetectionStrategy
ChangeDetectionStrategy,
NgZone
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
@ -15,14 +16,19 @@ import { Store } from '@ngrx/store';
import { MatDialog } from '@angular/material/dialog';
import { UserInfo } from '@ucap/domain-organization';
import { LogService } from '@ucap/ng-logger';
import { I18nService } from '@ucap/ng-i18n';
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 { UserInfo } from '@ucap/protocol-sync';
import { SortViewType } from '../types/sort-view.type';
@Component({
@ -47,25 +53,43 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
}
// tslint:disable-next-line: variable-name
_companySearchData: SearchData;
isBackspaceCanceled = true;
showType: SortViewType;
sortViewType = SortViewType;
private ngOnDestroySubject: Subject<void> = new Subject();
private loginSession: LoginSession;
constructor(
private activatedRoute: ActivatedRoute,
private router: Router,
private appAuthenticationService: AppAuthenticationService,
private logService: LogService,
private i18nService: I18nService,
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
public dialog: MatDialog
public dialog: MatDialog,
private appUiService: AppUiService,
private ngZone: NgZone
) {}
ngOnInit(): void {
this.showType = SortViewType.all;
this.showGroupMenuIcon(SortViewType.all);
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 {
@ -132,6 +156,21 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
}
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: '' };
@ -146,6 +185,7 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
}
onClickUser(userInfo: UserInfo) {
this.ngZone.run(() => {
this.router.navigate(
[
'group',
@ -157,5 +197,12 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
queryParams: { id: Number(userInfo.seq) }
}
);
this.appUiService.closeLeftSidenavOnNarrowMode();
});
}
onOpenProfile(userInfo: UserInfo) {
this.onClickUser(userInfo);
}
}

View File

@ -8,7 +8,7 @@ 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 { UiModule } from '@ucap/ng-ui';
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
@ -34,7 +34,6 @@ import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
AppGroupSectionModule,
AppGroupRoutingPageModule,
UiModule,
I18nModule
],
declarations: [...COMPONENTS],

View File

@ -9,7 +9,7 @@
width: 200px;
height: 200px;
margin-top: -100px;
background-image: url(../../../../assets/images/ico/img_coming_soon.png);
background-image: url(/assets/images/ico/img_coming_soon.png);
background-size: 100% auto;
}
.coming-soon-index-copy {

View File

@ -2,6 +2,7 @@
<!-- search start-->
<div fxFlex="0 0 50px">
<app-organization-search-for-tenant
[isBackspaceCanceled]="isBackspaceCanceled"
[(searchData)]="companySearchData"
(canceled)="onCanceledSearch()"
>

View File

@ -12,11 +12,11 @@ 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';
import { UserStore } from '@app/models/user-store';
@Component({
selector: 'app-pages-organization-index',
@ -36,7 +36,7 @@ export class IndexPageComponent implements OnInit, OnDestroy {
_companySearchData: SearchData;
deptSearchData: SearchData;
isBackspaceCanceled = true;
deptSeq: string;
private ngOnDestroySubject: Subject<void> = new Subject();
@ -107,6 +107,7 @@ export class IndexPageComponent implements OnInit, OnDestroy {
onCanceledSearch() {
const queryParams: Params = {};
queryParams[QueryParams.DEPT_SEQ] = String(this.deptSeq);
this._navigate(queryParams);
}

View File

@ -12,6 +12,7 @@
<div fxFlex="1 1 auto">
<app-organization-tree
[initialExpanded]="initialExpanded"
[windowSizeMode]="windowSizeMode"
(clicked)="onClickedTree($event)"
></app-organization-tree>
</div>

View File

@ -12,12 +12,17 @@ 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 { DeptInfo } from '@ucap/protocol-query';
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';
@ -32,6 +37,7 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
initialExpanded: number;
displayRoot = false;
displayRootDept: DeptInfo;
windowSizeMode: WindowSizeMode;
private ngOnDestroySubject: Subject<void> = new Subject();
@ -40,6 +46,7 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
private activatedRoute: ActivatedRoute,
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
private appUiService: AppUiService,
private logService: LogService
) {
this.logService.info('app-pages-ogranization-sidenav');
@ -91,6 +98,15 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
}
}
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(AppSelector.windowSizeMode)
)
.subscribe((windowSizeMode) => {
this.windowSizeMode = windowSizeMode;
});
}
ngOnDestroy(): void {
@ -103,7 +119,6 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
onClickedTree(node: DeptInfo) {
const queryParams: Params = {};
queryParams[QueryParams.DEPT_SEQ] = String(node.seq);
this.router.navigate(
[
'organization',
@ -115,5 +130,6 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
queryParams
}
);
this.appUiService.closeLeftSidenavOnNarrowMode();
}
}

View File

@ -13,7 +13,7 @@ import { AppOrganizationRoutingPageModule } from './organization-routing.page.mo
import { COMPONENTS } from './components';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { UiModule } from '@ucap/ng-ui';
// import { UiModule } from '@ucap/ng-ui';
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
@NgModule({
@ -30,8 +30,7 @@ import { OrganizationUiModule } from '@ucap/ng-ui-organization';
AppOrganizationRoutingPageModule,
I18nModule,
OrganizationUiModule,
UiModule
OrganizationUiModule
],
declarations: [...COMPONENTS],
entryComponents: [],

View File

@ -101,13 +101,13 @@ export class AppSessionResolver implements Resolve<void> {
this.store.dispatch(
ConfigurationActions.versionInfo2Success({
res: versionInfo2Res
versionInfo: versionInfo2Res.versionInfo
})
);
this.store.dispatch(
ConfigurationActions.urlInfoSuccess({
res: urlInfoRes
urlInfo: urlInfoRes.urlInfo
})
);
@ -128,7 +128,9 @@ export class AppSessionResolver implements Resolve<void> {
}
);
this.protocolService.connect(versionInfo2Res.serverIp);
this.protocolService.connect(
versionInfo2Res.versionInfo.serverIp
);
},
(error) => {
reject(error);

View File

@ -13,7 +13,7 @@ import { MatSelectModule } from '@angular/material/select';
import { MatTabsModule } from '@angular/material/tabs';
import { I18nModule } from '@ucap/ng-i18n';
import { UiModule } from '@ucap/ng-ui';
// import { UiModule } from '@ucap/ng-ui';
import { AuthenticationUiModule } from '@ucap/ng-ui-authentication';
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
@ -38,7 +38,6 @@ import { COMPONENTS } from './components';
I18nModule,
UiModule,
AuthenticationUiModule,
OrganizationUiModule,

View File

@ -0,0 +1,63 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatTabsModule } from '@angular/material/tabs';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
// import { UiModule } from '@ucap/ng-ui';
import { CallUiModule } from '@ucap/ng-ui-call';
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
import { AppLayoutsModule } from '@app/layouts/layouts.module';
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
import { AppGroupSectionModule } from '../group/group.section.module';
import { COMPONENTS } from './components';
import { DIALOGS } from './dialogs';
import { AppCallModule } from '@app/ucap/call/call.module';
@NgModule({
imports: [
CommonModule,
FlexLayoutModule,
MatIconModule,
MatButtonModule,
MatTooltipModule,
MatTabsModule,
MatProgressBarModule,
PerfectScrollbarModule,
ScrollingModule,
I18nModule,
AppLayoutsModule,
AppGroupSectionModule,
AppOrganizationModule,
AppCallModule,
CallUiModule,
OrganizationUiModule
],
exports: [...COMPONENTS, ...DIALOGS],
declarations: [...COMPONENTS, ...DIALOGS],
entryComponents: [...DIALOGS],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['call', 'common']
}
]
})
export class AppCallSectionModule {}

View File

@ -0,0 +1,4 @@
import { InfoSectionComponent } from './info.section.component';
import { ListSectionComponent } from './list.section.component';
export const COMPONENTS = [InfoSectionComponent, ListSectionComponent];

View File

@ -0,0 +1,28 @@
<div class="app-sections-call-info-container" fxLayout="row">
<div class="history-all" fxFlex="1 1 auto">
<div class="subtitle2">{{ 'call:label.callHistory' | ucapI18n }}</div>
<div class="list-container">
<app-call-time-line
[historyList]="originalHistoryList"
></app-call-time-line>
</div>
</div>
<div class="history-other" fxFlex="44%">
<div class="history-away">
<div class="subtitle2">{{ 'call:label.missed' | ucapI18n }}</div>
<div class="list-container">
<app-call-time-line
[historyList]="awayHistoryList"
></app-call-time-line>
</div>
</div>
<div class="history-noreply">
<div class="subtitle2">{{ 'call:label.unanswered' | ucapI18n }}</div>
<div class="list-container">
<app-call-time-line
[historyList]="unansweredHistoryList"
></app-call-time-line>
</div>
</div>
</div>
</div>

View File

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

View File

@ -0,0 +1,106 @@
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input
} from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Subject, combineLatest } from 'rxjs';
import { takeUntil, withLatestFrom } from 'rxjs/operators';
import { CallSelector } from '@ucap/ng-store-call';
import { CallHistory, CallResultType } from '@ucap/domain-call';
import { ActivatedRoute } from '@angular/router';
import { UserSelector } from '@ucap/ng-store-organization';
import { QueryParams } from '@app/pages/call/types/params.type';
@Component({
selector: 'app-sections-call-info',
templateUrl: './info.section.component.html',
styleUrls: ['./info.section.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class InfoSectionComponent implements OnInit, OnDestroy {
isMe: boolean;
userSeq: string;
originalHistoryList: CallHistory[];
awayHistoryList: CallHistory[];
unansweredHistoryList: CallHistory[];
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private store: Store<any>,
private activatedRoute: ActivatedRoute,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit(): void {
combineLatest([
this.activatedRoute.queryParams,
this.store.pipe(select(CallSelector.callHistory))
])
.pipe(
takeUntil(this.ngOnDestroySubject),
withLatestFrom(this.store.pipe(select(UserSelector.user)))
)
.subscribe(([[params, callHistory], user]) => {
let existParams = false;
if (!!params) {
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;
}
this.originalHistoryList = callHistory;
this.getFiltered();
}
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
getFiltered(): void {
// original
this.originalHistoryList = this.originalHistoryList.filter((item) => {
let result = true;
if (!this.isMe && !!this.userSeq) {
if (!!item.sendYn && item.sendYn === true) {
result = item.calledUserSeq === this.userSeq;
} else {
result = item.callingUserSeq === this.userSeq;
}
}
return result;
});
// away
this.awayHistoryList = this.originalHistoryList.filter(
(item) => item.callResult === CallResultType.Away
);
// unanswered
this.unansweredHistoryList = this.originalHistoryList.filter(
(item) => item.callResult === CallResultType.Missed
);
this.changeDetectorRef.markForCheck();
}
}

View File

@ -0,0 +1,132 @@
<div fxFlexFill class="list-container">
<div class="select-call-section-content">
<mat-tab-group
#selectUserTabGroup
mat-stretch-tabs
class="tap-container tab_num2"
>
<mat-tab>
<ng-template mat-tab-label>
<p>
<mat-icon>history</mat-icon>
</p>
</ng-template>
<ng-template matTabContent>
<div fxFlexFill class="select-tap">
<div class="sub-title">
{{ 'call:label.callHistory' | ucapI18n
}}<strong>{{ filteredCallHistory.length }}</strong>
</div>
<div class="btns">
<button
mat-button
[ngClass]="filteringByTypeClass(CallHistorySearchType.All)"
(click)="filteringByType(CallHistorySearchType.All)"
>
{{ 'call:label.all' | ucapI18n }}
</button>
<button
mat-button
[ngClass]="filteringByTypeClass(CallHistorySearchType.Send)"
(click)="filteringByType(CallHistorySearchType.Send)"
>
{{ 'call:label.send' | ucapI18n }}
</button>
<button
mat-button
[ngClass]="filteringByTypeClass(CallHistorySearchType.Receive)"
(click)="filteringByType(CallHistorySearchType.Receive)"
>
{{ 'call:label.recieve' | ucapI18n }}
</button>
<button
mat-button
[ngClass]="filteringByTypeClass(CallHistorySearchType.Missed)"
(click)="filteringByType(CallHistorySearchType.Missed)"
>
{{ 'call:label.missed' | ucapI18n }}
</button>
<button
mat-button
[ngClass]="
filteringByTypeClass(CallHistorySearchType.UnAnswered)
"
(click)="filteringByType(CallHistorySearchType.UnAnswered)"
>
{{ 'call:label.unanswered' | ucapI18n }}
</button>
</div>
<div class="list">
<app-call-history-expansion [itemList]="filteredCallHistory">
</app-call-history-expansion>
</div>
</div>
</ng-template>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<p>
<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>
</p>
</ng-template>
<ng-template matTabContent>
<div fxFlexFill class="select-tap">
<div class="sub-title">
{{ 'call:label.organization' | ucapI18n }}
</div>
<div class="list">
<app-organization-profile-navigation-list
[checkable]="false"
[useFAB]="true"
>
</app-organization-profile-navigation-list>
</div>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
</div>

View File

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

View File

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

View File

@ -0,0 +1,114 @@
import { Subject } from 'rxjs';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input
} from '@angular/core';
import { LogService } from '@ucap/ng-logger';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { Store, select } from '@ngrx/store';
import { takeUntil } from 'rxjs/operators';
import { CallSelector } from '@ucap/ng-store-call';
import {
CallHistory,
CallHistorySearchType,
CallResultType
} from '@ucap/domain-call';
@Component({
selector: 'app-sections-call-list',
templateUrl: './list.section.component.html',
styleUrls: ['./list.section.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListSectionComponent implements OnInit, OnDestroy {
@Input()
set searchObj(obj: SearchData) {
this._getFiltered();
}
get searchObj(): SearchData {
return this._searchObj;
}
// tslint:disable-next-line: variable-name
_searchObj: SearchData;
callHistory: CallHistory[];
filteredCallHistory: CallHistory[];
searchType = CallHistorySearchType.All;
CallHistorySearchType = CallHistorySearchType;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
private logService: LogService
) {}
ngOnInit(): void {
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(CallSelector.callHistory)
)
.subscribe((callHistory) => {
this.callHistory = callHistory;
this._getFiltered();
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
filteringByTypeClass(searchType: CallHistorySearchType): string {
return this.searchType === searchType ? 'cdk-active' : '';
}
filteringByType(searchType: CallHistorySearchType) {
this.searchType = searchType;
this._getFiltered();
}
private _getFiltered() {
if (!!this.callHistory && this.callHistory.length > 0) {
switch (this.searchType) {
case CallHistorySearchType.Send:
this.filteredCallHistory = this.callHistory.filter(
(item) => !!item.sendYn
);
break;
case CallHistorySearchType.Receive:
this.filteredCallHistory = this.callHistory.filter(
(item) => !item.sendYn
);
break;
case CallHistorySearchType.Missed:
this.filteredCallHistory = this.callHistory.filter(
(item) => item.callResult === CallResultType.Away
);
break;
case CallHistorySearchType.UnAnswered:
this.filteredCallHistory = this.callHistory.filter(
(item) => item.callResult === CallResultType.Missed
);
break;
default:
// case CallHistorySearchType.All:
this.filteredCallHistory = this.callHistory;
break;
}
} else {
this.filteredCallHistory = [];
}
this.changeDetectorRef.markForCheck();
}
}

View File

@ -0,0 +1,14 @@
<div class="dialog-container">
<app-layouts-default-dialog
[disableClose]="false"
(closed)="onClosed($event)"
class="ucap-dialog-call-dialpad-container"
>
<div appLayoutsDefaultDialog="header">
{{ 'call:label.dialpad' | ucapI18n }}
</div>
<div class="dialog-body" appLayoutsDefaultDialog="body">
<ucap-call-dialpad (sendCall)="onSendCall($event)"></ucap-call-dialpad>
</div>
</app-layouts-default-dialog>
</div>

View File

@ -0,0 +1,75 @@
@import '~@ucap/lg-scss/mixins';
.dialog-container {
width: 100%;
height: 100%;
.dialog-body {
width: 100%;
height: 100%;
.profile {
display: flex;
flex-direction: row;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #ccc;
.profile-image {
border-radius: 50%;
overflow: hidden;
width: 36px;
height: 36px;
margin-left: 0;
background-color: #ffe8cb;
img {
max-width: 100%;
height: auto;
vertical-align: top;
border: none;
}
}
.user-info {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding-left: 16px;
.user-n-g {
display: flex;
flex-flow: row-reverse nowrap;
align-items: flex-end;
height: 22px;
.user-name {
@include ellipsis-column(1);
height: 22px;
font-size: 14px;
font-weight: 600;
order: 1;
-ms-flex-order: 1;
}
.user-grade {
@include ellipsis(1);
align-self: stretch;
font: {
size: 13px;
}
margin-left: 4px;
order: 0;
-ms-flex-order: 0;
}
.write-date {
font-size: 12px;
}
}
}
}
.contents {
width: 100%;
height: calc(100% - 60px);
overflow: hidden;
perfect-scrollbar {
width: 100%;
height: 100%;
padding-right: 10px;
}
}
}
}

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 { TextDetailDialogComponent } from './text-detail.dialog.component';
describe('ucap::ui-organization::CreateChatDialogComponent', () => {
let component: TextDetailDialogComponent;
let fixture: ComponentFixture<TextDetailDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TextDetailDialogComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TextDetailDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,86 @@
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject
} from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
MatDialog
} from '@angular/material/dialog';
import { Subject } from 'rxjs';
import { Store, select } from '@ngrx/store';
import { takeUntil } from 'rxjs/operators';
import { User } from '@ucap/domain-organization';
import { UserSelector } from '@ucap/ng-store-organization';
import { LoginSelector } from '@ucap/ng-store-authentication';
import { AppCallService } from '@app/services/app-call.service';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { LoginSession, LoginInfo } from '@ucap/domain-authentication';
export interface DialpadDialogData {}
export interface DialpadDialogResult {}
@Component({
selector: 'app-dialog-call-dialpad',
templateUrl: './dialpad.dialog.component.html',
styleUrls: ['./dialpad.dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DialpadDialogComponent implements OnInit, OnDestroy {
loginSession: LoginSession;
loginInfo: LoginInfo;
user: User;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private store: Store<any>,
public dialogRef: MatDialogRef<DialpadDialogData, DialpadDialogResult>,
@Inject(MAT_DIALOG_DATA) public data: DialpadDialogData,
private appCallService: AppCallService,
private appAuthenticationService: AppAuthenticationService,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit(): void {
this.loginSession = this.appAuthenticationService.getLoginSession();
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user))
.subscribe((user) => {
this.user = user;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginInfo))
.subscribe((loginInfo) => {
this.loginInfo = loginInfo;
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onSendCall(calleeNumber: string) {
this.appCallService.sendClicktocall(
this.loginInfo,
this.user,
this.loginSession.deviceType,
calleeNumber
);
this.dialogRef.close();
}
onClosed(event: MouseEvent): void {
this.dialogRef.close();
}
}

View File

@ -0,0 +1,3 @@
import { DialpadDialogComponent } from './dialpad.dialog.component';
export const DIALOGS = [DialpadDialogComponent];

View File

@ -4,8 +4,6 @@ import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatRippleModule } from '@angular/material/core';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatBadgeModule } from '@angular/material/badge';
@ -30,7 +28,12 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { UiModule } from '@ucap/ng-ui';
import { UiCoreModule } from '@ucap/ng-ui/core';
import { UiDateModule } from '@ucap/ng-ui/date';
import { UiViewerModule } from '@ucap/ng-ui/viewer';
import { UiscrollingModule } from '@ucap/ng-ui/scrolling';
import { ChatUiModule } from '@ucap/ng-ui-chat';
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
import { AppOrganizationModule } from '@app/ucap/organization/organization.module';
@ -42,6 +45,7 @@ import { AppGroupSectionModule } from '../group/group.section.module';
import { COMPONENTS } from './components';
import { DIALOGS } from './dialogs';
import { DRAWERS } from './drawers';
import { DIRECTIVES } from './directives';
import { AppGroupModule } from '@app/ucap/group/group.module';
@NgModule({
@ -73,10 +77,13 @@ import { AppGroupModule } from '@app/ucap/group/group.module';
MatProgressBarModule,
PerfectScrollbarModule,
ScrollingModule,
I18nModule,
UiModule,
UiCoreModule,
UiDateModule,
UiViewerModule,
UiscrollingModule,
AppLayoutsModule,
AppGroupSectionModule,
@ -87,8 +94,8 @@ import { AppGroupModule } from '@app/ucap/group/group.module';
AppChatModule,
AppGroupModule
],
exports: [...COMPONENTS, ...DIALOGS, ...DRAWERS],
declarations: [...COMPONENTS, ...DIALOGS, ...DRAWERS],
exports: [...COMPONENTS, ...DIALOGS, ...DRAWERS, ...DIRECTIVES],
declarations: [...COMPONENTS, ...DIALOGS, ...DRAWERS, ...DIRECTIVES],
entryComponents: [...DIALOGS, ...DRAWERS],
providers: [
{

View File

@ -3,15 +3,13 @@
<app-chat-selector-sticker
*ngSwitchCase="SelectorType.STICKER"
(selectedSticker)="onSelectedSticker($event)"
(closed)="selectorType = SelectorType.EMPTY"
(closed)="clearSelector()"
>
</app-chat-selector-sticker>
<app-chat-selector-translation
*ngSwitchCase="SelectorType.TRANSLATION"
(closed)="
selectorType = SelectorType.EMPTY; translationPreviewInfo = null
"
(closed)="clearSelector()"
[destLocale]="destLocale"
[simpleView]="translationSimpleview"
[preView]="translationPreview"
@ -26,18 +24,36 @@
<app-chat-selector-file-upload
#fileUploadSelector
*ngSwitchCase="SelectorType.FILEUPLOAD"
(closed)="selectorType = SelectorType.EMPTY"
(closed)="clearSelector()"
>
</app-chat-selector-file-upload>
<app-chat-selector-email-send
*ngSwitchCase="SelectorType.EMAILSENDER"
(sendEventEmail)="onSendEventEmail($event)"
(closed)="selectorType = SelectorType.EMPTY"
(closed)="clearSelector()"
></app-chat-selector-email-send>
</ng-container>
<div class="chat-form-area ucap-mat-input-container">
<!-- <div class="message-text" fxFlex floatLabel="never" appearance="none">
<textarea
#messageInput
placeholder="{{ 'chat:label.inputChatMessage' | ucapI18n }}"
name="message"
(keydown)="textareaResize($event, messageInput)"
(keydown.enter)="onKeydown($event)"
></textarea>
</div> -->
<!-- <mat-form-field
class="message-text"
fxFlex
floatLabel="never"
appearance="none"
>
<mat-label>{{ 'chat:label.inputChatMessage' | ucapI18n }}</mat-label>
<textarea matInput #messageInput></textarea>
</mat-form-field> -->
<mat-form-field
class="message-text"
fxFlex
@ -45,19 +61,13 @@
appearance="none"
>
<mat-label>{{ 'chat:label.inputChatMessage' | ucapI18n }}</mat-label>
<!-- <textarea
<textarea
matInput
#messageInput
name="message"
[matTextareaAutosize]="true"
(keydown.enter)="onKeydown($event)"
></textarea> -->
<input
matInput
#messageInput
name="message"
(keydown.enter)="onKeydown($event)"
/>
></textarea>
</mat-form-field>
<input
@ -94,7 +104,7 @@
</button>
<!-- <button
mat-icon-button
*ngIf="!!authRes && !!authRes.useCapturePcScreen"
*ngIf="!!userPermission && !!userPermission.useCapturePcScreen"
aria-label="screenshot"
matTooltipPosition="above"
matTooltip="{{ 'label.screenshot' | ucapI18n }}"
@ -115,7 +125,7 @@
</button>
<button
mat-icon-button
*ngIf="!!authRes && !!authRes.canSendEmail"
*ngIf="!!userPermission && !!userPermission.canSendEmail"
aria-label="emailSend"
matTooltipPosition="above"
matTooltip="{{ 'label.emailSend' | ucapI18n }}"
@ -126,7 +136,7 @@
</button>
<button
mat-icon-button
*ngIf="!!authRes && !!authRes.canTranslation"
*ngIf="!!userPermission && !!userPermission.canTranslation"
aria-label="translation"
matTooltipPosition="above"
matTooltip="{{ 'label.translation' | ucapI18n }}"
@ -137,7 +147,7 @@
</button>
<!-- <button
mat-icon-button
*ngIf="!!authRes && !!authRes.useGams"
*ngIf="!!userPermission && !!userPermission.useGams"
aria-label="gams"
matTooltipPosition="above"
matTooltip="{{ 'label.gams' | ucapI18n }}"

View File

@ -10,7 +10,7 @@
max-height: 100%;
min-height: 20px;
background-color: $white;
padding-left: 30px;
padding: 0 20px;
font-size: 0.929em;
margin: 8px 0;
overflow-x: hidden;
@ -19,11 +19,15 @@
@include screen(xs) {
padding-left: 16px;
}
.message-text {
@include font-family($font-regular);
textarea {
min-height: 22px;
margin: 0;
overflow: hidden;
}
}
}
.button-area {
display: flex;
flex-direction: row;

View File

@ -18,28 +18,29 @@ import { Store, select } from '@ngrx/store';
import { MatDialog } from '@angular/material/dialog';
import { FileUploadItem } from '@ucap/domain-common';
import { User } from '@ucap/domain-organization';
import { UserPermission } from '@ucap/domain-authorization';
import {
RoomInfo,
EventType,
MassTranslationEventJson,
TranslationEventJson
} from '@ucap/domain-chat';
import { StickerFilesInfo } from '@ucap/ng-core';
import { StatusCode, FileUploadItem } from '@ucap/api';
import { StatusCode } from '@ucap/api';
import {
TranslationSaveResponse,
TranslationSaveRequest
} from '@ucap/api-common';
import { VersionInfo2Response } from '@ucap/api-public';
import {
SendEventMailType,
SendEventEmailRequest,
SendEventEmailResponse,
StatusCode as PiStatusCode
} from '@ucap/pi';
import { RoomInfo } from '@ucap/protocol-room';
import {
EventType,
MassTranslationEventJson,
TranslationEventJson
} from '@ucap/protocol-event';
import { LoginResponse } from '@ucap/protocol-authentication';
import { AuthResponse } from '@ucap/protocol-query';
import { User } from '@ucap/protocol-info';
import { I18nService } from '@ucap/ng-i18n';
import { LogService } from '@ucap/ng-logger';
@ -61,7 +62,7 @@ import {
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
} from '@ucap/ng-ui';
} from '@ucap/ng-ui/core';
import { LoginSession } from '@app/models/login-session';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
@ -70,6 +71,7 @@ import { FileUploadSelectorComponent } from '@app/ucap/chat/components/file-uplo
import { AppChatService } from '@app/services/app-chat.service';
import { environment } from '@environments';
import { LoginInfo, VersionInfo } from '@ucap/domain-authentication';
export enum SelectorType {
EMPTY = '',
@ -107,14 +109,17 @@ export class FormSectionComponent implements OnInit, OnDestroy {
@Output()
changeTranslationSimpleview = new EventEmitter<boolean>();
@Output()
openFormSelector = new EventEmitter<string>();
@Output()
eventSendTrigger = new EventEmitter<any>();
versionInfo2Res: VersionInfo2Response;
versionInfo: VersionInfo;
loginSession: LoginSession;
loginRes: LoginResponse;
loginInfo: LoginInfo;
user: User;
authRes: AuthResponse;
userPermission: UserPermission;
currentRoomInfo: RoomInfo;
@ -125,8 +130,8 @@ export class FormSectionComponent implements OnInit, OnDestroy {
/** About Translation */
isTranslationProcess = false;
translationSimpleview = true;
translationPreview = true;
translationSimpleview = false;
translationPreview = false;
destLocale = 'en'; // default English :: en
translationPreviewInfo: {
previewInfo: TranslationSaveResponse | null;
@ -159,10 +164,10 @@ export class FormSectionComponent implements OnInit, OnDestroy {
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
.subscribe((versionInfo) => {
this.versionInfo = versionInfo;
});
this.store
@ -172,18 +177,18 @@ export class FormSectionComponent implements OnInit, OnDestroy {
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginInfo))
.subscribe((loginInfo) => {
this.loginInfo = loginInfo;
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(AuthorizationSelector.authResponse)
select(AuthorizationSelector.userPermission)
)
.subscribe((authRes) => {
this.authRes = authRes;
.subscribe((userPermission) => {
this.userPermission = userPermission;
});
}
@ -206,9 +211,10 @@ export class FormSectionComponent implements OnInit, OnDestroy {
this.selectorType = SelectorType.EMPTY;
this.translationSimpleview = false;
this.changeTranslationSimpleview.emit(false);
this.translationPreview = true;
this.translationPreview = false;
this.destLocale = 'en'; // default English :: en
this.translationPreviewInfo = null;
this.selectedSticker = undefined;
this.store
.pipe(
@ -225,11 +231,19 @@ export class FormSectionComponent implements OnInit, OnDestroy {
/** About Selector */
onOpenSelector(type: SelectorType): void {
this.selectorType = type;
this.changeDetectorRef.markForCheck();
this.selectedSticker = null;
this.translationPreviewInfo = null;
this.changeDetectorRef.detectChanges();
this.openFormSelector.emit(type);
}
clearSelector(): void {
this.selectorType = SelectorType.EMPTY;
this.selectedSticker = null;
this.translationPreviewInfo = null;
this.changeDetectorRef.markForCheck();
}
@ -246,62 +260,6 @@ export class FormSectionComponent implements OnInit, OnDestroy {
}
}
onChangeFileInput(): void {
const self = this;
const fileList = this.fileInput.nativeElement.files;
this.appFileService
.validUploadFile(fileList, this.versionInfo2Res?.fileAllowSize)
.then((result) => {
if (!result) {
self.fileInput.nativeElement.value = '';
return;
} else {
// selector open
self.onOpenSelector(SelectorType.FILEUPLOAD);
self.changeDetectorRef.detectChanges();
// FileuploadItem Init. & FileSelector Init.
const fileUploadItems = FileUploadItem.fromFiles(fileList);
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onFileSelected(fileUploadItems);
}
self.fileInput.nativeElement.value = '';
// File Upload..
self.appChatService
.sendMessageOfAttachFile(
self.loginRes,
self.user,
self.loginSession.deviceType,
self.currentRoomInfo.roomId,
fileUploadItems
)
.then((success) => {
if (!!success) {
self.clearSelector();
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onUploadComplete();
}
}
})
.catch((err) => {
self.clearSelector();
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onUploadComplete();
}
const msg = this.i18nService.t('common:file.errors.failToUpload');
alert(msg);
});
}
})
.catch((err) => {
self.fileInput.nativeElement.value = '';
self.logService.error(`validUploadFile ${err}`);
});
}
onKeydown(event: KeyboardEvent) {
// if (event.key === 'PageUp' || event.key === 'PageDown') {
// event.preventDefault();
@ -311,10 +269,17 @@ export class FormSectionComponent implements OnInit, OnDestroy {
// this.send();
// }
event.preventDefault();
event.stopPropagation();
this.send();
}
textareaResize(event: KeyboardEvent, obj) {
const self = obj;
setTimeout(() => {
self.style.height = '1px';
self.style.height = self.scrollHeight + 'px';
});
}
onSelectedSticker(stickerInfo: StickerFilesInfo) {
this.selectedSticker = stickerInfo;
this.focus(false);
@ -412,7 +377,7 @@ export class FormSectionComponent implements OnInit, OnDestroy {
) {
/** CASE : MASS TEXT */
this.appChatService.sendMessageOfMassText(
this.loginRes,
this.loginInfo,
this.user,
this.loginSession.deviceType,
roomId,
@ -437,7 +402,7 @@ export class FormSectionComponent implements OnInit, OnDestroy {
this.commonApiService
.translationSave({
userSeq: String(this.user.info.seq),
token: this.loginRes.tokenString,
token: this.loginInfo.tokenString,
deviceType: this.loginSession.deviceType,
original: message,
roomId: this.roomId,
@ -547,6 +512,7 @@ export class FormSectionComponent implements OnInit, OnDestroy {
sentMessage
);
this.translationPreviewInfo = undefined;
this.eventSendTrigger.emit(0);
this.focus();
}
@ -580,7 +546,7 @@ export class FormSectionComponent implements OnInit, OnDestroy {
const req: SendEventEmailRequest = {
userSeq: String(this.user.info.seq),
deviceType: this.loginSession.deviceType,
tokenKey: this.loginRes.tokenString,
tokenKey: this.loginInfo.tokenString,
roomSeq: this._roomId,
eventSeq: String(eventList[0].seq),
sendType: type
@ -624,4 +590,100 @@ export class FormSectionComponent implements OnInit, OnDestroy {
}
});
}
/**
* About File Upload
*/
/** FileInput change event. */
onChangeFileInput(): void {
const fileList = this.fileInput.nativeElement.files;
this._fileSend(fileList);
}
/** DragAndDrop :: Add fileupload queue. not sending. */
onDragAndDropStandbyFileupload(fileList: FileList) {
const self = this;
this.appFileService
.validUploadFile(fileList, this.versionInfo?.fileAllowSize)
.then((result) => {
if (!result) {
self.fileInput.nativeElement.value = '';
return;
} else {
// selector open
self.onOpenSelector(SelectorType.FILEUPLOAD);
// FileuploadItem Init. & FileSelector Init.
const fileUploadItems = FileUploadItem.fromFiles(fileList);
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onFileSelected(fileUploadItems);
}
self.fileInput.nativeElement.value = '';
}
})
.catch((err) => {
self.fileInput.nativeElement.value = '';
self.logService.error(`validUploadFile ${err}`);
});
}
/** DragAndDrop :: fileUpload direct. */
onDragAndDropFileupload(fileList: FileList): void {
this._fileSend(fileList);
}
private _fileSend(fileList: FileList) {
const self = this;
this.appFileService
.validUploadFile(fileList, this.versionInfo?.fileAllowSize)
.then((result) => {
if (!result) {
self.fileInput.nativeElement.value = '';
return;
} else {
// selector open
self.onOpenSelector(SelectorType.FILEUPLOAD);
self.changeDetectorRef.detectChanges();
// FileuploadItem Init. & FileSelector Init.
const fileUploadItems = FileUploadItem.fromFiles(fileList);
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onFileSelected(fileUploadItems);
}
self.fileInput.nativeElement.value = '';
// File Upload..
self.appChatService
.sendMessageOfAttachFile(
self.loginInfo,
self.user,
self.loginSession.deviceType,
self.currentRoomInfo.roomId,
fileUploadItems
)
.then((success) => {
if (!!success) {
self.eventSendTrigger.emit(0);
self.clearSelector();
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onUploadComplete();
}
}
})
.catch((err) => {
self.clearSelector();
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onUploadComplete();
}
const msg = this.i18nService.t('common:file.errors.failToUpload');
alert(msg);
});
}
})
.catch((err) => {
self.fileInput.nativeElement.value = '';
self.logService.error(`validUploadFile ${err}`);
});
}
}

View File

@ -1,11 +1,19 @@
<mat-toolbar class="info-chat-toolbar">
<mat-toolbar-row class="info-chat-toolbar-content">
<div class="chat-room-profile">
<div class="profile-image">
<div
class="profile-image"
(click)="onOpenProfile()"
[style.cursor]="
!!currentRoomInfo && currentRoomInfo.roomType === RoomType.Single
? 'pointer'
: 'default'
"
>
<img
class="thumbnail"
ucapImage
[base]="versionInfo2Res?.profileRoot"
[base]="versionInfo?.profileRoot"
[path]="roomImage"
[default]="
currentRoomInfo?.roomType === RoomType.Multi

View File

@ -13,15 +13,16 @@ import {
} from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Dictionary } from '@ngrx/entity';
import { MatDialog } from '@angular/material/dialog';
import { LocaleCode } from '@ucap/core';
import { LocaleCode } from '@ucap/domain-common';
import { User } from '@ucap/domain-organization';
import { RoomInfo, RoomType } from '@ucap/domain-chat';
import { I18nService } from '@ucap/ng-i18n';
import { VersionInfo2Response } from '@ucap/api-public';
import { RoomInfo, RoomType, UpdateRequest } from '@ucap/protocol-room';
import { User } from '@ucap/protocol-info';
import { UpdateRequest } from '@ucap/protocol-room';
import { UserSelector } from '@ucap/ng-store-organization';
import { ConfigurationSelector } from '@ucap/ng-store-authentication';
@ -40,6 +41,12 @@ import {
import { AppChatService } from '@app/services/app-chat.service';
import { ChatDrawType } from '@app/pages/chat/types/chat-draw.type';
import { DrawInfo } from '@app/pages/chat/models/draw-info';
import {
ProfileDialogComponent,
ProfileDialogData,
ProfileDialogResult
} from '@app/sections/organization/dialogs/profile.dialog.component';
import { VersionInfo } from '@ucap/domain-authentication';
@Component({
selector: 'app-sections-chat-info',
@ -53,11 +60,13 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
@Input()
set roomId(roomId: string) {
if (this._roomId !== roomId) {
this._roomId = roomId;
this.roomIdSubject.next(roomId);
this.initializeRoomData();
}
// request selected room
if (!!this.roomId) {
@ -81,7 +90,7 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
@Output()
rightDrawerToggle = new EventEmitter<DrawInfo>();
versionInfo2Res: VersionInfo2Response;
versionInfo: VersionInfo;
user: User;
defaultProfileImage: string;
@ -123,10 +132,10 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
.subscribe((versionInfo) => {
this.versionInfo = versionInfo;
});
}
@ -240,6 +249,36 @@ export class InfoSectionComponent implements OnInit, OnDestroy {
}
}
onOpenProfile(): void {
if (
!!this.currentRoomInfo &&
this.currentRoomInfo.roomType === RoomType.Single
) {
const returnValue = this.appChatService.getRoomUserList01(
this.user,
this.roomUsersMap,
this.roomUsersShortMap
);
if (
!!returnValue &&
!!returnValue.existUsers &&
returnValue.users.length > 0
) {
const userSeq = String(returnValue.users[0].seq);
this.dialog.open<
ProfileDialogComponent,
ProfileDialogData,
ProfileDialogResult
>(ProfileDialogComponent, {
panelClass: 'mid-create-dialog',
data: {
userSeq
}
});
}
}
}
getShowContextMenu(menuType: string) {
if (
['EVENT', 'ROOM_USERS', 'CHANGE_ROOM_USERS', 'ADD_GROUP', 'SETTING'].some(

View File

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

View File

@ -11,12 +11,15 @@ import {
EventEmitter,
Output
} from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { RoomInfo } from '@ucap/domain-chat';
import { LogService } from '@ucap/ng-logger';
import { RoomInfo } from '@ucap/protocol-room';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { QueryParams } from '@app/pages/chat/types/params.type';
import { SearchInfo } from '@app/pages/chat/models/search-info';
import { AppChatService } from '@app/services/app-chat.service';
@Component({
selector: 'app-sections-chat-list',
@ -51,6 +54,7 @@ export class ListSectionComponent implements OnInit, OnDestroy {
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private appChatService: AppChatService,
private changeDetectorRef: ChangeDetectorRef,
private logService: LogService
) {}
@ -76,17 +80,7 @@ export class ListSectionComponent implements OnInit, OnDestroy {
}
onOpenChatRoom(roomInfo: RoomInfo): void {
this.router.navigate(
[
'chat',
{
outlets: { content: 'chatroom' }
}
],
{
queryParams: { roomId: roomInfo.roomId }
}
);
this.appChatService.openRoombyRoomId(roomInfo.roomId);
}
onSearchResultList(searchResultList: RoomInfo[]) {

View File

@ -1,4 +1,4 @@
<div #chatMessagesContainer class="ucap-message-section">
<!-- <div class="ucap-message-section"> -->
<button
mat-stroked-button
color="accent"
@ -16,8 +16,21 @@
<mat-icon>arrow_upward</mat-icon>
</button>-->
<div #chatMessagesList class="chat-area">
<div class="ucap-chat-more-event" *ngIf="!!currentChatting?.remainEvent">
<ucap-virtual-scroll-viewport
#vsList
fxFlexFill
class="ucap-message-section"
perfectScrollbar
measureSize
minBufferPx="200"
maxBufferPx="300"
(contentSizeChanged)="onContentSizeChanged($event)"
(measured)="onMeasured()"
(psYReachEnd)="isScrollReachBottom = true; resetRecentMessage()"
(psScrollUp)="isScrollReachBottom = false"
>
<ng-template ucapVirtualScrollHeader>
<div class="ucap-chat-more-event" *ngIf="!!isRemainEvent">
<button
mat-button
class="btn-more-pre bg-primary-chat"
@ -28,31 +41,79 @@
>{{ 'event.showPreviousEvents' | ucapI18n }}
</button>
</div>
</ng-template>
<div class="chat-area">
<app-chat-message-box
*ngFor="let event of eventList"
*ucapVirtualFor="
let event of eventList;
keyOf: keyOf;
templateCacheSize: 0
"
[message]="event"
[roomInfo]="currentRoomInfo"
[isMe]="event.senderSeq + '' === String(user?.info?.seq) + ''"
[loginSession]="loginSession"
[loginInfo]="loginInfo"
[user]="user"
[userPermission]="userPermission"
[isMe]="String(user?.info?.seq) === String(event.senderSeq)"
[translationSimpleview]="translationSimpleview"
[senderInfo]="getSenderInfo(event.senderSeq)"
[defaultProfileImage]="defaultProfileImage"
[profileImageRoot]="versionInfo2Res?.profileRoot"
[profileImageRoot]="versionInfo?.profileRoot"
[dateChanged]="getDateSplitter(event)"
[unreadCount]="getUnreadCount(event)"
(messageContextMenu)="onClickMessageContextMenu($event)"
(fileViewer)="onFileViewer($event)"
(fileSave)="onFileSave($event)"
(massTranslationDetail)="onMassTranslationDetail($event)"
(openProfile)="onOpenProfile($event)"
(joinConference)="onJoinConference($event)"
>
</app-chat-message-box>
<!-- <app-chat-message-box
*ngFor="let event of eventList; trackBy: trackByEvent"
[message]="event"
></app-chat-message-box> -->
<!-- <app-chat-message-box
*ngFor="let event of eventList; trackBy: trackByEvent"
[message]="event"
[roomInfo]="currentRoomInfo"
[loginInfo]="loginInfo"
[user]="user"
[loginSession]="loginSession"
[isMe]="String(user?.info?.seq) === String(event.senderSeq)"
[translationSimpleview]="translationSimpleview"
[senderInfo]="getSenderInfo(event.senderSeq)"
[defaultProfileImage]="defaultProfileImage"
[profileImageRoot]="versionInfo?.profileRoot"
[dateChanged]="getDateSplitter(event)"
[unreadCount]="getUnreadCount(event)"
(messageContextMenu)="onClickMessageContextMenu($event)"
(fileViewer)="onFileViewer($event)"
(massTranslationDetail)="onMassTranslationDetail($event)"
(openProfile)="onOpenProfile($event)"
></app-chat-message-box>
<div *ngIf="false" class="recent-receive-message">
<!-- 최근 메시지 영역 -->
></app-chat-message-box> -->
<!-- <p *ngFor="let event of eventList; trackBy: trackByEvent">
{{ event.seq }} / {{ event.type }}
</p> -->
<!-- <div *ngIf="!!recentUserInfo" class="recent-receive-message">
<app-chat-recent-message
[senderInfo]="getSenderInfo(user?.info?.seq)"
[senderInfo]="recentUserInfo"
[message]="recentMessage"
[defaultProfileImage]="defaultProfileImage"
[profileImageRoot]="versionInfo2Res?.profileRoot"
[profileImageRoot]="versionInfo?.profileRoot"
(gotoBottom)="gotoScrollToBottom()"
></app-chat-recent-message>
</div> -->
</div>
</ucap-virtual-scroll-viewport>
<div *ngIf="!!recentUserInfo" class="recent-receive-message">
<app-chat-recent-message
[senderInfo]="recentUserInfo"
[message]="recentMessage"
[defaultProfileImage]="defaultProfileImage"
[profileImageRoot]="versionInfo?.profileRoot"
(gotoBottom)="gotoScrollToBottom()"
></app-chat-recent-message>
</div>
</div>
</div>
<!-- </div> -->

View File

@ -1,26 +1,17 @@
@import '~@ucap/lg-scss/mixins';
.ucap-message-section {
display: flex;
flex-direction: column;
height: 100%;
padding: 0 30px;
overflow: auto;
min-width: 450px;
@include screen(xs) {
padding: 0 16px;
min-width: 100%;
}
.icon-button-arrow {
@include ucapMatButton(36px, 36px, 6px, 36px);
border-color: $lipstick;
background-color: rgba(255, 255, 255, 0.5);
box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.16);
position: sticky;
// position: sticky;
position: absolute;
right: 20px;
z-index: 5;
width: 36px;
align-self: flex-end;
margin-right: -10px;
// align-self: flex-end;
// margin-right: -10px;
&.top-position {
bottom: 90%;
order: 2;
@ -34,17 +25,38 @@
}
}
.chat-area {
flex-grow: 1;
order: 1;
padding-top: 30px;
.ucap-message-section {
display: flex;
flex-direction: column;
height: 100%;
padding: 0 30px;
overflow: auto;
min-width: 450px;
@include screen(xs) {
padding: 0 16px;
min-width: 100%;
}
.ucap-chat-more-event {
padding: 20px 0 0;
padding: 20px 30px 0;
.btn-more-pre {
@include ucapMatButton(100%, 30px, 2px, 30px);
font-size: 0.857em;
@include screen(xs) {
padding: 0 16px;
}
}
}
.chat-area {
flex-grow: 1;
order: 1;
padding: 0 30px;
display: flex;
flex-direction: column;
@include screen(xs) {
padding: 0 16px;
}
}
}
.recent-receive-message {
@ -56,9 +68,8 @@
margin: 0 -30px;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
width: 100%;
@include screen(xs) {
margin: 0 -15px;
}
}
}
}

View File

@ -1,7 +1,23 @@
import moment from 'moment';
import { Subject, merge, combineLatest, Observable } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import {
Subject,
merge,
combineLatest,
Observable,
fromEvent,
interval
} from 'rxjs';
import {
takeUntil,
take,
tap,
withLatestFrom,
debounce,
debounceTime
} from 'rxjs/operators';
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
import {
Component,
@ -10,9 +26,9 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Input,
AfterViewInit,
ViewChild,
ElementRef
ElementRef,
Inject
} from '@angular/core';
import { Store, select } from '@ngrx/store';
@ -20,53 +36,72 @@ import { Dictionary } from '@ngrx/entity';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { TransMassTalkDownloadRequest } from '@ucap/api-common';
import { VersionInfo2Response } from '@ucap/api-public';
import { DeviceType, FileDownloadItem } from '@ucap/domain-common';
import { User } from '@ucap/domain-organization';
import {
Info,
EventJson,
EventType,
FileType,
MassTranslationEventJson
} from '@ucap/protocol-event';
import {
MassTranslationEventJson,
FileEventJson,
UserInfo as RoomUserInfo,
UserInfoShort as RoomUserInfoShort,
RoomInfo,
UpdateTimerSetRequest,
UpdateRequest,
RoomType
} from '@ucap/protocol-room';
import { LoginResponse } from '@ucap/protocol-authentication';
import { FileInfo } from '@ucap/protocol-file';
import { User } from '@ucap/protocol-info';
RoomType,
FileInfo
} from '@ucap/domain-chat';
import { TransMassTalkDownloadRequest } from '@ucap/api-common';
import {
ReadRequest,
SSVC_TYPE_EVENT_SEND_NOTI,
SendNotification
} from '@ucap/protocol-event';
import { UpdateTimerSetRequest, UpdateRequest } from '@ucap/protocol-room';
import { NativeService } from '@ucap/native';
import { I18nService } from '@ucap/ng-i18n';
import { UserSelector } from '@ucap/ng-store-organization';
import {
LoginSelector,
ConfigurationSelector
ConfigurationSelector,
AuthorizationSelector
} from '@ucap/ng-store-authentication';
import {
ChattingSelector,
RoomSelector,
ChattingActions,
RoomActions,
Chatting
ChatUtil
} from '@ucap/ng-store-chat';
import {
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult,
ClipboardService,
SelectFileInfo
} from '@ucap/ng-ui';
AlertDialogComponent,
AlertDialogData,
AlertDialogResult,
ClipboardService
} from '@ucap/ng-ui/core';
import { SelectFileInfo } from '@ucap/ng-ui/viewer';
import {
VirtualScrollViewportComponent,
MeasureSizeVirtualScrollDirective
} from '@ucap/ng-ui/scrolling';
import { EventProtocolService } from '@ucap/ng-protocol-event';
import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native';
import { LogService } from '@ucap/ng-logger';
import { LoginSession } from '@app/models/login-session';
import { AppChatService } from '@app/services/app-chat.service';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { AppOrganizationService } from '@app/services/app-organization.service';
import { AppFileService } from '@app/services/app-file.service';
import {
ProfileDialogComponent,
ProfileDialogData,
@ -78,16 +113,15 @@ import {
ForwardDialogData,
ForwardDialogResult
} from '../dialogs/forward.dialog.component';
import {
FileViewerDialogComponent,
FileViewerDialogData,
FileViewerDialogResult
} from '../dialogs/file-viewer.dialog.component';
import { FileViewerDialogData } from '../dialogs/file-viewer.dialog.component';
import {
SettingDialogComponent,
SettingDialogData,
SettingDialogResult
} from '../dialogs/setting.dialog.component';
import { ConferenceJoinRequest } from '@ucap/api-prompt';
import { LoginInfo, VersionInfo } from '@ucap/domain-authentication';
import { UserPermission } from '@ucap/domain-authorization';
@Component({
selector: 'app-sections-chat-message',
@ -96,19 +130,20 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MessageSectionComponent
implements OnInit, OnDestroy, AfterViewInit {
export class MessageSectionComponent implements OnInit, OnDestroy {
private roomIdSubject = new Subject<string>();
private ngOnDestroySubject: Subject<void> = new Subject();
@Input()
set roomId(roomId: string) {
if (this._roomId !== roomId) {
this._roomId = roomId;
this.roomIdSubject.next(roomId);
this.initializeRoomData();
}
}
get roomId(): string {
return this._roomId;
}
@ -121,13 +156,14 @@ export class MessageSectionComponent
@Input()
eventSendTrigger$: Observable<any>;
versionInfo2Res: VersionInfo2Response;
loginRes: LoginResponse;
versionInfo: VersionInfo;
loginInfo: LoginInfo;
user: User;
loginSession: LoginSession;
userPermission: UserPermission;
currentRoomInfo: RoomInfo;
currentChatting: Chatting;
isRemainEvent = false;
currentFileInfoList: FileInfo[] = [];
roomUsers: RoomUserInfoShort[] = [];
// eventList$: Observable<Info<EventJson>[]>;
@ -137,6 +173,10 @@ export class MessageSectionComponent
EventType = EventType;
FileType = FileType;
/** 최근 메시지 */
recentMessage: Info<EventJson>;
recentUserInfo: RoomUserInfoShort | RoomUserInfo | undefined;
/** Timer 대화방의 대화 삭제를 위한 interval */
interval: any;
@ -145,9 +185,25 @@ export class MessageSectionComponent
/** About Scroll */
isInitScrollbottom = true;
@ViewChild('chatMessagesContainer', { static: false })
isScrollReachBottom = false;
@ViewChild('chatMessagesContainer', { static: true })
chatMessagesContainer: ElementRef<HTMLElement>;
@ViewChild('vsList', { static: false })
vsList: VirtualScrollViewportComponent;
@ViewChild('vsList', {
static: true,
read: MeasureSizeVirtualScrollDirective
})
measureSize: MeasureSizeVirtualScrollDirective;
@ViewChild(PerfectScrollbarDirective, {
static: true,
read: PerfectScrollbarDirective
})
psDirectiveRef: PerfectScrollbarDirective;
String = String;
constructor(
@ -156,32 +212,109 @@ export class MessageSectionComponent
private changeDetectorRef: ChangeDetectorRef,
private dialog: MatDialog,
private i18nService: I18nService,
private appAuthenticationService: AppAuthenticationService
private eventProtocolService: EventProtocolService,
private appAuthenticationService: AppAuthenticationService,
private appOrganizationService: AppOrganizationService,
private appFileService: AppFileService,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
private logService: LogService
) {
this.defaultProfileImage = this.appChatService.defaultProfileImage;
// scroll handling when window size changing.
fromEvent(window, 'resize')
.pipe(takeUntil(this.ngOnDestroySubject), debounceTime(300))
.subscribe((event: any) => {
this.refreshAndScrollToBottom();
});
this.eventProtocolService.notification$
.pipe(
takeUntil(this.ngOnDestroySubject),
withLatestFrom(this.store.pipe(select(RoomSelector.rooms))),
tap(([notiOrRes, roomList]) => {
switch (notiOrRes.SSVC_TYPE) {
case SSVC_TYPE_EVENT_SEND_NOTI:
{
const noti = notiOrRes as SendNotification;
const roomId = noti.roomId;
const eventInfo = noti.info;
const trgtRoom = roomList.find(
(roomInfo) => roomInfo.roomId === roomId
);
const contents = ChatUtil.convertFinalEventMessage(
noti.eventType,
noti.info.sentMessageJson
);
this.logService.debug(
'Notification::eventProtocolService::SendNotification in message.section.components',
noti
);
if (!!roomId && roomId === this.roomId) {
if (!!this.isScrollReachBottom) {
// Fires when I enter the event
this.isInitScrollbottom = true;
} else {
this.recentMessage = noti.info;
this.recentUserInfo = this.getSenderInfo(
Number(noti.SENDER_SEQ)
);
}
}
}
break;
default:
// ignore..
break;
}
})
)
.subscribe();
}
ngOnInit(): void {
this.vsList
.elementScrolled()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe(() => {
if (!!this.psDirectiveRef) {
this.psDirectiveRef.update();
}
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
select(AuthorizationSelector.userPermission)
)
.subscribe((versionInfo2Res) => (this.versionInfo2Res = versionInfo2Res));
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => (this.loginRes = loginRes));
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user))
.subscribe((user) => (this.user = user));
.subscribe((userPermission) => {
this.userPermission = userPermission;
});
this.eventSendTrigger$
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((_) => {
// Fires when I enter the event
this.gotoScrollToBottom();
this.isInitScrollbottom = true;
});
this.nativeService
.window_onFocus$()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((isFocus) => {
if (!!this.roomId && !!this.eventList && this.eventList.length > 0) {
const roomId = this.roomId;
const lastReadSeq = this.eventList[this.eventList.length - 1].seq;
this.store.dispatch(
ChattingActions.read({
roomId,
lastReadSeq
} as ReadRequest)
);
}
});
}
@ -202,35 +335,21 @@ export class MessageSectionComponent
}
}
ngAfterViewInit() {}
initializeRoomData() {
/** About initialize roomId */
this.isInitScrollbottom = true;
this.onClickInvalidateCache();
this.loginSession = this.appAuthenticationService.getLoginSession();
this.store
.pipe(
takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)),
select(RoomSelector.room, this.roomId)
)
.subscribe((room) => {
this.currentRoomInfo = room;
// About Interval
if (!!this.interval) {
clearInterval(this.interval);
this.interval = undefined;
}
if (!!this.currentRoomInfo && !!this.currentRoomInfo.isTimeRoom) {
this.interval = setInterval(() => {
this.store.dispatch(
ChattingActions.intervalClearEvent({ roomId: this.roomId })
);
}, 1000);
}
});
.pipe(take(1), select(ConfigurationSelector.versionInfo))
.subscribe((versionInfo) => (this.versionInfo = versionInfo));
this.store
.pipe(take(1), select(LoginSelector.loginInfo))
.subscribe((loginInfo) => (this.loginInfo = loginInfo));
this.store
.pipe(take(1), select(UserSelector.user))
.subscribe((user) => (this.user = user));
this.store
.pipe(
@ -250,7 +369,12 @@ export class MessageSectionComponent
select(ChattingSelector.chatting, this.roomId)
)
.subscribe((chatting) => {
this.currentChatting = chatting;
if (!!chatting && !!chatting.remainEvent) {
this.isRemainEvent = chatting.remainEvent;
} else {
this.isRemainEvent = false;
}
if (
!!chatting &&
!!chatting.fileInfoList &&
@ -285,12 +409,6 @@ export class MessageSectionComponent
.subscribe((eventList) => {
if (!!eventList && eventList.length > 0) {
this.eventList = eventList;
this.changeDetectorRef.markForCheck();
if (!!this.isInitScrollbottom) {
this.gotoScrollToBottom();
this.isInitScrollbottom = false;
}
}
});
@ -301,11 +419,46 @@ export class MessageSectionComponent
])
.pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)))
.subscribe(([roomInfo, standbyRooms]) => {
// get room info.
if (!!roomInfo) {
if (
!!this.currentRoomInfo &&
this.currentRoomInfo.roomId === roomInfo.roomId
) {
this.currentRoomInfo = {
...this.currentRoomInfo,
roomName: roomInfo.roomName,
joinUserCount: roomInfo.joinUserCount,
isJoinRoom: roomInfo.isJoinRoom,
expiredFileStdSeq: roomInfo.expiredFileStdSeq,
timeRoomInterval: roomInfo.timeRoomInterval
};
} else {
// all refresh
this.currentRoomInfo = roomInfo;
}
// About Interval
if (!!this.interval) {
clearInterval(this.interval);
this.interval = undefined;
}
if (!!this.currentRoomInfo && !!this.currentRoomInfo.isTimeRoom) {
this.interval = setInterval(() => {
this.store.dispatch(
ChattingActions.intervalClearEvent({ roomId: this.roomId })
);
}, 1000);
}
}
// new room setting popup
if (
!!roomInfo &&
roomInfo.roomId === this.roomId &&
roomInfo.roomType !== RoomType.Mytalk &&
roomInfo.roomType !== RoomType.Single &&
(roomInfo.roomType !== RoomType.Single ||
(roomInfo.roomType === RoomType.Single && !!roomInfo.isTimeRoom)) &&
!!standbyRooms &&
standbyRooms.length > 0 &&
standbyRooms.findIndex((roomId) => roomId === this.roomId) > -1
@ -337,13 +490,57 @@ export class MessageSectionComponent
});
}
/** About scrolling */
keyOf = (item: Info<EventJson>): string | number => {
return item.seq;
};
onMeasured() {
if (!!this.isInitScrollbottom) {
this.gotoScrollToBottom();
this.isInitScrollbottom = false;
}
}
onContentSizeChanged(size: number) {
if (!!this.psDirectiveRef) {
this.psDirectiveRef.update();
}
}
onClickScrollTo(i: string) {
const index = Number(i);
// const offset = this.vsList.offsetForIndex(index, 'start');
// this.psDirectiveRef.scrollToY(offset);
// console.log('offset', offset, this.psDirectiveRef.ps().element.scrollTop);
this.vsList.scrollToIndex(index, 'center');
}
onClickInvalidateCache() {
if (!!this.measureSize) {
this.measureSize.invalidateMeasurements();
}
}
gotoScrollToBottom() {
if (!!this.chatMessagesContainer) {
const self = this;
setTimeout(() => {
self.chatMessagesContainer.nativeElement.scrollTop =
self.chatMessagesContainer.nativeElement.scrollHeight;
}, 500);
// recent message reset;
this.resetRecentMessage();
// scrolling.
if (!!this.vsList && !!this.eventList && this.eventList.length > 0) {
this.vsList.scrollToIndex(this.eventList.length - 1, 'end');
}
}
refreshAndScrollToBottom() {
// if (!!this.vsList && !!this.isScrollReachBottom) {
// this.isInitScrollbottom = false;
// this.vsList.checkViewportSize();
// }
if (!!this.psDirectiveRef && !!this.isScrollReachBottom) {
this.psDirectiveRef.update();
this.psDirectiveRef.scrollToBottom();
}
}
@ -353,7 +550,7 @@ export class MessageSectionComponent
): RoomUserInfoShort | RoomUserInfo | undefined {
if (!!this.roomUsers && this.roomUsers.length) {
return this.roomUsers.find(
(userInfo) => userInfo.seq + '' === senderSeq + ''
(userInfo) => String(senderSeq) === String(userInfo.seq)
);
}
@ -366,8 +563,7 @@ export class MessageSectionComponent
if (curIndex === 0) {
return true;
}
if (curIndex > 0) {
} else if (curIndex > 0) {
if (!this.eventList[curIndex]) {
return false;
}
@ -376,16 +572,18 @@ export class MessageSectionComponent
'day'
);
}
return false;
}
/** Unread Count calculation */
getUnreadCount(message: Info<EventJson>): string | number {
const unreadCnt = this.roomUsers
.filter(
(user) => user.isJoinRoom && user.seq + '' !== message.senderSeq + ''
)
.filter((user) => user.lastReadEventSeq < message.seq).length;
const unreadCnt = this.roomUsers.filter(
(user) =>
user.isJoinRoom &&
String(message.senderSeq) !== String(user.seq) &&
user.lastReadEventSeq < message.seq
).length;
return unreadCnt === 0 ? '' : unreadCnt;
}
@ -393,7 +591,7 @@ export class MessageSectionComponent
event.preventDefault();
event.stopPropagation();
if (!!this.currentChatting.remainEvent) {
if (!!this.isRemainEvent) {
this.store.dispatch(
ChattingActions.moreEvents({
roomId: this.roomId
@ -422,7 +620,7 @@ export class MessageSectionComponent
this.appChatService.massTextDownload({
userSeq: String(this.user.info.seq),
deviceType: this.loginSession.deviceType,
token: this.loginRes.tokenString,
token: this.loginInfo.tokenString,
eventMassSeq: params.message.seq
});
}
@ -525,7 +723,7 @@ export class MessageSectionComponent
break;
case 'FORWARD_TO_ME':
{
if (this.loginRes.talkWithMeBotSeq > -1) {
if (this.loginInfo.talkWithMeBotSeq > -1) {
const seqs = this.user.talkWithMeBotSeq as any;
this.store.dispatch(
ChattingActions.forward({
@ -580,7 +778,7 @@ export class MessageSectionComponent
}) {
const req = {
userSeq: String(this.user.info.seq),
token: this.loginRes.tokenString,
token: this.loginInfo.tokenString,
deviceType: this.loginSession.deviceType,
eventTransSeq: params.message.sentMessageJson.translationSeq.toString()
} as TransMassTalkDownloadRequest;
@ -592,16 +790,133 @@ export class MessageSectionComponent
);
}
onFileViewer(selectFileInfo: SelectFileInfo) {
onFileViewer(params: {
selectFileInfo: SelectFileInfo;
senderInfo: RoomUserInfoShort | RoomUserInfo;
}) {
const self = this;
this.appOrganizationService
.getUserInfo({ userSeq: String(params.senderInfo.seq), user: this.user })
.then((result) => {
let isValid = false;
if (
!!result &&
!!result.userInfo &&
!!self.userPermission &&
self.userPermission.fileTransferAllowedCompanyList.indexOf(
result.userInfo.companyCode
) > -1
) {
isValid = true;
}
if (!!isValid) {
const data: FileViewerDialogData = {
fileInfos: this.currentFileInfoList,
selectFileInfo,
downloadUrl: this.versionInfo2Res.downloadUrl,
deviceType: this.loginSession.deviceType,
token: this.loginRes.tokenString,
userSeq: String(this.user.info.seq)
fileInfos: self.currentFileInfoList,
selectFileInfo: params.selectFileInfo,
downloadUrl: self.versionInfo.downloadUrl,
deviceType: self.loginSession.deviceType,
token: self.loginInfo.tokenString,
userSeq: String(self.user.info.seq)
};
this.appChatService.openFileviwer(data);
self.appChatService.openFileviwer(data);
} else {
self._openAlert(
self.i18nService.t('common:file.errors.disapprovalCompany')
);
}
})
.catch((reason) => {
self._openAlert(
self.i18nService.t('common:file.errors.disapprovalCompany')
);
});
}
onFileSave(params: {
fileInfo: FileEventJson;
fileDownloadItem: FileDownloadItem;
type: string;
senderInfo: RoomUserInfoShort | RoomUserInfo;
}): void {
const self = this;
this.appOrganizationService
.getUserInfo({ userSeq: String(params.senderInfo.seq), user: this.user })
.then((rst) => {
let isValid = false;
if (
!!rst &&
!!rst.userInfo &&
!!self.userPermission &&
self.userPermission.fileTransferAllowedCompanyList.indexOf(
rst.userInfo.companyCode
) > -1
) {
isValid = true;
}
if (!!isValid) {
if (
params.type === 'saveAs' &&
self.loginSession.deviceType === DeviceType.PC
) {
self.nativeService
.file_selectForSave({ defaultPath: params.fileInfo.fileName })
.then((result) => {
if (!!result) {
if (!!result.canceled) {
// 취소함.
} else {
self.appFileService.saveFile(
{
fileInfo: params.fileInfo,
fileDownloadItem: params.fileDownloadItem,
type: params.type,
fileName: params.fileInfo.fileName,
fileDownloadUrl: undefined,
savePath: result.filePath
},
self.loginInfo,
self.user,
self.loginSession
);
}
}
})
.catch((reason) => {
// self.snackBarService.open(
// self.translateService.instant(
// 'common:file.errors.failToSpecifyPath'
// ),
// self.translateService.instant('common:file.errors.label')
// );
});
} else {
self.appFileService.saveFile(
{
fileInfo: params.fileInfo,
fileDownloadItem: params.fileDownloadItem,
type: params.type,
fileName: params.fileInfo.fileName,
fileDownloadUrl: undefined,
savePath: undefined
},
self.loginInfo,
self.user,
self.loginSession
);
}
} else {
self._openAlert(
self.i18nService.t('common:file.errors.disapprovalCompany')
);
}
})
.catch((reason) => {
self._openAlert(
self.i18nService.t('common:file.errors.disapprovalCompany')
);
});
}
private _standByRoomSetting(result: SettingDialogResult) {
@ -640,4 +955,33 @@ export class MessageSectionComponent
}
});
}
onJoinConference(conferenceSeq: number): void {
const loginSession = this.appAuthenticationService.getLoginSession();
const req: ConferenceJoinRequest = {
userSeq: String(this.user.info.seq),
deviceType: loginSession.deviceType,
tokenKey: this.loginInfo.tokenString,
conferenceSeq
};
this.appChatService.joinVideoConference(req);
}
private _openAlert(msg: string) {
this.dialog.open<AlertDialogComponent, AlertDialogData, AlertDialogResult>(
AlertDialogComponent,
{
panelClass: 'min-create-dialog',
data: {
title: this.i18nService.t('common:file.errors.title'),
message: msg
}
}
);
}
resetRecentMessage() {
this.recentMessage = undefined;
this.recentUserInfo = undefined;
}
}

View File

@ -12,9 +12,17 @@
#searchWordInput
type="text"
formControlName="searchInput"
[matAutocomplete]="auto"
(keyup.backspace)="onKeyupBackspace($event)"
(keydown.enter)="onKeyDownEnter($event, searchWordInput.value)"
/>
<!-- <input
matInput
#searchWordInput
type="text"
formControlName="searchInput"
[matAutocomplete]="auto"
(keydown.enter)="onKeyDownEnter($event, searchWordInput.value)"
/> -->
<mat-autocomplete #auto="matAutocomplete">
<mat-option
*ngFor="let filteredRecommendedWord of filteredRecommendedWordList"

View File

@ -9,7 +9,8 @@ import {
EventEmitter,
ChangeDetectorRef,
ChangeDetectionStrategy,
ViewChild
ViewChild,
ElementRef
} from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
@ -17,7 +18,7 @@ import { Store, select } from '@ngrx/store';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { User } from '@ucap/protocol-info';
import { User } from '@ucap/domain-organization';
import { LogService } from '@ucap/ng-logger';
@ -45,6 +46,8 @@ export class SearchSectionComponent implements OnInit, OnDestroy {
recommendedWordList: string[];
filteredRecommendedWordList: string[];
@ViewChild('searchWordInput', { static: true })
searchWordInput: ElementRef<HTMLInputElement>;
@ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger;
private ngOnDestroySubject: Subject<void> = new Subject();
@ -148,11 +151,20 @@ export class SearchSectionComponent implements OnInit, OnDestroy {
}
}
onKeyupBackspace(event: Event) {
if (this.searchWordInput.nativeElement.value === '') {
this.searchCancel.emit();
}
event.stopPropagation();
}
onKeyDownEnter(event: KeyboardEvent, searchWord: string) {
event.preventDefault();
event.stopPropagation();
if (!!this.autocomplete) {
this.autocomplete.closePanel();
}
this.keyDownEnter.emit({ searchWord });
}

View File

@ -6,19 +6,19 @@
>
<div appLayoutsDefaultDialog="header">
<span *ngIf="stepper.selectedIndex === 0">
{{ 'dialog.title.newChatRoom' | ucapI18n }}
{{ 'chat:dialog.title.newChatRoom' | ucapI18n }}
</span>
<span *ngIf="stepper.selectedIndex !== 0">
{{ (!!isTimer ? 'dialog.timerRoom' : 'dialog.normalRoom') | ucapI18n }}
{{ (!!isTimer ? 'chat:dialog.timerRoom' : 'chat:dialog.normalRoom') | ucapI18n }}
</span>
<div appLayoutsDefaultDialog="sub-header" class="sub-header-tit">
<span appLayoutsDefaultDialog="sub-header" class="sub-header-tit">
<span *ngIf="stepper.selectedIndex === 0">
{{ 'dialog.title.subSelectRoomType' | ucapI18n }}
{{ 'chat:dialog.title.subSelectRoomType' | ucapI18n }}
</span>
<span *ngIf="stepper.selectedIndex !== 0">
{{ 'dialog.title.subSelectUser' | ucapI18n }}
{{ 'chat:dialog.title.subSelectUser' | ucapI18n }}
</span>
</span>
</div>
</div>
<div class="dialog-body" appLayoutsDefaultDialog="body">
@ -31,12 +31,12 @@
<mat-step label="Select room type" fxFlexFill>
<div class="ucap-dialog-select-room-type">
<div class="normal-room room-type">
<span class="title">{{ 'dialog.normalRoom' | ucapI18n }}</span>
<span class="title">{{ 'chat:dialog.normalRoom' | ucapI18n }}</span>
<div class="img"></div>
<div
class="description"
[innerHTML]="
'dialog.normalRoomDescription'
'chat:dialog.normalRoomDescription'
| ucapI18n: { maxCount: maxChatRoomUser }
"
></div>
@ -69,12 +69,12 @@
</div>
<div class="room-type timer-room">
<span class="title">{{ 'dialog.timerRoom' | ucapI18n }}</span>
<span class="title">{{ 'chat:dialog.timerRoom' | ucapI18n }}</span>
<div class="img"></div>
<div
class="description"
[innerHTML]="
'dialog.timerRoomDescription'
'chat:dialog.timerRoomDescription'
| ucapI18n: { maxCount: maxChatRoomUser }
"
></div>
@ -113,8 +113,9 @@
[isDialog]="true"
[checkable]="true"
[selectedUserList]="selectedUserList"
(toggleCheckUser)="onChangeUserList($event)"
(toggleCheckGroup)="onChangeGroupList($event)"
[filteredTimerChatAuth]="isTimer"
(toggleCheckUser)="onToggleCheckUser($event)"
(toggleCheckGroup)="onToggleCheckGroup($event)"
></app-group-select-user>
</div>
<div class="ucap-dialog-organization-profile-selection">
@ -130,7 +131,7 @@
class="selected-head-area"
>
<p>
{{ 'dialog.selectedUserList' | ucapI18n }}
{{ 'chat:dialog.selectedUserList' | ucapI18n }}
</p>
<span
>(<em class="number">{{ selectedUserList?.length }}</em
@ -144,7 +145,7 @@
</div>
<div appLayoutsDefaultDialog="action" class="btn-box">
<button mat-button mat-stroked-button (click)="onCancel(stepper)">
{{ 'dialog.button.cancel' | ucapI18n }}
{{ 'chat:dialog.button.cancel' | ucapI18n }}
</button>
<button
mat-flat-button
@ -153,7 +154,7 @@
[disabled]="this.isTimer === undefined"
(click)="onConfirm(stepper)"
>
{{ 'dialog.button.selectRoomUser' | ucapI18n }}
{{ 'chat:dialog.button.selectRoomUser' | ucapI18n }}
</button>
<button
mat-flat-button
@ -162,7 +163,7 @@
[disabled]="selectedUserList.length === 0"
(click)="onOpenRoom(stepper)"
>
{{ 'dialog.button.openRoom' | ucapI18n }}
{{ 'chat:dialog.button.openRoom' | ucapI18n }}
</button>
</div>
</app-layouts-default-dialog>

View File

@ -80,7 +80,7 @@
flex-direction: column;
align-items: center;
width: 48%;
height: 210px;
height: 200px;
border: 1px solid #999999;
background-color: #f7f8fa;
text-align: center;
@ -98,7 +98,7 @@
width: 60px;
height: 60px;
margin-bottom: 10px;
background-image: url(../../../../assets/images/ico/icon_normal_chat_g60.svg);
background-image: url(/assets/images/ico/icon_normal_chat_g60.svg);
}
}
&.timer-room {
@ -106,7 +106,7 @@
width: 60px;
height: 60px;
margin-bottom: 10px;
background-image: url(../../../../assets/images/ico/icon_timer_chat_g60.svg);
background-image: url(/assets/images/ico/icon_timer_chat_g60.svg);
}
}
.selecter {

View File

@ -6,9 +6,7 @@ import {
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject,
Input,
ViewChild
Inject
} from '@angular/core';
import {
@ -17,32 +15,24 @@ import {
MatDialog
} from '@angular/material/dialog';
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
import {
UserInfoSS,
UserInfoF,
UserInfoDN,
AuthResponse
} from '@ucap/protocol-query';
import { UserInfo as RoomUserInfo } from '@ucap/protocol-room';
import { UserInfo, UserInfoSS } from '@ucap/domain-organization';
import { GroupInfoDetail } from '@ucap/domain-group';
import { MatStepper } from '@angular/material/stepper';
import { I18nService } from '@ucap/ng-i18n';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
} from '@ucap/ng-ui';
} from '@ucap/ng-ui/core';
import { environment } from '@environments';
import { Store, select } from '@ngrx/store';
import { takeUntil } from 'rxjs/operators';
import { AuthorizationSelector } from '@ucap/ng-store-authentication';
export type UserInfoTypes =
| UserInfo
| UserInfoSS
| UserInfoF
| UserInfoDN
| RoomUserInfo;
import { AppGroupService } from '@app/services/app-group.service';
import { UserInfoTypes } from '@app/types';
import { AppChatService } from '@app/services/app-chat.service';
import { UserPermission } from '@ucap/domain-authorization';
export interface CreateDialogData {}
export interface CreateDialogResult {
@ -62,7 +52,7 @@ export class CreateDialogComponent implements OnInit, OnDestroy {
isTimer: boolean | undefined;
selectedUserList: UserInfoTypes[] = [];
authRes: AuthResponse;
userPermission: UserPermission;
private ngOnDestroySubject: Subject<void> = new Subject();
@ -72,7 +62,9 @@ export class CreateDialogComponent implements OnInit, OnDestroy {
private i18nService: I18nService,
public dialog: MatDialog,
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef
private changeDetectorRef: ChangeDetectorRef,
private appGroupService: AppGroupService,
private appChatService: AppChatService
) {
this.maxChatRoomUser = environment.productConfig.chat.maxChatRoomUser;
}
@ -81,11 +73,11 @@ export class CreateDialogComponent implements OnInit, OnDestroy {
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(AuthorizationSelector.authResponse)
select(AuthorizationSelector.userPermission)
)
.subscribe((authRes) => {
this.authRes = authRes;
if (!!this.authRes && !this.authRes.canTimerChat) {
.subscribe((userPermission) => {
this.userPermission = userPermission;
if (!!this.userPermission && !this.userPermission.canTimerChat) {
this.isTimer = false;
this.currentStep = 1;
}
@ -103,7 +95,7 @@ export class CreateDialogComponent implements OnInit, OnDestroy {
this.dialogRef.close();
}
onCancel(stepper: MatStepper) {
if (!!this.authRes && !this.authRes.canTimerChat) {
if (!!this.userPermission && !this.userPermission.canTimerChat) {
// close.
} else if (stepper.selectedIndex > 0) {
stepper.previous();
@ -135,93 +127,51 @@ export class CreateDialogComponent implements OnInit, OnDestroy {
this.selectedUserList.map((user) => userSeqs.push(user.seq.toString()));
if (this.selectedUserList.length >= this.maxChatRoomUser) {
this.dialog.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
panelClass: 'min-create-dialog',
data: {
title: this.i18nService.t('chat:errors.label'),
html: this.i18nService.t('chat:errors.maxCountOfRoomMemberWith', {
maxCount: this.maxChatRoomUser
})
}
});
return;
}
// validation and openRoom
this.appChatService.newOpenRoom(userSeqs, this.isTimer);
// Open Room.
if (this.selectedUserList.length >= this.maxChatRoomUser) {
// not close popup..
return;
} else {
this.dialogRef.close({ userSeqs, isTimer: this.isTimer });
}
}
onChangeUserList(datas: { checked: boolean; userInfo: UserInfoSS }[]) {
if (!datas || 0 === datas.length) {
onToggleCheckUser(datas: { checked: boolean; userInfo: UserInfoSS }[]) {
const resList = this.appGroupService.getToggleCheckUser(
datas,
this.selectedUserList
);
if (!resList) {
return;
}
this.selectedUserList = resList;
}
onToggleCheckGroup(params: {
isChecked: boolean;
groupBuddyList: { group: GroupInfoDetail; buddyList: UserInfo[] };
}) {
const resList = this.appGroupService.getToggleCheckGroup(
params,
this.selectedUserList
);
if (!resList) {
return;
}
const pushs: UserInfoSS[] = [];
const pops: UserInfoSS[] = [];
datas.forEach((d) => {
const i = this.selectedUserList.findIndex(
(u) => String(u.seq) === String(d.userInfo.seq)
);
if (d.checked) {
if (-1 === i) {
pushs.push(d.userInfo);
}
} else {
if (-1 < i) {
pops.push(d.userInfo);
}
}
});
if (0 < pushs.length) {
this.selectedUserList = [...this.selectedUserList, ...pushs];
}
if (0 < pops.length) {
this.selectedUserList = this.selectedUserList.filter(
(u) => -1 === pops.findIndex((p) => String(p.seq) === String(u.seq))
);
}
}
onChangeGroupList(params: {
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}) {
if (params.isChecked) {
params.groupBuddyList.buddyList.forEach((item) => {
if (
this.selectedUserList.filter(
(user) => String(user.seq) === String(item.seq)
).length === 0
) {
this.selectedUserList = [...this.selectedUserList, item];
}
});
} else {
this.selectedUserList = this.selectedUserList.filter(
(item) =>
params.groupBuddyList.buddyList.filter(
(del) => String(del.seq) === String(item.seq)
).length === 0
);
}
this.selectedUserList = resList;
}
onRemovedProfileSelection(userInfo: UserInfo) {
const i = this.selectedUserList.findIndex(
(u) => String(u.seq) === String(userInfo.seq)
const resUserList = this.appGroupService.removedProfileSelection(
userInfo,
this.selectedUserList
);
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => String(u.seq) !== String(userInfo.seq)
);
if (!resUserList) {
}
this.selectedUserList = resUserList;
}
removableForSelection = (userInfo: UserInfo) => {
return true;

View File

@ -7,23 +7,25 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { select, Store } from '@ngrx/store';
import { DeviceType, LoginSession } from '@ucap/core';
import { FileDownloadItem } from '@ucap/api';
import { LoginResponse } from '@ucap/protocol-authentication';
import { FileEventJson } from '@ucap/protocol-event';
import { FileInfo, isMedia } from '@ucap/protocol-file';
import { User } from '@ucap/protocol-info';
import { DeviceType, FileDownloadItem } from '@ucap/domain-common';
import { User } from '@ucap/domain-organization';
import { LoginSession, LoginInfo } from '@ucap/domain-authentication';
import { FileEventJson, FileInfo, isMedia } from '@ucap/domain-chat';
import { LogService } from '@ucap/ng-logger';
import { CommonApiService } from '@ucap/ng-api-common';
import { UserSelector } from '@ucap/ng-store-organization';
import { LoginSelector } from '@ucap/ng-store-authentication';
import {
LoginSelector,
AuthorizationSelector
} from '@ucap/ng-store-authentication';
import { SelectFileInfo } from '@ucap/ng-ui';
import { SelectFileInfo } from '@ucap/ng-ui/viewer';
import { AppFileService } from '@app/services/app-file.service';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { UserPermission } from '@ucap/domain-authorization';
export interface FileViewerDialogData {
fileInfos: FileInfo[];
@ -51,9 +53,10 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
};
currentFileInfo: FileInfo;
loginRes: LoginResponse;
loginInfo: LoginInfo;
user: User;
loginSession: LoginSession;
userPermission: UserPermission;
constructor(
private store: Store<any>,
@ -96,14 +99,54 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
ngOnInit() {
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => (this.loginRes = loginRes));
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginInfo))
.subscribe((loginInfo) => (this.loginInfo = loginInfo));
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user))
.subscribe((user) => (this.user = user));
this.loginSession = this.appAuthenticationService.getLoginSession();
// auth.fileTransferAllowedCompanyList check.
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(AuthorizationSelector.userPermission)
)
.subscribe((userPermission) => {
if (!!userPermission) {
this.userPermission = userPermission;
}
if (
!!userPermission &&
!!this.data.fileInfos &&
this.data.fileInfos.length > 0
) {
this.fileInfo = {
fileInfos: this.data.fileInfos
.filter((item) => {
// filtered userPermission.fileTransferAllowedCompanyList
if (
!!userPermission.fileTransferAllowedCompanyList &&
userPermission.fileTransferAllowedCompanyList.length > 0
) {
return (
userPermission.fileTransferAllowedCompanyList.indexOf(
item.sentMessageJson.companyCode
) > -1
);
}
return true;
})
.sort((a, b) =>
a.eventSeq < b.eventSeq ? 1 : a.eventSeq > b.eventSeq ? -1 : 0
),
selectFileInfo: this.data.selectFileInfo
};
}
});
}
ngOnDestroy(): void {
@ -147,7 +190,7 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
fileDownloadUrl: undefined,
savePath: undefined
},
this.loginRes,
this.loginInfo,
this.user,
this.loginSession
);

View File

@ -10,9 +10,14 @@
<div class="dialog-body" appLayoutsDefaultDialog="body" fxFlexFill>
<div class="ucap-dialog-app-group-select-user">
<!-- search start-->
<div class="ucap-dialog-search">
<div
*ngIf="currentTabIndex === 0"
class="ucap-dialog-search"
[ngClass]="currentTabIndex === 0 ? 'ucap-dialog-search-disable' : ''"
>
<app-organization-search-for-tenant
placeholder="이름, 부서명, 전화번호, 이메일"
[isBackspaceCanceled]="isBackspaceCanceled"
[(searchData)]="companySearchData"
(canceled)="onCanceled()"
class="select-user-section-search"
@ -24,7 +29,7 @@
<mat-tab-group
mat-stretch-tabs
class="tap-container tab_num2"
(selectedIndexChange)="onSelectedIndexChange($event)"
[(selectedIndex)]="currentTabIndex"
>
<!--[S]그룹-->
<mat-tab>
@ -57,6 +62,8 @@
<div fxFlexFill class="ucap-dialog-chat-tap">
<perfect-scrollbar style="width: 100%; height: 100%;">
<app-chat-room-list
fxFlexFill
[selectedRoom]="selectedRoom"
[checkable]="true"
(toggleRoom)="onToggleRoom($event)"
></app-chat-room-list>
@ -81,12 +88,13 @@
<div class="search-result-list">
<perfect-scrollbar style="width: 100%; height: 100%;">
<app-group-profile-list
#appProfileList
[searchData]="companySearchData"
[selectedUser]="selectedUserList"
[checkable]="true"
[isDialog]="true"
(searched)="onSearched($event)"
(toggleCheck)="onToggleCheckUser($event)"
(toggleCheckUser)="onToggleCheckUser($event)"
class="ucap-dialog-search-result-container"
></app-group-profile-list>
</perfect-scrollbar>
@ -127,12 +135,7 @@
<button
mat-flat-button
class="bg-primary-darkest"
[disabled]="
!(
!!selectedRoom ||
(!!selectedUserList && selectedUserList.length > 0)
)
"
[disabled]="checkDisableBtn()"
(click)="onConfirm()"
>
{{ 'dialog.button.save' | ucapI18n }}

View File

@ -18,13 +18,16 @@
}
.forwrad-dialog-content {
height: calc(100% - 50px);
flex: 1 1 auto;
// height: calc(100% - 50px);
.tap-container {
height: 100%;
}
}
.ucap-dialog-app-group-select-user {
display: flex;
flex-direction: column;
flex: 1 0 auto;
//width: 60%;
height: 100%;
@ -35,6 +38,9 @@
height: 78%;
margin-bottom: 2%;
}
.ucap-dialog-search {
flex: 0 0 50px;
}
}
.ucap-dialog-organization-profile-selection {
position: relative;

View File

@ -19,10 +19,13 @@ import {
import { Store, select } from '@ngrx/store';
import { VersionInfo2Response } from '@ucap/api-public';
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query';
import { UserInfo as RoomUserInfo, RoomInfo } from '@ucap/protocol-room';
import {
UserInfo,
UserInfoSS,
UserInfoF,
UserInfoDN
} from '@ucap/domain-organization';
import { UserInfo as RoomUserInfo, RoomInfo } from '@ucap/domain-chat';
import { I18nService } from '@ucap/ng-i18n';
@ -33,18 +36,16 @@ import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
} from '@ucap/ng-ui';
} from '@ucap/ng-ui/core';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { Expansion02Component as AppExpansion02Component } from '@app/ucap/group/components/expansion-02.component';
import { ProfileListComponent as AppProfileListComponent } from '@app/ucap/group/components/profile-list.component';
import { environment } from '@environments';
export type UserInfoTypes =
| UserInfo
| UserInfoSS
| UserInfoF
| UserInfoDN
| RoomUserInfo;
import { AppGroupService } from '@app/services/app-group.service';
import { UserInfoTypes } from '@app/types';
import { VersionInfo } from '@ucap/domain-authentication';
import { GroupInfoDetail } from '@ucap/domain-group';
export interface ForwardDialogData {}
export interface ForwardDialogResult {
@ -63,6 +64,9 @@ export class ForwardDialogComponent implements OnInit, OnDestroy {
@ViewChild('appGroupExpansion', { static: false })
appGroupExpansion: AppExpansion02Component;
@ViewChild('appProfileList', { static: false })
appProfileList: AppProfileListComponent;
set companySearchData(searchData: SearchData) {
if (!!searchData && searchData.searchWord !== '') {
this._companySearchData = { ...searchData, bySearch: true };
@ -85,13 +89,23 @@ export class ForwardDialogComponent implements OnInit, OnDestroy {
selectedRoom: RoomInfo;
selectedUserList: UserInfoTypes[] = [];
versionInfo2Res: VersionInfo2Response;
groupList: GroupDetailData[];
versionInfo: VersionInfo;
groupList: GroupInfoDetail[];
isBackspaceCanceled = true;
isSearch = false;
searchedList: UserInfoSS[] = [];
currentTabIndex: number;
set currentTabIndex(idx: number) {
this._currentTabIndex = idx;
// this._resetSelectedObject();
}
get currentTabIndex() {
return this._currentTabIndex;
}
// tslint:disable-next-line: variable-name
_currentTabIndex: number;
constructor(
public dialogRef: MatDialogRef<ForwardDialogData, ForwardDialogResult>,
@ -99,7 +113,8 @@ export class ForwardDialogComponent implements OnInit, OnDestroy {
private store: Store<any>,
private i18nService: I18nService,
public dialog: MatDialog,
private changeDetectorRef: ChangeDetectorRef
private changeDetectorRef: ChangeDetectorRef,
private appGroupService: AppGroupService
) {
this.maxChatRoomUser = environment.productConfig.chat.maxChatRoomUser;
}
@ -108,10 +123,10 @@ export class ForwardDialogComponent implements OnInit, OnDestroy {
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
.subscribe((versionInfo) => {
this.versionInfo = versionInfo;
});
this.store
@ -119,6 +134,7 @@ export class ForwardDialogComponent implements OnInit, OnDestroy {
.subscribe((groups) => {
this.groupList = groups;
});
this.currentTabIndex = 0;
}
ngOnDestroy(): void {
@ -147,68 +163,49 @@ export class ForwardDialogComponent implements OnInit, OnDestroy {
onSearched(searchedUserInfos: UserInfoSS[]): void {
this.searchedList = searchedUserInfos;
if (!!this.appProfileList) {
this.appProfileList.psUpdate();
}
}
onToggleCheckUser(datas: { checked: boolean; userInfo: UserInfoSS }[]) {
if (!datas || 0 === datas.length) {
const resList = this.appGroupService.getToggleCheckUser(
datas,
this.selectedUserList
);
if (!resList) {
return;
}
const pushs: UserInfoSS[] = [];
const pops: UserInfoSS[] = [];
datas.forEach((d) => {
const i = this.selectedUserList.findIndex(
(u) => u.seq === d.userInfo.seq
);
if (d.checked) {
if (-1 === i) {
pushs.push(d.userInfo);
}
} else {
if (-1 < i) {
pops.push(d.userInfo);
}
}
});
if (0 < pushs.length) {
this.selectedUserList = [...this.selectedUserList, ...pushs];
}
if (0 < pops.length) {
this.selectedUserList = this.selectedUserList.filter(
(u) => -1 === pops.findIndex((p) => p.seq === u.seq)
);
}
this.selectedUserList = resList;
}
onToggleCheckGroup(params: {
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
groupBuddyList: { group: GroupInfoDetail; buddyList: UserInfo[] };
}) {
if (params.isChecked) {
params.groupBuddyList.buddyList.forEach((item) => {
if (
this.selectedUserList.filter((user) => user.seq === item.seq)
.length === 0
) {
this.selectedUserList = [...this.selectedUserList, item];
}
});
} else {
this.selectedUserList = this.selectedUserList.filter(
(item) =>
params.groupBuddyList.buddyList.filter((del) => del.seq === item.seq)
.length === 0
const resList = this.appGroupService.getToggleCheckGroup(
params,
this.selectedUserList
);
if (!resList) {
return;
}
this.selectedUserList = resList;
}
onCancel() {
this.dialogRef.close({ choice: false });
}
onConfirm() {
if (this.currentTabIndex === 0) {
this.selectedRoom = undefined;
} else {
this.selectedUserList = [];
}
if (
!this.selectedRoom &&
!!this.selectedUserList &&
@ -238,58 +235,8 @@ export class ForwardDialogComponent implements OnInit, OnDestroy {
onToggleRoom(roomInfo: RoomInfo): void {
if (!!roomInfo) {
this.selectedRoom = roomInfo;
}
}
getCheckedByRoomInfo(roomInfo: RoomInfo): boolean {
if (
!!this.selectedRoom &&
this.selectedRoom.roomId.localeCompare(roomInfo.roomId) === 0
) {
return true;
}
return false;
}
onChangeUserList(data: { checked: boolean; userInfo: UserInfoSS }) {
const i = this.selectedUserList.findIndex(
(u) => String(u.seq) === String(data.userInfo.seq)
);
if (data.checked) {
if (-1 === i) {
this.selectedUserList = [...this.selectedUserList, data.userInfo];
}
} else {
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => String(u.seq) !== String(data.userInfo.seq)
);
}
}
}
onChangeGroupList(params: {
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}) {
if (params.isChecked) {
params.groupBuddyList.buddyList.forEach((item) => {
if (
this.selectedUserList.filter(
(user) => String(user.seq) === String(item.seq)
).length === 0
) {
this.selectedUserList = [...this.selectedUserList, item];
}
});
} else {
this.selectedUserList = this.selectedUserList.filter(
(item) =>
params.groupBuddyList.buddyList.filter(
(del) => String(del.seq) === String(item.seq)
).length === 0
);
this.selectedRoom = undefined;
}
}
@ -321,31 +268,62 @@ export class ForwardDialogComponent implements OnInit, OnDestroy {
}
onToggleSearchAllItem(value: boolean): void {
const pushs: UserInfoSS[] = [];
const pops: UserInfoSS[] = [];
this.searchedList.forEach((user) => {
const i = this.selectedUserList.findIndex((u) => u.seq === user.seq);
if (-1 === i) {
pushs.push(user);
}
if (-1 < i) {
pops.push(user);
}
});
if (!!value) {
const targetRoomList = this.searchedList;
this.selectedUserList = targetRoomList.slice();
if (0 < pushs.length) {
this.selectedUserList = [...this.selectedUserList, ...pushs];
}
} else {
this.selectedUserList = [];
if (0 < pops.length) {
this.selectedUserList = this.selectedUserList.filter(
(u) => -1 === pops.findIndex((p) => p.seq === u.seq)
);
}
}
this.changeDetectorRef.markForCheck();
}
onCanceled() {
this.isSearch = false;
this.searchedList = [];
this.companySearchData = { ...this.companySearchData, searchWord: '' };
}
checkDisableBtn(): boolean {
if (
this.currentTabIndex === 0 &&
!!this.selectedUserList &&
this.selectedUserList.length > 0
) {
return false;
} else if (this.currentTabIndex === 1 && !!this.selectedRoom) {
return false;
}
return true;
}
onRemovedProfileSelection(userInfo: UserInfo) {
const i = this.selectedUserList.findIndex(
(u) => String(u.seq) === String(userInfo.seq)
const resUserList = this.appGroupService.removedProfileSelection(
userInfo,
this.selectedUserList
);
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => String(u.seq) !== String(userInfo.seq)
);
if (!resUserList) {
}
this.selectedUserList = resUserList;
}
removableForSelection = (userInfo: UserInfo) => {
return true;

View File

@ -1,4 +1,5 @@
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
Component,
@ -9,6 +10,8 @@ import {
Inject
} from '@angular/core';
import { Store, select } from '@ngrx/store';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@ -16,11 +19,9 @@ import {
MatDialogConfig
} from '@angular/material/dialog';
import { I18nService } from '@ucap/ng-i18n';
import { RoomInfo } from '@ucap/domain-chat';
import { Store, select } from '@ngrx/store';
import { takeUntil } from 'rxjs/operators';
import { RoomInfo } from '@ucap/protocol-room';
import { I18nService } from '@ucap/ng-i18n';
import { RoomSelector } from '@ucap/ng-store-chat';
export interface SettingDialogData {

View File

@ -12,7 +12,7 @@
<div class="profile-image">
<img
ucapImage
[base]="versionInfo2Res?.profileRoot"
[base]="versionInfo?.profileRoot"
[path]="userInfo.profileImageFile"
[default]="defaultProfileImage"
/>
@ -32,7 +32,7 @@
</div>
</div>
<div class="contents">
<perfect-scrollbar
<perfect-scrollbar #perfectScroll
><div
[innerHTML]="
contents | ucapSafeHtml | ucapLinefeedToHtml | ucapLinky

View File

@ -5,7 +5,9 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject,
ElementRef
ElementRef,
ViewChild,
AfterViewInit
} from '@angular/core';
import { Subject, of, combineLatest } from 'rxjs';
@ -18,16 +20,18 @@ import {
MatDialog
} from '@angular/material/dialog';
import { DeviceType } from '@ucap/core';
import { StatusCode } from '@ucap/api';
import { NativeService } from '@ucap/native';
import { VersionInfo2Response } from '@ucap/api-public';
import { MassTalkDownloadRequest } from '@ucap/api-common';
import { DeviceType } from '@ucap/domain-common';
import {
UserInfo as RoomUserInfo,
UserInfoShort as RoomUserInfoShort
} from '@ucap/protocol-room';
import { Info, EventJson } from '@ucap/protocol-event';
UserInfoShort as RoomUserInfoShort,
Info,
EventJson
} from '@ucap/domain-chat';
import { StatusCode } from '@ucap/api';
import { NativeService } from '@ucap/native';
import { MassTalkDownloadRequest } from '@ucap/api-common';
import { CommonApiService } from '@ucap/ng-api-common';
import { LogService } from '@ucap/ng-logger';
@ -36,6 +40,8 @@ import { RoomSelector } from '@ucap/ng-store-chat';
import { ConfigurationSelector } from '@ucap/ng-store-authentication';
import { AppChatService } from '@app/services/app-chat.service';
import { PerfectScrollbarComponent } from 'ngx-perfect-scrollbar';
import { VersionInfo } from '@ucap/domain-authentication';
export type UserInfoTypes = RoomUserInfo | RoomUserInfoShort;
@ -55,13 +61,15 @@ export interface TextDetailDialogResult {}
styleUrls: ['./text-detail.dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TextDetailDialogComponent implements OnInit, OnDestroy {
export class TextDetailDialogComponent
implements OnInit, OnDestroy, AfterViewInit {
userInfo: UserInfoTypes;
contents: string;
contents = '';
defaultProfileImage: string;
versionInfo2Res: VersionInfo2Response;
versionInfo: VersionInfo;
@ViewChild('perfectScroll') perfectScroll: PerfectScrollbarComponent;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
@ -85,10 +93,10 @@ export class TextDetailDialogComponent implements OnInit, OnDestroy {
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
.subscribe((versionInfo) => {
this.versionInfo = versionInfo;
});
this.commonApiService
@ -107,6 +115,7 @@ export class TextDetailDialogComponent implements OnInit, OnDestroy {
this.changeDetectorRef.markForCheck();
setTimeout(() => {
this.psUpdate();
if (
!!this.elementRef.nativeElement &&
!!this.elementRef.nativeElement.querySelector('a')
@ -154,6 +163,16 @@ export class TextDetailDialogComponent implements OnInit, OnDestroy {
}
}
ngAfterViewInit(): void {
this.psUpdate();
}
psUpdate() {
if (!!this.perfectScroll) {
this.perfectScroll.directiveRef.update();
}
}
onClickEvent(event: MouseEvent) {
this.nativeService.platform_openDefaultBrowser(
(event.target as HTMLAnchorElement).text

View File

@ -12,7 +12,7 @@
<div class="profile-image">
<img
ucapImage
[base]="versionInfo2Res?.profileRoot"
[base]="versionInfo?.profileRoot"
[path]="userInfo.profileImageFile"
[default]="defaultProfileImage"
/>

View File

@ -1,3 +1,6 @@
import { Subject, of, combineLatest } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
Component,
OnInit,
@ -9,7 +12,7 @@ import {
AfterViewInit
} from '@angular/core';
import { Subject, of, combineLatest } from 'rxjs';
import { Store, select } from '@ngrx/store';
import {
MatDialogRef,
@ -17,21 +20,21 @@ import {
MatDialog
} from '@angular/material/dialog';
import { NativeService } from '@ucap/native';
import { VersionInfo2Response } from '@ucap/api-public';
import {
UserInfo as RoomUserInfo,
UserInfoShort as RoomUserInfoShort
} from '@ucap/protocol-room';
UserInfoShort as RoomUserInfoShort,
Info,
MassTranslationEventJson
} from '@ucap/domain-chat';
import { NativeService } from '@ucap/native';
import { LogService } from '@ucap/ng-logger';
import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native';
import { Store, select } from '@ngrx/store';
import { takeUntil } from 'rxjs/operators';
import { ConfigurationSelector } from '@ucap/ng-store-authentication';
import { RoomSelector } from '@ucap/ng-store-chat';
import { Info, MassTranslationEventJson } from '@ucap/protocol-event';
import { VersionInfo } from '@ucap/domain-authentication';
export type UserInfoTypes = RoomUserInfo | RoomUserInfoShort;
@ -55,7 +58,7 @@ export class TransDetailDialogComponent
contents: string;
defaultProfileImage: string;
versionInfo2Res: VersionInfo2Response;
versionInfo: VersionInfo;
private ngOnDestroySubject: Subject<void> = new Subject();
@ -78,10 +81,10 @@ export class TransDetailDialogComponent
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
select(ConfigurationSelector.versionInfo)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
.subscribe((versionInfo) => {
this.versionInfo = versionInfo;
});
combineLatest([
this.store.pipe(select(RoomSelector.roomUser, this.data.roomId)),

View File

@ -0,0 +1,179 @@
import { Subject, fromEvent } from 'rxjs';
import {
Directive,
ElementRef,
EventEmitter,
HostListener,
Output,
Input,
AfterViewInit,
NgZone,
OnDestroy,
OnInit
} from '@angular/core';
import { FileUploadItem } from '@ucap/domain-common';
import { FileUploadQueueComponent } from '@ucap/ng-ui/file-upload';
import { takeUntil } from 'rxjs/operators';
@Directive({
selector: 'input[ucapFileUploadFor01], div[ucapFileUploadFor01]'
})
export class FileUploadForDirective
implements OnInit, OnDestroy, AfterViewInit {
@Input()
fileUploadQueue?: FileUploadQueueComponent;
@Input()
validator?: (fileList: FileList) => Promise<boolean>;
@Output()
public fileDragEnter = new EventEmitter<DataTransferItemList>();
@Output()
public fileDragOver = new EventEmitter<void>();
@Output()
public fileDragLeave = new EventEmitter<void>();
@Output()
public fileSelected = new EventEmitter<FileUploadItem[] | FileList>();
dragOver = false;
private ngOnDestroySubject: Subject<void> = new Subject();
constructor(
private elementRef: ElementRef,
private readonly ngZone: NgZone
) {}
ngOnInit(): void {
fromEvent(window, 'dragenter')
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((event: DragEvent) => {
if (!this.isFileDrag(event.dataTransfer)) {
return;
}
if (!this.dragOver) {
this.fileDragEnter.emit(event.dataTransfer.items);
this.dragOver = true;
if (!!this.fileUploadQueue) {
this.fileUploadQueue.onDragEnter(event.dataTransfer.items);
}
}
});
fromEvent(window, 'dragover')
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((event: DragEvent) => {
if (!this.isFileDrag(event.dataTransfer)) {
return;
}
// if (this.fileUploadQueue.isEventInElement(event)) {
// event.dataTransfer.dropEffect = 'copy';
// } else {
// event.dataTransfer.dropEffect = 'none';
// }
event.preventDefault();
});
fromEvent(window, 'dragleave')
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((event: DragEvent) => {
if (!this.isFileDrag(event.dataTransfer)) {
return;
}
if (event && event.pageX === 0 && event.pageY === 0) {
this.fileDragLeave.emit();
this.dragOver = false;
if (!!this.fileUploadQueue) {
this.fileUploadQueue.onDragLeave();
}
}
});
fromEvent(window, 'drop')
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((event: any) => {
if (!this.isFileDrag(event.dataTransfer)) {
return;
}
const self = this;
const files: FileList = event.dataTransfer.files;
event.preventDefault();
event.stopPropagation();
this.elementRef.nativeElement.value = ''; // Case defined directive in 'input' type.
this.dragOver = false;
if (!!this.validator) {
this.validator(files)
.then(async (result) => {
if (!result) {
if (!!this.fileUploadQueue) {
this.fileUploadQueue.onUploadComplete();
}
return;
} else {
const fileUploadItems = FileUploadItem.fromFiles(files);
self.fileSelected.emit(fileUploadItems);
if (!!self.fileUploadQueue) {
self.fileUploadQueue.onDrop(fileUploadItems);
}
}
})
.catch((err) => {
if (!!this.fileUploadQueue) {
this.fileUploadQueue.onUploadComplete();
}
});
} else {
const fileUploadItems = FileUploadItem.fromFiles(files);
this.fileSelected.emit(files);
if (!!this.fileUploadQueue) {
this.fileUploadQueue.onDrop(fileUploadItems);
}
}
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
ngAfterViewInit(): void {}
// @HostListener('change')
// public onChange(): any {
// const files = this.elementRef.nativeElement.files;
// this.fileSelected.emit(FileUploadItem.fromFiles(files));
// this.elementRef.nativeElement.value = '';
// }
private isFileDrag(dataTransfer: DataTransfer): boolean {
if (0 >= dataTransfer.items.length) {
return false;
}
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < dataTransfer.items.length; i++) {
const element = dataTransfer.items[i];
if ('file' !== element.kind) {
return false;
}
}
return true;
}
}

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