From 1ea7da819e5bfae7b443ed722c18c4165d101439 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Wed, 13 Jan 2021 09:12:55 +0100 Subject: add ability to remove one's avatar for account and channels (#3467) * add ability to remove one's avatar for account and channels * add ability to remove one's avatar for account and channels * only display avatar edition options after input change --- .../my-account-settings.component.html | 2 +- .../my-account-settings.component.ts | 13 +++++++++ .../my-video-channel-edit.component.html | 2 +- .../+my-video-channels/my-video-channel-edit.ts | 1 + .../my-video-channel-update.component.ts | 21 ++++++++++++++- client/src/app/core/users/user.model.ts | 5 ++-- client/src/app/core/users/user.service.ts | 10 +++++++ .../shared/shared-main/account/account.model.ts | 5 ++++ .../account/actor-avatar-info.component.html | 28 +++++++++++++++---- .../account/actor-avatar-info.component.scss | 14 ++++++++++ .../account/actor-avatar-info.component.ts | 31 ++++++++++++++++++---- .../video-channel/video-channel.model.ts | 5 ++++ .../video-channel/video-channel.service.ts | 10 +++++++ client/src/sass/include/_mixins.scss | 12 ++++++--- 14 files changed, 140 insertions(+), 19 deletions(-) (limited to 'client') diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html index 40505b92f..b0d2ec58d 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html @@ -3,7 +3,7 @@
- +
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts index d5d019b35..c16368952 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts @@ -53,4 +53,17 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked { }) ) } + + onAvatarDelete () { + this.userService.deleteAvatar() + .subscribe( + data => { + this.notifier.success($localize`Avatar deleted.`) + + this.user.updateAccountAvatar() + }, + + (err: HttpErrorResponse) => this.notifier.error(err.message) + ) + } } diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html index 5ea000400..735f9e3ba 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html +++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html @@ -46,7 +46,7 @@
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.ts b/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.ts index 09db0df9d..3e20a27ee 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.ts +++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.ts @@ -14,6 +14,7 @@ export abstract class MyVideoChannelEdit extends FormReactive { // We need this method so angular does not complain in child template that doesn't need this onAvatarChange (formData: FormData) { /* empty */ } + onAvatarDelete () { /* empty */ } // Should be implemented by the child isBulkUpdateVideosDisplayed () { diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts index c6cb5ade6..6cd1ff503 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts +++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts @@ -11,6 +11,8 @@ import { FormValidatorService } from '@app/shared/shared-forms' import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' import { ServerConfig, VideoChannelUpdate } from '@shared/models' import { MyVideoChannelEdit } from './my-video-channel-edit' +import { HttpErrorResponse } from '@angular/common/http' +import { uploadErrorHandler } from '@app/helpers' @Component({ selector: 'my-video-channel-update', @@ -107,10 +109,27 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements this.videoChannelToUpdate.updateAvatar(data.avatar) }, - err => this.notifier.error(err.message) + (err: HttpErrorResponse) => uploadErrorHandler({ + err, + name: $localize`avatar`, + notifier: this.notifier + }) ) } + onAvatarDelete () { + this.videoChannelService.deleteVideoChannelAvatar(this.videoChannelToUpdate.name) + .subscribe( + data => { + this.notifier.success($localize`Avatar deleted.`) + + this.videoChannelToUpdate.resetAvatar() + }, + + err => this.notifier.error(err.message) + ) + } + get maxAvatarSize () { return this.serverConfig.avatar.file.size.max } diff --git a/client/src/app/core/users/user.model.ts b/client/src/app/core/users/user.model.ts index 7c9569ed4..15a4f7f82 100644 --- a/client/src/app/core/users/user.model.ts +++ b/client/src/app/core/users/user.model.ts @@ -131,8 +131,9 @@ export class User implements UserServerModel { } } - updateAccountAvatar (newAccountAvatar: Avatar) { - this.account.updateAvatar(newAccountAvatar) + updateAccountAvatar (newAccountAvatar?: Avatar) { + if (newAccountAvatar) this.account.updateAvatar(newAccountAvatar) + else this.account.resetAvatar() } isUploadDisabled () { diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts index 2f3945169..99ed3b407 100644 --- a/client/src/app/core/users/user.service.ts +++ b/client/src/app/core/users/user.service.ts @@ -123,6 +123,16 @@ export class UserService { .pipe(catchError(err => this.restExtractor.handleError(err))) } + deleteAvatar () { + const url = UserService.BASE_USERS_URL + 'me/avatar' + + return this.authHttp.delete(url) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(err => this.restExtractor.handleError(err)) + ) + } + signup (userCreate: UserRegister) { return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate) .pipe( diff --git a/client/src/app/shared/shared-main/account/account.model.ts b/client/src/app/shared/shared-main/account/account.model.ts index b3dc6cfe5..b71a893d1 100644 --- a/client/src/app/shared/shared-main/account/account.model.ts +++ b/client/src/app/shared/shared-main/account/account.model.ts @@ -44,6 +44,11 @@ export class Account extends Actor implements ServerAccount { this.updateComputedAttributes() } + resetAvatar () { + this.avatar = null + this.avatarUrl = Account.GET_DEFAULT_AVATAR_URL() + } + private updateComputedAttributes () { this.avatarUrl = Account.GET_ACTOR_AVATAR_URL(this) } diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.html b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html index e63d8de2d..a34d27b26 100644 --- a/client/src/app/shared/shared-main/account/actor-avatar-info.component.html +++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html @@ -4,12 +4,18 @@ Avatar
-
+ +
+ + + +
+ +
- - +
+
@@ -22,4 +28,16 @@
{{ actor.followersCount }} subscribers
- \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss b/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss index 7118e9471..57c298508 100644 --- a/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss +++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss @@ -70,3 +70,17 @@ } } } + +.actor-img-edit-container ::ng-deep .popover-avatar-info .popover-body { + padding: 0; + + .dropdown-item { + padding: 6px 10px; + border-radius: 4px; + + &:first-child { + @include peertube-file; + display: block; + } + } +} diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts index de78a390e..451bbbba3 100644 --- a/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts +++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts @@ -1,22 +1,27 @@ -import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' +import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core' import { Notifier, ServerService } from '@app/core' import { getBytes } from '@root-helpers/bytes' import { ServerConfig } from '@shared/models' import { VideoChannel } from '../video-channel/video-channel.model' import { Account } from '../account/account.model' +import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' +import { Actor } from './actor.model' @Component({ selector: 'my-actor-avatar-info', templateUrl: './actor-avatar-info.component.html', styleUrls: [ './actor-avatar-info.component.scss' ] }) -export class ActorAvatarInfoComponent implements OnInit { +export class ActorAvatarInfoComponent implements OnInit, OnChanges { @ViewChild('avatarfileInput') avatarfileInput: ElementRef + @ViewChild('avatarPopover') avatarPopover: NgbPopover @Input() actor: VideoChannel | Account @Output() avatarChange = new EventEmitter() + @Output() avatarDelete = new EventEmitter() + private avatarUrl: string private serverConfig: ServerConfig constructor ( @@ -30,19 +35,31 @@ export class ActorAvatarInfoComponent implements OnInit { .subscribe(config => this.serverConfig = config) } - onAvatarChange () { + ngOnChanges (changes: SimpleChanges) { + if (changes['actor']) { + this.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.actor) + } + } + + onAvatarChange (input: HTMLInputElement) { + this.avatarfileInput = new ElementRef(input) + const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ] if (avatarfile.size > this.maxAvatarSize) { - this.notifier.error('Error', 'This image is too large.') + this.notifier.error('Error', $localize`This image is too large.`) return } const formData = new FormData() formData.append('avatarfile', avatarfile) - + this.avatarPopover?.close() this.avatarChange.emit(formData) } + deleteAvatar () { + this.avatarDelete.emit() + } + get maxAvatarSize () { return this.serverConfig.avatar.file.size.max } @@ -58,4 +75,8 @@ export class ActorAvatarInfoComponent implements OnInit { get avatarFormat () { return `${$localize`max size`}: 192*192px, ${this.maxAvatarSizeInBytes} ${$localize`extensions`}: ${this.avatarExtensions}` } + + get hasAvatar () { + return !!this.avatarUrl + } } diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts index 4f1f5b65d..c6a63fe6c 100644 --- a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts +++ b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts @@ -56,6 +56,11 @@ export class VideoChannel extends Actor implements ServerVideoChannel { this.updateComputedAttributes() } + resetAvatar () { + this.avatar = null + this.avatarUrl = VideoChannel.GET_DEFAULT_AVATAR_URL() + } + private updateComputedAttributes () { this.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(this) } diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.service.ts b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts index 64dcf638a..eff3fad4d 100644 --- a/client/src/app/shared/shared-main/video-channel/video-channel.service.ts +++ b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts @@ -89,6 +89,16 @@ export class VideoChannelService { .pipe(catchError(err => this.restExtractor.handleError(err))) } + deleteVideoChannelAvatar (videoChannelName: string) { + const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName + '/avatar' + + return this.authHttp.delete(url) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(err => this.restExtractor.handleError(err)) + ) + } + removeVideoChannel (videoChannel: VideoChannel) { return this.authHttp.delete(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost) .pipe( diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 0ce22354e..51cf4c3ed 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss @@ -260,15 +260,12 @@ } } -@mixin peertube-button-file ($width) { +@mixin peertube-file { position: relative; overflow: hidden; display: inline-block; - width: $width; min-height: 30px; - @include peertube-button; - input[type=file] { position: absolute; top: 0; @@ -286,6 +283,13 @@ } } +@mixin peertube-button-file ($width) { + width: $width; + + @include peertube-file; + @include peertube-button; +} + @mixin icon ($size) { display: inline-block; background-repeat: no-repeat; -- cgit v1.2.3