import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit, Output, EventEmitter, Inject, ChangeDetectorRef, ChangeDetectionStrategy, ElementRef } from '@angular/core'; import { ucapAnimations, SnackBarService, ClipboardService, DialogService, ConfirmDialogComponent, ConfirmDialogData, ConfirmDialogResult, AlertDialogComponent, AlertDialogData, AlertDialogResult, FileUploadQueueComponent, StringUtil, AlertSnackbarComponent, AlertSnackbarData } from '@ucap-webmessenger/ui'; import { Store, select } from '@ngrx/store'; import { NGXLogger } from 'ngx-logger'; import { Observable, Subscription, forkJoin, combineLatest, of, BehaviorSubject } from 'rxjs'; import { Info, EventType, isRecalled, isCopyable, isRecallable, isForwardable, InfoResponse, EventJson, FileEventJson, StickerEventJson, MassTextEventJson, TranslationEventJson, MassTranslationEventJson } 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 * as SyncStore from '@app/store/messenger/sync'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { SessionStorageService, LocalStorageService } from '@ucap-webmessenger/web-storage'; import { EnvironmentsInfo, KEY_ENVIRONMENTS_INFO, UserSelectDialogType, RightDrawer, KEY_STICKER_HISTORY } from '@app/types'; import { RoomInfo, UserInfo, RoomType, UserInfoShort } from '@ucap-webmessenger/protocol-room'; import { tap, take, map, catchError, finalize } from 'rxjs/operators'; import { FormComponent as UCapUiChatFormComponent, MessagesComponent as UCapUiChatMessagesComponent } from '@ucap-webmessenger/ui-chat'; import { KEY_VER_INFO } from '@app/types'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { MatMenuTrigger, MatSnackBarRef, SimpleSnackBar } from '@angular/material'; import { FileUploadItem, FileDownloadItem } from '@ucap-webmessenger/api'; import { CommonApiService, FileTalkSaveRequest, FileTalkSaveResponse, TranslationSaveRequest, TranslationSaveResponse } from '@ucap-webmessenger/api-common'; import { CreateChatDialogComponent, CreateChatDialogData, CreateChatDialogResult } from '../dialogs/chat/create-chat.dialog.component'; import { FileViewerDialogComponent, FileViewerDialogData, FileViewerDialogResult } from '@app/layouts/common/dialogs/file-viewer.dialog.component'; import { FileUtil, StickerFilesInfo, MimeUtil } from '@ucap-webmessenger/core'; import { StatusCode } from '@ucap-webmessenger/api'; import { EditChatRoomDialogComponent, EditChatRoomDialogResult, EditChatRoomDialogData } from '../dialogs/chat/edit-chat-room.dialog.component'; import { SelectGroupDialogComponent, SelectGroupDialogResult, SelectGroupDialogData } from '../dialogs/group/select-group.dialog.component'; import { GroupDetailData } from '@ucap-webmessenger/protocol-sync'; import { environment } from '../../../../environments/environment'; import { MassDetailComponent, MassDetailDialogData } from '../dialogs/chat/mass-detail.component'; import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native'; import { TranslateService } from '@ngx-translate/core'; import { TranslatePipe } from 'projects/ucap-webmessenger-ui/src/lib/pipes/translate.pipe'; import { TranslateService as UiTranslateService } from '@ucap-webmessenger/ui'; import { FileProtocolService } from '@ucap-webmessenger/protocol-file'; import { ClipboardDialogComponent, ClipboardDialogData, ClipboardDialogResult } from '../dialogs/chat/clipboard.dialog.component'; @Component({ selector: 'app-layout-messenger-messages', templateUrl: './messages.component.html', styleUrls: ['./messages.component.scss'], animations: ucapAnimations, changeDetection: ChangeDetectionStrategy.OnPush }) export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit { @Output() openProfile = new EventEmitter<{ userSeq: number; }>(); @Output() closeRightDrawer = new EventEmitter(); @ViewChild('chatForm', { static: false }) private chatForm: UCapUiChatFormComponent; @ViewChild('messageContextMenuTrigger', { static: true }) messageContextMenuTrigger: MatMenuTrigger; messageContextMenuPosition = { x: '0px', y: '0px' }; @ViewChild('fileUploadQueue', { static: true }) fileUploadQueue: FileUploadQueueComponent; @ViewChild('chatMessages', { static: true }) chatMessages: UCapUiChatMessagesComponent; environmentsInfo: EnvironmentsInfo; loginResSubscription: Subscription; loginResSubject = new BehaviorSubject(undefined); roomInfoSubscription: Subscription; roomInfoSubject = new BehaviorSubject(undefined); userInfoListSubscription: Subscription; userInfoListSubject = new BehaviorSubject(undefined); eventListSubscription: Subscription; eventListSubject = new BehaviorSubject[]>(undefined); eventListNewSubject = new BehaviorSubject[]>(undefined); eventInfoStatusSubscription: Subscription; eventInfoStatusSubject = new BehaviorSubject(undefined); eventRemainedSubscription: Subscription; eventRemainedSubject = new BehaviorSubject(false); // 이전대화가 남아 있는지 여부 sessionVerInfo: VersionInfo2Response; baseEventSeq = 0; eventListProcessing$: Observable; searchEventListProcessing: boolean; searchEventListProcessingSubscription: Subscription; isRecalledMessage = isRecalled; isCopyableMessage = isCopyable; isRecallableMessage = isRecallable; isForwardableMessage = isForwardable; /** Timer 대화방의 대화 삭제를 위한 interval */ interval: any; /** About Searching */ isShowSearchArea = false; moreSearchProcessing = false; searchText = ''; searchedListSubject = new BehaviorSubject[]>(undefined); searchedFocusEvent: Info; searchTotalCount = 0; searchCurrentIndex = 0; /** About Sticker */ isShowStickerSelector = false; selectedSticker: StickerFilesInfo; /** About Translation */ isTranslationProcess = false; isShowTranslation = false; translationSimpleview = false; translationPreview = false; destLocale = 'en'; // default English :: en translationPreviewInfo: { previewInfo: TranslationSaveResponse | null; translationType: EventType.Translation | EventType.MassTranslation; }; /** About ReadHere */ firstCheckReadHere = true; clearReadHere = false; initRoomLastEventSeq: number; snackBarPreviewEvent: MatSnackBarRef; RoomType = RoomType; environment = environment; constructor( private store: Store, private sessionStorageService: SessionStorageService, private localStorageService: LocalStorageService, private commonApiService: CommonApiService, private fileProtocolService: FileProtocolService, private clipboardService: ClipboardService, private uiTranslateService: UiTranslateService, private translateService: TranslateService, private changeDetectorRef: ChangeDetectorRef, private dialogService: DialogService, private snackBarService: SnackBarService, @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, 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)) .subscribe(loginRes => { this.loginResSubject.next(loginRes); }); this.roomInfoSubscription = this.store .pipe(select(AppStore.MessengerSelector.RoomSelector.roomInfo)) .subscribe(roomInfo => { if ( !this.roomInfoSubject.value || (!!this.roomInfoSubject.value && !!roomInfo && this.roomInfoSubject.value.roomSeq !== roomInfo.roomSeq) ) { this.clearView(); if (!!this.roomInfoSubject.value && !!this.interval) { clearInterval(this.interval); this.interval = undefined; } if ( !!this.roomInfoSubject.value && !!this.roomInfoSubject.value.isTimeRoom ) { this.interval = setInterval(() => { this.store.dispatch(EventStore.infoIntervalClear({})); }, 1000); } this.readyToReply(); } this.roomInfoSubject.next(roomInfo); }); this.userInfoListSubscription = this.store .pipe(select(AppStore.MessengerSelector.RoomSelector.selectUserinfolist)) .subscribe(userInfoList => { this.userInfoListSubject.next(userInfoList); }); this.eventListProcessing$ = this.store.pipe( select(AppStore.MessengerSelector.EventSelector.infoListProcessing) ); this.searchEventListProcessingSubscription = this.store .pipe( select( AppStore.MessengerSelector.EventSelector.infoSearchListProcessing ) ) .subscribe(process => { this.searchEventListProcessing = process; if (!process && this.isShowSearchArea) { this.doSearchTextInEvent(this.searchText); this.snackBarService.open( this.translateService.instant('chat.searchEventByTextEnd'), this.translateService.instant('common.messages.confirm'), { duration: 3000, verticalPosition: 'top', horizontalPosition: 'center' } ); } }); this.eventRemainedSubscription = this.store .pipe(select(AppStore.MessengerSelector.EventSelector.remainInfo)) .subscribe(remained => { this.eventRemainedSubject.next(remained); }); // [Daesang] this.eventListSubscription = this.store .pipe(select(AppStore.MessengerSelector.EventSelector.selectAllInfoList)) .subscribe(infoList => { if ( !!this.eventListSubject.value && this.eventListSubject.value.length > 0 ) { this.eventListNewSubject.next( infoList.filter(info => { if ( info.seq <= this.eventListSubject.value[ this.eventListSubject.value.length - 1 ].seq ) { return false; } return true; }) ); if ( !!infoList && infoList.length > 0 && !!this.roomInfoSubject.value && !!this.roomInfoSubject.value.lastReadEventSeq && this.baseEventSeq <= this.roomInfoSubject.value.lastReadEventSeq ) { // 조회된 내용중에 read here 가 있을 경우. this.firstCheckReadHere = false; } } this.eventListSubject.next(infoList); if (this.moreSearchProcessing) { const baseseq = this.baseEventSeq; // setTimeout(() => { // this.doSearchTextInEvent(this.searchText, baseseq); // }, 800); this.baseEventSeq = infoList[0].seq; } else { if (!!infoList && infoList.length > 0) { this.baseEventSeq = infoList[0].seq; } } }); // // [GROUP] // this.eventListSubscription = this.store // .pipe( // select(AppStore.MessengerSelector.EventSelector.selectAllInfoList), // tap(infoList => { // if (!!this.eventList && this.eventList.length > 0) { // this.eventListNew = infoList.filter(info => { // if (info.seq <= this.eventList[this.eventList.length - 1].seq) { // return false; // } // return true; // }); // } // this.eventList = infoList; // if (!!infoList && infoList.length > 0) { // this.baseEventSeq = infoList[0].seq; // this.readyToReply(); // } // }) // ) // .subscribe(); this.eventInfoStatusSubscription = this.store .pipe(select(AppStore.MessengerSelector.EventSelector.infoStatus)) .subscribe(res => { this.eventInfoStatusSubject.next(res); if (!!res) { const elist = this.eventListSubject.value; if (res.baseSeq === 0 && elist.length > 0) { this.initRoomLastEventSeq = elist[elist.length - 1].seq; } } }); } ngOnDestroy(): void { if (!!this.loginResSubscription) { this.loginResSubscription.unsubscribe(); } if (!!this.roomInfoSubscription) { this.roomInfoSubscription.unsubscribe(); } if (!!this.userInfoListSubscription) { this.userInfoListSubscription.unsubscribe(); } if (!!this.eventListSubscription) { this.eventListSubscription.unsubscribe(); } if (!!this.eventInfoStatusSubscription) { this.eventInfoStatusSubscription.unsubscribe(); } if (!!this.eventRemainedSubscription) { this.eventRemainedSubscription.unsubscribe(); } if (!!this.searchEventListProcessingSubscription) { this.searchEventListProcessingSubscription.unsubscribe(); } if (!!this.interval) { clearInterval(this.interval); } } ngAfterViewInit(): void { // this.readyToReply(); } /** * 채팅방의 여러 팝업들을 닫아준다. */ clearView() { // Right Drawer closed.. this.closeRightDrawer.emit(); // Sticker Selector Clear.. this.isShowStickerSelector = false; this.selectedSticker = undefined; // Chat Search Clear.. this.onCloseSearchArea(); // Translate Clear.. this.isTranslationProcess = false; this.isShowTranslation = false; this.translationSimpleview = false; this.translationPreview = false; // this.destLocale = 'en'; // default English :: en this.translationPreviewInfo = null; // Read here Clear.. this.firstCheckReadHere = true; // Chat Formfield Clear.. if (!!this.chatForm) { this.chatForm.replyForm.reset(); } } get _roomUserInfos() { if (this.roomInfoSubject.value.roomType === RoomType.Single) { return this.userInfoListSubject.value.filter(roomUserInfo => { return this.loginResSubject.value.userSeq !== roomUserInfo.seq; }); } else { return this.userInfoListSubject.value .filter(roomUserInfo => { return ( this.loginResSubject.value.userSeq !== roomUserInfo.seq && roomUserInfo.isJoinRoom ); }) .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); } } getRoomNameByRoomUser(roomUserInfos: (UserInfo | UserInfoShort)[]) { let roomName = new TranslatePipe( this.uiTranslateService, this.changeDetectorRef ).transform(roomUserInfos, 'name', ','); if (!roomName || roomName.trim().length === 0) { roomName = this.translateService.instant('chat.noRoomUser'); } return roomName; } /** 대화전송 가능한 방인지 판단 */ getEnableSend() { if (!this.roomInfoSubject.value) { return false; } if ( [ RoomType.Bot, RoomType.Allim, RoomType.Link, RoomType.Allim_Elephant, RoomType.Allim_TMS ].some(v => v === this.roomInfoSubject.value.roomType) ) { return false; } return true; } getConvertTimer(timerInterval: number, unit: number = 1) { if (timerInterval >= 0 && timerInterval < 60 * unit) { return Math.floor((timerInterval / 1) * unit) + ' 초'; } else if (timerInterval >= 60 * unit && timerInterval < 3600 * unit) { return Math.floor(((timerInterval / 1) * unit) / 60) + ' 분'; } else if (timerInterval >= 3600 * unit && timerInterval <= 86400 * unit) { return Math.floor(((timerInterval / 1) * unit) / 60 / 60) + ' 시간'; } else { return ''; } } getShowContextMenu(menuType: string) { if ( ['OPEN_ROOM_USER', 'ADD_MEMBER', 'ADD_GROUP', 'EDIT_ROOM'].some( v => v === menuType ) ) { if ( !this.roomInfoSubject.value || !this.roomInfoSubject.value.roomType || [ RoomType.Mytalk, RoomType.Allim, RoomType.Bot, RoomType.Link, RoomType.Allim_Elephant, RoomType.Allim_TMS ].some(v => v === this.roomInfoSubject.value.roomType) ) { return false; } } return true; } getShowUnreadCount(): boolean { if ( !this.roomInfoSubject.value || this.roomInfoSubject.value === undefined ) { return true; } if ( [ RoomType.Mytalk, RoomType.Allim, RoomType.Bot, RoomType.Link, RoomType.Allim_Elephant, RoomType.Allim_TMS ].some(v => v === this.roomInfoSubject.value.roomType) ) { return false; } return true; } readyToReply(): void { setTimeout(() => { this.focusReplyInput(); }); } focusReplyInput(): void { setTimeout(() => { if (!!this.chatForm) { this.chatForm.focus(); } }); } onScrollupMessages(event: any) {} onYReachStartMessages(event: any) { // 자동 스크롤이 아닌 버튼 방식으로 변경. // this.onMoreEvent(this.baseEventSeq); } onYReachEndMessages(event: any) { this.chatMessages.initEventMore(); if (!!this.snackBarPreviewEvent) { this.snackBarPreviewEvent.dismiss(); } // // clear readHere object.. 정책상 클리어 하지 않도록 함. // if (!this.firstCheckReadHere) { // this.clearReadHere = true; // } } /** More Event */ onMoreEvent(seq: number) { this.store.dispatch( EventStore.info({ roomSeq: this.roomInfoSubject.value.roomSeq, baseSeq: seq, requestCount: environment.productConfig.CommonSetting.eventRequestDefaultCount }) ); } /** Send Event */ async onSendMessage(message: string) { this.chatMessages.initEventMore(); if (!this.selectedSticker) { if (!message || message.trim().length === 0) { const result = await this.dialogService.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { width: '360px', data: { title: this.translateService.instant('chat.errors.label'), message: this.translateService.instant( 'chat.errors.inputChatMessage' ) } }); return; } } if (!!this.isShowTranslation && this.destLocale.trim().length > 0) { /** CASE : Translation */ // 번역할 대화 없이 스티커만 전송할 경우. if (!message || message.trim().length === 0) { this.sendMessageOfSticker(message); } else { this.sendMessageOfTranslate(message); } } else if (!!this.selectedSticker) { /** CASE : Sticker */ this.sendMessageOfSticker(message); } else if ( message.trim().length > environment.productConfig.CommonSetting.masstextLength ) { /** CASE : MASS TEXT */ this.sendMessageOfMassText(message); } else { /** CASE : Normal Text */ this.sendMessageOfNormal(message); } } /** Send Normal message */ sendMessageOfNormal(message: string) { this.store.dispatch( EventStore.send({ senderSeq: this.loginResSubject.value.userSeq, req: { roomSeq: this.roomInfoSubject.value.roomSeq, eventType: EventType.Character, sentMessage: message } }) ); } /** Send Sticker message */ async sendMessageOfSticker(message: string, isCheck: boolean = true) { if ( !!isCheck && !!message && message.trim().length > environment.productConfig.CommonSetting.masstextLength ) { const result = await this.dialogService.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { width: '360px', data: { title: this.translateService.instant('chat.errors.label'), message: this.translateService.instant( 'chat.errors.maxLengthOfMassText', { maxLength: environment.productConfig.CommonSetting.masstextLength } ) } }); return; } const stickerJson: StickerEventJson = { name: '스티커', file: this.selectedSticker.index, chat: !!message ? message.trim() : '' }; this.store.dispatch( EventStore.send({ senderSeq: this.loginResSubject.value.userSeq, req: { roomSeq: this.roomInfoSubject.value.roomSeq, eventType: EventType.Sticker, sentMessage: JSON.stringify(stickerJson) } }) ); this.isShowStickerSelector = false; this.setStickerHistory(this.selectedSticker); this.selectedSticker = null; } /** Send Masstext message */ sendMessageOfMassText(message: string) { this.store.dispatch( EventStore.sendMass({ senderSeq: this.loginResSubject.value.userSeq, req: { roomSeq: this.roomInfoSubject.value.roomSeq, eventType: EventType.MassText, // sentMessage: message.replace(/\n/gi, '\r\n') sentMessage: StringUtil.escapeHtml(message) } }) ); } /** Send Translation message */ sendMessageOfTranslate(message: string) { const destLocale = this.destLocale; const original = message; const roomSeq = this.roomInfoSubject.value.roomSeq; if (!!this.isTranslationProcess) { return; } this.isTranslationProcess = true; this.commonApiService .translationSave({ userSeq: this.loginResSubject.value.userSeq, deviceType: this.environmentsInfo.deviceType, token: this.loginResSubject.value.tokenString, roomSeq, original, srcLocale: '', destLocale } as TranslationSaveRequest) .pipe( take(1), map(res => { if (res.statusCode === StatusCode.Success) { let sentMessage = ''; let eventType = EventType.Translation; let previewObject: TranslationEventJson | MassTranslationEventJson; if (res.translationSeq > 0) { // Mass Text Translation previewObject = res; sentMessage = res.returnJson; eventType = EventType.MassTranslation; } else { // Normal Text Translation previewObject = { locale: destLocale, original, translation: res.translation, stickername: '', stickerfile: !!this.selectedSticker ? this.selectedSticker.index : '' }; sentMessage = JSON.stringify(previewObject); eventType = EventType.Translation; } if (!!this.translationPreview) { // preview this.translationPreviewInfo = { previewInfo: res, translationType: eventType }; } else { // direct send this.store.dispatch( EventStore.send({ senderSeq: this.loginResSubject.value.userSeq, req: { roomSeq, eventType, sentMessage } }) ); if (!!this.translationPreviewInfo) { this.translationPreviewInfo = null; } } if (!!this.selectedSticker) { this.isShowStickerSelector = false; this.setStickerHistory(this.selectedSticker); this.selectedSticker = null; } } else { this.isTranslationProcess = false; this.dialogService.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { width: '360px', data: { title: '', message: this.translateService.instant( 'chat.error.translateServerError' ) } }); this.logger.error('res', res); } }), catchError(error => { this.isTranslationProcess = false; this.dialogService.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { width: '360px', data: { title: '', message: this.translateService.instant( 'chat.error.translateServerError' ) } }); return of(this.logger.error('error', error)); }) ) .subscribe(() => { this.isTranslationProcess = false; }); } onClickReceiveAlarm() { this.store.dispatch( RoomStore.updateOnlyAlarm({ roomInfo: this.roomInfoSubject.value }) ); } /** MassText Detail View */ onMassDetail(value: number) { this.store.dispatch( ChatStore.selectedMassDetail({ massEventSeq: value }) ); } onMassTranslationDetail(params: { message: Info; contentsType: string; }) { this.commonApiService .transMassTalkDownload({ userSeq: this.loginResSubject.value.userSeq, deviceType: this.environmentsInfo.deviceType, token: this.loginResSubject.value.tokenString, eventTransSeq: params.message.sentMessageJson.translationSeq.toString() }) .pipe(take(1)) .subscribe(res => { let contents = ''; if (res.statusCode === StatusCode.Success) { contents = params.contentsType === 'T' ? res.translation : res.original; } else { contents = params.contentsType === 'T' ? params.message.sentMessageJson.translation : params.message.sentMessageJson.original; } this.dialogService.open( MassDetailComponent, { disableClose: false, width: '550px', data: { title: this.translateService.instant('chat.detailView'), contents } } ); }); } async onFileViewer(fileInfo: FileEventJson) { const result = await this.dialogService.open< FileViewerDialogComponent, FileViewerDialogData, FileViewerDialogResult >(FileViewerDialogComponent, { position: { top: '50px' }, maxWidth: '100vw', maxHeight: '100vh', height: 'calc(100% - 50px)', width: '100%', hasBackdrop: false, panelClass: 'app-dialog-full', data: { fileInfo, downloadUrl: this.sessionVerInfo.downloadUrl, deviceType: this.environmentsInfo.deviceType, token: this.loginResSubject.value.tokenString, userSeq: this.loginResSubject.value.userSeq } }); } /** File Save, Save As */ onSave(value: { fileInfo: FileEventJson; fileDownloadItem: FileDownloadItem; type: string; }) { this.logger.debug('fileSave', value); if (value.type === 'saveAs') { this.nativeService .selectSaveFilePath(value.fileInfo.fileName) .then(result => { if (!result) { return; } if (result.canceled) { this.snackBarService.open( this.translateService.instant('common.file.results.canceled'), this.translateService.instant('common.file.errors.label'), { duration: 1000 } ); } else { this.saveFile(value, result.filePath); } }) .catch(reason => { this.snackBarService.open( this.translateService.instant( 'common.file.errors.failToSpecifyPath' ), this.translateService.instant('common.file.errors.label') ); }); } else { this.saveFile(value); } } onFileDragEnter(items: DataTransferItemList) { this.clearView(); this.logger.debug('onFileDragEnter', items); } onFileDragOver() { this.logger.debug('onFileDragOver'); } onFileDragLeave() { this.logger.debug('onFileDragLeave'); } onExistNewMessage(info: Info) { let message = ''; const contents = StringUtil.convertFinalEventMessage( info.type, info.sentMessageJson || info.sentMessage ); if (!!contents) { const senderUser = this.userInfoListSubject.value.filter( user => user.seq === info.senderSeq ); if (!!senderUser && senderUser.length > 0) { message += `${senderUser[0].name} : `; } message += contents; this.snackBarPreviewEvent = this.snackBarService.open( message, this.translateService.instant('common.messages.confirm'), { // duration: 3000, verticalPosition: 'bottom', horizontalPosition: 'center', panelClass: ['chat-snackbar-class'] } ); this.snackBarPreviewEvent.onAction().subscribe(() => { this.chatMessages.initEventMore(); this.chatMessages.scrollToBottom(); this.snackBarPreviewEvent.dismiss(); }); } } saveFile( value: { fileInfo: FileEventJson; fileDownloadItem: FileDownloadItem; type: string; }, savePath?: string ) { this.commonApiService .fileTalkDownload({ userSeq: this.loginResSubject.value.userSeq, deviceType: this.environmentsInfo.deviceType, token: this.loginResSubject.value.tokenString, attachmentsSeq: value.fileInfo.attachmentSeq, fileDownloadItem: value.fileDownloadItem }) .pipe( take(1), map(async rawBlob => { const mimeType = MimeUtil.getMimeFromExtension( FileUtil.getExtension(value.fileInfo.fileName) ); const blob = rawBlob.slice(0, rawBlob.size, mimeType); FileUtil.fromBlobToBuffer(blob) .then(buffer => { /** download check */ this.fileProtocolService .downCheck({ seq: value.fileInfo.attachmentSeq }) .pipe(take(1)) .subscribe(); this.nativeService .saveFile(buffer, value.fileInfo.fileName, mimeType, savePath) .then(result => { if (!!result) { this.snackBarService.open( this.translateService.instant( 'common.file.results.savedToPath', { path: result } ), '', { duration: 3000, verticalPosition: 'bottom' } ); } else { this.snackBarService.openFromComponent< AlertSnackbarComponent, AlertSnackbarData >(AlertSnackbarComponent, { data: { html: this.translateService.instant( 'common.file.errors.failToSave' ), buttonText: this.translateService.instant( 'common.file.errors.label' ) } }); } }) .catch(reason => { this.snackBarService.openFromComponent< AlertSnackbarComponent, AlertSnackbarData >(AlertSnackbarComponent, { data: { html: this.translateService.instant( 'common.file.errors.failToSave' ), buttonText: this.translateService.instant( 'common.file.errors.label' ) } }); }); }) .catch(reason => { this.logger.error('download', reason); }); }), finalize(() => { setTimeout(() => { value.fileDownloadItem.downloadingProgress$ = undefined; }, 1000); }) ) .subscribe(); } async onFileSelected(fileUploadItems: FileUploadItem[]) { this.logger.debug('onFileSelected', fileUploadItems); this.clearView(); const info = { senderSeq: this.loginResSubject.value.userSeq, roomSeq: this.roomInfoSubject.value.roomSeq }; const allObservables: Observable[] = []; const fileAllowSize = !!this.sessionVerInfo && this.sessionVerInfo.fileAllowSize ? this.sessionVerInfo.fileAllowSize : environment.productConfig.CommonSetting.defaultFileAllowSize; if (fileAllowSize > 0) { if ( fileUploadItems.filter( fui => fui.file.size > fileAllowSize * 1024 * 1024 ).length ) { this.dialogService.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { width: '360px', data: { title: '', message: this.translateService.instant( 'common.file.errors.oversize', { maxSize: fileAllowSize } ) } }); if (!!this.fileUploadQueue) { this.fileUploadQueue.onUploadComplete(); } return; } } const checkExt = this.commonApiService.acceptableExtensionForFileTalk( fileUploadItems.map(fui => FileUtil.getExtension(fui.file.name)) ); if (!checkExt.accept) { if (!!this.fileUploadQueue) { this.fileUploadQueue.onUploadComplete(); } this.dialogService.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { data: { title: 'Alert', html: `${this.translateService.instant( 'common.file.errors.notSupporedType' )} ${ checkExt.reject.length > 0 ? '
(' + checkExt.reject.join(',') + ')' : '' }` } }); return; } for (const fileUploadItem of fileUploadItems) { let thumbnail: File; if ( -1 !== [ '3gp', 'avi', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'rv', 'ts', 'webm', 'wmv' ].indexOf(FileUtil.getExtension(fileUploadItem.file.name)) ) { thumbnail = await FileUtil.thumbnail(fileUploadItem.file); this.logger.debug('thumbnail', thumbnail); } const req: FileTalkSaveRequest = { userSeq: this.loginResSubject.value.userSeq, deviceType: this.environmentsInfo.deviceType, token: this.loginResSubject.value.tokenString, roomSeq: this.roomInfoSubject.value.roomSeq, file: fileUploadItem.file, fileName: fileUploadItem.file.name, thumb: thumbnail, fileUploadItem }; allObservables.push( this.commonApiService .fileTalkSave(req, this.sessionVerInfo.uploadUrl) .pipe( map(res => { if (!res) { return; } if (StatusCode.Success === res.statusCode) { return res; } else { throw res; } }) ) ); } forkJoin(allObservables) .pipe(take(1)) .subscribe( resList => { for (const res of resList) { this.store.dispatch( EventStore.send({ senderSeq: info.senderSeq, req: { roomSeq: info.roomSeq, eventType: EventType.File, sentMessage: res.returnJson } }) ); } }, error => { this.logger.debug('onFileSelected error', error); this.snackBarService.open( this.translateService.instant('common.file.errors.failToUpload'), this.translateService.instant('common.file.errors.label') ); if (!!this.fileUploadQueue) { this.fileUploadQueue.onUploadComplete(); } }, () => { this.fileUploadQueue.onUploadComplete(); } ); } onContextMenuMessage(params: { event: MouseEvent; message: Info; type?: string; }) { 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.loginResSubject.value, clicktype: params.type }; this.messageContextMenuTrigger.openMenu(); } async onClickMessageContextMenu( menuType: string, message: Info, clicktype?: string ) { switch (menuType) { case 'COPY': { switch (message.type) { case EventType.Character: { if ( this.clipboardService.copyFromContent( (message as Info).sentMessage ) ) { this.snackBarService.open( this.translateService.instant( 'common.clipboard.results.copied' ), '', { duration: 3000, verticalPosition: 'top', horizontalPosition: 'center' } ); } } break; case EventType.MassText: { this.commonApiService .massTalkDownload({ userSeq: this.loginResSubject.value.userSeq, deviceType: this.environmentsInfo.deviceType, token: this.loginResSubject.value.tokenString, eventMassSeq: message.seq }) .pipe(take(1)) .subscribe(res => { if (this.clipboardService.copyFromContent(res.content)) { this.snackBarService.open( this.translateService.instant( 'common.clipboard.results.copied' ), '', { duration: 3000, verticalPosition: 'top', horizontalPosition: 'center' } ); } }); } break; case EventType.Translation: { let trgtStr = ''; if (clicktype === 'translation') { // translation trgtStr = (message.sentMessageJson as TranslationEventJson) .translation; } else { // original trgtStr = (message.sentMessageJson as TranslationEventJson) .original; } if (this.clipboardService.copyFromContent(trgtStr)) { this.snackBarService.open( this.translateService.instant( 'common.clipboard.results.copied' ), '', { duration: 3000, verticalPosition: 'top', horizontalPosition: 'center' } ); } } break; case EventType.MassTranslation: { const sentMessageJson: MassTranslationEventJson = message.sentMessageJson as MassTranslationEventJson; this.commonApiService .transMassTalkDownload({ userSeq: this.loginResSubject.value.userSeq, deviceType: this.environmentsInfo.deviceType, token: this.loginResSubject.value.tokenString, eventTransSeq: sentMessageJson.translationSeq.toString() }) .pipe(take(1)) .subscribe(res => { let contents = ''; if (res.statusCode === StatusCode.Success) { contents = clicktype === 'translation' ? res.translation : res.original; } else { contents = clicktype === 'translation' ? sentMessageJson.translation : sentMessageJson.original; } if (this.clipboardService.copyFromContent(contents)) { this.snackBarService.open( this.translateService.instant( 'common.clipboard.results.copied' ), '', { duration: 3000, verticalPosition: 'top', horizontalPosition: 'center' } ); } }); } break; default: break; } } break; case 'REPLAY': { const result = await this.dialogService.open< CreateChatDialogComponent, CreateChatDialogData, CreateChatDialogResult >(CreateChatDialogComponent, { width: '600px', data: { type: UserSelectDialogType.MessageForward, title: this.translateService.instant('chat.forwardEventTo'), ignoreRoom: [this.roomInfoSubject.value] } }); 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.loginResSubject.value.userSeq, req: { roomSeq: '-999', eventType: message.type, sentMessage: message.sentMessage }, trgtUserSeqs: userSeqs, trgtRoomSeq: roomSeq }) ); } } } break; case 'REPLAY_TO_ME': { if (this.loginResSubject.value.talkWithMeBotSeq > -1) { this.store.dispatch( EventStore.forward({ senderSeq: this.loginResSubject.value.userSeq, req: { roomSeq: '-999', eventType: message.type, sentMessage: message.sentMessage }, trgtUserSeqs: [this.loginResSubject.value.talkWithMeBotSeq] }) ); } } break; case 'DELETE': { const result = await this.dialogService.open< ConfirmDialogComponent, ConfirmDialogData, ConfirmDialogResult >(ConfirmDialogComponent, { width: '400px', data: { title: this.translateService.instant('chat.removeEvent'), html: this.translateService.instant('chat.confirmRemoveEvent') } }); if (!!result && !!result.choice && result.choice) { this.store.dispatch( EventStore.del({ roomSeq: this.roomInfoSubject.value.roomSeq, eventSeq: message.seq }) ); } } break; case 'RECALL': { const result = await this.dialogService.open< ConfirmDialogComponent, ConfirmDialogData, ConfirmDialogResult >(ConfirmDialogComponent, { width: '400px', data: { title: this.translateService.instant('chat.recallEvent'), html: this.translateService.instant('chat.confirmRecallEvent') } }); if (!!result && !!result.choice && result.choice) { this.store.dispatch( EventStore.cancel({ roomSeq: this.roomInfoSubject.value.roomSeq, eventSeq: message.seq, deviceType: this.environmentsInfo.deviceType }) ); } } break; default: break; } } async onClickContextMenu(menuType: string) { switch (menuType) { case 'OPEN_ALBUM_LIST': { this.store.dispatch( ChatStore.selectedRightDrawer({ req: RightDrawer.AlbumBox }) ); } break; case 'OPEN_FILE_LIST': { this.store.dispatch( ChatStore.selectedRightDrawer({ req: RightDrawer.FileBox }) ); } break; case 'CHAT_SEARCH': { this.onShowToggleSearchArea(); } break; case 'OPEN_ROOM_USER': { this.store.dispatch( ChatStore.selectedRightDrawer({ req: RightDrawer.RoomUser }) ); } break; case 'ADD_MEMBER': { const result = await this.dialogService.open< CreateChatDialogComponent, CreateChatDialogData, CreateChatDialogResult >(CreateChatDialogComponent, { width: '600px', data: { type: UserSelectDialogType.EditChatMember, title: this.translateService.instant('chat.modifyRoomMember'), curRoomUser: this.userInfoListSubject.value.filter( user => user.seq !== this.loginResSubject.value.userSeq && user.isJoinRoom ) } }); if (!!result && !!result.choice && result.choice) { // include me here.. const userSeqs: number[] = this.userInfoListSubject.value .filter(userInfo => userInfo.isJoinRoom) .map(userInfo => userInfo.seq); if ( !!result.selectedUserList && result.selectedUserList.length > 0 ) { result.selectedUserList.forEach(user => { if (userSeqs.indexOf(user.seq) < 0) { userSeqs.push(user.seq); } }); } if (userSeqs.length > 0) { // include me const myUserSeq = this.loginResSubject.value.userSeq; if (!!myUserSeq && userSeqs.indexOf(myUserSeq) < 0) { userSeqs.push(myUserSeq); } this.store.dispatch( RoomStore.inviteOrOpen({ req: { divCd: 'Invite', userSeqs } }) ); } } } break; case 'ADD_GROUP': { const result = await this.dialogService.open< SelectGroupDialogComponent, SelectGroupDialogData, SelectGroupDialogResult >(SelectGroupDialogComponent, { width: '600px', data: { title: this.translateService.instant('chat.addMemberToGroup') } }); if (!!result && !!result.choice && result.choice) { if (!!result.group) { const oldGroup: GroupDetailData = result.group; const trgtUserSeq: number[] = []; result.group.userSeqs.map(seq => trgtUserSeq.push(seq)); this.userInfoListSubject.value .filter(v => v.isJoinRoom) .filter(v => result.group.userSeqs.indexOf(v.seq) < 0) .forEach(user => { trgtUserSeq.push(user.seq); }); this.store.dispatch( SyncStore.updateGroupMember({ oldGroup, trgtUserSeq }) ); } } } break; case 'EDIT_ROOM': { const result = await this.dialogService.open< EditChatRoomDialogComponent, EditChatRoomDialogData, EditChatRoomDialogResult >(EditChatRoomDialogComponent, { width: '600px', data: { title: this.translateService.instant('chat.settingsOfRoom'), roomInfo: this.roomInfoSubject.value } }); if (!!result && !!result.choice && result.choice) { const roomName: string = result.roomName; const roomNameChangeTarget: string = result.roomNameChangeTarget; const timeRoomInterval: number = result.timeRoomInterval; const roomInfo: RoomInfo = result.roomInfo; // 방제목 업데이트. this.store.dispatch( RoomStore.update({ req: { roomSeq: roomInfo.roomSeq, roomName, receiveAlarm: roomInfo.receiveAlarm, syncAll: roomNameChangeTarget.toUpperCase() === 'ALL' ? true : false } }) ); if ( roomInfo.isTimeRoom && timeRoomInterval > 0 && roomInfo.timeRoomInterval !== timeRoomInterval ) { this.store.dispatch( RoomStore.updateTimeRoomInterval({ roomSeq: roomInfo.roomSeq, timerInterval: timeRoomInterval }) ); } } } break; case 'CLOSE_ROOM': { this.store.dispatch(ChatStore.clearSelectedRoom()); } break; default: break; } } onClickOpenProfile(userSeq: number) { const roomType = this.roomInfoSubject.value.roomType; if ( roomType !== RoomType.Allim && roomType !== RoomType.Bot && roomType !== RoomType.Link && roomType !== RoomType.Allim_Elephant && roomType !== RoomType.Allim_TMS ) { this.openProfile.emit({ userSeq }); } } /** About Sticker */ onShowToggleStickerSelector() { this.isShowStickerSelector = !this.isShowStickerSelector; if (!this.isShowStickerSelector) { this.selectedSticker = null; } } onSelectedSticker(stickerInfo: StickerFilesInfo) { this.selectedSticker = stickerInfo; } setStickerHistory(sticker: StickerFilesInfo) { const history = this.localStorageService.get(KEY_STICKER_HISTORY); if (!!history && history.length > 0) { const stickers: string[] = []; [sticker.index, ...history.filter(hist => hist !== sticker.index)].map( (s, i) => { if (i < 10) { stickers.push(s); } } ); this.localStorageService.set(KEY_STICKER_HISTORY, stickers); } else { this.localStorageService.set(KEY_STICKER_HISTORY, [ sticker.index ]); } } getStickerHistory(): string[] { return this.localStorageService.get(KEY_STICKER_HISTORY); } /** About Chat Search */ onShowToggleSearchArea() { this.isShowSearchArea = !this.isShowSearchArea; if (!this.isShowSearchArea) { this.searchedListSubject.next([]); this.searchedFocusEvent = null; this.searchText = ''; } } onCloseSearchArea() { this.isShowSearchArea = false; this.searchedListSubject.next([]); this.searchedFocusEvent = null; this.searchText = ''; this.moreSearchProcessing = false; this.searchTotalCount = 0; this.searchCurrentIndex = 0; this.store.dispatch(EventStore.infoForSearchEnd({})); } onSearchChat(searchText: string, baseSeq?: number) { this.searchText = searchText; // CASE :: searching text after retrieve All event Infos. this.store.dispatch( EventStore.infoAll({ req: { roomSeq: this.roomInfoSubject.value.roomSeq, baseSeq: this.eventListSubject.value[0].seq, requestCount: environment.productConfig.CommonSetting.eventRequestDefaultCount * 2 }, infoList: undefined }) ); // this.doSearchTextInEvent(searchText); } doSearchTextInEvent(searchText: string, baseSeq?: number): void { this.searchedListSubject.next( this.eventListSubject.value.filter(event => { let contents = ''; if (event.type === EventType.Character) { contents = event.sentMessage; } else if ( event.type === EventType.Sticker && !!event.sentMessageJson ) { contents = (event.sentMessageJson as StickerEventJson).chat; } else if (event.type === EventType.File && !!event.sentMessageJson) { contents = (event.sentMessageJson as FileEventJson).fileName; } else if ( event.type === EventType.MassText && !!event.sentMessageJson ) { contents = (event.sentMessageJson as MassTextEventJson).content; } else if ( (event.type === EventType.Translation || event.type === EventType.MassTranslation) && !!event.sentMessageJson ) { contents = (event.sentMessageJson as TranslationEventJson).original; contents += (event.sentMessageJson as TranslationEventJson) .translation; } return contents.indexOf(searchText) > -1; }) ); if ( !!this.searchedListSubject.value && this.searchedListSubject.value.length > 0 ) { this.searchTotalCount = this.searchedListSubject.value.length; if (!!baseSeq && baseSeq > 0) { this.searchedListSubject.value.forEach((searched, index) => { if (searched.seq <= baseSeq) { this.searchCurrentIndex = index + 1; this.searchedFocusEvent = searched; } }); } else { this.searchCurrentIndex = this.searchedListSubject.value.length; this.searchedFocusEvent = this.searchedListSubject.value[ this.searchedListSubject.value.length - 1 ]; } this.store.dispatch(EventStore.infoForSearchEnd({})); this.chatMessages.gotoPosition(this.searchedFocusEvent.seq); } else { this.searchTotalCount = 0; this.searchCurrentIndex = 0; this.searchedFocusEvent = null; } } onPrevSearch() { this.searchedListSubject.value.forEach((event, index) => { if (event.seq === this.searchedFocusEvent.seq && index > 0) { this.searchCurrentIndex = this.searchCurrentIndex - 1; this.searchedFocusEvent = this.searchedListSubject.value[index - 1]; this.chatMessages.gotoPosition(this.searchedFocusEvent.seq); } }); } onNextSearch() { // let exist = false; // this.searchedList.forEach((event, index) => { // if ( // event.seq === this.searchedFocusEvent.seq && // index < this.searchedList.length - 2 && // !exist // ) { // exist = true; // this.searchCurrentIndex = this.searchCurrentIndex + 1; // this.searchedFocusEvent = this.searchedList[index + 1]; // this.goSearchPosition(this.searchedFocusEvent.seq); // } // }); if (this.searchCurrentIndex < this.searchedListSubject.value.length) { this.searchedFocusEvent = this.searchedListSubject.value[ this.searchCurrentIndex ]; this.searchCurrentIndex = this.searchCurrentIndex + 1; this.chatMessages.gotoPosition(this.searchedFocusEvent.seq); } } onSearchAndPrev() { if ( !!this.searchText && this.searchText.trim().length > 0 && this.eventRemainedSubject.value ) { this.moreSearchProcessing = true; this.chatMessages.storeScrollPosition(); // Case :: retrieve event infos step by step until include searchtext in event.. this.store.dispatch( EventStore.infoForSearch({ req: { roomSeq: this.roomInfoSubject.value.roomSeq, baseSeq: this.eventListSubject.value[0].seq, requestCount: environment.productConfig.CommonSetting.eventRequestDefaultCount }, searchText: this.searchText }) ); } } /** About Translation */ onShowToggleTranslation() { this.isShowTranslation = !this.isShowTranslation; if (!this.isShowTranslation) { } } onChangeTranslationSimpleView(value: boolean) { this.translationSimpleview = value; } onChangeTranslationPreView(value: boolean) { this.translationPreview = value; } onChangeDestLocale(destLocale: string) { this.destLocale = destLocale; } onCancelTranslation() { this.isTranslationProcess = false; this.translationPreviewInfo = null; } onSendTranslationMessage(params: { previewInfo: TranslationSaveResponse | null; translationType: EventType.Translation | EventType.MassTranslation; }) { let sentMessage = ''; if (params.translationType === EventType.MassTranslation) { // Mass Text Translation sentMessage = params.previewInfo.returnJson; } else { // Normal Text Translation sentMessage = JSON.stringify({ locale: params.previewInfo.destLocale, original: params.previewInfo.original, translation: params.previewInfo.translation, stickername: '', stickerfile: !!this.selectedSticker ? this.selectedSticker.index : '' }); } this.store.dispatch( EventStore.send({ senderSeq: this.loginResSubject.value.userSeq, req: { roomSeq: this.roomInfoSubject.value.roomSeq, eventType: params.translationType, sentMessage } }) ); this.isTranslationProcess = false; this.translationPreviewInfo = null; } onClipboardPaste(event: ClipboardEvent) { this.nativeService.readFromClipboard().then(async data => { if (!!data.image && !!data.text) { const result = await this.dialogService.open< ClipboardDialogComponent, ClipboardDialogData, ClipboardDialogResult >(ClipboardDialogComponent, { width: '800px', maxWidth: '800px', height: '800px', minHeight: '800px', disableClose: false, data: { content: data } }); if (result.selected.text) { this.onSendMessage(data.text); } if (result.selected.image) { const fileUploadItems = FileUploadItem.fromDataUrls( 'clipboard', data.imageDataUrl ); this.onFileSelected(fileUploadItems); } } else if (!!data.image && !data.text) { const fileUploadItems = FileUploadItem.fromDataUrls( 'clipboard', data.imageDataUrl ); this.onFileSelected(fileUploadItems); } else { const v = this.chatForm.replyInput.nativeElement.value; const selectionStart = this.chatForm.replyInput.nativeElement .selectionStart; const selectionEnd = this.chatForm.replyInput.nativeElement .selectionEnd; const start = v.substr(0, selectionStart); const end = v.substr(selectionEnd); this.chatForm.replyInput.nativeElement.value = `${start}${data.text}${end}`; event.preventDefault(); } }); } }