import { Component, OnInit, Output, EventEmitter, ViewChild, AfterViewInit, ChangeDetectorRef, OnDestroy, ElementRef } from '@angular/core'; import { ucapAnimations, DialogService } from '@ucap-webmessenger/ui'; import { NGXLogger } from 'ngx-logger'; import moment from 'moment'; import { FileUtil } from '@ucap-webmessenger/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { ContentType, CategoryType, MessageType } 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'; const ATTR_FILE = 'UCAP_ATTR_FILE'; interface Content { contentType: ContentType; content: string | File; } 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; } @Component({ selector: 'ucap-message-write', templateUrl: './write.component.html', styleUrls: ['./write.component.scss'], animations: ucapAnimations }) export class WriteComponent implements OnInit, OnDestroy, AfterViewInit { @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; attachmentList: File[]; fileUploadItem: FileUploadItem; receiverList: UserInfo[] = []; constructor( private formBuilder: FormBuilder, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, private logger: NGXLogger ) {} ngOnInit() { this.messageWriteForm = this.formBuilder.group({ receiverList: ['', [Validators.required]], title: ['', [Validators.required]] }); } ngOnDestroy(): void {} ngAfterViewInit(): void {} onClickImage() { this.fileInput.nativeElement.click(); const self = this; this.fileInput.nativeElement.onchange = async () => { const fileList: FileList = self.fileInput.nativeElement.files; 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 = ''; }; } onClickAttachment() { this.fileInput.nativeElement.click(); const self = this; this.fileInput.nativeElement.onchange = () => { const fileList: FileList = this.fileInput.nativeElement.files; 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 = ''; }; } onPasteEditor(event: ClipboardEvent) { const text = document.createTextNode( event.clipboardData.getData('text/plain') ); this.insertNode(text, true); return false; } 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 result = await this.dialogService.open< ScheduleSendDialogComponent, ScheduleSendDialogData, ScheduleSendDialogResult >(ScheduleSendDialogComponent, { width: '600px', height: '600px', disableClose: true, data: {} }); if (!!result && !!result.scheduleSendDate) { this.sendMessage(result.scheduleSendDate); } } private sendMessage(reservationDate?: moment.Moment) { const contentList: Content[] = this.generateContent(); if (!contentList || 0 === contentList.length) { return; } const listOrder: ContentType[] = []; const textContent: { text: string }[] = []; const recvUserList: { userSeq: number; userName: string }[] = []; const files: File[] = []; const title = this.messageWriteForm.get('title').value; contentList.forEach(v => { listOrder.push(v.contentType); switch (v.contentType) { case ContentType.Text: textContent.push({ text: v.content as string }); break; case ContentType.Image: case ContentType.AttachFile: files.push(v.content as File); break; default: break; } }); this.receiverList.forEach(v => { recvUserList.push({ userSeq: v.seq, userName: v.name }); }); this.fileUploadItem = FileUploadItem.from(); this.send.emit({ 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 }); } private generateContent(): Content[] { const contentList: Content[] = []; this.editor.nativeElement.childNodes.forEach((v, k) => { this.parseNode(contentList, v); }); if (!!this.attachmentList && 0 < this.attachmentList.length) { this.attachmentList.forEach(v => { contentList.push({ contentType: ContentType.AttachFile, content: v }); }); } 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]); } 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 ) { 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 }); } break; default: contentList.push({ contentType, content }); 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); } }