Copy message is implemented
This commit is contained in:
parent
0c1ae7ec64
commit
43afb43b0e
|
@ -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':
|
||||
|
|
|
@ -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
|
||||
];
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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: [],
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue
Block a user