Copy message is implemented

This commit is contained in:
병준 박 2019-10-16 18:05:18 +09:00
parent 0c1ae7ec64
commit 43afb43b0e
12 changed files with 377 additions and 11 deletions

View File

@ -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<Info[]>;
@ -53,9 +72,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked {
roomInfoSubscription: Subscription;
userInfoList$: Observable<UserInfo[]>;
eventListProcessing$: Observable<boolean>;
sessionVerInfo = this.sessionStorageService.get<VersionInfo2Response>(
KEY_VER_INFO
);
sessionVerInfo: VersionInfo2Response;
isRecalledMessage = isRecalled;
isCopyableMessage = isCopyable;
@ -64,8 +81,20 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked {
constructor(
private store: Store<any>,
private sessionStorageService: SessionStorageService,
private commonApiService: CommonApiService,
private clipboardService: ClipboardService,
private dialogService: DialogService,
private snackBarService: SnackBarService,
private logger: NGXLogger
) {}
) {
this.sessionVerInfo = this.sessionStorageService.get<VersionInfo2Response>(
KEY_VER_INFO
);
this.environmentsInfo = this.sessionStorageService.get<EnvironmentsInfo>(
KEY_ENVIRONMENTS_INFO
);
}
ngOnInit() {
const loginInfo = this.sessionStorageService.get<LoginInfo>(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':

View File

@ -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
];

View File

@ -0,0 +1,18 @@
<mat-card class="confirm-card">
<mat-card-header>
<mat-card-title>대화 전달</mat-card-title>
</mat-card-header>
<mat-card-content> </mat-card-content>
<mat-card-actions class="button-farm flex-row">
<button
mat-stroked-button
(click)="onClickChoice(false)"
class="mat-primary"
>
No
</button>
<button mat-flat-button (click)="onClickChoice(true)" class="mat-primary">
Yes
</button>
</mat-card-actions>
</mat-card>

View File

@ -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<RelayMessageDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [RelayMessageDialogComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RelayMessageDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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
});
}
}

View File

@ -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();
});
});

View File

@ -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;
}
}

View File

@ -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();
});
});

View File

@ -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);
}
}

View File

@ -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: [],

View File

@ -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';