import { Component, OnInit, OnDestroy, AfterViewChecked, ViewChild, ElementRef } from '@angular/core'; import { ucapAnimations, SnackBarService, ClipboardService, DialogService, ConfirmDialogComponent, ConfirmDialogData, ConfirmDialogResult } from '@ucap-webmessenger/ui'; import { Store, select } from '@ngrx/store'; import { NGXLogger } from 'ngx-logger'; import { Observable, Subscription } from 'rxjs'; import { Info, EventType, isRecalled, isCopyable, isRecallable } from '@ucap-webmessenger/protocol-event'; import * as AppStore from '@app/store'; import * as EventStore from '@app/store/messenger/event'; import * as ChatStore from '@app/store/messenger/chat'; import * as RoomStore from '@app/store/messenger/room'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { SessionStorageService } from '@ucap-webmessenger/web-storage'; import { EnvironmentsInfo, KEY_ENVIRONMENTS_INFO, UserSelectDialogType } from '@app/types'; import { RoomInfo, UserInfo, RoomType } from '@ucap-webmessenger/protocol-room'; import { tap, take } from 'rxjs/operators'; import { FileInfo } from '@ucap-webmessenger/ui-chat'; import { KEY_VER_INFO } from '@app/types/ver-info.type'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { MatMenuTrigger } from '@angular/material'; import { CommonApiService } from '@ucap-webmessenger/api-common'; import { CreateChatDialogComponent, CreateChatDialogData, CreateChatDialogResult } from '../dialogs/chat/create-chat.dialog.component'; import { ImageViewerDialogComponent, ImageViewerDialogData, ImageViewerDialogResult } from '@app/layouts/common/dialogs/image-viewer.dialog.component'; import { Maximum_Range } from '@ucap-webmessenger/core'; import { PerfectScrollbarComponent } from 'ngx-perfect-scrollbar'; @Component({ selector: 'app-layout-messenger-messages', templateUrl: './messages.component.html', styleUrls: ['./messages.component.scss'], animations: ucapAnimations }) export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked { @ViewChild('messageBoxContainer', { static: true }) private messageBoxContainer: ElementRef; @ViewChild('messageContextMenuTrigger', { static: true }) messageContextMenuTrigger: MatMenuTrigger; messageContextMenuPosition = { x: '0px', y: '0px' }; @ViewChild('psChatContent', { static: true }) psChatContent: PerfectScrollbarComponent; environmentsInfo: EnvironmentsInfo; loginRes: LoginResponse; loginResSubscription: Subscription; eventList$: Observable; roomInfo: RoomInfo; roomInfoSubscription: Subscription; userInfoList: UserInfo[]; userInfoListSubscription: Subscription; eventListProcessing$: Observable; sessionVerInfo: VersionInfo2Response; isRecalledMessage = isRecalled; isCopyableMessage = isCopyable; isRecallableMessage = isRecallable; fileDragOver = false; files: File[]; constructor( private store: Store, private sessionStorageService: SessionStorageService, private commonApiService: CommonApiService, private clipboardService: ClipboardService, private dialogService: DialogService, private snackBarService: SnackBarService, private logger: NGXLogger ) { this.sessionVerInfo = this.sessionStorageService.get( KEY_VER_INFO ); this.environmentsInfo = this.sessionStorageService.get( KEY_ENVIRONMENTS_INFO ); } ngOnInit() { this.loginResSubscription = this.store .pipe( select(AppStore.AccountSelector.AuthenticationSelector.loginRes), tap(loginRes => { this.loginRes = loginRes; }) ) .subscribe(); this.roomInfoSubscription = this.store .pipe( select(AppStore.MessengerSelector.RoomSelector.roomInfo), tap(roomInfo => { this.roomInfo = roomInfo; }) ) .subscribe(); this.userInfoListSubscription = this.store .pipe( select(AppStore.MessengerSelector.RoomSelector.userInfoList), tap(userInfoList => { this.userInfoList = userInfoList; }) ) .subscribe(); this.eventListProcessing$ = this.store.pipe( select(AppStore.MessengerSelector.EventSelector.infoListProcessing) ); this.eventList$ = this.store.pipe( select(AppStore.MessengerSelector.EventSelector.selectAllInfoList) ); this.psChatContent.directiveRef.scrollToBottom(0, 0); } ngOnDestroy(): void { if (!!this.loginResSubscription) { this.loginResSubscription.unsubscribe(); } if (!!this.roomInfoSubscription) { this.roomInfoSubscription.unsubscribe(); } if (!!this.userInfoListSubscription) { this.userInfoListSubscription.unsubscribe(); } } ngAfterViewChecked(): void { this.psChatContent.directiveRef.scrollToBottom(0, 0); } getRoomName() { if (!this.roomInfo || !this.userInfoList) { return '대화방명을 가져오고 있습니다..'; } if (!!this.roomInfo.roomName && '' !== this.roomInfo.roomName.trim()) { return this.roomInfo.roomName; } else if (this.roomInfo.roomType === RoomType.Mytalk) { return 'MyTalk'; } else { return this.userInfoList .filter(user => { if (this.roomInfo.roomType === RoomType.Single) { return user.seq !== this.loginRes.userSeq; } else { return true; } }) .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) .map(user => user.name) .join(','); } } selectContact() {} onSendMessage(message: string) { if (message.trim().length > Maximum_Range.MassText) { // MASS TEXT this.store.dispatch( EventStore.sendMass({ senderSeq: this.loginRes.userSeq, req: { roomSeq: this.roomInfo.roomSeq, eventType: EventType.MassText, // sentMessage: message.replace(/\n/gi, '\r\n') sentMessage: message } }) ); } else { this.store.dispatch( EventStore.send({ senderSeq: this.loginRes.userSeq, req: { roomSeq: this.roomInfo.roomSeq, eventType: EventType.Character, sentMessage: message } }) ); } } onClickReceiveAlarm() { this.store.dispatch(RoomStore.updateOnlyAlarm({ roomInfo: this.roomInfo })); } /** MassText Detail View */ onMassDetail(value: number) { this.store.dispatch( ChatStore.selectedMassDetail({ massEventSeq: value }) ); } async onImageViewer(value: FileInfo) { this.logger.debug('imageViewer', value); const result = await this.dialogService.open< ImageViewerDialogComponent, ImageViewerDialogData, ImageViewerDialogResult >(ImageViewerDialogComponent, { position: { top: '10px' }, width: '100%', height: '98%', data: {} }); } /** File Save, Save As */ onSave(value: { fileInfo: FileInfo; type: string }) { this.logger.debug('fileSave', value); } onFileDragEnter() { this.logger.debug('onFileDragEnter'); this.fileDragOver = true; } onFileDragOver() { this.logger.debug('onFileDragOver'); } onFileDragLeave() { this.logger.debug('onFileDragLeave'); this.fileDragOver = false; } onFileSelected(files: File[]) { this.logger.debug('onFileSelected', files); if (!this.files) { this.files = []; } this.files.push(...files); this.fileDragOver = false; } onContextMenuMessage(params: { event: MouseEvent; message: Info }) { params.event.preventDefault(); params.event.stopPropagation(); this.messageContextMenuPosition.x = params.event.clientX + 'px'; this.messageContextMenuPosition.y = params.event.clientY + 'px'; this.messageContextMenuTrigger.menu.focusFirstItem('mouse'); this.messageContextMenuTrigger.menuData = { message: params.message, loginRes: this.loginRes }; this.messageContextMenuTrigger.openMenu(); } async onClickMessageContextMenu(menuType: string, message: Info) { switch (menuType) { case 'COPY': { switch (message.type) { case EventType.Character: { if ( this.clipboardService.copyFromContent(message.sentMessage) ) { this.snackBarService.open('클립보드에 복사되었습니다.', '', { duration: 3000, verticalPosition: 'top', horizontalPosition: 'center' }); } } break; case EventType.MassText: { this.commonApiService .massTalkDownload({ userSeq: this.loginRes.userSeq, deviceType: this.environmentsInfo.deviceType, token: this.loginRes.tokenString, eventMassSeq: message.seq }) .pipe(take(1)) .subscribe(res => { if (this.clipboardService.copyFromContent(res.content)) { this.snackBarService.open( '클립보드에 복사되었습니다.', '', { duration: 3000, verticalPosition: 'top', horizontalPosition: 'center' } ); } }); } break; default: break; } } break; case 'REPLAY': { const result = await this.dialogService.open< CreateChatDialogComponent, CreateChatDialogData, CreateChatDialogResult >(CreateChatDialogComponent, { width: '600px', height: '500px', data: { type: UserSelectDialogType.MessageForward, title: 'MessageForward', ignoreRoom: [this.roomInfo] } }); if (!!result && !!result.choice && result.choice) { const userSeqs: number[] = []; let roomSeq = ''; if ( !!result.selectedUserList && result.selectedUserList.length > 0 ) { result.selectedUserList.map(user => userSeqs.push(user.seq)); } if (!!result.selectedRoom) { roomSeq = result.selectedRoom.roomSeq; } if (userSeqs.length > 0 || roomSeq.trim().length > 0) { this.store.dispatch( EventStore.forward({ senderSeq: this.loginRes.userSeq, req: { roomSeq: '-999', eventType: message.type, sentMessage: message.sentMessage }, trgtUserSeqs: userSeqs, trgtRoomSeq: roomSeq }) ); } } } break; case 'REPLAY_TO_ME': { if (this.loginRes.talkWithMeBotSeq > -1) { this.store.dispatch( EventStore.forward({ senderSeq: this.loginRes.userSeq, req: { roomSeq: '-999', eventType: message.type, sentMessage: message.sentMessage }, trgtUserSeqs: [this.loginRes.talkWithMeBotSeq] }) ); } } break; case 'DELETE': { const result = await this.dialogService.open< ConfirmDialogComponent, ConfirmDialogData, ConfirmDialogResult >(ConfirmDialogComponent, { width: '220px', data: { title: 'Delete', html: `선택한 메시지를 삭제하시겠습니까?
삭제된 메시지는 내 대화방에서만 적용되며 상대방의 대화방에서는 삭제되지 않습니다.` } }); if (!!result && !!result.choice && result.choice) { this.store.dispatch( EventStore.del({ roomSeq: this.roomInfo.roomSeq, eventSeq: message.seq }) ); } } break; case 'RECALL': { const result = await this.dialogService.open< ConfirmDialogComponent, ConfirmDialogData, ConfirmDialogResult >(ConfirmDialogComponent, { width: '220px', data: { title: 'ReCall', html: `해당 대화를 회수하시겠습니까?
상대방 대화창에서도 회수됩니다.` } }); if (!!result && !!result.choice && result.choice) { this.store.dispatch( EventStore.cancel({ roomSeq: this.roomInfo.roomSeq, eventSeq: message.seq, deviceType: this.environmentsInfo.deviceType }) ); } } break; default: break; } } onClickContextMenu(menuType: string) { switch (menuType) { case 'CLOSE_ROOM': { this.store.dispatch(ChatStore.clearSelectedRoom()); } break; default: break; } } }