Set channel banner/avatar in creation form

pull/3747/head
Chocobozzz 2021-04-08 12:09:54 +02:00
parent 84531547bc
commit 27ec473f53
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
10 changed files with 112 additions and 57 deletions

View File

@ -8,10 +8,12 @@ import {
VIDEO_CHANNEL_SUPPORT_VALIDATOR
} from '@app/shared/form-validators/video-channel-validators'
import { FormValidatorService } from '@app/shared/shared-forms'
import { VideoChannelService } from '@app/shared/shared-main'
import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
import { VideoChannelCreate } from '@shared/models'
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
import { MyVideoChannelEdit } from './my-video-channel-edit'
import { switchMap } from 'rxjs/operators'
import { of } from 'rxjs'
@Component({
templateUrl: './my-video-channel-edit.component.html',
@ -19,6 +21,10 @@ import { MyVideoChannelEdit } from './my-video-channel-edit'
})
export class MyVideoChannelCreateComponent extends MyVideoChannelEdit implements OnInit {
error: string
videoChannel = new VideoChannel({})
private avatar: FormData
private banner: FormData
constructor (
protected formValidatorService: FormValidatorService,
@ -50,23 +56,43 @@ export class MyVideoChannelCreateComponent extends MyVideoChannelEdit implements
support: body.support || null
}
this.videoChannelService.createVideoChannel(videoChannelCreate).subscribe(
() => {
this.authService.refreshUserInformation()
this.videoChannelService.createVideoChannel(videoChannelCreate)
.pipe(
switchMap(() => this.uploadAvatar()),
switchMap(() => this.uploadBanner())
).subscribe(
() => {
this.authService.refreshUserInformation()
this.notifier.success($localize`Video channel ${videoChannelCreate.displayName} created.`)
this.router.navigate([ '/my-library', 'video-channels' ])
},
this.notifier.success($localize`Video channel ${videoChannelCreate.displayName} created.`)
this.router.navigate(['/my-library', 'video-channels'])
},
err => {
if (err.status === HttpStatusCode.CONFLICT_409) {
this.error = $localize`This name already exists on this instance.`
return
err => {
if (err.status === HttpStatusCode.CONFLICT_409) {
this.error = $localize`This name already exists on this instance.`
return
}
this.error = err.message
}
)
}
this.error = err.message
}
)
onAvatarChange (formData: FormData) {
this.avatar = formData
}
onAvatarDelete () {
this.avatar = null
}
onBannerChange (formData: FormData) {
this.banner = formData
}
onBannerDelete () {
this.banner = null
}
isCreation () {
@ -76,4 +102,20 @@ export class MyVideoChannelCreateComponent extends MyVideoChannelEdit implements
getFormButtonTitle () {
return $localize`Create`
}
getUsername () {
return this.form.value.name
}
private uploadAvatar () {
if (!this.avatar) return of(undefined)
return this.videoChannelService.changeVideoChannelImage(this.getUsername(), this.avatar, 'avatar')
}
private uploadBanner () {
if (!this.banner) return of(undefined)
return this.videoChannelService.changeVideoChannelImage(this.getUsername(), this.banner, 'banner')
}
}

View File

@ -10,7 +10,7 @@
<ng-container *ngIf="!isCreation()">
<li class="breadcrumb-item active" i18n>Edit</li>
<li class="breadcrumb-item active" aria-current="page">
<a *ngIf="videoChannelToUpdate" [routerLink]="[ '/my-library/video-channels/update', videoChannelToUpdate?.nameWithHost ]">{{ videoChannelToUpdate?.displayName }}</a>
<a *ngIf="videoChannel" [routerLink]="[ '/my-library/video-channels/update', videoChannel?.nameWithHost ]">{{ videoChannel?.displayName }}</a>
</li>
</ng-container>
</ol>
@ -23,10 +23,22 @@
<div class="form-row"> <!-- channel grid -->
<div class="form-group col-12 col-lg-4 col-xl-3">
<div *ngIf="isCreation()" class="video-channel-title" i18n>NEW CHANNEL</div>
<div *ngIf="!isCreation() && videoChannelToUpdate" class="video-channel-title" i18n>CHANNEL</div>
<div *ngIf="!isCreation() && videoChannel" class="video-channel-title" i18n>CHANNEL</div>
</div>
<div class="form-group col-12 col-lg-8 col-xl-9">
<h6 i18n>Banner image of your channel</h6>
<my-actor-banner-edit
*ngIf="videoChannel" [previewImage]="isCreation()"
[actor]="videoChannel" (bannerChange)="onBannerChange($event)" (bannerDelete)="onBannerDelete()"
></my-actor-banner-edit>
<my-actor-avatar-edit
*ngIf="videoChannel" [previewImage]="isCreation()"
[actor]="videoChannel" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"
[displayUsername]="!isCreation()" [displaySubscribers]="!isCreation()"
></my-actor-avatar-edit>
<div class="form-group" *ngIf="isCreation()">
<label i18n for="name">Name</label>
@ -44,18 +56,6 @@
</div>
</div>
<h6 i18n>Banner image of your channel</h6>
<my-actor-banner-edit
*ngIf="!isCreation() && videoChannelToUpdate"
[actor]="videoChannelToUpdate" (bannerChange)="onBannerChange($event)" (bannerDelete)="onBannerDelete()"
></my-actor-banner-edit>
<my-actor-avatar-edit
*ngIf="!isCreation() && videoChannelToUpdate"
[actor]="videoChannelToUpdate" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"
></my-actor-avatar-edit>
<div class="form-group">
<label i18n for="display-name">Display name</label>
<input

View File

@ -2,8 +2,7 @@ import { FormReactive } from '@app/shared/shared-forms'
import { VideoChannel } from '@app/shared/shared-main'
export abstract class MyVideoChannelEdit extends FormReactive {
// We need it even in the create component because it's used in the edit template
videoChannelToUpdate: VideoChannel
videoChannel: VideoChannel
abstract isCreation (): boolean
abstract getFormButtonTitle (): string
@ -12,12 +11,6 @@ export abstract class MyVideoChannelEdit extends FormReactive {
return window.location.host
}
// We need this method so angular does not complain in child template that doesn't need this
onAvatarChange (formData: FormData) { /* empty */ }
onAvatarDelete () { /* empty */ }
onBannerChange (formData: FormData) { /* empty */ }
onBannerDelete () { /* empty */ }
// Should be implemented by the child
isBulkUpdateVideosDisplayed () {
return false

View File

@ -21,7 +21,7 @@ import { MyVideoChannelEdit } from './my-video-channel-edit'
})
export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements OnInit, OnDestroy {
error: string
videoChannelToUpdate: VideoChannel
videoChannel: VideoChannel
private paramsSub: Subscription
private oldSupportField: string
@ -56,7 +56,7 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
this.videoChannelService.getVideoChannel(videoChannelId).subscribe(
videoChannelToUpdate => {
this.videoChannelToUpdate = videoChannelToUpdate
this.videoChannel = videoChannelToUpdate
this.oldSupportField = videoChannelToUpdate.support
@ -87,7 +87,7 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
bulkVideosSupportUpdate: body.bulkVideosSupportUpdate || false
}
this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe(
this.videoChannelService.updateVideoChannel(this.videoChannel.name, videoChannelUpdate).subscribe(
() => {
this.authService.refreshUserInformation()
@ -101,12 +101,12 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
}
onAvatarChange (formData: FormData) {
this.videoChannelService.changeVideoChannelImage(this.videoChannelToUpdate.name, formData, 'avatar')
this.videoChannelService.changeVideoChannelImage(this.videoChannel.name, formData, 'avatar')
.subscribe(
data => {
this.notifier.success($localize`Avatar changed.`)
this.videoChannelToUpdate.updateAvatar(data.avatar)
this.videoChannel.updateAvatar(data.avatar)
},
(err: HttpErrorResponse) => uploadErrorHandler({
@ -118,12 +118,12 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
}
onAvatarDelete () {
this.videoChannelService.deleteVideoChannelImage(this.videoChannelToUpdate.name, 'avatar')
this.videoChannelService.deleteVideoChannelImage(this.videoChannel.name, 'avatar')
.subscribe(
data => {
this.notifier.success($localize`Avatar deleted.`)
this.videoChannelToUpdate.resetAvatar()
this.videoChannel.resetAvatar()
},
err => this.notifier.error(err.message)
@ -131,12 +131,12 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
}
onBannerChange (formData: FormData) {
this.videoChannelService.changeVideoChannelImage(this.videoChannelToUpdate.name, formData, 'banner')
this.videoChannelService.changeVideoChannelImage(this.videoChannel.name, formData, 'banner')
.subscribe(
data => {
this.notifier.success($localize`Banner changed.`)
this.videoChannelToUpdate.updateBanner(data.banner)
this.videoChannel.updateBanner(data.banner)
},
(err: HttpErrorResponse) => uploadErrorHandler({
@ -148,12 +148,12 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
}
onBannerDelete () {
this.videoChannelService.deleteVideoChannelImage(this.videoChannelToUpdate.name, 'banner')
this.videoChannelService.deleteVideoChannelImage(this.videoChannel.name, 'banner')
.subscribe(
data => {
this.notifier.success($localize`Banner deleted.`)
this.videoChannelToUpdate.resetBanner()
this.videoChannel.resetBanner()
},
err => this.notifier.error(err.message)

View File

@ -1,6 +1,6 @@
<div class="actor" *ngIf="actor">
<div class="d-flex">
<img [ngClass]="{ channel: isChannel() }" [src]="actor.avatarUrl" alt="Avatar" />
<img [ngClass]="{ channel: isChannel() }" [src]="preview || actor.avatarUrl" alt="Avatar" />
<div class="actor-img-edit-container">

View File

@ -1,4 +1,5 @@
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
import { Notifier, ServerService } from '@app/core'
import { Account, VideoChannel } from '@app/shared/shared-main'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
@ -20,6 +21,7 @@ export class ActorAvatarEditComponent implements OnInit {
@Input() editable = true
@Input() displaySubscribers = true
@Input() displayUsername = true
@Input() previewImage = false
@Output() avatarChange = new EventEmitter<FormData>()
@Output() avatarDelete = new EventEmitter<void>()
@ -28,7 +30,10 @@ export class ActorAvatarEditComponent implements OnInit {
maxAvatarSize = 0
avatarExtensions = ''
preview: SafeResourceUrl
constructor (
private sanitizer: DomSanitizer,
private serverService: ServerService,
private notifier: Notifier
) { }
@ -57,14 +62,19 @@ export class ActorAvatarEditComponent implements OnInit {
formData.append('avatarfile', avatarfile)
this.avatarPopover?.close()
this.avatarChange.emit(formData)
if (this.previewImage) {
this.preview = this.sanitizer.bypassSecurityTrustResourceUrl(URL.createObjectURL(avatarfile))
}
}
deleteAvatar () {
this.preview = undefined
this.avatarDelete.emit()
}
hasAvatar () {
return !!this.actor.avatar
return !!this.preview || !!this.actor.avatar
}
isChannel () {

View File

@ -1,7 +1,7 @@
<div class="actor" *ngIf="actor">
<div class="actor-img-edit-container">
<div class="banner-placeholder">
<img *ngIf="hasBanner()" [src]="actor.bannerUrl" alt="Banner" />
<img *ngIf="hasBanner()" [src]="preview || actor.bannerUrl" alt="Banner" />
</div>
<div *ngIf="!hasBanner()" class="actor-img-edit-button" [ngbTooltip]="bannerFormat" placement="right" container="body">

View File

@ -1,4 +1,5 @@
import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
import { Notifier, ServerService } from '@app/core'
import { VideoChannel } from '@app/shared/shared-main'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
@ -17,6 +18,7 @@ export class ActorBannerEditComponent implements OnInit {
@ViewChild('bannerPopover') bannerPopover: NgbPopover
@Input() actor: VideoChannel
@Input() previewImage = false
@Output() bannerChange = new EventEmitter<FormData>()
@Output() bannerDelete = new EventEmitter<void>()
@ -25,7 +27,10 @@ export class ActorBannerEditComponent implements OnInit {
maxBannerSize = 0
bannerExtensions = ''
preview: SafeResourceUrl
constructor (
private sanitizer: DomSanitizer,
private serverService: ServerService,
private notifier: Notifier
) { }
@ -54,13 +59,18 @@ export class ActorBannerEditComponent implements OnInit {
formData.append('bannerfile', bannerfile)
this.bannerPopover?.close()
this.bannerChange.emit(formData)
if (this.previewImage) {
this.preview = this.sanitizer.bypassSecurityTrustResourceUrl(URL.createObjectURL(bannerfile))
}
}
deleteBanner () {
this.preview = undefined
this.bannerDelete.emit()
}
hasBanner () {
return !!this.actor.bannerUrl
return !!this.preview || !!this.actor.bannerUrl
}
}

View File

@ -47,11 +47,11 @@ export abstract class Actor implements ActorServer {
return host.trim() === thisHost
}
protected constructor (hash: ActorServer) {
protected constructor (hash: Partial<ActorServer>) {
this.id = hash.id
this.url = hash.url
this.name = hash.name
this.host = hash.host
this.url = hash.url ?? ''
this.name = hash.name ?? ''
this.host = hash.host ?? ''
this.followingCount = hash.followingCount
this.followersCount = hash.followersCount

View File

@ -44,7 +44,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
return `${window.location.origin}/client/assets/images/default-avatar-videochannel.png`
}
constructor (hash: ServerVideoChannel) {
constructor (hash: Partial<ServerVideoChannel>) {
super(hash)
this.displayName = hash.displayName
@ -93,7 +93,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
this.updateBanner(null)
}
private updateComputedAttributes () {
updateComputedAttributes () {
this.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(this)
this.bannerUrl = VideoChannel.GET_ACTOR_BANNER_URL(this)
}