Redesign video miniature
This commit is contained in:
parent
364783e303
commit
c0b372d7a5
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -24,7 +24,7 @@ export class VideoUserSubscriptionsComponent implements DisableForReuseHook {
|
|||
actions = [
|
||||
{
|
||||
routerLink: '/my-library/subscriptions',
|
||||
label: $localize`Subscriptions`,
|
||||
label: $localize`Manage`,
|
||||
iconName: 'cog' as 'cog'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 ?? {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()">
|
||||
|
|
|
@ -146,7 +146,7 @@ my-video-thumbnail,
|
|||
|
||||
.video-info-owner,
|
||||
.video-info-timestamp,
|
||||
.video-miniature-created-at-views {
|
||||
.date-and-views {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -39,7 +39,6 @@ export interface ServerConfig {
|
|||
client: {
|
||||
videos: {
|
||||
miniature: {
|
||||
displayAuthorAvatar: boolean
|
||||
preferAuthorDisplayName: boolean
|
||||
}
|
||||
resumableUpload: {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in New Issue