Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ability to remove one's avatar for account and channels #3467

Merged
merged 3 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ <h1 class="sr-only" i18n>Settings</h1>
<div class="form-group col-12 col-lg-4 col-xl-3"></div>

<div class="form-group col-12 col-lg-8 col-xl-9">
<my-actor-avatar-info [actor]="user.account" (avatarChange)="onAvatarChange($event)"></my-actor-avatar-info>
<my-actor-avatar-info [actor]="user.account" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"></my-actor-avatar-info>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

<my-actor-avatar-info
*ngIf="!isCreation() && videoChannelToUpdate"
[actor]="videoChannelToUpdate" (avatarChange)="onAvatarChange($event)"
[actor]="videoChannelToUpdate" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"
></my-actor-avatar-info>

<div class="form-group">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
}
Expand Down
5 changes: 3 additions & 2 deletions client/src/app/core/users/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
10 changes: 10 additions & 0 deletions client/src/app/core/users/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
5 changes: 5 additions & 0 deletions client/src/app/shared/shared-main/account/account.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
<img [src]="actor.avatarUrl" alt="Avatar" />

<div class="actor-img-edit-container">
<div class="actor-img-edit-button" [ngbTooltip]="avatarFormat"
placement="right" container="body">

<div *ngIf="!hasAvatar" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body">
<my-global-icon iconName="upload"></my-global-icon>
<label class="sr-only" for="avatarfile" i18n>Upload a new avatar</label>
<input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
</div>

<div *ngIf="hasAvatar" class="actor-img-edit-button" #avatarPopover="ngbPopover" [ngbPopover]="avatarEditContent" popoverClass="popover-avatar-info" autoClose="outside" placement="right">
<my-global-icon iconName="edit"></my-global-icon>
<label for="avatarfile" i18n>Change your avatar</label>
<input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/>
<label class="sr-only" for="avatarMenu" i18n>Change your avatar</label>
</div>

</div>
</div>

Expand All @@ -22,4 +28,16 @@
<div i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div>
</div>
</div>
</ng-container>
</ng-container>

<ng-template #avatarEditContent>
<div class="dropdown-item c-hand" [ngbTooltip]="avatarFormat" placement="right" container="body">
<my-global-icon iconName="upload"></my-global-icon>
<span for="avatarfile" i18n>Upload a new avatar</span>
<input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
</div>
<div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()">
<my-global-icon iconName="delete"></my-global-icon>
<span i18n>Remove avatar</span>
</div>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,16 @@
}
}
}

::ng-deep .popover-avatar-info .popover-body {
rigelk marked this conversation as resolved.
Show resolved Hide resolved
padding: 0;

.dropdown-item {
padding: 6px 10px;
border-radius: 4px;

&:first-child {
@include peertube-file;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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',
Expand All @@ -12,10 +14,12 @@ import { Account } from '../account/account.model'
})
export class ActorAvatarInfoComponent implements OnInit {
@ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement>
@ViewChild('avatarPopover') avatarPopover: NgbPopover

@Input() actor: VideoChannel | Account

@Output() avatarChange = new EventEmitter<FormData>()
@Output() avatarDelete = new EventEmitter<void>()

private serverConfig: ServerConfig

Expand All @@ -30,7 +34,9 @@ export class ActorAvatarInfoComponent implements OnInit {
.subscribe(config => this.serverConfig = config)
}

onAvatarChange () {
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.')
Expand All @@ -39,10 +45,14 @@ export class ActorAvatarInfoComponent implements OnInit {

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
}
Expand All @@ -58,4 +68,8 @@ export class ActorAvatarInfoComponent implements OnInit {
get avatarFormat () {
return `${$localize`max size`}: 192*192px, ${this.maxAvatarSizeInBytes} ${$localize`extensions`}: ${this.avatarExtensions}`
}

get hasAvatar () {
return Actor.GET_ACTOR_AVATAR_URL(this.actor)
rigelk marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 8 additions & 4 deletions client/src/sass/include/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,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;
Expand All @@ -281,6 +278,13 @@
}
}

@mixin peertube-button-file ($width) {
width: $width;

@include peertube-file;
@include peertube-button;
}

@mixin icon ($size) {
display: inline-block;
background-repeat: no-repeat;
Expand Down
18 changes: 16 additions & 2 deletions server/controllers/api/users/me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CONFIG } from '../../../initializers/config'
import { MIMETYPES } from '../../../initializers/constants'
import { sequelizeTypescript } from '../../../initializers/database'
import { sendUpdateActor } from '../../../lib/activitypub/send'
import { updateActorAvatarFile } from '../../../lib/avatar'
import { deleteActorAvatarFile, updateActorAvatarFile } from '../../../lib/avatar'
import { getOriginalVideoFileTotalDailyFromUser, getOriginalVideoFileTotalFromUser, sendVerifyUserEmail } from '../../../lib/user'
import {
asyncMiddleware,
Expand Down Expand Up @@ -86,6 +86,11 @@ meRouter.post('/me/avatar/pick',
asyncRetryTransactionMiddleware(updateMyAvatar)
)

meRouter.delete('/me/avatar',
authenticate,
asyncRetryTransactionMiddleware(deleteMyAvatar)
)

// ---------------------------------------------------------------------------

export {
Expand Down Expand Up @@ -220,7 +225,16 @@ async function updateMyAvatar (req: express.Request, res: express.Response) {

const userAccount = await AccountModel.load(user.Account.id)

const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount)
const avatar = await updateActorAvatarFile(userAccount, avatarPhysicalFile)

return res.json({ avatar: avatar.toFormattedJSON() })
}

async function deleteMyAvatar (req: express.Request, res: express.Response) {
const user = res.locals.oauth.token.user

const userAccount = await AccountModel.load(user.Account.id)
await deleteActorAvatarFile(userAccount)

return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}
19 changes: 17 additions & 2 deletions server/controllers/api/video-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { MIMETYPES } from '../../initializers/constants'
import { sequelizeTypescript } from '../../initializers/database'
import { setAsyncActorKeys } from '../../lib/activitypub/actor'
import { sendUpdateActor } from '../../lib/activitypub/send'
import { updateActorAvatarFile } from '../../lib/avatar'
import { deleteActorAvatarFile, updateActorAvatarFile } from '../../lib/avatar'
import { JobQueue } from '../../lib/job-queue'
import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
import {
Expand Down Expand Up @@ -70,6 +70,13 @@ videoChannelRouter.post('/:nameWithHost/avatar/pick',
asyncMiddleware(updateVideoChannelAvatar)
)

videoChannelRouter.delete('/:nameWithHost/avatar',
authenticate,
// Check the rights
asyncMiddleware(videoChannelsUpdateValidator),
asyncMiddleware(deleteVideoChannelAvatar)
)

videoChannelRouter.put('/:nameWithHost',
authenticate,
asyncMiddleware(videoChannelsUpdateValidator),
Expand Down Expand Up @@ -133,7 +140,7 @@ async function updateVideoChannelAvatar (req: express.Request, res: express.Resp
const videoChannel = res.locals.videoChannel
const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())

const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel)
const avatar = await updateActorAvatarFile(videoChannel, avatarPhysicalFile)

auditLogger.update(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannel.toFormattedJSON()), oldVideoChannelAuditKeys)

Expand All @@ -144,6 +151,14 @@ async function updateVideoChannelAvatar (req: express.Request, res: express.Resp
.end()
}

async function deleteVideoChannelAvatar (req: express.Request, res: express.Response) {
const videoChannel = res.locals.videoChannel

await deleteActorAvatarFile(videoChannel)

return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}

async function addVideoChannel (req: express.Request, res: express.Response) {
const videoChannelInfo: VideoChannelCreate = req.body

Expand Down
Loading