From 43afb43b0e94931aa09e327dd2256ac0ede887b8 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Wed, 16 Oct 2019 18:05:18 +0900 Subject: [PATCH] Copy message is implemented --- .../components/messages.component.ts | 100 ++++++++++-- .../app/layouts/messenger/dialogs/index.ts | 6 +- .../relay-message.dialog.component.html | 18 +++ .../relay-message.dialog.component.scss | 0 .../relay-message.dialog.component.spec.ts | 24 +++ .../dialogs/relay-message.dialog.component.ts | 34 ++++ .../lib/services/clipboard.service.spec.ts | 12 ++ .../src/lib/services/clipboard.service.ts | 151 ++++++++++++++++++ .../lib/services/snack-bar.service.spec.ts | 12 ++ .../src/lib/services/snack-bar.service.ts | 18 +++ .../src/lib/ucap-ui.module.ts | 11 +- .../ucap-webmessenger-ui/src/public-api.ts | 2 + 12 files changed, 377 insertions(+), 11 deletions(-) create mode 100644 projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.html create mode 100644 projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.scss create mode 100644 projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.spec.ts create mode 100644 projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.ts create mode 100644 projects/ucap-webmessenger-ui/src/lib/services/clipboard.service.spec.ts create mode 100644 projects/ucap-webmessenger-ui/src/lib/services/clipboard.service.ts create mode 100644 projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.spec.ts create mode 100644 projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.ts diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/messages.component.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/messages.component.ts index 76d8591e..99d6bb2b 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/messages.component.ts +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/messages.component.ts @@ -6,7 +6,12 @@ import { ViewChild, ElementRef } from '@angular/core'; -import { ucapAnimations } from '@ucap-webmessenger/ui'; +import { + ucapAnimations, + SnackBarService, + ClipboardService, + DialogService +} from '@ucap-webmessenger/ui'; import { Store, select } from '@ngrx/store'; import { NGXLogger } from 'ngx-logger'; import { Observable, Subscription } from 'rxjs'; @@ -24,13 +29,25 @@ import * as ChatStore from '@app/store/messenger/chat'; import * as RoomStore from '@app/store/messenger/room'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { SessionStorageService } from '@ucap-webmessenger/web-storage'; -import { LoginInfo, KEY_LOGIN_INFO } from '@app/types'; +import { + LoginInfo, + KEY_LOGIN_INFO, + EnvironmentsInfo, + KEY_ENVIRONMENTS_INFO +} from '@app/types'; import { RoomInfo, UserInfo } from '@ucap-webmessenger/protocol-room'; -import { tap } from 'rxjs/operators'; +import { tap, take } from 'rxjs/operators'; import { FileInfo } from '@ucap-webmessenger/ui-chat'; import { KEY_VER_INFO } from '@app/types/ver-info.type'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { MatMenuTrigger } from '@angular/material'; +import { CommonApiService } from '@ucap-webmessenger/api-common'; + +import { + RelayMessageDialogComponent, + RelayMessageDialogData, + RelayMessageDialogResult +} from '@app/layouts/messenger/dialogs/relay-message.dialog.component'; @Component({ selector: 'app-layout-messenger-messages', @@ -46,6 +63,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked { messageContextMenuTrigger: MatMenuTrigger; messageContextMenuPosition = { x: '0px', y: '0px' }; + environmentsInfo: EnvironmentsInfo; + loginRes: LoginResponse; loginResSubscription: Subscription; eventList$: Observable; @@ -53,9 +72,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked { roomInfoSubscription: Subscription; userInfoList$: Observable; eventListProcessing$: Observable; - sessionVerInfo = this.sessionStorageService.get( - KEY_VER_INFO - ); + sessionVerInfo: VersionInfo2Response; isRecalledMessage = isRecalled; isCopyableMessage = isCopyable; @@ -64,8 +81,20 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked { constructor( private store: Store, private sessionStorageService: SessionStorageService, + private commonApiService: CommonApiService, + private clipboardService: ClipboardService, + private dialogService: DialogService, + private snackBarService: SnackBarService, private logger: NGXLogger - ) {} + ) { + this.sessionVerInfo = this.sessionStorageService.get( + KEY_VER_INFO + ); + + this.environmentsInfo = this.sessionStorageService.get( + KEY_ENVIRONMENTS_INFO + ); + } ngOnInit() { const loginInfo = this.sessionStorageService.get(KEY_LOGIN_INFO); @@ -173,15 +202,68 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked { this.messageContextMenuTrigger.openMenu(); } - onClickMessageContextMenu(menuType: string, message: Info) { + async onClickMessageContextMenu(menuType: string, message: Info) { switch (menuType) { case 'COPY': { - this.logger.debug('onClickMessageContextMenu', menuType, message); + switch (message.type) { + case EventType.Character: + { + if ( + this.clipboardService.copyFromContent(message.sentMessage) + ) { + this.snackBarService.open('클립보드에 복사되었습니다.', '', { + duration: 3000, + verticalPosition: 'top', + horizontalPosition: 'center' + }); + } + } + break; + case EventType.MassText: + { + this.commonApiService + .massTalkDownload({ + userSeq: this.loginRes.userSeq, + deviceType: this.environmentsInfo.deviceType, + token: this.loginRes.tokenString, + eventMassSeq: message.seq + }) + .pipe(take(1)) + .subscribe(res => { + if (this.clipboardService.copyFromContent(res.content)) { + this.snackBarService.open( + '클립보드에 복사되었습니다.', + '', + { + duration: 3000, + verticalPosition: 'top', + horizontalPosition: 'center' + } + ); + } + }); + } + break; + + default: + break; + } } break; case 'REPLAY': { + const result = await this.dialogService.open< + RelayMessageDialogComponent, + RelayMessageDialogData, + RelayMessageDialogResult + >(RelayMessageDialogComponent, { + width: '220px', + data: { + title: 'Logout', + message: 'Logout ?' + } + }); } break; case 'REPLAY_TO_ME': diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/index.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/index.ts index dc3d41a7..2925eadd 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/index.ts +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/index.ts @@ -3,9 +3,13 @@ import { DeleteGroupDialogComponent } from './delete-group.dialog.component'; import { EditGroupDialogComponent } from './edit-group.dialog.component'; import { EditGroupMemberDialogComponent } from './edit-group-member.dialog.component'; +import { RelayMessageDialogComponent } from './relay-message.dialog.component'; + export const DIALOGS = [ CreateGroupDialogComponent, DeleteGroupDialogComponent, EditGroupDialogComponent, - EditGroupMemberDialogComponent + EditGroupMemberDialogComponent, + + RelayMessageDialogComponent ]; diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.html b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.html new file mode 100644 index 00000000..bbcdd34b --- /dev/null +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.html @@ -0,0 +1,18 @@ + + + 대화 전달 + + + + + + + diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.scss b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.spec.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.spec.ts new file mode 100644 index 00000000..de92d8ca --- /dev/null +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RelayMessageDialogComponent } from './relay-message.dialog.component'; + +describe('app::layouts::messenger::RelayMessageDialogComponent', () => { + let component: RelayMessageDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [RelayMessageDialogComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RelayMessageDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.ts new file mode 100644 index 00000000..cdca4618 --- /dev/null +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/relay-message.dialog.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; + +export interface RelayMessageDialogData { + title: string; + message?: string; +} + +export interface RelayMessageDialogResult { + choice: boolean; +} + +@Component({ + selector: 'app-layout-messenger-relay-message', + templateUrl: './relay-message.dialog.component.html', + styleUrls: ['./relay-message.dialog.component.scss'] +}) +export class RelayMessageDialogComponent implements OnInit { + constructor( + public dialogRef: MatDialogRef< + RelayMessageDialogData, + RelayMessageDialogResult + >, + @Inject(MAT_DIALOG_DATA) public data: RelayMessageDialogData + ) {} + + ngOnInit(): void {} + + onClickChoice(choice: boolean): void { + this.dialogRef.close({ + choice + }); + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/services/clipboard.service.spec.ts b/projects/ucap-webmessenger-ui/src/lib/services/clipboard.service.spec.ts new file mode 100644 index 00000000..892c1912 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/services/clipboard.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { ClipboardService } from './clipboard.service'; + +describe('ui::ClipboardService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: ClipboardService = TestBed.get(ClipboardService); + expect(service).toBeTruthy(); + }); +}); diff --git a/projects/ucap-webmessenger-ui/src/lib/services/clipboard.service.ts b/projects/ucap-webmessenger-ui/src/lib/services/clipboard.service.ts new file mode 100644 index 00000000..7377b95f --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/services/clipboard.service.ts @@ -0,0 +1,151 @@ +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class ClipboardService { + private tempTextArea: HTMLTextAreaElement | undefined; + private window: any; + + constructor(@Inject(DOCUMENT) public document: any) { + this.window = window; + } + + public get isSupported(): boolean { + return ( + !!this.document.queryCommandSupported && + !!this.document.queryCommandSupported('copy') && + !!this.window + ); + } + + public isTargetValid( + element: HTMLInputElement | HTMLTextAreaElement + ): boolean { + if ( + element instanceof HTMLInputElement || + element instanceof HTMLTextAreaElement + ) { + if (element.hasAttribute('disabled')) { + throw new Error( + 'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute' + ); + } + return true; + } + throw new Error('Target should be input or textarea'); + } + + /** + * copyFromInputElement + */ + public copyFromInputElement( + targetElm: HTMLInputElement | HTMLTextAreaElement + ): boolean { + try { + this.selectTarget(targetElm); + const re = this.copyText(); + this.clearSelection(targetElm, this.window); + return re && this.isCopySuccessInIE11(); + } catch (error) { + return false; + } + } + + // this is for IE11 return true even if copy fail + isCopySuccessInIE11() { + // tslint:disable-next-line: no-string-literal + const clipboardData = this.window['clipboardData']; + if (clipboardData && clipboardData.getData) { + if (!clipboardData.getData('Text')) { + return false; + } + } + return true; + } + + /** + * Creates a fake textarea element, sets its value from `text` property, + * and makes a selection on it. + */ + public copyFromContent( + content: string, + container: HTMLElement = this.document.body + ) { + // check if the temp textarea still belongs to the current container. + // In case we have multiple places using ngx-clipboard, one is in a modal using container but the other one is not. + if (this.tempTextArea && !container.contains(this.tempTextArea)) { + this.destroy(this.tempTextArea.parentElement); + } + + if (!this.tempTextArea) { + this.tempTextArea = this.createTempTextArea(this.document, this.window); + try { + container.appendChild(this.tempTextArea); + } catch (error) { + throw new Error('Container should be a Dom element'); + } + } + this.tempTextArea.value = content; + + const toReturn = this.copyFromInputElement(this.tempTextArea); + + this.destroy(this.tempTextArea.parentElement); + + return toReturn; + } + + // remove temporary textarea if any + public destroy(container: HTMLElement = this.document.body) { + if (this.tempTextArea) { + container.removeChild(this.tempTextArea); + // removeChild doesn't remove the reference from memory + this.tempTextArea = undefined; + } + } + + // select the target html input element + private selectTarget( + inputElement: HTMLInputElement | HTMLTextAreaElement + ): number | undefined { + inputElement.select(); + inputElement.setSelectionRange(0, inputElement.value.length); + return inputElement.value.length; + } + + private copyText(): boolean { + return this.document.execCommand('copy'); + } + // Moves focus away from `target` and back to the trigger, removes current selection. + private clearSelection( + inputElement: HTMLInputElement | HTMLTextAreaElement, + window: Window + ) { + // tslint:disable-next-line:no-unused-expression + inputElement && inputElement.focus(); + window.getSelection().removeAllRanges(); + } + + // create a fake textarea for copy command + private createTempTextArea( + doc: Document, + window: Window + ): HTMLTextAreaElement { + const isRTL = doc.documentElement.getAttribute('dir') === 'rtl'; + let ta: HTMLTextAreaElement; + ta = doc.createElement('textarea'); + // Prevent zooming on iOS + ta.style.fontSize = '12pt'; + // Reset box model + ta.style.border = '0'; + ta.style.padding = '0'; + ta.style.margin = '0'; + // Move element out of screen horizontally + ta.style.position = 'absolute'; + ta.style[isRTL ? 'right' : 'left'] = '-9999px'; + // Move element to the same position vertically + const yPosition = window.pageYOffset || doc.documentElement.scrollTop; + ta.style.top = yPosition + 'px'; + ta.setAttribute('readonly', ''); + return ta; + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.spec.ts b/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.spec.ts new file mode 100644 index 00000000..97fb5eb4 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { BottomSheetService } from './bottom-sheet.service'; + +describe('ui::BottomSheetService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: BottomSheetService = TestBed.get(BottomSheetService); + expect(service).toBeTruthy(); + }); +}); diff --git a/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.ts b/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.ts new file mode 100644 index 00000000..34f1181b --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; + +import { MatSnackBar, MatSnackBarConfig } from '@angular/material'; + +@Injectable({ + providedIn: 'root' +}) +export class SnackBarService { + public constructor(private matSnackBar: MatSnackBar) {} + + public open( + message: string, + action?: string, + config?: MatSnackBarConfig + ): void { + this.matSnackBar.open(message, action, config); + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/ucap-ui.module.ts b/projects/ucap-webmessenger-ui/src/lib/ucap-ui.module.ts index aad8dab3..92400a16 100644 --- a/projects/ucap-webmessenger-ui/src/lib/ucap-ui.module.ts +++ b/projects/ucap-webmessenger-ui/src/lib/ucap-ui.module.ts @@ -4,12 +4,20 @@ import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; import { DragDropModule } from '@angular/cdk/drag-drop'; import { BottomSheetService } from './services/bottom-sheet.service'; +import { ClipboardService } from './services/clipboard.service'; import { DialogService } from './services/dialog.service'; -const SERVICES = [BottomSheetService, DialogService]; +import { SnackBarService } from './services/snack-bar.service'; +const SERVICES = [ + BottomSheetService, + ClipboardService, + DialogService, + SnackBarService +]; import { AlertDialogComponent } from './dialogs/alert.dialog.component'; import { ConfirmDialogComponent } from './dialogs/confirm.dialog.component'; @@ -21,6 +29,7 @@ const DIALOGS = [AlertDialogComponent, ConfirmDialogComponent]; MatButtonModule, MatCardModule, MatDialogModule, + MatSnackBarModule, DragDropModule ], exports: [], diff --git a/projects/ucap-webmessenger-ui/src/public-api.ts b/projects/ucap-webmessenger-ui/src/public-api.ts index cb5b8ca0..15acf44c 100644 --- a/projects/ucap-webmessenger-ui/src/public-api.ts +++ b/projects/ucap-webmessenger-ui/src/public-api.ts @@ -8,6 +8,8 @@ export * from './lib/dialogs/alert.dialog.component'; export * from './lib/dialogs/confirm.dialog.component'; export * from './lib/services/bottom-sheet.service'; +export * from './lib/services/clipboard.service'; export * from './lib/services/dialog.service'; +export * from './lib/services/snack-bar.service'; export * from './lib/ucap-ui.module';