From 52d9f792b3fee5acce80f948295b59e3ad2073eb Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 29 Jun 2018 14:34:04 +0200 Subject: [PATCH] Client: Add ability to update video channel avatar --- .../my-account-settings.component.html | 18 +------ .../my-account-settings.component.scss | 49 ------------------ .../my-account-settings.component.ts | 21 +------- ...-account-video-channel-edit.component.html | 6 ++- ...-account-video-channel-edit.component.scss | 5 ++ ...-account-video-channel-update.component.ts | 30 +++++++++-- .../src/app/+my-account/my-account.module.ts | 4 +- .../shared/actor-avatar-info.component.html | 19 +++++++ .../shared/actor-avatar-info.component.scss | 51 +++++++++++++++++++ .../shared/actor-avatar-info.component.ts | 48 +++++++++++++++++ client/src/app/shared/actor/actor.model.ts | 10 ++++ client/src/app/shared/users/user.model.ts | 17 +++---- .../video-channel/video-channel.service.ts | 8 +++ client/src/app/shared/video/video.model.ts | 3 ++ .../videos/+video-edit/video-add.component.ts | 8 +-- .../+video-watch/video-watch.component.html | 2 + .../+video-watch/video-watch.component.scss | 7 +++ server/models/video/video.ts | 4 ++ .../tests/api/check-params/video-channels.ts | 5 +- 19 files changed, 207 insertions(+), 108 deletions(-) create mode 100644 client/src/app/+my-account/shared/actor-avatar-info.component.html create mode 100644 client/src/app/+my-account/shared/actor-avatar-info.component.scss create mode 100644 client/src/app/+my-account/shared/actor-avatar-info.component.ts 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 f5d593f19..ff08cb777 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 @@ -1,20 +1,4 @@ -
- Avatar - - -
- -
- Change your avatar - -
-
(extensions: {{ avatarExtensions }}, max size: {{ maxAvatarSize | bytes }})
+
Video quota: {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }} diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss b/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss index ec0d40b93..16f26dfed 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.scss @@ -1,55 +1,6 @@ @import '_variables'; @import '_mixins'; -.user { - display: flex; - - img { - @include avatar(50px); - - margin-right: 15px; - } - - .user-info { - .user-info-names { - display: flex; - align-items: center; - - .user-info-display-name { - font-size: 20px; - font-weight: $font-bold; - } - - .user-info-username { - margin-left: 7px; - position: relative; - top: 2px; - font-size: 14px; - color: #777272; - } - } - - .user-info-followers { - font-size: 15px; - } - } -} - -.button-file { - @include peertube-button-file(160px); - - margin-top: 10px; - margin-bottom: 5px; -} - -.file-max-size { - display: inline-block; - font-size: 13px; - - position: relative; - top: -10px; -} - .user-quota { font-size: 15px; margin-top: 20px; 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 14293f14c..164a46a48 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 @@ -13,8 +13,6 @@ import { I18n } from '@ngx-translate/i18n-polyfill' styleUrls: [ './my-account-settings.component.scss' ] }) export class MyAccountSettingsComponent implements OnInit { - @ViewChild('avatarfileInput') avatarfileInput - user: User = null userVideoQuota = '0' userVideoQuotaUsed = 0 @@ -48,16 +46,7 @@ export class MyAccountSettingsComponent implements OnInit { .subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed) } - changeAvatar () { - const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ] - if (avatarfile.size > this.maxAvatarSize) { - this.notificationsService.error('Error', 'This image is too large.') - return - } - - const formData = new FormData() - formData.append('avatarfile', avatarfile) - + onAvatarChange (formData: FormData) { this.userService.changeAvatar(formData) .subscribe( data => { @@ -69,12 +58,4 @@ export class MyAccountSettingsComponent implements OnInit { err => this.notificationsService.error(this.i18n('Error'), err.message) ) } - - get maxAvatarSize () { - return this.serverService.getConfig().avatar.file.size.max - } - - get avatarExtensions () { - return this.serverService.getConfig().avatar.file.extensions.join(',') - } } diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html index 1c08cfdca..f7ca2ec43 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html @@ -1,5 +1,9 @@ + +
Create a video channel
-
Update {{ videoChannel?.displayName }}
{{ error }}
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss index 6fbb8ae8b..86c2598b7 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss @@ -5,6 +5,11 @@ margin-bottom: 20px; } +my-actor-avatar-info { + display: block; + margin-bottom: 20px; +} + input[type=text] { @include peertube-input-text(340px); diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts index 1510c5015..6db8ea8d6 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { NotificationsService } from 'angular2-notifications' import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' @@ -6,7 +6,7 @@ import { VideoChannelUpdate } from '../../../../../shared/models/videos' import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' import { Subscription } from 'rxjs' import { VideoChannel } from '@app/shared/video-channel/video-channel.model' -import { AuthService } from '@app/core' +import { AuthService, ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service' @@ -17,6 +17,8 @@ import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators styleUrls: [ './my-account-video-channel-edit.component.scss' ] }) export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelEdit implements OnInit, OnDestroy { + @ViewChild('avatarfileInput') avatarfileInput + error: string private videoChannelToUpdate: VideoChannel @@ -30,7 +32,8 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE private router: Router, private route: ActivatedRoute, private videoChannelService: VideoChannelService, - private i18n: I18n + private i18n: I18n, + private serverService: ServerService ) { super() } @@ -89,6 +92,27 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE ) } + onAvatarChange (formData: FormData) { + this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.uuid, formData) + .subscribe( + data => { + this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.')) + + this.videoChannelToUpdate.updateAvatar(data.avatar) + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + + get maxAvatarSize () { + return this.serverService.getConfig().avatar.file.size.max + } + + get avatarExtensions () { + return this.serverService.getConfig().avatar.file.extensions.join(',') + } + isCreation () { return false } diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 7e6b8c03e..2088273e6 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts @@ -10,6 +10,7 @@ import { MyAccountProfileComponent } from '@app/+my-account/my-account-settings/ import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component' import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' +import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' @NgModule({ imports: [ @@ -26,7 +27,8 @@ import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-accoun MyAccountVideosComponent, MyAccountVideoChannelsComponent, MyAccountVideoChannelCreateComponent, - MyAccountVideoChannelUpdateComponent + MyAccountVideoChannelUpdateComponent, + ActorAvatarInfoComponent ], exports: [ diff --git a/client/src/app/+my-account/shared/actor-avatar-info.component.html b/client/src/app/+my-account/shared/actor-avatar-info.component.html new file mode 100644 index 000000000..8bdff2f5a --- /dev/null +++ b/client/src/app/+my-account/shared/actor-avatar-info.component.html @@ -0,0 +1,19 @@ + +
+ Avatar + +
+
+
{{ actor.displayName }}
+
{{ actor.name }}
+
+
{{ actor.followersCount }} subscribers
+
+
+ +
+ Change the avatar + +
+
(extensions: {{ avatarExtensions }}, max size: {{ maxAvatarSize | bytes }})
+
\ No newline at end of file diff --git a/client/src/app/+my-account/shared/actor-avatar-info.component.scss b/client/src/app/+my-account/shared/actor-avatar-info.component.scss new file mode 100644 index 000000000..36a792f82 --- /dev/null +++ b/client/src/app/+my-account/shared/actor-avatar-info.component.scss @@ -0,0 +1,51 @@ +@import '_variables'; +@import '_mixins'; + +.actor { + display: flex; + + img { + @include avatar(50px); + + margin-right: 15px; + } + + .actor-info { + .actor-info-names { + display: flex; + align-items: center; + + .actor-info-display-name { + font-size: 20px; + font-weight: $font-bold; + } + + .actor-info-username { + margin-left: 7px; + position: relative; + top: 2px; + font-size: 14px; + color: #777272; + } + } + + .actor-info-followers { + font-size: 15px; + } + } +} + +.button-file { + @include peertube-button-file(160px); + + margin-top: 10px; + margin-bottom: 5px; +} + +.file-max-size { + display: inline-block; + font-size: 13px; + + position: relative; + top: -10px; +} \ No newline at end of file diff --git a/client/src/app/+my-account/shared/actor-avatar-info.component.ts b/client/src/app/+my-account/shared/actor-avatar-info.component.ts new file mode 100644 index 000000000..e0b25ad33 --- /dev/null +++ b/client/src/app/+my-account/shared/actor-avatar-info.component.ts @@ -0,0 +1,48 @@ +import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core' +import { AuthService } from '../../core' +import { ServerService } from '../../core/server' +import { UserService } from '../../shared/users' +import { NotificationsService } from 'angular2-notifications' +import { VideoChannel } from '@app/shared/video-channel/video-channel.model' +import { Account } from '@app/shared/account/account.model' + +@Component({ + selector: 'my-actor-avatar-info', + templateUrl: './actor-avatar-info.component.html', + styleUrls: [ './actor-avatar-info.component.scss' ] +}) +export class ActorAvatarInfoComponent { + @ViewChild('avatarfileInput') avatarfileInput + + @Input() actor: VideoChannel | Account + + @Output() avatarChange = new EventEmitter() + + constructor ( + private userService: UserService, + private authService: AuthService, + private serverService: ServerService, + private notificationsService: NotificationsService + ) {} + + onAvatarChange () { + const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ] + if (avatarfile.size > this.maxAvatarSize) { + this.notificationsService.error('Error', 'This image is too large.') + return + } + + const formData = new FormData() + formData.append('avatarfile', avatarfile) + + this.avatarChange.emit(formData) + } + + get maxAvatarSize () { + return this.serverService.getConfig().avatar.file.size.max + } + + get avatarExtensions () { + return this.serverService.getConfig().avatar.file.extensions.join(',') + } +} diff --git a/client/src/app/shared/actor/actor.model.ts b/client/src/app/shared/actor/actor.model.ts index f820dc3c4..811afb449 100644 --- a/client/src/app/shared/actor/actor.model.ts +++ b/client/src/app/shared/actor/actor.model.ts @@ -45,6 +45,16 @@ export abstract class Actor implements ActorServer { this.updatedAt = new Date(hash.updatedAt.toString()) this.avatar = hash.avatar + this.updateComputedAttributes() + } + + updateAvatar (newAvatar: Avatar) { + this.avatar = newAvatar + + this.updateComputedAttributes() + } + + private updateComputedAttributes () { this.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(this) } } diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 60a0f26df..581ea7859 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts @@ -34,7 +34,6 @@ export class User implements UserServerModel { account: Account videoChannels: VideoChannel[] createdAt: Date - accountAvatarUrl: string constructor (hash: UserConstructorHash) { this.id = hash.id @@ -65,8 +64,12 @@ export class User implements UserServerModel { if (hash.createdAt !== undefined) { this.createdAt = hash.createdAt } + } + + get accountAvatarUrl () { + if (!this.account) return '' - this.updateComputedAttributes() + return this.account.avatarUrl } hasRight (right: UserRight) { @@ -81,17 +84,9 @@ export class User implements UserServerModel { if (obj.account !== undefined) { this.account = new Account(obj.account) } - - this.updateComputedAttributes() } updateAccountAvatar (newAccountAvatar: Avatar) { - this.account.avatar = newAccountAvatar - - this.updateComputedAttributes() - } - - private updateComputedAttributes () { - this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) + this.account.updateAvatar(newAccountAvatar) } } diff --git a/client/src/app/shared/video-channel/video-channel.service.ts b/client/src/app/shared/video-channel/video-channel.service.ts index 55e4c2a31..0b9900208 100644 --- a/client/src/app/shared/video-channel/video-channel.service.ts +++ b/client/src/app/shared/video-channel/video-channel.service.ts @@ -9,6 +9,7 @@ import { ResultList } from '../../../../../shared' import { VideoChannel } from './video-channel.model' import { environment } from '../../../environments/environment' import { Account } from '@app/shared/account/account.model' +import { Avatar } from '../../../../../shared/models/avatars/avatar.model' @Injectable() export class VideoChannelService { @@ -54,6 +55,13 @@ export class VideoChannelService { ) } + changeVideoChannelAvatar (videoChannelUUID: string, avatarForm: FormData) { + const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelUUID + '/avatar/pick' + + return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) + .pipe(catchError(this.restExtractor.handleError)) + } + removeVideoChannel (videoChannel: VideoChannel) { return this.authHttp.delete(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.uuid) .pipe( diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts index 7f421dbbb..5c820a227 100644 --- a/client/src/app/shared/video/video.model.ts +++ b/client/src/app/shared/video/video.model.ts @@ -11,6 +11,7 @@ import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-s export class Video implements VideoServerModel { by: string accountAvatarUrl: string + videoChannelAvatarUrl: string createdAt: Date updatedAt: Date publishedAt: Date @@ -102,9 +103,11 @@ export class Video implements VideoServerModel { this.dislikes = hash.dislikes this.nsfw = hash.nsfw this.account = hash.account + this.channel = hash.channel this.by = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host) this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) + this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.channel) this.category.label = peertubeTranslate(this.category.label, translations) this.licence.label = peertubeTranslate(this.licence.label, translations) diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts index 3ddeda109..9fe121371 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts @@ -132,10 +132,10 @@ export class VideoAddComponent extends FormReactive implements OnInit, OnDestroy if (!videofile) return // Cannot upload videos > 4GB for now - if (videofile.size > 4 * 1024 * 1024 * 1024) { - this.notificationsService.error(this.i18n('Error'), this.i18n('We are sorry but PeerTube cannot handle videos > 4GB')) - return - } + // if (videofile.size > 4 * 1024 * 1024 * 1024) { + // this.notificationsService.error(this.i18n('Error'), this.i18n('We are sorry but PeerTube cannot handle videos > 4GB')) + // return + // } const videoQuota = this.authService.getUser().videoQuota if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index 492568d3c..cd470e320 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html @@ -25,6 +25,8 @@
{{ video.channel.displayName }} + + Video channel avatar diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index dfabdfa60..4404fa9b2 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss @@ -84,6 +84,12 @@ &:hover { opacity: 0.8; } + + img { + @include avatar(18px); + + margin: -2px 2px 0 5px; + } } my-help { @@ -106,6 +112,7 @@ img { @include avatar(18px); + margin-top: -2px; margin-left: 7px; } } diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 0af70cadf..5d8089328 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -251,6 +251,10 @@ export enum ScopeNames { attributes: [ 'host' ], model: () => ServerModel.unscoped(), required: false + }, + { + model: () => AvatarModel.unscoped(), + required: false } ] }, diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts index 7b05e5882..0980de73b 100644 --- a/server/tests/api/check-params/video-channels.ts +++ b/server/tests/api/check-params/video-channels.ts @@ -14,7 +14,8 @@ import { killallServers, makeGetRequest, makePostBodyRequest, - makePutBodyRequest, makeUploadRequest, + makePutBodyRequest, + makeUploadRequest, runServer, ServerInfo, setAccessTokensToServers, @@ -22,7 +23,7 @@ import { } from '../../utils' import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' import { User } from '../../../../shared/models/users' -import { join } from "path" +import { join } from 'path' const expect = chai.expect -- 2.25.1