2월째주 업무

This commit is contained in:
Park Byung Eun 2020-02-14 16:00:15 +09:00
parent 2db0103c0a
commit 8630261f42
27 changed files with 4155 additions and 27 deletions

View File

@ -1,5 +1,5 @@
묶음파일
업로드
묶음파일
업로드 (부분 완료)
묶음파일 request 모델 정의
userSeq: number;
deviceType: DeviceType;
@ -24,6 +24,12 @@
이벤트 타입별 출력
묶음파일 타입 그리드 썸네일 컴포넌트 정의
그리드 알고리즘 작성
썸네일 출력(완료)
출력 가이드 라인
최대 가로 출력 개수 3개
다음행에 출력 개수가 홀수 일때
빈 공간이 출력되지 않게 조정
최대 width, 최소 height 테스트 후 결정
앨범함
묶음파일 타입 처리
뷰어 컴포넌트 (슬라이드 기능) (진행)
@ -33,13 +39,6 @@
일반 이미지 출력
동영상인 경우
일반 동영상 썸네일 출력
썸네일 출력
출력 가이드 라인
최대 가로 출력 개수 3개
다음행에 출력 개수가 홀수 일때
빈 공간이 출력되지 않게 조정
최대 width, 최소 height 테스트 후 결정
카톡 벤치마킹
묶음파일 전송 후
1개의 이미지 전송
@ -47,6 +46,7 @@
동영상 전송
동영상 썸네일 출력
동영상은 묶음파일 지원안함
묶음파일 기획은 카톡과 동일하게 진행
원본 출력
//원본 파일 호출할 때 리플레이스
@ -93,26 +93,67 @@
기능 목록
모바일 주소록 동기화
기존방식 (데이터가 많은 경우 중간 서버에서 끊길 가능성이 농후)
PC 서버 요청
모바일 노티
for
모바일 주소록 서버 전송
모바일 주소록 서버 응답
서버가 주소록 리시브 노티
1. PC -> Server
모바일 주소록 동기화 시작
SSVC_TYPE_SYNC_PHONEBOOK_READY_REQ
2. Server -> Mobile
모바일 주소록 동기화 노티
3. Mobile -> Server
준비 완료 요청
4. Server -> PC
SSVC_TYPE_SYNC_PHONEBOOK_READY_NOTI
5. PC -> Server
모바일 주소록 받을 준비 완료 요청
SSVC_TYPE_SYNC_PHONEBOOK_READY_OK_REQ
6.Server -> PC
SSVC_TYPE_SYNC_PHONEBOOK_READY_OK_RES //사용없음
SSVC_TYPE_SYNC_PHONEBOOK_READY_OK_NOTI
PC가 모바일로부터 준비 확인 NOTI를 받는 처리를 수행
7.Server -> PC
SSVC_TYPE_SYNC_PHONEBOOK_SND_RES //사용없음
SSVC_TYPE_SYNC_PHONEBOOK_SND_NOTI //실데이터받는 프로토콜
박차장님이 제안한 방식
PC 서버 요청
모바일 노티
모바일 주소록 JSON 으로 전송
새로운 방식
생각해봐야함
서버에 개인주소록을 저장할 경우
PC에서 동기화 완료 후 서버 데이터 삭제 고려
서버에 개인주소록을 저장하지 않을 경우
서버 부하 고려
PC -> 서버
개인 주소록 동기화 요청
프로토콜 정의 협의
서버 -> 모바일
개인 주소록 동기화 요청에 대한 푸시
모바일 -> 서버 (REST API)
개인 주소록 POST 전송
데이터 JSON 형태
서버 -> 모바일 (REST API)
개인 주소록 전송에 대한 응답
(응답코드, 싱크번호, 시간, 사용자번호)
모바일 -> 서버
개인 주소록 전송 동기화 프로토콜 요청
(싱크번호, 시간, 사용자번호)
서버 -> 모바일
개인 주소록 전송 동기화 프로토콜 요청에 대한 응답
서버 -> PC
개인 주소록 동기화 요청 프로토콜에 대한 푸시
(싱크번호, 사용자번호)
PC -> 서버 (REST API)
개인 주소록 요청
(싱크번호, 사용자번호)
서버 -> PC
개인 주소록 JSON
(싱크번호, 시간, 사용자 번호)
모바일 주소록 초기화
PC에 저장된 모바일 주소록을 삭제
엑샐
Export/Import
템플릿
초기화
MAC용 빌드
MAC용 빌드
대화 저장
두개다 대화내용 암호화?
서버에서 대화내용을 제공
프로토콜 협의
PC에서 대화내용 조회후 제공
서버 부하
협의 필요

View File

@ -33,4 +33,5 @@
tems[position].FILE_THUMB_URL
.replace("WebFile", "AttFile")
.replace(".thumb.jpg", "")
클라이언트 참고 사항입니ㅏㄷ..실제 파일 열기 하실때는 실제 업로드된 파일경로로 접속하셔야 합니다.
클라이언트 참고 사항입니ㅏㄷ..실제 파일 열기 하실때는 실제 업로드된 파일경로로 접속하셔야 합니다.

View File

@ -13,7 +13,7 @@
fileInfo 조회
decodeInfoData->
WARNING in Circular dependency detected:
projects\ucap-webmessenger-protocol-event\src\lib\protocols\event-json\codec.ts ->
projects\ucap-webmessenger-protocol-event\src\lib\protocols\event-json\bundle-image.event-json.ts ->
@ -64,6 +64,7 @@ projects\ucap-webmessenger-protocol-file\src\public-api.ts -> projects\ucap-webm
1: "16F98292020-02-10 07:09:16{
↵"StatusCode":"200",
↵"ErrorMessage":"",
↵"RoomID":"76",

View File

@ -0,0 +1,151 @@
// 그리드 함수 백업
makeGrid() {
const totalCount = this.bundleImageJson.fileCount;
const remainder = totalCount % 3;
const quotient = Math.floor(totalCount / 3);
let tile;
if (remainder === 0) {
this.bundleImageJson.thumbUrls.forEach((v, idx) => {
tile = {} as Tile;
tile.cols = 2;
this.getTile(tile, v);
});
} else if (remainder === 1) {
if (quotient === 0) {
tile = {} as Tile;
this.bundleImageJson.thumbUrls.forEach(v => {
this.getTile(tile, v);
});
tile.cols = 6;
return;
}
this.bundleImageJson.thumbUrls.forEach((v, idx) => {
tile = {} as Tile;
if (quotient <= idx / 3 + 1) {
tile.cols = 3;
} else {
tile.cols = 2;
}
this.getTile(tile, v);
});
} else {
this.bundleImageJson.thumbUrls.forEach((v, idx) => {
tile = {} as Tile;
if (quotient <= idx / 3) {
tile.cols = 3;
} else {
tile.cols = 2;
}
this.getTile(tile, v);
});
}
}
private getTile(tile: Tile, v: string) {
tile.imgSrc = this.baseURL + v;
tile.color = 'white';
this.tiles.push(tile);
}
모바일 개인 주소록을 PC 버전에도 공유
PC->Mobile
주소록 요청
PC<-Mobile
주소록 전송
PC->Mobile
주소록 완료
주소록
UI 구성
검색 (이름, 전화번호)
검색 결과
리스트 출력
모바일 주소록 동기화(PC-> Mobile 요청)
모바일 주소록 초기화(PC에서 동기화된 주소록 초기화)
엑샐 탬플릿 (주소록을 입력할 수 있는 액샐 템플릿을 제공)
액샐 업로드 (주소록이 입력된 액샐 템플릿을 업로드 하여 PC 주소록 업데이트)
액샐 데이터 초기화 (액샐 업로드 데이터 초기화)
액셀 내려받기 (주소록 데이터 액셀로 다운로드)
기능 목록
모바일 주소록 동기화
기존방식 (데이터가 많은 경우 중간 서버에서 끊길 가능성)
1. PC -> Server
모바일 주소록 동기화 시작
SSVC_TYPE_SYNC_PHONEBOOK_READY_REQ
2. Server -> Mobile
모바일 주소록 동기화 노티
3. Mobile -> Server
모바일 준비 완료 요청 및 데이터 전송 (추측)
4. Server -> PC
4. Server -> PC
SSVC_TYPE_SYNC_PHONEBOOK_READY_NOTI
5. PC -> Server
PC 준비 완료 요청
SSVC_TYPE_SYNC_PHONEBOOK_READY_OK_REQ
6.Server -> PC
SSVC_TYPE_SYNC_PHONEBOOK_READY_OK_RES //사용없음
SSVC_TYPE_SYNC_PHONEBOOK_READY_OK_NOTI
PC가 모바일로부터 준비 확인 NOTI를 받는 처리를 수행
7.Server -> PC
SSVC_TYPE_SYNC_PHONEBOOK_SND_RES //사용없음
SSVC_TYPE_SYNC_PHONEBOOK_SND_NOTI //실데이터받는 프로토콜
박차장님이 제안한 방식
PC -> 서버
개인 주소록 동기화 요청
프로토콜 정의 협의
SSVC_TYPE_SYNC_PHONEBOOK_READY_REQ
(사용자번호)
서버 -> 모바일
개인 주소록 동기화 요청에 대한 푸시
모바일 -> 서버 (REST API)
개인 주소록 POST 전송
데이터 JSON 형태
서버 -> 모바일 (REST API)
개인 주소록 전송에 대한 응답
(응답코드, 싱크번호, 시간, 사용자번호)
모바일 -> 서버
개인 주소록 전송 동기화 프로토콜 요청
(싱크번호, 시간, 사용자번호)
서버 -> 모바일
개인 주소록 전송 동기화 프로토콜 요청에 대한 응답
서버 -> PC
모바일 개인 주소록 전송 완료 노티
SSVC_TYPE_SYNC_PHONEBOOK_SND_NOTI
(싱크번호, 사용자번호)
PC -> 서버 (REST API)
개인 주소록 요청
(싱크번호, 사용자번호)
서버 -> PC
개인 주소록 JSON
(싱크번호, 시간, 사용자 번호)
모바일 주소록 초기화
PC에 저장된 모바일 주소록을 삭제
엑샐
Export/Import
템플릿
초기화
대화방
TODO 완료
대화 묶음파일 그리드 컴포넌트 리팩토링
파일 업로드 큐 컴포넌트 수정
묶음파일 처리 추가
묶음파일 업로드 함수 리팩토링
묶음파일 모델링 수정
메세지 전송 컴포넌트 수정
묶음파일 전송 UI 추가
묶음파일 전송 로직 추가
이벤트 타입 추가
BundleImage = 'b';
파일 타입 추가
Bundle = 'b';
앨범함 컴포넌트 수정
묶음파일 타입 처리 추가

View File

@ -0,0 +1,37 @@
내일 TODO
Call 컴포넌트 작성
다이얼러 컴포넌트 작성
주소록 컴포넌트 작성
최근통화내역 컴포넌트 작성
요구사항
지역번호-3자리-4자리 /^(0(2|3[1-3]|4[1-4]|5[1-5]|6[1-4]))-(\d{3,4})-(\d{4})$/
010-4자리-4자리 || 011,017,016-3자리-4자리 /^(?:(010-\d{4})|(01[1|6|7|8|9]-\d{3,4}))-(\d{4})$/
기타번호4자리-4자리
// 추후에 +국가코드3자리-
입력번호, 출력번호
숫자 버튼,
통화 버튼,
삭제 버튼
4행 * 3열 그리드 출력
번호 함수
입력 이벤트 마다 지역번호, 핸드폰 번호 추출
하이픈 넣기
입력번호를 출력번호로 변환
삭제 함수
이벤트 발생 마다
입력번호 삭제
번호함수 호출
통화 함수
클릭투콜 API 호출
주간보고서 작성
묶음파일 업로드 추가
그리드 기능 추가
앨범함 기능 추가
묶음파일 전송 기능 추가
파일 업로드 기능 수정

View File

@ -0,0 +1,34 @@
SSVC_TYPE_SYNC_PHONEBOOK_READY_REQ = 21, || 모바일 주소록 씽크 준비 요청
PC -> Server
모바일 온라인 확인 단계 필요
SSVC_TYPE_SYNC_PHONEBOOK_READY_RES = 22, || 모바일 주소록 씽크 준비 요청완료
서버 -> PC
SSVC_TYPE_SYNC_PHONEBOOK_SND_NOTI 프로토콜을 30초동안 기다림
SSVC_TYPE_SYNC_PHONEBOOK_READY_NOTI = 23, || 모바일 주소록 씽크 준비 알림
SSVC_TYPE_SYNC_PHONEBOOK_READY_OK_REQ = 24, || 모바일 주소록 씽크 준비 확인 요청
3
SSVC_TYPE_SYNC_PHONEBOOK_READY_OK_RES = 25, || 모바일 주소록 씽크 준비 확인 요청완료
SSVC_TYPE_SYNC_PHONEBOOK_READY_OK_NOTI = 26, || 모바일 주소록 씽크 준비 확인 알림
SSVC_TYPE_SYNC_PHONEBOOK_SND_REQ = 31, || 모바일 주소록 씽크 요청
SSVC_TYPE_SYNC_PHONEBOOK_SND_RES = 32, || 모바일 주소록 씽크 완료
데이터는 M->PC 단방향 해당 RES는 PC 에서는 아무의미가 없다.
SSVC_TYPE_SYNC_PHONEBOOK_SND_NOTI = 33, || 모바일 주소록 씽크 알림
해당 노티는 PC가 받는 노티(실데이터가 있음)
//** 모바일에서 사용하는 프로토콜 **//
SSVC_TYPE_SYNC_PHONEBOOK_RCV_REQ = 34, || 모바일 주소록 씽크 확인 요청
SSVC_TYPE_SYNC_PHONEBOOK_RCV_RES = 35, || 모바일 주소록 씽크 확인 요청완료
PC 사용 X
SSVC_TYPE_SYNC_PHONEBOOK_RCV_NOTI = 36, || 모바일 주소록 씽크 확인 알림
PC 사용 X
모바일에서 사용한는 프로토콜
//** 모바일에서 사용하는 프로토콜 **//

View File

@ -0,0 +1,182 @@
<div fxLayout="column" class="rightDrawer-albumbox">
<div>
<mat-tab-group
mat-stretch-tabs
animationDuration="0ms"
(selectedIndexChange)="onSelectedIndexChange($event)"
>
<mat-tab label="{{ 'common.file.type.images' | translate }}"></mat-tab>
<mat-tab label="{{ 'common.file.type.video' | translate }}"></mat-tab>
</mat-tab-group>
</div>
<div fxFlex="1 1 240px" class="select-filebox bg-accent-brightest">
<ng-container *ngIf="!selectedFile">
<div class="empty-msg" *ngIf="currentTabIndex === 0">
<svg
xmlns="http://www.w3.org/2000/svg"
width="36"
height="36"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
aria-label="image"
>
<rect x="3" y="3" width="18" height="18" rx="2" />
<circle cx="8.5" cy="8.5" r="1.5" />
<path d="M20.4 14.5L16 10 4 20" />
</svg>
<span>{{ 'common.file.selectFiles' | translate }}</span>
</div>
<div class="empty-msg" *ngIf="currentTabIndex === 1">
<svg
xmlns="http://www.w3.org/2000/svg"
width="36"
height="36"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
aria-label="video"
>
<rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"></rect>
<line x1="7" y1="2" x2="7" y2="22"></line>
<line x1="17" y1="2" x2="17" y2="22"></line>
<line x1="2" y1="12" x2="22" y2="12"></line>
<line x1="2" y1="7" x2="7" y2="7"></line>
<line x1="2" y1="17" x2="7" y2="17"></line>
<line x1="17" y1="17" x2="22" y2="17"></line>
<line x1="17" y1="7" x2="22" y2="7"></line>
</svg>
<span>{{ 'common.file.selectFiles' | translate }}</span>
</div>
</ng-container>
<ng-container *ngIf="selectedFile">
<div class="select-file">
<img
*ngIf="selectedFile.info.type === FileType.Image"
[src]="getImageUrl(selectedFile)"
class="preview-image"
/>
<video
controls
controlsList="nodownload nofullscreen"
class="preview-video"
#videoPlayer
*ngIf="selectedFile.info.type === FileType.Video && playable"
[src]="getImageUrl(selectedFile)"
(loadeddata)="onLoadedDataVideo()"
></video>
<div
*ngIf="selectedFile.info.type === FileType.Video && !playable"
fxFlexFill
class="guide-msg"
>
{{ 'common.file.errors.cantPlay' | translate }}
</div>
</div>
<ul>
<li class="name">{{ selectedFile.info.name }}</li>
<li>
<span class="text-accent-color">size :</span>
{{ selectedFile.info.size | ucapBytes }}
</li>
<li>
<span class="text-accent-color">date :</span>
{{ selectedFile.info.sendDate | ucapDate: 'YYYY.MM.DD' }}
</li>
</ul>
</ng-container>
</div>
<div class="search-list">
<perfect-scrollbar class="album-scrollbar">
<div
*ngFor="let fileInfo of filteredList"
class="img-item"
(click)="onClickImage($event, fileInfo)"
>
<dl>
<dt>
<div
*ngIf="fileInfo.fileDownloadItem.downloadingProgress$ | async"
class="spinner"
>
<span class="mdi mdi-spin mdi-loading mdi-48px"></span>
</div>
<div
*ngIf="
!!fileInfo.info &&
!!fileInfo.info.sentMessageJson &&
!!fileInfo.info.sentMessageJson.thumbUrl;
then thumb;
else icon
"
></div>
<ng-template #thumb>
<img
#thumbImg
[src]="fileInfo.info.sentMessageJson.thumbUrl"
[matTooltip]="fileInfo.info.name"
(error)="onErrorThumbnail(thumbImg, fileInfo)"
/>
</ng-template>
<ng-template #icon>
<div
[ngClass]="[
'mime-icon',
'light',
'ico-' + getExtention(fileInfo.info.name)
]"
>
<div class="ico"></div>
</div>
</ng-template>
</dt>
<dd>
<span class="checkbox">
<mat-checkbox
#checkbox
[checked]="getCheckItem(fileInfo)"
(change)="onCheckItem(checkbox.checked, fileInfo)"
(click)="$event.stopPropagation()"
>
</mat-checkbox>
</span>
<span class="btn-download">
<button mat-button (click)="onClickDownload(fileInfo)">
<mat-icon>vertical_align_bottom</mat-icon>
</button>
</span>
</dd>
</dl>
</div>
</perfect-scrollbar>
</div>
<div
fxFlex="1 1 50px"
fxLayout="row"
fxLayoutAlign="center center"
class="btn-box"
>
<button
mat-flat-button
[disabled]="selectedFileList.length > 0 ? 'false' : 'true'"
class="mat-primary"
(click)="onClickDownloadAll()"
>
{{ 'common.file.downloadSelected' | translate }}
</button>
<button
mat-flat-button
class="mat-primary"
(click)="onClickOpenDownloadFolder()"
>
{{ 'common.file.openDownloadFolder' | translate }}
</button>
</div>
</div>

View File

@ -0,0 +1,264 @@
import {
Component,
OnInit,
OnDestroy,
Inject,
ElementRef,
ViewChild
} from '@angular/core';
import {
FileInfo,
FileDownloadInfo,
FileType
} from '@ucap-webmessenger/protocol-file';
import { Subscription, combineLatest } from 'rxjs';
import { Store, select } from '@ngrx/store';
import * as AppStore from '@app/store';
import { tap } from 'rxjs/operators';
import { FileUtil } from '@ucap-webmessenger/core';
import { CommonApiService } from '@ucap-webmessenger/api-common';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type';
import {
EnvironmentsInfo,
KEY_ENVIRONMENTS_INFO,
KEY_VER_INFO
} from '@app/types';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import { UCAP_NATIVE_SERVICE, NativeService } from '@ucap-webmessenger/native';
import { NGXLogger } from 'ngx-logger';
import { FileDownloadItem } from '@ucap-webmessenger/api';
import { ModuleConfig } from '@ucap-webmessenger/api-common';
import { _MODULE_CONFIG } from 'projects/ucap-webmessenger-api-common/src/lib/config/token';
import { AppFileService } from '@app/services/file.service';
export interface FileInfoTotal {
info: FileInfo;
checkInfo: FileDownloadInfo[];
fileDownloadItem: FileDownloadItem;
}
@Component({
selector: 'app-layout-chat-right-drawer-album-box',
templateUrl: './album-box.component.html',
styleUrls: ['./album-box.component.scss']
})
export class AlbumBoxComponent implements OnInit, OnDestroy {
@ViewChild('videoPlayer', { static: false })
videoPlayer: ElementRef<HTMLVideoElement>;
filteredList: FileInfoTotal[] = [];
fileInfoTotal: FileInfoTotal[];
fileInfoList: FileInfo[];
fileInfoListSubscription: Subscription;
selectedFile: FileInfoTotal;
selectedFileList: FileInfoTotal[] = [];
loginRes: LoginResponse;
environmentsInfo: EnvironmentsInfo;
sessionVerinfo: VersionInfo2Response;
FileType = FileType;
currentTabIndex = 0;
thumbBaseUrl: string;
playable = true;
constructor(
private store: Store<any>,
private sessionStorageService: SessionStorageService,
private commonApiService: CommonApiService,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
private appFileService: AppFileService,
@Inject(_MODULE_CONFIG) private moduleConfig: ModuleConfig,
private logger: NGXLogger
) {
this.loginRes = this.sessionStorageService.get<LoginResponse>(
KEY_LOGIN_RES_INFO
);
this.environmentsInfo = this.sessionStorageService.get<EnvironmentsInfo>(
KEY_ENVIRONMENTS_INFO
);
this.sessionVerinfo = this.sessionStorageService.get<VersionInfo2Response>(
KEY_VER_INFO
);
this.thumbBaseUrl = `${this.moduleConfig.hostConfig.protocol}://${
this.moduleConfig.hostConfig.domain
}:${String(this.moduleConfig.hostConfig.port)}`;
}
ngOnInit() {
this.fileInfoListSubscription = combineLatest([
this.store.pipe(select(AppStore.MessengerSelector.RoomSelector.roomInfo)),
this.store.pipe(
select(AppStore.MessengerSelector.EventSelector.selectAllFileInfoList)
),
this.store.pipe(
select(
AppStore.MessengerSelector.EventSelector.selectAllFileInfoCheckList
)
)
])
.pipe(
tap(() => (this.fileInfoTotal = [])),
tap(([roomInfo, fileInfoList, fileInfoCheckList]) => {
this.fileInfoList = fileInfoList.filter(fileInfo => {
if (
!!roomInfo &&
fileInfo.roomSeq === roomInfo.roomSeq &&
(fileInfo.type === FileType.Image ||
fileInfo.type === FileType.Video)
) {
return true;
} else {
return false;
}
});
this.fileInfoList.map(fileInfo => {
this.fileInfoTotal.push({
info: fileInfo,
checkInfo: fileInfoCheckList.filter(
checkInfo => checkInfo.seq === fileInfo.seq
),
fileDownloadItem: new FileDownloadItem()
});
});
this.onSelectedIndexChange(this.currentTabIndex);
})
)
.subscribe();
}
ngOnDestroy(): void {
if (!!this.fileInfoListSubscription) {
this.fileInfoListSubscription.unsubscribe();
}
}
getExtention(name: string): string {
return FileUtil.getExtension(name);
}
getImageUrl(fileInfo: FileInfoTotal): string {
return this.commonApiService.urlForFileTalkDownload(
{
userSeq: this.loginRes.userSeq,
deviceType: this.environmentsInfo.deviceType,
token: this.loginRes.tokenString,
attachmentsSeq: fileInfo.info.seq
},
this.sessionVerinfo.downloadUrl
);
}
onErrorThumbnail(el: HTMLElement, fileInfo: FileInfoTotal): void {
const iconEl = document.createElement('div');
iconEl.setAttribute(
'class',
'mime-icon light ico-' + this.getExtention(fileInfo.info.name)
);
iconEl.innerHTML = `<div class="ico"></div>`;
el.replaceWith(iconEl);
}
onSelectedIndexChange(index: number) {
this.selectedFile = null;
this.currentTabIndex = index;
if (this.currentTabIndex === 0) {
// Image
this.filteredList = this.fileInfoTotal.filter(
fileInfo => fileInfo.info.type === FileType.Image
);
} else {
// Video
this.filteredList = this.fileInfoTotal.filter(
fileInfo => fileInfo.info.type === FileType.Video
);
}
}
onClickImage(event: MouseEvent, fileInfo: FileInfoTotal) {
if (!!event) {
event.preventDefault();
event.stopPropagation();
}
this.playable = true;
this.selectedFile = fileInfo;
}
getCheckItem(fileInfo: FileInfoTotal) {
if (this.selectedFileList) {
if (
this.selectedFileList.filter(
info => info.info.seq === fileInfo.info.seq
).length > 0
) {
return true;
} else {
return false;
}
} else {
return false;
}
}
onCheckItem(value: boolean, fileInfo: FileInfoTotal) {
if (value) {
this.onClickImage(undefined, fileInfo);
this.selectedFileList.push(fileInfo);
} else {
this.selectedFileList = this.selectedFileList.filter(
info => info.info.seq !== fileInfo.info.seq
);
}
}
onClickDownload(fileInfo: FileInfoTotal) {
this.appFileService.fileTalkDownlod({
req: {
userSeq: this.loginRes.userSeq,
deviceType: this.environmentsInfo.deviceType,
token: this.loginRes.tokenString,
attachmentsSeq: fileInfo.info.seq,
fileDownloadItem: fileInfo.fileDownloadItem
},
fileName: fileInfo.info.name
});
}
onClickDownloadAll(): void {
this.selectedFileList.forEach(fileInfo => {
this.onClickDownload(fileInfo);
});
}
onClickOpenDownloadFolder(): void {
this.nativeService
.openDefaultDownloadFolder()
.then(result => {
if (!!result) {
} else {
throw new Error('response Error');
}
})
.catch(reason => {
this.logger.error(reason);
});
}
onLoadedDataVideo(): void {
if (
0 === this.videoPlayer.nativeElement.videoWidth ||
0 === this.videoPlayer.nativeElement.videoHeight
) {
this.playable = false;
}
}
}

View File

@ -0,0 +1,36 @@
import { StatusCode, JsonAnalization } from '@ucap-webmessenger/api';
import { FileType } from '@ucap-webmessenger/protocol-file';
import { EventJsonDecoder } from './event-json';
export interface BundleImageEventJson {
statusCode?: StatusCode;
errorMessage?: string;
roomSeq?: number;
attachmentSeq?: number;
fileCount?: number;
baseUrl?: string;
thumbUrls?: string[];
fileType?: FileType;
}
export const decodeBundleImageEventJson: EventJsonDecoder<BundleImageEventJson> = (
message: string
) => {
try {
const json = JsonAnalization.receiveAnalization(message);
return {
statusCode: json.StatusCode,
errorMessage: json.ErrorMessage,
roomSeq: json.RoomID,
attachmentSeq: json.AttSEQ,
fileCount: json.FileCount,
thumbUrls: json.ThumbURL
} as BundleImageEventJson;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e.toString()
} as BundleImageEventJson;
}
};

View File

@ -0,0 +1,382 @@
import { Injectable, Inject } from '@angular/core';
import {
HttpClient,
HttpEventType,
HttpResponse,
HttpRequest
} from '@angular/common/http';
import { Observable, Subject } from 'rxjs';
import { map, filter } from 'rxjs/operators';
import {
FileProfileSaveRequest,
FileProfileSaveResponse,
encodeFileProfileSave,
decodeFileProfileSave
} from '../apis/file-profile-save';
import {
FileTalkDownloadRequest,
encodeFileTalkDownload,
encodeFormDataFileTalkDownload
} from '../apis/file-talk-download';
import {
FileTalkSaveRequest,
FileTalkSaveResponse,
encodeFileTalkSave,
decodeFileTalkSave
} from '../apis/file-talk-save';
import {
FileTalkSaveMultiRequest,
FileTalkSaveMultiResponse,
encodeFileTalkSaveMulti,
decodeFileTalkSaveMulti
} from '../apis/file-talk-save-multi';
import {
FileTalkShareRequest,
FileTalkShareResponse,
encodeFileTalkShare,
decodeFileTalkShare
} from '../apis/file-talk-share';
import {
MassTalkDownloadRequest,
MassTalkDownloadResponse,
encodeMassTalkDownload,
decodeMassTalkDownload
} from '../apis/mass-talk-download';
import {
MassTalkSaveRequest,
MassTalkSaveResponse,
encodeMassTalkSave,
decodeMassTalkSave
} from '../apis/mass-talk-save';
import {
TransMassTalkDownloadRequest,
TransMassTalkDownloadResponse,
encodeTransMassTalkDownload,
decodeTransMassTalkDownload
} from '../apis/trans-mass-talk-download';
import {
TransMassTalkSaveRequest,
TransMassTalkSaveResponse,
encodeTransMassTalkSave,
decodeTransMassTalkSave
} from '../apis/trans-mass-talk-save';
import {
TranslationReqRequest,
TranslationReqResponse,
encodeTranslationReq,
decodeTranslationReq
} from '../apis/translation-req';
import {
TranslationSaveRequest,
TranslationSaveResponse,
encodeTranslationSave,
decodeTranslationSave
} from '../apis/translation-save';
import { _MODULE_CONFIG } from '../config/token';
import { ModuleConfig } from '../config/module-config';
import { Urls } from '../config/urls';
import { UrlConfig } from '@ucap-webmessenger/core';
@Injectable({
providedIn: 'root'
})
export class CommonApiService {
readonly urls: Urls;
constructor(
@Inject(_MODULE_CONFIG) private moduleConfig: ModuleConfig,
private httpClient: HttpClient
) {
this.urls = UrlConfig.getUrls(
this.moduleConfig.hostConfig,
this.moduleConfig.urls
);
}
public fileProfileSave(
req: FileProfileSaveRequest,
fileProfileSaveUrl?: string
): Observable<FileProfileSaveResponse> {
const httpReq = new HttpRequest(
'POST',
!!fileProfileSaveUrl ? fileProfileSaveUrl : this.urls.fileProfileSave,
encodeFileProfileSave(req),
{ reportProgress: true, responseType: 'text' as 'json' }
);
const progress = req.fileUploadItem.uploadStart();
return this.httpClient.request(httpReq).pipe(
filter(event => {
if (event instanceof HttpResponse) {
return true;
} else if (HttpEventType.UploadProgress === event.type) {
progress.next(Math.round((100 * event.loaded) / event.total));
}
return false;
}),
map((event: HttpResponse<any>) => {
req.fileUploadItem.uploadComplete();
return decodeFileProfileSave(event.body);
})
);
}
public urlForFileTalkDownload(
req: FileTalkDownloadRequest,
fileTalkDownloadUrl?: string
): string {
const httpReq = new HttpRequest(
'GET',
!!fileTalkDownloadUrl ? fileTalkDownloadUrl : this.urls.fileTalkDownload,
{},
{
params: encodeFileTalkDownload(req)
}
);
return httpReq.urlWithParams;
}
public fileTalkDownload(
req: FileTalkDownloadRequest,
fileTalkDownloadUrl?: string
): Observable<Blob> {
const httpReq = new HttpRequest(
'POST',
!!fileTalkDownloadUrl ? fileTalkDownloadUrl : this.urls.fileTalkDownload,
encodeFormDataFileTalkDownload(req),
{ reportProgress: true, responseType: 'blob' }
);
let progress: Subject<number>;
if (!!req.fileDownloadItem) {
progress = req.fileDownloadItem.downloadStart();
}
return this.httpClient.request(httpReq).pipe(
filter(event => {
if (event instanceof HttpResponse) {
return true;
} else if (HttpEventType.DownloadProgress === event.type) {
if (!!progress) {
progress.next(Math.round((100 * event.loaded) / event.total));
}
}
return false;
}),
map((event: HttpResponse<any>) => {
if (!!progress) {
req.fileDownloadItem.downloadComplete();
}
return event.body;
})
);
}
public fileTalkSaveMulti(
req: FileTalkSaveMultiRequest,
fileTalkSaveMultiUrl?: string
): Observable<FileTalkSaveMultiResponse> {
const httpReq = new HttpRequest(
'POST',
!!fileTalkSaveMultiUrl
? fileTalkSaveMultiUrl
: this.urls.fileTalkSaveMulti,
encodeFileTalkSaveMulti(req),
{ reportProgress: true, responseType: 'text' as 'json' }
);
const progressList: Subject<number>[] = [];
for (const p of req.fileUploadItems) {
progressList.push(p.uploadStart());
}
return this.httpClient.request(httpReq).pipe(
filter(event => {
if (event instanceof HttpResponse) {
return true;
} else if (HttpEventType.UploadProgress === event.type) {
// progress.next(Math.round((100 * event.loaded) / event.total));
for (const progress of progressList) {
progress.next(Math.round((100 * event.loaded) / event.total));
}
}
return false;
}),
map((event: HttpResponse<any>) => {
for (const p of req.fileUploadItems) {
p.uploadComplete();
}
return decodeFileTalkSaveMulti(event.body);
})
);
}
public fileTalkSave(
req: FileTalkSaveRequest,
fileTalkSaveUrl?: string
): Observable<FileTalkSaveResponse> {
const httpReq = new HttpRequest(
'POST',
!!fileTalkSaveUrl ? fileTalkSaveUrl : this.urls.fileTalkSave,
encodeFileTalkSave(req),
{ reportProgress: true, responseType: 'text' as 'json' }
);
const progress = req.fileUploadItem.uploadStart();
return this.httpClient.request(httpReq).pipe(
filter(event => {
if (event instanceof HttpResponse) {
return true;
} else if (HttpEventType.UploadProgress === event.type) {
progress.next(Math.round((100 * event.loaded) / event.total));
}
return false;
}),
map((event: HttpResponse<any>) => {
req.fileUploadItem.uploadComplete();
return decodeFileTalkSave(event.body);
})
);
}
public acceptableExtensionForFileTalk(
extensions: string[]
): { accept: boolean; reject: string[] } {
let accept = true;
const reject: string[] = [];
for (const extension of extensions) {
if (
-1 ===
this.moduleConfig.acceptableFileExtensions.indexOf(
extension.toLowerCase()
)
) {
reject.push(extension);
accept = false;
}
}
return {
accept,
reject
};
}
public fileTalkShare(
req: FileTalkShareRequest
): Observable<FileTalkShareResponse> {
return this.httpClient
.post<any>(
this.urls.fileTalkShare,
{},
{
params: encodeFileTalkShare(req)
}
)
.pipe(map(res => decodeFileTalkShare(res)));
}
public massTalkDownload(
req: MassTalkDownloadRequest
): Observable<MassTalkDownloadResponse> {
return this.httpClient
.post<any>(
this.urls.massTalkDownload,
{},
{
params: encodeMassTalkDownload(req),
responseType: 'text' as 'json'
}
)
.pipe(map(res => decodeMassTalkDownload(res)));
}
public massTalkSave(
req: MassTalkSaveRequest
): Observable<MassTalkSaveResponse> {
const httpReq = new HttpRequest(
'POST',
this.urls.massTalkSave,
encodeMassTalkSave(req),
{ reportProgress: true, responseType: 'text' as 'json' }
);
return this.httpClient.request(httpReq).pipe(
filter(event => {
if (event instanceof HttpResponse) {
return true;
}
return false;
}),
map(res => decodeMassTalkSave((res as HttpResponse<any>).body))
);
}
public transMassTalkDownload(
req: TransMassTalkDownloadRequest
): Observable<TransMassTalkDownloadResponse> {
return this.httpClient
.post<any>(
this.urls.transMassTalkDownload,
{},
{
params: encodeTransMassTalkDownload(req)
}
)
.pipe(map(res => decodeTransMassTalkDownload(res)));
}
public transMassTalkSave(
req: TransMassTalkSaveRequest
): Observable<TransMassTalkSaveResponse> {
return this.httpClient
.post<any>(
this.urls.transMassTalkSave,
{},
{
params: encodeTransMassTalkSave(req)
}
)
.pipe(map(res => decodeTransMassTalkSave(res)));
}
public translationReq(
req: TranslationReqRequest
): Observable<TranslationReqResponse> {
return this.httpClient
.post<any>(
this.urls.translationReq,
{},
{
params: encodeTranslationReq(req)
}
)
.pipe(map(res => decodeTranslationReq(res)));
}
public translationSave(
req: TranslationSaveRequest
): Observable<TranslationSaveResponse> {
const httpReq = new HttpRequest(
'POST',
this.urls.translationSave,
encodeTranslationSave(req),
{ reportProgress: true }
);
return this.httpClient.request(httpReq).pipe(
filter(event => {
if (event instanceof HttpResponse) {
return true;
}
return false;
}),
map(res => decodeTranslationSave((res as HttpResponse<any>).body))
);
}
}

View File

@ -0,0 +1,79 @@
import { DeviceType } from '@ucap-webmessenger/core';
import {
APIRequest,
APIResponse,
APIEncoder,
APIDecoder,
ParameterUtil,
StatusCode,
JsonAnalization,
APIFormDataEncoder
} from '@ucap-webmessenger/api';
import { FileUploadItem } from '../../../../ucap-webmessenger-api/src/lib/models/file-upload-item';
export interface FileTalkSaveMultiRequest extends APIRequest {
userSeq: number;
deviceType: DeviceType;
token: string;
files: File[];
fileUploadItems: FileUploadItem[];
roomSeq?: string;
type?: string;
}
export interface FileTalkSaveMultiResponse extends APIResponse {
roomSeq?: string;
attachmentSeq?: string;
fileCount?: string;
baseUrl?: string;
fileType?: string;
thumbnailUrls?: string[];
returnJson?: string;
}
const fileTalkSaveEncodeMapTemp = {
userSeq: 'p_user_seq',
deviceType: 'p_device_type',
token: 'p_token',
roomSeq: 'p_room_id',
files: 'file[]',
type: 'p_type'
};
export const encodeFileTalkSaveMulti: APIFormDataEncoder<FileTalkSaveMultiRequest> = (
req: FileTalkSaveMultiRequest
) => {
const extraParams: any = {};
extraParams.userSeq = String(req.userSeq);
return ParameterUtil.encodeFormData(
fileTalkSaveEncodeMapTemp,
req,
extraParams
);
};
export const decodeFileTalkSaveMulti: APIDecoder<FileTalkSaveMultiResponse> = (
res: any
) => {
try {
const json = JsonAnalization.receiveAnalization(res);
const fileTypeDefault =
!json.fileType || json.fileType === '' ? 'b' : json.fileType;
return {
statusCode: json.StatusCode,
roomSeq: json.RoomID,
attachmentSeq: json.AttSEQ,
fileCount: json.FileCount,
fileType: fileTypeDefault,
baseUrl: json.BaseURL,
thumbnailUrls: json.ThumbURL,
returnJson: res
} as FileTalkSaveMultiResponse;
} catch (e) {
return {
statusCode: StatusCode.Fail,
errorMessage: e
} as FileTalkSaveMultiResponse;
}
};

View File

@ -0,0 +1,44 @@
<div fxLayout="row wrap" fxFlex="100" class="ucap-file-upload-queue-container">
<div *ngFor="let fileUploadItem of fileUploadItems" class="file-upload-item">
<div fxLayout="row" class="file-upload-info">
<!--<mat-icon>image</mat-icon>-->
<!--파일이미지 svg-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path
d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"
></path>
</svg>
<div class="file-upload-name">{{ fileUploadItem.file.name }}</div>
<!-- <div (click)="onClickClear(fileUploadItem)">
<mat-icon>clear</mat-icon>
</div> -->
</div>
<div fxLayout="row" class="file-upload-progress">
<mat-progress-bar
mode="determinate"
[value]="fileUploadItem.uploadingProgress$ | async"
>
</mat-progress-bar>
</div>
</div>
<div *ngIf="uploadItems" fxLayout="column" class="uploadItems">
<div class="msg-guide">
<span class="icon-img"><i class="mid mdi-arrow-expand-all"></i></span
>{{ 'common.file.dropZoneForUpload' | translate }}
</div>
<div></div>
</div>
</div>

View File

@ -0,0 +1,55 @@
@mixin ellipsis($row) {
overflow: hidden;
text-overflow: ellipsis;
@if $row == 1 {
display: block;
white-space: nowrap;
word-wrap: normal;
} @else if $row >= 2 {
display: -webkit-box;
-webkit-line-clamp: $row;
-webkit-box-orient: vertical;
word-wrap: break-word;
}
}
.ucap-file-upload-queue-container {
width: 100%;
height: 100%;
.file-upload-item {
min-width: 200px;
margin: 0 1%;
margin-bottom: 10px;
width: 100%;
border-radius: 3px;
background-color: #f9f9f9;
border: 1px solid #dddddd;
.file-upload-info {
padding: 10px;
svg {
margin-right: 6px;
}
.file-upload-name {
height: 20px;
@include ellipsis(2);
}
}
.file-upload-progress {
padding: 6px 10px;
}
}
.uploadItems {
width: 100%;
font-size: 0.9em;
.msg-guide {
display: flex;
flex: row;
color: #ffffff;
justify-content: center;
align-items: center;
.icon-img {
margin-right: 6px;
}
}
}
}

View File

@ -0,0 +1,117 @@
import {
Component,
OnInit,
Input,
ElementRef,
AfterViewInit
} from '@angular/core';
import { NGXLogger } from 'ngx-logger';
import { FileUploadItem } from '@ucap-webmessenger/api';
@Component({
selector: 'ucap-file-upload-queue',
templateUrl: './file-upload-queue.component.html',
styleUrls: ['./file-upload-queue.component.scss']
})
export class FileUploadQueueComponent implements OnInit, AfterViewInit {
@Input()
dropZoneIncludeParent = false;
fileUploadItems: FileUploadItem[];
uploadItems: DataTransferItem[];
constructor(
private elementRef: ElementRef<HTMLElement>,
private logger: NGXLogger
) {}
ngOnInit() {}
ngAfterViewInit(): void {
this.changeStyleDisplay(false);
}
onDragEnter(items: DataTransferItemList): void {
if (!items || 0 === items.length) {
return;
}
const uploadItems: DataTransferItem[] = [];
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < items.length; i++) {
uploadItems.push(items[i]);
}
this.uploadItems = [...uploadItems];
this.changeStyleDisplay(true);
}
onDragLeave(): void {
this.changeStyleDisplay(false);
}
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)) {
if (this.dropZoneIncludeParent) {
this.elementRef.nativeElement.parentElement.style.display = '';
} else {
this.elementRef.nativeElement.style.display = '';
}
} else {
if (this.dropZoneIncludeParent) {
this.elementRef.nativeElement.parentElement.style.display = 'none';
} 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

@ -0,0 +1,384 @@
<!-- CHAT -->
<div class="container" fxFlex fxLayout="column">
<!-- CHAT TOOLBAR -->
<mat-toolbar class="chat-toolbar">
<div fxFlex fxLayout="row" class="chat-header">
<div fxLayout="row" fxLayoutAlign="start center" class="profile-img">
<!-- RESPONSIVE CHATS BUTTON-->
<button
mat-icon-button
aria-label="chats button"
class="responsive-chats-button"
>
<!--<mat-icon>chat</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
>
<g>
<path
d="M3,21.8c-0.2,0-0.4-0.1-0.5-0.2c-0.2-0.2-0.3-0.5-0.2-0.8l1.8-5.4c-0.6-1.2-0.8-2.5-0.8-3.9c0-3.5,2-6.7,5.1-8.3
c1.3-0.6,2.7-0.9,4.1-1H13c4.7,0.3,8.5,4,8.7,8.7l0,0.5c0,1.4-0.3,2.9-1,4.1c-1.6,3.2-4.7,5.1-8.3,5.1c0,0,0,0,0,0
c-1.3,0-2.6-0.3-3.8-0.8l-5.4,1.8C3.2,21.7,3.1,21.8,3,21.8z M12.5,3.8C11.3,3.8,10.1,4,9,4.6c-2.6,1.3-4.3,4-4.3,6.9
c0,1.2,0.3,2.4,0.8,3.5c0.1,0.2,0.1,0.4,0,0.6l-1.4,4.3l4.3-1.4c0.2-0.1,0.4,0,0.6,0c1.1,0.5,2.3,0.8,3.5,0.8c3,0,5.6-1.6,6.9-4.3
c0.5-1.1,0.8-2.3,0.8-3.5c0,0,0,0,0,0V11C20,7.1,16.9,4,13,3.7L12.5,3.8C12.5,3.8,12.5,3.8,12.5,3.8z"
/>
</g>
<g>
<circle cx="9" cy="12" r="1" />
<circle cx="12.5" cy="12" r="1" />
<circle cx="16" cy="12" r="1" />
</g>
</svg>
</button>
<!-- / RESPONSIVE CHATS BUTTON-->
<button
mat-icon-button
aria-label="chats button"
class="responsive-chats-button chat-timer"
*ngIf="!!roomInfoSubject.value && roomInfoSubject.value.isTimeRoom"
>
<mat-icon>timer</mat-icon>
</button>
</div>
<div class="room-info">
<div
*ngIf="roomInfoSubject.value && roomInfoSubject.value.isTimeRoom"
class="room-type text-accent-color "
>
<span class="bg-accent-darkest"
>{{ getConvertTimer(roomInfoSubject.value.timeRoomInterval) }}
</span>
</div>
<h3 class="room-name">
<ng-container
*ngIf="!roomInfoSubject.value || !userInfoListSubject.value"
>
{{ 'chat.getRoomNameInProgress' | translate }}
</ng-container>
<ng-container
*ngIf="!!roomInfoSubject.value && !!userInfoListSubject.value"
>
<ng-container [ngSwitch]="roomInfoSubject.value.roomType">
<ng-container *ngSwitchCase="RoomType.Mytalk">
MyTalk
</ng-container>
<ng-container
*ngSwitchCase="
RoomType.Bot ||
RoomType.Allim ||
RoomType.Allim_Elephant ||
RoomType.Allim_TMS
"
>
{{ _roomUserInfos | ucapTranslate: 'name':',' }}
</ng-container>
<ng-container *ngSwitchDefault>
<ng-template
[ngIf]="
!!roomInfoSubject.value.roomName &&
'' !== roomInfoSubject.value.roomName.trim()
"
[ngIfElse]="roomNameNotExist"
>
{{ roomInfoSubject.value.roomName }}
</ng-template>
<ng-template #roomNameNotExist>
{{ getRoomNameByRoomUser(_roomUserInfos) }}
</ng-template>
</ng-container>
</ng-container>
</ng-container>
</h3>
<!-- Timer Room Info -->
<!--<div
*ngIf="roomInfoSubject.value && roomInfoSubject.value.isTimeRoom"
class="room-type text-accent-color "
>
<span class="bg-accent-darkest"
>{{
getConvertTimer(roomInfoSubject.value.timeRoomInterval)
}} </span
>{{ 'chat.isRoomTypeSecret' | translate }}
</div>-->
<!-- Timer Room Info -->
</div>
<div class="room-option">
<button
*ngIf="!!roomInfoSubject.value"
mat-icon-button
(click)="onClickReceiveAlarm()"
aria-label="Toggle Receive Alarm"
>
<!--<mat-icon
class="amber-fg"
*ngIf="roomInfoSubject.value.receiveAlarm"
matTooltip="{{ 'chat.notificationIsOn' | translate }}"
>notifications_active</mat-icon>-->
<mat-icon
class="icon-button"
*ngIf="roomInfoSubject.value.receiveAlarm"
matTooltip="{{ 'chat.notificationIsOn' | translate }}"
><i class="mid mdi-bell-ring-outline"></i
></mat-icon>
<mat-icon
class="icon-button"
*ngIf="!roomInfoSubject.value.receiveAlarm"
matTooltip="{{ 'chat.notificationIsOff' | translate }}"
><i class="mid mdi-bell-off-outline"></i
></mat-icon>
</button>
<button
mat-icon-button
#chatMenuTrigger="matMenuTrigger"
[matMenuTriggerFor]="contactMenu"
[matMenuTriggerRestoreFocus]="false"
aria-label="more"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</div>
<div class="chat-search-frame">
<div
*ngIf="eventListProcessing$ | async"
style="position: absolute; width: 100%;"
>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<div *ngIf="isShowSearchArea" class="chat-search bg-accent-color">
<ucap-chat-search
[totalCount]="searchTotalCount"
[curIndex]="searchCurrentIndex"
(searchText)="onSearchChat($event)"
(prevSearch)="onPrevSearch()"
(nextSearch)="onNextSearch()"
(searchAndPrev)="onSearchAndPrev()"
(closeSearchArea)="onCloseSearchArea()"
></ucap-chat-search>
</div>
</div>
</mat-toolbar>
<!-- / CHAT TOOLBAR -->
<!-- CHAT CONTENT -->
<div
fxFlex="1 1 auto"
class="chat-content"
ucapFileUploadFor
[fileUploadQueue]="fileUploadQueue"
(fileSelected)="onFileSelected($event)"
(fileDragEnter)="onFileDragEnter($event)"
(fileDragOver)="onFileDragOver()"
(fileDragLeave)="onFileDragLeave()"
>
<!-- CHAT MESSAGES -->
<!-- [translationSimpleview]="translationSimpleview" (massTranslationDetail)="onMassTranslationDetail($event)" -->
<ucap-chat-messages
#chatMessages
[eventList$]="eventListSubject.asObservable()"
[newEventList$]="eventListNewSubject.asObservable()"
[searchedList$]="searchedListSubject.asObservable()"
[roomInfo$]="roomInfoSubject.asObservable()"
[eventInfoStatus$]="eventInfoStatusSubject.asObservable()"
[eventRemained$]="eventRemainedSubject.asObservable()"
[userInfos$]="userInfoListSubject.asObservable()"
[loginRes$]="loginResSubject.asObservable()"
[lock$]="lockSubject.asObservable()"
[sessionVerInfo]="sessionVerInfo"
[isShowUnreadCount]="getShowUnreadCount()"
[clearReadHere]="clearReadHere"
[minShowReadHere]="
environment.productConfig.CommonSetting.readHereShowMinimumEventCount
"
[searchingMode]="moreSearchProcessing"
(moreEvent)="onMoreEvent($event)"
(massDetail)="onMassDetail($event)"
(save)="onSave($event)"
(fileViewer)="onFileViewer($event)"
(contextMenu)="onContextMenuMessage($event)"
(openProfile)="onClickOpenProfile($event)"
(scrollUp)="onScrollupMessages($event)"
(yReachStart)="onYReachStartMessages($event)"
(yReachEnd)="onYReachEndMessages($event)"
(existNewMessage)="onExistNewMessage($event)"
>
</ucap-chat-messages>
<!-- CHAT MESSAGES -->
<div class="file-drop-zone-container">
<ucap-file-upload-queue
#fileUploadQueue
[dropZoneIncludeParent]="true"
class="file-drop-zone"
>
</ucap-file-upload-queue>
</div>
</div>
<!-- / CHAT CONTENT -->
<!-- sticker-selector -->
<div class="sticker-selector-container">
<ucap-sticker-selector
*ngIf="isShowStickerSelector"
#stickerSelector
[stickerHistory]="getStickerHistory()"
(selectedSticker)="onSelectedSticker($event)"
(closeSticker)="onShowToggleStickerSelector()"
class="sticker-selector-zone"
></ucap-sticker-selector>
<div></div>
</div>
<!-- / sticker-selector -->
<!-- Translation
<div class="translation-container">-->
<!-- <ucap-translation-section
*ngIf="isShowTranslation"
[destLocale]="destLocale"
[simpleView]="translationSimpleview"
[preView]="translationPreview"
[translationPreviewInfo]="translationPreviewInfo"
(changeTranslationSimpleview)="onChangeTranslationSimpleView($event)"
(changeTranslationPreview)="onChangeTranslationPreView($event)"
(changeDestLocale)="onChangeDestLocale($event)"
(cancelTranslation)="onCancelTranslation($event)"
(sendTranslationMessage)="onSendTranslationMessage($event)"
>
</ucap-translation-section> -->
<!-- </div>
/ Translation -->
<!-- CHAT FOOTER -->
<div fxFlex="0 0 auto" fxLayout="column" *ngIf="getEnableSend()">
<!-- REPLY FORM -->
<ucap-chat-form
#chatForm
[fileUploadQueue]="fileUploadQueue"
(send)="onSendMessage($event)"
(sendFiles)="onFileSelected($event)"
(clearView)="clearView()"
(toggleStickerSelector)="onShowToggleStickerSelector($event)"
(clipboardPaste)="onClipboardPaste($event)"
>
</ucap-chat-form>
<!-- / REPLY FORM -->
</div>
<!-- / CHAT FOOTER-->
</div>
<!-- / CHAT -->
<mat-menu #contactMenu="matMenu">
<button
mat-menu-item
*ngIf="getShowContextMenu('OPEN_ALBUM_LIST')"
(click)="onClickContextMenu('OPEN_ALBUM_LIST')"
>
{{ 'chat.albumBox.label' | translate }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('OPEN_FILE_LIST')"
(click)="onClickContextMenu('OPEN_FILE_LIST')"
>
{{ 'chat.fileBox.label' | translate }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('CHAT_SEARCH')"
(click)="onClickContextMenu('CHAT_SEARCH')"
>
{{ 'chat.searchEventByText' | translate }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('OPEN_ROOM_USER')"
(click)="onClickContextMenu('OPEN_ROOM_USER')"
>
{{ 'chat.listOfRoomMember' | translate }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('ADD_MEMBER')"
(click)="onClickContextMenu('ADD_MEMBER')"
>
{{ 'chat.addMemberToRoom' | translate }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('ADD_GROUP')"
(click)="onClickContextMenu('ADD_GROUP')"
>
{{ 'chat.addMemberToGroup' | translate }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('EDIT_ROOM')"
(click)="onClickContextMenu('EDIT_ROOM')"
>
{{ 'chat.settingsOfRoom' | translate }}
</button>
<button
mat-menu-item
*ngIf="getShowContextMenu('CLOSE_ROOM')"
(click)="onClickContextMenu('CLOSE_ROOM')"
>
{{ 'chat.closeRoom' | translate }}
</button>
</mat-menu>
<div
style="visibility: hidden; position: fixed"
[style.left]="messageContextMenuPosition.x"
[style.top]="messageContextMenuPosition.y"
#messageContextMenuTrigger="matMenuTrigger"
[matMenuTriggerFor]="messageContextMenu"
></div>
<mat-menu #messageContextMenu="matMenu">
<ng-template matMenuContent let-message="message" let-clicktype="clicktype">
<ng-container *ngIf="!isRecalledMessage(message.type)">
<button
mat-menu-item
*ngIf="isCopyableMessage(message.type)"
(click)="onClickMessageContextMenu('COPY', message, clicktype)"
>
{{ 'chat.copyChatText' | translate }}
</button>
<button
mat-menu-item
*ngIf="isForwardableMessage(message, roomInfoSubject.value)"
(click)="onClickMessageContextMenu('FORWARD', message)"
>
{{ 'chat.forwardEventTo' | translate }}
</button>
<button
mat-menu-item
*ngIf="isForwardableMessage(message, roomInfoSubject.value)"
(click)="onClickMessageContextMenu('FORWARD_TO_ME', message)"
>
{{ 'chat.forwardEventToMe' | translate }}
</button>
<button
mat-menu-item
(click)="onClickMessageContextMenu('DELETE', message)"
>
{{ 'chat.removeEvent' | translate }}
</button>
<button
mat-menu-item
*ngIf="isRecallableMessage(message, loginResSubject.value.userSeq)"
(click)="onClickMessageContextMenu('RECALL', message)"
>
{{ 'chat.recallEvent' | translate }}
</button>
</ng-container>
</ng-template>
</mat-menu>

View File

@ -0,0 +1,233 @@
@charset 'utf-s';
:host {
display: flex;
width: 100%;
height: 100%;
}
@mixin ellipsis($row) {
overflow: hidden;
text-overflow: ellipsis;
@if $row == 1 {
display: block;
white-space: nowrap;
word-wrap: normal;
} @else if $row >= 2 {
display: -webkit-box;
-webkit-line-clamp: $row;
-webkit-box-orient: vertical;
word-wrap: break-word;
}
}
.container {
position: relative;
width: 100%;
}
.chat-toolbar {
position: relative;
display: flex;
flex-flow: column;
width: 100%;
height: auto;
align-items: center;
background-color: #ffffff !important;
border-bottom: 1px solid #dddddd;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
z-index: 1;
padding: 0;
.chat-header {
width: 100%;
align-items: center;
display: flex;
justify-content: space-between;
justify-items: center;
padding: 4px 20px;
.profile-img {
margin-right: 10px;
width: 30px;
height: 30px;
.responsive-chats-button {
display: none;
line-height: normal;
cursor: unset;
&:last-child {
display: block;
padding: 0;
width: 30px;
height: 30px;
border-radius: 50%;
color: #efefef;
font-size: 1rem;
}
}
&.thumbnail-mask {
border-radius: 50%;
overflow: hidden;
img {
width: 50px;
height: auto;
}
}
}
.room-info {
display: flex;
flex-flow: row;
overflow: hidden;
align-items: center;
.room-name {
font-size: 0.94rem;
line-height: normal;
@include ellipsis(1);
}
.room-type {
font-size: 0.9rem;
line-height: normal;
height: 20px;
span {
border-radius: 10px;
padding: 2px 6px;
margin-right: 6px;
font-size: 0.7rem;
}
}
}
.room-option {
margin-left: auto;
margin-right: -10px;
.icon-button {
transform: translateY(-2px);
i {
font-size: 0.9em;
}
}
}
}
.chat-search-frame {
position: relative;
width: 100%;
.chat-search {
margin: 0 4px 4px;
}
}
}
.chat-content {
position: relative;
background: transparent;
overflow: auto;
-webkit-overflow-scrolling: touch;
.file-drop-zone-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent;
.file-drop-zone {
position: absolute;
padding: 10px;
background-color: rgb(54, 54, 54, 0.8);
bottom: 0;
width: 100%;
}
}
.sticker-selector-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: transparent;
.sticker-selector-zone {
position: absolute;
padding: 10px 10px 0 10px;
background-color: rgba(250, 255, 255, 0.8);
bottom: 0;
width: 100%;
}
}
}
.translation-container {
.translation-section {
display: flex;
flex-flow: column;
border-top: 1px solid #dddddd;
.translation-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: transparent;
background-color: rgba(250, 255, 255, 0.8);
border-top: 1px solid;
.translation-zone {
position: absolute;
padding: 10px 10px 0 10px;
background-color: rgba(250, 255, 255, 0.8);
bottom: 0;
width: 100%;
}
}
}
}
//mat-snack-bar
::ng-deep .cdk-global-overlay-wrapper {
.mat-snack-bar-container {
margin: 0;
padding: 30px;
max-width: 60vw;
.mat-simple-snackbar {
display: flex;
justify-content: center;
span {
@include ellipsis(3);
display: inline-block;
padding: 7px 20px;
border: 1px solid #ffffff;
background-color: rgb(255, 255, 255, 0.2);
color: #ffffff;
margin-right: 4px;
flex: 1 1 auto;
max-width: 40vw;
}
&-action {
display: inline-flex;
margin-left: auto;
flex: 0 0 auto;
height: 100%;
button {
//background-color: #00b6d5;
border-radius: 2px;
span {
padding: 0 20px;
color: #ffffff;
background: none;
border: none;
font-weight: 500;
}
}
}
}
}
}
::ng-deep .chat-header {
.profile-img {
.chat-timer {
.mat-button-wrapper {
display: flex;
justify-content: center;
justify-items: center;
.mat-icon {
line-height: normal;
color: #ffffff;
font-size: 20px;
transform: translateY(1px);
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
c:\projects\work\next-ucap-messenger\projects\ucap-webmessenger-api-common\src\lib\services\common-api.service.ts