upload of profile image is implemented
This commit is contained in:
parent
4da93a5351
commit
eb542bd923
|
@ -2,35 +2,67 @@ import { DeviceType } from '@ucap-webmessenger/core';
|
|||
import {
|
||||
APIRequest,
|
||||
APIResponse,
|
||||
APIEncoder,
|
||||
APIDecoder,
|
||||
ParameterUtil
|
||||
ParameterUtil,
|
||||
APIFormDataEncoder,
|
||||
JsonAnalization,
|
||||
StatusCode
|
||||
} from '@ucap-webmessenger/api';
|
||||
import { FileUploadItem } from '../models/file-upload-item';
|
||||
|
||||
export interface FileProfileSaveRequest extends APIRequest {
|
||||
userSeq: number;
|
||||
deviceType: DeviceType;
|
||||
token: string;
|
||||
file?: File;
|
||||
fileUploadItem: FileUploadItem;
|
||||
intro?: string;
|
||||
initProfileImage?: boolean;
|
||||
}
|
||||
|
||||
export interface FileProfileSaveResponse extends APIResponse {
|
||||
ProfileURL?: string;
|
||||
ProfileSubDir?: string;
|
||||
profileURL?: string;
|
||||
profileSubDir?: string;
|
||||
returnJson?: any;
|
||||
}
|
||||
|
||||
const fileProfileEncodeMap = {};
|
||||
const fileProfileEncodeMap = {
|
||||
userSeq: 'p_user_seq',
|
||||
deviceType: 'p_device_type',
|
||||
token: 'p_token',
|
||||
file: 'file',
|
||||
intro: 'p_intro',
|
||||
initProfileImage: 'p_init_profile_img_yn'
|
||||
};
|
||||
|
||||
export const encodeFileProfileSave: APIEncoder<FileProfileSaveRequest> = (
|
||||
export const encodeFileProfileSave: APIFormDataEncoder<FileProfileSaveRequest> = (
|
||||
req: FileProfileSaveRequest
|
||||
) => {
|
||||
return ParameterUtil.encode(fileProfileEncodeMap, req);
|
||||
const extraParams: any = {};
|
||||
|
||||
extraParams.userSeq = String(req.userSeq);
|
||||
if (!!req.initProfileImage) {
|
||||
extraParams.initProfileImage = req.initProfileImage ? 'Y' : 'N';
|
||||
}
|
||||
|
||||
return ParameterUtil.encodeFormData(fileProfileEncodeMap, req, extraParams);
|
||||
};
|
||||
|
||||
export const decodeFileProfileSave: APIDecoder<FileProfileSaveResponse> = (
|
||||
res: any
|
||||
) => {
|
||||
return {} as FileProfileSaveResponse;
|
||||
try {
|
||||
const json = JsonAnalization.receiveAnalization(res);
|
||||
return {
|
||||
statusCode: json.StatusCode,
|
||||
profileURL: json.ProfileURL,
|
||||
profileSubDir: json.ProfileSubDir,
|
||||
returnJson: res
|
||||
} as FileProfileSaveResponse;
|
||||
} catch (e) {
|
||||
return {
|
||||
statusCode: StatusCode.Fail,
|
||||
errorMessage: e
|
||||
} as FileProfileSaveResponse;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -94,15 +94,29 @@ export class CommonApiService {
|
|||
req: FileProfileSaveRequest,
|
||||
fileProfileSaveUrl?: string
|
||||
): Observable<FileProfileSaveResponse> {
|
||||
return this.httpClient
|
||||
.post<any>(
|
||||
const httpReq = new HttpRequest(
|
||||
'POST',
|
||||
!!fileProfileSaveUrl ? fileProfileSaveUrl : this.urls.fileProfileSave,
|
||||
{},
|
||||
{
|
||||
params: encodeFileProfileSave(req)
|
||||
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));
|
||||
}
|
||||
)
|
||||
.pipe(map(res => decodeFileProfileSave(res)));
|
||||
return false;
|
||||
}),
|
||||
map((event: HttpResponse<any>) => {
|
||||
req.fileUploadItem.uploadComplete();
|
||||
return decodeFileProfileSave(event.body);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public urlForFileTalkDownload(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ucap-profile-profile
|
||||
[userInfo]="data.userInfo"
|
||||
[userInfo]="userInfo"
|
||||
[profileImageRoot]="sessionVerinfo.profileRoot"
|
||||
[isMe]="isMe"
|
||||
[isBuddy]="isBuddy"
|
||||
|
@ -7,5 +7,6 @@
|
|||
(openChat)="onClickChat($event)"
|
||||
(toggleFavorit)="onClickToggleFavorit($event)"
|
||||
(toggleBuddy)="onClickToggleBuddy($event)"
|
||||
(uploadProfileImage)="onUploadProfileImage($event)"
|
||||
>
|
||||
</ucap-profile-profile>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Store, select } from '@ngrx/store';
|
|||
import * as AppStore from '@app/store';
|
||||
import * as ChatStore from '@app/store/messenger/chat';
|
||||
import * as SyncStore from '@app/store/messenger/sync';
|
||||
import * as AuthenticationStore from '@app/store/account/authentication';
|
||||
|
||||
import { UserInfo, GroupDetailData } from '@ucap-webmessenger/protocol-sync';
|
||||
import {
|
||||
|
@ -15,12 +16,28 @@ import {
|
|||
UserInfoF,
|
||||
UserInfoDN
|
||||
} from '@ucap-webmessenger/protocol-query';
|
||||
import { DialogService, ConfirmDialogComponent, ConfirmDialogData, ConfirmDialogResult } from '@ucap-webmessenger/ui';
|
||||
import {
|
||||
DialogService,
|
||||
ConfirmDialogComponent,
|
||||
ConfirmDialogData,
|
||||
ConfirmDialogResult,
|
||||
SnackBarService
|
||||
} from '@ucap-webmessenger/ui';
|
||||
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
|
||||
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { map, take, finalize } from 'rxjs/operators';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { SelectGroupDialogComponent, SelectGroupDialogData, SelectGroupDialogResult } from '../group/select-group.dialog.component';
|
||||
import {
|
||||
SelectGroupDialogComponent,
|
||||
SelectGroupDialogData,
|
||||
SelectGroupDialogResult
|
||||
} from '../group/select-group.dialog.component';
|
||||
import {
|
||||
FileUploadItem,
|
||||
CommonApiService
|
||||
} from '@ucap-webmessenger/api-common';
|
||||
import { EnvironmentsInfo, KEY_ENVIRONMENTS_INFO } from '@app/types';
|
||||
import { StatusCode } from '@ucap-webmessenger/api';
|
||||
|
||||
export interface ProfileDialogData {
|
||||
userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN;
|
||||
|
@ -34,8 +51,11 @@ export interface ProfileDialogResult {}
|
|||
styleUrls: ['./profile.dialog.component.scss']
|
||||
})
|
||||
export class ProfileDialogComponent implements OnInit, OnDestroy {
|
||||
userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN;
|
||||
loginRes: LoginResponse;
|
||||
sessionVerinfo: VersionInfo2Response;
|
||||
environmentsInfo: EnvironmentsInfo;
|
||||
|
||||
isMe: boolean;
|
||||
isBuddy: boolean;
|
||||
isFavorit: boolean;
|
||||
|
@ -47,6 +67,8 @@ export class ProfileDialogComponent implements OnInit, OnDestroy {
|
|||
@Inject(MAT_DIALOG_DATA) public data: ProfileDialogData,
|
||||
private dialogService: DialogService,
|
||||
private sessionStorageService: SessionStorageService,
|
||||
private commonApiService: CommonApiService,
|
||||
private snackBarService: SnackBarService,
|
||||
private store: Store<any>
|
||||
) {
|
||||
this.sessionVerinfo = this.sessionStorageService.get<VersionInfo2Response>(
|
||||
|
@ -55,6 +77,11 @@ export class ProfileDialogComponent implements OnInit, OnDestroy {
|
|||
this.loginRes = this.sessionStorageService.get<LoginResponse>(
|
||||
KEY_LOGIN_RES_INFO
|
||||
);
|
||||
this.environmentsInfo = this.sessionStorageService.get<EnvironmentsInfo>(
|
||||
KEY_ENVIRONMENTS_INFO
|
||||
);
|
||||
|
||||
this.userInfo = data.userInfo;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -156,8 +183,68 @@ export class ProfileDialogComponent implements OnInit, OnDestroy {
|
|||
this.store.dispatch(
|
||||
SyncStore.delBuddyAndClear({ seq: param.userInfo.seq })
|
||||
);
|
||||
this.isBuddy = false
|
||||
this.isBuddy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onUploadProfileImage(profileImageFileUploadItem: FileUploadItem) {
|
||||
this.commonApiService
|
||||
.fileProfileSave(
|
||||
{
|
||||
userSeq: this.loginRes.userSeq,
|
||||
deviceType: this.environmentsInfo.deviceType,
|
||||
token: this.loginRes.tokenString,
|
||||
file: profileImageFileUploadItem.file,
|
||||
fileUploadItem: profileImageFileUploadItem
|
||||
},
|
||||
this.sessionVerinfo.profileUploadUrl
|
||||
)
|
||||
.pipe(
|
||||
take(1),
|
||||
map(res => {
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
if (StatusCode.Success === res.statusCode) {
|
||||
return res;
|
||||
} else {
|
||||
throw res;
|
||||
}
|
||||
}),
|
||||
finalize(() => {
|
||||
setTimeout(() => {
|
||||
profileImageFileUploadItem.uploadingProgress$ = undefined;
|
||||
}, 1000);
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
res => {
|
||||
const userInfo = {
|
||||
...this.loginRes.userInfo,
|
||||
profileImageFile: res.profileSubDir
|
||||
};
|
||||
|
||||
this.store.dispatch(
|
||||
AuthenticationStore.updateLoginRes({
|
||||
loginRes: {
|
||||
...this.loginRes,
|
||||
userInfo
|
||||
}
|
||||
})
|
||||
);
|
||||
this.userInfo = userInfo as any;
|
||||
},
|
||||
error => {
|
||||
this.snackBarService.open(
|
||||
`프로필 이미지 변경중에 문제가 발생하였습니다.`,
|
||||
'',
|
||||
{
|
||||
duration: 3000,
|
||||
verticalPosition: 'bottom'
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,3 +113,10 @@ export const userPasswordSetFailure = createAction(
|
|||
'[Account::Authentication] User Password Set Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
||||
export const updateLoginRes = createAction(
|
||||
'[Account::Authentication] Update LoginRes',
|
||||
props<{
|
||||
loginRes: LoginResponse;
|
||||
}>()
|
||||
);
|
||||
|
|
|
@ -5,7 +5,8 @@ import {
|
|||
increaseLoginFailCount,
|
||||
initialLoginFailCount,
|
||||
logout,
|
||||
logoutInitialize
|
||||
logoutInitialize,
|
||||
updateLoginRes
|
||||
} from './actions';
|
||||
|
||||
export const reducer = createReducer(
|
||||
|
@ -17,6 +18,13 @@ export const reducer = createReducer(
|
|||
};
|
||||
}),
|
||||
|
||||
on(updateLoginRes, (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
loginRes: action.loginRes
|
||||
};
|
||||
}),
|
||||
|
||||
on(increaseLoginFailCount, (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -15,6 +15,39 @@
|
|||
[path]="userInfo.profileImageFile"
|
||||
[default]="'assets/images/img_nophoto_50.png'"
|
||||
/>
|
||||
|
||||
<mat-spinner
|
||||
*ngIf="
|
||||
profileImageFileUploadItem &&
|
||||
profileImageFileUploadItem.uploadingProgress$
|
||||
"
|
||||
mode="determinate"
|
||||
strokeWidth="5"
|
||||
diameter="84"
|
||||
[value]="profileImageFileUploadItem.uploadingProgress$ | async"
|
||||
class="upload-profile-image-spinner"
|
||||
></mat-spinner>
|
||||
|
||||
<button
|
||||
mat-mini-fab
|
||||
class="mat-elevation-z6 upload-profile-image-btn"
|
||||
*ngIf="isMe"
|
||||
matTooltip="프로필 이미지 변경"
|
||||
matTooltipPosition="above"
|
||||
[disabled]="
|
||||
profileImageFileUploadItem &&
|
||||
profileImageFileUploadItem.uploadingProgress$
|
||||
"
|
||||
(click)="profileImageFileInput.click()"
|
||||
>
|
||||
<span class="mdi mdi-upload mdi-24px"></span>
|
||||
</button>
|
||||
<input
|
||||
type="file"
|
||||
#profileImageFileInput
|
||||
style="display: none"
|
||||
(change)="onChangeFileInput()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isMe" class="profile-option">
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
::ng-deep .mat-card-header-text{
|
||||
width:100%;
|
||||
.mat-card-subtitle{
|
||||
::ng-deep .mat-card-header-text {
|
||||
width: 100%;
|
||||
.mat-card-subtitle {
|
||||
color: rgb(256, 256, 256, 0.7) !important;
|
||||
text-align:center;
|
||||
margin-top:10px !important;
|
||||
text-align: center;
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,78 +25,90 @@
|
|||
width: 400px;
|
||||
padding: 0 0 20px;
|
||||
position: relative;
|
||||
.mat-card-header{
|
||||
.mat-card-header {
|
||||
justify-content: center;
|
||||
padding-bottom: 40px;
|
||||
background: #76d9c5;
|
||||
/*background: linear-gradient(to right, #345385, #ef4c73);*/
|
||||
color: #ffffff;
|
||||
padding-top: 20px;
|
||||
width:100%;
|
||||
.mat-card-title{
|
||||
width: 100%;
|
||||
.mat-card-title {
|
||||
margin-bottom: 12px;
|
||||
max-width: 100%;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
margin:0 20px;
|
||||
span{
|
||||
margin: 0 20px;
|
||||
span {
|
||||
@include ellipsis(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.mat-card-content{
|
||||
margin-top:-40px;
|
||||
.profile-img{
|
||||
display:flex;
|
||||
height:80px;
|
||||
.mat-card-content {
|
||||
margin-top: -40px;
|
||||
.profile-img {
|
||||
display: flex;
|
||||
height: 80px;
|
||||
justify-content: center;
|
||||
margin-bottom:20px;
|
||||
img{
|
||||
margin-bottom: 20px;
|
||||
img {
|
||||
widows: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background-color:#efefef;
|
||||
border:2px solid #ffffff;
|
||||
background-color: #efefef;
|
||||
border: 2px solid #ffffff;
|
||||
}
|
||||
|
||||
.upload-profile-image-spinner {
|
||||
position: absolute;
|
||||
top: 90px;
|
||||
left: 160px;
|
||||
}
|
||||
|
||||
.upload-profile-image-btn {
|
||||
position: absolute;
|
||||
top: 140px;
|
||||
left: 210px;
|
||||
}
|
||||
}
|
||||
.profile-option{
|
||||
display:flex;
|
||||
padding:0 20px;
|
||||
color:#ffffff;
|
||||
.profile-option {
|
||||
display: flex;
|
||||
padding: 0 20px;
|
||||
color: #ffffff;
|
||||
margin-top: -100px;
|
||||
height: 120px;
|
||||
.btn-favorite{
|
||||
.btn-favorite {
|
||||
cursor: pointer;
|
||||
.on{
|
||||
fill:yellow;
|
||||
.on {
|
||||
fill: yellow;
|
||||
}
|
||||
}
|
||||
.btn-groupadd{
|
||||
margin-left:auto;
|
||||
.btn-groupadd {
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
svg{
|
||||
display:none;
|
||||
&.on{
|
||||
display:block;
|
||||
svg {
|
||||
display: none;
|
||||
&.on {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ul{
|
||||
padding:0 20px;
|
||||
display:flex;
|
||||
ul {
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin-top:-20px;
|
||||
li{
|
||||
display:inline-flex;
|
||||
height:30px;
|
||||
margin-top: -20px;
|
||||
li {
|
||||
display: inline-flex;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
flex-flow:row;
|
||||
margin-bottom:20px;
|
||||
flex-flow: row;
|
||||
margin-bottom: 20px;
|
||||
|
||||
svg{
|
||||
margin-right:10px;
|
||||
color:#777777;
|
||||
svg {
|
||||
margin-right: 10px;
|
||||
color: #777777;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Input,
|
||||
EventEmitter,
|
||||
Output,
|
||||
ViewChild,
|
||||
ElementRef
|
||||
} from '@angular/core';
|
||||
|
||||
import { UserInfo } from '@ucap-webmessenger/protocol-sync';
|
||||
import {
|
||||
UserInfoSS,
|
||||
UserInfoF,
|
||||
UserInfoDN
|
||||
} from '@ucap-webmessenger/protocol-query';
|
||||
import { UserInfoF } from '@ucap-webmessenger/protocol-query';
|
||||
import { FileUploadItem } from '@ucap-webmessenger/api-common';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-profile-profile',
|
||||
|
@ -37,6 +42,14 @@ export class ProfileComponent implements OnInit {
|
|||
isBuddy: boolean;
|
||||
}>();
|
||||
|
||||
@Output()
|
||||
uploadProfileImage = new EventEmitter<FileUploadItem>();
|
||||
|
||||
@ViewChild('profileImageFileInput', { static: false })
|
||||
profileImageFileInput: ElementRef<HTMLInputElement>;
|
||||
|
||||
profileImageFileUploadItem: FileUploadItem;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
@ -73,4 +86,14 @@ export class ProfileComponent implements OnInit {
|
|||
isBuddy: false
|
||||
});
|
||||
}
|
||||
|
||||
onChangeFileInput() {
|
||||
this.profileImageFileUploadItem = FileUploadItem.fromFiles(
|
||||
this.profileImageFileInput.nativeElement.files
|
||||
)[0];
|
||||
|
||||
this.uploadProfileImage.emit(this.profileImageFileUploadItem);
|
||||
|
||||
this.profileImageFileInput.nativeElement.value = '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { FlexLayoutModule } from '@angular/flex-layout';
|
|||
import { MatRippleModule, MatCheckboxModule } from '@angular/material';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
|
||||
import { UCapUiModule } from '@ucap-webmessenger/ui';
|
||||
|
||||
|
@ -35,6 +36,7 @@ const SERVICES = [];
|
|||
MatCheckboxModule,
|
||||
MatCardModule,
|
||||
MatTooltipModule,
|
||||
MatProgressSpinnerModule,
|
||||
|
||||
UCapUiModule
|
||||
],
|
||||
|
|
|
@ -5,15 +5,19 @@ import {
|
|||
Output,
|
||||
Input,
|
||||
AfterViewInit,
|
||||
OnInit
|
||||
OnInit,
|
||||
OnChanges,
|
||||
SimpleChanges
|
||||
} from '@angular/core';
|
||||
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
|
||||
const PATH = 'path';
|
||||
|
||||
@Directive({
|
||||
selector: 'img[ucapImage]'
|
||||
})
|
||||
export class ImageDirective implements OnInit, AfterViewInit {
|
||||
export class ImageDirective implements OnInit, AfterViewInit, OnChanges {
|
||||
@Input()
|
||||
base: string;
|
||||
|
||||
|
@ -50,6 +54,19 @@ export class ImageDirective implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.loadImage();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
const pathChanges = changes[PATH];
|
||||
|
||||
if (!!pathChanges && !pathChanges.firstChange) {
|
||||
this.loadImage();
|
||||
this.logger.debug('ucapImage.ngOnChanges', changes);
|
||||
}
|
||||
}
|
||||
|
||||
private loadImage(): void {
|
||||
if (this.imageSrc === this.default) {
|
||||
this.elementRef.nativeElement.src = this.default;
|
||||
this.loaded.emit(this.elementRef.nativeElement);
|
||||
|
|
Loading…
Reference in New Issue
Block a user