Refactor actor avatar display

pull/4027/head
Chocobozzz 2021-04-28 11:49:34 +02:00
parent ec489ce2f7
commit 746018f6b8
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
62 changed files with 346 additions and 278 deletions

View 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">

View File

@ -27,14 +27,12 @@
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 {

View 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>

View 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: [

View 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,

View 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>

View 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>

View File

@ -106,7 +106,7 @@
<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>

View 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
],

View 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">

View 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;
}

View 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: [

View 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: [

View 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>

View 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>

View 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;
margin-top: 0;
font-size: 13px;
color: pvar(--mainForegroundColor);
span:hover {
opacity: 0.8;
}
my-actor-avatar {
margin-left: 7px;
display: inline-block;
vertical-align: top;
}
}
.video-subscriptions-header {

View File

@ -33,20 +33,15 @@
<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>

View 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;
}
@ -53,10 +53,8 @@
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 {
@ -92,14 +90,12 @@
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 {
@ -115,7 +111,7 @@
}
@include on-mobile-main-col {
.video-channel img {
.video-channel my-actor-avatar {
@include build-channel-img-size($video-thumbnail-small-width);
}
}

View 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 () {

View 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
],

View 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>

View File

@ -107,8 +107,8 @@
display: flex;
margin-bottom: 15px;
img {
@include avatar(48px);
.account-avatar {
@include actor-avatar-size(48px);
}
.actor-info {
@ -289,8 +289,8 @@
margin-top: -5px;
}
img {
@include channel-avatar(64px);
.account-avatar {
@include actor-avatar-size(64px);
margin: -30px 0 0 15px;
}

View 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: [

View 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

View File

@ -13,8 +13,7 @@ form {
display: flex;
margin-bottom: 10px;
my-account-avatar {
vertical-align: top;
my-actor-avatar {
margin-right: 10px;
}

View 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"

View File

@ -24,6 +24,10 @@
}
}
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 {

View 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

View 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>

View 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: [

View 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>

View File

@ -49,9 +49,10 @@
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;
}
}

View 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[]) {

View 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: [

View 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,

View 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>

View File

@ -177,7 +177,7 @@ my-notification {
}
}
my-account-avatar {
my-actor-avatar {
margin-right: 10px;
}

View 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>

View 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>

View 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: [

View File

@ -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>

View File

@ -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]
}
}

View File

@ -1,2 +0,0 @@
export * from './account-avatar.component'
export * from './shared-account-avatar.module'

View 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>

View 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 {

View 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
}
}

View 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
],

View File

@ -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>

View 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;

View File

@ -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]
}
}

View File

@ -0,0 +1 @@
export * from './shared-actor-image.module'

View 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 { }

View 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()
}
}

View File

@ -29,8 +29,11 @@
}
.avatar {
@include avatar(30px);
width: 30px;
height: 30px;
min-width: 30px;
min-height: 30px;
border-radius: 5px;
margin-right: 10px;
}

View 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) {

View 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>

View 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: [

View 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: [

View File

@ -10,14 +10,15 @@
<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"

View 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;
}

View 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 {
@ -77,12 +77,8 @@
font-size: 22px;
}
.channel-avatar {
@include channel-avatar(80px);
}
.account-avatar {
@include avatar(120px);
.main-avatar {
@include actor-avatar-size(80px);
}
}
}

View File

@ -539,23 +539,14 @@
}
}
@mixin avatar ($size) {
object-fit: cover;
border-radius: 50%;
@mixin actor-avatar-size ($size) {
display: inline-block;
width: $size;
height: $size;
min-width: $size;
min-height: $size;
}
@mixin channel-avatar ($size) {
width: $size;
height: $size;
min-width: $size;
min-height: $size;
border-radius: 5px;
}
@mixin chevron ($size, $border-width) {
border-style: solid;
border-width: $border-width $border-width 0 0;
@ -595,26 +586,6 @@
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;