From c418d483004dfbae9ea38d54aa1577db46d34a8a Mon Sep 17 00:00:00 2001 From: kimsible Date: Wed, 18 Nov 2020 19:20:05 +0100 Subject: [PATCH] Add new default different avatar for channel and account --- .../users/user-list/user-list.component.ts | 4 ++-- .../my-ownership/my-ownership.component.ts | 4 ++-- .../comment/video-comment-add.component.ts | 4 ++-- .../comment/video-comment.component.ts | 4 ++-- .../abuse-details.component.ts | 4 ++-- .../abuse-list-table.component.ts | 2 +- .../select/select-channel.component.ts | 4 ++-- .../shared-main/account/account.model.ts | 22 +++++++++++++++++- .../shared/shared-main/account/actor.model.ts | 18 -------------- .../users/user-notification.model.ts | 18 ++++++++------ .../video-channel/video-channel.model.ts | 22 +++++++++++++++++- .../shared/shared-main/video/video.model.ts | 6 ++--- .../account-blocklist.component.ts | 4 ++-- .../video-comment.model.ts | 6 ++--- .../video-playlist.model.ts | 6 ++--- .../assets/images/default-avatar-account.png | Bin 0 -> 4944 bytes .../images/default-avatar-videochannel.png | Bin 0 -> 4024 bytes client/src/assets/images/default-avatar.png | Bin 1274 -> 0 bytes 18 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 client/src/assets/images/default-avatar-account.png create mode 100644 client/src/assets/images/default-avatar-videochannel.png delete mode 100644 client/src/assets/images/default-avatar.png diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index 9f92358a0..7875b74ad 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts @@ -2,7 +2,7 @@ import { SortMeta } from 'primeng/api' import { Component, OnInit, ViewChild } from '@angular/core' import { ActivatedRoute, Params, Router } from '@angular/router' import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core' -import { Actor, DropdownAction } from '@app/shared/shared-main' +import { Account, DropdownAction } from '@app/shared/shared-main' import { UserBanModalComponent } from '@app/shared/shared-moderation' import { ServerConfig, User, UserRole } from '@shared/models' @@ -164,7 +164,7 @@ export class UserListComponent extends RestTable implements OnInit { } switchToDefaultAvatar ($event: Event) { - ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() + ($event.target as HTMLImageElement).src = Account.GET_DEFAULT_AVATAR_URL() } async unbanUsers (users: User[]) { diff --git a/client/src/app/+my-library/my-ownership/my-ownership.component.ts b/client/src/app/+my-library/my-ownership/my-ownership.component.ts index e1aca65f6..78c3d9192 100644 --- a/client/src/app/+my-library/my-ownership/my-ownership.component.ts +++ b/client/src/app/+my-library/my-ownership/my-ownership.component.ts @@ -1,7 +1,7 @@ import { SortMeta } from 'primeng/api' import { Component, OnInit, ViewChild } from '@angular/core' import { Notifier, RestPagination, RestTable } from '@app/core' -import { Account, Actor, VideoOwnershipService } from '@app/shared/shared-main' +import { Account, VideoOwnershipService } from '@app/shared/shared-main' import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '@shared/models' import { MyAcceptOwnershipComponent } from './my-accept-ownership/my-accept-ownership.component' @@ -44,7 +44,7 @@ export class MyOwnershipComponent extends RestTable implements OnInit { } switchToDefaultAvatar ($event: Event) { - ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() + ($event.target as HTMLImageElement).src = Account.GET_DEFAULT_AVATAR_URL() } openAcceptModal (videoChangeOwnership: VideoChangeOwnership) { diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts index 4bde5c53d..f1f0dfeba 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts @@ -4,7 +4,7 @@ import { Router } from '@angular/router' import { Notifier, User } from '@app/core' import { VIDEO_COMMENT_TEXT_VALIDATOR } from '@app/shared/form-validators/video-comment-validators' import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' -import { Video } from '@app/shared/shared-main' +import { Video, Account } from '@app/shared/shared-main' import { VideoComment, VideoCommentService } from '@app/shared/shared-video-comment' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { VideoCommentCreate } from '@shared/models' @@ -145,7 +145,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges, getAvatarUrl () { if (this.user) return this.user.accountAvatarUrl - return window.location.origin + '/client/assets/images/default-avatar.png' + return Account.GET_DEFAULT_AVATAR_URL() } gotoLogin () { diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts index 3b3a5cc81..0958b25c0 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' import { MarkdownService, Notifier, UserService } from '@app/core' import { AuthService } from '@app/core/auth' -import { Account, Actor, DropdownAction, Video } from '@app/shared/shared-main' +import { Account, DropdownAction, Video } from '@app/shared/shared-main' import { CommentReportComponent } from '@app/shared/shared-moderation/report-modals/comment-report.component' import { VideoComment, VideoCommentThreadTree } from '@app/shared/shared-video-comment' import { User, UserRight } from '@shared/models' @@ -130,7 +130,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { } switchToDefaultAvatar ($event: Event) { - ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() + ($event.target as HTMLImageElement).src = Account.GET_DEFAULT_AVATAR_URL() } isNotDeletedOrDeletedWithReplies () { diff --git a/client/src/app/shared/shared-abuse-list/abuse-details.component.ts b/client/src/app/shared/shared-abuse-list/abuse-details.component.ts index 282a6fe19..31cf3389d 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-details.component.ts +++ b/client/src/app/shared/shared-abuse-list/abuse-details.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core' import { durationToString } from '@app/helpers' -import { Actor } from '@app/shared/shared-main' +import { Account } from '@app/shared/shared-main' import { AbusePredefinedReasonsString } from '@shared/models' import { ProcessedAbuse } from './processed-abuse.model' @@ -47,6 +47,6 @@ export class AbuseDetailsComponent { } switchToDefaultAvatar ($event: Event) { - ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() + ($event.target as HTMLImageElement).src = Account.GET_DEFAULT_AVATAR_URL() } } diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts index 807665b9c..904f62b57 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts @@ -122,7 +122,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV } switchToDefaultAvatar ($event: Event) { - ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() + ($event.target as HTMLImageElement).src = Account.GET_DEFAULT_AVATAR_URL() } async removeAbuse (abuse: AdminAbuse) { diff --git a/client/src/app/shared/shared-forms/select/select-channel.component.ts b/client/src/app/shared/shared-forms/select/select-channel.component.ts index 1b0db9b6f..1d91d59bc 100644 --- a/client/src/app/shared/shared-forms/select/select-channel.component.ts +++ b/client/src/app/shared/shared-forms/select/select-channel.component.ts @@ -1,6 +1,6 @@ import { Component, forwardRef, Input } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' -import { Actor } from '@app/shared/shared-main/account/actor.model' +import { VideoChannel } from '@app/shared/shared-main' export type SelectChannelItem = { id: number @@ -34,7 +34,7 @@ export class SelectChannelComponent implements ControlValueAccessor { get channels () { return this.items.map(c => Object.assign(c, { - avatarPath: c.avatarPath ? c.avatarPath : Actor.GET_DEFAULT_AVATAR_URL() + avatarPath: c.avatarPath ? c.avatarPath : VideoChannel.GET_DEFAULT_AVATAR_URL() })) } 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 6df2e9d10..b3dc6cfe5 100644 --- a/client/src/app/shared/shared-main/account/account.model.ts +++ b/client/src/app/shared/shared-main/account/account.model.ts @@ -1,4 +1,4 @@ -import { Account as ServerAccount } from '@shared/models/actors/account.model' +import { Account as ServerAccount, Avatar } from '@shared/models' import { Actor } from './actor.model' export class Account extends Actor implements ServerAccount { @@ -13,9 +13,19 @@ export class Account extends Actor implements ServerAccount { userId?: number + static GET_ACTOR_AVATAR_URL (actor: object) { + return Actor.GET_ACTOR_AVATAR_URL(actor) || this.GET_DEFAULT_AVATAR_URL() + } + + static GET_DEFAULT_AVATAR_URL () { + return `${window.location.origin}/client/assets/images/default-avatar-account.png` + } + constructor (hash: ServerAccount) { super(hash) + this.updateComputedAttributes() + this.displayName = hash.displayName this.description = hash.description this.userId = hash.userId @@ -27,4 +37,14 @@ export class Account extends Actor implements ServerAccount { this.mutedServerByUser = false this.mutedServerByInstance = false } + + updateAvatar (newAvatar: Avatar) { + this.avatar = newAvatar + + this.updateComputedAttributes() + } + + private updateComputedAttributes () { + this.avatarUrl = Account.GET_ACTOR_AVATAR_URL(this) + } } diff --git a/client/src/app/shared/shared-main/account/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts index 950e256ff..8222c9769 100644 --- a/client/src/app/shared/shared-main/account/actor.model.ts +++ b/client/src/app/shared/shared-main/account/actor.model.ts @@ -24,12 +24,6 @@ export abstract class Actor implements ActorServer { return absoluteAPIUrl + actor.avatar.path } - - return this.GET_DEFAULT_AVATAR_URL() - } - - static GET_DEFAULT_AVATAR_URL () { - return window.location.origin + '/client/assets/images/default-avatar.png' } static CREATE_BY_STRING (accountName: string, host: string, forceHostname = false) { @@ -61,17 +55,5 @@ export abstract class Actor implements ActorServer { this.avatar = hash.avatar this.isLocal = Actor.IS_LOCAL(this.host) - - 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/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts index 648bb7fe0..b1df4a584 100644 --- a/client/src/app/shared/shared-main/users/user-notification.model.ts +++ b/client/src/app/shared/shared-main/users/user-notification.model.ts @@ -7,7 +7,7 @@ import { VideoInfo, UserRight } from '@shared/models' -import { Actor } from '../account/actor.model' +import { Account, Actor, VideoChannel } from '@app/shared/shared-main' import { AuthUser } from '@app/core' export class UserNotification implements UserNotificationServer { @@ -95,22 +95,22 @@ export class UserNotification implements UserNotificationServer { // To prevent a notification popup crash in case of bug, wrap it inside a try/catch try { this.video = hash.video - if (this.video) this.setAvatarUrl(this.video.channel) + if (this.video) this.setVideoChannelAvatarUrl(this.video.channel) this.videoImport = hash.videoImport this.comment = hash.comment - if (this.comment) this.setAvatarUrl(this.comment.account) + if (this.comment) this.setAccountAvatarUrl(this.comment.account) this.abuse = hash.abuse this.videoBlacklist = hash.videoBlacklist this.account = hash.account - if (this.account) this.setAvatarUrl(this.account) + if (this.account) this.setAccountAvatarUrl(this.account) this.actorFollow = hash.actorFollow - if (this.actorFollow) this.setAvatarUrl(this.actorFollow.follower) + if (this.actorFollow) this.setAccountAvatarUrl(this.actorFollow.follower) this.createdAt = hash.createdAt this.updatedAt = hash.updatedAt @@ -222,7 +222,11 @@ export class UserNotification implements UserNotificationServer { return [ this.buildVideoUrl(comment.video), { threadId: comment.threadId } ] } - private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { - actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor) + private setAccountAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { + actor.avatarUrl = Account.GET_ACTOR_AVATAR_URL(actor) + } + + private setVideoChannelAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { + actor.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(actor) } } 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 123389afb..4f1f5b65d 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 @@ -1,4 +1,4 @@ -import { VideoChannel as ServerVideoChannel, ViewsPerDate, Account } from '@shared/models' +import { VideoChannel as ServerVideoChannel, ViewsPerDate, Account, Avatar } from '@shared/models' import { Actor } from '../account/actor.model' export class VideoChannel extends Actor implements ServerVideoChannel { @@ -17,9 +17,19 @@ export class VideoChannel extends Actor implements ServerVideoChannel { viewsPerDay?: ViewsPerDate[] + static GET_ACTOR_AVATAR_URL (actor: object) { + return Actor.GET_ACTOR_AVATAR_URL(actor) || this.GET_DEFAULT_AVATAR_URL() + } + + static GET_DEFAULT_AVATAR_URL () { + return `${window.location.origin}/client/assets/images/default-avatar-videochannel.png` + } + constructor (hash: ServerVideoChannel) { super(hash) + this.updateComputedAttributes() + this.displayName = hash.displayName this.description = hash.description this.support = hash.support @@ -39,4 +49,14 @@ export class VideoChannel extends Actor implements ServerVideoChannel { this.ownerAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.ownerAccount) } } + + updateAvatar (newAvatar: Avatar) { + this.avatar = newAvatar + + this.updateComputedAttributes() + } + + private updateComputedAttributes () { + this.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(this) + } } diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts index 92f5bc0c0..8e0e68020 100644 --- a/client/src/app/shared/shared-main/video/video.model.ts +++ b/client/src/app/shared/shared-main/video/video.model.ts @@ -12,7 +12,7 @@ import { VideoScheduleUpdate, VideoState } from '@shared/models' -import { Actor } from '../account/actor.model' +import { Account, Actor, VideoChannel } from '@app/shared/shared-main' export class Video implements VideoServerModel { byVideoChannel: string @@ -142,8 +142,8 @@ export class Video implements VideoServerModel { this.byAccount = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host) this.byVideoChannel = Actor.CREATE_BY_STRING(hash.channel.name, hash.channel.host) - this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) - this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.channel) + this.accountAvatarUrl = Account.GET_ACTOR_AVATAR_URL(this.account) + this.videoChannelAvatarUrl = VideoChannel.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/shared/shared-moderation/account-blocklist.component.ts b/client/src/app/shared/shared-moderation/account-blocklist.component.ts index 68928a2c2..3de9587b8 100644 --- a/client/src/app/shared/shared-moderation/account-blocklist.component.ts +++ b/client/src/app/shared/shared-moderation/account-blocklist.component.ts @@ -1,7 +1,7 @@ import { SortMeta } from 'primeng/api' import { Directive, OnInit } from '@angular/core' import { Notifier, RestPagination, RestTable } from '@app/core' -import { Actor } from '@app/shared/shared-main' +import { Account } from '@app/shared/shared-main' import { AccountBlock } from './account-block.model' import { BlocklistComponentType, BlocklistService } from './blocklist.service' @@ -31,7 +31,7 @@ export class GenericAccountBlocklistComponent extends RestTable implements OnIni } switchToDefaultAvatar ($event: Event) { - ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() + ($event.target as HTMLImageElement).src = Account.GET_DEFAULT_AVATAR_URL() } unblockAccount (accountBlock: AccountBlock) { diff --git a/client/src/app/shared/shared-video-comment/video-comment.model.ts b/client/src/app/shared/shared-video-comment/video-comment.model.ts index eeee397af..bf718ae08 100644 --- a/client/src/app/shared/shared-video-comment/video-comment.model.ts +++ b/client/src/app/shared/shared-video-comment/video-comment.model.ts @@ -1,5 +1,5 @@ import { getAbsoluteAPIUrl } from '@app/helpers' -import { Actor } from '@app/shared/shared-main' +import { Account, Actor } from '@app/shared/shared-main' import { Account as AccountInterface, VideoComment as VideoCommentServerModel, VideoCommentAdmin as VideoCommentAdminServerModel } from '@shared/models' export class VideoComment implements VideoCommentServerModel { @@ -38,7 +38,7 @@ export class VideoComment implements VideoCommentServerModel { if (this.account) { this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host) - this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) + this.accountAvatarUrl = Account.GET_ACTOR_AVATAR_URL(this.account) const absoluteAPIUrl = getAbsoluteAPIUrl() const thisHost = new URL(absoluteAPIUrl).host @@ -97,7 +97,7 @@ export class VideoCommentAdmin implements VideoCommentAdminServerModel { if (this.account) { this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host) - this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) + this.accountAvatarUrl = Account.GET_ACTOR_AVATAR_URL(this.account) this.account.localUrl = '/accounts/' + this.by } diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts index 3db3b7a2e..9bec16d77 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts @@ -1,5 +1,5 @@ import { getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers' -import { Actor } from '@app/shared/shared-main' +import { Account, Actor, VideoChannel } from '@app/shared/shared-main' import { peertubeTranslate } from '@shared/core-utils/i18n' import { AccountSummary, @@ -78,12 +78,12 @@ export class VideoPlaylist implements ServerVideoPlaylist { this.ownerAccount = hash.ownerAccount this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) - this.ownerAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.ownerAccount) + this.ownerAvatarUrl = Account.GET_ACTOR_AVATAR_URL(this.ownerAccount) if (hash.videoChannel) { this.videoChannel = hash.videoChannel this.videoChannelBy = Actor.CREATE_BY_STRING(hash.videoChannel.name, hash.videoChannel.host) - this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.videoChannel) + this.videoChannelAvatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(this.videoChannel) } this.privacy.label = peertubeTranslate(this.privacy.label, translations) diff --git a/client/src/assets/images/default-avatar-account.png b/client/src/assets/images/default-avatar-account.png new file mode 100644 index 0000000000000000000000000000000000000000..0bedaa0c87bf2071ee4170077c28c1be66931854 GIT binary patch literal 4944 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 s5 z=aufR{x)kO+X0&$PxYVv+aR&O>Fnp5-hYbok6++9A-D6-eWUj?K59kmtQ9^lzilg% zS=yi4!t%#I^zDv(p1sHJz`pb1Q!byF@A$AT@XwY-+y%$Pr?2j4`jk_+XZtetf+NkR z)@PjmZ>Vu^^$UiaPY*722+WscIqqNdrQuC7$AR7pg7s}jKJ(c==s2x9p_Z%rdwl?N zQJzME-kghk&X0fQXE_+}sjX95l^`YeURRKrjp;y$NekDbpJKWRC&g6*CjDx9RPSua zIBi939RKl+aHShxO}W@6M4YFxcs5$jze5NK?nA2_`CT7SIfseA9mh9 zP<$%i;SM{W%I^brJSs(2dGe~_R&kst^OnYKc zF#RF}$IUD6+^whZ)U;@uC?jdr@SLS-ZM5WS5c4d^m|q&=9^xp{p;X|vy1+` z7oJx(;PrUmQH|^NH ztLr59H+_2eTJZgnTvcD^S8c~uW@JnZM#3Nqb zi;H$0FJbJ`bN!bWDBX}|RW`$}!7yb0{|gs55=5VJbL~G=!gQtHh3_(V!t|`q`>)jL zO$pv*DDiX)b429Bh*#5Q)-gnX4tYBN1-q(Uv}pLs<9+ zzGp!{PtWsPe=g@{(zW=GMJ0XY1rLB>O#jc%}GBba%P{#9o$zg&Re-I zbnEps*E&C>i>s+myQDVtzVP&@=jDz6^=j^Et<39xwrKaQ7cu

nDGyu-Nm?WR;4q z;QcGoSNAyHX8IS#gfAXIa{D5Fa@Q|T^;g+4MGweTEpd$~Nl7e8wMs5Z1yT$~28I^8 z2Ijhk79oaaRt6?kCWhJu237_J*}b;nC>nC}Q!>*kachve(7GCAqo=E%%Q~loCIHEj B7?1z} -- 2.41.0