Redesign video miniature

This commit is contained in:
Chocobozzz 2024-11-13 16:11:32 +01:00
parent 364783e303
commit c0b372d7a5
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
28 changed files with 297 additions and 268 deletions

View File

@ -77,7 +77,7 @@ export class MyAccountPage {
async countVideos (names: string[]) {
const elements = await $$('.video').filter(async e => {
const t = await e.$('.video-miniature-name').getText()
const t = await e.$('.video-name').getText()
return names.some(n => t.includes(n))
})
@ -140,7 +140,7 @@ export class MyAccountPage {
private async getVideoElement (name: string) {
const video = async () => {
const videos = await $$('.video').filter(async e => {
const t = await e.$('.video-miniature-name').getText()
const t = await e.$('.video-name').getText()
return t.includes(name)
})

View File

@ -66,25 +66,25 @@ export class VideoListPage {
}
async getVideosListName () {
const elems = await $$('.videos .video-miniature .video-miniature-name')
const elems = await $$('.videos .video-miniature .video-name')
const texts = await elems.map(e => e.getText())
return texts.map(t => t.trim())
}
videoExists (name: string) {
return $('.video-miniature-name=' + name).isDisplayed()
return $('.video-name=' + name).isDisplayed()
}
async videoIsBlurred (name: string) {
const filter = await $('.video-miniature-name=' + name).getCSSProperty('filter')
const filter = await $('.video-name=' + name).getCSSProperty('filter')
return filter.value !== 'none'
}
async clickOnVideo (videoName: string) {
const video = async () => {
const videos = await $$('.videos .video-miniature .video-miniature-name').filter(async e => {
const videos = await $$('.videos .video-miniature .video-name').filter(async e => {
const t = await e.getText()
return t === videoName
@ -106,7 +106,7 @@ export class VideoListPage {
async clickOnFirstVideo () {
const video = () => $('.videos .video-miniature .video-thumbnail')
const videoName = () => $('.videos .video-miniature .video-miniature-name')
const videoName = () => $('.videos .video-miniature .video-name')
await video().waitForClickable()
@ -119,7 +119,7 @@ export class VideoListPage {
}
private waitForList () {
return $('.videos .video-miniature .video-miniature-name').waitForDisplayed()
return $('.videos .video-miniature .video-name').waitForDisplayed()
}
private waitForTitle (title: string) {

View File

@ -64,7 +64,8 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
privacyText: true,
state: true,
blacklistInfo: true,
forceChannelInBy: true
forceChannelInBy: true,
nsfw: true
}
videoDropdownDisplayOptions: VideoActionsDisplayType = {
playlist: true,

View File

@ -83,11 +83,9 @@
.video-channel-display-name {
font-weight: $font-semibold;
font-size: $video-miniature-row-name-font-size;
}
.video-channel-name {
font-size: $video-miniature-row-info-font-size;
color: pvar(--greyForegroundColor);
}

View File

@ -60,7 +60,7 @@ export class SearchComponent implements OnInit, OnDestroy {
date: true,
views: true,
by: true,
avatar: false,
avatar: true,
privacyLabel: false,
privacyText: false,
state: false,

View File

@ -24,7 +24,7 @@ export class VideoUserSubscriptionsComponent implements DisableForReuseHook {
actions = [
{
routerLink: '/my-library/subscriptions',
label: $localize`Subscriptions`,
label: $localize`Manage`,
iconName: 'cog' as 'cog'
}
]

View File

@ -36,7 +36,7 @@
</div>
</div>
<ngx-loading-bar [includeSpinner]="false" [color]="'var(--mainColor)'"></ngx-loading-bar>
<ngx-loading-bar [includeSpinner]="false" color="var(--mainColor)"></ngx-loading-bar>
<my-confirm></my-confirm>

View File

@ -8,8 +8,6 @@
@include peertube-input-text(270px, 14px);
& {
padding-top: 6px;
padding-bottom: 6px;
padding-inline-start: 42px; // For the search icon
padding-inline-end: 20px; // For the search icon
}

View File

@ -1,54 +1,60 @@
<div class="menu-wrapper" [ngClass]="{ collapsed: collapsed }">
<div class="main-menu">
<div class="toggle-menu-container">
@if (collapsed) {
<button type="button" class="button-unstyle toggle-menu" i18n-title title="Display the lateral bar" (click)="toggleMenu()">
<my-global-icon class="transform-rotate-180" iconName="chevron-left"></my-global-icon>
</button>
} @else {
<button type="button" class="button-unstyle toggle-menu" i18n-title title="Hide the lateral bar" (click)="toggleMenu()">
<my-global-icon iconName="chevron-left"></my-global-icon>
</button>
}
</div>
<div class="menu-container" [ngClass]="{ collapsed: collapsed }">
<nav>
<ng-container *ngFor="let menuSection of menuSections" >
<ul class="ul-unstyle" [ngClass]="[ menuSection.key, 'menu-block' ]">
<li i18n>
<div class="block-title ellipsis" [ngClass]="{ 'visually-hidden': collapsed }">{{ menuSection.title }}</div>
<div class="main-menu-wrapper">
<div class="main-menu-scrollbar">
<ul class="ul-unstyle">
<li *ngFor="let link of menuSection.links">
@if (link.isPrimaryButton === true) {
<my-button class="d-block menu-button" theme="primary" [icon]="link.icon" [ariaLabel]="link.label" [ptRouterLink]="link.path">
@if (!collapsed) {
{{ link.label }}
<div class="main-menu">
<div class="toggle-menu-container">
@if (collapsed) {
<button type="button" class="button-unstyle toggle-menu" i18n-title title="Display the lateral bar" (click)="toggleMenu()">
<my-global-icon class="transform-rotate-180" iconName="chevron-left"></my-global-icon>
</button>
} @else {
<button type="button" class="button-unstyle toggle-menu" i18n-title title="Hide the lateral bar" (click)="toggleMenu()">
<my-global-icon iconName="chevron-left"></my-global-icon>
</button>
}
</div>
<nav>
<ng-container *ngFor="let menuSection of menuSections" >
<ul class="ul-unstyle" [ngClass]="[ menuSection.key, 'menu-block' ]">
<li i18n>
<div class="block-title ellipsis" [ngClass]="{ 'visually-hidden': collapsed }">{{ menuSection.title }}</div>
<ul class="ul-unstyle">
<li *ngFor="let link of menuSection.links">
@if (link.isPrimaryButton === true) {
<my-button class="d-block menu-button" theme="primary" [icon]="link.icon" [ariaLabel]="link.label" [ptRouterLink]="link.path">
@if (!collapsed) {
{{ link.label }}
}
</my-button>
} @else {
<a class="menu-link ellipsis" [routerLink]="link.path" routerLinkActive="active">
<my-global-icon *ngIf="link.icon" [iconName]="link.icon" [ngClass]="link.iconClass" aria-hidden="true"></my-global-icon>
<span [ngClass]="{ 'visually-hidden': collapsed }">{{ link.shortLabel }}</span>
</a>
}
</my-button>
} @else {
<a class="menu-link ellipsis" [routerLink]="link.path" routerLinkActive="active">
<my-global-icon *ngIf="link.icon" [iconName]="link.icon" [ngClass]="link.iconClass" aria-hidden="true"></my-global-icon>
<span [ngClass]="{ 'visually-hidden': collapsed }">{{ link.shortLabel }}</span>
</a>
}
</li>
</ul>
</li>
</ul>
</li>
</ul>
</ng-container>
</nav>
</ng-container>
</nav>
<div class="about">
<div [ngClass]="{ 'visually-hidden': collapsed }" class="block-title">About</div>
<div class="about">
<div [ngClass]="{ 'visually-hidden': collapsed }" class="block-title">About</div>
<div [ngClass]="{ 'visually-hidden': collapsed }" class="description">{{ shortDescription }}</div>
<div [ngClass]="{ 'visually-hidden': collapsed }" class="description">{{ shortDescription }}</div>
<my-button class="mt-2 d-block" theme="secondary" icon="help" i18n-ariaLabel aria-label="More info" i18n ptRouterLink="/about">
@if (!collapsed) {
More info
}
</my-button>
<my-button class="mt-2 d-block" theme="secondary" icon="help" i18n-ariaLabel aria-label="More info" i18n ptRouterLink="/about">
@if (!collapsed) {
More info
}
</my-button>
</div>
</div>
</div>
</div>

View File

@ -2,17 +2,14 @@
@use '_variables' as *;
@use '_mixins' as *;
.menu-wrapper {
.menu-container {
--menuXPadding: 1.5rem;
z-index: z(menu);
width: calc(#{$menu-width} - 2rem);
position: fixed;
height: calc(100vh - #{$header-height});
z-index: z(menu);
scrollbar-color: pvar(--actionButtonColor) pvar(--menuBackgroundColor);
width: calc(#{$menu-width} - 2rem);
@include margin-left(2rem);
&.collapsed {
@ -40,9 +37,13 @@
background-color: pvar(--secondary-400);
@include button-with-icon(20px, 0, -1px, 1px);
&:hover {
opacity: 0.8;
}
}
.menu-wrapper:not(.collapsed) .toggle-menu {
.menu-container:not(.collapsed) .toggle-menu {
position: absolute;
top: 20px;
@ -54,16 +55,19 @@
top: 0;
}
.main-menu {
position: relative;
padding-top: 1.5rem;
padding-bottom: 1.5rem;
border-radius: 14px;
background-color: pvar(--secondary-300);
.main-menu-wrapper {
// For the scrollbar
overflow: hidden;
width: 100%;
height: 100%;
max-height: calc(100% - 50px); // Space for links below the menu
}
.main-menu-scrollbar {
overflow-y: auto;
scrollbar-color: transparent transparent;
max-height: calc(100% - 50px); // Space for links below the menu
scrollbar-width: thin;
max-height: 100%;
&:focus,
&:hover {
@ -71,18 +75,31 @@
}
@media not all and (hover: hover) and (pointer: fine) {
border-radius: 0;
scrollbar-color: auto;
}
}
.collapsed .main-menu {
max-height: calc(100% - 10px);
.main-menu {
position: relative;
padding-top: 1.5rem;
padding-bottom: 1.5rem;
border-radius: 14px;
background-color: pvar(--secondary-300);
}
border-start-start-radius: 0;
border-end-start-radius: 0;
.collapsed {
.main-menu-wrapper {
max-height: 100%;
}
padding-top: 1rem;
padding-bottom: 1rem;
.main-menu {
border-start-start-radius: 0;
border-end-start-radius: 0;
padding-top: 1rem;
padding-bottom: 1rem;
}
}
.menu-link,
@ -152,11 +169,10 @@
}
}
.menu-wrapper.collapsed .menu-link {
.menu-container.collapsed .menu-link {
justify-content: center;
}
.menu-button {
margin-top: 0.5rem;
}

View File

@ -23,9 +23,7 @@
.label-overlay,
.duration-overlay,
.live-overlay {
border-radius: 3px;
font-size: 12px;
font-weight: $font-semibold;
line-height: 1.1;
z-index: z(miniature);
@ -33,6 +31,7 @@
}
.label-overlay {
border-radius: 3px;
position: absolute;
padding: 0 5px;
left: 5px;
@ -46,13 +45,14 @@
.duration-overlay,
.live-overlay {
position: absolute;
padding: 0 3px;
right: 5px;
bottom: 5px;
padding: 2px 8px;
right: 0;
bottom: 0;
border-top-left-radius: 4px;
}
.live-overlay {
font-weight: $font-semibold;
font-weight: $font-bold;
color: #fff;
&:not(.live-ended) {
@ -68,6 +68,8 @@
top: 5px;
opacity: 0;
font-weight: $font-semibold;
div:not(:first-child) {
margin-top: 2px;
}
@ -75,6 +77,7 @@
.watch-icon-overlay {
padding: 3px;
border-radius: 3px;
my-global-icon {
width: 22px;

View File

@ -4,71 +4,75 @@
[video]="video" [nsfw]="isVideoBlur" [videoRouterLink]="videoRouterLink" [videoHref]="videoHref" [videoTarget]="videoTarget"
[displayWatchLaterPlaylist]="isWatchLaterPlaylistDisplayed()" [inWatchLaterPlaylist]="inWatchLaterPlaylist" (watchLaterClick)="onWatchLaterClick($event)"
>
<ng-container ngProjectAs="label-warning" *ngIf="displayOptions.privacyLabel && isUnlistedVideo()" i18n>Unlisted</ng-container>
<ng-container ngProjectAs="label-danger" *ngIf="displayOptions.privacyLabel && isPrivateVideo()" i18n>Private</ng-container>
<ng-container ngProjectAs="label-danger" *ngIf="displayOptions.privacyLabel && isPasswordProtectedVideo()" i18n>Password protected</ng-container>
@if (displayOptions.privacyLabel) {
<ng-container ngProjectAs="label-warning" *ngIf="isUnlistedVideo()" i18n>Unlisted</ng-container>
<ng-container ngProjectAs="label-danger" *ngIf="isPrivateVideo()" i18n>Private</ng-container>
<ng-container ngProjectAs="label-danger" *ngIf="isPasswordProtectedVideo()" i18n>Password protected</ng-container>
}
</my-video-thumbnail>
<div class="video-bottom">
<div class="video-miniature-information">
<div class="d-flex video-miniature-meta">
<my-actor-avatar
*ngIf="displayOptions.avatar && displayOwnerVideoChannel() && !displayAsRow" [title]="channelLinkTitle"
[actor]="video.channel" actorType="channel" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]"
size="32"
></my-actor-avatar>
<div class="video-info">
<div *ngIf="displayOptions.avatar || displayOptions.by" class="owner">
@if (displayOptions.avatar) {
<my-actor-avatar
*ngIf="displayOptions.avatar && displayOwnerAccount() && !displayAsRow" [title]="channelLinkTitle"
[actor]="video.account" actorType="channel" [size]="actorImageSize" [internalHref]="[ '/c', video.byVideoChannel ]"
size="32"
></my-actor-avatar>
@if (displayOwnerVideoChannel()) {
<my-actor-avatar
[title]="channelLinkTitle" actorType="channel" [size]="actorImageSize"
[actor]="video.channel" [internalHref]="[ '/c', video.byVideoChannel ]"
></my-actor-avatar>
} @else if (displayOwnerAccount()) {
<my-actor-avatar
[title]="channelLinkTitle" actorType="account" [size]="actorImageSize"
[actor]="video.account" [internalHref]="[ '/c', video.byVideoChannel ]"
></my-actor-avatar>
}
}
<div class="w-100 d-flex flex-column min-width-0">
<my-link
[internalLink]="videoRouterLink" [href]="videoHref" [target]="videoTarget" [inheritParentCSS]="true"
[ariaLabel]="getAriaLabel()"
[title]="video.name" class="video-miniature-name" className="ellipsis-multiline-2" [ngClass]="{ 'blur-filter': isVideoBlur }"
>
{{ video.name }}
</my-link>
<span class="video-miniature-created-at-views">
<my-date-toggle *ngIf="displayOptions.date" [date]="video.publishedAt"></my-date-toggle>
<span class="views" [title]="video.getExactNumberOfViews()">
<ng-container *ngIf="displayOptions.date && displayOptions.views"></ng-container>
<my-video-views-counter *ngIf="displayOptions.views" [video]="video"></my-video-views-counter>
</span>
</span>
<a *ngIf="displayOptions.by" class="video-miniature-account" [routerLink]="[ '/c', video.byVideoChannel ]">
<ng-container *ngIf="displayOwnerAccount()">{{ authorAccount }}</ng-container>
<ng-container *ngIf="displayOwnerVideoChannel()">{{ authorChannel }}</ng-container>
</a>
<div class="video-info-privacy">
<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="contained-in-playlists">
<a *ngFor="let playlist of containedInPlaylists" class="pt-badge badge-secondary" [routerLink]="['/w/p/', playlist.playlistShortUUID]">
{{ playlist.playlistDisplayName }}
</a>
</div>
</div>
<a *ngIf="displayOptions.by" class="owner-label" [routerLink]="[ '/c', video.byVideoChannel ]">
@if (displayOwnerAccount()) {
{{ authorAccount }}
} @else if (displayOwnerVideoChannel()) {
{{ authorChannel }}
}
</a>
</div>
<div *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="video-info-blocked">
<span class="blocked-label" i18n>Blocked</span>
<span class="blocked-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
<my-link
[internalLink]="videoRouterLink" [href]="videoHref" [target]="videoTarget" [inheritParentCSS]="true"
[ariaLabel]="getAriaLabel()"
[title]="video.name" class="video-name" className="ellipsis-multiline-2" [ngClass]="{ 'blur-filter': isVideoBlur }"
>
{{ video.name }}
</my-link>
<div class="date-and-views">
<my-date-toggle *ngIf="displayOptions.date" [date]="video.publishedAt"></my-date-toggle>
<span class="views" [title]="video.getExactNumberOfViews()">
<ng-container *ngIf="displayOptions.date && displayOptions.views"></ng-container>
<my-video-views-counter *ngIf="displayOptions.views" [video]="video"></my-video-views-counter>
</span>
</div>
<div i18n *ngIf="displayOptions.nsfw && video.nsfw" class="video-info-nsfw">
Sensitive
<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>

View File

@ -5,29 +5,37 @@
$more-button-width: 40px;
.video-miniature {
font-size: 14px;
--fs-small: 0.75rem;
--fs-medium: 0.875rem;
--fs-big: 1rem;
}
.video-miniature-name {
.video-name {
font-size: var(--fs-big);
@include miniature-name;
}
.video-miniature-information {
.video-info {
width: calc(100% - #{$more-button-width});
display: grid;
}
my-actor-avatar {
@include margin(10px, 10px, 0, 0);
.owner {
display: flex;
position: relative;
font-size: var(--fs-medium);
}
.video-miniature-created-at-views {
font-size: 13px;
.date-and-views,
.video-info-privacy,
.badges {
font-size: var(--fs-small);
}
.video-miniature-account,
.video-miniature-channel {
.owner-label {
display: block;
font-size: 13px;
color: pvar(--greyForegroundColor);
@include disable-default-a-behaviour;
@ -38,24 +46,6 @@ my-actor-avatar {
}
}
.video-info-privacy,
.video-info-blocked .blocked-label,
.video-info-nsfw {
font-weight: $font-semibold;
}
.video-info-blocked {
color: #ff0000;
.blocked-reason::before {
content: ' - ';
}
}
.video-info-nsfw {
color: #ff0000;
}
.video-actions {
width: $more-button-width;
height: 30px;
@ -85,31 +75,59 @@ my-actor-avatar {
}
}
.video-bottom {
display: flex;
min-width: 1px;
}
// Grid mode
// Takes all the width on mobile
.video-miniature:not(.display-as-row) {
display: flex;
flex-direction: column;
padding-bottom: $video-miniature-margin-bottom;
padding-bottom: 1rem;
width: 100%;
my-video-thumbnail {
margin-bottom: 0.35rem;
@include block-ratio($selector: '::ng-deep .video-thumbnail');
}
.video-bottom {
display: flex;
width: 100%;
min-width: 1px;
}
.video-miniature-name {
margin-top: 10px;
margin-bottom: 5px;
&.has-avatar .video-bottom {
@include padding-left(0.75rem);
}
.video-miniature-created-at-views {
display: block;
my-actor-avatar {
border: 2px solid pvar(--bg);
position: absolute;
top: -20px;
background: pvar(--bg);
@include left(-2px);
&[actorType=account] {
// TODO: use SASS var in sync with thumbnail component
border-radius: 100%;
}
&[actorType=channel] {
// TODO: use SASS var in sync with thumbnail component
border-radius: 5px;
}
& + .owner-label {
// Keep 34px in sync with the component
@include margin-left(calc(0.5rem + 34px));
}
}
.date-and-views {
margin-top: 3px;
}
.video-actions {
@ -120,7 +138,7 @@ my-actor-avatar {
width: 100%;
margin-bottom: 25px;
.video-miniature-information {
.video-info {
margin: 0 10px;
width: 100%;
@ -144,22 +162,16 @@ my-actor-avatar {
}
.video-miniature.display-as-row {
--fs-small: 0.875rem;
--fs-medium: 1rem;
--fs-big: 1.25rem;
--rowThumbnailWidth: #{$video-thumbnail-width};
--rowThumbnailHeight: #{$video-thumbnail-height};
display: flex;
flex-direction: row;
.video-bottom {
display: flex;
min-width: 1px;
}
// We don't display avatar in row mode
.channel-avatar {
display: none;
}
my-video-thumbnail {
min-width: var(--rowThumbnailWidth);
max-width: var(--rowThumbnailWidth);
@ -168,14 +180,28 @@ my-actor-avatar {
@include margin-right(1rem);
}
.video-miniature-name {
font-size: $video-miniature-row-name-font-size;
.video-info {
grid-template-rows: auto;
height: min-content;
}
.video-miniature-created-at-views,
.video-miniature-account,
.video-miniature-channel {
font-size: $video-miniature-row-info-font-size;
.video-name {
grid-row: 1;
}
.date-and-views {
grid-row: 2;
}
.owner {
grid-row: 3;
align-items: center;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
my-actor-avatar + .owner-label {
@include margin-left(0.5rem);
}
.video-actions {
@ -183,12 +209,12 @@ my-actor-avatar {
}
}
.contained-in-playlists {
.badges {
display: flex;
flex-wrap: wrap;
column-gap: 5px;
row-gap: 5px;
gap: 5px;
font-size: 1rem;
margin-top: 0.25rem;
}
@include on-small-main-col {
@ -202,15 +228,5 @@ my-actor-avatar {
.video-miniature.display-as-row {
--rowThumbnailWidth: #{$video-thumbnail-small-width};
--rowThumbnailHeight: #{$video-thumbnail-small-height};
.video-miniature-name {
font-size: $video-miniature-row-info-font-size;
}
.video-miniature-created-at-views,
.video-miniature-account,
.video-miniature-channel {
font-size: $video-miniature-row-mobile-info-font-size;
}
}
}

View File

@ -1,4 +1,4 @@
import { switchMap } from 'rxjs/operators'
import { NgClass, NgFor, NgIf } from '@angular/common'
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
@ -9,22 +9,23 @@ import {
LOCALE_ID,
OnInit,
Output,
booleanAttribute,
numberAttribute
} from '@angular/core'
import { RouterLink } from '@angular/router'
import { AuthService, ScreenService, ServerService, User } from '@app/core'
import { HTMLServerConfig, VideoExistInPlaylist, VideoPlaylistType, VideoPrivacy, VideoState } from '@peertube/peertube-models'
import { switchMap } from 'rxjs/operators'
import { LinkType } from '../../../types/link.type'
import { VideoActionsDisplayType, VideoActionsDropdownComponent } from './video-actions-dropdown.component'
import { RouterLink } from '@angular/router'
import { VideoViewsCounterComponent } from '../shared-video/video-views-counter.component'
import { DateToggleComponent } from '../shared-main/date/date-toggle.component'
import { LinkComponent } from '../shared-main/common/link.component'
import { ActorAvatarComponent } from '../shared-actor-image/actor-avatar.component'
import { VideoThumbnailComponent } from '../shared-thumbnail/video-thumbnail.component'
import { NgClass, NgIf, NgFor } from '@angular/common'
import { LinkComponent } from '../shared-main/common/link.component'
import { DateToggleComponent } from '../shared-main/date/date-toggle.component'
import { Video } from '../shared-main/video/video.model'
import { VideoService } from '../shared-main/video/video.service'
import { VideoThumbnailComponent } from '../shared-thumbnail/video-thumbnail.component'
import { VideoPlaylistService } from '../shared-video-playlist/video-playlist.service'
import { VideoViewsCounterComponent } from '../shared-video/video-views-counter.component'
import { VideoActionsDisplayType, VideoActionsDropdownComponent } from './video-actions-dropdown.component'
export type MiniatureDisplayOptions = {
date?: boolean
@ -89,9 +90,9 @@ export class VideoMiniatureComponent implements OnInit {
stats: false
}
@Input({ transform: numberAttribute }) actorImageSize = 40
@Input({ transform: numberAttribute }) actorImageSize = 34
@Input() displayAsRow = false
@Input({ transform: booleanAttribute }) displayAsRow = false
@Input() videoLinkType: LinkType = 'internal'
@ -304,7 +305,8 @@ export class VideoMiniatureComponent implements OnInit {
getClasses () {
return {
'display-as-row': this.displayAsRow
'display-as-row': this.displayAsRow,
'has-avatar': this.displayOptions.avatar
}
}

View File

@ -2,25 +2,9 @@
<div class="videos-header mb-4">
<div *ngIf="headerActions.length !== 0" class="action-block mt-3">
<ng-container *ngFor="let action of headerActions">
<a *ngIf="action.routerLink" class="ms-2" [routerLink]="action.routerLink" routerLinkActive="active">
<ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container>
</a>
<button *ngIf="!action.routerLink && !action.href && action.click" class="ms-2 button-unstyle" (click)="action.click($event)" (key.enter)="action.click($event)">
<ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container>
</button>
<a *ngIf="!action.routerLink && action.href && action.click" class="ms-2" (click)="action.click($event)" (key.enter)="action.click($event)" [href]="action.href">
<ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container>
</a>
<ng-template #actionContent let-action>
@if (action.justIcon) {
<my-button [icon]="action.iconName" [ngbTooltip]="action.label"></my-button>
} @else {
<my-button [icon]="action.iconName" [label]="action.label"></my-button>
}
</ng-template>
@if (action.routerLink) {
<my-button theme="secondary" [ptRouterLink]="action.routerLink" [icon]="action.iconName">{{ action.label }}</my-button>
}
</ng-container>
</div>
</div>

View File

@ -7,7 +7,6 @@ import {
Notifier,
PeerTubeRouterService,
ScreenService,
ServerService,
User,
UserService
} from '@app/core'
@ -33,10 +32,7 @@ const debugLogger = debug('peertube:videos:VideosListComponent')
export type HeaderAction = {
iconName: GlobalIconName
label: string
justIcon?: boolean
routerLink?: string
href?: string
click?: (e: Event) => void
}
enum GroupDate {
@ -62,6 +58,7 @@ enum GroupDate {
NgFor,
RouterLinkActive,
RouterLink,
ButtonComponent,
NgTemplateOutlet,
ButtonComponent,
VideoFiltersHeaderComponent,
@ -112,7 +109,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
date: true,
views: true,
by: true,
avatar: false,
avatar: true,
privacyLabel: true,
privacyText: false,
state: false,
@ -146,8 +143,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
private userService: UserService,
private route: ActivatedRoute,
private screenService: ScreenService,
private peertubeRouter: PeerTubeRouterService,
private serverService: ServerService
private peertubeRouter: PeerTubeRouterService
) {
}
@ -208,8 +204,8 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
if (changes['displayOptions'] || !this.displayOptions) {
this.displayOptions = {
...this.defaultDisplayOptions,
avatar: this.serverService.getHTMLConfig().client.videos.miniature.displayAuthorAvatar,
...changes['displayOptions']
...(changes['displayOptions']?.currentValue ?? {})
}
}

View File

@ -25,7 +25,7 @@
<span i18n *ngIf="isVideoPasswordProtected()" class="pt-badge badge-yellow">Password protected</span>
</div>
<span class="video-miniature-created-at-views">
<span class="date-and-views">
<my-date-toggle [date]="playlistElement.video.publishedAt"></my-date-toggle>
<span class="views" [title]="playlistElement.video.getExactNumberOfViews()">

View File

@ -146,7 +146,7 @@ my-video-thumbnail,
.video-info-owner,
.video-info-timestamp,
.video-miniature-created-at-views {
.date-and-views {
font-size: 14px;
}

View File

@ -312,3 +312,12 @@ my-global-icon[iconName=external-link] {
}
}
}
ngx-loading-bar {
.ngx-bar {
height: 4px !important;
border-radius: 0 !important;
border-start-end-radius: 6px !important;
border-end-end-radius: 6px !important;
}
}

View File

@ -2,9 +2,8 @@
@use '_mixins' as *;
@mixin miniature-name {
font-size: 1.1em;
transition: color 0.2s;
font-weight: $font-semibold;
font-weight: $font-bold;
color: pvar(--mainForegroundColor);
@include peertube-word-wrap(false);
@ -28,7 +27,7 @@
display: flex;
flex-direction: column;
position: relative;
border-radius: 3px;
border-radius: 4px;
width: 100%;
height: 100%;
overflow: hidden;
@ -94,7 +93,7 @@
@mixin static-thumbnail-overlay {
display: inline-block;
background-color: rgba(0, 0, 0, 0.7);
background-color: rgba(17, 17, 17, 0.8);
color: #fff;
}

View File

@ -84,18 +84,23 @@
line-height: $font-size + math.round(math.div($font-size, 2));
}
@mixin rounded-line-height-1-42 ($font-size) {
line-height: math.round($font-size * 1.4285714286);
}
@mixin peertube-input-text($width, $font-size: $form-input-font-size) {
font-size: $font-size;
padding: 3px 15px;
padding: 6px 20px;
line-height: 1.66;
width: $width;
max-width: $width;
color: pvar(--inputForegroundColor);
background-color: pvar(--inputBackgroundColor);
border: 1px solid pvar(--inputBorderColor);
border-radius: 3px;
border-radius: 4px;
@include rounded-line-height-1-5($font-size);
@include rounded-line-height-1-42($font-size);
&::placeholder {
color: pvar(--inputPlaceholderColor);

View File

@ -58,8 +58,6 @@ $footer-margin: 30px;
$separator-border-color: rgba(0, 0, 0, 0.1);
$video-miniature-margin-bottom: 15px;
$video-miniature-row-name-font-size: 1.3em;
$video-miniature-row-mobile-name-font-size: 14px;
@ -91,7 +89,7 @@ $sub-menu-margin-bottom-small-view: 10px;
$activated-action-button-color: #212529;
$focus-box-shadow-form: 0 0 0 .2rem;
$form-input-font-size: 15px;
$form-input-font-size: 14px;
$video-watch-info-margin-left: 44px;

View File

@ -1032,7 +1032,6 @@ client:
miniature:
# By default PeerTube client displays author username
prefer_author_display_name: false
display_author_avatar: false
resumable_upload:
# Max size of upload chunks, e.g. '90MB'

View File

@ -1042,7 +1042,6 @@ client:
miniature:
# By default PeerTube client displays author username
prefer_author_display_name: false
display_author_avatar: false
resumable_upload:
# Max size of upload chunks, e.g. '90MB'

View File

@ -39,7 +39,6 @@ export interface ServerConfig {
client: {
videos: {
miniature: {
displayAuthorAvatar: boolean
preferAuthorDisplayName: boolean
}
resumableUpload: {

View File

@ -48,7 +48,6 @@ function checkMissedConfig () {
'import.video_channel_synchronization.check_interval', 'import.video_channel_synchronization.videos_limit_per_synchronization',
'import.video_channel_synchronization.full_sync_videos_limit',
'auto_blacklist.videos.of_users.enabled', 'trending.videos.interval_days',
'client.videos.miniature.display_author_avatar',
'client.videos.miniature.prefer_author_display_name', 'client.menu.login.redirect_on_single_external_auth',
'defaults.publish.download_enabled', 'defaults.publish.comments_policy', 'defaults.publish.privacy', 'defaults.publish.licence',
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',

View File

@ -77,8 +77,7 @@ const CONFIG = {
CLIENT: {
VIDEOS: {
MINIATURE: {
get PREFER_AUTHOR_DISPLAY_NAME () { return config.get<boolean>('client.videos.miniature.prefer_author_display_name') },
get DISPLAY_AUTHOR_AVATAR () { return config.get<boolean>('client.videos.miniature.display_author_avatar') }
get PREFER_AUTHOR_DISPLAY_NAME () { return config.get<boolean>('client.videos.miniature.prefer_author_display_name') }
},
RESUMABLE_UPLOAD: {
get MAX_CHUNK_SIZE () { return parseBytes(config.get<number>('client.videos.resumable_upload.max_chunk_size') || 0) }

View File

@ -56,7 +56,6 @@ class ServerConfigManager {
client: {
videos: {
miniature: {
displayAuthorAvatar: CONFIG.CLIENT.VIDEOS.MINIATURE.DISPLAY_AUTHOR_AVATAR,
preferAuthorDisplayName: CONFIG.CLIENT.VIDEOS.MINIATURE.PREFER_AUTHOR_DISPLAY_NAME
},
resumableUpload: {