import { Component, OnInit, Output, EventEmitter, ViewChild, AfterViewInit, ChangeDetectorRef, OnDestroy, ElementRef, Input } from '@angular/core'; import { ucapAnimations, DialogService, AlertDialogResult, AlertDialogComponent, AlertDialogData } from '@ucap-webmessenger/ui'; import { NGXLogger } from 'ngx-logger'; import moment from 'moment'; import { FileUtil } from '@ucap-webmessenger/core'; import { FormGroup, FormBuilder } from '@angular/forms'; import { ContentType, CategoryType, MessageType, DetailResponse, DetailReceiver, DetailContent } from '@ucap-webmessenger/api-message'; import { FileUploadItem } from '@ucap-webmessenger/api'; import { UserInfo } from '@ucap-webmessenger/protocol-sync'; import { ScheduleSendDialogComponent, ScheduleSendDialogData, ScheduleSendDialogResult } from '../dialogs/schedule-send.dialog.component'; import { RoleCode } from '@ucap-webmessenger/protocol-authentication'; import { EmployeeType } from '@ucap-webmessenger/protocol-room'; import { TranslateService } from '@ngx-translate/core'; import { CommonApiService } from '@ucap-webmessenger/api-common'; const ATTR_FILE = 'UCAP_ATTR_FILE'; interface Content { contentType: ContentType; content: string | File; resSeq: number; } export interface Message { category: CategoryType; type: MessageType; title: string; listOrder: ContentType[]; textContent: { text: string }[]; recvUserList: { userSeq: number; userName: string }[]; files?: File[]; fileUploadItem?: FileUploadItem; reservationTime?: string; smsYn?: boolean; } export interface MessageModify extends Message { resSeqList: number[]; reservationTime: string; msgId: number; } @Component({ selector: 'ucap-message-write', templateUrl: './write.component.html', styleUrls: ['./write.component.scss'], animations: ucapAnimations }) export class WriteComponent implements OnInit, OnDestroy, AfterViewInit { @Input() curReceiverList: UserInfo[] = []; @Input() detail?: DetailResponse; @Input() detailContents?: string; @Input() isModify = false; @Input() fileAllowSize: number; @Output() send = new EventEmitter(); @Output() cancel = new EventEmitter(); @Output() selectReceiver = new EventEmitter(); @ViewChild('editor', { static: true }) editor: ElementRef; @ViewChild('fileInput', { static: true }) fileInput: ElementRef; messageWriteForm: FormGroup; oldAttachmentList: DetailContent[] = []; attachmentList: File[]; fileUploadItem: FileUploadItem; receiverList: UserInfo[] = []; contentLength = 0; constructor( private formBuilder: FormBuilder, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, private translateService: TranslateService, private commonApiService: CommonApiService, private logger: NGXLogger ) {} ngOnInit() { this.messageWriteForm = this.formBuilder.group({ title: ['', []] }); if (!!this.curReceiverList && this.curReceiverList.length > 0) { this.receiverList = this.curReceiverList; } else if (!!this.detail && this.detail.recvList.length > 0) { this.receiverList = this.detail.recvList.map(recvInfo => this.convertDetailReceivertoUserInfo(recvInfo) ); } if (this.isModify) { if (!!this.detail.msgInfo.title) { this.messageWriteForm.setValue({ title: this.detail.msgInfo.title }); } if (!!this.detailContents) { this.editor.nativeElement.innerHTML = this.detailContents; this.onInputEditor(); } this.detail.contents.forEach(content => { if (content.resType === ContentType.AttachFile) { this.oldAttachmentList.push(content); } }); } } ngOnDestroy(): void {} ngAfterViewInit(): void {} onClickDeleteOldAttachment(oldAttachment: DetailContent) { this.oldAttachmentList = this.oldAttachmentList.filter( detailContent => detailContent.resSeq !== oldAttachment.resSeq ); } onClickImage() { this.fileInput.nativeElement.setAttribute('accept', 'image/*'); this.fileInput.nativeElement.click(); const self = this; this.fileInput.nativeElement.onchange = async () => { const fileList: FileList = self.fileInput.nativeElement.files; if (!this.validUploadFile(fileList)) { return; } for (let i = 0; i < fileList.length; i++) { const file = fileList.item(i); const dataUrl = await FileUtil.fromBlobToDataUrl(file); const img = document.createElement('img'); img.src = dataUrl as string; img[ATTR_FILE] = file; self.insertNode(img); } self.fileInput.nativeElement.value = ''; this.fileInput.nativeElement.onchange = undefined; }; } onClickAttachment() { this.fileInput.nativeElement.removeAttribute('accept'); this.fileInput.nativeElement.click(); const self = this; this.fileInput.nativeElement.onchange = () => { const fileList: FileList = this.fileInput.nativeElement.files; if (!this.validUploadFile(fileList)) { return; } if (!self.attachmentList) { self.attachmentList = []; } for (let i = 0; i < fileList.length; i++) { const file = fileList.item(i); self.attachmentList.push(file); } self.changeDetectorRef.detectChanges(); self.fileInput.nativeElement.value = ''; }; } validUploadFile(fileList: FileList): boolean { let valid = true; if (this.fileAllowSize > 0) { for (let i = 0; i < fileList.length; i++) { const file = fileList.item(i); if (file.size > this.fileAllowSize * 1024 * 1024) { valid = false; break; } } if (!valid) { this.dialogService.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { width: '360px', data: { title: '', message: this.translateService.instant( 'common.file.errors.oversize', { maxSize: this.fileAllowSize } ) } }); return valid; } } const checkExt = this.commonApiService.acceptableExtensionForFileTalk( FileUtil.getExtensions(fileList) ); if (!checkExt.accept) { 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 valid; } return valid; } onPasteEditor(event: ClipboardEvent) { const text = document.createTextNode( event.clipboardData.getData('text/plain') ); this.insertNode(text, true); this.checkContentLength(); return false; } onInputEditor() { this.checkContentLength(); } onRemovedReceiver(receiver: UserInfo) { const index = this.receiverList.indexOf(receiver); if (index >= 0) { this.receiverList.splice(index, 1); } } onClickReceiverList() { this.selectReceiver.emit(this.receiverList); } onClickSend() { this.sendMessage(); } onClickCancel() { this.cancel.emit(); } async onClickSendSchedule() { const reservationDate = this.isModify ? moment(this.detail.msgInfo.reservationTime) : undefined; const result = await this.dialogService.open< ScheduleSendDialogComponent, ScheduleSendDialogData, ScheduleSendDialogResult >(ScheduleSendDialogComponent, { width: '600px', height: '600px', disableClose: true, data: { reservationDate } }); if (!!result && !!result.scheduleSendDate) { this.sendMessage(result.scheduleSendDate); } } private checkContentLength() { const result = this.parseContent(); if (!result) { return; } const { textContent } = result; if (!textContent || 0 === textContent.length) { this.contentLength = 0; } else { let len = 0; textContent.forEach(c => { len += c.text.length; }); this.contentLength = len; } } private sendMessage(reservationDate?: moment.Moment) { const result = this.parseContent(); if (!result) { return; } const { listOrder, textContent, files, resSeqList } = result; if (!listOrder || 0 === listOrder.length) { return; } const recvUserList: { userSeq: number; userName: string }[] = []; const title = this.messageWriteForm.get('title').value; this.receiverList.forEach(v => { recvUserList.push({ userSeq: v.seq, userName: v.name }); }); this.fileUploadItem = FileUploadItem.from(); if (!this.isModify) { const message: Message = { category: CategoryType.General, type: !!reservationDate ? MessageType.Reservation : MessageType.Send, title, listOrder, textContent, recvUserList, reservationTime: !!reservationDate ? `${reservationDate.format('YYYY-MM-DD HH:mm')}:00` : undefined, files, fileUploadItem: this.fileUploadItem }; this.send.emit(message); } else { const message: MessageModify = { category: CategoryType.General, type: !!reservationDate ? MessageType.Reservation : MessageType.Send, msgId: this.detail.msgInfo.msgId, title, listOrder, resSeqList, textContent, recvUserList, reservationTime: !!reservationDate ? `${reservationDate.format('YYYY-MM-DD HH:mm')}:00` : undefined, files, fileUploadItem: this.fileUploadItem }; this.send.emit(message); } } private parseContent(): | { listOrder: ContentType[]; textContent: { text: string }[]; files: File[]; resSeqList: number[]; } | undefined { const contentList: Content[] = this.generateContent(); if (!contentList || 0 === contentList.length) { return; } const listOrder: ContentType[] = []; const textContent: { text: string }[] = []; const files: File[] = []; const resSeqList: number[] = []; contentList.forEach(v => { listOrder.push(v.contentType); resSeqList.push(v.resSeq); switch (v.contentType) { case ContentType.Text: let content = v.content as string; if ('\n' === content.charAt(content.length - 1)) { content = content.substring(0, content.length - 1); } textContent.push({ text: content }); break; case ContentType.Image: case ContentType.AttachFile: { if (v.resSeq === 0) { files.push(v.content as File); } } break; default: break; } }); return { listOrder, textContent, files, resSeqList }; } private generateContent(): Content[] { const contentList: Content[] = []; this.editor.nativeElement.childNodes.forEach((v, k) => { this.parseNode(contentList, v); }); if (!!this.oldAttachmentList && 0 < this.oldAttachmentList.length) { this.oldAttachmentList.forEach(v => { contentList.push({ contentType: ContentType.AttachFile, content: v.resContent, resSeq: v.resSeq }); }); } if (!!this.attachmentList && 0 < this.attachmentList.length) { this.attachmentList.forEach(v => { contentList.push({ contentType: ContentType.AttachFile, content: v, resSeq: 0 }); }); } return contentList; } private parseNode(contentList: Content[], node: ChildNode) { switch (node.nodeType) { case Node.ELEMENT_NODE: { if (0 < node.childNodes.length) { let prevNode: ChildNode; node.childNodes.forEach(v => { if ( !!prevNode && 'IMG' === prevNode.nodeName && 'BR' === v.nodeName ) { prevNode = v; return; } prevNode = v; this.parseNode(contentList, v); }); } else { if ('IMG' === node.nodeName) { const img: HTMLImageElement = node as HTMLImageElement; this.appendNode( contentList, ContentType.Image, img[ATTR_FILE], !!img.getAttribute('id') ? Number(img.getAttribute('id')) : 0 ); } else if ('BR' === node.nodeName) { this.appendNode(contentList, ContentType.Text, `\n`); } else { } } } break; case Node.TEXT_NODE: this.appendNode(contentList, ContentType.Text, node.textContent); break; default: break; } } private appendNode( contentList: Content[], contentType: ContentType, content: string | File, resSeq: number = 0 ) { const prevContent = contentList[contentList.length - 1]; switch (contentType) { case ContentType.Text: if (!!prevContent && ContentType.Text === prevContent.contentType) { prevContent.content = `${prevContent.content}${content}`; } else { contentList.push({ contentType: ContentType.Text, content, resSeq }); } break; default: contentList.push({ contentType, content, resSeq }); break; } } private insertNode(node: Node, removeSelected: boolean = false) { const selection: Selection = document.getSelection(); const range: Range = selection.getRangeAt(0); if (removeSelected) { selection.empty(); } range.insertNode(node); } private convertDetailReceivertoUserInfo(base: DetailReceiver): UserInfo { return { seq: base.userSeq, name: base.userName, profileImageFile: '', grade: '', intro: '', companyCode: '', hpNumber: '', lineNumber: '', email: '', isMobile: false, deptName: '', isFavorit: false, isBuddy: false, isActive: false, roleCd: RoleCode.Self, employeeNum: '', madn: '', hardSadn: '', fmcSadn: '', nameEn: '', nameCn: '', gradeEn: '', gradeCn: '', deptNameEn: '', deptNameCn: '', isPrivacyAgree: false, isValidLogin: false, employeeType: EmployeeType.Regular, nickName: '', order: '' }; } }