aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
authorkontrollanten <6680299+kontrollanten@users.noreply.github.com>2022-02-28 08:34:43 +0100
committerGitHub <noreply@github.com>2022-02-28 08:34:43 +0100
commitd0800f7661f13fabe7bb6f4aa0ea50764f106405 (patch)
treed43e6b0b6f4a5a32e03487e6464edbcaf288be2a /client/src/app
parent5cad2ca9db9b9d138f8a33058d10b94a9fd50c69 (diff)
downloadPeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.gz
PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.zst
PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.zip
Implement avatar miniatures (#4639)
* client: remove unused file * refactor(client/my-actor-avatar): size from input Read size from component input instead of scss, to make it possible to use smaller avatar images when implemented. * implement avatar miniatures close #4560 * fix(test): max file size * fix(search-index): normalize res acc to avatarMini * refactor avatars to an array * client/search: resize channel avatar to 120 * refactor(client/videos): remove unused function * client(actor-avatar): set default size * fix tests and avatars full result When findOne is used only an array containting one avatar is returned. * update migration version and version notations * server/search: harmonize normalizing * Cleanup avatar miniature PR Co-authored-by: Chocobozzz <me@florianbigard.com>
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.html7
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.scss1
-rw-r--r--client/src/app/+accounts/accounts.component.html2
-rw-r--r--client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html56
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html2
-rw-r--r--client/src/app/+manage/video-channel-edit/video-channel-update.component.ts4
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.ts2
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.component.html2
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss1
-rw-r--r--client/src/app/+my-library/my-follows/my-followers.component.html2
-rw-r--r--client/src/app/+my-library/my-follows/my-followers.component.scss2
-rw-r--r--client/src/app/+my-library/my-follows/my-subscriptions.component.html2
-rw-r--r--client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts4
-rw-r--r--client/src/app/+my-library/my-ownership/my-ownership.component.html2
-rw-r--r--client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts4
-rw-r--r--client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts4
-rw-r--r--client/src/app/+search/search.component.html2
-rw-r--r--client/src/app/+search/search.component.scss4
-rw-r--r--client/src/app/+video-channels/video-channels.component.html4
-rw-r--r--client/src/app/+video-channels/video-channels.component.scss4
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-send.ts4
-rw-r--r--client/src/app/+videos/+video-edit/video-update.resolver.ts4
-rw-r--r--client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html2
-rw-r--r--client/src/app/+videos/+video-watch/shared/comment/video-comment.component.scss9
-rw-r--r--client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html6
-rw-r--r--client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss9
-rw-r--r--client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.ts4
-rw-r--r--client/src/app/+videos/video-list/overview/video-overview.component.html2
-rw-r--r--client/src/app/+videos/video-list/overview/video-overview.component.scss1
-rw-r--r--client/src/app/core/users/user.model.ts4
-rw-r--r--client/src/app/core/users/user.service.ts2
-rw-r--r--client/src/app/helpers/utils/channel.ts7
-rw-r--r--client/src/app/modal/account-setup-warning-modal.component.ts2
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.html2
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts2
-rw-r--r--client/src/app/shared/shared-actor-image/actor-avatar.component.scss29
-rw-r--r--client/src/app/shared/shared-actor-image/actor-avatar.component.ts15
-rw-r--r--client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.html2
-rw-r--r--client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.scss2
-rw-r--r--client/src/app/shared/shared-forms/select/select-channel.component.ts2
-rw-r--r--client/src/app/shared/shared-main/account/account.model.ts16
-rw-r--r--client/src/app/shared/shared-main/account/actor.model.ts20
-rw-r--r--client/src/app/shared/shared-main/misc/channels-setup-message.component.ts2
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.model.ts8
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.model.ts42
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.service.ts2
-rw-r--r--client/src/app/shared/shared-main/video/video.model.ts12
-rw-r--r--client/src/app/shared/shared-moderation/account-blocklist.component.html2
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.html2
49 files changed, 123 insertions, 204 deletions
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
index 105bc12c3..379c0443e 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
@@ -9,8 +9,11 @@
9 9
10 <div class="channel-avatar-row"> 10 <div class="channel-avatar-row">
11 <my-actor-avatar 11 <my-actor-avatar
12 [channel]="videoChannel" [internalHref]="getVideoChannelLink(videoChannel)" 12 [channel]="videoChannel"
13 i18n-title title="See this video channel" 13 [internalHref]="getVideoChannelLink(videoChannel)"
14 i18n-title
15 title="See this video channel"
16 size="75"
14 ></my-actor-avatar> 17 ></my-actor-avatar>
15 18
16 <h2> 19 <h2>
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
index be9e94f69..30b8098be 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
@@ -29,7 +29,6 @@
29 grid-template-rows: auto 1fr; 29 grid-template-rows: auto 1fr;
30 30
31 my-actor-avatar { 31 my-actor-avatar {
32 @include actor-avatar-size(75px);
33 @include margin-right(15px); 32 @include margin-right(15px);
34 33
35 grid-column: 1; 34 grid-column: 1;
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html
index 8362e6b7e..1544ad034 100644
--- a/client/src/app/+accounts/accounts.component.html
+++ b/client/src/app/+accounts/accounts.component.html
@@ -2,7 +2,7 @@
2 <div class="account-info"> 2 <div class="account-info">
3 3
4 <div class="account-avatar-row"> 4 <div class="account-avatar-row">
5 <my-actor-avatar class="main-avatar" [account]="account"></my-actor-avatar> 5 <my-actor-avatar class="main-avatar" [account]="account" size="120"></my-actor-avatar>
6 6
7 <div> 7 <div>
8 <div class="section-label" i18n>ACCOUNT</div> 8 <div class="section-label" i18n>ACCOUNT</div>
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
deleted file mode 100644
index feade0c26..000000000
--- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
+++ /dev/null
@@ -1,56 +0,0 @@
1<p-table
2 [value]="blockedAccounts" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
3 [sortField]="sort.field" [sortOrder]="sort.order"
4 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
5 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
6 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted accounts"
7>
8 <ng-template pTemplate="caption">
9 <div class="caption">
10 <div class="ml-auto">
11 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
12 </div>
13 </div>
14 </ng-template>
15
16 <ng-template pTemplate="header">
17 <tr>
18 <th style="width: 150px;">Action</th> <!-- column for action buttons -->
19 <th style="width: calc(100% - 300px);" i18n>Account</th>
20 <th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
21 </tr>
22 </ng-template>
23
24 <ng-template pTemplate="body" let-accountBlock>
25 <tr>
26 <td class="action-cell">
27 <button class="unblock-button" (click)="unblockAccount(accountBlock)" i18n>Unmute</button>
28 </td>
29
30 <td>
31 <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
32 <div class="chip two-lines">
33 <my-actor-avatar [account]="accountBlock.blockedAccount"></my-actor-avatar>
34 <div>
35 {{ accountBlock.blockedAccount.displayName }}
36 <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span>
37 </div>
38 </div>
39 </a>
40 </td>
41
42 <td>{{ accountBlock.createdAt | date: 'short' }}</td>
43 </tr>
44 </ng-template>
45
46 <ng-template pTemplate="emptymessage">
47 <tr>
48 <td colspan="6">
49 <div class="no-results">
50 <ng-container *ngIf="search" i18n>No account found matching current filters.</ng-container>
51 <ng-container *ngIf="!search" i18n>No account found.</ng-container>
52 </div>
53 </td>
54 </tr>
55 </ng-template>
56</p-table>
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
index 9bf23c21a..0dbbbe1cc 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
@@ -66,7 +66,7 @@
66 <td> 66 <td>
67 <a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 67 <a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
68 <div class="chip two-lines"> 68 <div class="chip two-lines">
69 <my-actor-avatar [account]="videoComment.account"></my-actor-avatar> 69 <my-actor-avatar [account]="videoComment.account" size="32"></my-actor-avatar>
70 <div> 70 <div>
71 {{ videoComment.account.displayName }} 71 {{ videoComment.account.displayName }}
72 <span>{{ videoComment.by }}</span> 72 <span>{{ videoComment.by }}</span>
diff --git a/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
index 21b6167b2..51cd45605 100644
--- a/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
+++ b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
@@ -111,7 +111,7 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI
111 next: data => { 111 next: data => {
112 this.notifier.success($localize`Avatar changed.`) 112 this.notifier.success($localize`Avatar changed.`)
113 113
114 this.videoChannel.updateAvatar(data.avatar) 114 this.videoChannel.updateAvatar(data.avatars)
115 }, 115 },
116 116
117 error: (err: HttpErrorResponse) => genericUploadErrorHandler({ 117 error: (err: HttpErrorResponse) => genericUploadErrorHandler({
@@ -141,7 +141,7 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI
141 next: data => { 141 next: data => {
142 this.notifier.success($localize`Banner changed.`) 142 this.notifier.success($localize`Banner changed.`)
143 143
144 this.videoChannel.updateBanner(data.banner) 144 this.videoChannel.updateBanner(data.banners)
145 }, 145 },
146 146
147 error: (err: HttpErrorResponse) => genericUploadErrorHandler({ 147 error: (err: HttpErrorResponse) => genericUploadErrorHandler({
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 a5bcb6496..577f4a252 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
@@ -43,7 +43,7 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked {
43 next: data => { 43 next: data => {
44 this.notifier.success($localize`Avatar changed.`) 44 this.notifier.success($localize`Avatar changed.`)
45 45
46 this.user.updateAccountAvatar(data.avatar) 46 this.user.updateAccountAvatar(data.avatars)
47 }, 47 },
48 48
49 error: (err: HttpErrorResponse) => genericUploadErrorHandler({ 49 error: (err: HttpErrorResponse) => genericUploadErrorHandler({
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
index 77947315b..c1ded0f6d 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
@@ -19,7 +19,7 @@
19 19
20<div class="video-channels"> 20<div class="video-channels">
21 <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel"> 21 <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel">
22 <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/c', videoChannel.nameWithHost ]"></my-actor-avatar> 22 <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar>
23 23
24 <div class="video-channel-info"> 24 <div class="video-channel-info">
25 <a [routerLink]="[ '/c', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page"> 25 <a [routerLink]="[ '/c', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page">
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss
index 998e46cb2..484355967 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss
@@ -24,7 +24,6 @@ my-edit-button {
24 padding-bottom: 0; 24 padding-bottom: 0;
25 25
26 my-actor-avatar { 26 my-actor-avatar {
27 @include actor-avatar-size(80px);
28 @include margin-right(10px); 27 @include margin-right(10px);
29 } 28 }
30} 29}
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.html b/client/src/app/+my-library/my-follows/my-followers.component.html
index eac750c86..4303695a3 100644
--- a/client/src/app/+my-library/my-follows/my-followers.component.html
+++ b/client/src/app/+my-library/my-follows/my-followers.component.html
@@ -14,7 +14,7 @@
14 14
15<div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> 15<div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
16 <div *ngFor="let follow of follows" class="actor"> 16 <div *ngFor="let follow of follows" class="actor">
17 <my-actor-avatar [account]="follow.follower" [href]="follow.follower.url"></my-actor-avatar> 17 <my-actor-avatar [account]="follow.follower" [href]="follow.follower.url" size="40"></my-actor-avatar>
18 18
19 <div class="actor-info"> 19 <div class="actor-info">
20 <a [href]="follow.follower.url" class="actor-names" rel="noopener noreferrer" target="_blank" i18n-title title="Follower page"> 20 <a [href]="follow.follower.url" class="actor-names" rel="noopener noreferrer" target="_blank" i18n-title title="Follower page">
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.scss b/client/src/app/+my-library/my-follows/my-followers.component.scss
index 15b51c419..fae4cd972 100644
--- a/client/src/app/+my-library/my-follows/my-followers.component.scss
+++ b/client/src/app/+my-library/my-follows/my-followers.component.scss
@@ -12,7 +12,7 @@ input[type=text] {
12} 12}
13 13
14.actor { 14.actor {
15 @include actor-row($avatar-size: 40px, $min-height: auto, $separator: true); 15 @include actor-row($min-height: auto, $separator: true);
16 16
17 .actor-display-name { 17 .actor-display-name {
18 font-size: 16px; 18 font-size: 16px;
diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.html b/client/src/app/+my-library/my-follows/my-subscriptions.component.html
index 775f0e783..391c4d3be 100644
--- a/client/src/app/+my-library/my-follows/my-subscriptions.component.html
+++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.html
@@ -14,7 +14,7 @@
14 14
15<div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> 15<div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
16 <div *ngFor="let videoChannel of videoChannels" class="actor"> 16 <div *ngFor="let videoChannel of videoChannels" class="actor">
17 <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/c', videoChannel.nameWithHost ]"></my-actor-avatar> 17 <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar>
18 18
19 <div class="actor-info"> 19 <div class="actor-info">
20 <a [routerLink]="[ '/c', videoChannel.nameWithHost ]" class="actor-names" i18n-title title="Channel page"> 20 <a [routerLink]="[ '/c', videoChannel.nameWithHost ]" class="actor-names" i18n-title title="Channel page">
diff --git a/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts b/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts
index 764da2369..8ead237c7 100644
--- a/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts
+++ b/client/src/app/+my-library/my-ownership/my-accept-ownership/my-accept-ownership.component.ts
@@ -1,7 +1,7 @@
1import { SelectChannelItem } from 'src/types/select-options-item.model' 1import { SelectChannelItem } from 'src/types/select-options-item.model'
2import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 2import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
3import { AuthService, Notifier } from '@app/core' 3import { AuthService, Notifier } from '@app/core'
4import { listUserChannels } from '@app/helpers' 4import { listUserChannelsForSelect } from '@app/helpers'
5import { OWNERSHIP_CHANGE_CHANNEL_VALIDATOR } from '@app/shared/form-validators/video-ownership-change-validators' 5import { OWNERSHIP_CHANGE_CHANNEL_VALIDATOR } from '@app/shared/form-validators/video-ownership-change-validators'
6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
7import { VideoOwnershipService } from '@app/shared/shared-main' 7import { VideoOwnershipService } from '@app/shared/shared-main'
@@ -36,7 +36,7 @@ export class MyAcceptOwnershipComponent extends FormReactive implements OnInit {
36 ngOnInit () { 36 ngOnInit () {
37 this.videoChannels = [] 37 this.videoChannels = []
38 38
39 listUserChannels(this.authService) 39 listUserChannelsForSelect(this.authService)
40 .subscribe(channels => this.videoChannels = channels) 40 .subscribe(channels => this.videoChannels = channels)
41 41
42 this.buildForm({ 42 this.buildForm({
diff --git a/client/src/app/+my-library/my-ownership/my-ownership.component.html b/client/src/app/+my-library/my-ownership/my-ownership.component.html
index 4c02c78fc..cb032505e 100644
--- a/client/src/app/+my-library/my-ownership/my-ownership.component.html
+++ b/client/src/app/+my-library/my-ownership/my-ownership.component.html
@@ -37,7 +37,7 @@
37 <td> 37 <td>
38 <a [href]="videoChangeOwnership.initiatorAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 38 <a [href]="videoChangeOwnership.initiatorAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
39 <div class="chip two-lines"> 39 <div class="chip two-lines">
40 <my-actor-avatar [account]="videoChangeOwnership.initiatorAccount"></my-actor-avatar> 40 <my-actor-avatar [account]="videoChangeOwnership.initiatorAccount" size="32"></my-actor-avatar>
41 <div> 41 <div>
42 {{ videoChangeOwnership.initiatorAccount.displayName }} 42 {{ videoChangeOwnership.initiatorAccount.displayName }}
43 <span class="text-muted">{{ videoChangeOwnership.initiatorAccount.nameWithHost }}</span> 43 <span class="text-muted">{{ videoChangeOwnership.initiatorAccount.nameWithHost }}</span>
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts
index 8bc78b2db..9eb3e9888 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-create.component.ts
@@ -1,7 +1,7 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { Router } from '@angular/router' 2import { Router } from '@angular/router'
3import { AuthService, Notifier, ServerService } from '@app/core' 3import { AuthService, Notifier, ServerService } from '@app/core'
4import { listUserChannels } from '@app/helpers' 4import { listUserChannelsForSelect } from '@app/helpers'
5import { 5import {
6 setPlaylistChannelValidator, 6 setPlaylistChannelValidator,
7 VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR, 7 VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR,
@@ -46,7 +46,7 @@ export class MyVideoPlaylistCreateComponent extends MyVideoPlaylistEdit implemen
46 setPlaylistChannelValidator(this.form.get('videoChannelId'), privacy) 46 setPlaylistChannelValidator(this.form.get('videoChannelId'), privacy)
47 }) 47 })
48 48
49 listUserChannels(this.authService) 49 listUserChannelsForSelect(this.authService)
50 .subscribe(channels => this.userVideoChannels = channels) 50 .subscribe(channels => this.userVideoChannels = channels)
51 51
52 this.serverService.getVideoPlaylistPrivacies() 52 this.serverService.getVideoPlaylistPrivacies()
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts
index 06ac3ad50..ef7ba0018 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-update.component.ts
@@ -3,7 +3,7 @@ import { map, switchMap } from 'rxjs/operators'
3import { Component, OnDestroy, OnInit } from '@angular/core' 3import { Component, OnDestroy, OnInit } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { AuthService, Notifier, ServerService } from '@app/core' 5import { AuthService, Notifier, ServerService } from '@app/core'
6import { listUserChannels } from '@app/helpers' 6import { listUserChannelsForSelect } from '@app/helpers'
7import { 7import {
8 setPlaylistChannelValidator, 8 setPlaylistChannelValidator,
9 VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR, 9 VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR,
@@ -51,7 +51,7 @@ export class MyVideoPlaylistUpdateComponent extends MyVideoPlaylistEdit implemen
51 setPlaylistChannelValidator(this.form.get('videoChannelId'), privacy) 51 setPlaylistChannelValidator(this.form.get('videoChannelId'), privacy)
52 }) 52 })
53 53
54 listUserChannels(this.authService) 54 listUserChannelsForSelect(this.authService)
55 .subscribe(channels => this.userVideoChannels = channels) 55 .subscribe(channels => this.userVideoChannels = channels)
56 56
57 this.paramsSub = this.route.params 57 this.paramsSub = this.route.params
diff --git a/client/src/app/+search/search.component.html b/client/src/app/+search/search.component.html
index 412b962d1..2c84dd930 100644
--- a/client/src/app/+search/search.component.html
+++ b/client/src/app/+search/search.component.html
@@ -36,7 +36,7 @@
36 <ng-container *ngFor="let result of results"> 36 <ng-container *ngFor="let result of results">
37 <div *ngIf="isVideoChannel(result)" class="entry video-channel"> 37 <div *ngIf="isVideoChannel(result)" class="entry video-channel">
38 38
39 <my-actor-avatar [channel]="result" [internalHref]="getInternalChannelUrl(result)" [href]="getExternalChannelUrl(result)"></my-actor-avatar> 39 <my-actor-avatar [channel]="result" [internalHref]="getInternalChannelUrl(result)" [href]="getExternalChannelUrl(result)" size="120"></my-actor-avatar>
40 40
41 <div class="video-channel-info"> 41 <div class="video-channel-info">
42 <a *ngIf="!isExternalChannelUrl()" [routerLink]="getInternalChannelUrl(result)" class="video-channel-names"> 42 <a *ngIf="!isExternalChannelUrl()" [routerLink]="getInternalChannelUrl(result)" class="video-channel-names">
diff --git a/client/src/app/+search/search.component.scss b/client/src/app/+search/search.component.scss
index b521825e5..cab1d0e88 100644
--- a/client/src/app/+search/search.component.scss
+++ b/client/src/app/+search/search.component.scss
@@ -58,10 +58,6 @@
58 max-width: 800px; 58 max-width: 800px;
59} 59}
60 60
61.video-channel my-actor-avatar {
62 @include build-channel-img-size($video-thumbnail-width);
63}
64
65.video-channel-info { 61.video-channel-info {
66 flex-grow: 1; 62 flex-grow: 1;
67 margin: 0 10px; 63 margin: 0 10px;
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html
index 212e2f867..09f81d2ce 100644
--- a/client/src/app/+video-channels/video-channels.component.html
+++ b/client/src/app/+video-channels/video-channels.component.html
@@ -23,7 +23,7 @@
23 <div class="section-label" i18n>OWNER ACCOUNT</div> 23 <div class="section-label" i18n>OWNER ACCOUNT</div>
24 24
25 <div class="avatar-row"> 25 <div class="avatar-row">
26 <my-actor-avatar class="account-avatar" [account]="ownerAccount" [internalHref]="getAccountUrl()"></my-actor-avatar> 26 <my-actor-avatar class="account-avatar" [account]="ownerAccount" [internalHref]="getAccountUrl()" size="48"></my-actor-avatar>
27 27
28 <div class="actor-info"> 28 <div class="actor-info">
29 <h4> 29 <h4>
@@ -51,7 +51,7 @@
51 </ng-template> 51 </ng-template>
52 52
53 <div class="channel-avatar-row"> 53 <div class="channel-avatar-row">
54 <my-actor-avatar class="main-avatar" [channel]="videoChannel"></my-actor-avatar> 54 <my-actor-avatar class="main-avatar" [channel]="videoChannel" size="120"></my-actor-avatar>
55 55
56 <div> 56 <div>
57 <div class="section-label" i18n>VIDEO CHANNEL</div> 57 <div class="section-label" i18n>VIDEO CHANNEL</div>
diff --git a/client/src/app/+video-channels/video-channels.component.scss b/client/src/app/+video-channels/video-channels.component.scss
index 72ee2d7bb..004ad7998 100644
--- a/client/src/app/+video-channels/video-channels.component.scss
+++ b/client/src/app/+video-channels/video-channels.component.scss
@@ -107,10 +107,6 @@
107 display: flex; 107 display: flex;
108 margin-bottom: 15px; 108 margin-bottom: 15px;
109 109
110 .account-avatar {
111 @include actor-avatar-size(48px);
112 }
113
114 .actor-info { 110 .actor-info {
115 @include margin-left(15px); 111 @include margin-left(15px);
116 } 112 }
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-send.ts b/client/src/app/+videos/+video-edit/video-add-components/video-send.ts
index 3d0e1bf2a..9de373cd3 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-send.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-send.ts
@@ -2,7 +2,7 @@ import { catchError, switchMap, tap } from 'rxjs/operators'
2import { SelectChannelItem } from 'src/types/select-options-item.model' 2import { SelectChannelItem } from 'src/types/select-options-item.model'
3import { Directive, EventEmitter, OnInit } from '@angular/core' 3import { Directive, EventEmitter, OnInit } from '@angular/core'
4import { AuthService, CanComponentDeactivateResult, Notifier, ServerService } from '@app/core' 4import { AuthService, CanComponentDeactivateResult, Notifier, ServerService } from '@app/core'
5import { listUserChannels } from '@app/helpers' 5import { listUserChannelsForSelect } from '@app/helpers'
6import { FormReactive } from '@app/shared/shared-forms' 6import { FormReactive } from '@app/shared/shared-forms'
7import { VideoCaptionEdit, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' 7import { VideoCaptionEdit, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
8import { LoadingBarService } from '@ngx-loading-bar/core' 8import { LoadingBarService } from '@ngx-loading-bar/core'
@@ -38,7 +38,7 @@ export abstract class VideoSend extends FormReactive implements OnInit {
38 ngOnInit () { 38 ngOnInit () {
39 this.buildForm({}) 39 this.buildForm({})
40 40
41 listUserChannels(this.authService) 41 listUserChannelsForSelect(this.authService)
42 .subscribe(channels => { 42 .subscribe(channels => {
43 this.userVideoChannels = channels 43 this.userVideoChannels = channels
44 this.firstStepChannelId = this.userVideoChannels[0].id 44 this.firstStepChannelId = this.userVideoChannels[0].id
diff --git a/client/src/app/+videos/+video-edit/video-update.resolver.ts b/client/src/app/+videos/+video-edit/video-update.resolver.ts
index 91e76b7fe..82dae5c1c 100644
--- a/client/src/app/+videos/+video-edit/video-update.resolver.ts
+++ b/client/src/app/+videos/+video-edit/video-update.resolver.ts
@@ -3,7 +3,7 @@ import { map, switchMap } from 'rxjs/operators'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { ActivatedRouteSnapshot, Resolve } from '@angular/router' 4import { ActivatedRouteSnapshot, Resolve } from '@angular/router'
5import { AuthService } from '@app/core' 5import { AuthService } from '@app/core'
6import { listUserChannels } from '@app/helpers' 6import { listUserChannelsForSelect } from '@app/helpers'
7import { VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main' 7import { VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main'
8import { LiveVideoService } from '@app/shared/shared-video-live' 8import { LiveVideoService } from '@app/shared/shared-video-live'
9 9
@@ -33,7 +33,7 @@ export class VideoUpdateResolver implements Resolve<any> {
33 .loadCompleteDescription(video.descriptionPath) 33 .loadCompleteDescription(video.descriptionPath)
34 .pipe(map(description => Object.assign(video, { description }))), 34 .pipe(map(description => Object.assign(video, { description }))),
35 35
36 listUserChannels(this.authService), 36 listUserChannelsForSelect(this.authService),
37 37
38 this.videoCaptionService 38 this.videoCaptionService
39 .listCaptions(video.id) 39 .listCaptions(video.id)
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html
index d0e9bcd29..5014b9692 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.html
@@ -1,6 +1,6 @@
1<div *ngIf="isCommentDisplayed()" class="root-comment" [ngClass]="{ 'is-child': isChild() }"> 1<div *ngIf="isCommentDisplayed()" class="root-comment" [ngClass]="{ 'is-child': isChild() }">
2 <div class="left"> 2 <div class="left">
3 <my-actor-avatar *ngIf="!comment.isDeleted" [href]="comment.account.url" [account]="comment.account"></my-actor-avatar> 3 <my-actor-avatar *ngIf="!comment.isDeleted" [href]="comment.account.url" [account]="comment.account" [size]="isChild() ? '25' : '36'"></my-actor-avatar>
4 <div class="vertical-border"></div> 4 <div class="vertical-border"></div>
5 </div> 5 </div>
6 6
diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.scss b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.scss
index 87e313d41..54f828014 100644
--- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.scss
+++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.scss
@@ -25,10 +25,6 @@
25 } 25 }
26} 26}
27 27
28my-actor-avatar {
29 @include actor-avatar-size(36px);
30}
31
32.comment { 28.comment {
33 flex-grow: 1; 29 flex-grow: 1;
34 // Fix word-wrap with flex 30 // Fix word-wrap with flex
@@ -160,11 +156,6 @@ my-video-comment-add {
160} 156}
161 157
162.is-child { 158.is-child {
163 // Reduce avatars size for replies
164 my-actor-avatar {
165 @include actor-avatar-size(25px);
166 }
167
168 .left { 159 .left {
169 @include margin-right(6px); 160 @include margin-right(6px);
170 } 161 }
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html
index d433c7aba..a23152b67 100644
--- a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html
+++ b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.html
@@ -2,19 +2,19 @@
2 <my-actor-avatar 2 <my-actor-avatar
3 *ngIf="showChannel" 3 *ngIf="showChannel"
4 class="channel" 4 class="channel"
5 [class.main-avatar]="showChannel"
6 [channel]="video.channel" 5 [channel]="video.channel"
7 [internalHref]="[ '/c', video.byVideoChannel ]" 6 [internalHref]="[ '/c', video.byVideoChannel ]"
8 [title]="channelLinkTitle" 7 [title]="channelLinkTitle"
8 size="35"
9 ></my-actor-avatar> 9 ></my-actor-avatar>
10 10
11 <my-actor-avatar 11 <my-actor-avatar
12 *ngIf="showAccount" 12 *ngIf="showAccount"
13 class="account" 13 class="account"
14 [class.main-avatar]="!showChannel"
15 [class.second-avatar]="showChannel" 14 [class.second-avatar]="showChannel"
16 [account]="video.account" 15 [account]="video.account"
17 [internalHref]="[ '/a', video.byAccount ]" 16 [internalHref]="[ '/a', video.byAccount ]"
18 [title]="accountLinkTitle"> 17 [title]="accountLinkTitle"
18 size="35">
19 </my-actor-avatar> 19 </my-actor-avatar>
20</div> 20</div>
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss
index 71c5e4b5a..80711ff32 100644
--- a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss
+++ b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.scss
@@ -1,9 +1,5 @@
1@use '_mixins' as *; 1@use '_mixins' as *;
2 2
3@mixin main {
4 @include actor-avatar-size(35px);
5}
6
7@mixin secondary { 3@mixin secondary {
8 height: 60%; 4 height: 60%;
9 width: 60%; 5 width: 60%;
@@ -14,16 +10,11 @@
14} 10}
15 11
16.wrapper { 12.wrapper {
17 @include actor-avatar-size(35px);
18 @include margin-right(5px); 13 @include margin-right(5px);
19 14
20 position: relative; 15 position: relative;
21 margin-bottom: 5px; 16 margin-bottom: 5px;
22 17
23 .main-avatar {
24 @include main();
25 }
26
27 .second-avatar { 18 .second-avatar {
28 @include secondary(); 19 @include secondary();
29 } 20 }
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.ts
index 146c440b3..4bfc7bd9d 100644
--- a/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/metadata/video-avatar-channel.component.ts
@@ -20,8 +20,4 @@ export class VideoAvatarChannelComponent implements OnInit {
20 this.channelLinkTitle = $localize`${this.video.account.name} (channel page)` 20 this.channelLinkTitle = $localize`${this.video.account.name} (channel page)`
21 this.accountLinkTitle = $localize`${this.video.byAccount} (account page)` 21 this.accountLinkTitle = $localize`${this.video.byAccount} (account page)`
22 } 22 }
23
24 isChannelAvatarNull () {
25 return this.video.channel.avatar === null
26 }
27} 23}
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.html b/client/src/app/+videos/video-list/overview/video-overview.component.html
index 1a715560c..f250c2407 100644
--- a/client/src/app/+videos/video-list/overview/video-overview.component.html
+++ b/client/src/app/+videos/video-list/overview/video-overview.component.html
@@ -33,7 +33,7 @@
33 <div class="section channel videos" *ngFor="let object of overview.channels"> 33 <div class="section channel videos" *ngFor="let object of overview.channels">
34 <div class="section-title"> 34 <div class="section-title">
35 <a [routerLink]="[ '/c', buildVideoChannelBy(object) ]"> 35 <a [routerLink]="[ '/c', buildVideoChannelBy(object) ]">
36 <my-actor-avatar [channel]="buildVideoChannel(object)"></my-actor-avatar> 36 <my-actor-avatar [channel]="buildVideoChannel(object)" size="28"></my-actor-avatar>
37 37
38 <h2 class="section-title">{{ object.channel.displayName }}</h2> 38 <h2 class="section-title">{{ object.channel.displayName }}</h2>
39 </a> 39 </a>
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.scss b/client/src/app/+videos/video-list/overview/video-overview.component.scss
index 2239d1913..8b2aa88f2 100644
--- a/client/src/app/+videos/video-list/overview/video-overview.component.scss
+++ b/client/src/app/+videos/video-list/overview/video-overview.component.scss
@@ -52,7 +52,6 @@
52 align-items: center; 52 align-items: center;
53 53
54 my-actor-avatar { 54 my-actor-avatar {
55 @include actor-avatar-size(28px);
56 @include margin-right(8px); 55 @include margin-right(8px);
57 56
58 font-size: initial; 57 font-size: initial;
diff --git a/client/src/app/core/users/user.model.ts b/client/src/app/core/users/user.model.ts
index f211051ce..6ba30e4b8 100644
--- a/client/src/app/core/users/user.model.ts
+++ b/client/src/app/core/users/user.model.ts
@@ -132,8 +132,8 @@ export class User implements UserServerModel {
132 } 132 }
133 } 133 }
134 134
135 updateAccountAvatar (newAccountAvatar?: ActorImage) { 135 updateAccountAvatar (newAccountAvatars?: ActorImage[]) {
136 if (newAccountAvatar) this.account.updateAvatar(newAccountAvatar) 136 if (newAccountAvatars) this.account.updateAvatar(newAccountAvatars)
137 else this.account.resetAvatar() 137 else this.account.resetAvatar()
138 } 138 }
139 139
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts
index b14bff193..b4024c02d 100644
--- a/client/src/app/core/users/user.service.ts
+++ b/client/src/app/core/users/user.service.ts
@@ -118,7 +118,7 @@ export class UserService {
118 changeAvatar (avatarForm: FormData) { 118 changeAvatar (avatarForm: FormData) {
119 const url = UserService.BASE_USERS_URL + 'me/avatar/pick' 119 const url = UserService.BASE_USERS_URL + 'me/avatar/pick'
120 120
121 return this.authHttp.post<{ avatar: ActorImage }>(url, avatarForm) 121 return this.authHttp.post<{ avatars: ActorImage[] }>(url, avatarForm)
122 .pipe(catchError(err => this.restExtractor.handleError(err))) 122 .pipe(catchError(err => this.restExtractor.handleError(err)))
123 } 123 }
124 124
diff --git a/client/src/app/helpers/utils/channel.ts b/client/src/app/helpers/utils/channel.ts
index 93863a8af..2cd56d6e6 100644
--- a/client/src/app/helpers/utils/channel.ts
+++ b/client/src/app/helpers/utils/channel.ts
@@ -1,8 +1,9 @@
1import { minBy } from 'lodash-es'
1import { first, map } from 'rxjs/operators' 2import { first, map } from 'rxjs/operators'
2import { SelectChannelItem } from 'src/types/select-options-item.model' 3import { SelectChannelItem } from 'src/types/select-options-item.model'
3import { AuthService } from '../../core/auth' 4import { AuthService } from '../../core/auth'
4 5
5function listUserChannels (authService: AuthService) { 6function listUserChannelsForSelect (authService: AuthService) {
6 return authService.userInformationLoaded 7 return authService.userInformationLoaded
7 .pipe( 8 .pipe(
8 first(), 9 first(),
@@ -23,12 +24,12 @@ function listUserChannels (authService: AuthService) {
23 id: c.id, 24 id: c.id,
24 label: c.displayName, 25 label: c.displayName,
25 support: c.support, 26 support: c.support,
26 avatarPath: c.avatar?.path 27 avatarPath: minBy(c.avatars, 'width')[0]?.path
27 }) as SelectChannelItem) 28 }) as SelectChannelItem)
28 }) 29 })
29 ) 30 )
30} 31}
31 32
32export { 33export {
33 listUserChannels 34 listUserChannelsForSelect
34} 35}
diff --git a/client/src/app/modal/account-setup-warning-modal.component.ts b/client/src/app/modal/account-setup-warning-modal.component.ts
index c78de447d..c4cbf92b6 100644
--- a/client/src/app/modal/account-setup-warning-modal.component.ts
+++ b/client/src/app/modal/account-setup-warning-modal.component.ts
@@ -32,7 +32,7 @@ export class AccountSetupWarningModalComponent {
32 } 32 }
33 33
34 hasAccountAvatar (user: User) { 34 hasAccountAvatar (user: User) {
35 return !!user.account.avatar 35 return user.account.avatars.length !== 0
36 } 36 }
37 37
38 hasAccountDescription (user: User) { 38 hasAccountDescription (user: User) {
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
index 07cc73461..f0a27c6e2 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
@@ -43,7 +43,7 @@
43 <td *ngIf="isAdminView()"> 43 <td *ngIf="isAdminView()">
44 <a *ngIf="abuse.reporterAccount" [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 44 <a *ngIf="abuse.reporterAccount" [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
45 <div class="chip two-lines"> 45 <div class="chip two-lines">
46 <my-actor-avatar [account]="abuse.reporterAccount"></my-actor-avatar> 46 <my-actor-avatar [account]="abuse.reporterAccount" size="32"></my-actor-avatar>
47 <div> 47 <div>
48 {{ abuse.reporterAccount.displayName }} 48 {{ abuse.reporterAccount.displayName }}
49 <span>{{ abuse.reporterAccount.nameWithHost }}</span> 49 <span>{{ abuse.reporterAccount.nameWithHost }}</span>
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
index 8b7d64ed3..01bb401fb 100644
--- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
+++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
@@ -72,7 +72,7 @@ export class ActorAvatarEditComponent implements OnInit {
72 } 72 }
73 73
74 hasAvatar () { 74 hasAvatar () {
75 return !!this.preview || !!this.actor.avatar 75 return !!this.preview || this.actor.avatars.length !== 0
76 } 76 }
77 77
78 isChannel () { 78 isChannel () {
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.scss b/client/src/app/shared/shared-actor-image/actor-avatar.component.scss
index a2424b593..68bf74553 100644
--- a/client/src/app/shared/shared-actor-image/actor-avatar.component.scss
+++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.scss
@@ -20,38 +20,23 @@
20 } 20 }
21} 21}
22 22
23.avatar-18 { 23$sizes: '18', '25', '28', '32', '34', '35', '36', '40', '48', '75', '80', '100', '120';
24 --avatarSize: 18px;
25 --initialFontSize: 13px;
26}
27 24
28.avatar-25 { 25@each $size in $sizes {
29 --avatarSize: 25px; 26 .avatar-#{$size} {
27 --avatarSize: #{$size}px;
28 }
30} 29}
31 30
32.avatar-32 { 31.avatar-18 {
33 --avatarSize: 32px; 32 --initialFontSize: 13px;
34}
35
36.avatar-34 {
37 --avatarSize: 34px;
38}
39
40.avatar-36 {
41 --avatarSize: 36px;
42}
43
44.avatar-40 {
45 --avatarSize: 40px;
46} 33}
47 34
48.avatar-100 { 35.avatar-100 {
49 --avatarSize: 100px;
50 --initialFontSize: 40px; 36 --initialFontSize: 40px;
51} 37}
52 38
53.avatar-120 { 39.avatar-120 {
54 --avatarSize: 120px;
55 --initialFontSize: 46px; 40 --initialFontSize: 46px;
56} 41}
57 42
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
index c323dc724..bc7e8a096 100644
--- a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
+++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
@@ -4,11 +4,11 @@ import { Account } from '../shared-main/account/account.model'
4 4
5type ActorInput = { 5type ActorInput = {
6 name: string 6 name: string
7 avatar?: { url?: string, path: string } 7 avatars: { width: number, url?: string, path: string }[]
8 url: string 8 url: string
9} 9}
10 10
11export type ActorAvatarSize = '18' | '25' | '32' | '34' | '36' | '40' | '100' | '120' 11export type ActorAvatarSize = '18' | '25' | '28' | '32' | '34' | '35' | '36' | '40' | '48' | '75' | '80' | '100' | '120'
12 12
13@Component({ 13@Component({
14 selector: 'my-actor-avatar', 14 selector: 'my-actor-avatar',
@@ -23,7 +23,7 @@ export class ActorAvatarComponent {
23 23
24 @Input() previewImage: string 24 @Input() previewImage: string
25 25
26 @Input() size: ActorAvatarSize 26 @Input() size: ActorAvatarSize = '32'
27 27
28 // Use an external link 28 // Use an external link
29 @Input() href: string 29 @Input() href: string
@@ -50,14 +50,13 @@ export class ActorAvatarComponent {
50 } 50 }
51 51
52 get defaultAvatarUrl () { 52 get defaultAvatarUrl () {
53 if (this.channel) return VideoChannel.GET_DEFAULT_AVATAR_URL() 53 if (this.account) return Account.GET_DEFAULT_AVATAR_URL(+this.size)
54 54 if (this.channel) return VideoChannel.GET_DEFAULT_AVATAR_URL(+this.size)
55 return Account.GET_DEFAULT_AVATAR_URL()
56 } 55 }
57 56
58 get avatarUrl () { 57 get avatarUrl () {
59 if (this.account) return Account.GET_ACTOR_AVATAR_URL(this.account) 58 if (this.account) return Account.GET_ACTOR_AVATAR_URL(this.account, +this.size)
60 if (this.channel) return VideoChannel.GET_ACTOR_AVATAR_URL(this.channel) 59 if (this.channel) return VideoChannel.GET_ACTOR_AVATAR_URL(this.channel, +this.size)
61 60
62 return '' 61 return ''
63 } 62 }
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.html b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.html
index de150aac9..52a402329 100644
--- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.html
+++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.html
@@ -1,7 +1,7 @@
1<div *ngIf="channel" class="channel"> 1<div *ngIf="channel" class="channel">
2 2
3 <div class="channel-avatar-row"> 3 <div class="channel-avatar-row">
4 <my-actor-avatar [channel]="channel" [internalHref]="getVideoChannelLink()" i18n-title title="See this video channel"></my-actor-avatar> 4 <my-actor-avatar [channel]="channel" [internalHref]="getVideoChannelLink()" i18n-title title="See this video channel" size="75"></my-actor-avatar>
5 5
6 <h6> 6 <h6>
7 <a [routerLink]="getVideoChannelLink()" i18n-title title="See this video channel"> 7 <a [routerLink]="getVideoChannelLink()" i18n-title title="See this video channel">
diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.scss b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.scss
index 39e8d2091..e8ef478d9 100644
--- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.scss
+++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.scss
@@ -26,8 +26,6 @@
26 } 26 }
27 27
28 my-actor-avatar { 28 my-actor-avatar {
29 @include actor-avatar-size(75px);
30
31 grid-column: 1; 29 grid-column: 1;
32 grid-row: 1 / 4; 30 grid-row: 1 / 4;
33 } 31 }
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 40a7c53bb..5fcae0050 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
@@ -31,7 +31,7 @@ export class SelectChannelComponent implements ControlValueAccessor, OnChanges {
31 this.channels = this.items.map(c => { 31 this.channels = this.items.map(c => {
32 const avatarPath = c.avatarPath 32 const avatarPath = c.avatarPath
33 ? c.avatarPath 33 ? c.avatarPath
34 : VideoChannel.GET_DEFAULT_AVATAR_URL() 34 : VideoChannel.GET_DEFAULT_AVATAR_URL(20)
35 35
36 return Object.assign({}, c, { avatarPath }) 36 return Object.assign({}, c, { avatarPath })
37 }) 37 })
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 8b78d01a6..a26a9c11c 100644
--- a/client/src/app/shared/shared-main/account/account.model.ts
+++ b/client/src/app/shared/shared-main/account/account.model.ts
@@ -17,11 +17,15 @@ export class Account extends Actor implements ServerAccount {
17 17
18 userId?: number 18 userId?: number
19 19
20 static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) { 20 static GET_ACTOR_AVATAR_URL (actor: { avatars: { width: number, url?: string, path: string }[] }, size: number) {
21 return Actor.GET_ACTOR_AVATAR_URL(actor) 21 return Actor.GET_ACTOR_AVATAR_URL(actor, size)
22 } 22 }
23 23
24 static GET_DEFAULT_AVATAR_URL () { 24 static GET_DEFAULT_AVATAR_URL (size: number) {
25 if (size <= 48) {
26 return `${window.location.origin}/client/assets/images/default-avatar-account-48x48.png`
27 }
28
25 return `${window.location.origin}/client/assets/images/default-avatar-account.png` 29 return `${window.location.origin}/client/assets/images/default-avatar-account.png`
26 } 30 }
27 31
@@ -42,12 +46,12 @@ export class Account extends Actor implements ServerAccount {
42 this.mutedServerByInstance = false 46 this.mutedServerByInstance = false
43 } 47 }
44 48
45 updateAvatar (newAvatar: ActorImage) { 49 updateAvatar (newAvatars: ActorImage[]) {
46 this.avatar = newAvatar 50 this.avatars = newAvatars
47 } 51 }
48 52
49 resetAvatar () { 53 resetAvatar () {
50 this.avatar = null 54 this.avatars = []
51 } 55 }
52 56
53 updateBlockStatus (blockStatus: BlockStatus) { 57 updateBlockStatus (blockStatus: BlockStatus) {
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 082f44fb9..a54f51aa4 100644
--- a/client/src/app/shared/shared-main/account/actor.model.ts
+++ b/client/src/app/shared/shared-main/account/actor.model.ts
@@ -13,20 +13,22 @@ export abstract class Actor implements ServerActor {
13 13
14 createdAt: Date | string 14 createdAt: Date | string
15 15
16 avatar: ActorImage 16 // TODO: remove, deprecated in 4.2
17 avatar: never
18
19 avatars: ActorImage[]
17 20
18 isLocal: boolean 21 isLocal: boolean
19 22
20 static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) { 23 static GET_ACTOR_AVATAR_URL (actor: { avatars: { width: number, url?: string, path: string }[] }, size: number) {
21 if (actor?.avatar?.url) return actor.avatar.url 24 const avatar = actor.avatars.sort((a, b) => a.width - b.width).find(a => a.width >= size)
22 25
23 if (actor?.avatar) { 26 if (!avatar) return ''
24 const absoluteAPIUrl = getAbsoluteAPIUrl() 27 if (avatar.url) return avatar.url
25 28
26 return absoluteAPIUrl + actor.avatar.path 29 const absoluteAPIUrl = getAbsoluteAPIUrl()
27 }
28 30
29 return '' 31 return absoluteAPIUrl + avatar.path
30 } 32 }
31 33
32 static CREATE_BY_STRING (accountName: string, host: string, forceHostname = false) { 34 static CREATE_BY_STRING (accountName: string, host: string, forceHostname = false) {
@@ -55,7 +57,7 @@ export abstract class Actor implements ServerActor {
55 57
56 if (hash.createdAt) this.createdAt = new Date(hash.createdAt.toString()) 58 if (hash.createdAt) this.createdAt = new Date(hash.createdAt.toString())
57 59
58 this.avatar = hash.avatar 60 this.avatars = hash.avatars
59 this.isLocal = Actor.IS_LOCAL(this.host) 61 this.isLocal = Actor.IS_LOCAL(this.host)
60 } 62 }
61} 63}
diff --git a/client/src/app/shared/shared-main/misc/channels-setup-message.component.ts b/client/src/app/shared/shared-main/misc/channels-setup-message.component.ts
index 702475029..4f9cbc525 100644
--- a/client/src/app/shared/shared-main/misc/channels-setup-message.component.ts
+++ b/client/src/app/shared/shared-main/misc/channels-setup-message.component.ts
@@ -19,7 +19,7 @@ export class ChannelsSetupMessageComponent implements OnInit {
19 hasChannelNotConfigured () { 19 hasChannelNotConfigured () {
20 if (!this.user.videoChannels) return false 20 if (!this.user.videoChannels) return false
21 21
22 return this.user.videoChannels.filter((channel: VideoChannel) => (!channel.avatar || !channel.description)).length > 0 22 return this.user.videoChannels.filter((channel: VideoChannel) => (channel.avatars.length === 0 || !channel.description)).length > 0
23 } 23 }
24 24
25 ngOnInit () { 25 ngOnInit () {
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 439547102..1eb69d5a2 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
@@ -254,11 +254,11 @@ export class UserNotification implements UserNotificationServer {
254 return [ this.buildVideoUrl(comment.video), { threadId: comment.threadId } ] 254 return [ this.buildVideoUrl(comment.video), { threadId: comment.threadId } ]
255 } 255 }
256 256
257 private setAccountAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { 257 private setAccountAvatarUrl (actor: { avatarUrl?: string, avatars: { width: number, url?: string, path: string }[] }) {
258 actor.avatarUrl = Account.GET_ACTOR_AVATAR_URL(actor) || Account.GET_DEFAULT_AVATAR_URL() 258 actor.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(actor, 48) || Account.GET_DEFAULT_AVATAR_URL(48)
259 } 259 }
260 260
261 private setVideoChannelAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { 261 private setVideoChannelAvatarUrl (actor: { avatarUrl?: string, avatars: { width: number, url?: string, path: string }[] }) {
262 actor.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(actor) || VideoChannel.GET_DEFAULT_AVATAR_URL() 262 actor.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(actor, 48) || VideoChannel.GET_DEFAULT_AVATAR_URL(48)
263 } 263 }
264} 264}
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 ac2679b42..e22b0cfd0 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
@@ -12,7 +12,11 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
12 nameWithHost: string 12 nameWithHost: string
13 nameWithHostForced: string 13 nameWithHostForced: string
14 14
15 banner: ActorImage 15 // TODO: remove, deprecated in 4.2
16 banner: never
17
18 banners: ActorImage[]
19
16 bannerUrl: string 20 bannerUrl: string
17 21
18 updatedAt: Date | string 22 updatedAt: Date | string
@@ -24,23 +28,25 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
24 28
25 viewsPerDay?: ViewsPerDate[] 29 viewsPerDay?: ViewsPerDate[]
26 30
27 static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) { 31 static GET_ACTOR_AVATAR_URL (actor: { avatars: { width: number, url?: string, path: string }[] }, size: number) {
28 return Actor.GET_ACTOR_AVATAR_URL(actor) 32 return Actor.GET_ACTOR_AVATAR_URL(actor, size)
29 } 33 }
30 34
31 static GET_ACTOR_BANNER_URL (channel: ServerVideoChannel) { 35 static GET_ACTOR_BANNER_URL (channel: ServerVideoChannel) {
32 if (channel?.banner?.url) return channel.banner.url 36 if (!channel) return ''
33
34 if (channel?.banner) {
35 const absoluteAPIUrl = getAbsoluteAPIUrl()
36 37
37 return absoluteAPIUrl + channel.banner.path 38 const banner = channel.banners[0]
38 } 39 if (!banner) return ''
39 40
40 return '' 41 if (banner.url) return banner.url
42 return getAbsoluteAPIUrl() + banner.path
41 } 43 }
42 44
43 static GET_DEFAULT_AVATAR_URL () { 45 static GET_DEFAULT_AVATAR_URL (size: number) {
46 if (size <= 48) {
47 return `${window.location.origin}/client/assets/images/default-avatar-video-channel-48x48.png`
48 }
49
44 return `${window.location.origin}/client/assets/images/default-avatar-video-channel.png` 50 return `${window.location.origin}/client/assets/images/default-avatar-video-channel.png`
45 } 51 }
46 52
@@ -51,7 +57,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
51 this.description = hash.description 57 this.description = hash.description
52 this.support = hash.support 58 this.support = hash.support
53 59
54 this.banner = hash.banner 60 this.banners = hash.banners
55 61
56 this.isLocal = hash.isLocal 62 this.isLocal = hash.isLocal
57 63
@@ -74,24 +80,24 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
74 this.updateComputedAttributes() 80 this.updateComputedAttributes()
75 } 81 }
76 82
77 updateAvatar (newAvatar: ActorImage) { 83 updateAvatar (newAvatars: ActorImage[]) {
78 this.avatar = newAvatar 84 this.avatars = newAvatars
79 85
80 this.updateComputedAttributes() 86 this.updateComputedAttributes()
81 } 87 }
82 88
83 resetAvatar () { 89 resetAvatar () {
84 this.updateAvatar(null) 90 this.updateAvatar([])
85 } 91 }
86 92
87 updateBanner (newBanner: ActorImage) { 93 updateBanner (newBanners: ActorImage[]) {
88 this.banner = newBanner 94 this.banners = newBanners
89 95
90 this.updateComputedAttributes() 96 this.updateComputedAttributes()
91 } 97 }
92 98
93 resetBanner () { 99 resetBanner () {
94 this.updateBanner(null) 100 this.updateBanner([])
95 } 101 }
96 102
97 updateComputedAttributes () { 103 updateComputedAttributes () {
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 f37f13c51..480d250fb 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
@@ -80,7 +80,7 @@ export class VideoChannelService {
80 changeVideoChannelImage (videoChannelName: string, avatarForm: FormData, type: 'avatar' | 'banner') { 80 changeVideoChannelImage (videoChannelName: string, avatarForm: FormData, type: 'avatar' | 'banner') {
81 const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName + '/' + type + '/pick' 81 const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName + '/' + type + '/pick'
82 82
83 return this.authHttp.post<{ avatar?: ActorImage, banner?: ActorImage }>(url, avatarForm) 83 return this.authHttp.post<{ avatars?: ActorImage[], banners?: ActorImage[] }>(url, avatarForm)
84 .pipe(catchError(err => this.restExtractor.handleError(err))) 84 .pipe(catchError(err => this.restExtractor.handleError(err)))
85 } 85 }
86 86
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 fe5643688..8e275181c 100644
--- a/client/src/app/shared/shared-main/video/video.model.ts
+++ b/client/src/app/shared/shared-main/video/video.model.ts
@@ -84,7 +84,11 @@ export class Video implements VideoServerModel {
84 displayName: string 84 displayName: string
85 url: string 85 url: string
86 host: string 86 host: string
87 avatar?: ActorImage 87
88 // TODO: remove, deprecated in 4.2
89 avatar: ActorImage
90
91 avatars: ActorImage[]
88 } 92 }
89 93
90 channel: { 94 channel: {
@@ -93,7 +97,11 @@ export class Video implements VideoServerModel {
93 displayName: string 97 displayName: string
94 url: string 98 url: string
95 host: string 99 host: string
96 avatar?: ActorImage 100
101 // TODO: remove, deprecated in 4.2
102 avatar: ActorImage
103
104 avatars: ActorImage[]
97 } 105 }
98 106
99 userHistory?: { 107 userHistory?: {
diff --git a/client/src/app/shared/shared-moderation/account-blocklist.component.html b/client/src/app/shared/shared-moderation/account-blocklist.component.html
index 637abcb51..0143194e9 100644
--- a/client/src/app/shared/shared-moderation/account-blocklist.component.html
+++ b/client/src/app/shared/shared-moderation/account-blocklist.component.html
@@ -33,7 +33,7 @@
33 <td> 33 <td>
34 <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 34 <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
35 <div class="chip two-lines"> 35 <div class="chip two-lines">
36 <my-actor-avatar [account]="accountBlock.blockedAccount"></my-actor-avatar> 36 <my-actor-avatar [account]="accountBlock.blockedAccount" size="32"></my-actor-avatar>
37 <div> 37 <div>
38 {{ accountBlock.blockedAccount.displayName }} 38 {{ accountBlock.blockedAccount.displayName }}
39 <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span> 39 <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span>
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.html b/client/src/app/shared/shared-video-miniature/video-miniature.component.html
index 30483831a..3cf128de0 100644
--- a/client/src/app/shared/shared-video-miniature/video-miniature.component.html
+++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.html
@@ -13,11 +13,13 @@
13 <my-actor-avatar 13 <my-actor-avatar
14 *ngIf="displayOptions.avatar && displayOwnerVideoChannel() && !displayAsRow" [title]="channelLinkTitle" 14 *ngIf="displayOptions.avatar && displayOwnerVideoChannel() && !displayAsRow" [title]="channelLinkTitle"
15 [channel]="video.channel" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]" 15 [channel]="video.channel" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]"
16 size="32"
16 ></my-actor-avatar> 17 ></my-actor-avatar>
17 18
18 <my-actor-avatar 19 <my-actor-avatar
19 *ngIf="displayOptions.avatar && displayOwnerAccount() && !displayAsRow" [title]="channelLinkTitle" 20 *ngIf="displayOptions.avatar && displayOwnerAccount() && !displayAsRow" [title]="channelLinkTitle"
20 [account]="video.account" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]" 21 [account]="video.account" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]"
22 size="32"
21 ></my-actor-avatar> 23 ></my-actor-avatar>
22 24
23 <div class="w-100 d-flex flex-column"> 25 <div class="w-100 d-flex flex-column">