]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Refactor actor avatar display
authorChocobozzz <me@florianbigard.com>
Wed, 28 Apr 2021 09:49:34 +0000 (11:49 +0200)
committerChocobozzz <me@florianbigard.com>
Wed, 28 Apr 2021 09:49:34 +0000 (11:49 +0200)
62 files changed:
client/src/app/+accounts/account-video-channels/account-video-channels.component.html
client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
client/src/app/+accounts/accounts.component.html
client/src/app/+accounts/accounts.module.ts
client/src/app/+admin/admin.module.ts
client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
client/src/app/+admin/users/user-list/user-list.component.html
client/src/app/+my-account/my-account.module.ts
client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss
client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts
client/src/app/+my-library/my-library.module.ts
client/src/app/+my-library/my-ownership/my-ownership.component.html
client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html
client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss
client/src/app/+search/search.component.html
client/src/app/+search/search.component.scss
client/src/app/+search/search.component.ts
client/src/app/+search/search.module.ts
client/src/app/+video-channels/video-channels.component.html
client/src/app/+video-channels/video-channels.component.scss
client/src/app/+video-channels/video-channels.module.ts
client/src/app/+videos/+video-watch/comment/video-comment-add.component.html
client/src/app/+videos/+video-watch/comment/video-comment-add.component.scss
client/src/app/+videos/+video-watch/comment/video-comment.component.html
client/src/app/+videos/+video-watch/comment/video-comment.component.scss
client/src/app/+videos/+video-watch/comment/video-comment.component.ts
client/src/app/+videos/+video-watch/video-avatar-channel.component.html
client/src/app/+videos/+video-watch/video-watch.module.ts
client/src/app/+videos/video-list/overview/video-overview.component.html
client/src/app/+videos/video-list/overview/video-overview.component.scss
client/src/app/+videos/video-list/overview/video-overview.component.ts
client/src/app/+videos/videos.module.ts
client/src/app/app.module.ts
client/src/app/menu/menu.component.html
client/src/app/menu/menu.component.scss
client/src/app/shared/shared-abuse-list/abuse-details.component.html
client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts
client/src/app/shared/shared-account-avatar/account-avatar.component.html [deleted file]
client/src/app/shared/shared-account-avatar/account-avatar.component.ts [deleted file]
client/src/app/shared/shared-account-avatar/index.ts [deleted file]
client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html
client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss
client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
client/src/app/shared/shared-actor-image-edit/shared-actor-image-edit.module.ts
client/src/app/shared/shared-actor-image/actor-avatar.component.html [new file with mode: 0644]
client/src/app/shared/shared-actor-image/actor-avatar.component.scss [moved from client/src/app/shared/shared-account-avatar/account-avatar.component.scss with 53% similarity]
client/src/app/shared/shared-actor-image/actor-avatar.component.ts [new file with mode: 0644]
client/src/app/shared/shared-actor-image/index.ts [new file with mode: 0644]
client/src/app/shared/shared-actor-image/shared-actor-image.module.ts [moved from client/src/app/shared/shared-account-avatar/shared-account-avatar.module.ts with 65% similarity]
client/src/app/shared/shared-main/users/user-notification.model.ts
client/src/app/shared/shared-main/users/user-notifications.component.scss
client/src/app/shared/shared-main/video-channel/video-channel.model.ts
client/src/app/shared/shared-moderation/account-blocklist.component.html
client/src/app/shared/shared-moderation/shared-moderation.module.ts
client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts
client/src/app/shared/shared-video-miniature/video-miniature.component.html
client/src/app/shared/shared-video-miniature/video-miniature.component.scss
client/src/sass/include/_actor.scss
client/src/sass/include/_mixins.scss

index 19a4b3c9c4888fe0c7b38e5c1f546603b6acfe97..922608127ac871218a0e71c0ff0cb18c87b0a93a 100644 (file)
@@ -8,9 +8,10 @@
     <div class="channel" *ngFor="let videoChannel of videoChannels">
 
       <div class="channel-avatar-row">
-        <a class="avatar-link" [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel">
-          <img [src]="videoChannel.avatarUrl" alt="Avatar" />
-        </a>
+        <my-actor-avatar
+          [channel]="videoChannel" [internalHref]="getVideoChannelLink(videoChannel)"
+          i18n-title title="See this video channel"
+        ></my-actor-avatar>
 
         <h2>
           <a [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel">
index 7e88802f323027e5e1e712c57652e49d50b23063..0c490ad5393d99ee4369a33d53222e58929e281b 100644 (file)
   grid-template-columns: auto auto 1fr;
   grid-template-rows: auto 1fr;
 
-  .avatar-link {
+  my-actor-avatar {
+    @include actor-avatar-size(75px);
+
     grid-column: 1;
     grid-row: 1 / 3;
-    margin-right: 30px;
-  }
-
-  img {
-    @include channel-avatar(75px);
+    margin-right: 15px;
   }
 
   a {
index ea7a317eb63780f64cb08c6bbe8a352ecad7eb79..350c77f1e697d275d8cdabc4d335fb3bc04c2c0f 100644 (file)
@@ -2,7 +2,7 @@
   <div class="account-info">
 
     <div class="account-avatar-row">
-      <my-account-avatar [account]="account" size="120"></my-account-avatar>
+      <my-actor-avatar class="main-avatar" [account]="account"></my-actor-avatar>
 
       <div>
         <div class="section-label" i18n>PEERTUBE ACCOUNT</div>
index 22cdd0642550d8fe99b9e35d1e4bcc81bd7306cb..1bafc5141b45cb56204da3a12bd59254fd339de6 100644 (file)
@@ -10,7 +10,7 @@ import { AccountVideoChannelsComponent } from './account-video-channels/account-
 import { AccountVideosComponent } from './account-videos/account-videos.component'
 import { AccountsRoutingModule } from './accounts-routing.module'
 import { AccountsComponent } from './accounts.component'
-import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
 
 @NgModule({
   imports: [
@@ -22,7 +22,7 @@ import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/share
     SharedModerationModule,
     SharedVideoMiniatureModule,
     SharedGlobalIconModule,
-    SharedAccountAvatarModule
+    SharedActorImageModule
   ],
 
   declarations: [
index 97ce8d1b8018be4e8a9533fddf1fc94ee2d6a6ed..45366f9ec41c1770d2a22548a250c61bc3530b74 100644 (file)
@@ -9,7 +9,7 @@ import { SharedGlobalIconModule } from '@app/shared/shared-icons'
 import { SharedMainModule } from '@app/shared/shared-main'
 import { SharedModerationModule } from '@app/shared/shared-moderation'
 import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
-import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
 import { AdminRoutingModule } from './admin-routing.module'
 import { AdminComponent } from './admin.component'
 import {
@@ -51,7 +51,7 @@ import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersCom
     SharedGlobalIconModule,
     SharedAbuseListModule,
     SharedVideoCommentModule,
-    SharedAccountAvatarModule,
+    SharedActorImageModule,
     SharedActorImageEditModule,
 
     TableModule,
index f5cf93adb8ab48f57e992f77b463c30c6d16a88f..84ce381cc17e7a95ad00d3d71a36669f6da0e8f4 100644 (file)
@@ -34,7 +34,7 @@
       <td>
         <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
           <div class="chip two-lines">
-            <my-account-avatar [account]="accountBlock.blockedAccount"></my-account-avatar>
+            <my-actor-avatar [account]="accountBlock.blockedAccount"></my-actor-avatar>
             <div>
               {{ accountBlock.blockedAccount.displayName }}
               <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span>
index d360c3c51043559cb6c91be006fb50bc42799a67..b6cec9c5165725cb98a4ea4be905b751d0deeaf6 100644 (file)
@@ -86,7 +86,7 @@
       <td>
         <a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
           <div class="chip two-lines">
-            <my-account-avatar [account]="videoComment.account"></my-account-avatar>
+            <my-actor-avatar [account]="videoComment.account"></my-actor-avatar>
           <div>
               {{ videoComment.account.displayName }}
               <span>{{ videoComment.by }}</span>
index eefb8ea88bbe9c002c9a086f934be7cb8f6f9b99..f84d3fd0c18c62317ff9de98bccb507453f74020 100644 (file)
       <td *ngIf="isSelected('username')">
         <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/accounts/' + user.username ]">
           <div class="chip two-lines">
-            <my-account-avatar [account]="user?.account" size="32"></my-account-avatar>
+            <my-actor-avatar [account]="user?.account" size="32"></my-actor-avatar>
            <div>
               <span class="user-table-primary-text">{{ user.account.displayName }}</span>
               <span class="text-muted">{{ user.username }}</span>
index 36df10edcbc2869bde76aedcd1caa10888b64927..4081e4f01376096c9a98c73703ffb9abe3341317 100644 (file)
@@ -10,7 +10,7 @@ import { SharedMainModule } from '@app/shared/shared-main'
 import { SharedModerationModule } from '@app/shared/shared-moderation'
 import { SharedShareModal } from '@app/shared/shared-share-modal'
 import { SharedUserInterfaceSettingsModule } from '@app/shared/shared-user-settings'
-import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
 import { MyAccountAbusesListComponent } from './my-account-abuses/my-account-abuses-list.component'
 import { MyAccountApplicationsComponent } from './my-account-applications/my-account-applications.component'
 import { MyAccountBlocklistComponent } from './my-account-blocklist/my-account-blocklist.component'
@@ -40,7 +40,7 @@ import { MyAccountComponent } from './my-account.component'
     SharedGlobalIconModule,
     SharedAbuseListModule,
     SharedShareModal,
-    SharedAccountAvatarModule,
+    SharedActorImageModule,
     SharedActorImageEditModule
   ],
 
index b704a1cc6835ecc7af295e48204193bd1cc3ef90..a2bdfa31a2f36f576fd3bb3b748618d318905115 100644 (file)
@@ -22,9 +22,7 @@
 
 <div class="video-channels">
   <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel">
-    <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]">
-      <img [src]="videoChannel.avatarUrl" alt="Avatar" />
-    </a>
+    <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/video-channels', videoChannel.nameWithHost ]"></my-actor-avatar>
 
     <div class="video-channel-info">
       <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page">
index 8804fa95c533c2fb42fbf332027d1e4b42f1b04a..dafba925e86668882cca0a8eb2edf7a5d3a6ff4f 100644 (file)
@@ -20,8 +20,8 @@ input[type=text] {
 
   padding-bottom: 0;
 
-  img {
-    @include channel-avatar(80px);
+  my-actor-avatar {
+    @include actor-avatar-size(80px);
 
     margin-right: 10px;
   }
index a23b53ee0f73f0d0c54ec5fad71e6b7c03e67c3c..c775bfdee46e931a917092394dea19b7316c213e 100644 (file)
@@ -8,6 +8,7 @@ import { MyVideoChannelCreateComponent } from './my-video-channel-create.compone
 import { MyVideoChannelUpdateComponent } from './my-video-channel-update.component'
 import { MyVideoChannelsRoutingModule } from './my-video-channels-routing.module'
 import { MyVideoChannelsComponent } from './my-video-channels.component'
+import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-actor-image.module'
 
 @NgModule({
   imports: [
@@ -18,7 +19,8 @@ import { MyVideoChannelsComponent } from './my-video-channels.component'
     SharedMainModule,
     SharedFormModule,
     SharedGlobalIconModule,
-    SharedActorImageEditModule
+    SharedActorImageEditModule,
+    SharedActorImageModule
   ],
 
   declarations: [
index a1d706f0bf35e2ecc61f4c776abf063795baac67..264ad03f7d19b9467d05d16099a482ab2f6c023e 100644 (file)
@@ -26,7 +26,7 @@ import { MyVideoPlaylistUpdateComponent } from './my-video-playlists/my-video-pl
 import { MyVideoPlaylistsComponent } from './my-video-playlists/my-video-playlists.component'
 import { VideoChangeOwnershipComponent } from './my-videos/modals/video-change-ownership.component'
 import { MyVideosComponent } from './my-videos/my-videos.component'
-import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
 
 @NgModule({
   imports: [
@@ -47,7 +47,7 @@ import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/share
     SharedAbuseListModule,
     SharedShareModal,
     SharedVideoLiveModule,
-    SharedAccountAvatarModule
+    SharedActorImageModule
   ],
 
   declarations: [
index d0eff0521bae7b008f0e9a45ca66569d259b1c2a..4c02c78fc22c8d0a21375fbd5896f01e8316e09e 100644 (file)
@@ -37,7 +37,7 @@
       <td>
         <a [href]="videoChangeOwnership.initiatorAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
           <div class="chip two-lines">
-            <my-account-avatar [account]="videoChangeOwnership.initiatorAccount"></my-account-avatar>
+            <my-actor-avatar [account]="videoChangeOwnership.initiatorAccount"></my-actor-avatar>
             <div>
               {{ videoChangeOwnership.initiatorAccount.displayName }}
               <span class="text-muted">{{ videoChangeOwnership.initiatorAccount.nameWithHost }}</span>
index ff448ad871f70c5a02fb1446347f2bd60cc99a4d..853d47fe6fb6d0869cce21cbd77410a668413479 100644 (file)
@@ -19,9 +19,7 @@
 
 <div class="video-channels" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
   <div *ngFor="let videoChannel of videoChannels" class="video-channel">
-    <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]">
-      <img [src]="videoChannel.avatarUrl" alt="Avatar" />
-    </a>
+    <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/video-channels', videoChannel.nameWithHost ]"></my-actor-avatar>
 
     <div class="video-channel-info">
       <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page">
@@ -33,7 +31,8 @@
 
       <a [routerLink]="[ '/accounts', videoChannel.ownerBy ]" i18n-title title="Owner account page" class="actor-owner">
         <span i18n>Created by {{ videoChannel.ownerBy }}</span>
-        <img [src]="videoChannel.ownerAvatarUrl" alt="Owner account avatar" />
+
+        <my-actor-avatar [account]="videoChannel.ownerAccount" size="18"></my-actor-avatar>
       </a>
     </div>
 
index 3c1a4d2ad0e1008845a1b232346d4006c6ddff32..53ceaa250f5cac0ba2b3eda55fa1be93efedbc67 100644 (file)
@@ -8,8 +8,8 @@ input[type=text] {
 .video-channel {
   @include row-blocks;
 
-  img {
-    @include channel-avatar(80px);
+  > my-actor-avatar {
+    @include actor-avatar-size(80px);
 
     margin-right: 10px;
   }
@@ -40,9 +40,20 @@ input[type=text] {
 }
 
 .actor-owner {
-  @include actor-owner;
+  @include disable-default-a-behaviour;
+
+  font-size: 13px;
+  color: pvar(--mainForegroundColor);
 
-  margin-top: 0;
+  span:hover {
+    opacity: 0.8;
+  }
+
+  my-actor-avatar {
+    margin-left: 7px;
+    display: inline-block;
+    vertical-align: top;
+  }
 }
 
 .video-subscriptions-header {
index 65d4b6ecd63cff61b3fd9dfc58f1d6f216158938..130be75fc11f1cd2e24caee405ab174ca964552c 100644 (file)
 
   <ng-container *ngFor="let result of results">
     <div *ngIf="isVideoChannel(result)" class="entry video-channel">
-      <a class="link-avatar" *ngIf="!isExternalChannelUrl()" [routerLink]="getChannelUrl(result)">
-        <img [src]="result.avatarUrl" alt="Avatar" />
-      </a>
 
-      <a class="link-avatar" *ngIf="isExternalChannelUrl()" [href]="getChannelUrl(result)" target="_blank">
-        <img [src]="result.avatarUrl" alt="Avatar" />
-      </a>
+      <my-actor-avatar [channel]="result" [internalHref]="getInternalChannelUrl(result)" [href]="getExternalChannelUrl(result)"></my-actor-avatar>
 
       <div class="video-channel-info">
-        <a *ngIf="!isExternalChannelUrl()" [routerLink]="getChannelUrl(result)" class="video-channel-names">
+        <a *ngIf="!isExternalChannelUrl()" [routerLink]="getInternalChannelUrl(result)" class="video-channel-names">
           <ng-container *ngTemplateOutlet="aContent"></ng-container>
         </a>
 
-        <a *ngIf="isExternalChannelUrl()" [href]="getChannelUrl(result)" target="_blank" class="video-channel-names">
+        <a *ngIf="isExternalChannelUrl()" [href]="getExternalChannelUrl(result)" target="_blank" class="video-channel-names">
           <ng-container *ngTemplateOutlet="aContent"></ng-container>
         </a>
 
index 91c8272d7f1b1e9a664feffc5de8790dfd9304e4..a8002ba886ba0d6ab14f207bfbb9ebc1def22550 100644 (file)
@@ -5,7 +5,7 @@
   $image-size: min(130px, $video-img-width);
   $margin-size: ($video-img-width - $image-size) / 2; // So we have the same width than the video miniature
 
-  @include channel-avatar($image-size);
+  @include actor-avatar-size($image-size);
 
   margin: 0 $margin-size 0 $margin-size;
 }
   max-width: 800px;
 }
 
-.video-channel {
-  img {
-    @include build-channel-img-size($video-thumbnail-width);
-  }
+.video-channel my-actor-avatar {
+  @include build-channel-img-size($video-thumbnail-width);
 }
 
 .video-channel-info {
     grid-template-columns: auto 1fr;
     grid-template-rows: auto auto;
 
-    .link-avatar {
+    my-actor-avatar {
+      @include build-channel-img-size($video-thumbnail-medium-width);
+
       grid-column: 1;
       grid-row: 1 / -1;
     }
-
-    img {
-      @include build-channel-img-size($video-thumbnail-medium-width);
-    }
   }
 
   .video-channel-info {
 }
 
 @include on-mobile-main-col {
-  .video-channel img {
+  .video-channel my-actor-avatar {
     @include build-channel-img-size($video-thumbnail-small-width);
   }
 }
index 2be952e161b9306b9afafaa1ba651dc3e4123d85..ecede19a3da6e13d392403c5db79ab44e853a608 100644 (file)
@@ -132,10 +132,6 @@ export class SearchComponent implements OnInit, OnDestroy {
     return 'internal'
   }
 
-  isExternalChannelUrl () {
-    return this.getVideoLinkType() === 'external'
-  }
-
   search () {
     forkJoin([
       this.getVideosObs(),
@@ -200,17 +196,33 @@ export class SearchComponent implements OnInit, OnDestroy {
     this.results = this.results.filter(r => !this.isVideo(r) || r.id !== video.id)
   }
 
-  getChannelUrl (channel: VideoChannel) {
+  isExternalChannelUrl () {
+    return this.getVideoLinkType() === 'external'
+  }
+
+  getExternalChannelUrl (channel: VideoChannel) {
     // Same algorithm than videos
     if (this.getVideoLinkType() === 'external') {
       return channel.url
     }
 
-    if (this.getVideoLinkType() === 'internal') {
+    // lazy-load or internal
+    return undefined
+  }
+
+  getInternalChannelUrl (channel: VideoChannel) {
+    const linkType = this.getVideoLinkType()
+
+    if (linkType === 'internal') {
       return [ '/video-channels', channel.nameWithHost ]
     }
 
-    return [ '/search/lazy-load-channel', { url: channel.url } ]
+    if (linkType === 'lazy-load') {
+      return [ '/search/lazy-load-channel', { url: channel.url } ]
+    }
+
+    // external
+    return undefined
   }
 
   hideActions () {
index e85ae07d00d3b0ab876e7d9bc8d7171dbf8f7514..390833abc041162077d0e93795e1807f78435fe5 100644 (file)
@@ -1,4 +1,5 @@
 import { NgModule } from '@angular/core'
+import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-actor-image.module'
 import { SharedFormModule } from '@app/shared/shared-forms'
 import { SharedMainModule } from '@app/shared/shared-main'
 import { SharedSearchModule } from '@app/shared/shared-search'
@@ -18,6 +19,7 @@ import { VideoLazyLoadResolver } from './video-lazy-load.resolver'
     SharedMainModule,
     SharedSearchModule,
     SharedFormModule,
+    SharedActorImageModule,
     SharedUserSubscriptionModule,
     SharedVideoMiniatureModule
   ],
index 9308d5bb66c3b52b80ea430f262a668b8cbac028..b4d81fe39d595ce7078a8933579d878ca620c8eb 100644 (file)
@@ -6,16 +6,16 @@
   <div class="channel-info">
 
     <ng-template #buttonsTemplate>
-        <a *ngIf="isManageable()" [routerLink]="[ '/my-library/video-channels/update', videoChannel.nameWithHost ]" class="peertube-button-link orange-button" i18n>
-          Manage channel
-        </a>
+      <a *ngIf="isManageable()" [routerLink]="[ '/my-library/video-channels/update', videoChannel.nameWithHost ]" class="peertube-button-link orange-button" i18n>
+        Manage channel
+      </a>
 
-        <my-subscribe-button *ngIf="!isManageable()" #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button>
+      <my-subscribe-button *ngIf="!isManageable()" #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button>
 
-        <button *ngIf="videoChannel.support" (click)="showSupportModal()" class="support-button peertube-button orange-button-inverted">
-          <my-global-icon iconName="support" aria-hidden="true"></my-global-icon>
-          <span class="icon-text" i18n>Support</span>
-        </button>
+      <button *ngIf="videoChannel.support" (click)="showSupportModal()" class="support-button peertube-button orange-button-inverted">
+        <my-global-icon iconName="support" aria-hidden="true"></my-global-icon>
+        <span class="icon-text" i18n>Support</span>
+      </button>
     </ng-template>
 
     <ng-template #ownerTemplate>
@@ -23,7 +23,7 @@
         <div class="section-label" i18n>OWNER ACCOUNT</div>
 
         <div class="avatar-row">
-          <my-account-avatar [account]="videoChannel.ownerAccount" [internalHref]="getAccountUrl()" size="120"></my-account-avatar>
+          <my-actor-avatar class="account-avatar" [account]="videoChannel.ownerAccount" [internalHref]="getAccountUrl()"></my-actor-avatar>
 
           <div class="actor-info">
             <h4>
@@ -49,7 +49,7 @@
     </ng-template>
 
     <div class="channel-avatar-row">
-      <img class="channel-avatar" [src]="videoChannel.avatarUrl" alt="Avatar" />
+      <my-actor-avatar class="main-avatar" [channel]="videoChannel"></my-actor-avatar>
 
       <div>
         <div class="section-label" i18n>VIDEO CHANNEL</div>
index e946707efa1aba97f418d7db01d7e49bf2c5f005..360a993421a87389089df7c39861aec8dac9b2c2 100644 (file)
     display: flex;
     margin-bottom: 15px;
 
-    img {
-      @include avatar(48px);
+    .account-avatar {
+      @include actor-avatar-size(48px);
     }
 
     .actor-info {
         margin-top: -5px;
       }
 
-      img {
-        @include channel-avatar(64px);
+      .account-avatar {
+        @include actor-avatar-size(64px);
 
         margin: -30px 0 0 15px;
       }
index 2e387f40177c3da287531f32afccc247cba2255b..35c39cc2ec12723637f1fc3924b2e0acbcedd8c3 100644 (file)
@@ -10,7 +10,7 @@ import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-
 import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component'
 import { VideoChannelsRoutingModule } from './video-channels-routing.module'
 import { VideoChannelsComponent } from './video-channels.component'
-import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
 
 @NgModule({
   imports: [
@@ -23,7 +23,7 @@ import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/share
     SharedUserSubscriptionModule,
     SharedGlobalIconModule,
     SharedSupportModal,
-    SharedAccountAvatarModule
+    SharedActorImageModule
   ],
 
   declarations: [
index 7bd9b7c90b44ccba0098e18d30c634e498c603f8..42adfed8d73203263644d1ddc02e16ff4cf01ff9 100644 (file)
@@ -1,6 +1,6 @@
 <form novalidate [formGroup]="form" (ngSubmit)="formValidated()">
   <div class="avatar-and-textarea">
-    <my-account-avatar [account]="user?.account" size="25"></my-account-avatar>
+    <my-actor-avatar [account]="user?.account" size="25"></my-actor-avatar>
 
     <div class="form-group">
       <textarea i18n-placeholder placeholder="Add comment..." myAutoResize
index 1aa9255c290aa34a854b532d4c0ff9c29f2a10e8..54e61eac458df591058d46ee7fdbf536210f39e4 100644 (file)
@@ -13,8 +13,7 @@ form {
   display: flex;
   margin-bottom: 10px;
 
-  my-account-avatar {
-    vertical-align: top;
+  my-actor-avatar {
     margin-right: 10px;
   }
 
index 2b0739261dfa738e5419b651719f939b727a84cf..d7ba40ef6cce432d8d3953c4d7e0e429860839b9 100644 (file)
@@ -1,12 +1,10 @@
-<div *ngIf="isCommentDisplayed()" class="root-comment">
+<div *ngIf="isCommentDisplayed()" class="root-comment" [ngClass]="{ 'is-child': isChild() }">
   <div class="left">
-    <my-account-avatar *ngIf="!comment.isDeleted" [href]="comment.account.url" [account]="comment.account"></my-account-avatar>
+    <my-actor-avatar *ngIf="!comment.isDeleted" [href]="comment.account.url" [account]="comment.account"></my-actor-avatar>
     <div class="vertical-border"></div>
   </div>
 
   <div class="right" [ngClass]="{ 'mb-3': firstInThread }">
-    <span *ngIf="comment.isDeleted" class="comment-avatar"></span>
-
     <div class="comment">
       <ng-container *ngIf="!comment.isDeleted">
         <div *ngIf="highlightedComment === true" class="highlighted-comment" i18n>Highlighted comment</div>
@@ -68,7 +66,7 @@
         [textValue]="redraftValue"
       ></my-video-comment-add>
 
-      <div *ngIf="commentTree" class="children">
+      <div *ngIf="commentTree">
         <div *ngFor="let commentChild of commentTree.children">
           <my-video-comment
             [comment]="commentChild.comment"
index cf33a5b0e5ab90c1965d432b60e64ab2cb4c5e91..f0dcc08b8caf1246893c6618875f55a999b3cf72 100644 (file)
   }
 }
 
+my-actor-avatar {
+  @include actor-avatar-size(36px);
+}
+
 .comment {
   flex-grow: 1;
   // Fix word-wrap with flex
@@ -148,10 +152,10 @@ my-video-comment-add {
   }
 }
 
-.children {
+.is-child {
   // Reduce avatars size for replies
-  .comment-avatar {
-    @include avatar(25px);
+  my-actor-avatar {
+    @include actor-avatar-size(25px);
   }
 
   .left {
index dd3db0c6573e29b92c8cc49425d6ba554443f1db..fd379e80e7523e6c9e34de2614e27a88fdb2984a 100644 (file)
@@ -138,6 +138,10 @@ export class VideoCommentComponent implements OnInit, OnChanges {
       (this.commentTree?.hasDisplayedChildren) // Or this is a reply that have other replies
   }
 
+  isChild () {
+    return this.parentComments.length !== 0
+  }
+
   private getUserIfNeeded (account: Account) {
     if (!account.userId) return
     if (!this.authService.isLoggedIn()) return
index a02373f2d6086a34081dfcdcba07520c5ce2488b..b8b5d7843c2c974433c5d7a7a22c7e41ee1598f3 100644 (file)
@@ -4,11 +4,11 @@
       <img [src]="video.videoChannelAvatarUrl" i18n-alt alt="Channel avatar" class="channel-avatar" />
     </a>
 
-    <my-account-avatar [account]="video.account" [title]="accountLinkTitle" [internalHref]="[ '/accounts', video.byAccount ]"></my-account-avatar>
+    <my-actor-avatar [account]="video.account" [title]="accountLinkTitle" [internalHref]="[ '/accounts', video.byAccount ]"></my-actor-avatar>
 </ng-container>
 
   <ng-container *ngIf="!isChannelAvatarNull() && genericChannel">
-    <my-account-avatar [account]="video.account" [title]="accountLinkTitle" [internalHref]="[ '/accounts', video.byAccount ]"></my-account-avatar>
+    <my-actor-avatar [account]="video.account" [title]="accountLinkTitle" [internalHref]="[ '/accounts', video.byAccount ]"></my-actor-avatar>
 
     <a [routerLink]="[ '/video-channels', video.byVideoChannel ]" [title]="channelLinkTitle">
       <img [src]="video.videoChannelAvatarUrl" i18n-alt alt="Channel avatar" class="channel-avatar" />
@@ -16,6 +16,6 @@
   </ng-container>
 
   <ng-container *ngIf="isChannelAvatarNull()">
-    <my-account-avatar [account]="video.account" [title]="accountLinkTitle" [internalHref]="[ '/accounts', video.byAccount ]"></my-account-avatar>
+    <my-actor-avatar [account]="video.account" [title]="accountLinkTitle" [internalHref]="[ '/accounts', video.byAccount ]"></my-actor-avatar>
   </ng-container>
 </div>
index cf6afd852f78eb897ad94637def6f96202737dd5..62ce7be2ddf542001df235f26e7bfaa8c16ceb0c 100644 (file)
@@ -20,7 +20,7 @@ import { TimestampRouteTransformerDirective } from './timestamp-route-transforme
 import { VideoWatchPlaylistComponent } from './video-watch-playlist.component'
 import { VideoWatchRoutingModule } from './video-watch-routing.module'
 import { VideoWatchComponent } from './video-watch.component'
-import { SharedAccountAvatarModule } from '../../shared/shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from '../../shared/shared-actor-image/shared-actor-image.module'
 import { VideoAvatarChannelComponent } from './video-avatar-channel.component'
 
 @NgModule({
@@ -39,7 +39,7 @@ import { VideoAvatarChannelComponent } from './video-avatar-channel.component'
     SharedShareModal,
     SharedVideoModule,
     SharedSupportModal,
-    SharedAccountAvatarModule
+    SharedActorImageModule
   ],
 
   declarations: [
index 639a96c43e158974a4b00eefd67524210ba923aa..e21bffb6c5ad9ebe04b696f13c14ce1043ec08c0 100644 (file)
@@ -33,7 +33,7 @@
       <div class="section channel videos" *ngFor="let object of overview.channels">
         <div class="section-title">
           <a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]">
-            <img [src]="buildVideoChannelAvatarUrl(object)" alt="Avatar" />
+            <my-actor-avatar [channel]="buildVideoChannel(object)"></my-actor-avatar>
 
             <h2 class="section-title">{{ object.channel.displayName }}</h2>
           </a>
index ec73c628c2e7abdcbca79d324f62525332304a1a..251eae456764c978bb4678c6c466856821444aef 100644 (file)
         width: fit-content;
         align-items: center;
 
-        img {
-          @include channel-avatar(28px);
+        my-actor-avatar {
+          @include actor-avatar-size(28px);
 
+          font-size: initial;
           margin-right: 8px;
         }
       }
index b3be1d7b5e5247988bc51009736edf0f95a95d26..14532ca1ed02b8a4fcbfb29d953c955c38a19921 100644 (file)
@@ -45,8 +45,8 @@ export class VideoOverviewComponent implements OnInit {
     return object.videos[0].byVideoChannel
   }
 
-  buildVideoChannelAvatarUrl (object: { videos: Video[] }) {
-    return object.videos[0].videoChannelAvatarUrl
+  buildVideoChannel (object: { videos: Video[] }) {
+    return object.videos[0].channel
   }
 
   buildVideos (videos: Video[]) {
index 61d012d63375bd558d19c389c39af6289b80b3be..8a35015d654c969fd8b6410ab0333995e3e89530 100644 (file)
@@ -1,4 +1,5 @@
 import { NgModule } from '@angular/core'
+import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-actor-image.module'
 import { SharedFormModule } from '@app/shared/shared-forms'
 import { SharedGlobalIconModule } from '@app/shared/shared-icons'
 import { SharedMainModule } from '@app/shared/shared-main'
@@ -21,7 +22,8 @@ import { VideosComponent } from './videos.component'
     SharedFormModule,
     SharedVideoMiniatureModule,
     SharedUserSubscriptionModule,
-    SharedGlobalIconModule
+    SharedGlobalIconModule,
+    SharedActorImageModule
   ],
 
   declarations: [
index 41c59cc86da5d9f01f7db5fd969a10a3b6df8e0b..3cec6d7392ef459b4ccb50d66946c2ffc2120843 100644 (file)
@@ -24,7 +24,7 @@ import { SharedGlobalIconModule } from './shared/shared-icons'
 import { SharedInstanceModule } from './shared/shared-instance'
 import { SharedMainModule } from './shared/shared-main'
 import { SharedUserInterfaceSettingsModule } from './shared/shared-user-settings'
-import { SharedAccountAvatarModule } from './shared/shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from './shared/shared-actor-image/shared-actor-image.module'
 
 registerLocaleData(localeOc, 'oc')
 
@@ -60,7 +60,7 @@ registerLocaleData(localeOc, 'oc')
     SharedUserInterfaceSettingsModule,
     SharedGlobalIconModule,
     SharedInstanceModule,
-    SharedAccountAvatarModule,
+    SharedActorImageModule,
 
     MetaModule.forRoot({
       provide: MetaLoader,
index df5c7971d3d1d33eee4bd864f2f00aba52975891..2e07deca2a4cfc50ced9fafa5353277b6673a9a3 100644 (file)
@@ -5,7 +5,7 @@
         <div>
           <div class="logged-in-more" ngbDropdown #dropdown="ngbDropdown" placement="bottom-left" [container]="dropdownContainer" (openChange)="onDropdownOpenChange($event)" autoClose="outside">
             <div ngbDropdownToggle>
-              <my-account-avatar [account]="user.account" size="34"></my-account-avatar>
+              <my-actor-avatar [account]="user.account" size="34"></my-actor-avatar>
               <div class="logged-in-info">
                 <div class="logged-in-display-name">{{ user.account?.displayName }}</div>
 
index 00d1a1f69c2c9d9c3c60d649bf432b301f5c15ce..c84a08b1d562e78dd37e229e80c05f143f8bb77e 100644 (file)
@@ -177,7 +177,7 @@ my-notification {
   }
 }
 
-my-account-avatar {
+my-actor-avatar {
   margin-right: 10px;
 }
 
index 658d425374e17764ddbeda929eef9c0e1cbf2248..f2eaeb32f5e18e2c4386f49d546019124d25bc60 100644 (file)
@@ -10,7 +10,7 @@
         <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }"
           class="chip"
         >
-          <my-account-avatar [account]="abuse.reporterAccount"></my-account-avatar>
+          <my-actor-avatar [account]="abuse.reporterAccount"></my-actor-avatar>
           <div>
             <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span>
           </div>
@@ -30,7 +30,7 @@
         <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.flaggedAccount.displayName + '&quot;' }"
           class="chip"
         >
-          <my-account-avatar [account]="abuse.flaggedAccount"></my-account-avatar>
+          <my-actor-avatar [account]="abuse.flaggedAccount"></my-actor-avatar>
           <div>
             <span class="text-muted">{{ abuse.flaggedAccount ? abuse.flaggedAccount.nameWithHost : '' }}</span>
           </div>
index 29b51f09c20a4ba130d2fd0fbbd2326772f55e8d..b41bc75d479ecd1946263f46a12a55945225ac9d 100644 (file)
@@ -65,7 +65,7 @@
       <td *ngIf="isAdminView()">
         <a *ngIf="abuse.reporterAccount" [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
           <div class="chip two-lines">
-            <my-account-avatar [account]="abuse.reporterAccount"></my-account-avatar>
+            <my-actor-avatar [account]="abuse.reporterAccount"></my-actor-avatar>
             <div>
               {{ abuse.reporterAccount.displayName }}
               <span>{{ abuse.reporterAccount.nameWithHost }}</span>
index 19b6d456d145ea4007c69547e568918a8c5aa51c..8f3830a17211d9ff6cbe651106fed0b0cae94452 100644 (file)
@@ -10,7 +10,7 @@ import { AbuseDetailsComponent } from './abuse-details.component'
 import { AbuseListTableComponent } from './abuse-list-table.component'
 import { AbuseMessageModalComponent } from './abuse-message-modal.component'
 import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
-import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
 
 @NgModule({
   imports: [
@@ -21,7 +21,7 @@ import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-accou
     SharedModerationModule,
     SharedGlobalIconModule,
     SharedVideoCommentModule,
-    SharedAccountAvatarModule
+    SharedActorImageModule
   ],
 
   declarations: [
diff --git a/client/src/app/shared/shared-account-avatar/account-avatar.component.html b/client/src/app/shared/shared-account-avatar/account-avatar.component.html
deleted file mode 100644 (file)
index 083dacc..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<ng-template #img>
-  <img *ngIf="avatarUrl || !initial" [class]="class" [src]="avatarUrl || defaultAvatarUrl" i18n-alt alt="Account avatar" />
-  <div *ngIf="!avatarUrl && initial" [class]="class">
-    <span>{{ initial }}</span>
-  </div>
-</ng-template>
-
-<a *ngIf="account && href" [href]="href" target="_blank" rel="noopener noreferrer" [title]="title">
-  <ng-template *ngTemplateOutlet="img"></ng-template>
-</a>
-
-<a *ngIf="account && internalHref" [routerLink]="internalHref" [title]="title">
-  <ng-template *ngTemplateOutlet="img"></ng-template>
-</a>
-
-<ng-container *ngIf="!account || (!href && !internalHref)">
-  <ng-template *ngTemplateOutlet="img"></ng-template>
-</ng-container>
diff --git a/client/src/app/shared/shared-account-avatar/account-avatar.component.ts b/client/src/app/shared/shared-account-avatar/account-avatar.component.ts
deleted file mode 100644 (file)
index 76b6965..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Component, Input } from '@angular/core'
-import { Account } from '../shared-main/account/account.model'
-
-@Component({
-  selector: 'my-account-avatar',
-  styleUrls: [ './account-avatar.component.scss' ],
-  templateUrl: './account-avatar.component.html'
-})
-export class AccountAvatarComponent {
-  @Input() account: {
-    name: string
-    avatar?: { url?: string, path: string }
-    url: string
-  }
-  @Input() size: '25' | '32' | '34' | '36' | '40' | '120' = '36'
-
-  // Use an external link
-  @Input() href: string
-  // Use routerLink
-  @Input() internalHref: string | string[]
-
-  @Input() set title (value) {
-    this._title = value
-  }
-
-  defaultAvatarUrl = Account.GET_DEFAULT_AVATAR_URL()
-
-  private _title: string
-
-  get title () {
-    return this._title || $localize`${this.account.name} (account page)`
-  }
-
-  get class () {
-    return `avatar avatar-${this.size}` + (this.avatarUrl ? '' : ` initial ${this.getColorTheme()}`)
-  }
-
-  get avatarUrl () {
-    return Account.GET_ACTOR_AVATAR_URL(this.account)
-  }
-
-  get initial () {
-    return this.account?.name.slice(0, 1)
-  }
-
-  private getColorTheme () {
-    const themes = {
-      abc: 'blue',
-      def: 'green',
-      ghi: 'purple',
-      jkl: 'gray',
-      mno: 'yellow',
-      pqr: 'orange',
-      stv: 'red',
-      wxyz: 'dark-blue'
-    }
-
-    const theme = Object.keys(themes).find(chars => chars.includes(this.initial))
-
-    return themes[theme]
-  }
-}
diff --git a/client/src/app/shared/shared-account-avatar/index.ts b/client/src/app/shared/shared-account-avatar/index.ts
deleted file mode 100644 (file)
index 40c742b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './account-avatar.component'
-export * from './shared-account-avatar.module'
\ No newline at end of file
index 0829263f45e2e4af6a7d35288b92579755d5c4cf..e9c5fadcf5f8b485c46494dd0b9c413a49b4a0d3 100644 (file)
@@ -1,6 +1,6 @@
 <div class="actor" *ngIf="actor">
   <div class="d-flex">
-    <img [ngClass]="{ channel: isChannel() }" [src]="preview || actor.avatarUrl" alt="Avatar" />
+    <my-actor-avatar [channel]="getChannel()" [account]="getAccount()" [previewImage]="preview" size="100"></my-actor-avatar>
 
     <div class="actor-img-edit-container">
 
@@ -34,6 +34,7 @@
     <span for="avatarfile" i18n>Upload a new avatar</span>
     <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
   </div>
+
   <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()">
     <my-global-icon iconName="delete"></my-global-icon>
     <span i18n>Remove avatar</span>
index 8b0172315b6aa8a064d7cce5ecf68aa62c73456e..08e80c3b4f13a3cf037ebc54b14736c4156d7f28 100644 (file)
@@ -4,16 +4,8 @@
 .actor {
   display: flex;
 
-  img {
+  my-actor-avatar {
     margin-right: 15px;
-
-    &:not(.channel) {
-      @include avatar(100px);
-    }
-
-    &.channel {
-      @include channel-avatar(100px);
-    }
   }
 
   .actor-info {
index d0d269489e0aa551608c5a95c93009f025c363df..840946690c8b80c3eddb1dda9a3987c31c8527d1 100644 (file)
@@ -80,4 +80,16 @@ export class ActorAvatarEditComponent implements OnInit {
   isChannel () {
     return !!(this.actor as VideoChannel).ownerAccount
   }
+
+  getChannel (): VideoChannel {
+    if (this.isChannel()) return this.actor as VideoChannel
+
+    return undefined
+  }
+
+  getAccount (): Account {
+    if (this.isChannel()) return undefined
+
+    return this.actor as Account
+  }
 }
index c9c6472b9278009fccd2b0c567d08632fef9689a..f6a397d5cb299ecaeb067c2cc2674aad0c0534db 100644 (file)
@@ -1,6 +1,7 @@
 
 import { CommonModule } from '@angular/common'
 import { NgModule } from '@angular/core'
+import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
 import { SharedGlobalIconModule } from '../shared-icons'
 import { SharedMainModule } from '../shared-main'
 import { ActorAvatarEditComponent } from './actor-avatar-edit.component'
@@ -11,6 +12,7 @@ import { ActorBannerEditComponent } from './actor-banner-edit.component'
     CommonModule,
 
     SharedMainModule,
+    SharedActorImageModule,
     SharedGlobalIconModule
   ],
 
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.html b/client/src/app/shared/shared-actor-image/actor-avatar.component.html
new file mode 100644 (file)
index 0000000..607f28e
--- /dev/null
@@ -0,0 +1,19 @@
+<ng-template #img>
+  <img *ngIf="previewImage || avatarUrl || !initial" [class]="class" [src]="previewImage || avatarUrl || defaultAvatarUrl" [alt]="alt" />
+
+  <div *ngIf="!avatarUrl && initial" [class]="class">
+    <span>{{ initial }}</span>
+  </div>
+</ng-template>
+
+<a *ngIf="hasActor() && href" [href]="href" target="_blank" rel="noopener noreferrer" [title]="title">
+  <ng-template *ngTemplateOutlet="img"></ng-template>
+</a>
+
+<a *ngIf="hasActor() && internalHref" [routerLink]="internalHref" [title]="title">
+  <ng-template *ngTemplateOutlet="img"></ng-template>
+</a>
+
+<ng-container *ngIf="!hasActor() || (!href && !internalHref)">
+  <ng-template *ngTemplateOutlet="img"></ng-template>
+</ng-container>
similarity index 53%
rename from client/src/app/shared/shared-account-avatar/account-avatar.component.scss
rename to client/src/app/shared/shared-actor-image/actor-avatar.component.scss
index 75a4cbf866dbbbf39eabc242ee38b426afaaf0e9..f014dec48a69e4ce8b9b051eb0da1079a172c04b 100644 (file)
@@ -1,32 +1,58 @@
 @import '_variables';
 @import '_mixins';
 
+.avatar {
+  --avatarSize: 100%;
+  --initialFontSize: 22px;
+
+  width: var(--avatarSize);
+  height: var(--avatarSize);
+  min-width: var(--avatarSize);
+  min-height: var(--avatarSize);
+
+  &.account {
+    object-fit: cover;
+    border-radius: 50%;
+  }
+
+  &.channel {
+    border-radius: 5px;
+  }
+}
+
+.avatar-18 {
+  --avatarSize: 18px;
+  --initialFontSize: 13px;
+}
+
 .avatar-25 {
-  @include avatar(25px);
+  --avatarSize: 25px;
 }
 
 .avatar-32 {
 @include avatar(32px);
--avatarSize: 32px;
 }
 
 .avatar-34 {
 @include avatar(34px);
--avatarSize: 34px;
 }
 
 .avatar-36 {
 @include avatar(36px);
--avatarSize: 36px;
 }
 
 .avatar-40 {
 @include avatar(40px);
--avatarSize: 40px;
 }
 
-.avatar-120 {
-  @include avatar(120px);
+.avatar-100 {
+  --avatarSize: 100px;
+  --initialFontSize: 40px;
+ }
 
-  &.initial {
   font-size: 46px;
-  }
+.avatar-120 {
--avatarSize: 120px;
+ --initialFontSize: 46px;
 }
 
 a:hover {
@@ -39,8 +65,7 @@ a:hover {
   display: flex;
   align-items: center;
   justify-content: center;
-  font-size: 22px;
-  border-radius: 50%;
+  font-size: var(--initialFontSize);
 
   &.blue {
     background-color: #009FD4;
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
new file mode 100644 (file)
index 0000000..6bb3b65
--- /dev/null
@@ -0,0 +1,110 @@
+import { Component, Input } from '@angular/core'
+import { SafeResourceUrl } from '@angular/platform-browser'
+import { VideoChannel } from '../shared-main'
+import { Account } from '../shared-main/account/account.model'
+
+type ActorInput = {
+  name: string
+  avatar?: { url?: string, path: string }
+  url: string
+}
+
+@Component({
+  selector: 'my-actor-avatar',
+  styleUrls: [ './actor-avatar.component.scss' ],
+  templateUrl: './actor-avatar.component.html'
+})
+export class ActorAvatarComponent {
+  @Input() account: ActorInput
+  @Input() channel: ActorInput
+
+  @Input() previewImage: SafeResourceUrl
+
+  @Input() size: '18' | '25' | '32' | '34' | '36' | '40' | '100' | '120'
+
+  // Use an external link
+  @Input() href: string
+  // Use routerLink
+  @Input() internalHref: string | any[]
+
+  @Input() set title (value) {
+    this._title = value
+  }
+
+  private _title: string
+
+  get title () {
+    if (this._title) return this._title
+    if (this.account) return $localize`${this.account.name} (account page)`
+    if (this.channel) return $localize`${this.channel.name} (channel page)`
+
+    return ''
+  }
+
+  get alt () {
+    if (this.account) return $localize`Account avatar`
+    if (this.channel) return $localize`Channel avatar`
+
+    return ''
+  }
+
+  get class () {
+    const base = [ 'avatar' ]
+
+    if (this.size) base.push(`avatar-${this.size}`)
+
+    if (this.account) base.push('account')
+    else base.push('channel')
+
+    if (this.initial) {
+      base.push('initial')
+      base.push(this.getColorTheme())
+    }
+
+    return base
+  }
+
+  get defaultAvatarUrl () {
+    if (this.account) Account.GET_DEFAULT_AVATAR_URL()
+    if (this.channel) return VideoChannel.GET_DEFAULT_AVATAR_URL()
+
+    return ''
+  }
+
+  get avatarUrl () {
+    if (this.account) return Account.GET_ACTOR_AVATAR_URL(this.account)
+    if (this.channel) return VideoChannel.GET_ACTOR_AVATAR_URL(this.account)
+
+    return ''
+  }
+
+  get initial () {
+    const name = this.account?.name
+    if (!name) return ''
+
+    return name.slice(0, 1)
+  }
+
+  hasActor () {
+    return !!this.account || !!this.channel
+  }
+
+  private getColorTheme () {
+    // Keep consistency with CSS
+    const themes = {
+      abc: 'blue',
+      def: 'green',
+      ghi: 'purple',
+      jkl: 'gray',
+      mno: 'yellow',
+      pqr: 'orange',
+      stv: 'red',
+      wxyz: 'dark-blue'
+    }
+
+    const theme = Object.keys(themes)
+                        .find(chars => chars.includes(this.initial))
+
+    return themes[theme]
+  }
+}
diff --git a/client/src/app/shared/shared-actor-image/index.ts b/client/src/app/shared/shared-actor-image/index.ts
new file mode 100644 (file)
index 0000000..18a9038
--- /dev/null
@@ -0,0 +1 @@
+export * from './shared-actor-image.module'
similarity index 65%
rename from client/src/app/shared/shared-account-avatar/shared-account-avatar.module.ts
rename to client/src/app/shared/shared-actor-image/shared-actor-image.module.ts
index 17b27589f729aa49acf3d95e4d6cd12aa793c1d8..8ea4bb2bf4122883bfc25489e08a3ce8069a551a 100644 (file)
@@ -2,7 +2,7 @@
 import { NgModule } from '@angular/core'
 import { SharedGlobalIconModule } from '../shared-icons'
 import { SharedMainModule } from '../shared-main/shared-main.module'
-import { AccountAvatarComponent } from './account-avatar.component'
+import { ActorAvatarComponent } from './actor-avatar.component'
 
 @NgModule({
   imports: [
@@ -11,13 +11,13 @@ import { AccountAvatarComponent } from './account-avatar.component'
   ],
 
   declarations: [
-    AccountAvatarComponent
+    ActorAvatarComponent
   ],
 
   exports: [
-    AccountAvatarComponent
+    ActorAvatarComponent
   ],
 
   providers: [ ]
 })
-export class SharedAccountAvatarModule { }
+export class SharedActorImageModule { }
index 88a4811da0681ec350e267bd1a789f3dcc3ccd1e..ed5791794d1a05e1c88a6207e2bc3b5c0def2fc5 100644 (file)
@@ -258,10 +258,10 @@ export class UserNotification implements UserNotificationServer {
   }
 
   private setAccountAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) {
-    actor.avatarUrl = Account.GET_ACTOR_AVATAR_URL(actor)
+    actor.avatarUrl = Account.GET_ACTOR_AVATAR_URL(actor) || Account.GET_DEFAULT_AVATAR_URL()
   }
 
   private setVideoChannelAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) {
-    actor.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(actor)
+    actor.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(actor) || VideoChannel.GET_DEFAULT_AVATAR_URL()
   }
 }
index 5166bd559d7f19fedadbb889960b18652a8fc615..fa9c55ec9dafc92a1707c9a1c081e540c630451d 100644 (file)
   }
 
   .avatar {
-    @include avatar(30px);
-
+    width: 30px;
+    height: 30px;
+    min-width: 30px;
+    min-height: 30px;
+    border-radius: 5px;
     margin-right: 10px;
   }
 
index 1ba3fcc0e651bac6de069ade7b712443c0b872fc..548725e04d4e3004d9b95b3620c70a207a1e4fd2 100644 (file)
@@ -25,7 +25,7 @@ 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()
+    return Actor.GET_ACTOR_AVATAR_URL(actor)
   }
 
   static GET_ACTOR_BANNER_URL (channel: ServerVideoChannel) {
index 3f2f5555945d7f6c6a3187ec9e617c22d232390c..e914a7c3caf2dafe8bc5b54bcdd922dfa3fd01a2 100644 (file)
@@ -38,7 +38,7 @@
       <td>
         <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
           <div class="chip two-lines">
-            <my-account-avatar [account]="accountBlock.blockedAccount"></my-account-avatar>
+            <my-actor-avatar [account]="accountBlock.blockedAccount"></my-actor-avatar>
             <div>
               {{ accountBlock.blockedAccount.displayName }}
               <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span>
index c7e201792c419fc461857b09556561eedefc4ff3..95213e2bd969376308dadbe607d80906fc8ff53f 100644 (file)
@@ -13,7 +13,7 @@ import { UserBanModalComponent } from './user-ban-modal.component'
 import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
 import { VideoBlockComponent } from './video-block.component'
 import { VideoBlockService } from './video-block.service'
-import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
 
 @NgModule({
   imports: [
@@ -21,7 +21,7 @@ import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-accou
     SharedFormModule,
     SharedGlobalIconModule,
     SharedVideoCommentModule,
-    SharedAccountAvatarModule
+    SharedActorImageModule
   ],
 
   declarations: [
index 32cfdfd685125245c04ff9b13fad7d6d914663cd..03be6d2ffd396621decaae0ac3d78b51c8cb58be 100644 (file)
@@ -13,7 +13,7 @@ import { VideoDownloadComponent } from './video-download.component'
 import { VideoMiniatureComponent } from './video-miniature.component'
 import { VideosSelectionComponent } from './videos-selection.component'
 import { VideoListHeaderComponent } from './video-list-header.component'
-import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-account-avatar.module'
+import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
 
 @NgModule({
   imports: [
@@ -25,7 +25,7 @@ import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-accou
     SharedGlobalIconModule,
     SharedVideoLiveModule,
     SharedVideoModule,
-    SharedAccountAvatarModule
+    SharedActorImageModule
   ],
 
   declarations: [
index bc19127aaf5759b0f421df0cde49bb4c052406d2..7c4fcc4911230b3aee122285c3e73a39ebd56c04 100644 (file)
   <div class="video-bottom">
     <div class="video-miniature-information">
       <div class="d-flex video-miniature-meta">
-        <a *ngIf="displayOptions.avatar && displayOwnerVideoChannel()" class="channel-avatar" [routerLink]="[ '/video-channels', video.byVideoChannel ]" [title]="channelLinkTitle">
-          <img [src]="getAvatarUrl()" alt="" />
-        </a>
+        <my-actor-avatar
+          *ngIf="displayOptions.avatar && displayOwnerVideoChannel()" [title]="channelLinkTitle"
+          [channel]="video.channel" size="40" [internalHref]="[ '/video-channels', video.byVideoChannel ]"
+        ></my-actor-avatar>
 
-        <my-account-avatar
+        <my-actor-avatar
           *ngIf="displayOptions.avatar && displayOwnerAccount()" [title]="channelLinkTitle"
-          [account]="video.account" size="40" [internalHref]="'/video-channels/' + video.byVideoChannel"
-        ></my-account-avatar>
+          [account]="video.account" size="40" [internalHref]="[ '/video-channels', video.byVideoChannel ]"
+        ></my-actor-avatar>
 
         <div class="w-100 d-flex flex-column">
           <a *ngIf="!videoHref" tabindex="-1" class="video-miniature-name"
index f6f2925f03c035d621273b4fffd42a7373ee2492..c142e2e93b4777173e09eb5c7317051fa612f400 100644 (file)
@@ -12,15 +12,10 @@ $more-button-width: 40px;
   width: calc(100% - #{$more-button-width});
 }
 
-my-account-avatar,
-.channel-avatar {
+my-actor-avatar {
   margin: 10px 10px 0 0;
 }
 
-.channel-avatar img{
-  @include channel-avatar(40px);
-}
-
 .video-miniature-created-at-views {
   font-size: 13px;
 }
index a4798ce1d68ff58ee9111b2df9f2d847c20074e2..ccfd73ecd011f26f7a389b18d1dc0b6cf9a1e3df 100644 (file)
@@ -25,8 +25,8 @@
   grid-column: 1;
   margin-bottom: 30px;
 
-  .channel-avatar {
-    @include channel-avatar(120px);
+  .main-avatar {
+    @include actor-avatar-size(120px);
   }
 
   > div {
       font-size: 22px;
     }
 
-    .channel-avatar {
-      @include channel-avatar(80px);
-    }
-
-    .account-avatar {
-      @include avatar(120px);
+    .main-avatar {
+      @include actor-avatar-size(80px);
     }
   }
 }
index 2c8ad1d5736b83e8806e968f2677f6906a662f16..dc6ab8076c4d87adabf69a30ef6b46f9498c8c95 100644 (file)
   }
 }
 
-@mixin avatar ($size) {
-  object-fit: cover;
-  border-radius: 50%;
-  width: $size;
-  height: $size;
-  min-width: $size;
-  min-height: $size;
-}
-
-@mixin channel-avatar ($size) {
+@mixin actor-avatar-size ($size) {
+  display: inline-block;
   width: $size;
   height: $size;
   min-width: $size;
   min-height: $size;
-  border-radius: 5px;
 }
 
 @mixin chevron ($size, $border-width) {
   margin-bottom: 10px;
 }
 
-@mixin actor-owner {
-  @include disable-default-a-behaviour;
-
-  font-size: 13px;
-  margin-top: 4px;
-  color: pvar(--mainForegroundColor);
-
-  span:hover {
-    opacity: 0.8;
-  }
-
-  img {
-    @include avatar(18px);
-
-    margin-left: 7px;
-    position: relative;
-    top: -2px;
-  }
-}
-
 @mixin create-button {
   @include peertube-button-link;
   @include orange-button;