<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">
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 {
<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>
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: [
SharedModerationModule,
SharedVideoMiniatureModule,
SharedGlobalIconModule,
- SharedAccountAvatarModule
+ SharedActorImageModule
],
declarations: [
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 {
SharedGlobalIconModule,
SharedAbuseListModule,
SharedVideoCommentModule,
- SharedAccountAvatarModule,
+ SharedActorImageModule,
SharedActorImageEditModule,
TableModule,
<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>
<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>
<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>
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'
SharedGlobalIconModule,
SharedAbuseListModule,
SharedShareModal,
- SharedAccountAvatarModule,
+ SharedActorImageModule,
SharedActorImageEditModule
],
<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">
padding-bottom: 0;
- img {
- @include channel-avatar(80px);
+ my-actor-avatar {
+ @include actor-avatar-size(80px);
margin-right: 10px;
}
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: [
SharedMainModule,
SharedFormModule,
SharedGlobalIconModule,
- SharedActorImageEditModule
+ SharedActorImageEditModule,
+ SharedActorImageModule
],
declarations: [
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: [
SharedAbuseListModule,
SharedShareModal,
SharedVideoLiveModule,
- SharedAccountAvatarModule
+ SharedActorImageModule
],
declarations: [
<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>
<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">
<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>
.video-channel {
@include row-blocks;
- img {
- @include channel-avatar(80px);
+ > my-actor-avatar {
+ @include actor-avatar-size(80px);
margin-right: 10px;
}
}
.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 {
<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>
$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);
}
}
return 'internal'
}
- isExternalChannelUrl () {
- return this.getVideoLinkType() === 'external'
- }
-
search () {
forkJoin([
this.getVideosObs(),
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 () {
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'
SharedMainModule,
SharedSearchModule,
SharedFormModule,
+ SharedActorImageModule,
SharedUserSubscriptionModule,
SharedVideoMiniatureModule
],
<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>
<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>
</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>
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;
}
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: [
SharedUserSubscriptionModule,
SharedGlobalIconModule,
SharedSupportModal,
- SharedAccountAvatarModule
+ SharedActorImageModule
],
declarations: [
<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
display: flex;
margin-bottom: 10px;
- my-account-avatar {
- vertical-align: top;
+ my-actor-avatar {
margin-right: 10px;
}
-<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>
[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"
}
}
+my-actor-avatar {
+ @include actor-avatar-size(36px);
+}
+
.comment {
flex-grow: 1;
// Fix word-wrap with flex
}
}
-.children {
+.is-child {
// Reduce avatars size for replies
- .comment-avatar {
- @include avatar(25px);
+ my-actor-avatar {
+ @include actor-avatar-size(25px);
}
.left {
(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
<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" />
</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>
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({
SharedShareModal,
SharedVideoModule,
SharedSupportModal,
- SharedAccountAvatarModule
+ SharedActorImageModule
],
declarations: [
<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>
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;
}
}
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[]) {
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'
SharedFormModule,
SharedVideoMiniatureModule,
SharedUserSubscriptionModule,
- SharedGlobalIconModule
+ SharedGlobalIconModule,
+ SharedActorImageModule
],
declarations: [
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')
SharedUserInterfaceSettingsModule,
SharedGlobalIconModule,
SharedInstanceModule,
- SharedAccountAvatarModule,
+ SharedActorImageModule,
MetaModule.forRoot({
provide: MetaLoader,
<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>
}
}
-my-account-avatar {
+my-actor-avatar {
margin-right: 10px;
}
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }"
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>
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }"
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>
<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>
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: [
SharedModerationModule,
SharedGlobalIconModule,
SharedVideoCommentModule,
- SharedAccountAvatarModule
+ SharedActorImageModule
],
declarations: [
+++ /dev/null
-<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>
+++ /dev/null
-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]
- }
-}
+++ /dev/null
-export * from './account-avatar.component'
-export * from './shared-account-avatar.module'
\ No newline at end of file
<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">
<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>
.actor {
display: flex;
- img {
+ my-actor-avatar {
margin-right: 15px;
-
- &:not(.channel) {
- @include avatar(100px);
- }
-
- &.channel {
- @include channel-avatar(100px);
- }
}
.actor-info {
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
+ }
}
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'
CommonModule,
SharedMainModule,
+ SharedActorImageModule,
SharedGlobalIconModule
],
--- /dev/null
+<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>
@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 {
display: flex;
align-items: center;
justify-content: center;
- font-size: 22px;
- border-radius: 50%;
+ font-size: var(--initialFontSize);
&.blue {
background-color: #009FD4;
--- /dev/null
+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]
+ }
+}
--- /dev/null
+export * from './shared-actor-image.module'
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: [
],
declarations: [
- AccountAvatarComponent
+ ActorAvatarComponent
],
exports: [
- AccountAvatarComponent
+ ActorAvatarComponent
],
providers: [ ]
})
-export class SharedAccountAvatarModule { }
+export class SharedActorImageModule { }
}
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()
}
}
}
.avatar {
- @include avatar(30px);
-
+ width: 30px;
+ height: 30px;
+ min-width: 30px;
+ min-height: 30px;
+ border-radius: 5px;
margin-right: 10px;
}
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) {
<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>
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: [
SharedFormModule,
SharedGlobalIconModule,
SharedVideoCommentModule,
- SharedAccountAvatarModule
+ SharedActorImageModule
],
declarations: [
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: [
SharedGlobalIconModule,
SharedVideoLiveModule,
SharedVideoModule,
- SharedAccountAvatarModule
+ SharedActorImageModule
],
declarations: [
<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"
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;
}
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);
}
}
}
}
}
-@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;