This commit is contained in:
leejinho 2019-11-13 15:42:56 +09:00
commit 2c41c49cdb
25 changed files with 422 additions and 148 deletions

View File

@ -241,8 +241,9 @@ ipcMain.on(
try { try {
const buffer: Buffer = args[0]; const buffer: Buffer = args[0];
const fileName: string = args[1]; const fileName: string = args[1];
const mimeType: string = args[2];
let savePath: string = path.join( let savePath: string = path.join(
!!args[2] ? args[2] : DefaultFolder.downloads(), !!args[3] ? args[3] : DefaultFolder.downloads(),
fileName fileName
); );
savePath = await FileUtil.uniqueFileName(savePath); savePath = await FileUtil.uniqueFileName(savePath);

9
package-lock.json generated
View File

@ -2789,6 +2789,15 @@
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
"dev": true "dev": true
}, },
"angular-split": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/angular-split/-/angular-split-3.0.2.tgz",
"integrity": "sha512-km59k1kEgVlplo2t4t5Ob43Vx16qVXWXsl5gbsdQtqrOW7341So4CFUmCjcZgfk1swu9RBaCdSQEqzNWOe/89w==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"ansi-align": { "ansi-align": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz",

View File

@ -1,11 +1,6 @@
<mat-progress-bar
*ngIf="fileDownloadItem && fileDownloadItem.downloadingProgress$"
mode="determinate"
[value]="fileDownloadItem.downloadingProgress$ | async"
></mat-progress-bar>
<ucap-file-viewer <ucap-file-viewer
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
(download)="onDownload()" (download)="onDownload($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-file-viewer> ></ucap-file-viewer>

View File

@ -3,7 +3,7 @@ import {
OnInit, OnInit,
OnDestroy, OnDestroy,
Inject, Inject,
EventEmitter EventEmitter,
} from '@angular/core'; } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
@ -15,7 +15,7 @@ import { take, map, finalize, tap } from 'rxjs/operators';
import { SnackBarService } from '@ucap-webmessenger/ui'; import { SnackBarService } from '@ucap-webmessenger/ui';
import { import {
FileDownloadItem, FileDownloadItem,
CommonApiService CommonApiService,
} from '@ucap-webmessenger/api-common'; } from '@ucap-webmessenger/api-common';
export interface FileViewerDialogData { export interface FileViewerDialogData {
@ -31,7 +31,7 @@ export interface FileViewerDialogResult {}
@Component({ @Component({
selector: 'app-layout-common-file-viewer', selector: 'app-layout-common-file-viewer',
templateUrl: './file-viewer.dialog.component.html', templateUrl: './file-viewer.dialog.component.html',
styleUrls: ['./file-viewer.dialog.component.scss'] styleUrls: ['./file-viewer.dialog.component.scss'],
}) })
export class FileViewerDialogComponent implements OnInit, OnDestroy { export class FileViewerDialogComponent implements OnInit, OnDestroy {
fileInfo: FileEventJson; fileInfo: FileEventJson;
@ -40,8 +40,6 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
deviceType: DeviceType; deviceType: DeviceType;
token: string; token: string;
fileDownloadItem: FileDownloadItem;
fileDownloadUrl: string; fileDownloadUrl: string;
constructor( constructor(
@ -66,7 +64,7 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
userSeq: this.userSeq, userSeq: this.userSeq,
deviceType: this.deviceType, deviceType: this.deviceType,
token: this.token, token: this.token,
attachmentsSeq: this.fileInfo.attachmentSeq attachmentsSeq: this.fileInfo.attachmentSeq,
}, },
this.downloadUrl this.downloadUrl
); );
@ -76,8 +74,7 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {} ngOnDestroy(): void {}
onDownload(): void { onDownload(fileDownloadItem: FileDownloadItem): void {
this.fileDownloadItem = new FileDownloadItem();
this.commonApiService this.commonApiService
.fileTalkDownload( .fileTalkDownload(
{ {
@ -85,37 +82,36 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
deviceType: this.deviceType, deviceType: this.deviceType,
token: this.token, token: this.token,
attachmentsSeq: this.fileInfo.attachmentSeq, attachmentsSeq: this.fileInfo.attachmentSeq,
fileDownloadItem: this.fileDownloadItem fileDownloadItem,
}, },
this.downloadUrl this.downloadUrl
) )
.pipe( .pipe(
take(1), take(1),
map(async rawBlob => { map(async rawBlob => {
const blob = rawBlob.slice( const mimeType = MimeUtil.getMimeFromExtension(this.fileInfo.fileExt);
0, const blob = rawBlob.slice(0, rawBlob.size, mimeType);
rawBlob.size,
MimeUtil.getMimeFromExtension(this.fileInfo.fileExt)
);
FileUtil.fromBlobToBuffer(blob) FileUtil.fromBlobToBuffer(blob)
.then(buffer => { .then(buffer => {
this.nativeService this.nativeService
.saveFile(buffer, this.fileInfo.fileName) .saveFile(buffer, this.fileInfo.fileName, mimeType)
.pipe(take(1)) .then(result => {
.subscribe(result => {
if (!!result) { if (!!result) {
this.snackBarService.open( this.snackBarService.open(
`파일이 경로[${result}]에 저장되었습니다.`, `파일이 경로[${result}]에 저장되었습니다.`,
'', '',
{ {
duration: 3000, duration: 3000,
verticalPosition: 'bottom' verticalPosition: 'bottom',
} }
); );
} else { } else {
this.snackBarService.open('파일 저장에 실패하였습니다.'); this.snackBarService.open('파일 저장에 실패하였습니다.');
} }
})
.catch(reason => {
this.snackBarService.open('파일 저장에 실패하였습니다.');
}); });
}) })
.catch(reason => { .catch(reason => {
@ -123,7 +119,9 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
}); });
}), }),
finalize(() => { finalize(() => {
this.fileDownloadItem = undefined; setTimeout(() => {
fileDownloadItem.downloadingProgress$ = undefined;
}, 1000);
}) })
) )
.subscribe(); .subscribe();

View File

@ -485,7 +485,6 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
} }
async onFileViewer(fileInfo: FileEventJson) { async onFileViewer(fileInfo: FileEventJson) {
this.logger.debug('onFileViewer', fileInfo);
const result = await this.dialogService.open< const result = await this.dialogService.open<
FileViewerDialogComponent, FileViewerDialogComponent,
FileViewerDialogData, FileViewerDialogData,

View File

@ -1,7 +1,7 @@
import { Injectable, Inject } from '@angular/core'; import { Injectable, Inject } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { of } from 'rxjs'; import { of, Observable } from 'rxjs';
import { catchError, exhaustMap, map, tap, switchMap } from 'rxjs/operators'; import { catchError, exhaustMap, map, tap, switchMap } from 'rxjs/operators';
import { Actions, ofType, createEffect } from '@ngrx/effects'; import { Actions, ofType, createEffect } from '@ngrx/effects';
@ -86,19 +86,28 @@ export class Effects {
() => () =>
this.actions$.pipe( this.actions$.pipe(
ofType(webLoginSuccess), ofType(webLoginSuccess),
switchMap(params => switchMap(
this.nativeService.checkForUpdates().pipe( params =>
map((update: boolean) => { new Observable<void>(subscriber => {
if (!update) { this.nativeService
this.appAuthenticationService.login( .checkForUpdates()
params.loginInfo, .then((update: boolean) => {
params.rememberMe if (!update) {
); this.appAuthenticationService.login(
this.router.navigate(['/messenger']); params.loginInfo,
} params.rememberMe
}), );
catchError(error => of(error)) this.router.navigate(['/messenger']);
) }
subscriber.next();
})
.catch(reason => {
subscriber.error(reason);
})
.finally(() => {
subscriber.complete();
});
})
) )
), ),
{ dispatch: false } { dispatch: false }

View File

@ -69,6 +69,113 @@ export class FileUtil {
a.remove(); a.remove();
} }
static save(
buffer: Buffer,
fileName: string,
mimeType: string
): Promise<string> {
return new Promise<string>((resolve, reject) => {
const defaultMime = 'application/octet-stream';
const mime = !!mimeType ? mimeType : defaultMime;
const blob = new Blob([buffer], { type: mime });
if (navigator.msSaveBlob) {
// IE10+ : (has Blob, but not a[download] or URL)
navigator.msSaveBlob(blob, fileName);
resolve(fileName);
return;
}
const download = (url: string, mode: boolean = false) => {
const anchor = document.createElement('a');
if ('download' in anchor) {
// html5 A[download]
anchor.href = url;
anchor.setAttribute('download', fileName);
anchor.className = 'download-js-link';
anchor.innerHTML = 'downloading...';
anchor.style.display = 'none';
document.body.appendChild(anchor);
setTimeout(() => {
anchor.click();
document.body.removeChild(anchor);
resolve(fileName);
if (mode) {
setTimeout(() => {
window.URL.revokeObjectURL(anchor.href);
}, 250);
}
}, 66);
return;
}
// handle non-a[download] safari as best we can:
if (
/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(
navigator.userAgent
)
) {
url = url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
if (!window.open(url)) {
// popup blocked, offer direct download:
if (
confirm(
'Displaying New Document\n\nUse Save As... to download, then click back to return to this page.'
)
) {
location.href = url;
}
}
resolve(fileName);
return;
}
// do iframe dataURL download (old ch+FF):
const f = document.createElement('iframe');
document.body.appendChild(f);
if (!mode) {
// force a mime that will download:
url = 'data:' + url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
}
f.src = url;
setTimeout(() => {
document.body.removeChild(f);
}, 333);
};
if (window.URL) {
download(window.URL.createObjectURL(blob), true);
} else {
if (
typeof blob === 'string' ||
(blob as any).constructor === this.toString
) {
try {
download(
'data:' + mime + ';base64,' + window.btoa((blob as any) as string)
);
} catch (e) {
download(
'data:' +
mime +
';base64,' +
encodeURIComponent((blob as any) as string)
);
}
} else {
const fileReader = new FileReader();
fileReader.onload = () => {
download(fileReader.result as string);
};
fileReader.readAsDataURL(blob);
}
}
});
}
static thumbnail(file: File): Promise<File> { static thumbnail(file: File): Promise<File> {
return new Promise<File>((resolve, reject) => { return new Promise<File>((resolve, reject) => {
const fileReader = new FileReader(); const fileReader = new FileReader();

View File

@ -12,6 +12,7 @@ import { TranslateLoader } from '@ngx-translate/core';
import { TranslateLoaderService } from '../translate/browser-loader'; import { TranslateLoaderService } from '../translate/browser-loader';
import { NotificationService } from '../notification/notification.service'; import { NotificationService } from '../notification/notification.service';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FileUtil } from '@ucap-webmessenger/core';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -34,34 +35,35 @@ export class BrowserNativeService extends NativeService {
} }
closeAllNotify(): void {} closeAllNotify(): void {}
checkForUpdates(): Observable<boolean> { checkForUpdates(): Promise<boolean> {
return new Observable<boolean>(subscriber => { return new Promise<boolean>((resolve, reject) => {
try { resolve(false);
subscriber.next(false);
} catch (error) {
subscriber.error(error);
} finally {
subscriber.complete();
}
}); });
} }
showImageViewer(): void {} showImageViewer(): void {}
readFile(path: string): Observable<Buffer> { readFile(path: string): Promise<Buffer> {
return this.httpClient.get(path, { responseType: 'arraybuffer' }).pipe( return new Promise<Buffer>((resolve, reject) => {
map(arrayBuffer => { resolve(null);
return Buffer.from(arrayBuffer); });
})
);
} }
saveFile( saveFile(
buffer: Buffer, buffer: Buffer,
fileName: string, fileName: string,
mimeType: string,
path?: string path?: string
): Observable<string> { ): Promise<string> {
return this.httpClient.post<string>(path, buffer, {}); return new Promise<string>((resolve, reject) => {
try {
FileUtil.save(buffer, fileName, mimeType).then(fn => {
resolve(fn);
});
} catch (error) {
reject(error);
}
});
} }
windowStateChanged(): Observable<WindowState> { windowStateChanged(): Observable<WindowState> {
@ -85,7 +87,6 @@ export class BrowserNativeService extends NativeService {
idleStateChanged(): Observable<WindowIdle> { idleStateChanged(): Observable<WindowIdle> {
return new Observable<WindowIdle>(subscriber => { return new Observable<WindowIdle>(subscriber => {
try { try {
subscriber.next(WindowIdle.Active);
} catch (error) { } catch (error) {
subscriber.error(error); subscriber.error(error);
} finally { } finally {

View File

@ -1,7 +1,6 @@
import { TranslateLoader } from '@ngx-translate/core'; import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { NativeService } from '@ucap-webmessenger/native'; import { NativeService } from '@ucap-webmessenger/native';
import { take, map } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
@Injectable({ @Injectable({
@ -18,13 +17,18 @@ export class TranslateLoaderService implements TranslateLoader {
* Gets the translations from the server * Gets the translations from the server
*/ */
public getTranslation(lang: string): Observable<any> { public getTranslation(lang: string): Observable<any> {
return this.nativeService return new Observable<any>(subscriber => {
.readFile(`${this.prefix}${lang}.${this.suffix}`) this.nativeService
.pipe( .readFile(`${this.prefix}${lang}.${this.suffix}`)
take(1), .then(buffer => {
map(buffer => { subscriber.next(JSON.parse(buffer.toString('utf-8')));
return JSON.parse(buffer.toString('utf-8'));
}) })
); .catch(reason => {
subscriber.error(reason);
})
.finally(() => {
subscriber.complete();
});
});
} }
} }

View File

@ -47,14 +47,12 @@ export class ElectronNativeService implements NativeService {
this.ipcRenderer.send(NotificationChannel.CloseAllNotify); this.ipcRenderer.send(NotificationChannel.CloseAllNotify);
} }
checkForUpdates(): Observable<boolean> { checkForUpdates(): Promise<boolean> {
return new Observable<boolean>(subscriber => { return new Promise<boolean>((resolve, reject) => {
try { try {
subscriber.next(this.ipcRenderer.sendSync(UpdaterChannel.Check)); resolve(this.ipcRenderer.sendSync(UpdaterChannel.Check));
} catch (error) { } catch (error) {
subscriber.error(error); reject(error);
} finally {
subscriber.complete();
} }
}); });
} }
@ -63,14 +61,12 @@ export class ElectronNativeService implements NativeService {
this.ipcRenderer.send(FileChannel.ShowImageViewer); this.ipcRenderer.send(FileChannel.ShowImageViewer);
} }
readFile(path: string): Observable<Buffer> { readFile(path: string): Promise<Buffer> {
return new Observable<Buffer>(subscriber => { return new Promise<Buffer>((resolve, reject) => {
try { try {
subscriber.next(this.ipcRenderer.sendSync(FileChannel.ReadFile, path)); resolve(this.ipcRenderer.sendSync(FileChannel.ReadFile, path));
} catch (error) { } catch (error) {
subscriber.error(error); reject(error);
} finally {
subscriber.complete();
} }
}); });
} }
@ -78,22 +74,22 @@ export class ElectronNativeService implements NativeService {
saveFile( saveFile(
buffer: Buffer, buffer: Buffer,
fileName: string, fileName: string,
mimeType: string,
path?: string path?: string
): Observable<string> { ): Promise<string> {
return new Observable<string>(subscriber => { return new Promise<string>((resolve, reject) => {
try { try {
subscriber.next( resolve(
this.ipcRenderer.sendSync( this.ipcRenderer.sendSync(
FileChannel.SaveFile, FileChannel.SaveFile,
buffer, buffer,
fileName, fileName,
mimeType,
path path
) )
); );
} catch (error) { } catch (error) {
subscriber.error(error); reject(error);
} finally {
subscriber.complete();
} }
}); });
} }

View File

@ -1,7 +1,6 @@
import { TranslateLoader } from '@ngx-translate/core'; import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { NativeService } from '@ucap-webmessenger/native'; import { NativeService } from '@ucap-webmessenger/native';
import { take, map } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
@Injectable({ @Injectable({
@ -18,13 +17,18 @@ export class TranslateLoaderService implements TranslateLoader {
* Gets the translations from the server * Gets the translations from the server
*/ */
public getTranslation(lang: string): Observable<any> { public getTranslation(lang: string): Observable<any> {
return this.nativeService return new Observable<any>(subscriber => {
.readFile(`${this.prefix}${lang}.${this.suffix}`) this.nativeService
.pipe( .readFile(`${this.prefix}${lang}.${this.suffix}`)
take(1), .then(buffer => {
map(buf => { subscriber.next(JSON.parse(buffer.toString('utf-8')));
return JSON.parse(buf.toString());
}) })
); .catch(reason => {
subscriber.error(reason);
})
.finally(() => {
subscriber.complete();
});
});
} }
} }

View File

@ -11,16 +11,17 @@ export abstract class NativeService {
abstract notify(noti: NotificationRequest): void; abstract notify(noti: NotificationRequest): void;
abstract closeAllNotify(): void; abstract closeAllNotify(): void;
abstract checkForUpdates(): Observable<boolean>; abstract checkForUpdates(): Promise<boolean>;
abstract showImageViewer(): void; abstract showImageViewer(): void;
abstract saveFile( abstract saveFile(
buffer: Buffer, buffer: Buffer,
fileName: string, fileName: string,
mimeType: string,
path?: string path?: string
): Observable<string>; ): Promise<string>;
abstract readFile(path: string): Observable<Buffer>; abstract readFile(path: string): Promise<Buffer>;
abstract windowStateChanged(): Observable<WindowState>; abstract windowStateChanged(): Observable<WindowState>;
abstract windowClose(): void; abstract windowClose(): void;

View File

@ -191,12 +191,18 @@ export class ProtocolService {
responseSubject = new Subject<ProtocolMessage>().pipe( responseSubject = new Subject<ProtocolMessage>().pipe(
finalize(() => { finalize(() => {
this.logger.debug(
'ProtocolService::pendingRequests.finalize',
requestId
);
if (this.pendingRequests.has(requestId)) { if (this.pendingRequests.has(requestId)) {
this.pendingRequests.delete(requestId); this.pendingRequests.delete(requestId);
} }
this.logger.debug( this.logger.debug(
'ProtocolService::pendingRequests.size', 'ProtocolService::pendingRequests.size',
this.pendingRequests.size this.pendingRequests.size,
this.pendingRequests
); );
}) })
) as Subject<ProtocolMessage>; ) as Subject<ProtocolMessage>;

View File

@ -6,35 +6,35 @@
*ngSwitchCase="FileViewerType.Document" *ngSwitchCase="FileViewerType.Document"
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
(download)="onDownload()" (download)="onDownload($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-document-viewer> ></ucap-document-viewer>
<ucap-image-viewer <ucap-image-viewer
*ngSwitchCase="FileViewerType.Image" *ngSwitchCase="FileViewerType.Image"
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
(download)="onDownload()" (download)="onDownload($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-image-viewer> ></ucap-image-viewer>
<ucap-sound-viewer <ucap-sound-viewer
*ngSwitchCase="FileViewerType.Sound" *ngSwitchCase="FileViewerType.Sound"
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
(download)="onDownload()" (download)="onDownload($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-sound-viewer> ></ucap-sound-viewer>
<ucap-video-viewer <ucap-video-viewer
*ngSwitchCase="FileViewerType.Video" *ngSwitchCase="FileViewerType.Video"
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
(download)="onDownload()" (download)="onDownload($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-video-viewer> ></ucap-video-viewer>
<ucap-binary-viewer <ucap-binary-viewer
*ngSwitchDefault *ngSwitchDefault
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
(download)="onDownload()" (download)="onDownload($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-binary-viewer> ></ucap-binary-viewer>
</div> </div>

View File

@ -3,12 +3,13 @@ import { ucapAnimations } from '../animations';
import { FileEventJson } from '@ucap-webmessenger/protocol-event'; import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { FileViewerType } from '../types/file-viewer.type'; import { FileViewerType } from '../types/file-viewer.type';
import { FileType } from '@ucap-webmessenger/protocol-file'; import { FileType } from '@ucap-webmessenger/protocol-file';
import { FileDownloadItem } from '@ucap-webmessenger/api-common';
@Component({ @Component({
selector: 'ucap-file-viewer', selector: 'ucap-file-viewer',
templateUrl: './file-viewer.component.html', templateUrl: './file-viewer.component.html',
styleUrls: ['./file-viewer.component.scss'], styleUrls: ['./file-viewer.component.scss'],
animations: ucapAnimations animations: ucapAnimations,
}) })
export class FileViewerComponent implements OnInit { export class FileViewerComponent implements OnInit {
@Input() @Input()
@ -18,7 +19,7 @@ export class FileViewerComponent implements OnInit {
fileDownloadUrl: string; fileDownloadUrl: string;
@Output() @Output()
download = new EventEmitter<void>(); download = new EventEmitter<FileDownloadItem>();
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@ -44,8 +45,8 @@ export class FileViewerComponent implements OnInit {
return FileViewerType.Binary; return FileViewerType.Binary;
} }
} }
onDownload(): void { onDownload(fileDownloadItem: FileDownloadItem): void {
this.download.emit(); this.download.emit(fileDownloadItem);
} }
onClosedViewer(): void { onClosedViewer(): void {

View File

@ -24,6 +24,11 @@
Close Close
</button> </button>
</mat-toolbar> </mat-toolbar>
<mat-progress-bar
*ngIf="fileDownloadItem && fileDownloadItem.downloadingProgress$"
mode="determinate"
[value]="fileDownloadItem.downloadingProgress$ | async"
></mat-progress-bar>
<div class="ucap-binary-viewer-body"> <div class="ucap-binary-viewer-body">
<div <div
class="ucap-binary-viewer-content-wrapper" class="ucap-binary-viewer-content-wrapper"

View File

@ -2,12 +2,13 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ucapAnimations } from '../../animations'; import { ucapAnimations } from '../../animations';
import { FileEventJson } from '@ucap-webmessenger/protocol-event'; import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { DeviceType } from '@ucap-webmessenger/core'; import { DeviceType } from '@ucap-webmessenger/core';
import { FileDownloadItem } from '@ucap-webmessenger/api-common';
@Component({ @Component({
selector: 'ucap-binary-viewer', selector: 'ucap-binary-viewer',
templateUrl: './binary-viewer.component.html', templateUrl: './binary-viewer.component.html',
styleUrls: ['./binary-viewer.component.scss'], styleUrls: ['./binary-viewer.component.scss'],
animations: ucapAnimations animations: ucapAnimations,
}) })
export class BinaryViewerComponent implements OnInit { export class BinaryViewerComponent implements OnInit {
@Input() @Input()
@ -17,17 +18,20 @@ export class BinaryViewerComponent implements OnInit {
fileDownloadUrl: string; fileDownloadUrl: string;
@Output() @Output()
download = new EventEmitter<void>(); download = new EventEmitter<FileDownloadItem>();
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
fileDownloadItem: FileDownloadItem;
constructor() {} constructor() {}
ngOnInit() {} ngOnInit() {}
onClickDownload(): void { onClickDownload(): void {
this.download.emit(); this.fileDownloadItem = new FileDownloadItem();
this.download.emit(this.fileDownloadItem);
} }
onClickClose(): void { onClickClose(): void {

View File

@ -15,4 +15,9 @@
>delete</mat-icon >delete</mat-icon
> >
</mat-toolbar> </mat-toolbar>
<mat-progress-bar
*ngIf="fileDownloadItem && fileDownloadItem.downloadingProgress$"
mode="determinate"
[value]="fileDownloadItem.downloadingProgress$ | async"
></mat-progress-bar>
</div> </div>

View File

@ -2,12 +2,13 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ucapAnimations } from '../../animations'; import { ucapAnimations } from '../../animations';
import { FileEventJson } from '@ucap-webmessenger/protocol-event'; import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { DeviceType } from '@ucap-webmessenger/core'; import { DeviceType } from '@ucap-webmessenger/core';
import { FileDownloadItem } from '@ucap-webmessenger/api-common';
@Component({ @Component({
selector: 'ucap-document-viewer', selector: 'ucap-document-viewer',
templateUrl: './document-viewer.component.html', templateUrl: './document-viewer.component.html',
styleUrls: ['./document-viewer.component.scss'], styleUrls: ['./document-viewer.component.scss'],
animations: ucapAnimations animations: ucapAnimations,
}) })
export class DocumentViewerComponent implements OnInit { export class DocumentViewerComponent implements OnInit {
@Input() @Input()
@ -17,16 +18,21 @@ export class DocumentViewerComponent implements OnInit {
fileDownloadUrl: string; fileDownloadUrl: string;
@Output() @Output()
download = new EventEmitter<void>(); download = new EventEmitter<FileDownloadItem>();
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
fileDownloadItem: FileDownloadItem;
constructor() {} constructor() {}
ngOnInit() {} ngOnInit() {}
onClickDownload(): void {} onClickDownload(): void {
this.fileDownloadItem = new FileDownloadItem();
this.download.emit(this.fileDownloadItem);
}
onClickClose(): void { onClickClose(): void {
this.closed.emit(); this.closed.emit();

View File

@ -1,11 +1,22 @@
<div class="ucap-image-viewer-container"> <div class="ucap-image-viewer-container">
<mat-toolbar class="ucap-image-viewer-header bg-primary-dark"> <mat-toolbar class="ucap-image-viewer-header bg-primary-dark">
<!--<mat-icon class="ucap-image-viewer-icon">image</mat-icon>--> <!--<mat-icon class="ucap-image-viewer-icon">image</mat-icon>-->
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg
stroke-width="2" stroke-linecap="butt" stroke-linejoin="round" class="ucap-image-viewer-icon"> xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
class="ucap-image-viewer-icon"
>
<rect x="3" y="3" width="18" height="18" rx="2" /> <rect x="3" y="3" width="18" height="18" rx="2" />
<circle cx="8.5" cy="8.5" r="1.5" /> <circle cx="8.5" cy="8.5" r="1.5" />
<path d="M20.4 14.5L16 10 4 20" /></svg> <path d="M20.4 14.5L16 10 4 20" />
</svg>
<span class="ucap-image-viewer-title">{{ fileInfo.fileName }}</span> <span class="ucap-image-viewer-title">{{ fileInfo.fileName }}</span>
<span class="ucap-image-viewer-spacer"></span> <span class="ucap-image-viewer-spacer"></span>
@ -18,9 +29,20 @@
> >
<!--<mat-icon>settings_overscan</mat-icon>--> <!--<mat-icon>settings_overscan</mat-icon>-->
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg
stroke-width="2" stroke-linecap="butt" stroke-linejoin="round"> xmlns="http://www.w3.org/2000/svg"
<path d="M3.8 3.8l16.4 16.4M20.2 3.8L3.8 20.2M15 3h6v6M9 3H3v6M15 21h6v-6M9 21H3v-6" /> width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path
d="M3.8 3.8l16.4 16.4M20.2 3.8L3.8 20.2M15 3h6v6M9 3H3v6M15 21h6v-6M9 21H3v-6"
/>
</svg> </svg>
</button> </button>
<button <button
@ -31,7 +53,21 @@
aria-label="" aria-label=""
> >
<!--<mat-icon>zoom_out</mat-icon>--> <!--<mat-icon>zoom_out</mat-icon>-->
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="butt" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg> <svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
<line x1="8" y1="11" x2="14" y2="11"></line>
</svg>
</button> </button>
<button <button
mat-icon-button mat-icon-button
@ -41,8 +77,17 @@
aria-label="" aria-label=""
> >
<!--<mat-icon>zoom_in</mat-icon>--> <!--<mat-icon>zoom_in</mat-icon>-->
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg
stroke-width="2" stroke-linecap="butt" stroke-linejoin="round"> xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle> <circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line> <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
<line x1="11" y1="8" x2="11" y2="14"></line> <line x1="11" y1="8" x2="11" y2="14"></line>
@ -58,25 +103,51 @@
(click)="onClickDownload()" (click)="onClickDownload()"
> >
<!--<mat-icon>get_app</mat-icon>--> <!--<mat-icon>get_app</mat-icon>-->
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg
stroke-width="2" stroke-linecap="butt" stroke-linejoin="round"> xmlns="http://www.w3.org/2000/svg"
<path d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 9l-5 5-5-5M12 12.8V2.5" /></svg> width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path
d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 9l-5 5-5-5M12 12.8V2.5"
/>
</svg>
</button> </button>
<span class="stroke-bar"></span> <span class="stroke-bar"></span>
<button <button
mat-icon-button mat-icon-button
color="warn" color="warn"
class="ucap-image-viewer-action btn-close" class="ucap-image-viewer-action btn-close"
matTooltip="뷰어닫기" matTooltip="뷰어닫기"
(click)="onClickClose()" (click)="onClickClose()"
> >
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg
stroke-width="2" stroke-linecap="butt" stroke-linejoin="round"> xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line> <line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line>
</svg> </svg>
</button> </button>
</mat-toolbar> </mat-toolbar>
<mat-progress-bar
*ngIf="fileDownloadItem && fileDownloadItem.downloadingProgress$"
mode="determinate"
[value]="fileDownloadItem.downloadingProgress$ | async"
></mat-progress-bar>
<div class="ucap-image-viewer-body"> <div class="ucap-image-viewer-body">
<div <div
class="ucap-image-viewer-image-wrapper" class="ucap-image-viewer-image-wrapper"

View File

@ -2,13 +2,16 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ucapAnimations } from '../../animations'; import { ucapAnimations } from '../../animations';
import { FileEventJson } from '@ucap-webmessenger/protocol-event'; import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { DeviceType } from '@ucap-webmessenger/core'; import { DeviceType } from '@ucap-webmessenger/core';
import { CommonApiService } from '@ucap-webmessenger/api-common'; import {
CommonApiService,
FileDownloadItem,
} from '@ucap-webmessenger/api-common';
@Component({ @Component({
selector: 'ucap-image-viewer', selector: 'ucap-image-viewer',
templateUrl: './image-viewer.component.html', templateUrl: './image-viewer.component.html',
styleUrls: ['./image-viewer.component.scss'], styleUrls: ['./image-viewer.component.scss'],
animations: ucapAnimations animations: ucapAnimations,
}) })
export class ImageViewerComponent implements OnInit { export class ImageViewerComponent implements OnInit {
@Input() @Input()
@ -21,14 +24,17 @@ export class ImageViewerComponent implements OnInit {
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@Output() @Output()
download = new EventEmitter<void>(); download = new EventEmitter<FileDownloadItem>();
fileDownloadItem: FileDownloadItem;
constructor() {} constructor() {}
ngOnInit() {} ngOnInit() {}
onClickDownload(): void { onClickDownload(): void {
this.download.emit(); this.fileDownloadItem = new FileDownloadItem();
this.download.emit(this.fileDownloadItem);
} }
onClickClose(): void { onClickClose(): void {

View File

@ -22,6 +22,11 @@
Close Close
</button> </button>
</mat-toolbar> </mat-toolbar>
<mat-progress-bar
*ngIf="fileDownloadItem && fileDownloadItem.downloadingProgress$"
mode="determinate"
[value]="fileDownloadItem.downloadingProgress$ | async"
></mat-progress-bar>
<div class="ucap-sound-viewer-body"> <div class="ucap-sound-viewer-body">
<div <div
class="ucap-sound-viewer-sound-icon" class="ucap-sound-viewer-sound-icon"

View File

@ -5,17 +5,18 @@ import {
Output, Output,
EventEmitter, EventEmitter,
ViewChild, ViewChild,
ElementRef ElementRef,
} from '@angular/core'; } from '@angular/core';
import { ucapAnimations } from '../../animations'; import { ucapAnimations } from '../../animations';
import { FileEventJson } from '@ucap-webmessenger/protocol-event'; import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { MatSlider, MatSliderChange } from '@angular/material'; import { MatSlider, MatSliderChange } from '@angular/material';
import { FileDownloadItem } from '@ucap-webmessenger/api-common';
@Component({ @Component({
selector: 'ucap-sound-viewer', selector: 'ucap-sound-viewer',
templateUrl: './sound-viewer.component.html', templateUrl: './sound-viewer.component.html',
styleUrls: ['./sound-viewer.component.scss'], styleUrls: ['./sound-viewer.component.scss'],
animations: ucapAnimations animations: ucapAnimations,
}) })
export class SoundViewerComponent implements OnInit { export class SoundViewerComponent implements OnInit {
@Input() @Input()
@ -25,7 +26,7 @@ export class SoundViewerComponent implements OnInit {
fileDownloadUrl: string; fileDownloadUrl: string;
@Output() @Output()
download = new EventEmitter<void>(); download = new EventEmitter<FileDownloadItem>();
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@ -41,6 +42,7 @@ export class SoundViewerComponent implements OnInit {
currentTime = 0; currentTime = 0;
volume = 0.1; volume = 0.1;
loading = false; loading = false;
fileDownloadItem: FileDownloadItem;
constructor() {} constructor() {}
@ -90,7 +92,8 @@ export class SoundViewerComponent implements OnInit {
} }
onClickDownload(): void { onClickDownload(): void {
this.download.emit(); this.fileDownloadItem = new FileDownloadItem();
this.download.emit(this.fileDownloadItem);
} }
onClickClose(): void { onClickClose(): void {

View File

@ -1,8 +1,18 @@
<div class="ucap-video-viewer-container"> <div class="ucap-video-viewer-container">
<mat-toolbar color="accent" class="ucap-video-viewer-header"> <mat-toolbar color="accent" class="ucap-video-viewer-header">
<!--<mat-icon class="ucap-video-viewer-icon">video_label</mat-icon>--> <!--<mat-icon class="ucap-video-viewer-icon">video_label</mat-icon>-->
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg
stroke-width="2" stroke-linecap="butt" stroke-linejoin="round" class="ucap-video-viewer-icon"> xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
class="ucap-video-viewer-icon"
>
<rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"></rect> <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="7" y1="2" x2="7" y2="22"></line>
<line x1="17" y1="2" x2="17" y2="22"></line> <line x1="17" y1="2" x2="17" y2="22"></line>
@ -23,9 +33,20 @@
(click)="onClickDownload()" (click)="onClickDownload()"
> >
<!--<mat-icon>get_app</mat-icon>--> <!--<mat-icon>get_app</mat-icon>-->
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg
stroke-width="2" stroke-linecap="butt" stroke-linejoin="round"> xmlns="http://www.w3.org/2000/svg"
<path d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 9l-5 5-5-5M12 12.8V2.5" /> width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path
d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 9l-5 5-5-5M12 12.8V2.5"
/>
</svg> </svg>
</button> </button>
<span class="stroke-bar"></span> <span class="stroke-bar"></span>
@ -33,16 +54,30 @@
mat-icon-button mat-icon-button
color="warn" color="warn"
class="ucap-image-viewer-action btn-close" class="ucap-image-viewer-action btn-close"
matTooltip="뷰어닫기" matTooltip="뷰어닫기"
(click)="onClickClose()" (click)="onClickClose()"
> >
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg
stroke-width="2" stroke-linecap="butt" stroke-linejoin="round"> xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line> <line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line>
</svg> </svg>
</button> </button>
</mat-toolbar> </mat-toolbar>
<mat-progress-bar
*ngIf="fileDownloadItem && fileDownloadItem.downloadingProgress$"
mode="determinate"
[value]="fileDownloadItem.downloadingProgress$ | async"
></mat-progress-bar>
<div class="ucap-video-viewer-body"> <div class="ucap-video-viewer-body">
<div <div
class="ucap-video-viewer-video-icon" class="ucap-video-viewer-video-icon"

View File

@ -5,17 +5,18 @@ import {
Output, Output,
EventEmitter, EventEmitter,
ViewChild, ViewChild,
ElementRef ElementRef,
} from '@angular/core'; } from '@angular/core';
import { ucapAnimations } from '../../animations'; import { ucapAnimations } from '../../animations';
import { FileEventJson } from '@ucap-webmessenger/protocol-event'; import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { MatSlider, MatSliderChange } from '@angular/material'; import { MatSlider, MatSliderChange } from '@angular/material';
import { FileDownloadItem } from '@ucap-webmessenger/api-common';
@Component({ @Component({
selector: 'ucap-video-viewer', selector: 'ucap-video-viewer',
templateUrl: './video-viewer.component.html', templateUrl: './video-viewer.component.html',
styleUrls: ['./video-viewer.component.scss'], styleUrls: ['./video-viewer.component.scss'],
animations: ucapAnimations animations: ucapAnimations,
}) })
export class VideoViewerComponent implements OnInit { export class VideoViewerComponent implements OnInit {
@Input() @Input()
@ -25,7 +26,7 @@ export class VideoViewerComponent implements OnInit {
fileDownloadUrl: string; fileDownloadUrl: string;
@Output() @Output()
download = new EventEmitter<void>(); download = new EventEmitter<FileDownloadItem>();
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@ -41,6 +42,7 @@ export class VideoViewerComponent implements OnInit {
currentTime = 0; currentTime = 0;
volume = 0.1; volume = 0.1;
loading = false; loading = false;
fileDownloadItem: FileDownloadItem;
constructor() {} constructor() {}
@ -90,7 +92,8 @@ export class VideoViewerComponent implements OnInit {
} }
onClickDownload(): void { onClickDownload(): void {
this.download.emit(); this.fileDownloadItem = new FileDownloadItem();
this.download.emit(this.fileDownloadItem);
} }
onClickClose(): void { onClickClose(): void {