write of message is implemented

This commit is contained in:
병준 박 2019-12-04 17:58:59 +09:00
parent 8dbd747f16
commit 4f6b1dd6ca
10 changed files with 296 additions and 81 deletions

View File

@ -23,14 +23,14 @@ export interface SendRequest extends APIRequest {
title: string; title: string;
titleYn: boolean; titleYn: boolean;
listOrder: ContentType[]; listOrder: ContentType[];
reservationTime: string; reservationTime?: string;
smsYn: boolean; smsYn?: boolean;
textContent: { text: string }[]; textContent: { text: string }[];
recvUserList: { userSeq: number; userName: string }[]; recvUserList: { userSeq: number; userName: string }[];
files: File[]; files?: File[];
fileUploadItem: FileUploadItem; fileUploadItem?: FileUploadItem;
} }
export interface SendResponse extends MessageAPIResponse {} export interface SendResponse extends MessageAPIResponse {}
@ -72,26 +72,10 @@ export const encodeSend: APIFormDataEncoder<SendRequest> = (
extraParams.listOrder = s; extraParams.listOrder = s;
} }
if (!!req.textContent) { if (!!req.textContent) {
let s = ''; extraParams.textContent = JSON.stringify(req.textContent);
req.textContent.forEach(v => {
if ('' !== s) {
s = s + `,${JSON.stringify(v)}`;
} else {
s = s + `${JSON.stringify(v)}`;
}
});
extraParams.textContent = `[${s}]`;
} }
if (!!req.recvUserList) { if (!!req.recvUserList) {
let s = ''; extraParams.recvUserList = JSON.stringify(req.recvUserList);
req.recvUserList.forEach(v => {
if ('' !== s) {
s = s + `,${JSON.stringify(v)}`;
} else {
s = s + `${JSON.stringify(v)}`;
}
});
extraParams.recvUserList = `[${s}]`;
} }
return ParameterUtil.encodeFormData(sendEncodeMap, req, extraParams); return ParameterUtil.encodeFormData(sendEncodeMap, req, extraParams);
}; };

View File

@ -9,7 +9,7 @@ export class FileUploadItem {
private uploadingProgress: Subject<number>; private uploadingProgress: Subject<number>;
private uploadStartTime: number; private uploadStartTime: number;
private constructor(file: File) { private constructor(file?: File) {
this.file = file; this.file = file;
} }
@ -24,6 +24,10 @@ export class FileUploadItem {
return fileItems; return fileItems;
} }
static from(): FileUploadItem {
return new FileUploadItem();
}
uploadStart(): Subject<number> { uploadStart(): Subject<number> {
this.uploadStartTime = new Date().getTime(); this.uploadStartTime = new Date().getTime();
this.uploadingProgress = new Subject<number>(); this.uploadingProgress = new Subject<number>();

View File

@ -56,6 +56,10 @@ export class ParameterUtil {
if (!!v) { if (!!v) {
if (v instanceof File) { if (v instanceof File) {
formData.append(parameterMap[key], v, v.name); formData.append(parameterMap[key], v, v.name);
} else if (Array.isArray(v)) {
v.forEach(f => {
formData.append(parameterMap[key], f, f.name);
});
} else { } else {
formData.append(parameterMap[key], v); formData.append(parameterMap[key], v);
} }

View File

@ -44,6 +44,7 @@ import {
MessageWriteDialogResult, MessageWriteDialogResult,
MessageWriteDialogData MessageWriteDialogData
} from '../dialogs/message/message-write.dialog.component'; } from '../dialogs/message/message-write.dialog.component';
import { EnvironmentsInfo, KEY_ENVIRONMENTS_INFO } from '@app/types';
export enum MainMenu { export enum MainMenu {
Group = 'GROUP', Group = 'GROUP',
@ -85,6 +86,7 @@ export class LeftSideComponent implements OnInit, OnDestroy {
MainMenu = MainMenu; MainMenu = MainMenu;
sessionVerinfo: VersionInfo2Response; sessionVerinfo: VersionInfo2Response;
environmentsInfo: EnvironmentsInfo;
loginRes: LoginResponse; loginRes: LoginResponse;
loginResSubscription: Subscription; loginResSubscription: Subscription;
@ -98,6 +100,10 @@ export class LeftSideComponent implements OnInit, OnDestroy {
this.sessionVerinfo = this.sessionStorageService.get<VersionInfo2Response>( this.sessionVerinfo = this.sessionStorageService.get<VersionInfo2Response>(
KEY_VER_INFO KEY_VER_INFO
); );
this.environmentsInfo = this.sessionStorageService.get<EnvironmentsInfo>(
KEY_ENVIRONMENTS_INFO
);
} }
ngOnInit() { ngOnInit() {
@ -216,7 +222,8 @@ export class LeftSideComponent implements OnInit, OnDestroy {
width: '600px', width: '600px',
height: '600px', height: '600px',
data: { data: {
loginRes: this.loginRes loginRes: this.loginRes,
environmentsInfo: this.environmentsInfo
} }
}); });

View File

@ -5,6 +5,10 @@
</mat-card-title> </mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<ucap-message-write #messageWrite></ucap-message-write> <ucap-message-write
#messageWrite
(send)="onSend($event)"
(selectReceiver)="onSelectReceiver($event)"
></ucap-message-write>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

View File

@ -12,10 +12,22 @@ import {
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { MessageStatusCode } from '@ucap-webmessenger/api'; import { MessageStatusCode } from '@ucap-webmessenger/api';
import { WriteComponent as UCapMessageWriteComponent } from '@ucap-webmessenger/ui-message'; import {
WriteComponent as UCapMessageWriteComponent,
Message
} from '@ucap-webmessenger/ui-message';
import { UserInfo } from '@ucap-webmessenger/protocol-sync';
import {
CreateChatDialogComponent,
CreateChatDialogData,
CreateChatDialogResult
} from '../chat/create-chat.dialog.component';
import { UserSelectDialogType, EnvironmentsInfo } from '@app/types';
import { take } from 'rxjs/operators';
export interface MessageWriteDialogData { export interface MessageWriteDialogData {
loginRes: LoginResponse; loginRes: LoginResponse;
environmentsInfo: EnvironmentsInfo;
detail?: DetailResponse; detail?: DetailResponse;
} }
@ -49,15 +61,52 @@ export class MessageWriteDialogComponent implements OnInit {
ngOnInit(): void {} ngOnInit(): void {}
async onSelectReceiver(receiverList: UserInfo[]) {
const result = await this.dialogService.open<
CreateChatDialogComponent,
CreateChatDialogData,
CreateChatDialogResult
>(CreateChatDialogComponent, {
width: '600px',
data: {
type: UserSelectDialogType.EditChatMember,
title: '쪽지 수신자 선택',
curRoomUser: receiverList
}
});
if (!!result && !!result.choice && result.choice) {
while (receiverList.length) {
receiverList.pop();
}
result.selectedUserList.forEach(v => {
receiverList.push(v as UserInfo);
});
}
}
onSend(message: Message) {
this.messageApiService
.sendMessage({
...message,
userSeq: this.data.loginRes.userInfo.seq,
userName: this.data.loginRes.userInfo.name,
deviceType: this.data.environmentsInfo.deviceType,
tokenKey: this.data.loginRes.tokenString,
titleYn: '' !== message.title ? true : false
})
.pipe(take(1))
.subscribe(
res => {
this.logger.debug('onSend', res);
},
error => {
this.logger.debug('onSend', error);
}
);
}
getBtnValid() { getBtnValid() {
return true; return true;
} }
onClickChoice(choice: boolean): void {
this.dialogRef.close();
}
onClickTest() {
this.messageWrite.printEditor();
}
} }

View File

@ -1,19 +1,5 @@
<mat-card class="mat-elevation-z0 ucap-message-write"> <mat-card class="mat-elevation-z0 ucap-message-write">
<form name="messageWriteForm" [formGroup]="messageWriteForm" novalidate> <form name="messageWriteForm" [formGroup]="messageWriteForm" novalidate>
<mat-form-field class="message-receiver-list">
<mat-chip-list #chipList aria-label="Fruit selection">
<mat-chip *ngFor="let receiver of receiverList">
{{ receiver }}
<span matChipRemove class="mdi mdi-close"></span>
</mat-chip>
<input
matInput
placeholder="수신자"
formControlName="receiverList"
[matChipInputFor]="chipList"
/>
</mat-chip-list>
</mat-form-field>
<mat-form-field class="message-title"> <mat-form-field class="message-title">
<input matInput formControlName="title" placeholder="제목" /> <input matInput formControlName="title" placeholder="제목" />
</mat-form-field> </mat-form-field>
@ -32,23 +18,68 @@
</mat-list-item> </mat-list-item>
</mat-list> </mat-list>
</mat-card-content> </mat-card-content>
<mat-form-field class="message-receiver-list">
<mat-chip-list #chipList aria-label="receiver selection">
<mat-chip
*ngFor="let receiver of receiverList"
removable="true"
(removed)="onRemovedReceiver(receiver)"
>
{{ receiver.name }}
<span matChipRemove class="mdi mdi-close"></span>
</mat-chip>
<input
[matChipInputFor]="chipList"
placeholder="수신자"
readonly
(click)="onClickReceiverList()"
/>
</mat-chip-list>
</mat-form-field>
<mat-card-actions> <mat-card-actions>
<button mat-icon-button aria-label="이미지" (click)="onClickImage()"> <div class="editor-tools">
<span class="mdi mdi-camera mdi-24px"></span> <button mat-icon-button aria-label="이미지" (click)="onClickImage()">
</button> <span class="mdi mdi-camera mdi-24px"></span>
<button </button>
mat-icon-button <button
aria-label="첨부파일" mat-icon-button
(click)="onClickAttachment()" aria-label="첨부파일"
> (click)="onClickAttachment()"
<span class="mdi mdi-attachment mdi-rotate-90 mdi-24px"></span> >
</button> <span class="mdi mdi-attachment mdi-rotate-90 mdi-24px"></span>
<button mat-stroked-button (click)="onClickCancel()" class="mat-primary"> </button>
취소 </div>
</button> <div class="editor-actions-spacer"></div>
<button mat-flat-button (click)="onClickSend()" class="mat-primary"> <div>
보내기 <mat-form-field>
</button> <input matInput [matDatepicker]="picker" placeholder="발송일자" />
<mat-datepicker-toggle
matSuffix
[for]="picker"
></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
<div class="editor-actions-spacer"></div>
<div class="editor-actions">
<button
mat-stroked-button
(click)="onClickCancel()"
class="mat-primary"
>
취소
</button>
<button
mat-flat-button
(click)="onClickSend()"
class="mat-primary"
style="margin-left: 3px;"
>
보내기
</button>
</div>
</mat-card-actions> </mat-card-actions>
</form> </form>
</mat-card> </mat-card>

View File

@ -20,5 +20,36 @@
height: 100%; height: 100%;
min-height: 270px; min-height: 270px;
} }
[contenteditable] {
transition: padding 0.3s ease-in-out;
}
[contenteditable]:hover,
[contenteditable]:focus {
padding: 0.25em;
}
[contenteditable]:hover {
background: #fafafa;
}
[contenteditable]:focus {
background: #efefef;
}
}
mat-card-actions {
display: flex;
width: 100%;
.editor-tools {
padding-left: 0px;
}
.editor-actions-spacer {
flex: 1 1 auto;
}
.editor-actions {
}
} }
} }

View File

@ -1,12 +1,9 @@
import { import {
Component, Component,
OnInit, OnInit,
Input,
Output, Output,
EventEmitter, EventEmitter,
ViewChild, ViewChild,
ContentChild,
TemplateRef,
AfterViewInit, AfterViewInit,
ChangeDetectorRef, ChangeDetectorRef,
OnDestroy, OnDestroy,
@ -18,10 +15,32 @@ import { ucapAnimations } from '@ucap-webmessenger/ui';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { FileUtil } from '@ucap-webmessenger/core'; import { FileUtil } from '@ucap-webmessenger/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 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';
const ATTR_FILE = 'UCAP_ATTR_FILE';
interface Content { interface Content {
contentType: 'text' | 'image' | 'attachment'; contentType: ContentType;
content: string; 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({ @Component({
@ -31,6 +50,12 @@ interface Content {
animations: ucapAnimations animations: ucapAnimations
}) })
export class WriteComponent implements OnInit, OnDestroy, AfterViewInit { export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
@Output()
send = new EventEmitter<Message>();
@Output()
selectReceiver = new EventEmitter<UserInfo[]>();
@ViewChild('editor', { static: true }) @ViewChild('editor', { static: true })
editor: ElementRef<HTMLDivElement>; editor: ElementRef<HTMLDivElement>;
@ -38,8 +63,9 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
fileInput: ElementRef<HTMLInputElement>; fileInput: ElementRef<HTMLInputElement>;
messageWriteForm: FormGroup; messageWriteForm: FormGroup;
receiverList: string[] = ['이진호', '강희경', '이유진'];
attachmentList: File[]; attachmentList: File[];
fileUploadItem: FileUploadItem;
receiverList: UserInfo[] = [];
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
@ -70,6 +96,7 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
const dataUrl = await FileUtil.fromBlobToDataUrl(file); const dataUrl = await FileUtil.fromBlobToDataUrl(file);
const img = document.createElement('img'); const img = document.createElement('img');
img.src = dataUrl as string; img.src = dataUrl as string;
img[ATTR_FILE] = file;
self.insertNode(img); self.insertNode(img);
} }
@ -107,17 +134,85 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
return false; return false;
} }
onClickSend() {} onRemovedReceiver(receiver: UserInfo) {
const index = this.receiverList.indexOf(receiver);
if (index >= 0) {
this.receiverList.splice(index, 1);
}
}
onClickReceiverList() {
this.selectReceiver.emit(this.receiverList);
}
onClickSend() {
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: MessageType.Send,
title,
listOrder,
textContent,
recvUserList,
files,
fileUploadItem: this.fileUploadItem
});
}
onClickCancel() {} onClickCancel() {}
printEditor(): void { private generateContent(): Content[] {
const contentList: Content[] = []; const contentList: Content[] = [];
this.editor.nativeElement.childNodes.forEach((v, k) => { this.editor.nativeElement.childNodes.forEach((v, k) => {
this.parseNode(contentList, v); this.parseNode(contentList, v);
}); });
this.logger.debug('printEditor', contentList); 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) { private parseNode(contentList: Content[], node: ChildNode) {
@ -140,16 +235,17 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
}); });
} else { } else {
if ('IMG' === node.nodeName) { if ('IMG' === node.nodeName) {
this.appendNode(contentList, 'image', node.textContent); const img: HTMLImageElement = node as HTMLImageElement;
this.appendNode(contentList, ContentType.Image, img[ATTR_FILE]);
} else if ('BR' === node.nodeName) { } else if ('BR' === node.nodeName) {
this.appendNode(contentList, 'text', `\n`); this.appendNode(contentList, ContentType.Text, `\n`);
} else { } else {
} }
} }
} }
break; break;
case Node.TEXT_NODE: case Node.TEXT_NODE:
this.appendNode(contentList, 'text', node.textContent); this.appendNode(contentList, ContentType.Text, node.textContent);
break; break;
default: default:
@ -159,17 +255,17 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
private appendNode( private appendNode(
contentList: Content[], contentList: Content[],
contentType: 'text' | 'image' | 'attachment', contentType: ContentType,
content: string content: string | File
) { ) {
const prevContent = contentList[contentList.length - 1]; const prevContent = contentList[contentList.length - 1];
switch (contentType) { switch (contentType) {
case 'text': case ContentType.Text:
if (!!prevContent && 'text' === prevContent.contentType) { if (!!prevContent && ContentType.Text === prevContent.contentType) {
prevContent.content = `${prevContent.content}${content}`; prevContent.content = `${prevContent.content}${content}`;
} else { } else {
contentList.push({ contentList.push({
contentType: 'text', contentType: ContentType.Text,
content content
}); });
} }

View File

@ -9,6 +9,7 @@ import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatChipsModule } from '@angular/material/chips'; import { MatChipsModule } from '@angular/material/chips';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatGridListModule } from '@angular/material/grid-list'; import { MatGridListModule } from '@angular/material/grid-list';
@ -16,6 +17,8 @@ import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list'; import { MatListModule } from '@angular/material/list';
import { MatMomentDateModule } from '@angular/material-moment-adapter';
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
import { WriteComponent } from './components/write.component'; import { WriteComponent } from './components/write.component';
@ -36,12 +39,14 @@ const SERVICES = [];
MatButtonModule, MatButtonModule,
MatCardModule, MatCardModule,
MatChipsModule, MatChipsModule,
MatDatepickerModule,
MatDividerModule, MatDividerModule,
MatFormFieldModule, MatFormFieldModule,
MatGridListModule, MatGridListModule,
MatIconModule, MatIconModule,
MatInputModule, MatInputModule,
MatListModule, MatListModule,
MatMomentDateModule,
PerfectScrollbarModule PerfectScrollbarModule
], ],