file upload is implemented

This commit is contained in:
병준 박 2019-11-05 13:46:17 +09:00
parent 5b9fa9f55c
commit 086a4556f8
17 changed files with 415 additions and 188 deletions

3
package-lock.json generated
View File

@ -14104,7 +14104,8 @@
"tslib": { "tslib": {
"version": "1.10.0", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
"dev": true
}, },
"tslint": { "tslint": {
"version": "5.15.0", "version": "5.15.0",

View File

@ -6,25 +6,28 @@ import {
APIDecoder, APIDecoder,
ParameterUtil, ParameterUtil,
StatusCode, StatusCode,
JsonAnalization JsonAnalization,
APIFormDataEncoder
} from '@ucap-webmessenger/api'; } from '@ucap-webmessenger/api';
import { JsonObject } from 'type-fest'; import { JsonObject } from 'type-fest';
import { FileUploadItem } from '../models/file-upload-item';
export interface FileTalkSaveRequest extends APIRequest { export interface FileTalkSaveRequest extends APIRequest {
userSeq: string; userSeq: number;
deviceType: DeviceType; deviceType: DeviceType;
token: string; token: string;
file?: File; file: File;
fileName?: string; fileName?: string;
fileUploadItem: FileUploadItem;
thumb?: File; thumb?: File;
voice?: boolean; voice?: boolean;
voiceTime?: string; voiceTime?: string;
roomId?: string; roomSeq?: string;
type?: string; type?: string;
} }
export interface FileTalkSaveResponse extends APIResponse { export interface FileTalkSaveResponse extends APIResponse {
roomID?: string; roomSeq?: string;
fileName?: string; fileName?: string;
fileExt?: string; fileExt?: string;
fileType?: string; fileType?: string;
@ -49,19 +52,21 @@ const fileTalkSaveEncodeMap = {
thumb: 'thumb', thumb: 'thumb',
voice: 'p_voice', voice: 'p_voice',
voiceTime: 'p_voice_time', voiceTime: 'p_voice_time',
roomId: 'p_room_id', roomSeq: 'p_room_id',
type: 'p_type' type: 'p_type'
}; };
export const encodeFileTalkSave: APIEncoder<FileTalkSaveRequest> = ( export const encodeFileTalkSave: APIFormDataEncoder<FileTalkSaveRequest> = (
req: FileTalkSaveRequest req: FileTalkSaveRequest
) => { ) => {
const extraParams: any = {}; const extraParams: any = {};
extraParams.userSeq = String(req.userSeq);
if (!!req.voice) { if (!!req.voice) {
extraParams.voice = req.voice ? 'Y' : 'N'; extraParams.voice = req.voice ? 'Y' : 'N';
} }
return ParameterUtil.encode(fileTalkSaveEncodeMap, req, extraParams); return ParameterUtil.encodeFormData(fileTalkSaveEncodeMap, req, extraParams);
}; };
export const decodeFileTalkSave: APIDecoder<FileTalkSaveResponse> = ( export const decodeFileTalkSave: APIDecoder<FileTalkSaveResponse> = (
@ -71,7 +76,7 @@ export const decodeFileTalkSave: APIDecoder<FileTalkSaveResponse> = (
const json: JsonObject | Error = JsonAnalization.receiveAnalization(res); const json: JsonObject | Error = JsonAnalization.receiveAnalization(res);
return { return {
statusCode: json.StatusCode, statusCode: json.StatusCode,
roomID: json.RoomID, roomSeq: json.RoomID,
fileName: json.FileName, fileName: json.FileName,
fileExt: json.FileExt, fileExt: json.FileExt,
fileType: json.FileType, fileType: json.FileType,

View File

@ -4,32 +4,43 @@ import {
APIResponse, APIResponse,
APIEncoder, APIEncoder,
APIDecoder, APIDecoder,
ParameterUtil ParameterUtil,
JsonAnalization,
StatusCode
} from '@ucap-webmessenger/api'; } from '@ucap-webmessenger/api';
import { JsonObject } from 'type-fest';
export interface FileTalkShareRequest extends APIRequest { export interface FileTalkShareRequest extends APIRequest {
userSeq: string; userSeq: string;
deviceType: DeviceType; deviceType: DeviceType;
token: string; token: string;
attachmentsSeq?: string; attachmentsSeq?: string;
roomId?: string; roomSeq?: string;
synapKey?: string; synapKey?: string;
} }
export interface FileTalkShareResponse extends APIResponse { export interface FileTalkShareResponse extends APIResponse {
RoomID?: string; roomSeq?: string;
FileName?: string; fileName?: string;
FileExt?: string; fileExt?: string;
FileType?: string; fileType?: string;
ThumbURL?: string; thumbnailUrl?: string;
AttSEQ?: string; attachmentSeq?: string;
AttSize?: string; attachmentSize?: string;
AttRegDate?: string; attachmentRegDate?: string;
CompanyCode?: string; companyCode?: string;
SynapKey?: string; synapKey?: string;
returnJson?: any;
} }
const fileTalkShareEncodeMap = {}; const fileTalkShareEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
attachmentsSeq: 'p_att_seq',
roomSeq: 'p_room_id',
synapKey: 'p_synap_key'
};
export const encodeFileTalkShare: APIEncoder<FileTalkShareRequest> = ( export const encodeFileTalkShare: APIEncoder<FileTalkShareRequest> = (
req: FileTalkShareRequest req: FileTalkShareRequest
@ -40,5 +51,26 @@ export const encodeFileTalkShare: APIEncoder<FileTalkShareRequest> = (
export const decodeFileTalkShare: APIDecoder<FileTalkShareResponse> = ( export const decodeFileTalkShare: APIDecoder<FileTalkShareResponse> = (
res: any res: any
) => { ) => {
return {} as FileTalkShareResponse; try {
const json: JsonObject | Error = JsonAnalization.receiveAnalization(res);
return {
statusCode: json.StatusCode,
roomSeq: json.RoomID,
fileName: json.FileName,
fileExt: json.FileExt,
fileType: json.FileType,
thumbnailUrl: json.ThumbURL,
attachmentSeq: json.AttSEQ,
attachmentSize: json.AttSize,
attachmentRegDate: json.AttRegDate,
companyCode: json.CompanyCode,
synapKey: json.SynapKey,
returnJson: res
} as FileTalkShareResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as FileTalkShareResponse;
}
}; };

View File

@ -20,7 +20,7 @@ export interface MassTalkSaveRequest extends APIRequest {
export interface MassTalkSaveResponse extends APIResponse { export interface MassTalkSaveResponse extends APIResponse {
eventMassSeq?: string; eventMassSeq?: string;
roomID?: string; roomSeq?: string;
regDate?: string; regDate?: string;
content?: string; content?: string;
returnJson?: any; returnJson?: any;
@ -51,7 +51,7 @@ export const decodeMassTalkSave: APIDecoder<MassTalkSaveResponse> = (
content: json.Content, content: json.Content,
eventMassSeq: json.EventMassSeq, eventMassSeq: json.EventMassSeq,
regDate: json.RegDate, regDate: json.RegDate,
roomID: json.RoomID, roomSeq: json.RoomID,
returnJson: res returnJson: res
} as MassTalkSaveResponse; } as MassTalkSaveResponse;
} catch (e) { } catch (e) {

View File

@ -4,8 +4,11 @@ import {
APIResponse, APIResponse,
APIEncoder, APIEncoder,
APIDecoder, APIDecoder,
ParameterUtil ParameterUtil,
JsonAnalization,
StatusCode
} from '@ucap-webmessenger/api'; } from '@ucap-webmessenger/api';
import { JsonObject } from 'type-fest';
export interface TransMassTalkSaveRequest extends APIRequest { export interface TransMassTalkSaveRequest extends APIRequest {
userSeq: string; userSeq: string;
@ -13,20 +16,29 @@ export interface TransMassTalkSaveRequest extends APIRequest {
token: string; token: string;
original?: string; original?: string;
translation?: string; translation?: string;
roomId?: string; roomSeq?: string;
locale: string; locale: string;
} }
export interface TransMassTalkSaveResponse extends APIResponse { export interface TransMassTalkSaveResponse extends APIResponse {
EventTransSEQ?: string; roomSeq?: string;
RoomID?: string; registrationDate?: string;
RegDate?: string; translationSeq?: string;
Locale?: string; locale?: string;
Original?: string; original?: string;
Translation?: string; translation?: string;
returnJson?: any;
} }
const transMassTalkSaveEncodeMap = {}; const transMassTalkSaveEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
original: 'p_original',
translation: 'p_translation',
roomSeq: 'p_room_id',
locale: 'p_locale'
};
export const encodeTransMassTalkSave: APIEncoder<TransMassTalkSaveRequest> = ( export const encodeTransMassTalkSave: APIEncoder<TransMassTalkSaveRequest> = (
req: TransMassTalkSaveRequest req: TransMassTalkSaveRequest
@ -37,5 +49,22 @@ export const encodeTransMassTalkSave: APIEncoder<TransMassTalkSaveRequest> = (
export const decodeTransMassTalkSave: APIDecoder<TransMassTalkSaveResponse> = ( export const decodeTransMassTalkSave: APIDecoder<TransMassTalkSaveResponse> = (
res: any res: any
) => { ) => {
return {} as TransMassTalkSaveResponse; try {
const json: JsonObject | Error = JsonAnalization.receiveAnalization(res);
return {
statusCode: json.StatusCode,
translationSeq: json.EventTransSEQ,
roomSeq: json.RoomID,
registrationDate: json.RegDate,
locale: json.Locale,
original: json.Original,
translation: json.Translation,
returnJson: res
} as TransMassTalkSaveResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as TransMassTalkSaveResponse;
}
}; };

View File

@ -4,30 +4,42 @@ import {
APIResponse, APIResponse,
APIEncoder, APIEncoder,
APIDecoder, APIDecoder,
ParameterUtil ParameterUtil,
JsonAnalization,
StatusCode
} from '@ucap-webmessenger/api'; } from '@ucap-webmessenger/api';
import { JsonObject } from 'type-fest';
export interface TranslationSaveRequest extends APIRequest { export interface TranslationSaveRequest extends APIRequest {
userSeq: string; userSeq: string;
deviceType: DeviceType; deviceType: DeviceType;
token: string; token: string;
roomId?: string; roomSeq?: string;
original?: string; original?: string;
srcLocale: string; srcLocale: string;
destLocale: string; destLocale: string;
} }
export interface TranslationSaveResponse extends APIResponse { export interface TranslationSaveResponse extends APIResponse {
EventTransSeq?: string; translationSeq?: string;
RoomID?: string; roomSeq?: string;
RegDate?: string; registrationDate?: string;
SrcLocale?: string; srcLocale?: string;
DestLocale?: string; destLocale?: string;
Original?: string; original?: string;
Translation?: string; translation?: string;
returnJson?: any;
} }
const translationSaveEncodeMap = {}; const translationSaveEncodeMap = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
roomSeq: 'p_room_id',
original: 'p_original',
srcLocale: 'p_src_locale',
destLocale: 'p_dest_locale'
};
export const encodeTranslationSave: APIEncoder<TranslationSaveRequest> = ( export const encodeTranslationSave: APIEncoder<TranslationSaveRequest> = (
req: TranslationSaveRequest req: TranslationSaveRequest
@ -38,5 +50,23 @@ export const encodeTranslationSave: APIEncoder<TranslationSaveRequest> = (
export const decodeTranslationSave: APIDecoder<TranslationSaveResponse> = ( export const decodeTranslationSave: APIDecoder<TranslationSaveResponse> = (
res: any res: any
) => { ) => {
return {} as TranslationSaveResponse; try {
const json: JsonObject | Error = JsonAnalization.receiveAnalization(res);
return {
statusCode: json.StatusCode,
translationSeq: json.EventTransSEQ,
roomSeq: json.RoomID,
registrationDate: json.RegDate,
srcLocale: json.SrcLocale,
destLocale: json.DestLocale,
original: json.Original,
translation: json.Translation,
returnJson: res
} as TranslationSaveResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as TranslationSaveResponse;
}
}; };

View File

@ -3,12 +3,11 @@ import {
HttpClient, HttpClient,
HttpEventType, HttpEventType,
HttpResponse, HttpResponse,
HttpRequest, HttpRequest
HttpProgressEvent
} from '@angular/common/http'; } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map, filter } from 'rxjs/operators';
import { _MODULE_CONFIG } from '../types/token'; import { _MODULE_CONFIG } from '../types/token';
import { ModuleConfig } from '../types/module-config'; import { ModuleConfig } from '../types/module-config';
@ -115,21 +114,30 @@ export class CommonApiService {
public fileTalkSave( public fileTalkSave(
req: FileTalkSaveRequest, req: FileTalkSaveRequest,
fileTalkSaveUrl?: string fileTalkSaveUrl?: string
): Observable<FileTalkSaveResponse | HttpProgressEvent> { ): Observable<FileTalkSaveResponse> {
const asa = encodeFileTalkSave(req);
const httpReq = new HttpRequest( const httpReq = new HttpRequest(
'POST', 'POST',
!!fileTalkSaveUrl ? fileTalkSaveUrl : this.moduleConfig.urls.fileTalkSave, !!fileTalkSaveUrl ? fileTalkSaveUrl : this.moduleConfig.urls.fileTalkSave,
encodeFileTalkSave(req), encodeFileTalkSave(req),
{ reportProgress: true } { reportProgress: true, responseType: 'text' as 'json' }
); );
const progress = req.fileUploadItem.uploadStart();
return this.httpClient.request(httpReq).pipe( return this.httpClient.request(httpReq).pipe(
map(event => { filter(event => {
if (event instanceof HttpResponse) { if (event instanceof HttpResponse) {
return decodeFileTalkSave(event); return true;
} else if (HttpEventType.UploadProgress === event.type) { } else if (HttpEventType.UploadProgress === event.type) {
return event; progress.next(Math.round((100 * event.loaded) / event.total));
} }
return false;
}),
map((event: HttpResponse<any>) => {
req.fileUploadItem.uploadComplete();
return decodeFileTalkSave(event.body);
}) })
); );
} }

View File

@ -1,14 +1,14 @@
.current-head{ .current-head {
display: flex; display: flex;
justify-content: center; justify-content: center;
padding: 0 10px; padding: 0 10px;
height: 60px; height: 60px;
h3{ h3 {
display: inline-flex; display: inline-flex;
padding-left: 10px; padding-left: 10px;
align-items: center; align-items: center;
} }
.btn-box{ .btn-box {
height: 100%; height: 100%;
margin-left: auto; margin-left: auto;
display: inline-flex; display: inline-flex;
@ -24,41 +24,41 @@
padding: 0; padding: 0;
font-size: 14px; font-size: 14px;
border-bottom: 1px solid #dddddd; border-bottom: 1px solid #dddddd;
.searchbox{ .searchbox {
width:100%; width: 100%;
height:100%; height: 100%;
} }
} }
::ng-deep .searchbox{ ::ng-deep .searchbox {
.mat-form-field{ .mat-form-field {
display:block; display: block;
.mat-form-field-wrapper{ .mat-form-field-wrapper {
padding: 0; padding: 0;
padding-bottom:0 !important; padding-bottom: 0 !important;
height: 100%; height: 100%;
.mat-form-field-flex{ .mat-form-field-flex {
height: 59px; height: 59px;
padding:0 20px; padding: 0 20px;
align-items: center; align-items: center;
.mat-form-field-infix{ .mat-form-field-infix {
width:100%; width: 100%;
font-size:14px; font-size: 14px;
border:none; border: none;
} }
.mat-form-field-suffix{ .mat-form-field-suffix {
.mat-icon{ .mat-icon {
line-height:24px; line-height: 24px;
} }
} }
} }
} }
} }
.mat-form-field-appearance-legacy{ .mat-form-field-appearance-legacy {
.mat-form-field-wrapper{ .mat-form-field-wrapper {
padding: 0; padding: 0;
} }
.mat-form-field-underline{ .mat-form-field-underline {
bottom:0; bottom: 0;
background-color: unset !important; background-color: unset !important;
} }
} }

View File

@ -81,7 +81,8 @@
fxFlex="1 1 auto" fxFlex="1 1 auto"
class="chat-content" class="chat-content"
#messageBoxContainer #messageBoxContainer
ucapUiFileUploadFor ucapFileUploadFor
[fileUploadQueue]="fileUploadQueue"
(fileSelected)="onFileSelected($event)" (fileSelected)="onFileSelected($event)"
(fileDragEnter)="onFileDragEnter($event)" (fileDragEnter)="onFileDragEnter($event)"
(fileDragOver)="onFileDragOver()" (fileDragOver)="onFileDragOver()"
@ -109,16 +110,11 @@
</ucap-chat-messages> </ucap-chat-messages>
</perfect-scrollbar> </perfect-scrollbar>
<!-- CHAT MESSAGES --> <!-- CHAT MESSAGES -->
<div <div class="file-drop-zone-container">
*ngIf="fileDragOver || (files && 0 < files.length)" <ucap-file-upload-queue
class="file-drop-zone-container" #fileUploadQueue
> class="file-drop-zone"
<div class="file-drop-zone"> ></ucap-file-upload-queue>
<ucap-ui-file-upload-queue
[(files)]="files"
[items]="fileItems"
></ucap-ui-file-upload-queue>
</div>
</div> </div>
</div> </div>
<!-- / CHAT CONTENT --> <!-- / CHAT CONTENT -->
@ -128,6 +124,7 @@
<!-- REPLY FORM --> <!-- REPLY FORM -->
<ucap-chat-form <ucap-chat-form
#chatForm #chatForm
[fileUploadQueue]="fileUploadQueue"
(send)="onSendMessage($event)" (send)="onSendMessage($event)"
(sendFiles)="onFileSelected($event)" (sendFiles)="onFileSelected($event)"
></ucap-chat-form> ></ucap-chat-form>
@ -147,7 +144,7 @@
<mat-menu <mat-menu
#messageContextMenu="matMenu" #messageContextMenu="matMenu"
[hasBackdrop]="false" [hasBackdrop]="false"
(ucapUiClickOutside)="messageContextMenuTrigger.closeMenu()" (ucapClickOutside)="messageContextMenuTrigger.closeMenu()"
> >
<ng-template matMenuContent let-message="message" let-loginRes="loginRes"> <ng-template matMenuContent let-message="message" let-loginRes="loginRes">
<ng-container *ngIf="!isRecalledMessage(message.type)"> <ng-container *ngIf="!isRecalledMessage(message.type)">

View File

@ -91,15 +91,16 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgba(255, 255, 255, 0.95); background-color: transparent;
.file-drop-zone { .file-drop-zone {
position: absolute; position: absolute;
background-color: rgb(180, 180, 180);
top: 10%; top: calc(100% - 200px);
left: 10%; left: 20%;
width: 80%; width: 60%;
height: 80%; height: 200px;
} }
} }
} }

View File

@ -2,10 +2,8 @@ import {
Component, Component,
OnInit, OnInit,
OnDestroy, OnDestroy,
AfterViewChecked,
ViewChild, ViewChild,
ElementRef, ElementRef,
AfterContentInit,
AfterViewInit AfterViewInit
} from '@angular/core'; } from '@angular/core';
import { import {
@ -18,11 +16,12 @@ import {
ConfirmDialogResult, ConfirmDialogResult,
AlertDialogComponent, AlertDialogComponent,
AlertDialogData, AlertDialogData,
AlertDialogResult AlertDialogResult,
FileUploadQueueComponent
} from '@ucap-webmessenger/ui'; } from '@ucap-webmessenger/ui';
import { Store, select } from '@ngrx/store'; import { Store, select } from '@ngrx/store';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription, forkJoin, of } from 'rxjs';
import { import {
Info, Info,
EventType, EventType,
@ -44,7 +43,7 @@ import {
UserSelectDialogType UserSelectDialogType
} from '@app/types'; } from '@app/types';
import { RoomInfo, UserInfo, RoomType } from '@ucap-webmessenger/protocol-room'; import { RoomInfo, UserInfo, RoomType } from '@ucap-webmessenger/protocol-room';
import { tap, take } from 'rxjs/operators'; import { tap, take, map, catchError } from 'rxjs/operators';
import { import {
FileInfo, FileInfo,
FormComponent as UCapUiChatFormComponent FormComponent as UCapUiChatFormComponent
@ -52,7 +51,12 @@ import {
import { KEY_VER_INFO } from '@app/types/ver-info.type'; import { KEY_VER_INFO } from '@app/types/ver-info.type';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import { MatMenuTrigger } from '@angular/material'; import { MatMenuTrigger } from '@angular/material';
import { CommonApiService } from '@ucap-webmessenger/api-common'; import {
CommonApiService,
FileUploadItem,
FileTalkSaveRequest,
FileTalkSaveResponse
} from '@ucap-webmessenger/api-common';
import { import {
CreateChatDialogComponent, CreateChatDialogComponent,
CreateChatDialogData, CreateChatDialogData,
@ -65,6 +69,7 @@ import {
} from '@app/layouts/common/dialogs/image-viewer.dialog.component'; } from '@app/layouts/common/dialogs/image-viewer.dialog.component';
import { CONST } from '@ucap-webmessenger/core'; import { CONST } from '@ucap-webmessenger/core';
import { PerfectScrollbarComponent } from 'ngx-perfect-scrollbar'; import { PerfectScrollbarComponent } from 'ngx-perfect-scrollbar';
import { StatusCode } from '@ucap-webmessenger/api';
@Component({ @Component({
selector: 'app-layout-messenger-messages', selector: 'app-layout-messenger-messages',
@ -86,6 +91,9 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChild('psChatContent', { static: true }) @ViewChild('psChatContent', { static: true })
psChatContent: PerfectScrollbarComponent; psChatContent: PerfectScrollbarComponent;
@ViewChild('fileUploadQueue', { static: true })
fileUploadQueue: FileUploadQueueComponent;
environmentsInfo: EnvironmentsInfo; environmentsInfo: EnvironmentsInfo;
loginRes: LoginResponse; loginRes: LoginResponse;
@ -106,10 +114,6 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
isCopyableMessage = isCopyable; isCopyableMessage = isCopyable;
isRecallableMessage = isRecallable; isRecallableMessage = isRecallable;
fileDragOver = false;
files: File[];
fileItems: DataTransferItemList;
/** Timer 대화방의 대화 삭제를 위한 interval */ /** Timer 대화방의 대화 삭제를 위한 interval */
interval: any; interval: any;
@ -185,7 +189,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
); );
this.interval = setInterval(() => { this.interval = setInterval(() => {
if (!!this.roomInfo.isTimeRoom) { if (!!this.roomInfo && !!this.roomInfo.isTimeRoom) {
this.store.dispatch(EventStore.infoIntervalClear({})); this.store.dispatch(EventStore.infoIntervalClear({}));
} }
}, 1000); }, 1000);
@ -369,8 +373,6 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
onFileDragEnter(items: DataTransferItemList) { onFileDragEnter(items: DataTransferItemList) {
this.logger.debug('onFileDragEnter', items); this.logger.debug('onFileDragEnter', items);
this.fileDragOver = true;
this.fileItems = items;
} }
onFileDragOver() { onFileDragOver() {
@ -379,17 +381,64 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
onFileDragLeave() { onFileDragLeave() {
this.logger.debug('onFileDragLeave'); this.logger.debug('onFileDragLeave');
this.fileDragOver = false;
} }
onFileSelected(files: File[]) { onFileSelected(fileUploadItems: FileUploadItem[]) {
this.logger.debug('onFileSelected', files); this.logger.debug('onFileSelected', fileUploadItems);
if (!this.files) {
this.files = [];
}
this.files.push(...files);
this.fileDragOver = false; const allObservables: Observable<FileTalkSaveResponse>[] = [];
for (const fileUploadItem of fileUploadItems) {
const req: FileTalkSaveRequest = {
userSeq: this.loginRes.userSeq,
deviceType: this.environmentsInfo.deviceType,
token: this.loginRes.tokenString,
roomSeq: this.roomInfo.roomSeq,
file: fileUploadItem.file,
fileName: fileUploadItem.file.name,
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: this.loginRes.userSeq,
req: {
roomSeq: this.roomInfo.roomSeq,
eventType: EventType.File,
sentMessage: res.returnJson
}
})
);
}
},
error => {},
() => {
this.fileUploadQueue.onUploadComplete();
}
);
} }
onContextMenuMessage(params: { event: MouseEvent; message: Info }) { onContextMenuMessage(params: { event: MouseEvent; message: Info }) {

View File

@ -65,7 +65,6 @@ import {
cancel, cancel,
cancelFailure, cancelFailure,
forward, forward,
forwardFailure,
forwardAfterRoomOpen, forwardAfterRoomOpen,
sendMass, sendMass,
sendMassFailure, sendMassFailure,
@ -418,7 +417,7 @@ export class Effects {
send({ send({
senderSeq: action.senderSeq, senderSeq: action.senderSeq,
req: { req: {
roomSeq: res.roomID, roomSeq: res.roomSeq,
eventType: EventType.MassText, eventType: EventType.MassText,
sentMessage: res.returnJson sentMessage: res.returnJson
} }

View File

@ -4,9 +4,12 @@ import {
Output, Output,
EventEmitter, EventEmitter,
ViewChild, ViewChild,
ElementRef ElementRef,
Input
} from '@angular/core'; } from '@angular/core';
import { NgForm } from '@angular/forms'; import { NgForm } from '@angular/forms';
import { FileUploadItem } from '@ucap-webmessenger/api-common';
import { FileUploadQueueComponent } from '@ucap-webmessenger/ui';
@Component({ @Component({
selector: 'ucap-chat-form', selector: 'ucap-chat-form',
@ -14,11 +17,14 @@ import { NgForm } from '@angular/forms';
styleUrls: ['./form.component.scss'] styleUrls: ['./form.component.scss']
}) })
export class FormComponent implements OnInit { export class FormComponent implements OnInit {
@Input()
fileUploadQueue: FileUploadQueueComponent;
@Output() @Output()
send = new EventEmitter<string>(); send = new EventEmitter<string>();
@Output() @Output()
sendFiles = new EventEmitter<File[]>(); sendFiles = new EventEmitter<FileUploadItem[]>();
@ViewChild('replyForm', { static: false }) @ViewChild('replyForm', { static: false })
replyForm: NgForm; replyForm: NgForm;
@ -53,6 +59,13 @@ export class FormComponent implements OnInit {
for (let i = 0; i < this.fileInput.nativeElement.files.length; i++) { for (let i = 0; i < this.fileInput.nativeElement.files.length; i++) {
files.push(this.fileInput.nativeElement.files.item(i)); files.push(this.fileInput.nativeElement.files.item(i));
} }
this.sendFiles.emit(files);
const fileUploadItems = FileUploadItem.fromFiles(files);
if (!!this.fileUploadQueue) {
this.fileUploadQueue.onFileSelected(fileUploadItems);
}
this.sendFiles.emit(fileUploadItems);
} }
} }

View File

@ -1,41 +1,31 @@
<div <div fxLayout="row wrap" fxFlex="100" class="ucap-file-upload-queue-container">
fxLayout="row wrap"
fxFlex="100"
class="ucap-ui-file-upload-queue-container"
>
<div <div
fxLayout="column" fxLayout="column"
fxFlex="100" fxFlex="100"
fxFlex.gt-xs="100" fxFlex.gt-xs="100"
fxFlex.gt-md="25" fxFlex.gt-md="25"
*ngFor="let file of uploadFiles" *ngFor="let fileUploadItem of fileUploadItems"
> >
<div fxLayout="row"> <div fxLayout="row">
<div> <div>
<mat-icon>image</mat-icon> <mat-icon>image</mat-icon>
</div> </div>
<div>{{ file.name }}</div> <div>{{ fileUploadItem.file.name }}</div>
<div (click)="onClickClear(file)"> <!-- <div (click)="onClickClear(fileUploadItem)">
<mat-icon>clear</mat-icon> <mat-icon>clear</mat-icon>
</div> </div> -->
</div> </div>
<div fxLayout="row"> <div fxLayout="row">
<mat-progress-bar mode="determinate" value="40"> </mat-progress-bar> <mat-progress-bar
mode="determinate"
[value]="fileUploadItem.uploadingProgress$ | async"
>
</mat-progress-bar>
</div> </div>
</div> </div>
<div *ngIf="uploadItems" fxLayout="column"> <div *ngIf="uploadItems" fxLayout="column">
<div>여기에 파일을 Drop하시면 업로드 됩니다.</div> <div>여기에 파일을 Drop하시면 업로드 됩니다.</div>
<div> <div></div>
<div
fxLayout="row"
fxFlex="100"
fxFlex.gt-xs="20"
fxFlex.gt-md="25"
*ngFor="let item of uploadItems"
>
<mat-icon>image</mat-icon>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
.ucap-ui-file-upload-queue-container { .ucap-file-upload-queue-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }

View File

@ -1,40 +1,108 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import {
Component,
OnInit,
Input,
Output,
EventEmitter,
ElementRef,
AfterViewInit
} from '@angular/core';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { FileUploadItem } from '@ucap-webmessenger/api-common';
@Component({ @Component({
selector: 'ucap-ui-file-upload-queue', selector: 'ucap-file-upload-queue',
templateUrl: './file-upload-queue.component.html', templateUrl: './file-upload-queue.component.html',
styleUrls: ['./file-upload-queue.component.scss'] styleUrls: ['./file-upload-queue.component.scss']
}) })
export class FileUploadQueueComponent implements OnInit { export class FileUploadQueueComponent implements OnInit, AfterViewInit {
@Output() fileUploadItems: FileUploadItem[];
filesChange = new EventEmitter<File[]>(); uploadItems: DataTransferItem[];
@Input() set files(files: File[]) { constructor(
this.uploadFiles = files; private elementRef: ElementRef<HTMLElement>,
this.uploadItems = undefined; private logger: NGXLogger
) {}
ngOnInit() {}
ngAfterViewInit(): void {
this.changeStyleDisplay(false);
} }
uploadFiles: File[];
@Input() set items(items: DataTransferItemList) { onDragEnter(items: DataTransferItemList): void {
if (!items || 0 === items.length) {
return;
}
const uploadItems: DataTransferItem[] = []; const uploadItems: DataTransferItem[] = [];
// tslint:disable-next-line: prefer-for-of // tslint:disable-next-line: prefer-for-of
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
uploadItems.push(items[i]); uploadItems.push(items[i]);
} }
this.uploadItems = [...uploadItems]; this.uploadItems = [...uploadItems];
this.changeStyleDisplay(true);
} }
uploadItems: DataTransferItem[];
constructor(private logger: NGXLogger) {} onDragLeave(): void {
this.changeStyleDisplay(false);
ngOnInit() {}
onClickClear(file: File) {
this.uploadFiles = this.uploadFiles.filter(f => {
return f.name !== file.name && f.path !== file.path;
});
this.filesChange.emit(this.uploadFiles);
} }
onDrop(fileUploadItems: FileUploadItem[]) {
if (!fileUploadItems || 0 === fileUploadItems.length) {
return;
}
this.fileUploadItems = fileUploadItems;
this.uploadItems = undefined;
}
onFileSelected(fileUploadItems: FileUploadItem[]): void {
if (!fileUploadItems || 0 === fileUploadItems.length) {
return;
}
this.fileUploadItems = fileUploadItems;
this.uploadItems = undefined;
this.changeStyleDisplay(true);
}
onUploadComplete(): void {
setTimeout(() => {
this.fileUploadItems = undefined;
this.changeStyleDisplay(false);
}, 1000);
}
isEventInElement(event: DragEvent): boolean {
const rect = this.elementRef.nativeElement.getBoundingClientRect();
// const rect: DOMRect = this.elementRef.nativeElement.getBoundingClientRect();
if (
event.pageX >= rect.left &&
event.pageX <= rect.left + rect.width &&
event.pageY >= rect.top &&
event.pageY <= rect.top + rect.height
) {
return true;
}
return false;
}
private changeStyleDisplay(show: boolean): void {
if (show || (!!this.fileUploadItems && 0 < this.fileUploadItems.length)) {
this.elementRef.nativeElement.style.display = '';
} else {
this.elementRef.nativeElement.style.display = 'none';
}
}
// onClickClear(fileUploadItem: FileUploadItem) {
// this.fileUploadItems = this.fileUploadItems.filter(f => {
// return (
// f.file.name !== fileUploadItem.file.name &&
// f.file.path !== fileUploadItem.file.path
// );
// });
// this.filesChange.emit(this.fileUploadItems);
// }
} }

View File

@ -3,15 +3,22 @@ import {
ElementRef, ElementRef,
EventEmitter, EventEmitter,
HostListener, HostListener,
Output Output,
Input,
AfterViewInit
} from '@angular/core'; } from '@angular/core';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { FileUploadQueueComponent } from '../components/file-upload-queue.component';
import { FileUploadItem } from '@ucap-webmessenger/api-common';
@Directive({ @Directive({
selector: 'input[ucapUiFileUploadFor], div[ucapUiFileUploadFor]' selector: 'input[ucapFileUploadFor], div[ucapFileUploadFor]'
}) })
export class FileUploadForDirective { export class FileUploadForDirective implements AfterViewInit {
@Input()
fileUploadQueue: FileUploadQueueComponent;
@Output() @Output()
public fileDragEnter = new EventEmitter<DataTransferItemList>(); public fileDragEnter = new EventEmitter<DataTransferItemList>();
@ -22,12 +29,14 @@ export class FileUploadForDirective {
public fileDragLeave = new EventEmitter<void>(); public fileDragLeave = new EventEmitter<void>();
@Output() @Output()
public fileSelected = new EventEmitter<File[]>(); public fileSelected = new EventEmitter<FileUploadItem[]>();
dragOver = false; dragOver = false;
constructor(private elementRef: ElementRef, private logger: NGXLogger) {} constructor(private elementRef: ElementRef, private logger: NGXLogger) {}
ngAfterViewInit(): void {}
@HostListener('window:dragenter', ['$event']) @HostListener('window:dragenter', ['$event'])
public onDragEnter(event: DragEvent): any { public onDragEnter(event: DragEvent): any {
if (!this.isFileDrag(event.dataTransfer)) { if (!this.isFileDrag(event.dataTransfer)) {
@ -37,6 +46,9 @@ export class FileUploadForDirective {
if (!this.dragOver) { if (!this.dragOver) {
this.fileDragEnter.emit(event.dataTransfer.items); this.fileDragEnter.emit(event.dataTransfer.items);
this.dragOver = true; this.dragOver = true;
if (!!this.fileUploadQueue) {
this.fileUploadQueue.onDragEnter(event.dataTransfer.items);
}
} }
} }
@ -46,7 +58,7 @@ export class FileUploadForDirective {
return; return;
} }
if (this.isEventInElement(event)) { if (this.fileUploadQueue.isEventInElement(event)) {
event.dataTransfer.dropEffect = 'copy'; event.dataTransfer.dropEffect = 'copy';
} else { } else {
event.dataTransfer.dropEffect = 'none'; event.dataTransfer.dropEffect = 'none';
@ -63,13 +75,16 @@ export class FileUploadForDirective {
if (event && event.pageX === 0 && event.pageY === 0) { if (event && event.pageX === 0 && event.pageY === 0) {
this.fileDragLeave.emit(); this.fileDragLeave.emit();
this.dragOver = false; this.dragOver = false;
if (!!this.fileUploadQueue) {
this.fileUploadQueue.onDragLeave();
}
} }
} }
@HostListener('change') @HostListener('change')
public onChange(): any { public onChange(): any {
const files = this.elementRef.nativeElement.files; const files = this.elementRef.nativeElement.files;
this.fileSelected.emit(files); this.fileSelected.emit(FileUploadItem.fromFiles(files));
this.elementRef.nativeElement.value = ''; this.elementRef.nativeElement.value = '';
} }
@ -79,11 +94,15 @@ export class FileUploadForDirective {
return; return;
} }
const files = event.dataTransfer.files; const files = event.dataTransfer.files;
this.fileSelected.emit(files); const fileUploadItems = FileUploadItem.fromFiles(files);
this.fileSelected.emit(fileUploadItems);
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.elementRef.nativeElement.value = ''; this.elementRef.nativeElement.value = '';
this.dragOver = false; this.dragOver = false;
if (!!this.fileUploadQueue) {
this.fileUploadQueue.onDrop(fileUploadItems);
}
} }
private isFileDrag(dataTransfer: DataTransfer): boolean { private isFileDrag(dataTransfer: DataTransfer): boolean {
@ -101,18 +120,4 @@ export class FileUploadForDirective {
return true; return true;
} }
private isEventInElement(event: DragEvent): boolean {
const rect: DOMRect = this.elementRef.nativeElement.getBoundingClientRect();
if (
event.pageX >= rect.left &&
event.pageX <= rect.left + rect.width &&
event.pageY >= rect.top &&
event.pageY <= rect.top + rect.height
) {
return true;
}
return false;
}
} }