Use a dropdown label to highlight actor host

This commit is contained in:
Chocobozzz 2025-01-13 11:26:05 +01:00
parent 038c410308
commit 6aa1ac1db4
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
10 changed files with 222 additions and 77 deletions

View File

@ -1,6 +1,9 @@
.inherit-parent { .inherit-parent-style {
color: inherit; color: inherit;
text-decoration: inherit; text-decoration: inherit;
}
.inherit-parent-dimension {
position: inherit; position: inherit;
width: inherit; width: inherit;
height: inherit; height: inherit;

View File

@ -1,5 +1,5 @@
import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common' import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common'
import { Component, Input, OnInit } from '@angular/core' import { booleanAttribute, Component, Input, OnInit } from '@angular/core'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
import { GlobalIconComponent, GlobalIconName } from '@app/shared/shared-icons/global-icon.component' import { GlobalIconComponent, GlobalIconName } from '@app/shared/shared-icons/global-icon.component'
@ -19,7 +19,8 @@ export class LinkComponent implements OnInit {
@Input() title?: string @Input() title?: string
@Input() className?: string @Input() className?: string
@Input() inheritParentCSS = false @Input({ transform: booleanAttribute }) inheritParentStyle = false
@Input({ transform: booleanAttribute }) inheritParentDimension = false
@Input() tabindex: string | number @Input() tabindex: string | number
@ -32,8 +33,12 @@ export class LinkComponent implements OnInit {
ngOnInit () { ngOnInit () {
this.builtClasses = this.className || '' this.builtClasses = this.className || ''
if (!this.builtClasses || this.inheritParentCSS) { if (this.inheritParentStyle) {
this.builtClasses += ' inherit-parent' this.builtClasses += ' inherit-parent-style'
}
if (this.inheritParentDimension) {
this.builtClasses += ' inherit-parent-dimension'
} }
} }
} }

View File

@ -11,76 +11,80 @@
} }
</my-video-thumbnail> </my-video-thumbnail>
<div class="video-bottom"> <div class="video-info">
<div class="video-info"> <div *ngIf="displayOptions.avatar || displayOptions.by" class="owner min-width-0">
<div *ngIf="displayOptions.avatar || displayOptions.by" class="owner min-width-0"> @if (displayOptions.avatar) {
@if (displayOptions.avatar) {
@if (displayOwnerVideoChannel()) { @if (displayOwnerVideoChannel()) {
<my-actor-avatar <my-actor-avatar
[title]="channelLinkTitle" actorType="channel" [size]="actorImageSize" [title]="channelLinkTitle" actorType="channel" [size]="actorImageSize"
[actor]="video.channel" [internalHref]="[ '/c', video.byVideoChannel ]" [actor]="video.channel" [internalHref]="[ '/c', video.byVideoChannel ]"
></my-actor-avatar> ></my-actor-avatar>
} @else if (displayOwnerAccount()) { } @else if (displayOwnerAccount()) {
<my-actor-avatar <my-actor-avatar
[title]="channelLinkTitle" actorType="account" [size]="actorImageSize" [title]="channelLinkTitle" actorType="account" [size]="actorImageSize"
[actor]="video.account" [internalHref]="[ '/c', video.byVideoChannel ]" [actor]="video.account" [internalHref]="[ '/c', video.byVideoChannel ]"
></my-actor-avatar> ></my-actor-avatar>
}
} }
}
<a *ngIf="displayOptions.by" class="owner-label ellipsis" [routerLink]="[ '/c', video.byVideoChannel ]"> <div class="owner-container" *ngIf="displayOptions.by">
<a class="owner-label ellipsis" [routerLink]="[ '/c', video.byVideoChannel ]">
@if (displayOwnerAccount()) { @if (displayOwnerAccount()) {
{{ authorAccount }} {{ authorAccount }}
} @else if (displayOwnerVideoChannel()) { } @else if (displayOwnerVideoChannel()) {
{{ authorChannel }} {{ authorChannel }}
} }
</a> </a>
</div>
<my-actor-host *ngIf="!video.isLocal" [host]="video.account.host"></my-actor-host>
</div>
</div>
<div class="video-name-container">
<my-link <my-link
[internalLink]="videoRouterLink" [href]="videoHref" [target]="videoTarget" [inheritParentCSS]="true" [internalLink]="videoRouterLink" [href]="videoHref" [target]="videoTarget" inheritParentStyle="true"
[ariaLabel]="getAriaLabel()" [ariaLabel]="getAriaLabel()"
[title]="video.name" class="video-name" className="ellipsis-multiline-2" [ngClass]="{ 'blur-filter': isVideoBlur }" [title]="video.name" class="video-name" className="ellipsis-multiline-2" [ngClass]="{ 'blur-filter': isVideoBlur }"
> >
{{ video.name }} {{ video.name }}
</my-link> </my-link>
<div class="date-and-views"> <div class="video-actions">
<my-date-toggle *ngIf="displayOptions.date" [date]="video.publishedAt"></my-date-toggle> <my-video-actions-dropdown
*ngIf="showActions" [video]="video" [displayOptions]="videoActionsDisplayOptions" placement="bottom-left bottom-right left auto"
<span class="views"> (videoRemoved)="onVideoRemoved()" (videoBlocked)="onVideoBlocked()" (videoUnblocked)="onVideoUnblocked()" (videoAccountMuted)="onVideoAccountMuted()"
<ng-container *ngIf="displayOptions.date && displayOptions.views"></ng-container> ></my-video-actions-dropdown>
<my-video-views-counter *ngIf="displayOptions.views" [isLive]="video.isLive" [viewers]="video.viewers" [views]="video.views"></my-video-views-counter>
</span>
</div>
<div class="video-info-privacy fw-semibold">
<ng-container *ngIf="displayOptions.privacyText">{{ video.privacy.label }}</ng-container>
<ng-container *ngIf="displayOptions.privacyText && displayOptions.state && getStateLabel(video)"> - </ng-container>
<ng-container *ngIf="displayOptions.state">{{ getStateLabel(video) }}</ng-container>
</div>
<div *ngIf="containedInPlaylists" class="badges">
<a *ngFor="let playlist of containedInPlaylists" class="pt-badge badge-secondary" [routerLink]="['/w/p/', playlist.playlistShortUUID]">
{{ playlist.playlistDisplayName }}
</a>
<span *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="pt-badge badge-danger video-info-blocked">
<span class="fw-semibold" i18n>Blocked</span>
<span *ngIf="video.blacklistedReason"> - {{ video.blacklistedReason }}</span>
</span>
<span i18n *ngIf="displayOptions.nsfw && video.nsfw" class="pt-badge badge-danger video-info-nsfw">Sensitive</span>
</div> </div>
</div> </div>
<div class="video-actions"> <div class="date-and-views">
<my-video-actions-dropdown <my-date-toggle *ngIf="displayOptions.date" [date]="video.publishedAt"></my-date-toggle>
*ngIf="showActions" [video]="video" [displayOptions]="videoActionsDisplayOptions" placement="bottom-left bottom-right left auto"
(videoRemoved)="onVideoRemoved()" (videoBlocked)="onVideoBlocked()" (videoUnblocked)="onVideoUnblocked()" (videoAccountMuted)="onVideoAccountMuted()" <span class="views">
></my-video-actions-dropdown> <ng-container *ngIf="displayOptions.date && displayOptions.views"></ng-container>
<my-video-views-counter *ngIf="displayOptions.views" [isLive]="video.isLive" [viewers]="video.viewers" [views]="video.views"></my-video-views-counter>
</span>
</div>
<div class="video-info-privacy fw-semibold">
<ng-container *ngIf="displayOptions.privacyText">{{ video.privacy.label }}</ng-container>
<ng-container *ngIf="displayOptions.privacyText && displayOptions.state && getStateLabel(video)"> - </ng-container>
<ng-container *ngIf="displayOptions.state">{{ getStateLabel(video) }}</ng-container>
</div>
<div *ngIf="containedInPlaylists" class="badges">
<a *ngFor="let playlist of containedInPlaylists" class="pt-badge badge-secondary" [routerLink]="['/w/p/', playlist.playlistShortUUID]">
{{ playlist.playlistDisplayName }}
</a>
<span *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="pt-badge badge-danger video-info-blocked">
<span class="fw-semibold" i18n>Blocked</span>
<span *ngIf="video.blacklistedReason"> - {{ video.blacklistedReason }}</span>
</span>
<span i18n *ngIf="displayOptions.nsfw && video.nsfw" class="pt-badge badge-danger video-info-nsfw">Sensitive</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -10,6 +10,11 @@ $more-button-width: 40px;
--co-fs-big: #{$miniature-fs-big}; --co-fs-big: #{$miniature-fs-big};
} }
.video-name-container {
display: flex;
min-width: 1px;
}
.video-name { .video-name {
font-size: var(--co-fs-big); font-size: var(--co-fs-big);
@ -36,18 +41,30 @@ $more-button-width: 40px;
color: pvar(--fg-200); color: pvar(--fg-200);
} }
.owner-container {
display: flex;
min-width: 1px;
}
my-actor-host {
max-width: 50%;
}
.owner-label { .owner-label {
display: block;
color: pvar(--fg-300); color: pvar(--fg-300);
@include disable-default-a-behaviour; @include disable-default-a-behaviour;
@include ellipsis;
&:hover { &:hover {
color: pvar(--fg); color: pvar(--fg);
} }
} }
my-actor-host {
@include margin-left(0.25rem);
}
.video-actions { .video-actions {
width: $more-button-width; width: $more-button-width;
height: 30px; height: 30px;
@ -77,11 +94,6 @@ $more-button-width: 40px;
} }
} }
.video-bottom {
display: flex;
min-width: 1px;
}
// Grid mode // Grid mode
// Takes all the width on mobile // Takes all the width on mobile
.video-miniature:not(.display-as-row) { .video-miniature:not(.display-as-row) {
@ -90,7 +102,7 @@ $more-button-width: 40px;
padding-bottom: 1rem; padding-bottom: 1rem;
width: 100%; width: 100%;
.video-info { .video-name {
width: calc(100% - #{$more-button-width}); width: calc(100% - #{$more-button-width});
} }
@ -100,11 +112,7 @@ $more-button-width: 40px;
@include block-ratio($selector: '::ng-deep .video-thumbnail'); @include block-ratio($selector: '::ng-deep .video-thumbnail');
} }
.video-bottom { &.has-avatar .video-info {
width: 100%;
}
&.has-avatar .video-bottom {
@include padding-left(0.75rem); @include padding-left(0.75rem);
} }
@ -126,7 +134,7 @@ $more-button-width: 40px;
border-radius: 5px; border-radius: 5px;
} }
& + .owner-label { & + .owner-container {
// Keep 34px in sync with the component // Keep 34px in sync with the component
@include margin-left(calc(0.5rem + 34px)); @include margin-left(calc(0.5rem + 34px));
} }

View File

@ -26,6 +26,7 @@ import { VideoThumbnailComponent } from '../shared-thumbnail/video-thumbnail.com
import { VideoPlaylistService } from '../shared-video-playlist/video-playlist.service' import { VideoPlaylistService } from '../shared-video-playlist/video-playlist.service'
import { VideoViewsCounterComponent } from '../shared-video/video-views-counter.component' import { VideoViewsCounterComponent } from '../shared-video/video-views-counter.component'
import { VideoActionsDisplayType, VideoActionsDropdownComponent } from './video-actions-dropdown.component' import { VideoActionsDisplayType, VideoActionsDropdownComponent } from './video-actions-dropdown.component'
import { ActorHostComponent } from '../standalone-actor/actor-host.component'
export type MiniatureDisplayOptions = { export type MiniatureDisplayOptions = {
date?: boolean date?: boolean
@ -56,7 +57,8 @@ export type MiniatureDisplayOptions = {
VideoViewsCounterComponent, VideoViewsCounterComponent,
RouterLink, RouterLink,
NgFor, NgFor,
VideoActionsDropdownComponent VideoActionsDropdownComponent,
ActorHostComponent
] ]
}) })
export class VideoMiniatureComponent implements OnInit { export class VideoMiniatureComponent implements OnInit {
@ -134,13 +136,13 @@ export class VideoMiniatureComponent implements OnInit {
get authorAccount () { get authorAccount () {
return this.serverConfig.client.videos.miniature.preferAuthorDisplayName return this.serverConfig.client.videos.miniature.preferAuthorDisplayName
? this.video.account.displayName ? this.video.account.displayName
: this.video.byAccount : this.video.account.name
} }
get authorChannel () { get authorChannel () {
return this.serverConfig.client.videos.miniature.preferAuthorDisplayName return this.serverConfig.client.videos.miniature.preferAuthorDisplayName
? this.video.channel.displayName ? this.video.channel.displayName
: this.video.byVideoChannel : this.video.channel.name
} }
get isVideoBlur () { get isVideoBlur () {

View File

@ -1,6 +1,6 @@
<div class="miniature" [ngClass]="{ 'no-videos': playlist.videosLength === 0, 'to-manage': toManage, 'display-as-row': displayAsRow }"> <div class="miniature" [ngClass]="{ 'no-videos': playlist.videosLength === 0, 'to-manage': toManage, 'display-as-row': displayAsRow }">
<my-link <my-link
[internalLink]="routerLink" [href]="playlistHref" [target]="playlistTarget" [inheritParentCSS]="true" [internalLink]="routerLink" [href]="playlistHref" [target]="playlistTarget" inheritParentStyle="true" inheritParentDimension="true"
[title]="playlist.description" class="miniature-thumbnail" tabindex="-1" [title]="playlist.description" class="miniature-thumbnail" tabindex="-1"
> >
<img alt="" [attr.aria-labelledby]="playlist.displayName" [attr.src]="playlist.thumbnailUrl" /> <img alt="" [attr.aria-labelledby]="playlist.displayName" [attr.src]="playlist.thumbnailUrl" />
@ -16,7 +16,7 @@
<div class="miniature-info"> <div class="miniature-info">
<my-link <my-link
[internalLink]="routerLink" [href]="playlistHref" [target]="playlistTarget" [inheritParentCSS]="true" [internalLink]="routerLink" [href]="playlistHref" [target]="playlistTarget" inheritParentStyle="true" inheritParentDimension="true"
[title]="playlist.description" class="miniature-name" className="ellipsis-multiline-2" [title]="playlist.description" class="miniature-name" className="ellipsis-multiline-2"
> >
{{ playlist.displayName }} {{ playlist.displayName }}

View File

@ -0,0 +1,28 @@
<div
ngbDropdown autoClose="outside" placement="bottom-end auto" container="body"
#dropdown="ngbDropdown"
>
<button class="host button-unstyle ellipsis" ngbDropdownToggle i18n-title title="Get more information" type="button">
<svg class="left" width="8" height="19" viewBox="0 0 8 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 19V0L7.45757 8.85586C7.77102 9.22809 7.77102 9.77191 7.45757 10.1441L0 19Z"/>
</svg>
{{ host }}
<svg class="right" width="9" height="19" viewBox="0 0 9 19" xmlns="http://www.w3.org/2000/svg">
<path d="m 8.0900763,0 h -7.08835 C 0.19446632,0 -0.28009268,0.907181 0.18029632,1.5703 L 5.4485063,9.15841 c 0.24316,0.35023 0.23751,0.81611 -0.01406,1.16039 L 0.25209632,17.41 c -0.482855,0.6607 -0.01097,1.59 0.80737998,1.59 h 7.0306 z"/>
</svg>
</button>
<div ngbDropdownMenu class="content">
<strong class="question mb-3" i18n>What is it?</strong>
<div class="answer mb-3">
This video comes from <a [href]="'https://' + host" target="_blank" rel="noopener noreferrer">{{ host }} <my-global-icon iconName="external-link"></my-global-icon></a> platform, to which we have subscribed.
</div>
<a i18n class="mt-3 mb-3 d-block peertube-button-link secondary-button" routerLink="/about/follows">Show all our subscriptions</a>
<my-button i18n class="d-block" theme="primary" ptRouterLink="/search" [ptQueryParams]="{ 'host': host }">More {{ host }} content</my-button>
</div>
</div>

View File

@ -0,0 +1,77 @@
@use '_variables' as *;
@use '_mixins' as *;
.content {
background-color: pvar(--bg-secondary-500);
border-radius: 14px;
max-width: 350px;
border: 0;
@include padding(1.5rem);
}
.question {
color: pvar(--fg-350);
font-weight: $font-bold;
@include font-size(20px);
}
.secondary-button {
border-color: pvar(--secondary-icon-color) !important;
}
.answer {
color: pvar(--fg-300);
a {
color: pvar(--fg-400);
font-weight: $font-bold;
text-decoration: underline;
my-global-icon {
color: pvar(--fg-400);
top: 0;
}
}
}
.host {
position: relative;
max-width: 100%;
&:hover {
opacity: 0.8;
}
svg {
fill: pvar(--bg);
z-index: 2;
width: auto;
height: calc(100% + 2px);;
top: -1px;
position: absolute;
&.left {
left: -2px;
}
&.right {
right: -2px;
}
}
}
.host {
color: pvar(--fg-450);
font-weight: $font-bold;
background: linear-gradient(to right, pvar(--bg-secondary-400), pvar(--bg-secondary-500));
position: relative;
padding: 4px 12px;
@include font-size(10px);
&::after {
display: none;
}
}

View File

@ -0,0 +1,17 @@
import { CommonModule } from '@angular/common'
import { Component, Input } from '@angular/core'
import { RouterLink } from '@angular/router'
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { GlobalIconComponent } from '../shared-icons/global-icon.component'
import { ButtonComponent } from '../shared-main/buttons/button.component'
@Component({
selector: 'my-actor-host',
templateUrl: 'actor-host.component.html',
styleUrls: [ 'actor-host.component.scss' ],
standalone: true,
imports: [ CommonModule, NgbDropdownModule, GlobalIconComponent, ButtonComponent, RouterLink ]
})
export class ActorHostComponent {
@Input({ required: true }) host: string
}

View File

@ -241,10 +241,11 @@ code {
a { a {
color: inherit; color: inherit;
}
&:hover { my-link:hover,
opacity: 0.8; a:hover {
} opacity: 0.8;
} }
.main-col { .main-col {